Skip to content

Commit f2d6ee1

Browse files
committed
Implement NSURLDirectoryEnumerator for Windows
1 parent 0a6f09b commit f2d6ee1

File tree

1 file changed

+106
-29
lines changed

1 file changed

+106
-29
lines changed

Foundation/FileManager.swift

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ open class FileManager : NSObject {
802802
}
803803

804804
#if os(Windows)
805-
private func joinPath(prefix: String, suffix: String) -> String {
805+
fileprivate func joinPath(prefix: String, suffix: String) -> String {
806806
var pszPath: PWSTR?
807807
_ = prefix.withCString(encodedAs: UTF16.self) { prefix in
808808
_ = suffix.withCString(encodedAs: UTF16.self) { suffix in
@@ -2377,46 +2377,126 @@ extension FileManager {
23772377
self.innerEnumerator = ie
23782378
}
23792379

2380-
@available(Windows, deprecated, message: "Not Yet Implemented")
23812380
override func nextObject() -> Any? {
23822381
let o = innerEnumerator.nextObject()
23832382
guard let url = o as? URL else {
23842383
return nil
23852384
}
23862385

23872386
#if os(Windows)
2388-
NSUnimplemented()
2387+
var relativePath = UnsafeMutableBufferPointer<WCHAR>.allocate(capacity: Int(MAX_PATH))
2388+
defer { relativePath.deallocate() }
2389+
func withURLCString<Result>(url: URL, _ f: (UnsafePointer<WCHAR>) -> Result?) -> Result? {
2390+
return url.withUnsafeFileSystemRepresentation { fsr in
2391+
(fsr.flatMap { String(utf8String: $0) })?.withCString(encodedAs: UTF16.self) { f($0) }
2392+
}
2393+
}
2394+
let result = withURLCString(url: baseURL) { pszFrom -> BOOL? in
2395+
withURLCString(url: url) { pszTo in
2396+
let fromAttrs = GetFileAttributesW(pszFrom)
2397+
let toAttrs = GetFileAttributesW(pszTo)
2398+
guard fromAttrs != INVALID_FILE_ATTRIBUTES, toAttrs != INVALID_FILE_ATTRIBUTES else {
2399+
return FALSE
2400+
}
2401+
return PathRelativePathToW(relativePath.baseAddress, pszFrom, fromAttrs, pszTo, toAttrs)
2402+
}
2403+
}
2404+
2405+
guard result == TRUE, let (path, _) = String.decodeCString(relativePath.baseAddress, as: UTF16.self) else {
2406+
return nil
2407+
}
23892408
#else
23902409
let path = url.path.replacingOccurrences(of: baseURL.path+"/", with: "")
2391-
_currentItemPath = path
2392-
return path
23932410
#endif
2411+
_currentItemPath = path
2412+
return _currentItemPath
23942413
}
23952414
}
23962415

2397-
#if os(Windows)
23982416
internal class NSURLDirectoryEnumerator : DirectoryEnumerator {
2399-
internal typealias ErrorHandler = /* @escaping */ (URL, Error) -> Bool
2417+
#if os(Windows)
2418+
var _options : FileManager.DirectoryEnumerationOptions
2419+
var _errorHandler : ((URL, Error) -> Bool)?
2420+
var _stack: [URL]
2421+
var _current: URL?
2422+
var _rootDepth : Int
24002423

2401-
init(url: URL, options: FileManager.DirectoryEnumerationOptions, errorHandler: ErrorHandler?) {
2424+
init(url: URL, options: FileManager.DirectoryEnumerationOptions, errorHandler: (/* @escaping */ (URL, Error) -> Bool)?) {
2425+
_options = options
2426+
_errorHandler = errorHandler
2427+
_stack = [url]
2428+
_rootDepth = url.pathComponents.count
2429+
}
2430+
2431+
override func nextObject() -> Any? {
2432+
func contentsOfDir(directory: URL) -> [URL]? {
2433+
var ffd: WIN32_FIND_DATAW = WIN32_FIND_DATAW()
2434+
guard let dirFSR = directory.withUnsafeFileSystemRepresentation({ $0.flatMap { fsr in String(utf8String: fsr) } })
2435+
else { return nil }
2436+
let dirPath = FileManager().joinPath(prefix: dirFSR, suffix: "*")
2437+
let h: HANDLE = dirPath.withCString(encodedAs: UTF16.self) {
2438+
FindFirstFileW($0, &ffd)
2439+
}
2440+
guard h != INVALID_HANDLE_VALUE else { return nil }
2441+
defer { FindClose(h) }
2442+
2443+
var files: [URL] = []
2444+
repeat {
2445+
let fileArr = Array<WCHAR>(
2446+
UnsafeBufferPointer(start: &ffd.cFileName.0,
2447+
count: MemoryLayout.size(ofValue: ffd.cFileName)))
2448+
let file = String(decodingCString: fileArr, as: UTF16.self)
2449+
if file != "."
2450+
&& file != ".."
2451+
&& (!_options.contains(.skipsHiddenFiles)
2452+
|| (ffd.dwFileAttributes & DWORD(FILE_ATTRIBUTE_HIDDEN) == 0)) {
2453+
files.append(URL(fileURLWithPath: FileManager().joinPath(prefix: dirFSR, suffix: file)))
2454+
}
2455+
} while(FindNextFileW(h, &ffd) != 0)
2456+
return files
2457+
}
2458+
while let url = _stack.popLast() {
2459+
if url.hasDirectoryPath && !_options.contains(.skipsSubdirectoryDescendants) {
2460+
guard let dirContents = contentsOfDir(directory: url)?.reversed() else {
2461+
if let handler = _errorHandler {
2462+
let dirFSR = url.withUnsafeFileSystemRepresentation { $0.flatMap { fsr in String(utf8String: fsr) } }
2463+
let keepGoing = handler(URL(fileURLWithPath: dirFSR ?? ""),
2464+
_NSErrorWithWindowsError(GetLastError(), reading: true))
2465+
if !keepGoing { return nil }
2466+
}
2467+
continue
2468+
}
2469+
_stack.append(contentsOf: dirContents)
2470+
}
2471+
_current = url
2472+
return url
2473+
}
2474+
return nil
2475+
}
2476+
2477+
override var level: Int {
2478+
return _rootDepth - (_current?.pathComponents.count ?? _rootDepth)
2479+
}
2480+
2481+
override func skipDescendants() {
2482+
_options.insert(.skipsSubdirectoryDescendants)
24022483
}
2403-
}
24042484
#else
2405-
internal class NSURLDirectoryEnumerator : DirectoryEnumerator {
24062485
var _url : URL
24072486
var _options : FileManager.DirectoryEnumerationOptions
24082487
var _errorHandler : ((URL, Error) -> Bool)?
24092488
var _stream : UnsafeMutablePointer<FTS>? = nil
24102489
var _current : UnsafeMutablePointer<FTSENT>? = nil
24112490
var _rootError : Error? = nil
24122491
var _gotRoot : Bool = false
2413-
2492+
2493+
24142494
// See @escaping comments above.
24152495
init(url: URL, options: FileManager.DirectoryEnumerationOptions, errorHandler: (/* @escaping */ (URL, Error) -> Bool)?) {
24162496
_url = url
24172497
_options = options
24182498
_errorHandler = errorHandler
2419-
2499+
24202500
if FileManager.default.fileExists(atPath: _url.path) {
24212501
let fsRep = FileManager.default.fileSystemRepresentation(withPath: _url.path)
24222502
let ps = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: 2)
@@ -2430,15 +2510,15 @@ extension FileManager {
24302510
_rootError = _NSErrorWithErrno(ENOENT, reading: true, url: url)
24312511
}
24322512
}
2433-
2513+
24342514
deinit {
24352515
if let stream = _stream {
24362516
fts_close(stream)
24372517
}
24382518
}
2439-
2440-
override func nextObject() -> Any? {
24412519

2520+
2521+
override func nextObject() -> Any? {
24422522
func match(filename: String, to options: DirectoryEnumerationOptions, isDir: Bool) -> (Bool, Bool) {
24432523
var showFile = true
24442524
var skipDescendants = false
@@ -2459,10 +2539,10 @@ extension FileManager {
24592539

24602540

24612541
if let stream = _stream {
2462-
2542+
24632543
if !_gotRoot {
24642544
_gotRoot = true
2465-
2545+
24662546
// Skip the root.
24672547
_current = fts_read(stream)
24682548
}
@@ -2504,7 +2584,7 @@ extension FileManager {
25042584
_current = fts_read(stream)
25052585
}
25062586
// TODO: Error handling if fts_read fails.
2507-
2587+
25082588
} else if let error = _rootError {
25092589
// Was there an error opening the stream?
25102590
if let handler = _errorHandler {
@@ -2513,24 +2593,21 @@ extension FileManager {
25132593
}
25142594
return nil
25152595
}
2516-
2517-
override var directoryAttributes : [FileAttributeKey : Any]? {
2518-
return nil
2519-
}
2520-
2521-
override var fileAttributes: [FileAttributeKey : Any]? {
2522-
return nil
2523-
}
2524-
25252596
override var level: Int {
25262597
return Int(_current?.pointee.fts_level ?? 0)
25272598
}
2528-
2599+
25292600
override func skipDescendants() {
25302601
if let stream = _stream, let current = _current {
25312602
fts_set(stream, current, FTS_SKIP)
25322603
}
25332604
}
2534-
}
25352605
#endif
2606+
override var directoryAttributes : [FileAttributeKey : Any]? {
2607+
return nil
2608+
}
2609+
override var fileAttributes: [FileAttributeKey : Any]? {
2610+
return nil
2611+
}
2612+
}
25362613
}

0 commit comments

Comments
 (0)