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:
@@ -71,3 +71,71 @@ def test_existing_jobs_are_placed_and_keep_their_expiry(db, make_user):
|
||||
}
|
||||
# alice: youtube, tiktok, upload, plex; bob: youtube.
|
||||
assert folders == 5
|
||||
|
||||
|
||||
def test_the_admins_retention_is_kept_and_the_downgrade_gives_the_asset_back_its_expiry(
|
||||
db, make_user
|
||||
):
|
||||
alice, bob = make_user().id, make_user().id
|
||||
cfg = Config("alembic.ini")
|
||||
command.downgrade(cfg, "0101_plex_text_plain")
|
||||
try:
|
||||
with engine.begin() as conn:
|
||||
conn.execute(
|
||||
text(
|
||||
"INSERT INTO system_config (key, value) VALUES ('download_retention_days', '7')"
|
||||
" ON CONFLICT (key) DO UPDATE SET value = '7'"
|
||||
)
|
||||
)
|
||||
yt = conn.execute(
|
||||
text(
|
||||
"INSERT INTO media_assets (source_kind, source_ref, format_sig, status,"
|
||||
" ref_count, expires_at) VALUES ('youtube', 'abcdefghijk', 'sig', 'ready', 2,"
|
||||
" '2030-01-02T00:00:00+00:00') RETURNING id"
|
||||
)
|
||||
).scalar_one()
|
||||
for user in (alice, bob):
|
||||
conn.execute(
|
||||
text(
|
||||
"INSERT INTO download_jobs (user_id, asset_id, source_kind, source_ref,"
|
||||
" profile_snapshot, status) VALUES (:u, :a, 'youtube', 'abcdefghijk',"
|
||||
" '{}', 'done')"
|
||||
),
|
||||
{"u": user, "a": yt},
|
||||
)
|
||||
command.upgrade(cfg, "head")
|
||||
with engine.connect() as conn:
|
||||
retention = conn.execute(
|
||||
text(
|
||||
"SELECT DISTINCT retention_days FROM file_folders WHERE source_key = 'youtube'"
|
||||
)
|
||||
).all()
|
||||
|
||||
def asset_expiry_after_downgrade(alice_at, bob_at):
|
||||
with engine.begin() as conn:
|
||||
for user, at in ((alice, alice_at), (bob, bob_at)):
|
||||
conn.execute(
|
||||
text(
|
||||
"UPDATE download_jobs SET expires_at = CAST(:at AS timestamptz)"
|
||||
" WHERE asset_id = :a AND user_id = :u"
|
||||
),
|
||||
{"at": at, "a": yt, "u": user},
|
||||
)
|
||||
command.downgrade(cfg, "0101_plex_text_plain")
|
||||
with engine.connect() as conn:
|
||||
got = conn.execute(
|
||||
text("SELECT expires_at::date FROM media_assets WHERE id = :a"), {"a": yt}
|
||||
).scalar()
|
||||
command.upgrade(cfg, "head")
|
||||
return str(got) if got else None
|
||||
|
||||
# The shared file goes when its last holder's copy would; one holder keeping it keeps it.
|
||||
latest = asset_expiry_after_downgrade(
|
||||
"2030-02-01T00:00:00+00:00", "2030-03-01T00:00:00+00:00"
|
||||
)
|
||||
kept_by_one = asset_expiry_after_downgrade("2030-02-01T00:00:00+00:00", None)
|
||||
finally:
|
||||
command.upgrade(cfg, "head")
|
||||
assert retention == [(7,)]
|
||||
assert latest == "2030-03-01"
|
||||
assert kept_by_one is None
|
||||
|
||||
Reference in New Issue
Block a user