From c56aebd85daf2d012d2c17f6e1ee65f59884f33b Mon Sep 17 00:00:00 2001 From: maxDorninger <97409287+maxDorninger@users.noreply.github.com> Date: Sun, 27 Jul 2025 14:40:41 +0200 Subject: [PATCH] extract logic for downloading torrent file and parsing the magnetlink/torrent file into separate function --- .../torrent/download_clients/qbittorrent.py | 70 ++++++------------- .../torrent/download_clients/transmission.py | 24 +------ media_manager/torrent/utils.py | 52 +++++++++++++- 3 files changed, 75 insertions(+), 71 deletions(-) diff --git a/media_manager/torrent/download_clients/qbittorrent.py b/media_manager/torrent/download_clients/qbittorrent.py index 769a610..8199fba 100644 --- a/media_manager/torrent/download_clients/qbittorrent.py +++ b/media_manager/torrent/download_clients/qbittorrent.py @@ -1,9 +1,6 @@ -import hashlib import logging -import bencoder import qbittorrentapi -import requests from qbittorrentapi import Conflict409Error from media_manager.config import AllEncompassingConfig @@ -12,6 +9,7 @@ from media_manager.torrent.download_clients.abstractDownloadClient import ( AbstractDownloadClient, ) from media_manager.torrent.schemas import TorrentStatus, Torrent +from media_manager.torrent.utils import get_torrent_hash log = logging.getLogger(__name__) @@ -96,56 +94,30 @@ class QbittorrentDownloadClient(AbstractDownloadClient): :param indexer_result: The indexer query result of the torrent file to download. :return: The torrent object with calculated hash and initial status. """ + global answer log.info(f"Attempting to download torrent: {indexer_result.title}") + torrent_hash = get_torrent_hash(torrent=indexer_result) - torrent_filepath = ( - AllEncompassingConfig().misc.torrent_directory - / f"{indexer_result.title}.torrent" + log.info( + f"Downloading torrent {indexer_result.title} with download_url: {indexer_result.download_url}" ) + try: + self.api_client.auth_log_in() + answer = self.api_client.torrents_add( + category="MediaManager", + urls=indexer_result.download_url, + save_path=indexer_result.title, + ) + finally: + self.api_client.auth_log_out() - if torrent_filepath.exists(): - log.warning(f"Torrent already exists: {torrent_filepath}") - # Calculate hash from existing file - with open(torrent_filepath, "rb") as file: - content = file.read() - decoded_content = bencoder.decode(content) - torrent_hash = hashlib.sha1( - bencoder.encode(decoded_content[b"info"]) - ).hexdigest() - else: - # Download the torrent file - with open(torrent_filepath, "wb") as file: - content = requests.get(str(indexer_result.download_url)).content - file.write(content) - - # Calculate hash and add to qBittorrent - with open(torrent_filepath, "rb") as file: - content = file.read() - try: - decoded_content = bencoder.decode(content) - except Exception as e: - log.error(f"Failed to decode torrent file: {e}") - raise e - - torrent_hash = hashlib.sha1( - bencoder.encode(decoded_content[b"info"]) - ).hexdigest() - - try: - self.api_client.auth_log_in() - answer = self.api_client.torrents_add( - category="MediaManager", - torrent_files=content, - save_path=indexer_result.title, - ) - finally: - self.api_client.auth_log_out() - - if answer != "Ok.": - log.error(f"Failed to download torrent. API response: {answer}") - raise RuntimeError( - f"Failed to download torrent, API-Answer isn't 'Ok.'; API Answer: {answer}" - ) + if answer != "Ok.": + log.error( + f"Failed to download torrent, API-Answer isn't 'Ok.'; API Answer: {answer}" + ) + raise RuntimeError( + f"Failed to download torrent, API-Answer isn't 'Ok.'; API Answer: {answer}" + ) log.info(f"Successfully processed torrent: {indexer_result.title}") diff --git a/media_manager/torrent/download_clients/transmission.py b/media_manager/torrent/download_clients/transmission.py index 58a3ead..03b89cf 100644 --- a/media_manager/torrent/download_clients/transmission.py +++ b/media_manager/torrent/download_clients/transmission.py @@ -1,16 +1,13 @@ -import hashlib import logging -import bencoder -import requests import transmission_rpc - from media_manager.config import AllEncompassingConfig from media_manager.indexer.schemas import IndexerQueryResult from media_manager.torrent.download_clients.abstractDownloadClient import ( AbstractDownloadClient, ) from media_manager.torrent.schemas import TorrentStatus, Torrent +from media_manager.torrent.utils import get_torrent_hash log = logging.getLogger(__name__) @@ -56,23 +53,8 @@ class TransmissionDownloadClient(AbstractDownloadClient): :return: The torrent object with calculated hash and initial status. """ log.info(f"Attempting to download torrent: {indexer_result.title}") - - try: - response = requests.get(str(indexer_result.download_url), timeout=30) - response.raise_for_status() - torrent_content = response.content - except Exception as e: - log.error(f"Failed to download torrent file: {e}") - raise - - try: - decoded_content = bencoder.decode(torrent_content) - torrent_hash = hashlib.sha1( - bencoder.encode(decoded_content[b"info"]) - ).hexdigest() - except Exception as e: - log.error(f"Failed to decode torrent file: {e}") - raise + torrent_hash = get_torrent_hash(torrent=indexer_result) + log.info(f"parsed torrent hash: {torrent_hash}") download_dir = ( AllEncompassingConfig().misc.torrent_directory / indexer_result.title ) diff --git a/media_manager/torrent/utils.py b/media_manager/torrent/utils.py index 208453f..585092d 100644 --- a/media_manager/torrent/utils.py +++ b/media_manager/torrent/utils.py @@ -1,10 +1,15 @@ +import hashlib import logging import mimetypes from pathlib import Path import shutil -import patoolib +import bencoder +import patoolib +import requests +import libtorrent from media_manager.config import AllEncompassingConfig +from media_manager.indexer.schemas import IndexerQueryResult from media_manager.torrent.schemas import Torrent log = logging.getLogger(__name__) @@ -102,3 +107,48 @@ def import_torrent(torrent: Torrent) -> (list[Path], list[Path], list[Path]): f"Found {len(all_files)} files ({len(video_files)} video files, {len(subtitle_files)} subtitle files) for further processing." ) return video_files, subtitle_files, all_files + + +def get_torrent_hash(torrent: IndexerQueryResult) -> str: + """ + Helper method to get the torrent hash from the torrent object. + + :param torrent: The torrent object. + :return: The hash of the torrent. + """ + torrent_filepath = ( + AllEncompassingConfig().misc.torrent_directory / f"{torrent.title}.torrent" + ) + if torrent_filepath.exists(): + log.warning(f"Torrent file already exists at: {torrent_filepath}") + + if torrent.download_url.startswith("magnet:"): + log.info(f"Parsing torrent with magnet URL: {torrent.title}") + log.debug(f"Magnet URL: {torrent.download_url}") + torrent_hash = str(libtorrent.parse_magnet_uri(torrent.download_url).info_hash) + else: + # downloading the torrent file + log.info(f"Downloading .torrent file of torrent: {torrent.title}") + try: + response = requests.get(str(torrent.download_url), timeout=30) + response.raise_for_status() + torrent_content = response.content + except Exception as e: + log.error(f"Failed to download torrent file: {e}") + raise + + # saving the torrent file + with open(torrent_filepath, "wb") as file: + file.write(torrent_content) + + # parsing info hash + log.debug(f"parsing torrent file: {torrent.download_url}") + try: + decoded_content = bencoder.decode(torrent_content) + torrent_hash = hashlib.sha1( + bencoder.encode(decoded_content[b"info"]) + ).hexdigest() + except Exception as e: + log.error(f"Failed to decode torrent file: {e}") + raise + return torrent_hash