mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-19 18:15:08 +00:00
65e7f37d36
File::booted() removed the bytes the moment a row was deleted. For a single file that is right. Two paths delete files inside a transaction, though, and both delete many at once: FolderService::delete() takes a folder's whole subtree, and DeletedAccountContent::cascadeDelete() takes everything an account uploaded. Anything that rolls either transaction back puts every row back while the bytes are already gone. A transaction exists to make a set of writes undoable, and removing the bytes was the one write in that set that nothing can undo. The account path is the sharper one: since content disposal is nested inside the caller's transaction, the write that fails need not be in this code at all. The two failure directions are not equal. Bytes gone with the rows restored leaves rows pointing at nothing and no way back. Rows gone with the bytes left leaves orphans on disk, which OrphanFileScanner already exists to find. Defer to the recoverable one. Three properties this relies on, all of them checked rather than assumed: without a pending transaction the callback runs immediately, so a single delete is unchanged; a savepoint committing inside a larger transaction does not fire it, which is exactly the account case; and the connection is the row's own rather than whichever is default. detachOnDelete stays inside the transaction — it repairs the version chain's pointers, which is database work that must roll back with everything else.