Repository navigation
fix(ios): preserve an active scan archive when starting over #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: codex/candidate-20261003
Are you sure you want to change the base?
Changes from all commits
ca98508
c813182
4a24cd5
6ec4016
2d39637
a7c2a64
a76ba4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "autoReview": ["open", "push"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,13 @@ import Foundation | |
| /// can exist, and only those are deleted, whenever the deletion gets to run. Listing at deletion | ||
| /// time instead let a cleanup that ran late delete a scan started after it: store A is made, | ||
| /// Start over makes B, and A's cleanup then found B in the listing and deleted the scan in use. | ||
| /// | ||
| /// A folder whose bundle is still being built has no `scan.zip` yet, so it is listed as never | ||
| /// packaged. The packet is assembled in the folder first and zipped last, and Start over doesn't | ||
| /// wait for that write, so deleting on the listing alone took the folder from under it and the | ||
| /// scan never reached Saved scans. The app therefore deletes with `run(after:)`, waiting for the | ||
| /// bundle writes that were started before the listing; the recheck in `run` then keeps a folder | ||
| /// whose bundle landed meanwhile. | ||
| public struct ScanFolderCleanup: Sendable { | ||
| /// The bundle Share scan offers. A scan is completed when this file is a whole capture packet | ||
| /// (`PacketArchiveCheck`); the file being there is not enough. | ||
|
|
@@ -132,7 +139,8 @@ public struct ScanFolderCleanup: Sendable { | |
| /// The deletion runs later than the listing, off the main actor, and a folder can change in | ||
| /// between: a bundle write can start in a folder listed as never packaged, or finish in one. | ||
| /// Each entry is read again and deleted only if it is still what it was when listed; one that | ||
| /// changed is left for the next cleanup. A change after this second read is not caught. | ||
| /// changed is left for the next cleanup. A change after this second read is not caught, so a | ||
| /// write still going when this runs can lose its folder: `run(after:)` waits for known writers. | ||
| public func run() -> [(url: URL, error: any Error)] { | ||
| let files = FileManager.default | ||
| return obsolete.compactMap { url in | ||
|
|
@@ -146,4 +154,14 @@ public struct ScanFolderCleanup: Sendable { | |
| } | ||
| } | ||
| } | ||
|
|
||
| /// `run()`, once `wait` returns. The app's `wait` is the scan bundle writes started before | ||
| /// this cleanup was listed (one chain: each write waits for the one before it), so a folder | ||
| /// listed while its packet was still being assembled is judged again only after that write | ||
| /// ends: a bundle that landed keeps the folder, and a write that failed before zipping leaves | ||
| /// it to be deleted. A `wait` that returns at once, as at launch with no write, is `run()`. | ||
| public func run(after wait: () async -> Void) async -> [(url: URL, error: any Error)] { | ||
| await wait() | ||
| return run() | ||
|
Comment on lines
+163
to
+165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Start over runs on a nearly full device and the snapshot contains both the active writer's folder and unrelated obsolete scans, this unconditional wait postpones every deletion until packet writing finishes. AGENTS.md reference: AGENTS.md:L16-L20 Useful? React with 👍 / 👎.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Disposition: accepted as a trade-off of this fix, recorded in the PR's Risk section, with no code change here. The cost is real: on a nearly full phone the save can fail for lack of space, where deleting unrelated old data first might have let it finish. The choice favors never deleting source data from under a write. An early split by entry type would depend on an undocumented ordering between writes and retention, and would still leave photo-only folders waiting, so this PR adds no second deletion mode without a demonstrated need. The existing error handling stays, and the app still promises no saved file until the zip is complete. Leaving this open for human review. |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When cleanup starts while two completed scans already exist and the previous scan is still assembling its packet, the snapshot keeps the two completed folders and marks the active folder obsolete. After the wait, that folder's new completed bundle no longer matches
listedAs, sorun()skips it without evicting the oldest completed folder. This leaves three completed archives on disk, and repeated rapid start-overs can leave more, until another cleanup happens;SavedScanCataloghides the extras because it still returns only two. Recalculate retention among the originally listed folders after the writer finishes while continuing to exclude folders created after the snapshot.AGENTS.md reference: AGENTS.md:L16-L20
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed. If two completed scans exist and the previous scan is still packing, the listing keeps the two and marks the packing folder obsolete. After the write lands,
run()skips that folder (its entry changed from.absentto.complete) and evicts nothing, so three archives stay on disk. The extra is bounded: the next store creation (the next Start over or launch) lists again and trims back to the two newest, and Saved scans lists only those two in the meantime. Fixing it means ranking completed scans again after the wait, including folders the listing kept, so it changes the listing-time retention rule this PR builds on. I have routed it to the PR's acceptance owner and left this thread open.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disposition: accepted as a trade-off of this fix, recorded in the PR's Risk section, with no code change here. The requirement for B44 is that no folder is deleted while its packet is being written, not that the phone holds exactly two archives at every moment. After a write completes during the wait, more completed archives can stay on disk until the next store is created; the next Start over or launch lists again and trims to the two newest. Saved scans shows only the newest two meanwhile. Correcting my earlier reply: I can't claim a fixed bound. The extra archives come from writes that complete during a wait, and nobody has measured their size. Ranking again after the wait, limited to the folders in the original listing, would be a separate retention-policy change, not part of this fix. Leaving this open for human review.