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.
230 lines
7.3 KiB
Python
230 lines
7.3 KiB
Python
from network.interface import get_interface_ip
|
|
from utils.general.wrappers import run_thread
|
|
from utils.logging.logs import consoleLog
|
|
from utils.data.state import state
|
|
import libtorrent as lt
|
|
import threading
|
|
import platform
|
|
import ctypes
|
|
import time
|
|
import os
|
|
|
|
|
|
_loop_lock = threading.Lock()
|
|
|
|
def get_free_space_mb(dirname):
|
|
if platform.system() == 'Windows':
|
|
free_bytes = ctypes.c_ulonglong(0)
|
|
ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(dirname), None, None, ctypes.pointer(free_bytes))
|
|
return free_bytes.value
|
|
else:
|
|
st = os.statvfs(dirname)
|
|
return st.f_bavail * st.f_frsize
|
|
|
|
def check_space():
|
|
while not state.shutdown_event.is_set():
|
|
with state.downloads_lock:
|
|
items = list(state.active_downloads.items())
|
|
for _, magnetdl in items:
|
|
try:
|
|
status = magnetdl.status()
|
|
except RuntimeError:
|
|
continue
|
|
|
|
if status.state == lt.torrent_status.downloading:
|
|
free_space = get_free_space_mb(state.download_path)
|
|
total_size = status.total_wanted
|
|
|
|
if free_space < total_size:
|
|
consoleLog(f"Not enough free space to continue downloading: {status.name}")
|
|
magnetdl.pause()
|
|
break
|
|
|
|
time.sleep(5)
|
|
|
|
def init_session():
|
|
if state.dl_session is not None:
|
|
return
|
|
|
|
state.dl_session = lt.session()
|
|
|
|
|
|
settings = {
|
|
"upload_rate_limit": state.up_speed_limit * 1024,
|
|
"download_rate_limit": state.down_speed_limit * 1024,
|
|
"enable_dht": True,
|
|
"enable_lsd": True,
|
|
"enable_upnp": True,
|
|
"enable_natpmp": True,
|
|
"dht_bootstrap_nodes": "router.bittorrent.com:6881,dht.transmissionbt.com:6881",
|
|
"connections_limit": state.max_connections,
|
|
"active_downloads": state.max_downloads
|
|
}
|
|
|
|
if state.bound_interface:
|
|
interface_ip = get_interface_ip(state.bound_interface)
|
|
if interface_ip:
|
|
settings["outgoing_interfaces"] = interface_ip
|
|
settings["listen_interfaces"] = f"{interface_ip}:6881"
|
|
consoleLog(f"Binding to Interface IP: {interface_ip}")
|
|
else:
|
|
consoleLog(f"Skipping Binding, no Interface set. ({state.bound_interface})")
|
|
|
|
state.dl_session.apply_settings(settings)
|
|
|
|
consoleLog("Initialized Session")
|
|
|
|
|
|
def add_download(magnet_uri):
|
|
|
|
if state.active_downloads is None:
|
|
state.active_downloads = {}
|
|
|
|
init_session()
|
|
free_space = get_free_space_mb(state.download_path)
|
|
|
|
with state.downloads_lock:
|
|
already_active = magnet_uri in state.active_downloads
|
|
handle = state.active_downloads.get(magnet_uri) if already_active else None
|
|
if already_active:
|
|
try:
|
|
status = handle.status()
|
|
|
|
if status.has_metadata:
|
|
filepath = os.path.join(status.save_path, status.name)
|
|
|
|
if not os.path.exists(filepath):
|
|
|
|
consoleLog(f"File Deleted, redownloading: {status.name}")
|
|
state.dl_session.remove_torrent(handle)
|
|
with state.downloads_lock:
|
|
del state.active_downloads[magnet_uri]
|
|
else:
|
|
consoleLog("Skipping Downloading, download already running...")
|
|
return False
|
|
else:
|
|
consoleLog("Skipping Downloading, download already running... ")
|
|
return False
|
|
except RuntimeError as e:
|
|
consoleLog(f"Error in LibTorrent Handle: {e}")
|
|
with state.downloads_lock:
|
|
del state.active_downloads[magnet_uri]
|
|
|
|
try:
|
|
params = lt.parse_magnet_uri(magnet_uri)
|
|
params.save_path = state.download_path
|
|
|
|
handle = state.dl_session.add_torrent(params)
|
|
metadata_timeout = 60
|
|
metadata_start = time.time()
|
|
while not handle.has_metadata():
|
|
if time.time() - metadata_start > metadata_timeout:
|
|
state.dl_session.remove_torrent(handle)
|
|
consoleLog("Timed out waiting for torrent metadata")
|
|
return False
|
|
time.sleep(1)
|
|
|
|
total_size = handle.get_torrent_info().total_size()
|
|
|
|
if free_space <= total_size:
|
|
state.dl_session.remove_torrent(handle)
|
|
consoleLog("Not enough free space to download this item.")
|
|
return False
|
|
|
|
except Exception as e:
|
|
consoleLog(f"Failed to add torrent or fetch info: {e}")
|
|
return False
|
|
if handle:
|
|
with state.downloads_lock:
|
|
state.active_downloads[magnet_uri] = handle
|
|
consoleLog(f"Added {magnet_uri} to downloads")
|
|
|
|
run_thread(threading.Thread(target=dl_status_loop))
|
|
return True
|
|
|
|
|
|
def add_seed(magnet_uri, file_path):
|
|
if state.active_downloads is None:
|
|
state.active_downloads = {}
|
|
|
|
init_session()
|
|
|
|
with state.downloads_lock:
|
|
if magnet_uri in state.active_downloads:
|
|
consoleLog("Already seeding this torrent")
|
|
return False
|
|
|
|
try:
|
|
magnetdl = lt.parse_magnet_uri(magnet_uri)
|
|
magnetdl.save_path = os.path.dirname(file_path)
|
|
handle = state.dl_session.add_torrent(magnetdl)
|
|
except Exception as e:
|
|
consoleLog(f"Failed to add seed: {e}")
|
|
return False
|
|
with state.downloads_lock:
|
|
state.active_downloads[magnet_uri] = handle
|
|
state.seeded_magnets.add(magnet_uri)
|
|
return True
|
|
|
|
|
|
def dl_status_loop():
|
|
if not _loop_lock.acquire(blocking=False):
|
|
return
|
|
|
|
try:
|
|
completed_set = set()
|
|
|
|
if not state.active_downloads:
|
|
consoleLog("No active downloads")
|
|
return
|
|
|
|
while state.active_downloads and not state.shutdown_event.is_set():
|
|
with state.downloads_lock:
|
|
items = list(state.active_downloads.items())
|
|
for magnet_uri, magnetdl in items:
|
|
try:
|
|
status = magnetdl.status()
|
|
except RuntimeError:
|
|
continue
|
|
|
|
if status.state == lt.torrent_status.seeding and magnet_uri not in completed_set:
|
|
consoleLog(f"Download completed: {status.name}")
|
|
|
|
completed_set.add(magnet_uri)
|
|
|
|
if not state.active_downloads:
|
|
break
|
|
|
|
time.sleep(1)
|
|
finally:
|
|
_loop_lock.release()
|
|
|
|
def update_settings():
|
|
|
|
if state.dl_session is None:
|
|
return
|
|
|
|
settings = {
|
|
"upload_rate_limit": state.up_speed_limit * 1024,
|
|
"download_rate_limit": state.down_speed_limit * 1024,
|
|
"connections_limit": state.max_connections,
|
|
"active_downloads": state.max_downloads
|
|
}
|
|
|
|
if state.bound_interface:
|
|
interface_ip = get_interface_ip(state.bound_interface)
|
|
if interface_ip:
|
|
settings["outgoing_interfaces"] = interface_ip
|
|
settings["listen_interfaces"] = f"{interface_ip}:6881"
|
|
consoleLog(f"Binding to Interface IP: {interface_ip}")
|
|
else:
|
|
consoleLog(f"Skipping Binding, no Interface set. ({state.bound_interface})")
|
|
|
|
state.dl_session.apply_settings(settings)
|
|
|
|
def update_bound_interface():
|
|
|
|
settings = { "outgoing_interfaces": state.bound_interface }
|
|
|
|
state.dl_session.apply_settings(settings)
|