Redline: OneDrive folder transport as alternative to AirDrop (MMDB-2631) #23
@@ -189,16 +189,33 @@ public enum OneDriveLocator {
|
|||||||
fileManager: FileManager = .default
|
fileManager: FileManager = .default
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
let probeURL = folder.appendingPathComponent(".redline-probe-\(UUID().uuidString)")
|
let probeURL = folder.appendingPathComponent(".redline-probe-\(UUID().uuidString)")
|
||||||
|
// Belt-and-suspenders cleanup, unconditional: AtomicFile.write renames the temp
|
||||||
|
// file onto probeURL and THEN fsyncs the containing directory — if that last
|
||||||
|
// fsync throws, the probe file already exists on disk but the catch below
|
||||||
|
// returns false before ever reaching the explicit removeItem call. And if the
|
||||||
|
// explicit removeItem itself throws, this is the only retry it gets. Either
|
||||||
|
// way, never leave the probe file behind just because we're about to return.
|
||||||
|
defer {
|
||||||
|
if fileManager.fileExists(atPath: probeURL.path) {
|
||||||
|
try? fileManager.removeItem(at: probeURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try AtomicFile.write(Data(), to: probeURL)
|
try AtomicFile.write(Data(), to: probeURL)
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try fileManager.removeItem(at: probeURL)
|
try fileManager.removeItem(at: probeURL)
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
// Only true when the explicit removal above actually succeeded AND the file is
|
||||||
|
// confirmed gone — never trust a removeItem call that returned without throwing
|
||||||
|
// as proof of anything on a File Provider domain.
|
||||||
|
return !fileManager.fileExists(atPath: probeURL.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,49 @@ func probeWritableFalseForAPlainFilePath() throws {
|
|||||||
#expect(!OneDriveLocator.probeWritable(at: filePath))
|
#expect(!OneDriveLocator.probeWritable(at: filePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The write itself succeeds (a real probe file lands on disk via AtomicFile.write,
|
||||||
|
/// which never touches this injected FileManager — it uses raw POSIX calls), but the
|
||||||
|
/// FIRST call to `removeItem(at:)` throws, simulating a transient File Provider
|
||||||
|
/// removal failure. probeWritable's own defer-based cleanup must retry and succeed
|
||||||
|
/// (the second call through this same override falls through to `super`), so no
|
||||||
|
/// probe file is left behind even though the function correctly still reports false
|
||||||
|
/// (the removal it explicitly attempted did fail).
|
||||||
|
private final class ThrowOnceOnRemoveFileManager: FileManager, @unchecked Sendable {
|
||||||
|
private let lock = NSLock()
|
||||||
|
private var hasThrown = false
|
||||||
|
|
||||||
|
override func removeItem(at URL: URL) throws {
|
||||||
|
lock.lock()
|
||||||
|
let shouldThrow = !hasThrown
|
||||||
|
hasThrown = true
|
||||||
|
lock.unlock()
|
||||||
|
if shouldThrow {
|
||||||
|
throw NSError(domain: "ShotdeckCoreTests.ThrowOnceOnRemove", code: 1)
|
||||||
|
}
|
||||||
|
try super.removeItem(at: URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func probeWritableFalseAndLeavesNoProbeFileWhenRemoveItemThrowsOnce() throws {
|
||||||
|
// Directory fsync failure (the OTHER way probeWritable's cleanup can be needed) has
|
||||||
|
// no injectable seam: AtomicFile.write's directory fsync is a raw Darwin fsync(2)
|
||||||
|
// call on an already-open file descriptor, not parameterized by any FileManager or
|
||||||
|
// other dependency this test can substitute, and there is no portable way to make
|
||||||
|
// fsync(2) itself fail via chmod or other standard test techniques (fsync failures
|
||||||
|
// are OS/filesystem/hardware-level events). Covering the removeItem-throws path
|
||||||
|
// (below) is what this test does; the fsync-throws path is covered by code
|
||||||
|
// inspection only — the same `defer` block guards both.
|
||||||
|
let dir = try makeTransportTemporaryDirectory(prefix: "shotdeck-probe-remove-throws")
|
||||||
|
defer { try? FileManager.default.removeItem(at: dir) }
|
||||||
|
let injectedFileManager = ThrowOnceOnRemoveFileManager()
|
||||||
|
|
||||||
|
#expect(!OneDriveLocator.probeWritable(at: dir, fileManager: injectedFileManager))
|
||||||
|
|
||||||
|
let leftovers = try FileManager.default.contentsOfDirectory(atPath: dir.path)
|
||||||
|
#expect(leftovers.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
struct TransportDefaultsSuite {
|
struct TransportDefaultsSuite {
|
||||||
let name: String
|
let name: String
|
||||||
let defaults: UserDefaults
|
let defaults: UserDefaults
|
||||||
|
|||||||
Reference in New Issue
Block a user