Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import TakeFile
/// 3. the check passes again against what is set aside;
/// 4. only then it is removed. Any check that fails, or a Take started meanwhile, puts it back.
///
/// The Drive's Take is never written. Synchronous file I/O: call it off the main actor.
/// The Drive's Stems are never written. Once the Device's Take is gone, a stand-in it listed is unlisted in the Drive's
/// `Take.json` (#947), as a move or recovery does; that write is last, so a failure leaves a stale line and nothing to undo.
/// Synchronous file I/O: call it off the main actor.
public enum DeviceTakeRelease {
public enum Outcome: Equatable, Sendable {
/// Removed from the Device; the Drive holds everything it held.
Expand Down Expand Up @@ -99,10 +101,15 @@ public enum DeviceTakeRelease {
}
return second == nil ? .stopped : .kept(reasons)
}
let heldStandIns = (try? TakeMetadata.read(fromTakeFolder: setAside))?.standIns?.isEmpty == false
hook(.removing)
guard (try? fm.removeItem(at: setAside)) != nil else {
return .kept(["\(name) is on the Drive, but \(setAsideName) couldn't be removed from the Device."])
}
if heldStandIns, var onDrive = try? TakeMetadata.read(fromTakeFolder: driveTake), onDrive.standIns != nil {
onDrive.standIns = nil
try? onDrive.write(to: driveTake)
}
return .released
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,36 @@ struct DeviceTakeReleaseTests {
#expect(try driveBytes() == before, "the Drive's Take is byte for byte as it was")
}

@Test("A kept stand-in Take deleted from the Device is no longer listed on the Drive, and its Stems are untouched (#947)")
func releasedStandInIsUnlisted() throws {
defer { try? FileManager.default.removeItem(at: root) }
try makeKeptStandIn()
let stem = try Data(contentsOf: driveTake.appending(path: Self.channel(1).stemFile))
var expected = try TakeMetadata.read(fromTakeFolder: driveTake)
expected.standIns = nil

#expect(run() == .released)
#expect(try TakeMetadata.read(fromTakeFolder: driveTake) == expected, "only the stand-in listing changed")
#expect(!FileManager.default.fileExists(atPath: deviceTake.path))
#expect(try TakeMetadata.read(fromTakeFolder: driveTake).standIns == nil, "the Drive names a stand-in only while the Device holds it")
#expect(try Data(contentsOf: driveTake.appending(path: Self.channel(1).stemFile)) == stem)
}

@Test("A kept stand-in Take put back because the Drive changed meanwhile stays listed on the Drive (#947)")
func putBackStandInStaysListed() throws {
defer { try? FileManager.default.removeItem(at: root) }
try makeKeptStandIn()
let driveStem = driveTake.appending(path: Self.channel(1).stemFile)

let outcome = run(at: { step in
if step == .setAside { try? writeStem(driveStem, frames: 4_800, channel: 1, differs: 100..<200) }
})

if case .kept = outcome {} else { Issue.record("expected kept, got \(outcome)") }
#expect(FileManager.default.fileExists(atPath: deviceTake.path))
#expect(try TakeMetadata.read(fromTakeFolder: driveTake).standIns == [.init(start: 0, end: 3_000)])
}

@Test("A Drive that differs keeps the Device's Take, and says why")
func differingDrive() throws {
defer { try? FileManager.default.removeItem(at: root) }
Expand Down Expand Up @@ -181,8 +211,6 @@ struct DeviceTakeReleaseTests {
#expect(results.map(\.take) == [1])
#expect(results.first?.outcome == .moved, "the Drive holds the stand-in's audio: the delete the operator asked for is finished")
#expect(try FileManager.default.contentsOfDirectory(atPath: deviceShow.path).isEmpty, "nothing is left on the Device, and Take 01 is not put back")
// A normal Release (no crash) never writes the Drive, so there the listing stays; only recovery clears it. The listing is
// read nowhere that matters once the Device's stand-in is gone, so this is pinned as it is, not as a promise.
#expect(try TakeMetadata.read(fromTakeFolder: driveTake).standIns == nil, "and no stand-in is listed on the Drive any more")
#expect(FileManager.default.fileExists(atPath: driveTake.appending(path: Self.channel(1).stemFile).path))
}
Expand Down
Loading