fix(files): review round 1, the server — the tray's window, the GC's locks, a file's origin, the upgrade path

- The queue keeps a done job by when it became done (ready_at), not by its last write: renaming,
  keeping or moving an old file, or the GC's expiry warning, no longer brings it back as just finished.
- GC expiry and eviction re-read each file under the asset→job locks the user routes take, and commit
  one file at a time: a delete, a re-download or a keep made while the GC runs is honoured, never a
  second release that frees a file another user still holds.
- A file records the origin it was placed under (download_jobs.source_key, migration 0105): an edit
  whose parent expired still goes back to its folder, not to Uploads.
- A folder a file is being put in is held (FOR SHARE) until it lands; a delete of it waits and takes
  that file home too, instead of failing on the foreign key.
- The hash route reads at most two files at once (429 past that) and holds no connection meanwhile.
- 0104's backfill dates old files by their request date only: no notice prod wrote names its job.
- The old DOWNLOAD_ROOT still finds the files; a files root that is not a mounted volume stops the
  worker and is logged by the API; the volume-copy recipe looks the project-prefixed names up.
- Stale comments about the /downloads mount and the Downloads page.
This commit is contained in:
2026-10-13 00:14:09 +02:00
parent 96509fe356
commit 815083bcc4
29 changed files with 593 additions and 92 deletions
+22
View File
@@ -6,6 +6,7 @@ from datetime import date
from app.downloads.storage import (
MediaMeta,
download_filename,
files_root_problem,
rel_path,
safe_abs_path,
sanitize,
@@ -174,3 +175,24 @@ def test_sidecars_keep_the_full_basename_when_the_title_has_a_dot(tmp_path: Path
assert f"{stem}.nfo" in names, names
assert f"{stem}.jpg" in names, names
assert "Chan_-_2026-08-12_-_Sonka_Hazilag.nfo" not in names # the old truncated orphan
def test_a_files_root_on_the_containers_own_disk_is_refused(tmp_path):
# The image's empty /files, left unmounted by an old compose file: every file would 404 and
# every download would go with the container.
assert "not a mounted volume" in files_root_problem(
str(tmp_path), system_root=str(tmp_path.parent)
)
assert "does not exist" in files_root_problem(str(tmp_path / "gone"))
# On another filesystem than the container's own, and writable: the volume.
assert files_root_problem(str(tmp_path), system_root="/proc") is None
def test_the_old_name_of_the_files_root_still_finds_the_files(monkeypatch):
from app.config import Settings
monkeypatch.delenv("FILES_ROOT", raising=False)
monkeypatch.setenv("DOWNLOAD_ROOT", "/downloads")
assert Settings().files_root == "/downloads"
monkeypatch.setenv("FILES_ROOT", "/files")
assert Settings().files_root == "/files"