Compare commits
16 Commits
87396a20da
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 800a8dca90 | |||
| c7b09b30bd | |||
| 5c3c864a9e | |||
| 3aac0116e6 | |||
| b4330c0362 | |||
| 25fd30a0c3 | |||
| 26d4613dd1 | |||
| 42332ac329 | |||
| c7e52a2382 | |||
| cf702fa316 | |||
| 955ea36433 | |||
| 9b9c25806b | |||
| 065ac7f250 | |||
| 0a5251ec10 | |||
| eb60fc4dd3 | |||
| 888ed093a1 |
@@ -1,7 +1,7 @@
|
|||||||
USER_ID=1000
|
USER_ID=1000
|
||||||
GROUP_ID=1000
|
GROUP_ID=1000
|
||||||
|
|
||||||
DOMAIN=127.0.0.1
|
TRAEFIK_HOST_RULE=Host(`example.com`) || Host(`www.example.com`)
|
||||||
|
|
||||||
LISTEN_PORT=8000
|
LISTEN_PORT=8000
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ FROM python:3.13-slim
|
|||||||
|
|
||||||
ARG puid=1000
|
ARG puid=1000
|
||||||
ARG pgid=1000
|
ARG pgid=1000
|
||||||
|
ARG debug=false
|
||||||
ENV PYTHONUNBUFFERED=1 \
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
LANG=C.UTF-8 \
|
LANG=C.UTF-8 \
|
||||||
@@ -40,8 +41,13 @@ RUN yarn install
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN pip install --upgrade pip
|
RUN pip install --upgrade pip
|
||||||
|
|
||||||
COPY ./requirements.txt ./requirements.txt
|
COPY requirements*.txt ./
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN if [ "$debug" = "true" ] ; then \
|
||||||
|
pip install --no-cache-dir -r requirements-dev.txt ; \
|
||||||
|
else \
|
||||||
|
pip install --no-cache-dir -r requirements-prod.txt ; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
#COPY . .
|
#COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ INSTALLED_APPS = [
|
|||||||
'user',
|
'user',
|
||||||
'api',
|
'api',
|
||||||
'torrent',
|
'torrent',
|
||||||
|
'watch_party'
|
||||||
]
|
]
|
||||||
|
if DEBUG:
|
||||||
|
INSTALLED_APPS = ["daphne"] + INSTALLED_APPS
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
@@ -139,6 +142,7 @@ USE_TZ = True
|
|||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / "static",
|
||||||
BASE_DIR / "frontend/dist",
|
BASE_DIR / "frontend/dist",
|
||||||
]
|
]
|
||||||
STATIC_ROOT = BASE_DIR / "static_collected"
|
STATIC_ROOT = BASE_DIR / "static_collected"
|
||||||
@@ -249,4 +253,5 @@ TRANSMISSION = {
|
|||||||
"port": getenv("TRANSMISSION_PORT", 9091),
|
"port": getenv("TRANSMISSION_PORT", 9091),
|
||||||
"username": getenv("TRANSMISSION_USERNAME"),
|
"username": getenv("TRANSMISSION_USERNAME"),
|
||||||
"password": getenv("TRANSMISSION_PASSWORD")
|
"password": getenv("TRANSMISSION_PASSWORD")
|
||||||
}
|
}
|
||||||
|
TORRENT_TTL = int(getenv("TORRENT_TTL", 90*24*60*60)) # 90 jours
|
||||||
@@ -17,6 +17,7 @@ Including another URLconf
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include # Added include for including app URLs
|
from django.urls import path, include # Added include for including app URLs
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.views.generic import RedirectView
|
||||||
from django.contrib.auth.views import (
|
from django.contrib.auth.views import (
|
||||||
PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, PasswordResetCompleteView, PasswordChangeView,
|
PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, PasswordResetCompleteView, PasswordChangeView,
|
||||||
PasswordChangeDoneView, LogoutView
|
PasswordChangeDoneView, LogoutView
|
||||||
@@ -28,6 +29,7 @@ urlpatterns = [
|
|||||||
path("", include("torrent.urls", "torrent")),
|
path("", include("torrent.urls", "torrent")),
|
||||||
path("user/", include("user.urls", "user")),
|
path("user/", include("user.urls", "user")),
|
||||||
path("api/", include("api.urls", "api")),
|
path("api/", include("api.urls", "api")),
|
||||||
|
path("home", RedirectView.as_view(url="/", permanent=False), name="home"),
|
||||||
|
|
||||||
# reset password related
|
# reset password related
|
||||||
path("password_reset/", PasswordResetView.as_view(), name="password_reset"),
|
path("password_reset/", PasswordResetView.as_view(), name="password_reset"),
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
CORES=$(nproc)
|
CORES=$(nproc)
|
||||||
WORKERS=$((2 * CORES + 1))
|
WORKERS=$((2 * CORES + 1))
|
||||||
|
|
||||||
cd /app/frontend && yarn dev &
|
cd /app/frontend && yarn dev &
|
||||||
cd /app && uvicorn app.asgi:application --workers $WORKERS --host 0.0.0.0 --port 8000 --lifespan off --loop uvloop --ws websockets --reload &
|
cd /app && python manage.py runserver 0.0.0.0:8000 &
|
||||||
|
|
||||||
wait -n
|
wait -n
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,11 @@
|
|||||||
<v-btn v-bind="props">Manage</v-btn>
|
<v-btn v-bind="props">Manage</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-list>
|
<v-list>
|
||||||
|
<v-list-item
|
||||||
|
v-if="!$qt.is_active"
|
||||||
|
href="https://gitea.devpanel.fr/oxpanel/oxapp25/releases/download/1.0/OxApp_Setup.exe"
|
||||||
|
title="Download app"
|
||||||
|
/>
|
||||||
<v-list-item href="/password_change/" title="Change password"/>
|
<v-list-item href="/password_change/" title="Change password"/>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-btn @click.stop="downloadClicked" :disabled="!is_download_finished" icon="mdi-download" color="green" variant="text"/>
|
<v-btn @click.stop="downloadClicked" :disabled="!is_download_finished" icon="mdi-download" color="green" variant="text"/>
|
||||||
|
<v-btn v-if="file.is_video" @click.stop="fluxClicked" :disabled="!is_download_finished" icon="mdi-vlc" color="orange" variant="text"></v-btn>
|
||||||
<v-dialog v-if="file.is_stream_video" v-model="video_modal" width="75%" height="100%">
|
<v-dialog v-if="file.is_stream_video" v-model="video_modal" width="75%" height="100%">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" icon="mdi-play" variant="text"/>
|
<v-btn v-bind="props" icon="mdi-play" variant="text"/>
|
||||||
@@ -45,14 +46,24 @@ export default {
|
|||||||
downloadClicked(){
|
downloadClicked(){
|
||||||
if(!this.is_download_finished) return;
|
if(!this.is_download_finished) return;
|
||||||
if(this.$qt.is_active){
|
if(this.$qt.is_active){
|
||||||
this.$qt.callMethod("add_files", this.file);
|
this.$qt.callMethod("add_files", [this.file]);
|
||||||
}else{
|
}else{
|
||||||
let a = document.createElement("a");
|
let a = document.createElement("a");
|
||||||
a.href = this.file.download_url;
|
a.href = this.file.download_url;
|
||||||
a.setAttribute("download", "download");
|
a.setAttribute("download", "download");
|
||||||
a.click();
|
a.click();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
fluxClicked(){
|
||||||
|
const full_url = new URL(this.file.flux_url, window.location.href).href
|
||||||
|
navigator.clipboard.writeText(full_url)
|
||||||
|
.then(() => {
|
||||||
|
// Optionnel: Vous pouvez ajouter une notification pour indiquer à l'utilisateur que l'URL a été copiée
|
||||||
|
console.log('URL copiée dans le presse-papier');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Erreur lors de la copie dans le presse-papier:', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async fetchFiles(){
|
async fetchFiles(){
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
let response = await fetch(`/api/torrent/files?${new URLSearchParams(this.filters)}`);
|
let response = await fetch(`/api/torrent/files/?${new URLSearchParams(this.filters)}`);
|
||||||
this.files = await response.json();
|
this.files = await response.json();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
return this.files;
|
return this.files;
|
||||||
|
|||||||
@@ -8,19 +8,32 @@
|
|||||||
<!-- indeterminate-->
|
<!-- indeterminate-->
|
||||||
<!-- :color="torrent.transmission_data.progress < 100 ? 'green':'red'"-->
|
<!-- :color="torrent.transmission_data.progress < 100 ? 'green':'red'"-->
|
||||||
<!-- />-->
|
<!-- />-->
|
||||||
<v-icon
|
|
||||||
v-if="torrent.transmission_data.rateDownload && torrent.transmission_data.rateUpload"
|
<!-- Si téléchargement en cours-->
|
||||||
:color="torrent.transmission_data.rateDownload ? 'green':'red'"
|
<v-chip variant="text" color="white" v-if="progressData.mode === 'download'">
|
||||||
:icon="torrent.transmission_data.rateDownload ? 'mdi-arrow-down-bold':'mdi-arrow-up-bold'"
|
<v-icon
|
||||||
/>
|
v-if="torrent.transmission_data.rateDownload"
|
||||||
<strong style="padding-left: 5px">
|
color="green"
|
||||||
|
icon="mdi-arrow-down-bold"
|
||||||
|
/>
|
||||||
{{progressData.value}}%
|
{{progressData.value}}%
|
||||||
<strong
|
<strong style="padding-left: 5px" v-if="torrent.transmission_data.rateDownload">
|
||||||
v-if="torrent.transmission_data.rateDownload || torrent.transmission_data.rateUpload"
|
({{fs_speed_format(torrent.transmission_data.rateDownload)}}/s)
|
||||||
>
|
|
||||||
({{torrent.transmission_data.rateDownload ? fs_speed_format(torrent.transmission_data.rateDownload):fs_speed_format(torrent.transmission_data.rateUpload)}}/s)
|
|
||||||
</strong>
|
</strong>
|
||||||
</strong>
|
</v-chip>
|
||||||
|
|
||||||
|
<!-- Si téléchargement terminé-->
|
||||||
|
<v-chip variant="text" color="red" v-else>
|
||||||
|
<v-icon
|
||||||
|
v-if="torrent.transmission_data.rateUpload"
|
||||||
|
color="red"
|
||||||
|
icon="mdi-arrow-up-bold"
|
||||||
|
/>
|
||||||
|
{{fs_format(torrent.transmission_data.uploadedEver)}}
|
||||||
|
<strong style="padding-left: 5px" v-if="torrent.transmission_data.rateUpload">
|
||||||
|
({{fs_speed_format(torrent.transmission_data.rateUpload)}}/s)
|
||||||
|
</strong>
|
||||||
|
</v-chip>
|
||||||
</v-progress-linear>
|
</v-progress-linear>
|
||||||
<v-row no-gutters>
|
<v-row no-gutters>
|
||||||
<!-- ligne du haut -->
|
<!-- ligne du haut -->
|
||||||
@@ -115,9 +128,10 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
progressData(){
|
progressData(){
|
||||||
let color = "red", value = 0, eta;
|
let color = "red", value = 0, mode, eta;
|
||||||
if(this.torrent.transmission_data.progress < 100){
|
if(this.torrent.transmission_data.progress < 100){
|
||||||
color = "blue";
|
color = "blue";
|
||||||
|
mode = "download";
|
||||||
value = this.torrent.transmission_data.progress;
|
value = this.torrent.transmission_data.progress;
|
||||||
if(this.torrent.transmission_data.eta !== -1){
|
if(this.torrent.transmission_data.eta !== -1){
|
||||||
eta = toHHMMSS(this.torrent.transmission_data.eta);
|
eta = toHHMMSS(this.torrent.transmission_data.eta);
|
||||||
@@ -126,10 +140,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
color = "green";
|
color = "green";
|
||||||
|
mode = "upload";
|
||||||
value = Number((this.torrent.transmission_data.uploadRatio / 5) * 100).toFixed(2)
|
value = Number((this.torrent.transmission_data.uploadRatio / 5) * 100).toFixed(2)
|
||||||
if(value > 100) value = 100;
|
if(value > 100) value = 100;
|
||||||
}
|
}
|
||||||
return {color, value, eta};
|
return {color, value, mode, eta};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,9 @@ django-filter
|
|||||||
djangorestframework-simplejwt
|
djangorestframework-simplejwt
|
||||||
channels
|
channels
|
||||||
channels_redis
|
channels_redis
|
||||||
|
|
||||||
celery
|
|
||||||
|
|
||||||
pytz
|
pytz
|
||||||
psycopg[binary]
|
|
||||||
uvicorn
|
|
||||||
transmission-rpc
|
transmission-rpc
|
||||||
stream-zip
|
stream-zip
|
||||||
anyio
|
anyio
|
||||||
websockets
|
websockets
|
||||||
uvloop
|
uvloop
|
||||||
watchfiles
|
|
||||||
2
app/requirements-dev.txt
Normal file
2
app/requirements-dev.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-r requirements-common.txt
|
||||||
|
daphne
|
||||||
2
app/requirements-prod.txt
Normal file
2
app/requirements-prod.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-r requirements-common.txt
|
||||||
|
uvicorn
|
||||||
BIN
app/static/oxpanel.png
Normal file
BIN
app/static/oxpanel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
@@ -1,9 +1,11 @@
|
|||||||
{% load django_vite %}<!DOCTYPE html>
|
{% load django_vite %}{% load static %}<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>{% block base_title %}{% block title %}{% endblock %}{% endblock %}</title>
|
<title>{% block base_title %}Oxpanel{% block title %}{% endblock %}{% endblock %}</title>
|
||||||
{% block base_css %}
|
<link rel="icon" type="image/png" href="{% static 'oxpanel.png' %}"/>
|
||||||
|
|
||||||
|
{% block base_css %}
|
||||||
{# {% stylesheet_pack 'app' %}#}
|
{# {% stylesheet_pack 'app' %}#}
|
||||||
<link data-n-head="ssr" rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
<link data-n-head="ssr" rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
||||||
{% block css %}{% endblock %}
|
{% block css %}{% endblock %}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ class TorrentEventConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
await self.channel_layer.group_discard("torrent", self.channel_name)
|
await self.channel_layer.group_discard("torrent", self.channel_name)
|
||||||
|
|
||||||
async def dispatch(self, message):
|
async def dispatch(self, message):
|
||||||
print("dispatch ws :", message)
|
|
||||||
return await super().dispatch(message)
|
return await super().dispatch(message)
|
||||||
|
|
||||||
async def receive_json(self, content, **kwargs):
|
async def receive_json(self, content, **kwargs):
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import close_old_connections
|
from django.db import close_old_connections
|
||||||
|
|
||||||
@@ -5,6 +7,7 @@ import time
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from torrent.models import Torrent
|
from torrent.models import Torrent
|
||||||
from torrent.utils import transmission_handler
|
from torrent.utils import transmission_handler
|
||||||
@@ -27,11 +30,22 @@ def update_transmission_data():
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def clean_old_torrents():
|
||||||
|
expired_date = timezone.now() - timedelta(days=settings.TORRENT_TTL)
|
||||||
|
torrent = Torrent.objects.filter(date_created__lt=expired_date).first()
|
||||||
|
if torrent:
|
||||||
|
torrent.delete()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
task_schedule = {
|
task_schedule = {
|
||||||
"update_transmission_data": {
|
"update_transmission_data": {
|
||||||
"func": update_transmission_data,
|
"func": update_transmission_data,
|
||||||
"schedule": 5.0
|
"schedule": 5.0
|
||||||
|
},
|
||||||
|
"clean_old_torrents": {
|
||||||
|
"func": clean_old_torrents,
|
||||||
|
"schedule": 60.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
histories = {}
|
histories = {}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class File(models.Model):
|
|||||||
def abs_pathname(self):
|
def abs_pathname(self):
|
||||||
return settings.DOWNLOAD_BASE_DIR / self.pathname
|
return settings.DOWNLOAD_BASE_DIR / self.pathname
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def mime_types(self):
|
def mime_types(self):
|
||||||
mime = mimetypes.guess_type(self.pathname)
|
mime = mimetypes.guess_type(self.pathname)
|
||||||
if mime:
|
if mime:
|
||||||
@@ -79,11 +79,15 @@ class File(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_stream_video(self):
|
def is_stream_video(self):
|
||||||
return self.pathname.stem in ["mp4", "flv", "webm"]
|
video_extensions = ["mp4", "flv", "webm"]
|
||||||
|
return self.pathname.suffix.lower() in video_extensions
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_video(self):
|
def is_video(self):
|
||||||
return self.pathname.stem in ["mp4", "flv", "webm", "avi", "mkv"]
|
if self.mime_types.startswith("video/"):
|
||||||
|
return True
|
||||||
|
video_extensions = ['.mp4', '.flv', '.webm', '.avi', '.mkv', '.mov', '.wmv']
|
||||||
|
return self.pathname.suffix.lower() in video_extensions
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def accel_redirect(self):
|
def accel_redirect(self):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.text import slugify
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ class FileSerializer(serializers.ModelSerializer):
|
|||||||
is_stream_video = serializers.BooleanField(read_only=True)
|
is_stream_video = serializers.BooleanField(read_only=True)
|
||||||
is_video = serializers.BooleanField(read_only=True)
|
is_video = serializers.BooleanField(read_only=True)
|
||||||
download_url = serializers.SerializerMethodField(read_only=True)
|
download_url = serializers.SerializerMethodField(read_only=True)
|
||||||
|
flux_url = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = File
|
model = File
|
||||||
@@ -28,3 +30,6 @@ class FileSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
def get_download_url(self, obj):
|
def get_download_url(self, obj):
|
||||||
return reverse("torrent:download_file", kwargs={"file_id": obj.id})
|
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)}'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import HomeView, download_file, download_torrent, pping
|
from .views import HomeView, download_file, download_torrent, pping, flux_file
|
||||||
|
|
||||||
app_name = "torrent"
|
app_name = "torrent"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -8,4 +8,5 @@ urlpatterns = [
|
|||||||
path("pping/", pping, name="pping"),
|
path("pping/", pping, name="pping"),
|
||||||
path("download_file/<uuid:file_id>", download_file, name="download_file"),
|
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"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ from user.models import User
|
|||||||
class Transmission:
|
class Transmission:
|
||||||
trpc_args = [
|
trpc_args = [
|
||||||
"id", "percentDone", "uploadRatio", "rateUpload", "rateDownload", "hashString", "status", "sizeWhenDone",
|
"id", "percentDone", "uploadRatio", "rateUpload", "rateDownload", "hashString", "status", "sizeWhenDone",
|
||||||
"leftUntilDone", "name", "eta", "totalSize"
|
"leftUntilDone", "name", "eta", "totalSize", "uploadedEver", "peersGettingFromUs", "peersSendingToUs",
|
||||||
|
"tracker", "trackerStats", "activityDate"
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -36,12 +37,13 @@ class Transmission:
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
"progress": data.progress,
|
"progress": data.progress,
|
||||||
|
"status_str": data.status,
|
||||||
**data.fields
|
**data.fields
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_all_data(self, hash_strings=None):
|
def get_all_data(self, hash_strings=None):
|
||||||
return {
|
return {
|
||||||
data.hashString: {"progress": data.progress, **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)
|
for data in self.client.get_torrents(hash_strings, self.trpc_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,23 @@ async def download_file(request, file_id):
|
|||||||
|
|
||||||
|
|
||||||
async def flux_file(request, file_id):
|
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()
|
user = await request.auser()
|
||||||
qs = File.objects.filter(
|
qs = File.objects.filter(
|
||||||
Q(torrent__user=user)
|
Q(torrent__user=user)
|
||||||
@@ -106,7 +123,7 @@ async def download_torrent(request, torrent_id):
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
response = StreamingZipFileResponse(
|
response = StreamingZipFileResponse(
|
||||||
filename="test.zip",
|
filename=f"{torrent.name}.zip",
|
||||||
file_list=[
|
file_list=[
|
||||||
(file.abs_pathname, file.rel_name)
|
(file.abs_pathname, file.rel_name)
|
||||||
async for file in torrent.files.all()
|
async for file in torrent.files.all()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from django.views.generic import CreateView
|
|||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.db.models import Count, Sum, F, IntegerField
|
from django.db.models import Count, Sum, F, IntegerField
|
||||||
|
from django.db.models.functions import Coalesce
|
||||||
|
|
||||||
from rest_framework.viewsets import ModelViewSet, GenericViewSet
|
from rest_framework.viewsets import ModelViewSet, GenericViewSet
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
@@ -96,9 +97,9 @@ class UserViewSet(mixins.RetrieveModelMixin,
|
|||||||
@action(methods=["get"], detail=False)
|
@action(methods=["get"], detail=False)
|
||||||
def user_stats(self, request):
|
def user_stats(self, request):
|
||||||
stats = User.objects.filter(id=request.user.id).aggregate(
|
stats = User.objects.filter(id=request.user.id).aggregate(
|
||||||
total_size=Sum("torrents__size"),
|
total_size=Coalesce(Sum("torrents__size"), 0),
|
||||||
total_torrent=Count("torrents"),
|
total_torrent=Coalesce(Count("torrents"), 0),
|
||||||
total_shared_torrent=Count("torrents_shares", distinct=True),
|
total_shared_torrent=Coalesce(Count("torrents_shares", distinct=True), 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
disk_usage = shutil.disk_usage("/")
|
disk_usage = shutil.disk_usage("/")
|
||||||
|
|||||||
0
app/watch_party/__init__.py
Normal file
0
app/watch_party/__init__.py
Normal file
3
app/watch_party/admin.py
Normal file
3
app/watch_party/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
app/watch_party/apps.py
Normal file
6
app/watch_party/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class WatchPartyConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'watch_party'
|
||||||
0
app/watch_party/migrations/__init__.py
Normal file
0
app/watch_party/migrations/__init__.py
Normal file
11
app/watch_party/models.py
Normal file
11
app/watch_party/models.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Room(models.Model):
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
|
||||||
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
|
created_by = models.ForeignKey("user.User", on_delete=models.CASCADE, related_name="rooms_created")
|
||||||
|
users = models.ManyToManyField("user.User", related_name="rooms")
|
||||||
|
all_admin = models.BooleanField(default=False)
|
||||||
3
app/watch_party/tests.py
Normal file
3
app/watch_party/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
app/watch_party/views.py
Normal file
3
app/watch_party/views.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
@@ -12,6 +12,9 @@ services:
|
|||||||
ports: !reset []
|
ports: !reset []
|
||||||
|
|
||||||
app:
|
app:
|
||||||
|
build:
|
||||||
|
args:
|
||||||
|
debug: true
|
||||||
restart: "no"
|
restart: "no"
|
||||||
ports:
|
ports:
|
||||||
- "${DEV_SERVER_PORT:-8080}:${DEV_SERVER_PORT:-8080}"
|
- "${DEV_SERVER_PORT:-8080}:${DEV_SERVER_PORT:-8080}"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ services:
|
|||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.docker.network=web"
|
- "traefik.docker.network=web"
|
||||||
# Routeur HTTP (port 80) pour la redirection vers HTTPS
|
# Routeur HTTP (port 80) pour la redirection vers HTTPS
|
||||||
- "traefik.http.routers.web-http.rule=Host(`${DOMAIN}`)"
|
- "traefik.http.routers.web-http.rule=${TRAEFIK_HOST_RULE}"
|
||||||
- "traefik.http.routers.web-http.entrypoints=web"
|
- "traefik.http.routers.web-http.entrypoints=web"
|
||||||
- "traefik.http.routers.web-http.middlewares=redirect-to-https"
|
- "traefik.http.routers.web-http.middlewares=redirect-to-https"
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ services:
|
|||||||
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
|
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
|
||||||
|
|
||||||
# Routeur HTTPS
|
# Routeur HTTPS
|
||||||
- "traefik.http.routers.web.rule=Host(`${DOMAIN}`)"
|
- "traefik.http.routers.web.rule=${TRAEFIK_HOST_RULE}"
|
||||||
- "traefik.http.routers.web.entrypoints=websecure"
|
- "traefik.http.routers.web.entrypoints=websecure"
|
||||||
- "traefik.http.routers.web.tls.certresolver=le"
|
- "traefik.http.routers.web.tls.certresolver=le"
|
||||||
|
|
||||||
@@ -44,8 +44,8 @@ services:
|
|||||||
- PUID=${USER_ID}
|
- PUID=${USER_ID}
|
||||||
- PGID=${GROUP_ID}
|
- PGID=${GROUP_ID}
|
||||||
ports:
|
ports:
|
||||||
- "51414:51414"
|
- "51413:51413"
|
||||||
- "51414:51414/udp"
|
- "51413:51413/udp"
|
||||||
volumes:
|
volumes:
|
||||||
- ./transmission/config:/config
|
- ./transmission/config:/config
|
||||||
- ./transmission/downloads:/downloads
|
- ./transmission/downloads:/downloads
|
||||||
@@ -58,6 +58,7 @@ services:
|
|||||||
args:
|
args:
|
||||||
puid: ${USER_ID}
|
puid: ${USER_ID}
|
||||||
pgid: ${GROUP_ID}
|
pgid: ${GROUP_ID}
|
||||||
|
debug: ${DEBUG:-false}
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
@@ -73,7 +74,7 @@ services:
|
|||||||
&& cd frontend && yarn build && cd ..
|
&& cd frontend && yarn build && cd ..
|
||||||
&& python manage.py collectstatic --noinput
|
&& python manage.py collectstatic --noinput
|
||||||
&& python manage.py migrate
|
&& python manage.py migrate
|
||||||
&& uvicorn app.asgi:application --workers 3 --host 0.0.0.0 --port 8000 --lifespan off --loop uvloop --ws websockets"
|
&& uvicorn app.asgi:application --workers 3 --host 0.0.0.0 --port 8000 --lifespan off --loop uvloop --ws websockets --log-level info"
|
||||||
|
|
||||||
# celery:
|
# celery:
|
||||||
# extends:
|
# extends:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
server {
|
server {
|
||||||
gzip off;
|
gzip off;
|
||||||
|
client_max_body_size 100M;
|
||||||
|
|
||||||
location /dl/ {
|
location /dl/ {
|
||||||
internal;
|
internal;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
server {
|
server {
|
||||||
gzip off;
|
gzip off;
|
||||||
|
client_max_body_size 100M;
|
||||||
|
|
||||||
location /dl/ {
|
location /dl/ {
|
||||||
internal;
|
internal;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"bind-address-ipv6": "::",
|
"bind-address-ipv6": "::",
|
||||||
"blocklist-enabled": false,
|
"blocklist-enabled": false,
|
||||||
"blocklist-url": "http://www.example.com/blocklist",
|
"blocklist-url": "http://www.example.com/blocklist",
|
||||||
"cache-size-mb": 4,
|
"cache-size-mb": 256,
|
||||||
"default-trackers": "",
|
"default-trackers": "",
|
||||||
"dht-enabled": true,
|
"dht-enabled": true,
|
||||||
"download-dir": "/downloads/complete",
|
"download-dir": "/downloads/complete",
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
"message-level": 2,
|
"message-level": 2,
|
||||||
"peer-congestion-algorithm": "",
|
"peer-congestion-algorithm": "",
|
||||||
"peer-id-ttl-hours": 6,
|
"peer-id-ttl-hours": 6,
|
||||||
"peer-limit-global": 200,
|
"peer-limit-global": 400,
|
||||||
"peer-limit-per-torrent": 50,
|
"peer-limit-per-torrent": 100,
|
||||||
"peer-port": 51413,
|
"peer-port": 51413,
|
||||||
"peer-port-random-high": 65535,
|
"peer-port-random-high": 65535,
|
||||||
"peer-port-random-low": 49152,
|
"peer-port-random-low": 49152,
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
"prefetch-enabled": true,
|
"prefetch-enabled": true,
|
||||||
"queue-stalled-enabled": true,
|
"queue-stalled-enabled": true,
|
||||||
"queue-stalled-minutes": 30,
|
"queue-stalled-minutes": 30,
|
||||||
"ratio-limit": 2,
|
"ratio-limit": 20,
|
||||||
"ratio-limit-enabled": false,
|
"ratio-limit-enabled": true,
|
||||||
"rename-partial-files": true,
|
"rename-partial-files": true,
|
||||||
"rpc-authentication-required": false,
|
"rpc-authentication-required": false,
|
||||||
"rpc-bind-address": "0.0.0.0",
|
"rpc-bind-address": "0.0.0.0",
|
||||||
@@ -73,10 +73,10 @@
|
|||||||
"start-added-torrents": true,
|
"start-added-torrents": true,
|
||||||
"tcp-enabled": true,
|
"tcp-enabled": true,
|
||||||
"torrent-added-verify-mode": "fast",
|
"torrent-added-verify-mode": "fast",
|
||||||
"trash-original-torrent-files": false,
|
"trash-original-torrent-files": true,
|
||||||
"umask": "002",
|
"umask": "002",
|
||||||
"upload-slots-per-torrent": 14,
|
"upload-slots-per-torrent": 50,
|
||||||
"utp-enabled": false,
|
"utp-enabled": true,
|
||||||
"watch-dir": "/watch",
|
"watch-dir": "/watch",
|
||||||
"watch-dir-enabled": true
|
"watch-dir-enabled": true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user