vpn integration
This commit is contained in:
+9
-4
@@ -2,13 +2,18 @@ from django.apps import AppConfig
|
||||
|
||||
|
||||
class TorrentConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'torrent'
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "torrent"
|
||||
|
||||
def ready(self):
|
||||
from django.db.models.signals import post_save, pre_delete, m2m_changed
|
||||
from .signals import on_post_save_torrent, on_pre_delete_torrent, on_shared_user_changed
|
||||
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
||||
|
||||
from .models import Torrent
|
||||
from .signals import (
|
||||
on_post_save_torrent,
|
||||
on_pre_delete_torrent,
|
||||
on_shared_user_changed,
|
||||
)
|
||||
|
||||
post_save.connect(on_post_save_torrent, sender=Torrent)
|
||||
pre_delete.connect(on_pre_delete_torrent, sender=Torrent)
|
||||
|
||||
+36
-32
@@ -1,11 +1,8 @@
|
||||
from django.db.models import Q
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||
from typing import Optional, Union
|
||||
import asyncio
|
||||
from django.db.models import Q
|
||||
|
||||
from user.models import User
|
||||
|
||||
from .models import Torrent
|
||||
|
||||
|
||||
@@ -14,17 +11,19 @@ class TorrentEventConsumer(AsyncJsonWebsocketConsumer):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.channel_groups = set()
|
||||
self.user: Optional[User] = None
|
||||
self.follow_user: Optional[User] = None
|
||||
self.user: User | None = None
|
||||
self.follow_user: User | None = None
|
||||
|
||||
async def connect(self):
|
||||
self.user = self.scope['user']
|
||||
self.user = self.scope["user"]
|
||||
if not self.user.is_authenticated:
|
||||
await self.close()
|
||||
return
|
||||
|
||||
self.follow_user = self.user
|
||||
await self.channel_layer.group_add(f"user_{self.follow_user.id}", self.channel_name)
|
||||
await self.channel_layer.group_add(
|
||||
f"user_{self.follow_user.id}", self.channel_name
|
||||
)
|
||||
|
||||
# user_id = int(self.scope['url_route']["kwargs"]["user_id"])
|
||||
# if user_id == self.user.id:
|
||||
@@ -53,47 +52,52 @@ class TorrentEventConsumer(AsyncJsonWebsocketConsumer):
|
||||
print("call websocket not supported", content)
|
||||
|
||||
async def change_follow_user(self, user_id):
|
||||
await self.channel_layer.group_discard(f"user_{self.follow_user.id}", self.channel_name)
|
||||
await self.channel_layer.group_discard(
|
||||
f"user_{self.follow_user.id}", self.channel_name
|
||||
)
|
||||
if user_id == self.user.id:
|
||||
self.follow_user = self.user
|
||||
await self.channel_layer.group_add(f"user_{self.follow_user.id}", self.channel_name)
|
||||
await self.channel_layer.group_add(
|
||||
f"user_{self.follow_user.id}", self.channel_name
|
||||
)
|
||||
return self.follow_user
|
||||
elif await self.user.friends.filter(id=user_id).aexists():
|
||||
self.follow_user = await User.objects.filter(id=user_id).aget()
|
||||
await self.channel_layer.group_add(f"user_{self.follow_user.id}", self.channel_name)
|
||||
await self.channel_layer.group_add(
|
||||
f"user_{self.follow_user.id}", self.channel_name
|
||||
)
|
||||
return self.follow_user
|
||||
else:
|
||||
return None
|
||||
|
||||
async def transmission_data_updated(self, datas):
|
||||
torrent_stats = datas["data"]
|
||||
qs = (Torrent.objects
|
||||
.filter(Q(user_id=self.follow_user.id) | Q(shared_users=self.follow_user.id))
|
||||
.values_list("id", flat=True).distinct())
|
||||
qs = (
|
||||
Torrent.objects.filter(
|
||||
Q(user_id=self.follow_user.id) | Q(shared_users=self.follow_user.id)
|
||||
)
|
||||
.values_list("id", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
torrent_ids = [i async for i in qs]
|
||||
|
||||
for hash_string, data in torrent_stats.items():
|
||||
if hash_string in torrent_ids:
|
||||
await self.send_json({
|
||||
"context": "transmission_data_updated",
|
||||
"data": data
|
||||
})
|
||||
await self.send_json(
|
||||
{"context": "transmission_data_updated", "data": data}
|
||||
)
|
||||
|
||||
async def add_torrent(self, data):
|
||||
await self.send_json({
|
||||
"context": "add_torrent",
|
||||
"torrent_id": data["data"]
|
||||
})
|
||||
await self.send_json({"context": "add_torrent", "torrent_id": data["data"]})
|
||||
|
||||
async def remove_torrent(self, data):
|
||||
await self.send_json({
|
||||
"context": "remove_torrent",
|
||||
"torrent_id": data["data"]
|
||||
})
|
||||
await self.send_json({"context": "remove_torrent", "torrent_id": data["data"]})
|
||||
|
||||
async def update_torrent(self, data):
|
||||
await self.send_json({
|
||||
"context": "update_torrent",
|
||||
"torrent_id": data["data"]["torrent_id"],
|
||||
"updated_fields": data["data"]["updated_fields"]
|
||||
})
|
||||
await self.send_json(
|
||||
{
|
||||
"context": "update_torrent",
|
||||
"torrent_id": data["data"]["torrent_id"],
|
||||
"updated_fields": data["data"]["updated_fields"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import close_old_connections
|
||||
|
||||
import time
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import close_old_connections
|
||||
from django.utils import timezone
|
||||
|
||||
from app.utils import send_sync_channel_message
|
||||
from torrent.models import Torrent
|
||||
from torrent.utils import transmission_handler
|
||||
from app.utils import send_sync_channel_message
|
||||
|
||||
|
||||
def update_transmission_data():
|
||||
@@ -24,10 +23,11 @@ def update_transmission_data():
|
||||
updated_torrents.append(torrent)
|
||||
if updated_torrents:
|
||||
Torrent.objects.bulk_update(updated_torrents, ["transmission_data"])
|
||||
send_sync_channel_message("torrent", "transmission_data_updated", {
|
||||
torrent.id: torrent.transmission_data
|
||||
for torrent in updated_torrents
|
||||
})
|
||||
send_sync_channel_message(
|
||||
"torrent",
|
||||
"transmission_data_updated",
|
||||
{torrent.id: torrent.transmission_data for torrent in updated_torrents},
|
||||
)
|
||||
|
||||
|
||||
def clean_old_torrents():
|
||||
@@ -37,24 +37,16 @@ def clean_old_torrents():
|
||||
print(f"delete torrent {torrent.name}")
|
||||
torrent.delete()
|
||||
|
||||
|
||||
def update_peer_port():
|
||||
transmission_handler.update_vpn_port()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
task_schedule = {
|
||||
"update_transmission_data": {
|
||||
"func": update_transmission_data,
|
||||
"schedule": 5.0
|
||||
},
|
||||
"clean_old_torrents": {
|
||||
"func": clean_old_torrents,
|
||||
"schedule": 5.0
|
||||
},
|
||||
"update_peer_port": {
|
||||
"func": update_peer_port,
|
||||
"schedule": 10.0
|
||||
}
|
||||
"update_transmission_data": {"func": update_transmission_data, "schedule": 5.0},
|
||||
"clean_old_torrents": {"func": clean_old_torrents, "schedule": 5.0},
|
||||
"update_peer_port": {"func": update_peer_port, "schedule": 10.0},
|
||||
}
|
||||
histories = {}
|
||||
run = True
|
||||
@@ -66,7 +58,10 @@ class Command(BaseCommand):
|
||||
self.stdout.write(self.style.SUCCESS("start"))
|
||||
while self.run:
|
||||
for name, task in self.task_schedule.items():
|
||||
if name not in self.histories or time.time() - self.histories[name] > task["schedule"]:
|
||||
if (
|
||||
name not in self.histories
|
||||
or time.time() - self.histories[name] > task["schedule"]
|
||||
):
|
||||
self.call_func(name)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
@@ -1,40 +1,55 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-04 23:41
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='File',
|
||||
name="File",
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('rel_name', models.TextField()),
|
||||
('size', models.BigIntegerField()),
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("rel_name", models.TextField()),
|
||||
("size", models.BigIntegerField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SharedUser',
|
||||
name="SharedUser",
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("date", models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Torrent',
|
||||
name="Torrent",
|
||||
fields=[
|
||||
('id', models.CharField(max_length=40, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('date_added', models.DateTimeField(auto_now_add=True)),
|
||||
('size', models.PositiveBigIntegerField()),
|
||||
('transmission_data', models.JSONField(default=dict)),
|
||||
(
|
||||
"id",
|
||||
models.CharField(max_length=40, primary_key=True, serialize=False),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("date_added", models.DateTimeField(auto_now_add=True)),
|
||||
("size", models.PositiveBigIntegerField()),
|
||||
("transmission_data", models.JSONField(default=dict)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,42 +6,58 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('torrent', '0001_initial'),
|
||||
("torrent", "0001_initial"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='shareduser',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
model_name="shareduser",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='torrent',
|
||||
name='shared_users',
|
||||
field=models.ManyToManyField(blank=True, related_name='torrents_shares', through='torrent.SharedUser', to=settings.AUTH_USER_MODEL),
|
||||
model_name="torrent",
|
||||
name="shared_users",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="torrents_shares",
|
||||
through="torrent.SharedUser",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='torrent',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='torrents', to=settings.AUTH_USER_MODEL),
|
||||
model_name="torrent",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="torrents",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='shareduser',
|
||||
name='torrent',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='torrent.torrent'),
|
||||
model_name="shareduser",
|
||||
name="torrent",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="torrent.torrent"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='file',
|
||||
name='torrent',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to='torrent.torrent'),
|
||||
model_name="file",
|
||||
name="torrent",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="files",
|
||||
to="torrent.torrent",
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='shareduser',
|
||||
unique_together={('user', 'torrent')},
|
||||
name="shareduser",
|
||||
unique_together={("user", "torrent")},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,15 +4,14 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('torrent', '0002_initial'),
|
||||
("torrent", "0002_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='torrent',
|
||||
name='date_modified',
|
||||
model_name="torrent",
|
||||
name="date_modified",
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,15 +4,14 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('torrent', '0003_torrent_date_modified'),
|
||||
("torrent", "0003_torrent_date_modified"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='shareduser',
|
||||
old_name='date',
|
||||
new_name='date_created',
|
||||
model_name="shareduser",
|
||||
old_name="date",
|
||||
new_name="date_created",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,15 +4,14 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('torrent', '0004_rename_date_shareduser_date_created'),
|
||||
("torrent", "0004_rename_date_shareduser_date_created"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='torrent',
|
||||
old_name='date_added',
|
||||
new_name='date_created',
|
||||
model_name="torrent",
|
||||
old_name="date_added",
|
||||
new_name="date_created",
|
||||
),
|
||||
]
|
||||
|
||||
+16
-17
@@ -1,12 +1,11 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
import mimetypes
|
||||
import uuid
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
import mimetypes
|
||||
import uuid
|
||||
import shlex
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Torrent(models.Model):
|
||||
@@ -14,8 +13,12 @@ class Torrent(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
user = models.ForeignKey("user.User", on_delete=models.CASCADE, related_name="torrents")
|
||||
shared_users = models.ManyToManyField("user.User", related_name="torrents_shares", blank=True, through="SharedUser")
|
||||
user = models.ForeignKey(
|
||||
"user.User", on_delete=models.CASCADE, related_name="torrents"
|
||||
)
|
||||
shared_users = models.ManyToManyField(
|
||||
"user.User", related_name="torrents_shares", blank=True, through="SharedUser"
|
||||
)
|
||||
size = models.PositiveBigIntegerField()
|
||||
transmission_data = models.JSONField(default=dict)
|
||||
|
||||
@@ -35,10 +38,7 @@ class Torrent(models.Model):
|
||||
|
||||
@cached_property
|
||||
def related_users(self):
|
||||
return [
|
||||
self.user_id,
|
||||
*self.shared_users.values_list("id", flat=True)
|
||||
]
|
||||
return [self.user_id, *self.shared_users.values_list("id", flat=True)]
|
||||
|
||||
|
||||
class SharedUser(models.Model):
|
||||
@@ -50,7 +50,6 @@ class SharedUser(models.Model):
|
||||
unique_together = ("user", "torrent")
|
||||
|
||||
|
||||
|
||||
class File(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
|
||||
torrent = models.ForeignKey("Torrent", models.CASCADE, related_name="files")
|
||||
@@ -86,7 +85,7 @@ class File(models.Model):
|
||||
def is_video(self):
|
||||
if self.mime_types.startswith("video/"):
|
||||
return True
|
||||
video_extensions = ['.mp4', '.flv', '.webm', '.avi', '.mkv', '.mov', '.wmv']
|
||||
video_extensions = [".mp4", ".flv", ".webm", ".avi", ".mkv", ".mov", ".wmv"]
|
||||
return self.pathname.suffix.lower() in video_extensions
|
||||
|
||||
@property
|
||||
@@ -95,13 +94,13 @@ class File(models.Model):
|
||||
encoded_parts = []
|
||||
for part in self.pathname.parts:
|
||||
# Ignorer un slash initial si présent
|
||||
if part == '/' or part == '\\':
|
||||
if part == "/" or part == "\\":
|
||||
continue
|
||||
encoded_parts.append(quote(part))
|
||||
|
||||
# Construction du chemin final avec le préfixe Nginx
|
||||
if settings.NGINX_ACCEL_BASE.endswith('/'):
|
||||
base = settings.NGINX_ACCEL_BASE.rstrip('/')
|
||||
if settings.NGINX_ACCEL_BASE.endswith("/"):
|
||||
base = settings.NGINX_ACCEL_BASE.rstrip("/")
|
||||
else:
|
||||
base = settings.NGINX_ACCEL_BASE
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from user.serializers import UserSerializer
|
||||
from .models import Torrent, File
|
||||
from .models import File, Torrent
|
||||
|
||||
|
||||
class TorrentSerializer(serializers.ModelSerializer):
|
||||
count_files = serializers.IntegerField(read_only=True, source="len_files")
|
||||
download_url = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Torrent
|
||||
fields = "__all__"
|
||||
@@ -32,4 +31,4 @@ class FileSerializer(serializers.ModelSerializer):
|
||||
return reverse("torrent:download_file", kwargs={"file_id": obj.id})
|
||||
|
||||
def get_flux_url(self, obj: File):
|
||||
return f'{reverse("torrent:flux_file", kwargs={"file_id": obj.id})}#{slugify(obj.filename)}'
|
||||
return f"{reverse('torrent:flux_file', kwargs={'file_id': obj.id})}#{slugify(obj.filename)}"
|
||||
|
||||
+32
-11
@@ -1,11 +1,14 @@
|
||||
from app.utils import send_sync_channel_message
|
||||
|
||||
from .models import Torrent
|
||||
from .utils import transmission_handler
|
||||
from .models import Torrent, SharedUser
|
||||
|
||||
|
||||
def on_post_save_torrent(instance: Torrent, created, **kwargs):
|
||||
if created:
|
||||
send_sync_channel_message(f"user_{instance.user_id}", "add_torrent", instance.id)
|
||||
send_sync_channel_message(
|
||||
f"user_{instance.user_id}", "add_torrent", instance.id
|
||||
)
|
||||
|
||||
|
||||
def on_pre_delete_torrent(instance: Torrent, **kwargs):
|
||||
@@ -25,20 +28,38 @@ def on_shared_user_changed(sender, instance: Torrent, action, pk_set, **kwargs):
|
||||
for user_id in pk_set:
|
||||
send_sync_channel_message(f"user_{user_id}", "add_torrent", instance.id)
|
||||
for user_id in instance.related_users:
|
||||
send_sync_channel_message(f"user_{user_id}", "update_torrent", {
|
||||
"torrent_id": instance.id,
|
||||
"updated_fields": {"shared_users": list(instance.shared_users.all().values_list("id", flat=True))}
|
||||
})
|
||||
send_sync_channel_message(
|
||||
f"user_{user_id}",
|
||||
"update_torrent",
|
||||
{
|
||||
"torrent_id": instance.id,
|
||||
"updated_fields": {
|
||||
"shared_users": list(
|
||||
instance.shared_users.all().values_list("id", flat=True)
|
||||
)
|
||||
},
|
||||
},
|
||||
)
|
||||
case "pre_remove":
|
||||
pass
|
||||
case "post_remove":
|
||||
for user_id in pk_set:
|
||||
send_sync_channel_message(f"user_{user_id}", "remove_torrent", instance.id)
|
||||
send_sync_channel_message(
|
||||
f"user_{user_id}", "remove_torrent", instance.id
|
||||
)
|
||||
for user_id in instance.related_users:
|
||||
send_sync_channel_message(f"user_{user_id}", "update_torrent", {
|
||||
"torrent_id": instance.id,
|
||||
"updated_fields": {"shared_users": list(instance.shared_users.all().values_list("id", flat=True))}
|
||||
})
|
||||
send_sync_channel_message(
|
||||
f"user_{user_id}",
|
||||
"update_torrent",
|
||||
{
|
||||
"torrent_id": instance.id,
|
||||
"updated_fields": {
|
||||
"shared_users": list(
|
||||
instance.shared_users.all().values_list("id", flat=True)
|
||||
)
|
||||
},
|
||||
},
|
||||
)
|
||||
case "pre_clear":
|
||||
pass
|
||||
case "post_clear":
|
||||
|
||||
+8
-10
@@ -1,12 +1,10 @@
|
||||
from django.db import close_old_connections
|
||||
from celery import shared_task
|
||||
from channels.layers import get_channel_layer
|
||||
from asgiref.sync import async_to_sync
|
||||
|
||||
from .models import Torrent, File
|
||||
from .utils import transmission_handler
|
||||
from app.utils import send_sync_channel_message
|
||||
|
||||
from .models import Torrent
|
||||
from .utils import transmission_handler
|
||||
|
||||
|
||||
@shared_task
|
||||
def update_transmission_data():
|
||||
@@ -19,8 +17,8 @@ def update_transmission_data():
|
||||
updated_torrents.append(torrent)
|
||||
if updated_torrents:
|
||||
Torrent.objects.bulk_update(updated_torrents, ["transmission_data"])
|
||||
send_sync_channel_message("torrent", "transmission_data_updated", {
|
||||
torrent.id: torrent.transmission_data
|
||||
for torrent in updated_torrents
|
||||
})
|
||||
|
||||
send_sync_channel_message(
|
||||
"torrent",
|
||||
"transmission_data_updated",
|
||||
{torrent.id: torrent.transmission_data for torrent in updated_torrents},
|
||||
)
|
||||
|
||||
+81
-111
@@ -1,39 +1,34 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
from rest_framework import status
|
||||
from django.conf import settings
|
||||
from unittest.mock import patch, MagicMock
|
||||
from rest_framework.test import APIClient, APITestCase
|
||||
|
||||
from .models import Torrent, SharedUser, File
|
||||
from user.models import User
|
||||
from .views import TorrentViewSet, FileViewSet
|
||||
from .utils import Transmission, torrent_proceed, torrent_share
|
||||
|
||||
from .models import File, SharedUser, Torrent
|
||||
from .utils import Transmission, torrent_proceed
|
||||
|
||||
|
||||
class TorrentModelTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='test@example.com',
|
||||
password='testpassword'
|
||||
username="testuser", email="test@example.com", password="testpassword"
|
||||
)
|
||||
self.torrent = Torrent.objects.create(
|
||||
id='abc123',
|
||||
name='Test Torrent',
|
||||
id="abc123",
|
||||
name="Test Torrent",
|
||||
user=self.user,
|
||||
size=1000,
|
||||
transmission_data={}
|
||||
transmission_data={},
|
||||
)
|
||||
self.shared_user = User.objects.create_user(
|
||||
username='shareduser',
|
||||
email='shared@example.com',
|
||||
password='sharedpassword'
|
||||
username="shareduser", email="shared@example.com", password="sharedpassword"
|
||||
)
|
||||
self.file = File.objects.create(
|
||||
torrent=self.torrent,
|
||||
rel_name='test_file.txt',
|
||||
size=100
|
||||
torrent=self.torrent, rel_name="test_file.txt", size=100
|
||||
)
|
||||
|
||||
def test_len_files(self):
|
||||
@@ -41,15 +36,11 @@ class TorrentModelTestCase(TestCase):
|
||||
self.assertEqual(self.torrent.len_files, 1)
|
||||
|
||||
# Add another file and test again
|
||||
File.objects.create(
|
||||
torrent=self.torrent,
|
||||
rel_name='another_file.txt',
|
||||
size=200
|
||||
)
|
||||
File.objects.create(torrent=self.torrent, rel_name="another_file.txt", size=200)
|
||||
|
||||
# Clear cached_property
|
||||
if hasattr(self.torrent, '_len_files'):
|
||||
delattr(self.torrent, '_len_files')
|
||||
if hasattr(self.torrent, "_len_files"):
|
||||
delattr(self.torrent, "_len_files")
|
||||
|
||||
self.assertEqual(self.torrent.len_files, 2)
|
||||
|
||||
@@ -62,8 +53,8 @@ class TorrentModelTestCase(TestCase):
|
||||
self.torrent.shared_users.add(self.shared_user)
|
||||
|
||||
# Clear cached_property
|
||||
if hasattr(self.torrent, '_related_users'):
|
||||
delattr(self.torrent, '_related_users')
|
||||
if hasattr(self.torrent, "_related_users"):
|
||||
delattr(self.torrent, "_related_users")
|
||||
|
||||
# Should include both users now
|
||||
self.assertIn(self.user.id, self.torrent.related_users)
|
||||
@@ -73,30 +64,26 @@ class TorrentModelTestCase(TestCase):
|
||||
class FileModelTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='test@example.com',
|
||||
password='testpassword'
|
||||
username="testuser", email="test@example.com", password="testpassword"
|
||||
)
|
||||
self.torrent = Torrent.objects.create(
|
||||
id='abc123',
|
||||
name='Test Torrent',
|
||||
id="abc123",
|
||||
name="Test Torrent",
|
||||
user=self.user,
|
||||
size=1000,
|
||||
transmission_data={}
|
||||
transmission_data={},
|
||||
)
|
||||
self.file = File.objects.create(
|
||||
torrent=self.torrent,
|
||||
rel_name='test/path/file.mp4',
|
||||
size=100
|
||||
torrent=self.torrent, rel_name="test/path/file.mp4", size=100
|
||||
)
|
||||
|
||||
def test_pathname(self):
|
||||
"""Test the pathname property returns the correct path"""
|
||||
self.assertEqual(str(self.file.pathname), 'test/path/file.mp4')
|
||||
self.assertEqual(str(self.file.pathname), "test/path/file.mp4")
|
||||
|
||||
def test_filename(self):
|
||||
"""Test the filename property returns the correct filename"""
|
||||
self.assertEqual(self.file.filename, 'file.mp4')
|
||||
self.assertEqual(self.file.filename, "file.mp4")
|
||||
|
||||
def test_abs_pathname(self):
|
||||
"""Test the abs_pathname property returns the correct absolute path"""
|
||||
@@ -109,9 +96,7 @@ class FileModelTestCase(TestCase):
|
||||
|
||||
# Test non-video file
|
||||
non_video_file = File.objects.create(
|
||||
torrent=self.torrent,
|
||||
rel_name='test/path/document.pdf',
|
||||
size=50
|
||||
torrent=self.torrent, rel_name="test/path/document.pdf", size=50
|
||||
)
|
||||
self.assertFalse(non_video_file.is_video)
|
||||
|
||||
@@ -119,29 +104,22 @@ class FileModelTestCase(TestCase):
|
||||
class SharedUserModelTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.owner = User.objects.create_user(
|
||||
username='owner',
|
||||
email='owner@example.com',
|
||||
password='ownerpassword'
|
||||
username="owner", email="owner@example.com", password="ownerpassword"
|
||||
)
|
||||
self.shared_user = User.objects.create_user(
|
||||
username='shareduser',
|
||||
email='shared@example.com',
|
||||
password='sharedpassword'
|
||||
username="shareduser", email="shared@example.com", password="sharedpassword"
|
||||
)
|
||||
self.torrent = Torrent.objects.create(
|
||||
id='abc123',
|
||||
name='Test Torrent',
|
||||
id="abc123",
|
||||
name="Test Torrent",
|
||||
user=self.owner,
|
||||
size=1000,
|
||||
transmission_data={}
|
||||
transmission_data={},
|
||||
)
|
||||
|
||||
def test_shared_user_creation(self):
|
||||
"""Test creating a shared user relationship"""
|
||||
shared = SharedUser.objects.create(
|
||||
user=self.shared_user,
|
||||
torrent=self.torrent
|
||||
)
|
||||
shared = SharedUser.objects.create(user=self.shared_user, torrent=self.torrent)
|
||||
self.assertEqual(shared.user, self.shared_user)
|
||||
self.assertEqual(shared.torrent, self.torrent)
|
||||
|
||||
@@ -152,109 +130,99 @@ class SharedUserModelTestCase(TestCase):
|
||||
class TorrentViewSetTestCase(APITestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='test@example.com',
|
||||
password='testpassword'
|
||||
username="testuser", email="test@example.com", password="testpassword"
|
||||
)
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
self.torrent = Torrent.objects.create(
|
||||
id='abc123',
|
||||
name='Test Torrent',
|
||||
id="abc123",
|
||||
name="Test Torrent",
|
||||
user=self.user,
|
||||
size=1000,
|
||||
transmission_data={}
|
||||
transmission_data={},
|
||||
)
|
||||
|
||||
self.file = File.objects.create(
|
||||
torrent=self.torrent,
|
||||
rel_name='test_file.txt',
|
||||
size=100
|
||||
torrent=self.torrent, rel_name="test_file.txt", size=100
|
||||
)
|
||||
|
||||
def test_list_torrents(self):
|
||||
"""Test listing torrents"""
|
||||
url = reverse('torrent-list')
|
||||
url = reverse("torrent-list")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertEqual(response.data[0]['id'], self.torrent.id)
|
||||
self.assertEqual(response.data[0]["id"], self.torrent.id)
|
||||
|
||||
def test_retrieve_torrent(self):
|
||||
"""Test retrieving a specific torrent"""
|
||||
url = reverse('torrent-detail', args=[self.torrent.id])
|
||||
url = reverse("torrent-detail", args=[self.torrent.id])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['id'], self.torrent.id)
|
||||
self.assertEqual(response.data['name'], 'Test Torrent')
|
||||
self.assertEqual(response.data["id"], self.torrent.id)
|
||||
self.assertEqual(response.data["name"], "Test Torrent")
|
||||
|
||||
@patch('torrent.views.torrent_share')
|
||||
@patch("torrent.views.torrent_share")
|
||||
def test_share_torrent(self, mock_torrent_share):
|
||||
"""Test sharing a torrent with another user"""
|
||||
mock_torrent_share.return_value = True
|
||||
|
||||
shared_user = User.objects.create_user(
|
||||
username='shareduser',
|
||||
email='shared@example.com',
|
||||
password='sharedpassword'
|
||||
username="shareduser", email="shared@example.com", password="sharedpassword"
|
||||
)
|
||||
|
||||
url = reverse('torrent-share', args=[self.torrent.id])
|
||||
response = self.client.post(url, {'user_id': shared_user.id})
|
||||
url = reverse("torrent-share", args=[self.torrent.id])
|
||||
response = self.client.post(url, {"user_id": shared_user.id})
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertTrue(response.data['success'])
|
||||
self.assertTrue(response.data["success"])
|
||||
mock_torrent_share.assert_called_once()
|
||||
|
||||
|
||||
class FileViewSetTestCase(APITestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='test@example.com',
|
||||
password='testpassword'
|
||||
username="testuser", email="test@example.com", password="testpassword"
|
||||
)
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
self.torrent = Torrent.objects.create(
|
||||
id='abc123',
|
||||
name='Test Torrent',
|
||||
id="abc123",
|
||||
name="Test Torrent",
|
||||
user=self.user,
|
||||
size=1000,
|
||||
transmission_data={}
|
||||
transmission_data={},
|
||||
)
|
||||
|
||||
self.file = File.objects.create(
|
||||
torrent=self.torrent,
|
||||
rel_name='test_file.txt',
|
||||
size=100
|
||||
torrent=self.torrent, rel_name="test_file.txt", size=100
|
||||
)
|
||||
|
||||
def test_list_files(self):
|
||||
"""Test listing files"""
|
||||
url = reverse('file-list')
|
||||
url = reverse("file-list")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_retrieve_file(self):
|
||||
"""Test retrieving a specific file"""
|
||||
url = reverse('file-detail', args=[self.file.id])
|
||||
url = reverse("file-detail", args=[self.file.id])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['id'], str(self.file.id))
|
||||
self.assertEqual(response.data['rel_name'], 'test_file.txt')
|
||||
self.assertEqual(response.data["id"], str(self.file.id))
|
||||
self.assertEqual(response.data["rel_name"], "test_file.txt")
|
||||
|
||||
|
||||
class TransmissionUtilsTestCase(TestCase):
|
||||
@patch('torrent.utils.Client')
|
||||
@patch("torrent.utils.Client")
|
||||
def test_transmission_init(self, mock_client):
|
||||
"""Test Transmission class initialization"""
|
||||
transmission = Transmission()
|
||||
mock_client.assert_called_once_with(**settings.TRANSMISSION)
|
||||
|
||||
@patch('torrent.utils.Client')
|
||||
@patch("torrent.utils.Client")
|
||||
def test_add_torrent(self, mock_client):
|
||||
"""Test adding a torrent"""
|
||||
mock_instance = mock_client.return_value
|
||||
@@ -267,62 +235,64 @@ class TransmissionUtilsTestCase(TestCase):
|
||||
mock_instance.add_torrent.assert_called_once_with(file_obj)
|
||||
self.assertEqual(result, mock_instance.add_torrent.return_value)
|
||||
|
||||
@patch('torrent.utils.Client')
|
||||
@patch("torrent.utils.Client")
|
||||
def test_get_data(self, mock_client):
|
||||
"""Test getting torrent data"""
|
||||
mock_instance = mock_client.return_value
|
||||
mock_torrent = MagicMock()
|
||||
mock_torrent.progress = 50
|
||||
mock_torrent.fields = {'name': 'Test', 'size': 1000}
|
||||
mock_torrent.fields = {"name": "Test", "size": 1000}
|
||||
mock_instance.get_torrent.return_value = mock_torrent
|
||||
|
||||
transmission = Transmission()
|
||||
result = transmission.get_data('hash123')
|
||||
result = transmission.get_data("hash123")
|
||||
|
||||
mock_instance.get_torrent.assert_called_once_with('hash123', transmission.trpc_args)
|
||||
self.assertEqual(result['progress'], 50)
|
||||
self.assertEqual(result['name'], 'Test')
|
||||
self.assertEqual(result['size'], 1000)
|
||||
mock_instance.get_torrent.assert_called_once_with(
|
||||
"hash123", transmission.trpc_args
|
||||
)
|
||||
self.assertEqual(result["progress"], 50)
|
||||
self.assertEqual(result["name"], "Test")
|
||||
self.assertEqual(result["size"], 1000)
|
||||
|
||||
|
||||
class TorrentProceedTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='test@example.com',
|
||||
password='testpassword',
|
||||
max_size=10000
|
||||
username="testuser",
|
||||
email="test@example.com",
|
||||
password="testpassword",
|
||||
max_size=10000,
|
||||
)
|
||||
|
||||
@patch('torrent.utils.transmission_handler')
|
||||
@patch("torrent.utils.transmission_handler")
|
||||
def test_torrent_proceed_size_exceed(self, mock_transmission):
|
||||
"""Test torrent_proceed when user size is exceeded"""
|
||||
# Set user's used size to exceed max_size
|
||||
self.user.max_size = 100
|
||||
Torrent.objects.create(
|
||||
id='abc123',
|
||||
name='Test Torrent',
|
||||
id="abc123",
|
||||
name="Test Torrent",
|
||||
user=self.user,
|
||||
size=200, # Exceeds max_size
|
||||
transmission_data={}
|
||||
transmission_data={},
|
||||
)
|
||||
|
||||
file_obj = MagicMock()
|
||||
result = torrent_proceed(self.user, file_obj)
|
||||
|
||||
self.assertEqual(result['status'], 'error')
|
||||
self.assertEqual(result['message'], 'Size exceed')
|
||||
self.assertEqual(result["status"], "error")
|
||||
self.assertEqual(result["message"], "Size exceed")
|
||||
mock_transmission.add_torrent.assert_not_called()
|
||||
|
||||
@patch('torrent.utils.transmission_handler')
|
||||
@patch("torrent.utils.transmission_handler")
|
||||
def test_torrent_proceed_transmission_error(self, mock_transmission):
|
||||
"""Test torrent_proceed when transmission raises an error"""
|
||||
from transmission_rpc.error import TransmissionError
|
||||
|
||||
mock_transmission.add_torrent.side_effect = TransmissionError('Test error')
|
||||
mock_transmission.add_torrent.side_effect = TransmissionError("Test error")
|
||||
|
||||
file_obj = MagicMock()
|
||||
result = torrent_proceed(self.user, file_obj)
|
||||
|
||||
self.assertEqual(result['status'], 'error')
|
||||
self.assertEqual(result['message'], 'Transmission Error')
|
||||
self.assertEqual(result["status"], "error")
|
||||
self.assertEqual(result["message"], "Transmission Error")
|
||||
|
||||
+4
-2
@@ -1,12 +1,14 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import HomeView, download_file, download_torrent, pping, flux_file
|
||||
from .views import HomeView, download_file, download_torrent, flux_file, pping
|
||||
|
||||
app_name = "torrent"
|
||||
urlpatterns = [
|
||||
path("", HomeView.as_view(), name="home"),
|
||||
path("pping/", pping, name="pping"),
|
||||
path("download_file/<uuid:file_id>", download_file, name="download_file"),
|
||||
path("download_torrent/<str:torrent_id>", download_torrent, name="download_torrent"),
|
||||
path(
|
||||
"download_torrent/<str:torrent_id>", download_torrent, name="download_torrent"
|
||||
),
|
||||
path("flux_file/<uuid:file_id>", flux_file, name="flux_file"),
|
||||
]
|
||||
|
||||
+63
-37
@@ -1,24 +1,38 @@
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
import traceback
|
||||
import base64
|
||||
import io
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from django.conf import settings
|
||||
from transmission_rpc import Client
|
||||
from transmission_rpc.error import TransmissionError
|
||||
|
||||
# from app.utils import send_sync_channel_message
|
||||
from .models import Torrent, File
|
||||
from user.models import User
|
||||
|
||||
# from app.utils import send_sync_channel_message
|
||||
from .models import File, Torrent
|
||||
|
||||
|
||||
class Transmission:
|
||||
trpc_args = [
|
||||
"id", "percentDone", "uploadRatio", "rateUpload", "rateDownload", "hashString", "status", "sizeWhenDone",
|
||||
"leftUntilDone", "name", "eta", "totalSize", "uploadedEver", "peersGettingFromUs", "peersSendingToUs",
|
||||
"tracker", "trackerStats", "activityDate"
|
||||
"id",
|
||||
"percentDone",
|
||||
"uploadRatio",
|
||||
"rateUpload",
|
||||
"rateDownload",
|
||||
"hashString",
|
||||
"status",
|
||||
"sizeWhenDone",
|
||||
"leftUntilDone",
|
||||
"name",
|
||||
"eta",
|
||||
"totalSize",
|
||||
"uploadedEver",
|
||||
"peersGettingFromUs",
|
||||
"peersSendingToUs",
|
||||
"tracker",
|
||||
"trackerStats",
|
||||
"activityDate",
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
@@ -31,7 +45,12 @@ class Transmission:
|
||||
if os.path.exists(port_file):
|
||||
try:
|
||||
with open(port_file) as f:
|
||||
vpn_port = int(f.read().strip())
|
||||
content = f.read().strip()
|
||||
if (
|
||||
not content
|
||||
): # Si le fichier est vide, on attend la prochaine itération
|
||||
return
|
||||
vpn_port = int(content)
|
||||
|
||||
# Récupère le port actuel configuré dans Transmission
|
||||
current_settings = self.client.get_session()
|
||||
@@ -55,15 +74,15 @@ class Transmission:
|
||||
def get_data(self, hash_string):
|
||||
data = self.client.get_torrent(hash_string, self.trpc_args)
|
||||
|
||||
return {
|
||||
"progress": data.progress,
|
||||
"status_str": data.status,
|
||||
**data.fields
|
||||
}
|
||||
return {"progress": data.progress, "status_str": data.status, **data.fields}
|
||||
|
||||
def get_all_data(self, hash_strings=None):
|
||||
return {
|
||||
data.hashString: {"progress": data.progress, "status_str": data.status, **data.fields}
|
||||
data.hashString: {
|
||||
"progress": data.progress,
|
||||
"status_str": data.status,
|
||||
**data.fields,
|
||||
}
|
||||
for data in self.client.get_torrents(hash_strings, self.trpc_args)
|
||||
}
|
||||
|
||||
@@ -84,7 +103,7 @@ class Transmission:
|
||||
port_file = "/tmp/gluetun/forwarded_port"
|
||||
vpn_port = None
|
||||
if os.path.exists(port_file):
|
||||
with open(port_file, "r") as f:
|
||||
with open(port_file) as f:
|
||||
vpn_port = f.read().strip()
|
||||
|
||||
# 2. Test de connectivité du port (via l'API Transmission)
|
||||
@@ -106,11 +125,7 @@ transmission_handler = Transmission()
|
||||
|
||||
|
||||
def torrent_proceed(user, file, file_mode="file_object"):
|
||||
r = {
|
||||
"torrent": None,
|
||||
"status": "error",
|
||||
"message": "Unexpected error"
|
||||
}
|
||||
r = {"torrent": None, "status": "error", "message": "Unexpected error"}
|
||||
|
||||
user: User
|
||||
if user.size_used > user.max_size:
|
||||
@@ -149,16 +164,18 @@ def torrent_proceed(user, file, file_mode="file_object"):
|
||||
name=data["name"],
|
||||
user=user,
|
||||
size=data["totalSize"],
|
||||
transmission_data=data
|
||||
transmission_data=data,
|
||||
)
|
||||
File.objects.bulk_create(
|
||||
[
|
||||
File(
|
||||
torrent=torrent,
|
||||
rel_name=file.name,
|
||||
size=file.size,
|
||||
)
|
||||
for file in transmission_handler.get_files(torrent.id)
|
||||
]
|
||||
)
|
||||
File.objects.bulk_create([
|
||||
File(
|
||||
torrent=torrent,
|
||||
rel_name=file.name,
|
||||
size=file.size,
|
||||
)
|
||||
for file in transmission_handler.get_files(torrent.id)
|
||||
])
|
||||
|
||||
r["torrent"] = torrent
|
||||
r["status"] = "success"
|
||||
@@ -167,13 +184,22 @@ def torrent_proceed(user, file, file_mode="file_object"):
|
||||
|
||||
|
||||
def torrent_share(torrent, current_user, target_user_id):
|
||||
from .models import Torrent, SharedUser
|
||||
from .models import SharedUser
|
||||
|
||||
torrent: Torrent
|
||||
|
||||
if (torrent.user_id != target_user_id and
|
||||
any([torrent.user == current_user, torrent.shared_users.filter(id=current_user.id)]) and
|
||||
not SharedUser.objects.filter(torrent_id=torrent.id, user_id=target_user_id).exists()):
|
||||
if (
|
||||
torrent.user_id != target_user_id
|
||||
and any(
|
||||
[
|
||||
torrent.user == current_user,
|
||||
torrent.shared_users.filter(id=current_user.id),
|
||||
]
|
||||
)
|
||||
and not SharedUser.objects.filter(
|
||||
torrent_id=torrent.id, user_id=target_user_id
|
||||
).exists()
|
||||
):
|
||||
torrent.shared_users.add(target_user_id)
|
||||
return True
|
||||
return False
|
||||
return False
|
||||
|
||||
+43
-37
@@ -1,21 +1,20 @@
|
||||
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 django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db.models import Q, Count, OuterRef, Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.http import HttpResponse, Http404, StreamingHttpResponse
|
||||
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from rest_framework import mixins
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import action
|
||||
import anyio
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from app.utils import StreamingZipFileResponse
|
||||
from user.models import User
|
||||
from .models import Torrent, File, SharedUser
|
||||
from .serializers import TorrentSerializer, FileSerializer
|
||||
|
||||
from .models import File, SharedUser, Torrent
|
||||
from .serializers import FileSerializer, TorrentSerializer
|
||||
from .utils import torrent_proceed, torrent_share
|
||||
|
||||
|
||||
@@ -35,7 +34,7 @@ async def download_file(request, file_id):
|
||||
| Q(torrent__user__friends=user)
|
||||
| Q(torrent__shared_users__friends=user),
|
||||
torrent__transmission_data__progress__gte=100,
|
||||
pk=file_id
|
||||
pk=file_id,
|
||||
).distinct()
|
||||
|
||||
try:
|
||||
@@ -44,10 +43,12 @@ async def download_file(request, file_id):
|
||||
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"
|
||||
@@ -86,7 +87,7 @@ async def secured_flux_file(request, file_id):
|
||||
| Q(torrent__user__friends=user)
|
||||
| Q(torrent__shared_users__friends=user),
|
||||
torrent__transmission_data__progress__gte=100,
|
||||
pk=file_id
|
||||
pk=file_id,
|
||||
).distinct()
|
||||
|
||||
try:
|
||||
@@ -105,39 +106,42 @@ async def secured_flux_file(request, file_id):
|
||||
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()
|
||||
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
|
||||
}))
|
||||
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()
|
||||
(file.abs_pathname, file.rel_name) async for file in torrent.files.all()
|
||||
],
|
||||
is_async=True
|
||||
is_async=True,
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
class TorrentViewSet(mixins.CreateModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
GenericViewSet):
|
||||
class TorrentViewSet(
|
||||
mixins.CreateModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
GenericViewSet,
|
||||
):
|
||||
queryset = Torrent.objects.all().annotate(count_files=Count("files"))
|
||||
serializer_class = TorrentSerializer
|
||||
|
||||
@@ -158,7 +162,9 @@ class TorrentViewSet(mixins.CreateModelMixin,
|
||||
else:
|
||||
user_id = self.request.user.id
|
||||
|
||||
sub = SharedUser.objects.filter(torrent_id=OuterRef("pk"), user_id=user_id).values("date_created")
|
||||
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)
|
||||
@@ -188,7 +194,9 @@ class TorrentViewSet(mixins.CreateModelMixin,
|
||||
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)
|
||||
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)
|
||||
@@ -196,9 +204,7 @@ class TorrentViewSet(mixins.CreateModelMixin,
|
||||
Torrent.objects.filter(user=self.request.user).aggregate(total_size=Sum("size"))
|
||||
|
||||
|
||||
class FileViewSet(mixins.RetrieveModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
GenericViewSet):
|
||||
class FileViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet):
|
||||
queryset = File.objects.all()
|
||||
serializer_class = FileSerializer
|
||||
filterset_fields = ["torrent"]
|
||||
|
||||
Reference in New Issue
Block a user