import re from pathlib import Path from urllib.parse import urlparse from django.conf import settings from django.core import signing from django.db.models import Min from django.http import Http404, StreamingHttpResponse from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.text import get_valid_filename from rest_framework import mixins, status, viewsets from rest_framework.decorators import action from rest_framework.permissions import AllowAny, IsAuthenticatedOrReadOnly from rest_framework.response import Response from rest_framework.views import APIView from . import services from .downloads import reap_stale_downloads, start_download_task from .models import DownloadTask, MediaItem from .permissions import CanUpload, IsUploaderOrStaffOrReadOnly from .serializers import DownloadTaskSerializer, MediaItemSerializer LIST_ORDERINGS = {"name", "-name", "size", "-size", "created_at", "-created_at"} MD5_RE = re.compile(r"[0-9a-fA-F]{32}") class MediaItemViewSet( mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet, ): serializer_class = MediaItemSerializer permission_classes = [IsAuthenticatedOrReadOnly, IsUploaderOrStaffOrReadOnly] http_method_names = ["get", "post", "patch", "head", "options"] def get_queryset(self): queryset = MediaItem.objects.prefetch_related("locations").annotate( name=Min("locations__rel_path") ) if not self.request.user.is_authenticated: queryset = queryset.filter(hidden_from_guests=False) j_ids = self.request.query_params.get("j_ids", "").strip() if j_ids: numeric_ids = [] for value in j_ids.split(","): text = value.strip() number = text[2:] if text.upper().startswith("J-") else text if number.isdigit(): numeric_ids.append(int(number)) queryset = queryset.filter(pk__in=numeric_ids) search = self.request.query_params.get("search", "").strip() if search: queryset = queryset.filter(locations__rel_path__icontains=search) ratings = [ value for value in self.request.query_params.get("rating", "").split(",") if value in {"s", "q", "e"} ] if ratings: queryset = queryset.filter(rating__in=ratings) ordering = self.request.query_params.get("ordering", "").strip() queryset = queryset.order_by( ordering if ordering in LIST_ORDERINGS else "-created_at" ) return queryset.distinct() def get_object(self): """Resolve J-, a bare numeric id, or an MD5 fingerprint.""" value = str(self.kwargs.get("pk", "")).strip() queryset = self.get_queryset() if MD5_RE.fullmatch(value): obj = get_object_or_404(queryset, md5=value.lower()) else: numeric = value[2:] if value.upper().startswith("J-") else value if not numeric.isdigit(): raise Http404 obj = get_object_or_404(queryset, pk=int(numeric)) self.check_object_permissions(self.request, obj) return obj def _signed_media_item(self, request, action_name): """Signed media URLs let /