diff --git a/apinotes/Foundation.apinotes b/apinotes/Foundation.apinotes index 34b9729083329..63893f2f70aa7 100644 --- a/apinotes/Foundation.apinotes +++ b/apinotes/Foundation.apinotes @@ -659,6 +659,9 @@ Classes: - Selector: 'URLForPublishingUbiquitousItemAtURL:expirationDate:error:' SwiftName: url(forPublishingUbiquitousItemAt:expiration:) MethodKind: Instance + - Selector: 'enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:' + MethodKind: Instance + Availability: nonswift - Name: NSFileVersion Methods: - Selector: 'versionOfItemAtURL:forPersistentIdentifier:' diff --git a/stdlib/public/SDK/Foundation/FileManager.swift b/stdlib/public/SDK/Foundation/FileManager.swift index d6d906a8f13b5..da055f226e6c2 100644 --- a/stdlib/public/SDK/Foundation/FileManager.swift +++ b/stdlib/public/SDK/Foundation/FileManager.swift @@ -21,6 +21,13 @@ internal func NS_Swift_NSFileManager_replaceItemAtURL_withItemAtURL_backupItemNa _ options: FileManager.ItemReplacementOptions, _ error: NSErrorPointer) -> NSURL? +@_silgen_name("NS_Swift_NSFileManager_enumeratorAt_includingPropertiesForKeys_options_errorHandler") +internal func NS_Swift_NSFileManager_enumeratorAt_includingPropertiesForKeys_options_errorHandler( + _ self_ : AnyObject, + _ url: AnyObject, + _ keys: NSArray?, + _ options: FileManager.DirectoryEnumerationOptions, + _ errorHandler: @escaping @convention(block) (NSURL, NSError) -> Bool) -> FileManager.DirectoryEnumerator? extension FileManager { /* @@ -45,4 +52,16 @@ extension FileManager { } throw error! } + + @available(OSX 10.6, iOS 4.0, *) + public func enumerator(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options mask: FileManager.DirectoryEnumerationOptions = [], errorHandler handler: ((URL, Error) -> Bool)? = nil) -> FileManager.DirectoryEnumerator? { + return NS_Swift_NSFileManager_enumeratorAt_includingPropertiesForKeys_options_errorHandler(self, url as NSURL, keys as NSArray?, mask, { (url, error) in + var errorResult = true; + if let h = handler { + errorResult = h(url as URL, error) + } + return errorResult + }) + } + } diff --git a/stdlib/public/SDK/Foundation/FileManagerThunks.m b/stdlib/public/SDK/Foundation/FileManagerThunks.m index bc200ac9b643c..cd98f008e525b 100644 --- a/stdlib/public/SDK/Foundation/FileManagerThunks.m +++ b/stdlib/public/SDK/Foundation/FileManagerThunks.m @@ -33,3 +33,31 @@ [backupItemName release]; return success ? [result retain] : nil; } + +extern /*"C"*/ NS_RETURNS_RETAINED id +NS_Swift_NSFileManager_enumeratorAt_includingPropertiesForKeys_options_errorHandler( + NSFileManager *NS_RELEASES_ARGUMENT _Nonnull self_, + NSURL *NS_RELEASES_ARGUMENT _Nonnull url, + NSArray *NS_RELEASES_ARGUMENT _Nullable keys, + NSUInteger options, + BOOL (^_Nonnull errorHandler)(NSURL * _Nonnull url, NSError * _Nonnull error) ) { + + NSDirectoryEnumerator *result = [self_ enumeratorAtURL:url + includingPropertiesForKeys:keys + options:(NSDirectoryEnumerationOptions)options + errorHandler:^(NSURL * url, NSError *error) { + + NSURL *realURL = url ?: error.userInfo[NSURLErrorKey]; + if (!realURL) { + NSString *path = error.userInfo[NSFilePathErrorKey]; + realURL = [NSURL fileURLWithPath:path]; + } + return errorHandler(realURL, error); + + }]; + [self_ release]; + [url release]; + [keys release]; + [errorHandler release]; + return [result retain]; +} diff --git a/test/stdlib/TestFileManager.swift b/test/stdlib/TestFileManager.swift index b01c09d6a9ba7..6e6dece01c597 100644 --- a/test/stdlib/TestFileManager.swift +++ b/test/stdlib/TestFileManager.swift @@ -72,12 +72,68 @@ class TestFileManager : TestFileManagerSuper { expectTrue(threw, "Should have thrown") } + + func testDirectoryEnumerator_error() { + let fm = FileManager.default + let nonexistantURL = URL(fileURLWithPath: "\(NSTemporaryDirectory())/nonexistant") + + var invoked = false + let e = fm.enumerator(at: nonexistantURL, includingPropertiesForKeys: []) { (url, err) in + invoked = true + expectEqual(nonexistantURL, url) + expectEqual((err as NSError).code, NSFileReadNoSuchFileError) + return true + } + + let url = e?.nextObject() + expectTrue(invoked) + expectTrue(url == nil) + + } + + func testDirectoryEnumerator_error_noHandler() { + let fm = FileManager.default + let nonexistantURL = URL(fileURLWithPath: "\(NSTemporaryDirectory())/nonexistant") + + let e = fm.enumerator(at: nonexistantURL, includingPropertiesForKeys: []) + let url = e?.nextObject() + expectTrue(url == nil) + + } + + func testDirectoryEnumerator_simple() { + let fm = FileManager.default + let dirPath = (NSTemporaryDirectory() as NSString).appendingPathComponent(NSUUID().uuidString) + try! fm.createDirectory(atPath: dirPath, withIntermediateDirectories: true, attributes: nil) + defer { try! FileManager.default.removeItem(atPath: dirPath) } + + let item1 = URL(fileURLWithPath: "\(dirPath)/1", isDirectory: false) + let item2 = URL(fileURLWithPath: "\(dirPath)/2", isDirectory: false) + + try! Data().write(to: item1) + try! Data().write(to: item2) + + let e = fm.enumerator(at: URL(fileURLWithPath: dirPath, isDirectory: true), includingPropertiesForKeys: []) + let result1 = e?.nextObject() + let result2 = e?.nextObject() + let result3 = e?.nextObject() + + // Avoid potential symlink discrepancy between the result and the original URL + expectEqual((result1! as! URL).lastPathComponent, item1.lastPathComponent) + expectEqual((result2! as! URL).lastPathComponent, item2.lastPathComponent) + expectTrue(result3 == nil) + + } + } #if !FOUNDATION_XCTEST var FMTests = TestSuite("TestFileManager") FMTests.test("testReplaceItem") { TestFileManager().testReplaceItem() } FMTests.test("testReplaceItem_error") { TestFileManager().testReplaceItem_error() } +FMTests.test("testDirectoryEnumerator_error") { TestFileManager().testDirectoryEnumerator_error() } +FMTests.test("testDirectoryEnumerator_error_noHandler") { TestFileManager().testDirectoryEnumerator_error_noHandler() } +FMTests.test("testDirectoryEnumerator_simple") { TestFileManager().testDirectoryEnumerator_simple() } runAllTests() #endif