Init
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -3,7 +3,7 @@
|
|||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="Python 3.13 (oxapp25)" />
|
<option name="sdkName" value="Python 3.13 (oxapp25)" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (oxapp25) (2)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 virtualenv at D:\Dev\oxapp25\.venv" project-jdk-type="Python SDK" />
|
||||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||||
<option name="version" value="3" />
|
<option name="version" value="3" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
2
.idea/oxapp25.iml
generated
2
.idea/oxapp25.iml
generated
@@ -4,7 +4,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.13 (oxapp25) (2)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.13 virtualenv at D:\Dev\oxapp25\.venv" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
79
main.py
79
main.py
@@ -1,6 +1,4 @@
|
|||||||
|
from PySide6.QtCore import QStandardPaths, QDataStream, QByteArray, QIODevice, Signal, Qt, QTimer, QCryptographicHash
|
||||||
|
|
||||||
from PySide6.QtCore import QStandardPaths, QDataStream, QByteArray, QIODevice, Signal, Qt
|
|
||||||
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
|
|
||||||
@@ -10,6 +8,10 @@ import asyncio
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import argparse
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import base64
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from src.logs import configure_logging
|
from src.logs import configure_logging
|
||||||
@@ -23,6 +25,7 @@ class SingleApplication(QApplication):
|
|||||||
def __init__(self, app_id, args):
|
def __init__(self, app_id, args):
|
||||||
super().__init__(args)
|
super().__init__(args)
|
||||||
self.app_id = app_id
|
self.app_id = app_id
|
||||||
|
self.shared_key = hashlib.sha256(app_id.encode()).hexdigest()[:16]
|
||||||
self.server = None
|
self.server = None
|
||||||
self.is_primary_instance = self.try_connect_to_primary()
|
self.is_primary_instance = self.try_connect_to_primary()
|
||||||
|
|
||||||
@@ -34,6 +37,52 @@ class SingleApplication(QApplication):
|
|||||||
# En cas d'erreur (serveur déjà existant mais zombie), on le supprime et on réessaie
|
# En cas d'erreur (serveur déjà existant mais zombie), on le supprime et on réessaie
|
||||||
QLocalServer.removeServer(self.app_id)
|
QLocalServer.removeServer(self.app_id)
|
||||||
self.server.listen(self.app_id)
|
self.server.listen(self.app_id)
|
||||||
|
else:
|
||||||
|
QTimer.singleShot(0, self.quit)
|
||||||
|
|
||||||
|
def encrypt_data(self, data_str):
|
||||||
|
"""Méthode simple pour brouiller les données"""
|
||||||
|
# Générer une "nonce" aléatoire pour éviter que les mêmes données produisent le même résultat
|
||||||
|
nonce = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8))
|
||||||
|
|
||||||
|
# Combiner la nonce, la clé et les données
|
||||||
|
combined = nonce + self.shared_key + data_str
|
||||||
|
|
||||||
|
# Utiliser SHA-256 pour obtenir un hash
|
||||||
|
hash_obj = QCryptographicHash(QCryptographicHash.Algorithm.Sha256)
|
||||||
|
hash_obj.addData(combined.encode())
|
||||||
|
signature = hash_obj.result().toHex().data().decode()[:16]
|
||||||
|
|
||||||
|
# Encoder le tout en base64
|
||||||
|
encoded = base64.b64encode((nonce + signature + data_str).encode()).decode()
|
||||||
|
return encoded
|
||||||
|
|
||||||
|
def decrypt_data(self, encoded_str):
|
||||||
|
"""Déchiffre les données et vérifie leur intégrité"""
|
||||||
|
try:
|
||||||
|
# Décoder de base64
|
||||||
|
decoded = base64.b64decode(encoded_str.encode()).decode()
|
||||||
|
|
||||||
|
# Extraire nonce, signature et données
|
||||||
|
nonce = decoded[:8]
|
||||||
|
signature = decoded[8:24]
|
||||||
|
data_str = decoded[24:]
|
||||||
|
|
||||||
|
# Vérifier la signature
|
||||||
|
combined = nonce + self.shared_key + data_str
|
||||||
|
hash_obj = QCryptographicHash(QCryptographicHash.Algorithm.Sha256)
|
||||||
|
hash_obj.addData(combined.encode())
|
||||||
|
expected_signature = hash_obj.result().toHex().data().decode()[:16]
|
||||||
|
|
||||||
|
if signature != expected_signature:
|
||||||
|
print("Signature invalide, données potentiellement corrompues ou falsifiées")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return data_str
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur lors du déchiffrement: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def try_connect_to_primary(self):
|
def try_connect_to_primary(self):
|
||||||
"""Essaie de se connecter à l'instance primaire de l'application"""
|
"""Essaie de se connecter à l'instance primaire de l'application"""
|
||||||
@@ -43,26 +92,35 @@ class SingleApplication(QApplication):
|
|||||||
if socket.waitForConnected(500):
|
if socket.waitForConnected(500):
|
||||||
# Récupérer les arguments pour les envoyer à l'instance primaire
|
# Récupérer les arguments pour les envoyer à l'instance primaire
|
||||||
args = sys.argv[1:] if len(sys.argv) > 1 else []
|
args = sys.argv[1:] if len(sys.argv) > 1 else []
|
||||||
|
encrypt_args = self.encrypt_data(";".join(args))
|
||||||
|
|
||||||
# Envoyer les arguments à l'instance primaire
|
# Envoyer les arguments à l'instance primaire
|
||||||
stream = QDataStream(socket)
|
stream = QDataStream(socket)
|
||||||
stream.writeQString(";".join(args))
|
stream.writeQString(encrypt_args)
|
||||||
socket.flush()
|
socket.flush()
|
||||||
|
socket.waitForBytesWritten(1000)
|
||||||
socket.disconnectFromServer()
|
socket.disconnectFromServer()
|
||||||
|
|
||||||
|
QTimer.singleShot(0, self.quit)
|
||||||
|
|
||||||
return False # Ce n'est pas l'instance primaire
|
return False # Ce n'est pas l'instance primaire
|
||||||
return True # C'est l'instance primaire
|
return True # C'est l'instance primaire
|
||||||
|
|
||||||
def handle_new_connection(self):
|
def handle_new_connection(self):
|
||||||
"""Gère une nouvelle connexion d'une instance secondaire"""
|
"""Gère une nouvelle connexion d'une instance secondaire"""
|
||||||
socket = self.server.nextPendingConnection()
|
socket = self.server.nextPendingConnection()
|
||||||
if socket.waitForReadyRead(1000):
|
|
||||||
|
if socket.waitForReadyRead(2000):
|
||||||
stream = QDataStream(socket)
|
stream = QDataStream(socket)
|
||||||
args_str = stream.readQString()
|
|
||||||
args = args_str.split(";") if args_str else []
|
encrypted_args = stream.readQString()
|
||||||
|
args_str = self.decrypt_data(encrypted_args)
|
||||||
|
|
||||||
# Émettre un signal pour informer l'application des fichiers à ouvrir
|
# Émettre un signal pour informer l'application des fichiers à ouvrir
|
||||||
if args:
|
if args_str:
|
||||||
self.files_received.emit(args)
|
args = args_str.split(";") if args_str else []
|
||||||
|
if args:
|
||||||
|
self.files_received.emit(args)
|
||||||
|
|
||||||
socket.disconnectFromServer()
|
socket.disconnectFromServer()
|
||||||
|
|
||||||
@@ -84,6 +142,9 @@ if __name__ == "__main__":
|
|||||||
app_id = "OxAPP25"
|
app_id = "OxAPP25"
|
||||||
app = SingleApplication(app_id, sys.argv)
|
app = SingleApplication(app_id, sys.argv)
|
||||||
|
|
||||||
|
if not app.is_primary_instance:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
event_loop = qasync.QEventLoop(app)
|
event_loop = qasync.QEventLoop(app)
|
||||||
asyncio.set_event_loop(event_loop)
|
asyncio.set_event_loop(event_loop)
|
||||||
|
|
||||||
|
|||||||
@@ -31,13 +31,6 @@ class ConfManager(QObject):
|
|||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.logger.setLevel(logging.DEBUG) # Niveau DEBUG pour capturer tous les messages
|
self.logger.setLevel(logging.DEBUG) # Niveau DEBUG pour capturer tous les messages
|
||||||
|
|
||||||
# S'assurer qu'un gestionnaire de log est présent
|
|
||||||
if not self.logger.handlers:
|
|
||||||
handler = logging.StreamHandler()
|
|
||||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
||||||
handler.setFormatter(formatter)
|
|
||||||
self.logger.addHandler(handler)
|
|
||||||
|
|
||||||
self.logger.info("Initialisation de ConfManager")
|
self.logger.info("Initialisation de ConfManager")
|
||||||
|
|
||||||
# Verrou pour les opérations de fichier (thread-safety)
|
# Verrou pour les opérations de fichier (thread-safety)
|
||||||
|
|||||||
201
src/download.py
201
src/download.py
@@ -1,3 +1,5 @@
|
|||||||
|
import traceback
|
||||||
|
|
||||||
from PySide6.QtCore import QObject, Signal, QTimer
|
from PySide6.QtCore import QObject, Signal, QTimer
|
||||||
from PySide6.QtNetwork import QNetworkCookie
|
from PySide6.QtNetwork import QNetworkCookie
|
||||||
|
|
||||||
@@ -13,6 +15,7 @@ import httpx
|
|||||||
|
|
||||||
from src.datatypes import FileType, FileStatsType
|
from src.datatypes import FileType, FileStatsType
|
||||||
from src.async_file import async_open
|
from src.async_file import async_open
|
||||||
|
from src.utils import aexec_in
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from windows.main_window import MainWindow
|
from windows.main_window import MainWindow
|
||||||
|
|
||||||
@@ -36,7 +39,7 @@ class DownloadManager(QObject):
|
|||||||
self.task_stats: dict[str, FileStatsType] = {}
|
self.task_stats: dict[str, FileStatsType] = {}
|
||||||
self.waiter = asyncio.Event()
|
self.waiter = asyncio.Event()
|
||||||
self.client_session: None|httpx.AsyncClient = None
|
self.client_session: None|httpx.AsyncClient = None
|
||||||
self.cookies = []
|
self.cookies = {}
|
||||||
|
|
||||||
# slots
|
# slots
|
||||||
# self.status_updated.connect(lambda data: self.conf.set_value("files", self.files))
|
# self.status_updated.connect(lambda data: self.conf.set_value("files", self.files))
|
||||||
@@ -55,21 +58,26 @@ class DownloadManager(QObject):
|
|||||||
self.status = {}
|
self.status = {}
|
||||||
self.update_status()
|
self.update_status()
|
||||||
|
|
||||||
async def initialize(self):
|
# async def initialize(self):
|
||||||
self.client_session = httpx.AsyncClient(
|
# self.client_session = httpx.AsyncClient(
|
||||||
timeout=httpx.Timeout(None),
|
# timeout=httpx.Timeout(
|
||||||
follow_redirects=True,
|
# connect=5.0, # 5 secondes pour établir la connexion
|
||||||
verify=False,
|
# read=None, # Pas de timeout pour la lecture des données (téléchargement)
|
||||||
# http2=True,
|
# write=60.0, # 60 secondes pour envoyer des données
|
||||||
)
|
# pool=5.0 # 5 secondes pour obtenir une connexion du pool
|
||||||
|
# ),
|
||||||
for cookie in self.cookies:
|
# follow_redirects=True,
|
||||||
await self.add_cookie(cookie)
|
# verify=False,
|
||||||
self.logger.info("Session aiohttp initialisée")
|
# # http2=True,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# for cookie in self.cookies:
|
||||||
|
# await self.add_cookie(cookie)
|
||||||
|
# self.logger.info("Session aiohttp initialisée")
|
||||||
|
|
||||||
async def loop_queue(self):
|
async def loop_queue(self):
|
||||||
if self.client_session is None:
|
# if self.client_session is None:
|
||||||
await self.initialize()
|
# await self.initialize()
|
||||||
|
|
||||||
self.logger.info("Démarrage de la boucle de téléchargement")
|
self.logger.info("Démarrage de la boucle de téléchargement")
|
||||||
while True:
|
while True:
|
||||||
@@ -78,6 +86,8 @@ class DownloadManager(QObject):
|
|||||||
else:
|
else:
|
||||||
file = await self.next_file()
|
file = await self.next_file()
|
||||||
if file is None:
|
if file is None:
|
||||||
|
if not self.tasks:
|
||||||
|
await self.set_pause(True)
|
||||||
await self.wait()
|
await self.wait()
|
||||||
else:
|
else:
|
||||||
self.tasks[file] = asyncio.create_task(self.download_file(file))
|
self.tasks[file] = asyncio.create_task(self.download_file(file))
|
||||||
@@ -89,22 +99,52 @@ class DownloadManager(QObject):
|
|||||||
self.logger.info("loop queue resumed")
|
self.logger.info("loop queue resumed")
|
||||||
|
|
||||||
|
|
||||||
def set_pause(self, value):
|
async def set_pause(self, value):
|
||||||
if self.pause == value:
|
if self.pause == value:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.pause = value
|
self.pause = value
|
||||||
if self.pause:
|
try:
|
||||||
for file_id, task in self.tasks.items():
|
if self.pause:
|
||||||
if not task.done():
|
self.logger.info("Essaie de la mise en pause")
|
||||||
task.cancel()
|
|
||||||
self.logger.info("Tous les téléchargements ont été mis en pause")
|
# on attend 0.5 sec pour voir si le téléchargement s'interrompt proprement avec le self.pause.
|
||||||
else:
|
asyncio.create_task(aexec_in(1, self._clean_connections))
|
||||||
self.pause = False
|
# await asyncio.sleep(0.5)
|
||||||
self.waiter.set()
|
# for task in self.tasks.values():
|
||||||
self.logger.info("Reprise des téléchargements")
|
# if task and not task.done():
|
||||||
|
# task.cancel()
|
||||||
|
# # Attendre que les tâches se terminent proprement
|
||||||
|
# await asyncio.gather(*[t for t in self.tasks.values() if t and not t.done()],
|
||||||
|
# return_exceptions=True)
|
||||||
|
else:
|
||||||
|
self.pause = False
|
||||||
|
self.waiter.set()
|
||||||
|
self.logger.info("Reprise des téléchargements")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Erreur lors de la mise en pause: {e}")
|
||||||
|
|
||||||
self.update_status()
|
self.update_status()
|
||||||
|
|
||||||
|
async def _clean_connections(self):
|
||||||
|
"""Fonction asynchrone interne pour nettoyer les connexions lors de la pause"""
|
||||||
|
self.logger.debug("cleaning connections")
|
||||||
|
try:
|
||||||
|
# Annuler proprement les tâches en cours
|
||||||
|
for file, task in self.tasks.items():
|
||||||
|
self.logger.error(f"task for {file.target} not cancelled: {task}")
|
||||||
|
if task and not task.done():
|
||||||
|
task.cancel()
|
||||||
|
self.logger.error(f"trying to cancel task for {file.target}")
|
||||||
|
|
||||||
|
|
||||||
|
# Attendre que les tâches se terminent
|
||||||
|
if self.tasks.values():
|
||||||
|
await asyncio.wait([t for t in self.tasks.values() if t and not t.done()],
|
||||||
|
timeout=2.0)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Erreur lors du nettoyage des connexions: {e}")
|
||||||
|
|
||||||
async def next_file(self) -> FileType | None:
|
async def next_file(self) -> FileType | None:
|
||||||
self.logger.debug("Recherche du prochain fichier à télécharger")
|
self.logger.debug("Recherche du prochain fichier à télécharger")
|
||||||
for file in self.files.values():
|
for file in self.files.values():
|
||||||
@@ -138,43 +178,60 @@ class DownloadManager(QObject):
|
|||||||
headers.update({"Range": f"bytes={stats.downloaded_size}-{stats.total_size}"})
|
headers.update({"Range": f"bytes={stats.downloaded_size}-{stats.total_size}"})
|
||||||
|
|
||||||
mode: Literal["ab", "wb"] = "ab" if stats.downloaded_size > 0 else "wb"
|
mode: Literal["ab", "wb"] = "ab" if stats.downloaded_size > 0 else "wb"
|
||||||
|
|
||||||
|
# Initilisation de la session:
|
||||||
try:
|
try:
|
||||||
async with self.client_session.stream("GET", file.url, headers=headers) as response:
|
async with httpx.AsyncClient(
|
||||||
async with await anyio.open_file(file_path, mode) as f:
|
timeout=httpx.Timeout(connect=5.0, read=None, write=60.0, pool=5.0),
|
||||||
|
follow_redirects=True,
|
||||||
|
verify=False,
|
||||||
|
cookies=self.cookies,
|
||||||
|
) as client:
|
||||||
|
# requête pour le téléchargement
|
||||||
|
async with client.stream("GET", file.url, headers=headers) as response:
|
||||||
|
# on trigger les bad requests
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
# on ouvre le fichier pour commencer à écrire
|
||||||
|
async with await anyio.open_file(file_path, mode) as f:
|
||||||
|
|
||||||
last_update_time = time.monotonic()
|
last_update_time = time.monotonic()
|
||||||
last_downloaded_size = stats.downloaded_size
|
last_downloaded_size = stats.downloaded_size
|
||||||
async for chunk in response.aiter_bytes(self.chunk_size):
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
await f.write(chunk)
|
|
||||||
if self.pause:
|
|
||||||
break
|
|
||||||
chunk_size = len(chunk)
|
|
||||||
stats.downloaded_size += chunk_size
|
|
||||||
|
|
||||||
current_time = time.monotonic()
|
async for chunk in response.aiter_bytes(self.chunk_size):
|
||||||
elapsed_time = current_time - last_update_time
|
if self.pause:
|
||||||
|
await response.aclose()
|
||||||
|
break
|
||||||
|
|
||||||
if elapsed_time >= 1.0:
|
if not chunk:
|
||||||
bytes_downloaded = stats.downloaded_size - last_downloaded_size
|
break
|
||||||
current_speed = bytes_downloaded / elapsed_time
|
|
||||||
if stats.speed > 0:
|
await f.write(chunk)
|
||||||
stats.speed = round(0.7 * current_speed + 0.3 * stats.speed)
|
chunk_size = len(chunk)
|
||||||
|
stats.downloaded_size += chunk_size
|
||||||
|
|
||||||
|
current_time = time.monotonic()
|
||||||
|
elapsed_time = current_time - last_update_time
|
||||||
|
|
||||||
|
if elapsed_time >= 1.0:
|
||||||
|
bytes_downloaded = stats.downloaded_size - last_downloaded_size
|
||||||
|
current_speed = bytes_downloaded / elapsed_time
|
||||||
|
if stats.speed > 0:
|
||||||
|
stats.speed = round(0.7 * current_speed + 0.3 * stats.speed)
|
||||||
|
else:
|
||||||
|
stats.speed = round(current_speed)
|
||||||
|
|
||||||
|
last_update_time = current_time
|
||||||
|
last_downloaded_size = stats.downloaded_size
|
||||||
else:
|
else:
|
||||||
stats.speed = round(current_speed)
|
await asyncio.sleep(0.005)
|
||||||
|
|
||||||
last_update_time = current_time
|
|
||||||
last_downloaded_size = stats.downloaded_size
|
|
||||||
else:
|
|
||||||
await asyncio.sleep(0.005)
|
|
||||||
except httpx.HTTPStatusError as e:
|
except httpx.HTTPStatusError as e:
|
||||||
self.logger.error(f"Erreur HTTP lors du téléchargement de {file.target}: {e.response.status_code} - {e}")
|
self.logger.error(
|
||||||
|
f"Erreur HTTP lors du téléchargement de {file.target}: {e.response.status_code} - {e}")
|
||||||
file.error = f"Erreur HTTP {e.response.status_code}: {str(e)}"
|
file.error = f"Erreur HTTP {e.response.status_code}: {str(e)}"
|
||||||
|
|
||||||
except httpx.TimeoutException as e:
|
except httpx.TimeoutException as e:
|
||||||
self.logger.error(f"Délai d'attente dépassé lors du téléchargement de {file.target}: {str(e)}")
|
self.logger.error(
|
||||||
|
f"Délai d'attente dépassé lors du téléchargement de {file.target}: {str(e)}")
|
||||||
file.error = f"Délai d'attente dépassé: {str(e)}"
|
file.error = f"Délai d'attente dépassé: {str(e)}"
|
||||||
|
|
||||||
except httpx.ConnectError as e:
|
except httpx.ConnectError as e:
|
||||||
@@ -198,13 +255,17 @@ class DownloadManager(QObject):
|
|||||||
file.error = f"Erreur d'E/S: {str(e)}"
|
file.error = f"Erreur d'E/S: {str(e)}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Erreur inattendue lors du téléchargement de {file.target}: {type(e).__name__} - {str(e)}")
|
self.logger.error(
|
||||||
|
f"Erreur inattendue lors du téléchargement de {file.target}: {type(e).__name__} - {str(e)}")
|
||||||
|
print(traceback.format_exc())
|
||||||
file.error = f"Erreur inattendue: {str(e)}"
|
file.error = f"Erreur inattendue: {str(e)}"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
file.downloaded = True
|
if self.pause:
|
||||||
self.logger.info(f"Téléchargement de {file.target} terminé avec succès")
|
self.logger.info(f"Téléchargement de {file.target} mis en pause")
|
||||||
|
else:
|
||||||
|
file.downloaded = True
|
||||||
|
self.logger.info(f"Téléchargement de {file.target} terminé avec succès")
|
||||||
finally:
|
finally:
|
||||||
await self.task_ended(file)
|
await self.task_ended(file)
|
||||||
|
|
||||||
@@ -253,29 +314,21 @@ class DownloadManager(QObject):
|
|||||||
self.files_updated.emit(self.files)
|
self.files_updated.emit(self.files)
|
||||||
|
|
||||||
def update_status(self):
|
def update_status(self):
|
||||||
self.status = {
|
new_status = {
|
||||||
"pause": self.pause,
|
"pause": self.pause,
|
||||||
"max_worker": self.max_worker,
|
"max_worker": self.max_worker,
|
||||||
"total_files": len(self.files),
|
"total_files": len(self.files),
|
||||||
"downloaded_files": sum(file.downloaded for file in self.files.values() if file.downloaded),
|
"downloaded_files": sum(file.downloaded for file in self.files.values() if file.downloaded),
|
||||||
"downloading": [task.id for task in self.tasks.keys()],
|
"downloading": [task.id for task in self.tasks.keys()],
|
||||||
"total_size": sum(file.total_size for file in self.files.values()),
|
"total_size": sum(file.total_size for file in self.files.values()),
|
||||||
"downloaded_size": sum(file.total_size for file in self.files.values() if file.downloaded) + sum((dl_stat.downloaded_size for dl_stat in self.task_stats.values()), 0),
|
# "downloaded_size": sum(file.total_size for file in self.files.values() if file.downloaded) + sum((dl_stat.downloaded_size for dl_stat in self.task_stats.values()), 0),
|
||||||
|
"downloaded_size": sum(file.size_downloaded for file in self.files.values()),
|
||||||
"speed": sum((dl_stat.speed for dl_stat in self.task_stats.values()), 0),
|
"speed": sum((dl_stat.speed for dl_stat in self.task_stats.values()), 0),
|
||||||
"downloader_stats": {key: dl_stat.to_dict() for key, dl_stat in self.task_stats.items()}
|
"downloader_stats": {key: dl_stat.to_dict() for key, dl_stat in self.task_stats.items()}
|
||||||
}
|
}
|
||||||
self.status_updated.emit(self.status)
|
if self.status != new_status:
|
||||||
|
self.status = new_status
|
||||||
# def update_dl_stats(self):
|
self.status_updated.emit(self.status)
|
||||||
# old_stats = deepcopy(self.dl_stats)
|
|
||||||
# self.dl_stats = {
|
|
||||||
# "speed": sum((dl_stat.speed for dl_stat in self.task_stats.values()), 0),
|
|
||||||
# "downloaded_size": sum((dl_stat.downloaded_size for dl_stat in self.task_stats.values()), 0),
|
|
||||||
# "downloading_stats": {key: dl_stat.to_dict() for key, dl_stat in self.task_stats.items()},
|
|
||||||
# }
|
|
||||||
# if old_stats != self.dl_stats:
|
|
||||||
# self.stats_updated.emit(self.dl_stats)
|
|
||||||
# return self.dl_stats
|
|
||||||
|
|
||||||
async def add_cookie(self, cookie: QNetworkCookie):
|
async def add_cookie(self, cookie: QNetworkCookie):
|
||||||
"""
|
"""
|
||||||
@@ -284,16 +337,8 @@ class DownloadManager(QObject):
|
|||||||
Args:
|
Args:
|
||||||
cookie: Un objet QNetworkCookie de PySide6
|
cookie: Un objet QNetworkCookie de PySide6
|
||||||
"""
|
"""
|
||||||
if self.client_session is None:
|
|
||||||
# Si la session n'est pas encore initialisée, stocker le cookie pour plus tard
|
|
||||||
self.cookies.append(cookie)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Extraction des informations essentielles du QNetworkCookie
|
# Extraction des informations essentielles du QNetworkCookie
|
||||||
name = cookie.name().data().decode()
|
name = cookie.name().data().decode()
|
||||||
value = cookie.value().data().decode()
|
value = cookie.value().data().decode()
|
||||||
|
self.cookies.update({name: value})
|
||||||
# Ajout direct du cookie sans attributs supplémentaires
|
|
||||||
self.client_session.cookies[name] = value
|
|
||||||
|
|
||||||
self.logger.info(f"Cookie ajouté: {name}={value}")
|
self.logger.info(f"Cookie ajouté: {name}={value}")
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
from PySide6.QtCore import QObject, Slot, Signal, QTimer, Property
|
from PySide6.QtCore import QObject, Slot, Signal, QTimer, Property
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -51,12 +53,12 @@ class WebHandler(QObject):
|
|||||||
|
|
||||||
@Slot(result=str)
|
@Slot(result=str)
|
||||||
def on_site_ready(self):
|
def on_site_ready(self):
|
||||||
self.site_ready = True
|
self.site_loaded = True
|
||||||
self.on_site_ready.emit()
|
self.site_ready.emit()
|
||||||
|
|
||||||
@Slot(bool)
|
@Slot(bool)
|
||||||
def set_pause(self, value):
|
def set_pause(self, value):
|
||||||
self.download_manager.set_pause(value)
|
asyncio.create_task(self.download_manager.set_pause(value))
|
||||||
|
|
||||||
@Slot(list)
|
@Slot(list)
|
||||||
@Slot(str)
|
@Slot(str)
|
||||||
@@ -70,7 +72,7 @@ class WebHandler(QObject):
|
|||||||
@Slot(list)
|
@Slot(list)
|
||||||
def del_files(self, file_ids):
|
def del_files(self, file_ids):
|
||||||
if isinstance(file_ids, str):
|
if isinstance(file_ids, str):
|
||||||
file_ids = json.loads(file_ids)
|
file_ids = [file_ids]
|
||||||
self.download_manager.del_files(file_ids)
|
self.download_manager.del_files(file_ids)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
from PySide6.QtNetwork import QNetworkCookie
|
from PySide6.QtNetwork import QNetworkCookie
|
||||||
|
|
||||||
import time
|
import time
|
||||||
@@ -94,3 +96,7 @@ class RestrictedUnpickler(pickle.Unpickler):
|
|||||||
raise pickle.UnpicklingError(f"Accès refusé à la classe {module}.{name} pour des raisons de sécurité")
|
raise pickle.UnpicklingError(f"Accès refusé à la classe {module}.{name} pour des raisons de sécurité")
|
||||||
|
|
||||||
|
|
||||||
|
async def aexec_in(secs, func):
|
||||||
|
await asyncio.sleep(secs)
|
||||||
|
return await func()
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ class SiteWindow(QWebEngineView):
|
|||||||
self.persistent_profile = QWebEngineProfile("OxAppProfile", parent=self)
|
self.persistent_profile = QWebEngineProfile("OxAppProfile", parent=self)
|
||||||
self.cookie_store = self.persistent_profile.cookieStore()
|
self.cookie_store = self.persistent_profile.cookieStore()
|
||||||
self.cookie_store.cookieAdded.connect(self.on_cookie_added.emit)
|
self.cookie_store.cookieAdded.connect(self.on_cookie_added.emit)
|
||||||
self.cookie_store.cookieAdded.connect(self.test_cookie)
|
|
||||||
self.persistent_profile.setHttpCacheType(QWebEngineProfile.HttpCacheType.MemoryHttpCache)
|
self.persistent_profile.setHttpCacheType(QWebEngineProfile.HttpCacheType.MemoryHttpCache)
|
||||||
self.persistent_profile.setPersistentStoragePath(str(self.conf.app_config_path / "web_cache"))
|
self.persistent_profile.setPersistentStoragePath(str(self.conf.app_config_path / "web_cache"))
|
||||||
self.persistent_profile.setPersistentCookiesPolicy(QWebEngineProfile.PersistentCookiesPolicy.AllowPersistentCookies)
|
self.persistent_profile.setPersistentCookiesPolicy(QWebEngineProfile.PersistentCookiesPolicy.AllowPersistentCookies)
|
||||||
@@ -60,14 +59,9 @@ class SiteWindow(QWebEngineView):
|
|||||||
|
|
||||||
self.load(parent.url)
|
self.load(parent.url)
|
||||||
|
|
||||||
def test_cookie(self, *args, **kwargs):
|
|
||||||
print("cook", *args, **kwargs)
|
|
||||||
# self.cookie_store.loadAllCookies()
|
|
||||||
|
|
||||||
|
|
||||||
@Slot(bool)
|
@Slot(bool)
|
||||||
def on_load_finished(self, is_success):
|
def on_load_finished(self, is_success):
|
||||||
print("load finished")
|
|
||||||
if is_success:
|
if is_success:
|
||||||
api_file = QFile(":/qtwebchannel/qwebchannel.js")
|
api_file = QFile(":/qtwebchannel/qwebchannel.js")
|
||||||
api_file.open(QIODevice.OpenModeFlag.ReadOnly)
|
api_file.open(QIODevice.OpenModeFlag.ReadOnly)
|
||||||
|
|||||||
Reference in New Issue
Block a user