import anyio from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Count, OuterRef, Q, Sum from django.db.models.functions import Coalesce from django.http import Http404, HttpResponse, StreamingHttpResponse from django.shortcuts import redirect from django.urls import reverse from django.views.generic import TemplateView from rest_framework import mixins from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet from app.utils import StreamingZipFileResponse from .models import File, SharedUser, Torrent from .serializers import FileSerializer, TorrentSerializer from .utils import torrent_proceed, torrent_share class HomeView(LoginRequiredMixin, TemplateView): template_name = "torrent/home.html" def pping(request): return HttpResponse(str(dict(request.session))) async def download_file(request, file_id): user = await request.auser() qs = File.objects.filter( Q(torrent__user=user) | Q(torrent__shared_users=user) | Q(torrent__user__friends=user) | Q(torrent__shared_users__friends=user), torrent__transmission_data__progress__gte=100, pk=file_id, ).distinct() try: file = await qs.aget() except File.DoesNotExist: raise Http404() else: if int(request.GET.get("dl_hotfix", 0)) == 1: async def read_file(): async with await anyio.open_file(file.abs_pathname, "rb") as f: while chunk := await f.read(128 * 1024): yield chunk response = StreamingHttpResponse(read_file()) response["Content-Length"] = file.size response["Content-Type"] = "application/octet-stream" response["Content-Disposition"] = file.disposition return response else: response = HttpResponse() response["X-Accel-Redirect"] = file.accel_redirect response["Content-Type"] = file.mime_types response["Content-Disposition"] = file.disposition return response async def flux_file(request, file_id): # todo : version non sécurisé, voir pour ajouter un contrôle IP (par ex) qs = File.objects.filter(pk=file_id) try: file = await qs.aget() except File.DoesNotExist: raise Http404() else: response = HttpResponse() response["X-Accel-Redirect"] = file.accel_redirect response["X-Accel-Buffering"] = "no" response["Content-Type"] = file.mime_types response["Content-Disposition"] = file.disposition return response async def secured_flux_file(request, file_id): user = await request.auser() qs = File.objects.filter( Q(torrent__user=user) | Q(torrent__shared_users=user) | Q(torrent__user__friends=user) | Q(torrent__shared_users__friends=user), torrent__transmission_data__progress__gte=100, pk=file_id, ).distinct() try: file = await qs.aget() except File.DoesNotExist: raise Http404() else: response = HttpResponse() response["X-Accel-Redirect"] = file.accel_redirect response["X-Accel-Buffering"] = "no" response["Content-Type"] = file.mime_types response["Content-Disposition"] = file.disposition return response async def download_torrent(request, torrent_id): # py version user = await request.auser() qs = ( Torrent.objects.filter( Q(user=user) | Q(shared_users=user) | Q(user__friends=user) | Q(shared_users__friends=user), transmission_data__progress__gte=100, pk=torrent_id, ) .annotate(count_files=Count("files")) .distinct() ) torrent = await qs.aget() if await torrent.alen_files == 1: file = await torrent.files.afirst() return redirect(reverse("torrent:download_file", kwargs={"file_id": file.pk})) response = StreamingZipFileResponse( filename=f"{torrent.name}.zip", file_list=[ (file.abs_pathname, file.rel_name) async for file in torrent.files.all() ], is_async=True, ) return response class TorrentViewSet( mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet, ): queryset = Torrent.objects.all().annotate(count_files=Count("files")) serializer_class = TorrentSerializer def get_queryset(self): qs = super().get_queryset() qs.filter( Q(user=self.request.user) | Q(shared_users=self.request.user) | Q(user__friends=self.request.user) | Q(shared_users__friends=self.request.user) ) # Récupération des torrents de l'utilisateur et de ceux partagé à celui-ci (ordonné par ordre de partage ou par date d'ajout du torrent) user_id = self.request.query_params.get("user", None) if user_id: qs = qs.filter(Q(user_id=user_id) | Q(shared_users=user_id)) else: user_id = self.request.user.id sub = SharedUser.objects.filter( torrent_id=OuterRef("pk"), user_id=user_id ).values("date_created") qs = qs.annotate(last_date=Coalesce(sub, "date_created")).order_by("-last_date") search = self.request.query_params.get("search", None) if search: qs = qs.filter(name__icontains=search) return qs def create(self, request, *args, **kwargs): file = request.data["file"] file_mode = request.data.get("file_mode", "file_object") r = torrent_proceed(self.request.user, file, file_mode) if r["torrent"]: r["torrent"] = self.get_serializer_class()(r["torrent"]).data return Response(r) def perform_destroy(self, instance): instance: Torrent if instance.user == self.request.user: return super().perform_destroy(instance) else: if instance.shared_users.filter(id=self.request.user.id).exists(): instance.shared_users.remove(self.request.user) @action(methods=["post"], detail=True) def share(self, request, pk): user_id = self.request.data.get("user_id") torrent = self.get_object() is_share_success = torrent_share( torrent=torrent, current_user=self.request.user, target_user_id=user_id ) return Response({"success": is_share_success}) @action(methods=["get"], detail=False) def user_stats(self, request): Torrent.objects.filter(user=self.request.user).aggregate(total_size=Sum("size")) class FileViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet): queryset = File.objects.all() serializer_class = FileSerializer filterset_fields = ["torrent"] def get_queryset(self): qs = super().get_queryset() return qs