Skip to content

Commit 9678f2d

Browse files
authored
Merge pull request #2149 from millenomi/filemanager-getrelationship
Parity: FileManager.getRelationship(…)
2 parents c9334f3 + cc13a12 commit 9678f2d

File tree

4 files changed

+184
-47
lines changed

4 files changed

+184
-47
lines changed

Foundation/FileManager+POSIX.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,20 @@ extension FileManager {
463463

464464
return self.string(withFileSystemRepresentation: buf, length: Int(len))
465465
}
466+
467+
/* Returns a String with a canonicalized path for the element at the specified path. */
468+
internal func _canonicalizedPath(toFileAtPath path: String) throws -> String {
469+
let bufSize = Int(PATH_MAX + 1)
470+
var buf = [Int8](repeating: 0, count: bufSize)
471+
let done = try _fileSystemRepresentation(withPath: path) {
472+
realpath($0, &buf) != nil
473+
}
474+
if !done {
475+
throw _NSErrorWithErrno(errno, reading: true, path: path)
476+
}
477+
478+
return self.string(withFileSystemRepresentation: buf, length: strlen(buf))
479+
}
466480

467481
internal func _readFrom(fd: Int32, toBuffer buffer: UnsafeMutablePointer<UInt8>, length bytesToRead: Int, filename: String) throws -> Int {
468482
var bytesRead = 0

Foundation/FileManager+Win32.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ extension FileManager {
279279
}
280280

281281
internal func _destinationOfSymbolicLink(atPath path: String) throws -> String {
282+
return try _canonicalizedPath(toFileAtPath: path)
283+
}
284+
285+
internal func _canonicalizedPath(toFileAtPath path: String) throws -> String {
282286
var hFile: HANDLE = INVALID_HANDLE_VALUE
283287
path.withCString(encodedAs: UTF16.self) { link in
284288
hFile = CreateFileW(link, GENERIC_READ, DWORD(FILE_SHARE_WRITE), nil, DWORD(OPEN_EXISTING), DWORD(FILE_FLAG_BACKUP_SEMANTICS), nil)

Foundation/FileManager.swift

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,68 @@ open class FileManager : NSObject {
165165
/* 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'.
166166
*/
167167
open func getRelationship(_ outRelationship: UnsafeMutablePointer<URLRelationship>, ofDirectoryAt directoryURL: URL, toItemAt otherURL: URL) throws {
168-
NSUnimplemented()
168+
let from = try _canonicalizedPath(toFileAtPath: directoryURL.path)
169+
let to = try _canonicalizedPath(toFileAtPath: otherURL.path)
170+
171+
if from == to {
172+
outRelationship.pointee = .same
173+
} else if to.hasPrefix(from) && to.count > from.count + 1 /* the contained file's canonicalized path must contain at least one path separator and one filename component */ {
174+
let character = to[to.index(to.startIndex, offsetBy: from.length)]
175+
if character == "/" || character == "\\" {
176+
outRelationship.pointee = .contains
177+
} else {
178+
outRelationship.pointee = .other
179+
}
180+
} else {
181+
outRelationship.pointee = .other
182+
}
169183
}
170184

171185
/* Similar to -[NSFileManager getRelationship:ofDirectoryAtURL:toItemAtURL:error:], except that the directory is instead defined by an NSSearchPathDirectory and NSSearchPathDomainMask. Pass 0 for domainMask to instruct the method to automatically choose the domain appropriate for 'url'. For example, to discover if a file is contained by a Trash directory, call [fileManager getRelationship:&result ofDirectory:NSTrashDirectory inDomain:0 toItemAtURL:url error:&error].
172186
*/
173187
open func getRelationship(_ outRelationship: UnsafeMutablePointer<URLRelationship>, of directory: SearchPathDirectory, in domainMask: SearchPathDomainMask, toItemAt url: URL) throws {
174-
NSUnimplemented()
188+
let actualMask: SearchPathDomainMask
189+
190+
if domainMask.isEmpty {
191+
switch directory {
192+
case .applicationDirectory: fallthrough
193+
case .demoApplicationDirectory: fallthrough
194+
case .developerApplicationDirectory: fallthrough
195+
case .adminApplicationDirectory: fallthrough
196+
case .developerDirectory: fallthrough
197+
case .userDirectory: fallthrough
198+
case .documentationDirectory:
199+
actualMask = .localDomainMask
200+
201+
case .libraryDirectory: fallthrough
202+
case .autosavedInformationDirectory: fallthrough
203+
case .documentDirectory: fallthrough
204+
case .desktopDirectory: fallthrough
205+
case .cachesDirectory: fallthrough
206+
case .applicationSupportDirectory: fallthrough
207+
case .downloadsDirectory: fallthrough
208+
case .inputMethodsDirectory: fallthrough
209+
case .moviesDirectory: fallthrough
210+
case .musicDirectory: fallthrough
211+
case .picturesDirectory: fallthrough
212+
case .sharedPublicDirectory: fallthrough
213+
case .preferencePanesDirectory: fallthrough
214+
case .applicationScriptsDirectory: fallthrough
215+
case .itemReplacementDirectory: fallthrough
216+
case .trashDirectory:
217+
actualMask = .userDomainMask
218+
219+
case .coreServiceDirectory: fallthrough
220+
case .printerDescriptionDirectory: fallthrough
221+
case .allApplicationsDirectory: fallthrough
222+
case .allLibrariesDirectory:
223+
actualMask = .systemDomainMask
224+
}
225+
} else {
226+
actualMask = domainMask
227+
}
228+
229+
try getRelationship(outRelationship, ofDirectoryAt: try self.url(for: directory, in: actualMask, appropriateFor: url, create: false), toItemAt: url)
175230
}
176231

177232
/* createDirectoryAtURL:withIntermediateDirectories:attributes:error: creates a directory at the specified URL. If you pass 'NO' for withIntermediateDirectories, the directory must not exist at the time this call is made. Passing 'YES' for withIntermediateDirectories will create any necessary intermediate directories. This method returns YES if all directories specified in 'url' were created and attributes were set. Directories are created with attributes specified by the dictionary passed to 'attributes'. If no dictionary is supplied, directories are created according to the umask of the process. This method returns NO if a failure occurs at any stage of the operation. If an error parameter was provided, a presentable NSError will be returned by reference.

TestFoundation/TestFileManager.swift

Lines changed: 109 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,51 +16,6 @@
1616
#endif
1717

1818
class TestFileManager : XCTestCase {
19-
20-
static var allTests: [(String, (TestFileManager) -> () throws -> Void)] {
21-
var tests: [(String, (TestFileManager) -> () throws -> Void)] = [
22-
("test_createDirectory", test_createDirectory ),
23-
("test_createFile", test_createFile ),
24-
("test_moveFile", test_moveFile),
25-
("test_fileSystemRepresentation", test_fileSystemRepresentation),
26-
("test_fileExists", test_fileExists),
27-
("test_isReadableFile", test_isReadableFile),
28-
("test_isWritableFile", test_isWritableFile),
29-
("test_isExecutableFile", test_isExecutableFile),
30-
("test_isDeletableFile", test_isDeletableFile),
31-
("test_fileAttributes", test_fileAttributes),
32-
("test_fileSystemAttributes", test_fileSystemAttributes),
33-
("test_setFileAttributes", test_setFileAttributes),
34-
("test_directoryEnumerator", test_directoryEnumerator),
35-
("test_pathEnumerator",test_pathEnumerator),
36-
("test_contentsOfDirectoryAtPath", test_contentsOfDirectoryAtPath),
37-
("test_subpathsOfDirectoryAtPath", test_subpathsOfDirectoryAtPath),
38-
("test_copyItemAtPathToPath", test_copyItemAtPathToPath),
39-
("test_linkItemAtPathToPath", test_linkItemAtPathToPath),
40-
("test_homedirectoryForUser", test_homedirectoryForUser),
41-
("test_temporaryDirectoryForUser", test_temporaryDirectoryForUser),
42-
("test_creatingDirectoryWithShortIntermediatePath", test_creatingDirectoryWithShortIntermediatePath),
43-
("test_mountedVolumeURLs", test_mountedVolumeURLs),
44-
("test_copyItemsPermissions", test_copyItemsPermissions),
45-
("test_emptyFilename", test_emptyFilename),
46-
]
47-
48-
#if !DEPLOYMENT_RUNTIME_OBJC && NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
49-
tests.append(contentsOf: [
50-
("test_xdgStopgapsCoverAllConstants", test_xdgStopgapsCoverAllConstants),
51-
("test_parseXDGConfiguration", test_parseXDGConfiguration),
52-
("test_xdgURLSelection", test_xdgURLSelection),
53-
])
54-
#endif
55-
56-
#if !DEPLOYMENT_RUNTIME_OBJC
57-
tests.append(contentsOf: [
58-
("test_fetchXDGPathsFromHelper", test_fetchXDGPathsFromHelper),
59-
])
60-
#endif
61-
62-
return tests
63-
}
6419

6520
func test_createDirectory() {
6621
let fm = FileManager.default
@@ -1437,4 +1392,113 @@ VIDEOS=StopgapVideos
14371392
// Not Implemented - XCTAssertNil(fm.componentsToDisplay(forPath: ""))
14381393
// Not Implemented - XCTAssertEqual(fm.displayName(atPath: ""), "")
14391394
}
1395+
1396+
func test_getRelationship() throws {
1397+
/* a/
1398+
a/b
1399+
a/bb
1400+
c -> symlink to a/b
1401+
d */
1402+
1403+
let a = writableTestDirectoryURL.appendingPathComponent("a")
1404+
let a_b = a.appendingPathComponent("b")
1405+
let a_bb = a.appendingPathComponent("bb")
1406+
let c = writableTestDirectoryURL.appendingPathComponent("c")
1407+
let a_b_d = a_b.appendingPathComponent("d")
1408+
1409+
let fm = FileManager.default
1410+
try fm.createDirectory(at: a, withIntermediateDirectories: true)
1411+
try fm.createDirectory(at: a_b, withIntermediateDirectories: true)
1412+
try Data().write(to: a_bb)
1413+
try Data().write(to: c)
1414+
try fm.createSymbolicLink(at: a_b_d, withDestinationURL: a)
1415+
1416+
var relationship: FileManager.URLRelationship = .other
1417+
1418+
try fm.getRelationship(&relationship, ofDirectoryAt: writableTestDirectoryURL, toItemAt: a)
1419+
XCTAssertEqual(relationship, .contains)
1420+
1421+
try fm.getRelationship(&relationship, ofDirectoryAt: a, toItemAt: a_b)
1422+
XCTAssertEqual(relationship, .contains)
1423+
1424+
// The path of one is a prefix to the other, but lacks the directory separator.
1425+
try fm.getRelationship(&relationship, ofDirectoryAt: a_b, toItemAt: a_bb)
1426+
XCTAssertEqual(relationship, .other)
1427+
1428+
try fm.getRelationship(&relationship, ofDirectoryAt: a_b, toItemAt: c)
1429+
XCTAssertEqual(relationship, .other)
1430+
1431+
try fm.getRelationship(&relationship, ofDirectoryAt: a_b_d, toItemAt: a)
1432+
XCTAssertEqual(relationship, .same)
1433+
}
1434+
1435+
// -----
1436+
1437+
var writableTestDirectoryURL: URL!
1438+
1439+
override func setUp() {
1440+
super.setUp()
1441+
1442+
let pid = ProcessInfo.processInfo.processIdentifier
1443+
writableTestDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("org.swift.TestFoundation.TestFileManager.\(pid)")
1444+
}
1445+
1446+
override func tearDown() {
1447+
if let directoryURL = writableTestDirectoryURL,
1448+
(try? FileManager.default.attributesOfItem(atPath: directoryURL.path)) != nil {
1449+
do {
1450+
try FileManager.default.removeItem(at: directoryURL)
1451+
} catch {
1452+
NSLog("Could not remove test directory at URL \(directoryURL): \(error)")
1453+
}
1454+
}
1455+
1456+
super.tearDown()
1457+
}
1458+
1459+
static var allTests: [(String, (TestFileManager) -> () throws -> Void)] {
1460+
var tests: [(String, (TestFileManager) -> () throws -> Void)] = [
1461+
("test_createDirectory", test_createDirectory ),
1462+
("test_createFile", test_createFile ),
1463+
("test_moveFile", test_moveFile),
1464+
("test_fileSystemRepresentation", test_fileSystemRepresentation),
1465+
("test_fileExists", test_fileExists),
1466+
("test_isReadableFile", test_isReadableFile),
1467+
("test_isWritableFile", test_isWritableFile),
1468+
("test_isExecutableFile", test_isExecutableFile),
1469+
("test_isDeletableFile", test_isDeletableFile),
1470+
("test_fileAttributes", test_fileAttributes),
1471+
("test_fileSystemAttributes", test_fileSystemAttributes),
1472+
("test_setFileAttributes", test_setFileAttributes),
1473+
("test_directoryEnumerator", test_directoryEnumerator),
1474+
("test_pathEnumerator",test_pathEnumerator),
1475+
("test_contentsOfDirectoryAtPath", test_contentsOfDirectoryAtPath),
1476+
("test_subpathsOfDirectoryAtPath", test_subpathsOfDirectoryAtPath),
1477+
("test_copyItemAtPathToPath", test_copyItemAtPathToPath),
1478+
("test_linkItemAtPathToPath", test_linkItemAtPathToPath),
1479+
("test_homedirectoryForUser", test_homedirectoryForUser),
1480+
("test_temporaryDirectoryForUser", test_temporaryDirectoryForUser),
1481+
("test_creatingDirectoryWithShortIntermediatePath", test_creatingDirectoryWithShortIntermediatePath),
1482+
("test_mountedVolumeURLs", test_mountedVolumeURLs),
1483+
("test_copyItemsPermissions", test_copyItemsPermissions),
1484+
("test_emptyFilename", test_emptyFilename),
1485+
("test_getRelationship", test_getRelationship),
1486+
]
1487+
1488+
#if !DEPLOYMENT_RUNTIME_OBJC && NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
1489+
tests.append(contentsOf: [
1490+
("test_xdgStopgapsCoverAllConstants", test_xdgStopgapsCoverAllConstants),
1491+
("test_parseXDGConfiguration", test_parseXDGConfiguration),
1492+
("test_xdgURLSelection", test_xdgURLSelection),
1493+
])
1494+
#endif
1495+
1496+
#if !DEPLOYMENT_RUNTIME_OBJC
1497+
tests.append(contentsOf: [
1498+
("test_fetchXDGPathsFromHelper", test_fetchXDGPathsFromHelper),
1499+
])
1500+
#endif
1501+
1502+
return tests
1503+
}
14401504
}

0 commit comments

Comments
 (0)