Async Download to Library with progress; Download to client

Backend:
- DownloadTask model + background thread runner: streams the file with
  progress (%, bytes, speed) and a cancel flag, then indexes it, names it
  J-<id>.<ext> and applies the e621 metadata
- DownloadTaskViewSet (create/retrieve/cancel) replaces the synchronous
  endpoint; the status footer's worker counts now reflect download jobs
- Client download proxy (/api/online/file/) streams an e621 original to
  the browser with Content-Disposition: attachment, restricted to the
  configured e621 CDN hosts so it cannot be used as an open proxy

Frontend:
- Online detail: progress bar with percentage, transferred size, speed
  and cancel while downloading; success links to the new J-ID
- New 'Download to client' button available to everyone (guests too)
This commit is contained in:
2026-09-17 12:06:15 -05:00
parent bf00cf36a2
commit 6962e483fc
11 changed files with 509 additions and 63 deletions
+27 -1
View File
@@ -4,7 +4,7 @@ from pathlib import Path
from django.core import signing
from rest_framework import serializers
from .models import MediaItem, MediaLocation, TempUpload
from .models import MediaItem, MediaLocation, TempUpload, DownloadTask
from .services import MEDIA_FILE_SALT, UPLOAD_FILE_SALT, VIDEO_EXTENSIONS
@@ -169,3 +169,29 @@ class TempUploadSerializer(serializers.ModelSerializer):
salt=MEDIA_FILE_SALT,
)
return f"/api/files/J-{item.id}/{action}/?sig={signature}"
class DownloadTaskSerializer(serializers.ModelSerializer):
task_id = serializers.UUIDField(source="id", read_only=True)
library_j_id = serializers.SerializerMethodField()
class Meta:
model = DownloadTask
fields = [
"task_id",
"post_id",
"filename",
"status",
"progress",
"downloaded",
"total",
"speed",
"error",
"library_j_id",
"created_at",
"updated_at",
]
read_only_fields = fields
def get_library_j_id(self, obj):
return f"J-{obj.library_item_id}" if obj.library_item_id else None