diff --git a/src/data/hosts/buzzheavier.py b/src/data/hosts/buzzheavier.py index c0d8251..f11cefb 100644 --- a/src/data/hosts/buzzheavier.py +++ b/src/data/hosts/buzzheavier.py @@ -1,9 +1,13 @@ +from utils.logging.logs import consoleLog import requests def scrape_buzzheavier(url): - - response = requests.get(url) - response.raise_for_status() + try: + response = requests.get(url, timeout=15) + response.raise_for_status() + except requests.RequestException as e: + consoleLog(f"Buzzheavier: Failed to fetch page - {e}") + return None download_url = url + '/download' @@ -12,7 +16,11 @@ def scrape_buzzheavier(url): 'hx-request': 'true', 'referer': url } - - head_response = requests.head(download_url, headers=headers, allow_redirects=False) - hx_redirect = head_response.headers.get('hx-redirect') - return hx_redirect \ No newline at end of file + + try: + head_response = requests.head(download_url, headers=headers, allow_redirects=False, timeout=15) + hx_redirect = head_response.headers.get('hx-redirect') + return hx_redirect + except requests.RequestException as e: + consoleLog(f"Buzzheavier: Failed to fetch download URL - {e}") + return None \ No newline at end of file diff --git a/src/data/hosts/gofile.py b/src/data/hosts/gofile.py index d99a6de..0ca0335 100644 --- a/src/data/hosts/gofile.py +++ b/src/data/hosts/gofile.py @@ -23,42 +23,44 @@ def scrape_gofile(url): content_id = match.group(1) session = requests.Session() + try: + # Create a guest account + r = session.post(f"{_API_BASE}/accounts", headers={"User-Agent": _USER_AGENT}, timeout=15) + data = r.json() + if data.get("status") != "ok": + consoleLog("GoFile: Failed to create guest account") + return None - # Create a guest account - r = session.post(f"{_API_BASE}/accounts", headers={"User-Agent": _USER_AGENT}) - data = r.json() - if data.get("status") != "ok": - consoleLog("GoFile: Failed to create guest account") - return None + account_token = data["data"]["token"] + website_token = _generate_website_token(account_token) - account_token = data["data"]["token"] - website_token = _generate_website_token(account_token) + headers = { + "User-Agent": _USER_AGENT, + "Authorization": f"Bearer {account_token}", + "X-Website-Token": website_token, + "X-BL": "en-US", + "Referer": "https://gofile.io/", + "Origin": "https://gofile.io", + } - headers = { - "User-Agent": _USER_AGENT, - "Authorization": f"Bearer {account_token}", - "X-Website-Token": website_token, - "X-BL": "en-US", - "Referer": "https://gofile.io/", - "Origin": "https://gofile.io", - } + # Fetch folder contents + r = session.get(f"{_API_BASE}/contents/{content_id}", headers=headers, timeout=15) + data = r.json() + if data.get("status") != "ok": + consoleLog(f"GoFile: API error - {data.get('status')}") + return None - # Fetch folder contents - r = session.get(f"{_API_BASE}/contents/{content_id}", headers=headers) - data = r.json() - if data.get("status") != "ok": - consoleLog(f"GoFile: API error - {data.get('status')}") - return None + children = data["data"].get("children", {}) + if not children: + consoleLog("GoFile: No files found") + return None - children = data["data"].get("children", {}) - if not children: - consoleLog("GoFile: No files found") - return None + first_child = next(iter(children.values())) + link = first_child.get("link") + if not link: + consoleLog("GoFile: No download link in response") + return None - first_child = next(iter(children.values())) - link = first_child.get("link") - if not link: - consoleLog("GoFile: No download link in response") - return None - - return link, headers \ No newline at end of file + return link, headers + finally: + session.close() \ No newline at end of file diff --git a/src/data/sources/rutracker.py b/src/data/sources/rutracker.py index 73b6a4b..59a259e 100644 --- a/src/data/sources/rutracker.py +++ b/src/data/sources/rutracker.py @@ -2,6 +2,7 @@ from utils.network.jsonhandler import split_data, format_data from utils.data.tracker import get_magnet_link from utils.logging.logs import consoleLog from utils.data.state import state +from urllib.parse import quote from typing import Dict import requests @@ -11,10 +12,18 @@ class RutrackerScraper: is_magnet = True def search(self, query): - search = requests.get(f"{state.api_url}/search?q={query}", timeout=15) + try: + search = requests.get(f"{state.api_url}/search?q={quote(query)}", timeout=15) + except requests.RequestException as e: + consoleLog(f"Failed to reach server: {e}") + return [] consoleLog("Sent request to server") if search: - _, data, _, success, cached = split_data(search.text) + try: + _, data, _, success, cached = split_data(search.text) + except (KeyError, ValueError) as e: + consoleLog(f"Failed to parse server response: {e}") + return [] if cached: consoleLog("Server response cached") if success: diff --git a/src/data/sources/steamrip.py b/src/data/sources/steamrip.py index af0c376..7162f17 100644 --- a/src/data/sources/steamrip.py +++ b/src/data/sources/steamrip.py @@ -27,7 +27,7 @@ class SteamripScraper: try: url = f"https://steamrip.com/games-list-page/" - response = requests.get(url) + response = requests.get(url, timeout=15) response.raise_for_status() text = response.text except requests.RequestException as e: @@ -61,7 +61,7 @@ class SteamripScraper: def scrape_steamrip_game_downloads(self, url): try: - response = requests.get(url) + response = requests.get(url, timeout=15) response.raise_for_status() except requests.RequestException as e: consoleLog(f"SteamRip: Failed to fetch game page - {e}") diff --git a/src/data/sources/uztracker.py b/src/data/sources/uztracker.py index 1e871af..c3c2137 100644 --- a/src/data/sources/uztracker.py +++ b/src/data/sources/uztracker.py @@ -19,7 +19,7 @@ class UztrackerScraper: posts = [] try: - response = requests.get(search_url) + response = requests.get(search_url, timeout=15) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_')) diff --git a/src/interface/dialogs/contextmenu.py b/src/interface/dialogs/contextmenu.py index 7e8d409..dffeb66 100644 --- a/src/interface/dialogs/contextmenu.py +++ b/src/interface/dialogs/contextmenu.py @@ -1,4 +1,5 @@ from PySide6.QtCore import Qt, QPoint, Signal, QThread +from network.direct_download.handle import DirectDownloadHandle from utils.logging.logs import consoleLog, remove_download_log from utils.network.download import download_selected from utils.data.tracker import get_magnet_link @@ -121,7 +122,12 @@ class ContextMenu_Downloads: remove_download_log(magnet_link) - if state.dl_session: + if isinstance(magnetdl, DirectDownloadHandle): + try: + magnetdl.stop() + except Exception as e: + consoleLog(f"Exception while stopping direct download: {e}") + elif state.dl_session: try: state.dl_session.remove_torrent(magnetdl) except Exception as e: @@ -179,29 +185,24 @@ class ContextMenu_Downloads: remove_download_log(magnet_link) - if state.dl_session: + if isinstance(magnetdl, DirectDownloadHandle): + try: + magnetdl.stop() + except Exception as e: + consoleLog(f"Exception while stopping direct download: {e}") + elif state.dl_session: try: state.dl_session.remove_torrent(magnetdl) - time.sleep(0.5) except Exception as e: consoleLog(f"Exception while removing download from LibTorrent: {e}") if download_path and os.path.exists(download_path): - last_error = None - for attempt in range(3): - try: - if os.path.isfile(download_path) or os.path.isdir(download_path): - send2trash(download_path) - consoleLog(f"Deleted files for: {torrent_name}", True) - last_error = None - break - except Exception as e: - last_error = e - if attempt < 2: - time.sleep(0.5) - - if last_error: - consoleLog(f"Error deleting files: {last_error}", True) + try: + if os.path.isfile(download_path) or os.path.isdir(download_path): + send2trash(download_path) + consoleLog(f"Deleted files for: {torrent_name}", True) + except Exception as e: + consoleLog(f"Error deleting files: {e}", True) else: consoleLog(f"Removed entry (files not found): {torrent_name}", True) diff --git a/src/interface/gui.py b/src/interface/gui.py index 4cf0eba..f64d774 100644 --- a/src/interface/gui.py +++ b/src/interface/gui.py @@ -307,6 +307,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): table.setItemDelegateForColumn(i, self._tracker_hover_delegate) def _start_search(self): + self.searchbar.setEnabled(False) + state.trackertable.setRowCount(0) + self.show_empty_results(False) + def _search_thread(): try: run_search(self) diff --git a/src/interface/utils/searchhelper.py b/src/interface/utils/searchhelper.py index eeefdf3..b2bf889 100644 --- a/src/interface/utils/searchhelper.py +++ b/src/interface/utils/searchhelper.py @@ -6,9 +6,6 @@ for scraper in SCRAPERS: state.trackers[scraper.name] = scraper def run_search(self) -> None: - state.trackertable.setRowCount(0) - self.show_empty_results(False) - search_text = self.searchbar.text() # Check for empty search if search_text == "": @@ -16,12 +13,11 @@ def run_search(self) -> None: return state.posts = [] - self.searchbar.setEnabled(False) consoleLog(f"User searched for: {search_text}") - + # Get current tracker and call its search function scraper = state.trackers[state.currenttracker] state.posts = scraper.search(search_text) - + # Update GUI headers to match the current scraper self.search_results_signal.emit(scraper.headers) \ No newline at end of file diff --git a/src/main.py b/src/main.py index af094f5..0826dd6 100644 --- a/src/main.py +++ b/src/main.py @@ -45,7 +45,6 @@ class SingleInstance(QObject): self.server.listen(SERVER_NAME) self.server.newConnection.connect(self.handle_connection) self.is_running = False - self.is_running = False def handle_connection(self): socket = self.server.nextPendingConnection() diff --git a/src/network/libtorrent_int.py b/src/network/libtorrent_int.py index a1e59da..0c71d3d 100644 --- a/src/network/libtorrent_int.py +++ b/src/network/libtorrent_int.py @@ -163,7 +163,7 @@ def add_seed(magnet_uri, file_path): return False with state.downloads_lock: state.active_downloads[magnet_uri] = handle - state.seeded_magnets.add(magnet_uri) + state.seeded_magnets.add(magnet_uri) return True diff --git a/src/network/libtorrent_misc.py b/src/network/libtorrent_misc.py index b1c6664..8eb50ef 100644 --- a/src/network/libtorrent_misc.py +++ b/src/network/libtorrent_misc.py @@ -48,13 +48,14 @@ def update_log(shutdown_event): try: with state.downloads_lock: items = list(state.active_downloads.items()) + seeded = set(state.seeded_magnets) for magnet_uri, magnetdl in items: if isinstance(magnetdl, dict): continue status = magnetdl.status() - if status.state == lt.torrent_status.seeding and magnet_uri not in updated and magnet_uri not in state.seeded_magnets: + if status.state == lt.torrent_status.seeding and magnet_uri not in updated and magnet_uri not in seeded: consoleLog(f"Marking {status.name} as completed") if hasattr(status, 'info_hashes'): info_hash = str(status.info_hashes.v1) diff --git a/src/utils/data/state.py b/src/utils/data/state.py index 6b08e9a..5277614 100644 --- a/src/utils/data/state.py +++ b/src/utils/data/state.py @@ -61,6 +61,7 @@ class AppState(QObject): self.max_connections: int = 200 self.max_downloads: int = 10 self.downloads_lock = threading.RLock() + self._log_lock = threading.Lock() @property def image_path(self) -> str: diff --git a/src/utils/logging/logs.py b/src/utils/logging/logs.py index 03b9de4..f50a11d 100644 --- a/src/utils/logging/logs.py +++ b/src/utils/logging/logs.py @@ -187,13 +187,15 @@ def _update_download_completed_by_hash_inner(info_hash, completed) -> DownloadLi def set_main_window(window): state.main_window = window -def flush_log_buffer(): # credits to claude - if state.log_buffer: +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 state.log_buffer: + for log_entry in buffer: MainWindow.add_log(log_entry) - state.log_buffer = [] except Exception as e: consoleLog(f"Exception while flushing log buffer: {e}") @@ -205,9 +207,11 @@ def consoleLog(text, printAnyways = False): try: from interface.gui import MainWindow if not MainWindow.add_log(formatted_text): - state.log_buffer.append(formatted_text) + with state._log_lock: + state.log_buffer.append(formatted_text) except Exception: - state.log_buffer.append(formatted_text) + with state._log_lock: + state.log_buffer.append(formatted_text) if state.debug or printAnyways: print(formatted_text) diff --git a/src/utils/network/updater.py b/src/utils/network/updater.py index c07b585..d419860 100644 --- a/src/utils/network/updater.py +++ b/src/utils/network/updater.py @@ -2,13 +2,12 @@ from network.direct_download.handle import DirectDownloadHandle from utils.general.shutdown import closehelper from utils.logging.logs import consoleLog from utils.data.state import state -from PySide6.QtCore import Qt +from PySide6.QtCore import Qt, QTimer, QEventLoop from PySide6 import QtWidgets import libtorrent as lt import subprocess import tempfile import hashlib -import time import sys import os @@ -55,14 +54,19 @@ def download_update(assets: list): handle = DirectDownloadHandle(url, filename, tempfile.gettempdir()) handle.start() - while True: - QtWidgets.QApplication.processEvents() + loop = QEventLoop() + timer = QTimer() + timer.setInterval(100) + + def poll_progress(): status = handle.status() if status.error: state.down_speed_limit = original_limit progress.close() consoleLog(f"Update download failed: {status.error}") + timer.stop() + loop.quit() return if status.total_wanted > 0: @@ -72,9 +76,15 @@ def download_update(assets: list): progress.setLabelText(f"Downloading update... ({speed_mb:.1f} MB/s)") if status.state == lt.torrent_status.seeding: - break + timer.stop() + loop.quit() - time.sleep(0.1) + timer.timeout.connect(poll_progress) + timer.start() + loop.exec() + + if handle.status().error: + return state.down_speed_limit = original_limit @@ -94,5 +104,4 @@ def download_update(assets: list): closehelper() subprocess.Popen([installer_path, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/SP-", "/CLOSEAPPLICATIONS"]) - time.sleep(1) os._exit(0) \ No newline at end of file