fix(files): review round 3, the server — a fetched locked re-read, a finish that follows its file, an eviction that counts again

- set_retention and delete_own re-read the folder under its lock and use that row (an unfetched ORM
  result never refreshes the object); a folder gone meanwhile is refused (409), and the route audits
  the retention actually replaced
- a finishing download is stamped only while it is still in the folder (and keep) it was read with:
  a move or a folder delete landing meanwhile sends the read round again, so no file is done with no
  expiry
- the eviction pass counts the disk before each candidate and skips one asked for since it began
- docs: source builds get their own volume-copy snippet
- race tests assert the lock wait; one built on a committing delete never raced
This commit is contained in:
2026-10-13 03:15:59 +02:00
parent 5bb101e493
commit c9baf68b16
6 changed files with 261 additions and 53 deletions
+16 -12
View File
@@ -421,18 +421,22 @@ def update_folder(
target_type="file_folder",
target_id=folder.id,
)
if "retention_days" in sent and body.retention_days != folder.retention_days:
before = folder.retention_days
folders.set_retention(db, folder, body.retention_days)
audit.record(
db,
user.id,
AuditAction.FILE_FOLDER_RETENTION,
target_type="file_folder",
target_id=folder.id,
before={"retention_days": before},
after={"retention_days": body.retention_days},
)
if "retention_days" in sent:
# The value replaced is the one read under the folder's lock, not the one read above it.
try:
before = folders.set_retention(db, folder, body.retention_days)
except folders.FolderError as e:
raise refused(409, e.reason) from e
if before != body.retention_days:
audit.record(
db,
user.id,
AuditAction.FILE_FOLDER_RETENTION,
target_type="file_folder",
target_id=folder.id,
before={"retention_days": before},
after={"retention_days": body.retention_days},
)
db.commit()
count, size = _folder_totals(db, folder.id)
return _serialize_folder(folder, count, size)