@@ -120,18 +120,320 @@ open class FileManager : NSObject {
120
120
return result
121
121
}
122
122
123
+ private enum _SearchPathDomain {
124
+ case system
125
+ case local
126
+ case network
127
+ case user
128
+
129
+ init ? ( _ domainMask: SearchPathDomainMask ) {
130
+ if domainMask == . systemDomainMask {
131
+ self = . system
132
+ }
133
+ if domainMask == . localDomainMask {
134
+ self = . local
135
+ }
136
+ if domainMask == . networkDomainMask {
137
+ self = . network
138
+ }
139
+ if domainMask == . userDomainMask {
140
+ self = . user
141
+ }
142
+
143
+ return nil
144
+ }
145
+ }
146
+
147
+ #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
148
+ private func darwinPathURLs( for domain: _SearchPathDomain , system: String ? , local: String ? , network: String ? , userHomeSubpath: String ? ) -> [ URL ] {
149
+ switch domain {
150
+ case . system:
151
+ guard let path = system else { return [ ] }
152
+ return [ URL ( fileURLWithPath: path, isDirectory: true ) ]
153
+ case . local:
154
+ guard let path = local else { return [ ] }
155
+ return [ URL ( fileURLWithPath: path, isDirectory: true ) ]
156
+ case . network:
157
+ guard let path = network else { return [ ] }
158
+ return [ URL ( fileURLWithPath: path, isDirectory: true ) ]
159
+ case . user:
160
+ guard let path = userHomeSubpath else { return [ ] }
161
+ return [ URL ( fileURLWithPath: path, isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
162
+ }
163
+ }
164
+
165
+ private func darwinPathURLs( for domain: _SearchPathDomain , all: String , useLocalDirectoryForSystem: Bool = false ) -> [ URL ] {
166
+ switch domain {
167
+ case . system:
168
+ return [ URL ( fileURLWithPath: useLocalDirectoryForSystem ? " / \( all) " : " /System/ \( all) " , isDirectory: true ) ]
169
+ case . local:
170
+ return [ URL ( fileURLWithPath: " / \( all) " , isDirectory: true ) ]
171
+ case . network:
172
+ return [ URL ( fileURLWithPath: " /Network/ \( all) " , isDirectory: true ) ]
173
+ case . user:
174
+ return [ URL ( fileURLWithPath: all, isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
175
+ }
176
+ }
177
+ #endif
178
+
179
+ #if os(Windows) // Non-Apple OSes that do not implement FHS/XDG are not currently supported.
180
+ @available ( * , unavailable, message: " Not implemented for this OS " )
181
+ open func urls( for directory: SearchPathDirectory , in domainMask: SearchPathDomainMask ) -> [ URL ] {
182
+ NSUnimplemented ( )
183
+ }
184
+ #else
185
+
123
186
/* -URLsForDirectory:inDomains: is analogous to NSSearchPathForDirectoriesInDomains(), but returns an array of NSURL instances for use with URL-taking APIs. This API is suitable when you need to search for a file or files which may live in one of a variety of locations in the domains specified.
124
187
*/
125
188
open func urls( for directory: SearchPathDirectory , in domainMask: SearchPathDomainMask ) -> [ URL ] {
126
- NSUnimplemented ( )
189
+
190
+ guard let domain = _SearchPathDomain ( domainMask) else {
191
+ fatalError ( " Values other than .systemDomainMask, .localDomainMask, .userDomainMask, .networkDomainMask are unsupported " )
192
+ }
193
+
194
+ // We are going to return appropriate paths on Darwin, but [] on platforms that do not have comparable locations.
195
+ // For example, on FHS/XDG systems, applications are not installed in a single path.
196
+
197
+ #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
198
+ // For Darwin:
199
+ switch directory {
200
+ case . applicationDirectory:
201
+ return darwinPathURLs ( for: domain, all: " Applications " , useLocalDirectoryForSystem: true )
202
+
203
+ case . demoApplicationDirectory:
204
+ return darwinPathURLs ( for: domain, all: " Demos " , useLocalDirectoryForSystem: true )
205
+
206
+ case . developerApplicationDirectory:
207
+ return darwinPathURLs ( for: domain, all: " Developer/Applications " , useLocalDirectoryForSystem: true )
208
+
209
+ case . adminApplicationDirectory:
210
+ return darwinPathURLs ( for: domain, all: " Applications/Utilities " , useLocalDirectoryForSystem: true )
211
+
212
+ case . libraryDirectory:
213
+ return darwinPathURLs ( for: domain, all: " Library " )
214
+
215
+ case . developerDirectory:
216
+ return darwinPathURLs ( for: domain, all: " Developer " , useLocalDirectoryForSystem: true )
217
+
218
+ case . documentationDirectory:
219
+ return darwinPathURLs ( for: domain, all: " Library/Documentation " )
220
+
221
+ case . coreServiceDirectory:
222
+ return darwinPathURLs ( for: domain, system: " /System/Library/CoreServices " , local: nil , network: nil , userHomeSubpath: nil )
223
+
224
+ case . autosavedInformationDirectory:
225
+ return darwinPathURLs ( for: domain, system: nil , local: nil , network: nil , userHomeSubpath: " Library/Autosave Information " )
226
+
227
+ case . inputMethodsDirectory:
228
+ return darwinPathURLs ( for: domain, all: " Library/Input Methods " )
229
+
230
+ case . preferencePanesDirectory:
231
+ return darwinPathURLs ( for: domain, system: " /System/Library/PreferencePanes " , local: " /Library/PreferencePanes " , network: nil , userHomeSubpath: " Library/PreferencePanes " )
232
+
233
+ case . applicationScriptsDirectory:
234
+ // Only the ObjC Foundation can know where this is.
235
+ return [ ]
236
+
237
+ case . allApplicationsDirectory:
238
+ var directories : [ URL ] = [ ]
239
+ directories. append ( contentsOf: darwinPathURLs ( for: domain, all: " Applications " , useLocalDirectoryForSystem: true ) )
240
+ directories. append ( contentsOf: darwinPathURLs ( for: domain, all: " Demos " , useLocalDirectoryForSystem: true ) )
241
+ directories. append ( contentsOf: darwinPathURLs ( for: domain, all: " Developer/Applications " , useLocalDirectoryForSystem: true ) )
242
+ directories. append ( contentsOf: darwinPathURLs ( for: domain, all: " Applications/Utilities " , useLocalDirectoryForSystem: true ) )
243
+ return directories
244
+
245
+ case . allLibrariesDirectory:
246
+ var directories : [ URL ] = [ ]
247
+ directories. append ( contentsOf: darwinPathURLs ( for: domain, all: " Library " ) )
248
+ directories. append ( contentsOf: darwinPathURLs ( for: domain, all: " Developer " ) )
249
+ return directories
250
+
251
+ case . printerDescriptionDirectory:
252
+ guard domain == . system else { return [ ] }
253
+ return [ URL ( fileURLWithPath: " /System/Library/Printers/PPD " , isDirectory: true ) ]
254
+
255
+ case . desktopDirectory:
256
+ guard domain == . user else { return [ ] }
257
+ return [ URL ( fileURLWithPath: " Desktop " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
258
+
259
+ case . documentDirectory:
260
+ guard domain == . user else { return [ ] }
261
+ return [ URL ( fileURLWithPath: " Documents " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
262
+
263
+ case . cachesDirectory:
264
+ guard domain == . user else { return [ ] }
265
+ return [ URL ( fileURLWithPath: " Library/Caches " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
266
+
267
+ case . applicationSupportDirectory:
268
+ guard domain == . user else { return [ ] }
269
+ return [ URL ( fileURLWithPath: " Library/Application Support " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
270
+
271
+ case . downloadsDirectory:
272
+ guard domain == . user else { return [ ] }
273
+ return [ URL ( fileURLWithPath: " Downloads " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
274
+
275
+ case . userDirectory:
276
+ return darwinPathURLs ( for: domain, system: nil , local: " /Users " , network: " /Network/Users " , userHomeSubpath: nil )
277
+
278
+ case . moviesDirectory:
279
+ guard domain == . user else { return [ ] }
280
+ return [ URL ( fileURLWithPath: " Movies " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
281
+
282
+ case . musicDirectory:
283
+ guard domain == . user else { return [ ] }
284
+ return [ URL ( fileURLWithPath: " Music " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
285
+
286
+ case . picturesDirectory:
287
+ guard domain == . user else { return [ ] }
288
+ return [ URL ( fileURLWithPath: " Pictures " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
289
+
290
+ case . sharedPublicDirectory:
291
+ guard domain == . user else { return [ ] }
292
+ return [ URL ( fileURLWithPath: " Public " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) ) ]
293
+
294
+ case . trashDirectory:
295
+ let userTrashURL = URL ( fileURLWithPath: " .Trash " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) )
296
+ if domain == . user || domain == . local {
297
+ return [ userTrashURL ]
298
+ } else {
299
+ return [ ]
300
+ }
301
+
302
+ case . itemReplacementDirectory:
303
+ // This directory is only returned by url(for:in:appropriateFor:create:)
304
+ return [ ]
305
+ }
306
+ #elseif !os(Windows)
307
+ // FHS/XDG-compliant OSes:
308
+ switch directory {
309
+ case . autosavedInformationDirectory:
310
+ let runtimePath = _SwiftValue. fetch ( nonOptional: _CFXDGCreateDataHomePath ( ) ) as! String
311
+ return [ URL ( fileURLWithPath: " Autosave Information " , isDirectory: true , relativeTo: URL ( fileURLWithPath: runtimePath, isDirectory: true ) ) ]
312
+
313
+ case . desktopDirectory:
314
+ guard domain == . user else { return [ ] }
315
+ return [ _XDGUserDirectory. desktop. url ]
316
+
317
+ case . documentDirectory:
318
+ guard domain == . user else { return [ ] }
319
+ return [ _XDGUserDirectory. documents. url ]
320
+
321
+ case . cachesDirectory:
322
+ guard domain == . user else { return [ ] }
323
+ let path = _SwiftValue. fetch ( nonOptional: _CFXDGCreateCacheDirectoryPath ( ) ) as! String
324
+ return [ URL ( fileURLWithPath: path, isDirectory: true ) ]
325
+
326
+ case . applicationSupportDirectory:
327
+ guard domain == . user else { return [ ] }
328
+ let path = _SwiftValue. fetch ( nonOptional: _CFXDGCreateDataHomePath ( ) ) as! String
329
+ return [ URL ( fileURLWithPath: path, isDirectory: true ) ]
330
+
331
+ case . downloadsDirectory:
332
+ guard domain == . user else { return [ ] }
333
+ return [ _XDGUserDirectory. download. url ]
334
+
335
+ case . userDirectory:
336
+ guard domain == . local else { return [ ] }
337
+ return [ URL ( fileURLWithPath: " /home " , isDirectory: true ) ]
338
+
339
+ case . moviesDirectory:
340
+ return [ ]
341
+
342
+ case . musicDirectory:
343
+ guard domain == . user else { return [ ] }
344
+ return [ _XDGUserDirectory. music. url ]
345
+
346
+ case . picturesDirectory:
347
+ guard domain == . user else { return [ ] }
348
+ return [ _XDGUserDirectory. pictures. url ]
349
+
350
+ case . sharedPublicDirectory:
351
+ guard domain == . user else { return [ ] }
352
+ return [ _XDGUserDirectory. publicShare. url ]
353
+
354
+ case . trashDirectory:
355
+ let userTrashURL = URL ( fileURLWithPath: " .Trash " , isDirectory: true , relativeTo: URL ( fileURLWithPath: NSHomeDirectory ( ) , isDirectory: true ) )
356
+ if domain == . user || domain == . local {
357
+ return [ userTrashURL ]
358
+ } else {
359
+ return [ ]
360
+ }
361
+
362
+ // None of these are supported outside of Darwin:
363
+ case . applicationDirectory:
364
+ fallthrough
365
+ case . demoApplicationDirectory:
366
+ fallthrough
367
+ case . developerApplicationDirectory:
368
+ fallthrough
369
+ case . adminApplicationDirectory:
370
+ fallthrough
371
+ case . libraryDirectory:
372
+ fallthrough
373
+ case . developerDirectory:
374
+ fallthrough
375
+ case . documentationDirectory:
376
+ fallthrough
377
+ case . coreServiceDirectory:
378
+ fallthrough
379
+ case . inputMethodsDirectory:
380
+ fallthrough
381
+ case . preferencePanesDirectory:
382
+ fallthrough
383
+ case . applicationScriptsDirectory:
384
+ fallthrough
385
+ case . allApplicationsDirectory:
386
+ fallthrough
387
+ case . allLibrariesDirectory:
388
+ fallthrough
389
+ case . printerDescriptionDirectory:
390
+ fallthrough
391
+ case . itemReplacementDirectory:
392
+ return [ ]
393
+ }
394
+ #endif
395
+
396
+ }
397
+ #endif
398
+
399
+ private enum URLForDirectoryError : Error {
400
+ case directoryUnknown
127
401
}
128
402
129
403
/* -URLForDirectory:inDomain:appropriateForURL:create:error: is a URL-based replacement for FSFindFolder(). It allows for the specification and (optional) creation of a specific directory for a particular purpose (e.g. the replacement of a particular item on disk, or a particular Library directory.
130
404
131
405
You may pass only one of the values from the NSSearchPathDomainMask enumeration, and you may not pass NSAllDomainsMask.
132
406
*/
133
407
open func url( for directory: SearchPathDirectory , in domain: SearchPathDomainMask , appropriateFor url: URL ? , create shouldCreate: Bool ) throws -> URL {
134
- NSUnimplemented ( )
408
+ let urls = self . urls ( for: directory, in: domain)
409
+ guard let url = urls. first else {
410
+ // On Apple OSes, this case returns nil without filling in the error parameter; Swift then synthesizes an error rather than trap.
411
+ // We simulate that behavior by throwing a private error.
412
+ throw URLForDirectoryError . directoryUnknown
413
+ }
414
+
415
+ if shouldCreate {
416
+ var attributes : [ FileAttributeKey : Any ] = [ : ]
417
+
418
+ switch _SearchPathDomain ( domain) {
419
+ case . some( . user) :
420
+ attributes [ . posixPermissions] = 0700
421
+
422
+ case . some( . system) :
423
+ attributes [ . posixPermissions] = 0755
424
+ attributes [ . ownerAccountID] = 0 // root
425
+ #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
426
+ attributes [ . ownerAccountID] = 80 // on Darwin, the admin group's fixed ID.
427
+ #endif
428
+
429
+ default :
430
+ break
431
+ }
432
+
433
+ try createDirectory ( at: url, withIntermediateDirectories: true , attributes: attributes)
434
+ }
435
+
436
+ return url
135
437
}
136
438
137
439
/* Sets 'outRelationship' to NSURLRelationshipContains if the directory at 'directoryURL' directly or indirectly contains the item at 'otherURL', meaning 'directoryURL' is found while enumerating parent URLs starting from 'otherURL'. Sets 'outRelationship' to NSURLRelationshipSame if 'directoryURL' and 'otherURL' locate the same item, meaning they have the same NSURLFileResourceIdentifierKey value. If 'directoryURL' is not a directory, or does not contain 'otherURL' and they do not locate the same file, then sets 'outRelationship' to NSURLRelationshipOther. If an error occurs, returns NO and sets 'error'.
0 commit comments