mirror of
https://github.com/KeksPirates/SoftwareManager.git
synced 2026-08-03 17:39:42 +02:00
f5809c466a
Add network timeouts and more robust error handling across scrapers/hosts, improve logging concurrency, and refine download handling: - Hosts/sources: add request timeouts (15s) and exception handling to buzzheavier, gofile, steamrip, uztracker and rutracker; use urllib.parse.quote for search queries and better parse error checks. - GoFile: use Session with try/finally, create guest account with timeout, validate API responses and close session. - Context menu: import DirectDownloadHandle and handle stopping direct downloads separately from libtorrent; simplify file deletion with single try/catch; improve logging on exceptions. - GUI/search flow: move UI state changes (clear table, disable searchbar, hide empty results) into MainWindow._start_search and reduce duplicated UI changes in run_search. - Main: remove duplicate is_running assignment. - Libtorrent: snapshot seeded magnets in update loop to avoid concurrent access; minor indentation change in add_seed. - State/logging: add _log_lock to AppState; protect state.log_buffer with lock in consoleLog and flush_log_buffer and fix flush semantics. - Updater: replace busy-wait with QEventLoop/QTimer polling for DirectDownloadHandle progress, stop timer and quit loop on completion/error, and remove unnecessary sleeps. These changes aim to make network operations more reliable, prevent UI freezes, avoid race conditions when logging, and handle direct vs libtorrent downloads more cleanly.
218 lines
7.9 KiB
Python
218 lines
7.9 KiB
Python
from utils.data.models import Download, DownloadList
|
|
from utils.data.state import state
|
|
from dataclasses import asdict
|
|
from datetime import datetime
|
|
import json
|
|
import time
|
|
import os
|
|
import re
|
|
|
|
|
|
def _downloads_file_path() -> str:
|
|
return os.path.join(state.settings_path, "downloads.json")
|
|
|
|
def _load_downloads() -> list[Download]:
|
|
downloads_file = _downloads_file_path()
|
|
if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0:
|
|
try:
|
|
with open(downloads_file, "r") as file:
|
|
existing_data = json.load(file)
|
|
return [Download(**d) for d in existing_data.get("data", [])]
|
|
except (json.JSONDecodeError, TypeError) as e:
|
|
consoleLog(f"Error loading downloads.json: {e}")
|
|
return []
|
|
|
|
def _save_downloads(downloads: list[Download]) -> DownloadList:
|
|
download_list = DownloadList(data=downloads, count=len(downloads))
|
|
with open(_downloads_file_path(), "w") as file:
|
|
json.dump(asdict(download_list), file, indent=4)
|
|
return download_list
|
|
|
|
|
|
def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
|
|
# wait for metadata outside the lock to avoid blocking other threads
|
|
magnetdl = state.active_downloads.get(magnet_uri)
|
|
if magnetdl:
|
|
status = magnetdl.status()
|
|
timeout = 60
|
|
start = time.time()
|
|
while not status.has_metadata and (time.time() - start) < timeout:
|
|
time.sleep(0.5)
|
|
status = magnetdl.status()
|
|
torrent_name = status.name
|
|
save_path = status.save_path
|
|
consoleLog(f"Torrent name (has_metadata={status.has_metadata}): {torrent_name}")
|
|
path = os.path.join(save_path, torrent_name)
|
|
else:
|
|
path = os.path.join(state.download_path, title)
|
|
with state.downloads_lock:
|
|
return _add_download_log_inner(title, url, magnet_uri, completed, path)
|
|
|
|
def _add_download_log_inner(title, url, magnet_uri, completed, path) -> DownloadList:
|
|
downloads = _load_downloads()
|
|
|
|
if any((magnet_uri and d.magnet_uri == magnet_uri) or (url and d.url == url) for d in downloads):
|
|
if magnet_uri and magnet_uri in state.active_downloads:
|
|
consoleLog("Skipping Logging, download already running...")
|
|
return DownloadList(data=downloads, count=len(downloads))
|
|
consoleLog("File already in Log, updating Download State...")
|
|
hash = extract_hash_from_magnet(magnet_uri)
|
|
if hash:
|
|
update_download_completed_by_hash(hash, False)
|
|
return DownloadList(data=downloads, count=len(downloads))
|
|
|
|
|
|
downloads.append(Download(
|
|
title=title,
|
|
url=url,
|
|
magnet_uri=magnet_uri,
|
|
path=path,
|
|
completed=completed
|
|
))
|
|
|
|
consoleLog(f"Added {title} to Log File")
|
|
return _save_downloads(downloads)
|
|
|
|
def remove_download_log(magnet_uri) -> DownloadList:
|
|
with state.downloads_lock:
|
|
return _remove_download_log_inner(magnet_uri)
|
|
|
|
def _remove_download_log_inner(magnet_uri) -> DownloadList:
|
|
downloads = _load_downloads()
|
|
|
|
magnet_link = (magnet_uri or "").strip()
|
|
if not magnet_link:
|
|
return DownloadList(data=downloads, count=len(downloads))
|
|
|
|
title = next((getattr(d, 'title', 'Unknown') for d in downloads if (getattr(d, 'magnet_uri', None) or '').strip() == magnet_link or (getattr(d, 'url', None) or '').strip() == magnet_link), 'Unknown')
|
|
downloads = [d for d in downloads if (getattr(d, 'magnet_uri', None) or "").strip() != magnet_link and (getattr(d, 'url', None) or "").strip() != magnet_link]
|
|
|
|
consoleLog(f"Removed {title} from Log File")
|
|
return _save_downloads(downloads)
|
|
|
|
def update_download_completed(magnet_uri, completed) -> DownloadList:
|
|
with state.downloads_lock:
|
|
return _update_download_completed_inner(magnet_uri, completed)
|
|
|
|
def _update_download_completed_inner(magnet_uri, completed) -> DownloadList:
|
|
downloads = _load_downloads()
|
|
|
|
identifier = (magnet_uri or "").strip()
|
|
if state.debug:
|
|
consoleLog(f"Updating download completed for identifier: {identifier!r}")
|
|
consoleLog(f"Total downloads in file: {len(downloads)}")
|
|
for i, d in enumerate(downloads):
|
|
try:
|
|
mag = (getattr(d, 'magnet_uri', None) or '')[:50]
|
|
url = (getattr(d, 'url', None) or '')[:50]
|
|
consoleLog(f" [{i}] magnet: {mag}... url: {url}...")
|
|
except Exception as e:
|
|
consoleLog(f" [{i}] Error reading download: {e}")
|
|
|
|
found = False
|
|
for download in downloads:
|
|
try:
|
|
stored_magnet = (getattr(download, 'magnet_uri', None) or "").strip()
|
|
stored_url = (getattr(download, 'url', None) or "").strip()
|
|
if identifier and (stored_magnet == identifier or stored_url == identifier):
|
|
download.completed = completed
|
|
found = True
|
|
except Exception as e:
|
|
consoleLog(f"Error comparing download: {e}")
|
|
|
|
if not found:
|
|
consoleLog("No matching download found to update")
|
|
return DownloadList(data=downloads, count=len(downloads))
|
|
|
|
consoleLog("Updated download log")
|
|
return _save_downloads(downloads)
|
|
|
|
|
|
def get_download_logs() -> DownloadList:
|
|
with state.downloads_lock:
|
|
return _get_download_logs_inner()
|
|
|
|
def _get_download_logs_inner() -> DownloadList:
|
|
downloads = _load_downloads()
|
|
return DownloadList(data=downloads, count=len(downloads))
|
|
|
|
|
|
def extract_hash_from_magnet(magnet_uri):
|
|
if not magnet_uri:
|
|
return None
|
|
try:
|
|
import libtorrent as lt
|
|
params = lt.parse_magnet_uri(magnet_uri)
|
|
if hasattr(params, 'info_hashes'): # lt 2.0+
|
|
return str(params.info_hashes.v1).upper()
|
|
return str(params.info_hash).upper()
|
|
except Exception:
|
|
match = re.search(r'urn:btih:([a-zA-Z0-9]+)', magnet_uri)
|
|
if match:
|
|
return match.group(1).upper()
|
|
return None
|
|
|
|
|
|
def update_download_completed_by_hash(info_hash, completed) -> DownloadList:
|
|
with state.downloads_lock:
|
|
return _update_download_completed_by_hash_inner(info_hash, completed)
|
|
|
|
def _update_download_completed_by_hash_inner(info_hash, completed) -> DownloadList:
|
|
downloads = _load_downloads()
|
|
|
|
info_hash_upper = (info_hash or "").upper().strip()
|
|
consoleLog(f"Updating download by hash: {info_hash_upper}")
|
|
consoleLog(f"Total downloads in file: {len(downloads)}")
|
|
|
|
found = False
|
|
for download in downloads:
|
|
try:
|
|
magnet_uri = getattr(download, 'magnet_uri', None)
|
|
if magnet_uri:
|
|
stored_hash = extract_hash_from_magnet(magnet_uri)
|
|
if stored_hash and stored_hash == info_hash_upper:
|
|
download.completed = completed
|
|
found = True
|
|
except Exception as e:
|
|
consoleLog(f"Error comparing download: {e}")
|
|
|
|
if not found:
|
|
consoleLog("No matching download found to update")
|
|
return DownloadList(data=downloads, count=len(downloads))
|
|
|
|
consoleLog("Updated download log")
|
|
return _save_downloads(downloads)
|
|
|
|
|
|
def set_main_window(window):
|
|
state.main_window = window
|
|
|
|
def flush_log_buffer():
|
|
with state._log_lock:
|
|
buffer = list(state.log_buffer)
|
|
state.log_buffer.clear()
|
|
if buffer:
|
|
try:
|
|
from interface.gui import MainWindow
|
|
for log_entry in buffer:
|
|
MainWindow.add_log(log_entry)
|
|
except Exception as e:
|
|
consoleLog(f"Exception while flushing log buffer: {e}")
|
|
|
|
def consoleLog(text, printAnyways = False):
|
|
now = datetime.now()
|
|
current_time = now.strftime("%H:%M:%S")
|
|
formatted_text = f"[{current_time}] {text}"
|
|
|
|
try:
|
|
from interface.gui import MainWindow
|
|
if not MainWindow.add_log(formatted_text):
|
|
with state._log_lock:
|
|
state.log_buffer.append(formatted_text)
|
|
except Exception:
|
|
with state._log_lock:
|
|
state.log_buffer.append(formatted_text)
|
|
|
|
if state.debug or printAnyways:
|
|
print(formatted_text)
|