Skip to content

Commit aa88f36

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

File tree

1 file changed

+102
-28
lines changed

1 file changed

+102
-28
lines changed

Foundation/FileManager.swift

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,46 +2377,123 @@ 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+
let dirPath = directory.absoluteString + "\\*"
2435+
let h: HANDLE = dirPath.withCString(encodedAs: UTF16.self) {
2436+
FindFirstFileW($0, &ffd)
2437+
}
2438+
guard h != INVALID_HANDLE_VALUE else { return nil }
2439+
defer { FindClose(h) }
2440+
2441+
var files: [URL] = []
2442+
repeat {
2443+
let fileArr = Array<WCHAR>(
2444+
UnsafeBufferPointer(start: &ffd.cFileName.0,
2445+
count: MemoryLayout.size(ofValue: ffd.cFileName)))
2446+
let file = String(decodingCString: fileArr, as: UTF16.self)
2447+
if file != "."
2448+
&& file != ".."
2449+
&& (!_options.contains(.skipsHiddenFiles)
2450+
|| (ffd.dwFileAttributes & DWORD(FILE_ATTRIBUTE_HIDDEN) == 0)) {
2451+
files.append(URL(fileURLWithPath: "\(directory.absoluteString)\\\(file)"))
2452+
}
2453+
} while(FindNextFileW(h, &ffd) != 0)
2454+
return files
2455+
}
2456+
while let url = _stack.popLast() {
2457+
if url.hasDirectoryPath && !_options.contains(.skipsSubdirectoryDescendants) {
2458+
guard let dirContents = contentsOfDir(directory: url)?.reversed() else {
2459+
if let handler = _errorHandler {
2460+
let keepGoing = handler(URL(fileURLWithPath: url.absoluteString),
2461+
_NSErrorWithWindowsError(GetLastError(), reading: true))
2462+
if !keepGoing { return nil }
2463+
}
2464+
continue
2465+
}
2466+
_stack.append(contentsOf: dirContents)
2467+
}
2468+
_current = url
2469+
return url
2470+
}
2471+
return nil
2472+
}
2473+
2474+
override var level: Int {
2475+
return _rootDepth - (_current?.pathComponents.count ?? _rootDepth)
2476+
}
2477+
2478+
override func skipDescendants() {
2479+
_options.insert(.skipsSubdirectoryDescendants)
24022480
}
2403-
}
24042481
#else
2405-
internal class NSURLDirectoryEnumerator : DirectoryEnumerator {
24062482
var _url : URL
24072483
var _options : FileManager.DirectoryEnumerationOptions
24082484
var _errorHandler : ((URL, Error) -> Bool)?
24092485
var _stream : UnsafeMutablePointer<FTS>? = nil
24102486
var _current : UnsafeMutablePointer<FTSENT>? = nil
24112487
var _rootError : Error? = nil
24122488
var _gotRoot : Bool = false
2413-
2489+
2490+
24142491
// See @escaping comments above.
24152492
init(url: URL, options: FileManager.DirectoryEnumerationOptions, errorHandler: (/* @escaping */ (URL, Error) -> Bool)?) {
24162493
_url = url
24172494
_options = options
24182495
_errorHandler = errorHandler
2419-
2496+
24202497
if FileManager.default.fileExists(atPath: _url.path) {
24212498
let fsRep = FileManager.default.fileSystemRepresentation(withPath: _url.path)
24222499
let ps = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: 2)
@@ -2430,15 +2507,15 @@ extension FileManager {
24302507
_rootError = _NSErrorWithErrno(ENOENT, reading: true, url: url)
24312508
}
24322509
}
2433-
2510+
24342511
deinit {
24352512
if let stream = _stream {
24362513
fts_close(stream)
24372514
}
24382515
}
2439-
2440-
override func nextObject() -> Any? {
24412516

2517+
2518+
override func nextObject() -> Any? {
24422519
func match(filename: String, to options: DirectoryEnumerationOptions, isDir: Bool) -> (Bool, Bool) {
24432520
var showFile = true
24442521
var skipDescendants = false
@@ -2459,10 +2536,10 @@ extension FileManager {
24592536

24602537

24612538
if let stream = _stream {
2462-
2539+
24632540
if !_gotRoot {
24642541
_gotRoot = true
2465-
2542+
24662543
// Skip the root.
24672544
_current = fts_read(stream)
24682545
}
@@ -2504,7 +2581,7 @@ extension FileManager {
25042581
_current = fts_read(stream)
25052582
}
25062583
// TODO: Error handling if fts_read fails.
2507-
2584+
25082585
} else if let error = _rootError {
25092586
// Was there an error opening the stream?
25102587
if let handler = _errorHandler {
@@ -2513,24 +2590,21 @@ extension FileManager {
25132590
}
25142591
return nil
25152592
}
2516-
2517-
override var directoryAttributes : [FileAttributeKey : Any]? {
2518-
return nil
2519-
}
2520-
2521-
override var fileAttributes: [FileAttributeKey : Any]? {
2522-
return nil
2523-
}
2524-
25252593
override var level: Int {
25262594
return Int(_current?.pointee.fts_level ?? 0)
25272595
}
2528-
2596+
25292597
override func skipDescendants() {
25302598
if let stream = _stream, let current = _current {
25312599
fts_set(stream, current, FTS_SKIP)
25322600
}
25332601
}
2534-
}
25352602
#endif
2603+
override var directoryAttributes : [FileAttributeKey : Any]? {
2604+
return nil
2605+
}
2606+
override var fileAttributes: [FileAttributeKey : Any]? {
2607+
return nil
2608+
}
2609+
}
25362610
}

0 commit comments

Comments
 (0)