Merge branch 'feat-steamrip-noschxl' into merge_candidate_noschxl, improve stuff (http dl impl still needed)

This commit is contained in:
Vxrtrauter
2026-03-09 23:48:41 +01:00
23 changed files with 740 additions and 459 deletions
+3 -1
View File
@@ -23,7 +23,7 @@ def create_config():
}
config["Paths"] = {
"bound_interface": f"{state.bound_interface}",
"bound_interface": f"{state.bound_interface}" if state.bound_interface is not None else "None",
"image_path": f"{state.image_path}"
}
@@ -72,6 +72,8 @@ def read_config():
# Paths
state.bound_interface = config.get("Paths", "bound_interface", fallback=state.bound_interface)
if state.bound_interface == "None":
state.bound_interface = None
state.image_path = config.get("Paths", "image_path", fallback=state.image_path)
create_config()
+22 -10
View File
@@ -1,6 +1,7 @@
import threading
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QTableWidget
from typing import Optional, Any, List, Dict
from typing import Any, List, Dict
from pathlib import Path
class AppState(QObject):
@@ -8,20 +9,26 @@ class AppState(QObject):
def __init__(self):
super().__init__()
self.posts: list[Any] | None = None
self.post_titles: Optional[List] = None
self.post_urls: Optional[List] = None
self.post_author: List[str] = []
self.post_seeders: List[str] = []
self.post_leechers: List[str] = []
self.posts: list[Dict[str,str]] | None = None # titles, urls, author, seederm leecher
self.version: str = "dev"
self._image_path: str = ""
self.ignore_updates: bool = False
self.debug: bool = False
self.autoresume: bool = True
self.tracker: str = "rutracker"
self.tracker_list: dict[str, QTableWidget] = {}
self.currenttracker: str = "rutracker"
self.trackertable: QTableWidget
self.trackers: Dict[str,Dict[str,Any]] = {}# each tracker should add itself here
'''
an example:
"rutracker" : {
"name" : "rutracker", # name of the tracker
"headers" : ["author", "title"], # keys shown in the table
"scrapeFunc" : function,
}
'''
self.api_url: str = "https://api.michijackson.xyz"
self.seeded_magnets: set = set()
self.download_path: str = str(Path.home() / "Downloads")
self.up_speed_limit: int = 0
self.down_speed_limit: int = 0
@@ -30,11 +37,16 @@ class AppState(QObject):
self.settings_path: str = ""
self.dl_session: Any = None
self.active_downloads: Dict = {}
self.seeded_magnets: set = set()
self.window_transparency: bool = False
self.interfaces: List = []
self.active_interfaces: List = []
self.bound_interface: Any = None
self.log_buffer: List[str] = []
self.downloads_lock = threading.RLock()
self.main_window: Any = None
self.loop_running: bool = False
self.shutdown_event = threading.Event()
@property
def image_path(self) -> str:
-25
View File
@@ -1,31 +1,6 @@
import requests
from bs4 import BeautifulSoup
from core.utils.data.state import state
from core.utils.logging.logs import consoleLog
from core.utils.network.jsonhandler import format_data
def get_item_url(item, posts, post_titles): # softwarelist currentitem, post list (dict), post titles list
post_index = post_titles.index(item)
if 0 <= post_index < len(post_titles):
if state.tracker == "uztracker":
item = "https://uztracker.net/" + state.post_urls[post_index].lstrip("./")
consoleLog(f"Found post URL: {item}")
return item
if state.tracker == "rutracker":
item_dict = posts[post_index]
_, post_links, _, _, _ = format_data([item_dict])
consoleLog(f"Found post URL: {post_links[0]}")
return post_links[0]
if state.tracker == "m0nkrus":
item_dict = posts[post_index]
_, post_links, _, _, _,= format_data([item_dict])
consoleLog(f"Found post URL: {post_links[0]}")
return post_links[0]
return None
def get_magnet_link(post_url):
+7 -4
View File
@@ -1,10 +1,13 @@
import threading
from core.utils.data.state import state
import os
shutdown_event = threading.Event()
def closehelper():
shutdown_event.set()
state.shutdown_event.set()
try:
from core.network.libtorrent_misc import cleanup_session
cleanup_session()
except Exception:
pass
def force_exit():
os._exit(0)
+4 -3
View File
@@ -15,18 +15,19 @@ def check_completed(downloads, resume):
if download.completed == False:
consoleLog(f"Found unfinished download: {download.title}")
if resume == True:
run_download_direct(download.magnet_uri)
dl_dir = os.path.dirname(download.path)
run_download_direct(download.magnet_uri, dl_dir)
consoleLog(f"Resuming {download.title}")
def check_downloads(downloads):
for download in downloads:
if os.path.exists(download.path):
if download.completed == True and os.path.exists(download.path):
consoleLog(f"Existing Download: {download.title}")
try:
seed_magnet(download.magnet_uri, download.path)
except Exception as e:
consoleLog(f"Failed to seed {download.title}: {e}")
else:
elif download.completed == True and not os.path.exists(download.path):
consoleLog(f"Inexistent Download: {download.title}")
remove_download_log(download.magnet_uri)
+28 -27
View File
@@ -8,10 +8,6 @@ import re
import time
import threading
_log_buffer = []
_downloads_lock = threading.RLock()
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)
@@ -28,7 +24,7 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
path = os.path.join(save_path, torrent_name)
else:
path = os.path.join(state.download_path, title)
with _downloads_lock:
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:
@@ -47,11 +43,11 @@ def _add_download_log_inner(title, url, magnet_uri, completed, path) -> Download
if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads):
if magnet_uri in state.active_downloads:
consoleLog("Skipping Logging, download already running...")
return
return DownloadList(data=downloads, count=len(downloads))
consoleLog("File already in Log, updating Download State...")
hash = extract_hash_from_magnet(magnet_uri)
update_download_completed_by_hash(hash, False)
return DownloadList(data=downloads, count=len(downloads)) # thanks again claude (im stupid)
return DownloadList(data=downloads, count=len(downloads))
downloads.append(Download(
@@ -71,7 +67,7 @@ def _add_download_log_inner(title, url, magnet_uri, completed, path) -> Download
return download_list
def remove_download_log(magnet_uri) -> DownloadList:
with _downloads_lock:
with state.downloads_lock:
return _remove_download_log_inner(magnet_uri)
def _remove_download_log_inner(magnet_uri) -> DownloadList:
@@ -101,7 +97,7 @@ def _remove_download_log_inner(magnet_uri) -> DownloadList:
return download_list
def update_download_completed(magnet_uri, completed) -> DownloadList:
with _downloads_lock:
with state.downloads_lock:
return _update_download_completed_inner(magnet_uri, completed)
def _update_download_completed_inner(magnet_uri, completed) -> DownloadList:
@@ -157,7 +153,7 @@ def _update_download_completed_inner(magnet_uri, completed) -> DownloadList:
def get_download_logs() -> DownloadList:
with _downloads_lock:
with state.downloads_lock:
return _get_download_logs_inner()
def _get_download_logs_inner() -> DownloadList:
@@ -176,15 +172,24 @@ def _get_download_logs_inner() -> DownloadList:
return DownloadList(data=downloads, count=len(downloads))
def extract_hash_from_magnet(magnet_uri): # full credits to claude for this
match = re.search(r'urn:btih:([A-F0-9]+)', magnet_uri, re.IGNORECASE)
if match:
return match.group(1).upper()
return None
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 _downloads_lock:
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:
@@ -230,20 +235,16 @@ def _update_download_completed_by_hash_inner(info_hash, completed) -> DownloadLi
return download_list
_main_window = None
def set_main_window(window):
global _main_window
_main_window = window
state.main_window = window
def flush_log_buffer(): # credits to claude
global _log_buffer
if _log_buffer:
if state.log_buffer:
try:
from core.interface.gui import MainWindow
for log_entry in _log_buffer:
for log_entry in state.log_buffer:
MainWindow.add_log(log_entry)
_log_buffer = []
state.log_buffer = []
except Exception:
pass
@@ -254,10 +255,10 @@ def consoleLog(text, printAnyways = False):
try:
from core.interface.gui import MainWindow
MainWindow.add_log(formatted_text)
if not MainWindow.add_log(formatted_text):
state.log_buffer.append(formatted_text)
except Exception:
global _log_buffer
_log_buffer.append(formatted_text)
state.log_buffer.append(formatted_text)
if state.debug or printAnyways:
print(formatted_text)
+61 -18
View File
@@ -1,14 +1,19 @@
from PySide6.QtWidgets import QTableWidgetItem
from PySide6.QtCore import Qt
from core.utils.logging.logs import consoleLog
from core.utils.data.tracker import get_item_url
from core.utils.data.tracker import get_magnet_link
from core.network.libtorrent_wrapper import add_download
from core.network.libtorrent_wrapper import add_magnet
from core.utils.general.wrappers import run_thread
from core.utils.logging.logs import add_download_log
from core.network.libtorrent_int import add_seed
from core.utils.data.state import state
from urllib.parse import urlparse, unquote
import threading
import re
import requests
import random
def download_selected(items, posts, post_titles):
def download_selected(items: list[QTableWidgetItem]):
if not items:
consoleLog("No item selected for download.")
return
@@ -16,25 +21,63 @@ def download_selected(items, posts, post_titles):
for item in items:
if item.column() != 0:
continue
text = item.text()
if text and text not in seen:
seen.add(text)
consoleLog(f"Downloading {text}")
run_thread(threading.Thread(target=run_download, args=(text, posts, post_titles)))
post_idx = item.data(Qt.ItemDataRole.UserRole)
if post_idx is None:
post_idx = item.row()
if post_idx not in seen:
seen.add(post_idx)
post = state.posts[post_idx]
consoleLog(f"Downloading {post.get('title', 'Unknown')}")
run_thread(threading.Thread(target=run_download, args=(post,)))
def run_download(item, posts, post_titles):
post_url = get_item_url(item, posts, post_titles)
consoleLog(f"Selected URL: {post_url}")
magnet_uri = get_magnet_link(post_url)
def get_direct_filename(url: str, headers) -> str:
cd = headers.get('content-disposition', '')
if cd:
match = re.search(r'filename\*=UTF-8\'\'(.+)', cd) # RFC 5987 encoded
if match:
return unquote(match.group(1))
match = re.search(r'filename="?([^";\n]+)"?', cd)
if match:
return match.group(1).strip()
if add_download(magnet_uri):
add_download_log(item, post_url, magnet_uri, False)
path = urlparse(url).path
name = path.split('/')[-1]
if name:
return unquote(name)
def run_download_direct(magnet_uri):
return f"download{random.randint(1,9999)}" #avoid collision
def filedownload(url):
try:
with requests.get(url, stream=True, timeout=30) as r:
r.raise_for_status()
name = get_direct_filename(url, r.headers)
with open(state.download_path + f"/{name}", 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
consoleLog(f"Finished downloading {name}")
except Exception as e:
consoleLog(f"Error downloading: {e}")
def run_download(post):
linkfunc = state.trackers[state.currenttracker]["linkFunc"]
ismagnet = state.trackers[state.currenttracker]["isMagnet"]
link = linkfunc(post)
if ismagnet:
add_magnet(link)
add_download_log(post.get("title", "Unknown"), "", link, False)
else:
filedownload(link)
def run_download_direct(magnet_uri, dl_path=None):
consoleLog(f"Direct download: {magnet_uri[:60]}")
add_download(magnet_uri)
add_magnet(magnet_uri, dl_path)
add_download_log("Direct Download", "", magnet_uri, False)
def seed_magnet(magnet_uri, file_path):
consoleLog(f"Seeding: {magnet_uri[:60]}")
add_seed(magnet_uri, file_path)
add_seed(magnet_uri, file_path)
+6 -6
View File
@@ -8,12 +8,12 @@ def get_updates():
if response.status_code != 200:
consoleLog(f"Failed to fetch releases: {response.status_code}")
return None, None
return None
release = response.json()
latest_version = release["name"]
assets = release["assets"]
latest_version = release.get("name") or release.get("tag_name")
assets = release.get("assets", [])
release_assets = []
@@ -26,11 +26,11 @@ def get_updates():
release_assets.append(dict(
name=asset['name'],
url=asset['browser_download_url'],
hash=asset['digest']
hash=asset.get('digest')
))
return release_assets
else:
return None, None
return None
else:
consoleLog("Already up-to-date.")
return None, None
return None