The Files library starts here. A user's every download, clip and upload now lands in a source folder made per origin (YouTube, Plex clips, uploads, one per other site), decided once in downloads/folders.py at every place a job is made. How long a file stays moves from the shared asset to the user's job: stamped from its folder when it is done, never for a file the user keeps. The GC warns each user once, then takes the expired file out of their library; the cache cap never evicts a kept file, and an evicted one leaves every holder's library. The media mount is /files now (FILES_ROOT, FILES_HOST_PATH, volume siftlode_files); docs/self-hosting.md says how to carry an old volume over. Migration 0102 places every existing job and carries each done file's expiry over from its asset. safe_abs_path takes a missing path and answers None, which drops one baselined type error.
64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
"""An editor clip is held to its owner's storage cap by its real size, as a download and a player
|
|
clip are: its file is new storage on the owner's footprint, and nothing else checked it.
|
|
|
|
Real ffmpeg — the size the check reads is the encoder's.
|
|
"""
|
|
|
|
import shutil
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
from app.config import settings
|
|
from app.downloads import errors as dlerrors
|
|
from app.downloads import pipeline, quota, service
|
|
from app.downloads import upload as uploadmod
|
|
from app.models import DownloadJob, DownloadQuota, MediaAsset
|
|
|
|
pytestmark = pytest.mark.skipif(shutil.which("ffmpeg") is None, reason="ffmpeg not available")
|
|
|
|
|
|
def test_an_editor_clip_larger_than_what_is_left_is_refused_and_not_placed(
|
|
db, make_user, tmp_path, monkeypatch
|
|
):
|
|
monkeypatch.setattr(settings, "files_root", str(tmp_path))
|
|
src = tmp_path / "in.mp4"
|
|
subprocess.run(
|
|
[
|
|
"ffmpeg", "-y", "-loglevel", "error",
|
|
"-f", "lavfi", "-i", "testsrc2=size=160x120:rate=10:duration=2",
|
|
"-c:v", "libx264", "-pix_fmt", "yuv420p", str(src),
|
|
],
|
|
check=True,
|
|
) # fmt: skip
|
|
u = make_user()
|
|
source = uploadmod.ingest_upload(
|
|
db, u.id, filename="clip.mp4", src_path=src, size=src.stat().st_size
|
|
)
|
|
job = service.enqueue_edit(db, u.id, source, {"trim": {"start_s": 0, "end_s": 1.5}})
|
|
asset = db.get(MediaAsset, job.asset_id)
|
|
job.status, asset.status = "running", "downloading" # the worker's claim
|
|
# Ten bytes left: no clip of a real video fits.
|
|
db.add(
|
|
DownloadQuota(
|
|
user_id=u.id,
|
|
max_bytes=quota.footprint(db, u.id) + 10,
|
|
max_concurrent=2,
|
|
max_jobs=100,
|
|
unlimited=False,
|
|
)
|
|
)
|
|
db.commit()
|
|
|
|
pipeline._process_edit(job.id, asset.id)
|
|
|
|
db.expire_all()
|
|
job, asset = db.get(DownloadJob, job.id), db.get(MediaAsset, job.asset_id)
|
|
assert (job.status, job.error_code, job.error) == (
|
|
"error",
|
|
"quota_exceeded",
|
|
dlerrors.CLIP_TOO_BIG_MESSAGE,
|
|
)
|
|
assert asset.rel_path is None
|
|
assert not list(tmp_path.glob(".edits/**/*.mp4"))
|