Skip to content

Commit e473ab7

Browse files
authored
FoundationEssentials: fix up linkOrCopyFile on Windows (#675)
Adjust the Windows path for copying (or linking) files. This adds the missing recursion in the copy (the documentation for `CopyFile2` was not clear that the recursive copy is not performed).
1 parent 21ed596 commit e473ab7

File tree

3 files changed

+34
-9
lines changed

3 files changed

+34
-9
lines changed

Sources/FoundationEssentials/FileManager/FileOperations.swift

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -799,20 +799,32 @@ enum _FileOperations {
799799
throw CocoaError.fileOperationError(.fileReadNoSuchFile, src, dst, variant: bCopyFile ? "Copy" : "Link")
800800
}
801801

802+
guard delegate.shouldPerformOnItemAtPath(src, to: dst) else { return }
803+
802804
try dst.withNTPathRepresentation { pwszDestination in
803-
if bCopyFile || faAttributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == FILE_ATTRIBUTE_REPARSE_POINT {
805+
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
806+
do {
807+
try fileManager.createDirectory(atPath: dst, withIntermediateDirectories: true)
808+
} catch {
809+
try delegate.throwIfNecessary(error, src, dst)
810+
}
811+
for item in _Win32DirectoryContentsSequence(path: src, appendSlashForDirectory: true) {
812+
try linkOrCopyFile(src.appendingPathComponent(item.fileName), dst: dst.appendingPathComponent(item.fileName), with: fileManager, delegate: delegate)
813+
}
814+
} else if bCopyFile || faAttributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == FILE_ATTRIBUTE_REPARSE_POINT {
804815
var ExtendedParameters: COPYFILE2_EXTENDED_PARAMETERS = .init()
805816
ExtendedParameters.dwSize = DWORD(MemoryLayout<COPYFILE2_EXTENDED_PARAMETERS>.size)
806-
ExtendedParameters.dwCopyFlags = COPY_FILE_COPY_SYMLINK | COPY_FILE_NO_BUFFERING | COPY_FILE_OPEN_AND_COPY_REPARSE_POINT
807-
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
808-
ExtendedParameters.dwCopyFlags |= COPY_FILE_DIRECTORY
809-
}
817+
ExtendedParameters.dwCopyFlags = COPY_FILE_FAIL_IF_EXISTS | COPY_FILE_COPY_SYMLINK | COPY_FILE_NO_BUFFERING | COPY_FILE_OPEN_AND_COPY_REPARSE_POINT
810818

811-
guard SUCCEEDED(CopyFile2(pwszSource, pwszDestination, &ExtendedParameters)) else {
812-
throw CocoaError.copyFileError(GetLastError(), src, dst)
819+
if FAILED(CopyFile2(pwszSource, pwszDestination, &ExtendedParameters)) {
820+
try delegate.throwIfNecessary(GetLastError(), src, dst)
813821
}
814822
} else {
815-
try fileManager.createSymbolicLink(atPath: dst, withDestinationPath: src)
823+
do {
824+
try fileManager.createSymbolicLink(atPath: dst, withDestinationPath: src)
825+
} catch {
826+
try delegate.throwIfNecessary(error, src, dst)
827+
}
816828
}
817829
}
818830
}

Sources/FoundationEssentials/WinSDK+Extensions.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
import WinSDK
1515

16+
package var COPY_FILE_FAIL_IF_EXISTS: DWORD {
17+
DWORD(WinSDK.COPY_FILE_FAIL_IF_EXISTS)
18+
}
19+
1620
package var COPY_FILE_COPY_SYMLINK: DWORD {
1721
DWORD(WinSDK.COPY_FILE_COPY_SYMLINK)
1822
}
@@ -249,6 +253,11 @@ package func PathAllocCombine(_ pszPathIn: String, _ pszMore: String, _ dwFlags:
249253
}
250254
}
251255

256+
@inline(__always)
257+
package func FAILED(_ hr: HRESULT) -> Bool {
258+
hr < 0
259+
}
260+
252261
@inline(__always)
253262
package func HRESULT_CODE(_ hr: HRESULT) -> DWORD {
254263
DWORD(hr) & 0xffff

Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,12 @@ final class FileManagerTests : XCTestCase {
399399
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["dir", "dir/bar", "dir/foo", "dir2", "dir2/bar", "dir2/foo", "other_file"])
400400
XCTAssertEqual($0.contents(atPath: "dir/foo"), data)
401401
XCTAssertEqual($0.contents(atPath: "dir2/foo"), data)
402+
#if os(Windows)
403+
XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("dir", "dir2"), .init("dir/bar", "dir2/bar"), .init("dir/foo", "dir2/foo")])
404+
#else
402405
XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("dir", "dir2"), .init("dir/foo", "dir2/foo"), .init("dir/bar", "dir2/bar")])
403-
406+
#endif
407+
404408
XCTAssertThrowsError(try $0.copyItem(atPath: "does_not_exist", toPath: "dir3")) {
405409
XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile)
406410
}

0 commit comments

Comments
 (0)