Merge pull request #88 from KeksPirates/refactor/stability

fix/refactor/Improve Stability
This commit is contained in:
shayaa
2026-04-17 14:57:48 +02:00
committed by GitHub
15 changed files with 201 additions and 155 deletions
+1
View File
@@ -143,6 +143,7 @@ venv/
ENV/
env.bak/
venv.bak/
.python-version
# Spyder project settings
.spyderproject
+14 -6
View File
@@ -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'
@@ -13,6 +17,10 @@ def scrape_buzzheavier(url):
'referer': url
}
head_response = requests.head(download_url, headers=headers, allow_redirects=False)
hx_redirect = head_response.headers.get('hx-redirect')
return hx_redirect
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
+35 -33
View File
@@ -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
return link, headers
finally:
session.close()
+11 -2
View File
@@ -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:
+2 -2
View File
@@ -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}")
+1 -1
View File
@@ -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_'))
+19 -18
View File
@@ -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)
+6 -9
View File
@@ -17,6 +17,7 @@ from interface.utils.searchhelper import run_search
from interface.utils.tabhelper import create_tab
from utils.general.shutdown import closehelper
from utils.general.wrappers import run_thread
from utils.logging.logs import log_emitter
from interface.dialogs.image import Image
from utils.data.state import state
@@ -68,14 +69,12 @@ class CenteredDelegate(QStyledItemDelegate):
class MainWindow(QtWidgets.QMainWindow, QWidget):
eventFilter = eventFilter
log_signal = Signal(str)
search_results_signal = Signal(list)
_instance = None
def __init__(self):
super().__init__()
MainWindow._instance = self
self.log_signal.connect(self._on_log_signal)
self.search_results_signal.connect(self._on_search_results)
pixmap = QPixmap()
@@ -132,6 +131,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.consoleLog = QTextEdit()
self.statusbar = QStatusBar()
log_emitter.new_log.connect(self._on_log_signal)
flush_log_buffer()
self._tracker_elided_delegate = ElidedItemDelegate(lambda: self._tracker_hovered_row, self)
@@ -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)
@@ -315,13 +319,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.search_results_signal.emit([])
run_thread(threading.Thread(target=_search_thread))
@staticmethod
def add_log(text):
if hasattr(MainWindow, "_instance") and MainWindow._instance:
MainWindow._instance.log_signal.emit(text)
return True
return False
def _on_log_signal(self, text):
if hasattr(self, 'consoleLog'):
self.consoleLog.append(text)
-4
View File
@@ -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,7 +13,6 @@ 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
+8 -6
View File
@@ -1,4 +1,3 @@
from network.libtorrent_misc import send_notification, update_log, check_deleted_files
from utils.logging.loghandler import split_data, check_completed, check_downloads
from network.interface import list_interfaces, init_interfaces
from PySide6.QtNetwork import QLocalServer, QLocalSocket
@@ -9,12 +8,13 @@ from utils.general.shutdown import closehelper
from utils.general.wrappers import run_thread
from interface.assets.base64_icons import logo_base64
from PySide6.QtWidgets import QSystemTrayIcon, QMenu
from network.libtorrent_misc import AppDaemons
from utils.general.shutdown import force_exit
from utils.config.config import read_config
from PySide6.QtGui import QAction, QPixmap
from utils.logging.logs import consoleLog
from interface.gui import MainWindow
from PySide6.QtCore import Qt, QObject
from PySide6.QtCore import Qt, QObject, QThread
from utils.data.state import state
from PySide6 import QtWidgets
import qdarktheme
@@ -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()
@@ -143,9 +142,12 @@ def main():
init_interfaces()
consoleLog(f"Current Bound: {state.bound_interface}")
# Start background daemon threads
run_thread(threading.Thread(target=send_notification, args=(state.shutdown_event,), daemon=True))
run_thread(threading.Thread(target=update_log, args=(state.shutdown_event,), daemon=True))
run_thread(threading.Thread(target=check_deleted_files, args=(state.shutdown_event,), daemon=True))
app._daemon_thread = QThread()
app._daemons = AppDaemons()
app._daemons.moveToThread(app._daemon_thread)
app._daemon_thread.started.connect(app._daemons.start_all)
app.aboutToQuit.connect(app._daemon_thread.quit)
app._daemon_thread.start()
# Start background non-daemon threads
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
run_thread(threading.Thread(target=check_downloads, args=(downloads,)))
+1 -1
View File
@@ -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
+51 -45
View File
@@ -1,5 +1,6 @@
from utils.logging.logs import update_download_completed_by_hash
from utils.logging.logs import consoleLog
from PySide6.QtCore import QObject, QTimer
from utils.data.state import state
from plyer import notification
import libtorrent as lt
@@ -19,55 +20,28 @@ def cleanup_session():
with state.downloads_lock:
state.active_downloads.clear()
def send_notification(shutdown_event):
notified = set()
while not shutdown_event.is_set():
try:
with state.downloads_lock:
items = list(state.active_downloads.items())
for magnet_uri, magnetdl in items:
if isinstance(magnetdl, dict):
continue
class AppDaemons(QObject):
def __init__(self):
super().__init__()
status = magnetdl.status()
self.notified_magnets = set()
self.updated_magnets = set()
if status.state == lt.torrent_status.seeding and magnet_uri not in notified:
notification.notify(
title="Download finished",
message=f"{status.name} has finished downloading.",
timeout=4
)
notified.add(magnet_uri)
except Exception as e:
consoleLog(f"Exception while sending notification: {e}")
time.sleep(5)
# Create Timers
self.deleted_files_timer = QTimer(self)
self.completed_downloads_timer = QTimer(self)
def update_log(shutdown_event):
updated = set()
while not shutdown_event.is_set():
try:
with state.downloads_lock:
items = list(state.active_downloads.items())
for magnet_uri, magnetdl in items:
if isinstance(magnetdl, dict):
continue
# Connect Timer Signals
self.deleted_files_timer.timeout.connect(self.check_deleted_files)
self.completed_downloads_timer.timeout.connect(self.check_completed_downloads)
status = magnetdl.status()
def start_all(self):
consoleLog("Starting Timer: deleted_files")
self.deleted_files_timer.start(2000)
consoleLog("Starting Timer: completed_downloads")
self.completed_downloads_timer.start(5000)
if status.state == lt.torrent_status.seeding and magnet_uri not in updated and magnet_uri not in state.seeded_magnets:
consoleLog(f"Marking {status.name} as completed")
if hasattr(status, 'info_hashes'):
info_hash = str(status.info_hashes.v1)
else:
info_hash = str(status.info_hash)
update_download_completed_by_hash(info_hash, True)
updated.add(magnet_uri)
except Exception as e:
consoleLog(f"Exception while updating log file: {e}")
time.sleep(5)
def check_deleted_files(shutdown_event):
while not shutdown_event.is_set():
def check_deleted_files(self):
try:
with state.downloads_lock:
items = list(state.active_downloads.items())
@@ -87,4 +61,36 @@ def check_deleted_files(shutdown_event):
del state.active_downloads[magnet_uri]
except Exception as e:
consoleLog(f"Exception while checking for file deletions: {e}")
time.sleep(5)
def check_completed_downloads(self):
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:
if magnet_uri not in self.updated_magnets 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)
else:
info_hash = str(status.info_hash)
update_download_completed_by_hash(info_hash, True)
self.updated_magnets.add(magnet_uri)
if magnet_uri not in self.notified_magnets:
notification.notify(
title="Download finished",
message=f"{status.name} has finished downloading.",
timeout=4
)
self.notified_magnets.add(magnet_uri)
except Exception as e:
consoleLog(f"Exception while checking downloads: {e}")
+1
View File
@@ -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:
+14 -14
View File
@@ -1,4 +1,5 @@
from utils.data.models import Download, DownloadList
from PySide6.QtCore import QObject, Signal
from utils.data.state import state
from dataclasses import asdict
from datetime import datetime
@@ -7,6 +8,10 @@ import time
import os
import re
class LogSignal(QObject):
new_log = Signal(str)
log_emitter = LogSignal()
def _downloads_file_path() -> str:
return os.path.join(state.settings_path, "downloads.json")
@@ -187,27 +192,22 @@ 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:
try:
from interface.gui import MainWindow
for log_entry in state.log_buffer:
MainWindow.add_log(log_entry)
state.log_buffer = []
except Exception as e:
consoleLog(f"Exception while flushing log buffer: {e}")
def flush_log_buffer():
with state._log_lock:
for text in state.log_buffer:
log_emitter.new_log.emit(text)
state.log_buffer.clear()
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):
state.log_buffer.append(formatted_text)
except Exception:
with state._log_lock:
state.log_buffer.append(formatted_text)
log_emitter.new_log.emit(formatted_text)
if state.debug or printAnyways:
print(formatted_text)
+31 -8
View File
@@ -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
@@ -42,7 +41,6 @@ def download_update(assets: list):
progress = QtWidgets.QProgressDialog("Downloading update... (0.0 MB/s)", None, 0, 100)
progress.setWindowTitle("Updating")
progress.setWindowModality(Qt.WindowModality.ApplicationModal)
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.setAutoClose(False)
progress.setAutoReset(False)
@@ -55,14 +53,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 +75,25 @@ 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)
if progress.wasCanceled():
state.down_speed_limit = original_limit
consoleLog("Update download cancelled by user.")
timer.stop()
loop.quit()
return
timer.timeout.connect(poll_progress)
timer.start()
loop.exec()
if progress.wasCanceled():
return
if handle.status().error:
return
state.down_speed_limit = original_limit
@@ -84,6 +103,11 @@ def download_update(assets: list):
else:
progress.close()
consoleLog("Error: Invalid file hash, file may be corrupted")
QtWidgets.QMessageBox.critical(
None,
"Update Failed",
"Error: Invalid file hash, file may be corrupted"
)
sys.exit(0)
else:
consoleLog("Skipping hash verification (no hash found for release)")
@@ -94,5 +118,4 @@ def download_update(assets: list):
closehelper()
subprocess.Popen([installer_path, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/SP-", "/CLOSEAPPLICATIONS"])
time.sleep(1)
os._exit(0)