Skip to content

Commit 413b15c

Browse files
authored
FoundationEssentials: tweak removeFile(_:with:) on Windows (#659)
Ensure that we call the callbacks at the appropriate time. Change the indentation and encapsulating scope to avoid a double callback.
1 parent 5afcc12 commit 413b15c

File tree

2 files changed

+67
-37
lines changed

2 files changed

+67
-37
lines changed

Sources/FoundationEssentials/FileManager/FileOperations.swift

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,12 @@ enum _FileOperations {
317317
try path.withNTPathRepresentation {
318318
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
319319
guard GetFileAttributesExW($0, GetFileExInfoStandard, &faAttributes) else {
320+
// NOTE: in the case that the 'stat' failed, we want to ensure
321+
// that we query if the item should be removed. If the item
322+
// should not be removed, we can continue, else we should check
323+
// if we should proceed after the error.
324+
guard filemanager?._shouldRemoveItemAtPath(path) ?? true else { return }
325+
320326
let error = CocoaError.removeFileError(GetLastError(), path)
321327
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: path) ?? false) else {
322328
throw error
@@ -342,47 +348,55 @@ enum _FileOperations {
342348
return
343349
}
344350
}
345-
}
346351

347-
var stack = [path]
348-
while let directory = stack.popLast() {
349-
guard filemanager?._shouldRemoveItemAtPath(directory) ?? true else { continue }
350-
try directory.withNTPathRepresentation {
351-
if RemoveDirectoryW($0) { return }
352-
let dwError: DWORD = GetLastError()
353-
guard dwError == ERROR_DIR_NOT_EMPTY else {
354-
let error = CocoaError.removeFileError(dwError, directory)
355-
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: directory) ?? false) else {
356-
throw error
352+
var stack = [(path, false)]
353+
while let (directory, checked) = stack.popLast() {
354+
try directory.withNTPathRepresentation {
355+
let ntpath = String(decodingCString: $0, as: UTF16.self)
356+
357+
guard checked || filemanager?._shouldRemoveItemAtPath(ntpath) ?? true else { return }
358+
359+
if RemoveDirectoryW($0) { return }
360+
var dwError: DWORD = GetLastError()
361+
guard dwError == ERROR_DIR_NOT_EMPTY else {
362+
let error = CocoaError.removeFileError(dwError, directory)
363+
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: ntpath) ?? false) else {
364+
throw error
365+
}
366+
return
357367
}
358-
return
359-
}
360-
stack.append(directory)
368+
stack.append((directory, true))
361369

362-
for entry in _Win32DirectoryContentsSequence(path: directory, appendSlashForDirectory: false, prefix: [directory]) {
363-
try entry.fileNameWithPrefix.withNTPathRepresentation {
364-
if entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY,
365-
entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != FILE_ATTRIBUTE_REPARSE_POINT {
366-
stack.append(entry.fileNameWithPrefix)
367-
} else {
368-
if entry.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
369-
guard SetFileAttributesW($0, entry.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY) else {
370-
throw CocoaError.removeFileError(GetLastError(), path)
370+
for entry in _Win32DirectoryContentsSequence(path: directory, appendSlashForDirectory: false, prefix: [directory]) {
371+
try entry.fileNameWithPrefix.withNTPathRepresentation {
372+
let ntpath = String(decodingCString: $0, as: UTF16.self)
373+
374+
if entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY,
375+
entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != FILE_ATTRIBUTE_REPARSE_POINT {
376+
if filemanager?._shouldRemoveItemAtPath(ntpath) ?? true {
377+
stack.append((ntpath, true))
371378
}
372-
}
373-
guard filemanager?._shouldRemoveItemAtPath(entry.fileNameWithPrefix) ?? true else { return }
374-
if entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
375-
if !RemoveDirectoryW($0) {
376-
let error = CocoaError.removeFileError(GetLastError(), entry.fileName)
377-
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: entry.fileNameWithPrefix) ?? false) else {
378-
throw error
379+
} else {
380+
if entry.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
381+
guard SetFileAttributesW($0, entry.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY) else {
382+
throw CocoaError.removeFileError(GetLastError(), ntpath)
379383
}
380384
}
381-
} else {
382-
if !DeleteFileW($0) {
383-
let error = CocoaError.removeFileError(GetLastError(), entry.fileName)
384-
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: entry.fileNameWithPrefix) ?? false) else {
385-
throw error
385+
if entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY {
386+
guard filemanager?._shouldRemoveItemAtPath(ntpath) ?? true else { return }
387+
if !RemoveDirectoryW($0) {
388+
let error = CocoaError.removeFileError(GetLastError(), entry.fileName)
389+
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: entry.fileNameWithPrefix) ?? false) else {
390+
throw error
391+
}
392+
}
393+
} else {
394+
guard filemanager?._shouldRemoveItemAtPath(ntpath) ?? true else { return }
395+
if !DeleteFileW($0) {
396+
let error = CocoaError.removeFileError(GetLastError(), entry.fileName)
397+
guard (filemanager?._shouldProceedAfter(error: error, removingItemAtPath: entry.fileNameWithPrefix) ?? false) else {
398+
throw error
399+
}
386400
}
387401
}
388402
}

Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,8 @@ final class FileManagerTests : XCTestCase {
426426
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["dir", "dir/foo", "other"])
427427
XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar")])
428428
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, [])
429-
430-
let rootDir = $0.currentDirectoryPath
429+
430+
let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path
431431
try $0.removeItem(atPath: "dir")
432432
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["other"])
433433
XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo")])
@@ -443,6 +443,22 @@ final class FileManagerTests : XCTestCase {
443443
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, [.init("does_not_exist", code: .fileNoSuchFile)])
444444
}
445445

446+
try FileManagerPlayground {
447+
Directory("dir") {
448+
Directory("dir2") {
449+
"file"
450+
}
451+
}
452+
}.test(captureDelegateCalls: true) {
453+
let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path
454+
455+
XCTAssertTrue($0.delegateCaptures.isEmpty)
456+
try $0.removeItem(atPath: "dir")
457+
XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), [])
458+
XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("\(rootDir)/dir"), .init("\(rootDir)/dir/dir2"), .init("\(rootDir)/dir/dir2/file")])
459+
XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, [])
460+
}
461+
446462
#if canImport(Darwin)
447463
// not supported on linux as the test depends on FileManager.removeItem calling removefile(3)
448464
// not supported on older versions of Darwin where removefile would return ENOENT instead of ENAMETOOLONG

0 commit comments

Comments
 (0)