Compare commits

...

9 Commits

18 changed files with 324 additions and 102 deletions
+1
View File
@@ -1,5 +1,6 @@
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import requests import requests
from core.utils.general.logs import consoleLog
def scrape_monkrus_telegram(query): def scrape_monkrus_telegram(query):
post = 100 post = 100
+3 -4
View File
@@ -1,17 +1,16 @@
import requests import requests
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
def scrape_rutracker(search_text): def scrape_rutracker(search_text):
search = requests.get(f"{state.api_url}/search/{search_text}") search = requests.get(f"{state.api_url}/search/{search_text}")
if state.debug: consoleLog("Sent request to server")
print("Sent request to server")
if search: if search:
try: try:
return search.text return search.text
except Exception: except Exception:
if state.debug: consoleLog("No results found / No response from server")
print("No results found / No response from server")
return None return None
else: else:
+11 -16
View File
@@ -2,6 +2,7 @@ import requests
import asyncio import asyncio
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
global url_uztracker global url_uztracker
@@ -16,13 +17,11 @@ async def init_uztracker():
up = True up = True
else: else:
up = False up = False
if state.debug: consoleLog(f"Uztracker seems down, status code: {response.status_code}")
print(f"Uztracker seems down, status code: {response.status_code}")
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
if state.debug: consoleLog(f"\nRequest Exception on {url_uztracker}:")
print(f"\nRequest Exception on {url_uztracker}:") consoleLog(str(e))
print(e) consoleLog("\nIs the Site down?")
print("\nIs the Site down?")
up = False up = False
asyncio.run(init_uztracker()) asyncio.run(init_uztracker())
@@ -30,8 +29,7 @@ asyncio.run(init_uztracker())
def scrape_uztracker(search): def scrape_uztracker(search):
if up: if up:
search_url = url_uztracker + search search_url = url_uztracker + search
if state.debug: consoleLog(search_url)
print(search_url)
result = False result = False
global results global results
global resulttitles global resulttitles
@@ -59,12 +57,11 @@ def scrape_uztracker(search):
return None return None
except requests.RequestException as e: except requests.RequestException as e:
if result and state.debug: if result:
print(f"Failed to fetch {search_url}: {e}") consoleLog(f"Failed to fetch {search_url}: {e}")
return None return None
else: else:
if state.debug: consoleLog("Error: Uztracker down")
print("Error: Uztracker down")
return None, None return None, None
def get_post_title(post_url): def get_post_title(post_url):
@@ -75,11 +72,9 @@ def get_post_title(post_url):
if maintitle: if maintitle:
return maintitle.text return maintitle.text
else: else:
if state.debug: consoleLog("Program not found")
print("Program not found")
else: else:
if state.debug: consoleLog("Error: Uztracker down")
print("Error: Uztracker down")
return None return None
+27 -12
View File
@@ -1,5 +1,6 @@
from core.utils.config.settings import save_settings from core.utils.config.settings import save_settings
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
@@ -16,8 +17,7 @@ from PySide6.QtWidgets import (
def settings_dialog(self): def settings_dialog(self):
if state.debug: consoleLog("Settings dialog opened")
print("Settings dialog opened")
dialog = QDialog(self) dialog = QDialog(self)
dialog.setWindowTitle("Settings") dialog.setWindowTitle("Settings")
dialog.setFixedSize(800, 400) dialog.setFixedSize(800, 400)
@@ -28,18 +28,33 @@ def settings_dialog(self):
def close_settings(): def close_settings():
dialog.reject() dialog.reject()
checkbox_container = QWidget() update_checkbox_container = QWidget()
checkbox_layout = QHBoxLayout() update_checkbox_layout = QHBoxLayout()
# ignore updates checkbox # ignore updates checkbox
checkbox = QCheckBox() update_checkbox = QCheckBox()
checkbox_container.setLayout(checkbox_layout) update_checkbox_container.setLayout(update_checkbox_layout)
checkbox_layout.addWidget(QLabel("Ignore Updates: ")) update_checkbox_layout.addWidget(QLabel("Ignore Updates: "))
checkbox_layout.addStretch() update_checkbox_layout.addStretch()
checkbox.setChecked(state.ignore_updates) update_checkbox.setChecked(state.ignore_updates)
checkbox.toggled.connect(lambda checked: setattr(state, 'ignore_updates', checked)) update_checkbox.toggled.connect(lambda checked: setattr(state, 'ignore_updates', checked))
checkbox_layout.addWidget(checkbox) update_checkbox_layout.addWidget(update_checkbox)
dialog.layout().addWidget(checkbox_container) dialog.layout().addWidget(update_checkbox_container)
# auto-resume downloads checkbox
autoresume_container = QWidget()
autoresume_layout = QHBoxLayout()
autoresume_checkbox = QCheckBox()
autoresume_container.setLayout(autoresume_layout)
autoresume_layout.addWidget(QLabel("Auto-Resume Downloads: "))
autoresume_layout.addStretch()
autoresume_checkbox.setChecked(state.autoresume)
autoresume_checkbox.toggled.connect(lambda checked: setattr(state, 'autoresume', checked))
autoresume_layout.addWidget(autoresume_checkbox)
dialog.layout().addWidget(autoresume_container)
################## ##################
# THREAD SETTING # # THREAD SETTING #
+31 -5
View File
@@ -16,6 +16,7 @@ from PySide6.QtWidgets import (
QHeaderView, QHeaderView,
QMessageBox, QMessageBox,
QTableWidget, QTableWidget,
QTextEdit
) )
from PySide6.QtGui import QIcon, QAction, QCloseEvent, QImage, QPixmap from PySide6.QtGui import QIcon, QAction, QCloseEvent, QImage, QPixmap
@@ -28,6 +29,7 @@ import subprocess
import time import time
import sys import sys
import json import json
from core.utils.general.logs import consoleLog
from core.utils.general.wrappers import run_thread from core.utils.general.wrappers import run_thread
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.network.download import download_selected from core.utils.network.download import download_selected
@@ -43,7 +45,7 @@ def download_update(latest_version):
new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe" new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe"
url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe" url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe"
print("Downloading update...") consoleLog("Downloading update...")
response = r.get(url, allow_redirects=True) response = r.get(url, allow_redirects=True)
with open(new_filename, "wb") as f: with open(new_filename, "wb") as f:
f.write(response.content) f.write(response.content)
@@ -63,8 +65,12 @@ def download_update(latest_version):
class MainWindow(QtWidgets.QMainWindow, QWidget): class MainWindow(QtWidgets.QMainWindow, QWidget):
log_signal = Signal(str) # Thread-safe signal for logging
def __init__(self): def __init__(self):
super().__init__() super().__init__()
MainWindow._instance = self
self.log_signal.connect(self._on_log_signal)
build_info_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "build_info.json") build_info_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "build_info.json")
if os.path.exists(build_info_path): if os.path.exists(build_info_path):
@@ -115,6 +121,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.downloadList = QTableView() self.downloadList = QTableView()
self.emptyLibrary = QLabel("No items in library.") self.emptyLibrary = QLabel("No items in library.")
self.emptyDownload = QLabel("No items in downloads.") self.emptyDownload = QLabel("No items in downloads.")
self.consoleLog = QTextEdit()
self.progressbar = QProgressBar() self.progressbar = QProgressBar()
# Table Widget for Item List # Table Widget for Item List
@@ -230,8 +238,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.tracker_list.activated.connect(self.set_tracker) self.tracker_list.activated.connect(self.set_tracker)
if darkdetect.isDark(): if darkdetect.isDark():
settings_action = QAction(QIcon("core/interface/assets/settings_black.png"), "Settings", self)
settings_action = QAction(QIcon("core/interface/assets/settings_white.png"), "Settings", self) settings_action = QAction(QIcon("core/interface/assets/settings_white.png"), "Settings", self)
else:
settings_action = QAction(QIcon("core/interface/assets/settings_black.png"), "Settings", self)
settings_action.triggered.connect(lambda: settings_dialog(self)) settings_action.triggered.connect(lambda: settings_dialog(self))
toolbar.addAction(settings_action) toolbar.addAction(settings_action)
@@ -239,8 +248,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
toolbar.setMovable(False) toolbar.setMovable(False)
self.progressbar.setValue(0) # self.progressbar.setValue(0)
containerLayout.addWidget(self.progressbar) self.consoleLog.setReadOnly(True)
self.consoleLog.setFixedHeight(150)
containerLayout.addWidget(self.consoleLog)
self.progress_timer = QTimer() self.progress_timer = QTimer()
self.progress_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.update_progress))) self.progress_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.update_progress)))
@@ -250,6 +261,17 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.download_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.download_list_update))) self.download_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.download_list_update)))
self.download_timer.start(500) self.download_timer.start(500)
@staticmethod
def add_log(text):
if MainWindow._instance:
MainWindow._instance.log_signal.emit(text)
def _on_log_signal(self, text):
self.consoleLog.append(text)
self.consoleLog.verticalScrollBar().setValue(
self.consoleLog.verticalScrollBar().maximum()
)
def update_image_overlay(self, new_image_path): def update_image_overlay(self, new_image_path):
self.image = QImage(new_image_path) self.image = QImage(new_image_path)
self.pixmap = QPixmap.fromImage(self.image) self.pixmap = QPixmap.fromImage(self.image)
@@ -286,4 +308,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
def resizeEvent(self, event): def resizeEvent(self, event):
super().resizeEvent(event) super().resizeEvent(event)
table_width = self.qtablewidget.viewport().width() table_width = self.qtablewidget.viewport().width()
self.qtablewidget.setColumnWidth(1, int(table_width * 0.3)) self.qtablewidget.setColumnWidth(1, int(table_width * 0.3))
+9 -15
View File
@@ -1,4 +1,5 @@
from PySide6.QtWidgets import QTableWidgetItem from PySide6.QtWidgets import QTableWidgetItem
from core.utils.general.logs import consoleLog
from core.data.scrapers.uztracker import scrape_uztracker from core.data.scrapers.uztracker import scrape_uztracker
from core.data.scrapers.rutracker import scrape_rutracker from core.data.scrapers.rutracker import scrape_rutracker
from core.data.scrapers.monkrus import scrape_monkrus_telegram from core.data.scrapers.monkrus import scrape_monkrus_telegram
@@ -8,11 +9,9 @@ from core.utils.data.state import state
def return_pressed(self): def return_pressed(self):
search_text = self.searchbar.text() search_text = self.searchbar.text()
if search_text == "": if search_text == "":
if state.debug: consoleLog("Error: Can't search for nothing")
print("Error: Can't search for nothing")
return return
if state.debug: consoleLog(f"User searched for: {search_text}")
print("User searched for:", search_text)
if state.tracker == "uztracker": if state.tracker == "uztracker":
response = scrape_uztracker(search_text) response = scrape_uztracker(search_text)
@@ -28,13 +27,11 @@ def return_pressed(self):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title)) self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
self.show_empty_results(False) self.show_empty_results(False)
else: else:
if state.debug: consoleLog(f"No Results for {search_text}")
print(f"No Results for {search_text}")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
else: else:
if state.debug: consoleLog(f"No response from uztracker")
print(f"No response from uztracker")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
@@ -43,8 +40,7 @@ def return_pressed(self):
if response: if response:
_, state.posts, _, _, cached = split_data(response) _, state.posts, _, _, cached = split_data(response)
if state.posts == []: if state.posts == []:
if state.debug: consoleLog(f"No Results for {search_text}")
print(f"No Results for {search_text}")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
else: else:
@@ -58,10 +54,9 @@ def return_pressed(self):
for i, title in enumerate(state.post_titles): for i, title in enumerate(state.post_titles):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title)) self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
if state.debug == True: if state.debug == True:
print(f"Response Cached: {cached}") consoleLog(f"Response Cached: {cached}")
else: else:
if state.debug: consoleLog(f"No response from rutracker")
print(f"No response from rutracker")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
@@ -69,8 +64,7 @@ def return_pressed(self):
state.posts = scrape_monkrus_telegram(search_text) state.posts = scrape_monkrus_telegram(search_text)
if state.posts == []: if state.posts == []:
if state.debug: consoleLog(f"No Results for {search_text}")
print(f"No Results for {search_text}")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
else: else:
+17
View File
@@ -1,6 +1,7 @@
import aria2p import aria2p
import subprocess import subprocess
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import update_download_completed_by_hash
from plyer import notification from plyer import notification
import time import time
import sys import sys
@@ -72,4 +73,20 @@ def send_notification(shutdown_event):
state.downloads = [d for d in state.aria2.get_downloads() if d.is_active and not d.is_metadata] state.downloads = [d for d in state.aria2.get_downloads() if d.is_active and not d.is_metadata]
except Exception: except Exception:
pass pass
time.sleep(5)
def update_log(shutdown_event):
updated = set()
while not shutdown_event.is_set():
try:
for d in state.aria2.get_downloads():
if d.is_metadata:
continue
if d.progress == 100 and d.gid not in updated:
consoleLog(f"Marking {d.name} as completed")
update_download_completed_by_hash(d.info_hash, True)
updated.add(d.gid)
state.downloads = [d for d in state.aria2.get_downloads() if d.is_active and not d.is_metadata]
except Exception:
pass
time.sleep(5) time.sleep(5)
+3 -4
View File
@@ -1,16 +1,15 @@
from .aria2_integration import run_aria2p from .aria2_integration import run_aria2p
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
def start_client(): def start_client():
global aria2 global aria2
aria2 = run_aria2p() aria2 = run_aria2p()
if state.debug: consoleLog("Started Aria2p")
print("Started Aria2p")
def add_magnet(uri): def add_magnet(uri):
aria2.add_magnet(uri) aria2.add_magnet(uri)
if state.debug: consoleLog("Magnet URI added to Aria2")
print("Magnet URI added to Aria2")
+3 -1
View File
@@ -7,7 +7,7 @@ def create_config():
config = configparser.ConfigParser() config = configparser.ConfigParser()
config["General"] = {"debug": True, "api_url": f"{state.api_url}", "aria2_threads": f"{state.aria2_threads}", "download_path": f"{state.download_path}", f"speed_limit": f"{state.speed_limit}", config["General"] = {"debug": True, "api_url": f"{state.api_url}", "aria2_threads": f"{state.aria2_threads}", "download_path": f"{state.download_path}", f"speed_limit": f"{state.speed_limit}",
"ignore_updates": f"{state.ignore_updates}", "image_path": f"{state.image_path}"} "ignore_updates": f"{state.ignore_updates}", "image_path": f"{state.image_path}", "autoresume": f"{state.autoresume}"}
if platform.system() == "Windows": if platform.system() == "Windows":
config_dir = os.environ.get("APPDATA", os.path.expanduser("~\\AppData\\Roaming")) config_dir = os.environ.get("APPDATA", os.path.expanduser("~\\AppData\\Roaming"))
@@ -51,3 +51,5 @@ def read_config():
state.ignore_updates = config.getboolean("General", "ignore_updates") state.ignore_updates = config.getboolean("General", "ignore_updates")
if state.image_path is not None: if state.image_path is not None:
state.image_path = config.get("General", "image_path") state.image_path = config.get("General", "image_path")
if state.autoresume is not None:
state.autoresume = config.getboolean("General", "autoresume")
+4 -1
View File
@@ -13,7 +13,7 @@ def restart_aria2c():
atexit.unregister(kill_aria2server) atexit.unregister(kill_aria2server)
atexit.register(kill_aria2server) atexit.register(kill_aria2server)
def save_settings(thread_count=None, close=lambda: None, apiurl=None, download_path=None, speed_limit=None, image_path=None): def save_settings(thread_count=None, close=lambda: None, apiurl=None, download_path=None, speed_limit=None, image_path=None, autoresume=None):
if thread_count is not None: if thread_count is not None:
state.aria2_threads = thread_count state.aria2_threads = thread_count
if apiurl is not None: if apiurl is not None:
@@ -24,6 +24,9 @@ def save_settings(thread_count=None, close=lambda: None, apiurl=None, download_p
state.speed_limit = speed_limit state.speed_limit = speed_limit
if image_path is not None: if image_path is not None:
state.image_path = image_path state.image_path = image_path
if autoresume is not None:
state.autoresume = autoresume
create_config() create_config()
restart_aria2c() restart_aria2c()
+1
View File
@@ -16,6 +16,7 @@ class AppState(QObject):
self._image_path: str = "" self._image_path: str = ""
self.ignore_updates: bool = False self.ignore_updates: bool = False
self.debug: bool = False self.debug: bool = False
self.autoresume: bool = True
self.tracker: str = "rutracker" self.tracker: str = "rutracker"
self.api_url: str = "https://api.michijackson.xyz" self.api_url: str = "https://api.michijackson.xyz"
self.download_path: str = str(Path.home() / "Downloads") self.download_path: str = str(Path.home() / "Downloads")
+8 -14
View File
@@ -1,6 +1,7 @@
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.network.jsonhandler import format_data from core.utils.network.jsonhandler import format_data
@@ -10,20 +11,17 @@ def get_item_url(item, posts, post_titles): # softwarelist currentitem, post lis
if 0 <= post_index < len(post_titles): if 0 <= post_index < len(post_titles):
if state.tracker == "uztracker": if state.tracker == "uztracker":
item = "https://uztracker.net/" + state.post_urls[post_index].lstrip("./") item = "https://uztracker.net/" + state.post_urls[post_index].lstrip("./")
if state.debug: consoleLog(f"Found post URL: {item}")
print("Found post URL: ", item)
return item return item
if state.tracker == "rutracker": if state.tracker == "rutracker":
item_dict = posts[post_index] item_dict = posts[post_index]
_, post_links, _ = format_data([item_dict]) _, post_links, _ = format_data([item_dict])
if state.debug: consoleLog(f"Found post URL: {post_links[0]}")
print("Found post URL: ", post_links[0])
return post_links[0] return post_links[0]
if state.tracker == "m0nkrus": if state.tracker == "m0nkrus":
item_dict = posts[post_index] item_dict = posts[post_index]
_, post_links, _ = format_data([item_dict]) _, post_links, _ = format_data([item_dict])
if state.debug: consoleLog(f"Found post URL: {post_links[0]}")
print("Found post URL: ", post_links[0])
return post_links[0] return post_links[0]
return None return None
@@ -33,20 +31,16 @@ def get_item_url(item, posts, post_titles): # softwarelist currentitem, post lis
def get_magnet_link(post_url): def get_magnet_link(post_url):
try: try:
response = requests.get(post_url) # eventually impl. cloudscraper response = requests.get(post_url) # eventually impl. cloudscraper
if state.debug: consoleLog("Sent Request to retrieve Magnet Link...")
print("Sent Request to retrieve Magnet Link...")
response.raise_for_status() response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser') soup = BeautifulSoup(response.text, 'html.parser')
magnet_link = soup.find('a', href=lambda x: x and x.startswith('magnet:')) magnet_link = soup.find('a', href=lambda x: x and x.startswith('magnet:'))
if magnet_link: if magnet_link:
if state.debug: consoleLog(f"Magnet Link Retrieved: {magnet_link['href']}")
print("Magnet Link Retrieved: ", magnet_link['href'])
return magnet_link['href'] return magnet_link['href']
else: else:
if state.debug: consoleLog("Magnet Link not Found!")
print("Magnet Link not Found!")
except requests.RequestException as e: except requests.RequestException as e:
if state.debug: consoleLog(f"Failed to fetch {post_url}: {e}")
print(f"Failed to fetch {post_url}: {e}")
return None return None
+22
View File
@@ -0,0 +1,22 @@
from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.network.download import run_download_direct
def split_data(data):
count = data.count
downloads = data.data
return count, downloads
def check_completed(downloads, resume):
for download in downloads:
if download.completed == False:
consoleLog(f"Found unfinished download: {download.title}")
if resume == True:
run_download_direct(download.magnet_uri)
consoleLog(f"Resuming {download.title}")
+145 -2
View File
@@ -3,6 +3,7 @@ from core.utils.data.state import state
from dataclasses import asdict from dataclasses import asdict
import json import json
import os import os
import re
def add_download_log(title, url, magnet_uri, completed) -> DownloadList: def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
@@ -18,15 +19,157 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
else: else:
downloads = [] downloads = []
if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads):
consoleLog("Skipped Logging, download already in file")
return DownloadList(data=downloads, count=len(downloads)) # thanks again claude (im stupid)
downloads.append(Download( downloads.append(Download(
title=title, title=title,
url=url, url=url,
magnet_uri=magnet_uri, magnet_uri=magnet_uri,
completed=completed completed=completed,
)) ))
consoleLog(f"Added {title} to Log File")
download_list = DownloadList(data=downloads, count=len(downloads)) download_list = DownloadList(data=downloads, count=len(downloads))
with open(downloads_file, "w") as file: with open(downloads_file, "w") as file:
json.dump(asdict(download_list), file, indent=4) json.dump(asdict(download_list), file, indent=4)
return download_list return download_list
def update_download_completed(magnet_uri, completed) -> DownloadList:
downloads_file = os.path.join(state.settings_path, "downloads.json")
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)
downloads = [Download(**d) for d in existing_data.get("data", [])]
except (json.JSONDecodeError, TypeError) as e:
consoleLog(f"Error loading downloads.json: {e}")
downloads = []
else:
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()
consoleLog(f"Comparing with magnet: {stored_magnet[:50] if stored_magnet else 'None'}...")
if identifier and (stored_magnet == identifier or stored_url == identifier):
consoleLog(f"Match found! Setting completed={completed}")
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))
download_list = DownloadList(data=downloads, count=len(downloads))
with open(downloads_file, "w") as file:
json.dump(asdict(download_list), file, indent=4)
consoleLog("Updated download log")
return download_list
def get_download_logs() -> DownloadList:
downloads_file = os.path.join(state.settings_path, "downloads.json")
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)
downloads = [Download(**d) for d in existing_data.get("data", [])]
except json.JSONDecodeError:
downloads = []
else:
downloads = []
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 update_download_completed_by_hash(info_hash, completed) -> DownloadList:
downloads_file = os.path.join(state.settings_path, "downloads.json")
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)
downloads = [Download(**d) for d in existing_data.get("data", [])]
except (json.JSONDecodeError, TypeError) as e:
consoleLog(f"Error loading downloads.json: {e}")
downloads = []
else:
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))
download_list = DownloadList(data=downloads, count=len(downloads))
with open(downloads_file, "w") as file:
json.dump(asdict(download_list), file, indent=4)
consoleLog("Updated download log")
return download_list
_main_window = None
def set_main_window(window):
global _main_window
_main_window = window
def consoleLog(text):
try:
from core.interface.gui import MainWindow
MainWindow.add_log(text)
except Exception:
pass
if state.debug:
print(text)
+3 -4
View File
@@ -1,4 +1,5 @@
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
import threading import threading
import psutil import psutil
import sys import sys
@@ -14,15 +15,13 @@ def kill_aria2server():
if state.aria2process: if state.aria2process:
state.aria2process.kill() state.aria2process.kill()
if state.debug: consoleLog("Killed Aria2")
print("Killed Aria2")
try: try:
for proc in psutil.process_iter(): for proc in psutil.process_iter():
if proc.name() == process: if proc.name() == process:
proc.kill() proc.kill()
if state.debug: consoleLog(f"Killed Aria2c (PID {proc.pid})")
print(f"Killed Aria2c (PID {proc.pid})")
except psutil.NoSuchProcess: except psutil.NoSuchProcess:
pass pass
+11 -6
View File
@@ -1,28 +1,33 @@
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.data.tracker import get_item_url from core.utils.data.tracker import get_item_url
from core.utils.data.tracker import get_magnet_link from core.utils.data.tracker import get_magnet_link
from core.network.aria2_wrapper import start_client from core.network.aria2_wrapper import start_client
from core.network.aria2_wrapper import add_magnet from core.network.aria2_wrapper import add_magnet
from core.utils.general.wrappers import run_thread from core.utils.general.wrappers import run_thread
from core.utils.general.logs import add_download_log
import threading import threading
def download_selected(item, posts, post_titles): def download_selected(item, posts, post_titles):
if item is not None: if item is not None:
if state.debug: consoleLog(f"Downloading {item.text()}")
print(f"Downloading {item.text()}")
run_thread(threading.Thread(target=run_download, args=(item.text(), posts, post_titles))) run_thread(threading.Thread(target=run_download, args=(item.text(), posts, post_titles)))
else: else:
if state.debug: consoleLog("No item selected for download.")
print("No item selected for download.")
def run_download(item, posts, post_titles): def run_download(item, posts, post_titles):
post_url = get_item_url(item, posts, post_titles) post_url = get_item_url(item, posts, post_titles)
if state.debug: consoleLog(f"Selected URL: {post_url}")
print("Selected URL: ", post_url)
magnet_uri = get_magnet_link(post_url) magnet_uri = get_magnet_link(post_url)
start_client() start_client()
add_download_log(item, post_url, magnet_uri, False)
add_magnet(magnet_uri) add_magnet(magnet_uri)
def run_download_direct(magnet_uri):
start_client()
add_magnet(magnet_uri)
+9 -12
View File
@@ -1,12 +1,13 @@
import requests import requests
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
def check_for_updates(): def check_for_updates():
url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases" url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases"
response = requests.get(url) response = requests.get(url)
if response.status_code != 200: if response.status_code != 200:
print(f"Failed to fetch releases: {response.status_code}") consoleLog(f"Failed to fetch releases: {response.status_code}")
exit(1) exit(1)
releases = response.json() releases = response.json()
@@ -18,21 +19,17 @@ def check_for_updates():
if state.version == "dev": if state.version == "dev":
if state.debug: consoleLog("Dev release detected, skipping version check")
print("Dev release detected, skipping version check") return None, None
return None, None
elif latest_version != state.version: elif latest_version != state.version:
if state.debug: consoleLog(f"New release available: {latest_version}")
print(f"New release available: {latest_version}")
if assets: if assets:
if state.debug: consoleLog("Assets:")
print("Assets:") for asset in assets:
for asset in assets: consoleLog(f" - {asset['name']}: {asset['browser_download_url']}")
print(f" - {asset['name']}: {asset['browser_download_url']}")
return assets, latest_version return assets, latest_version
else: else:
return None, None return None, None
else: else:
if state.debug: consoleLog("Already up-to-date.")
print("Already up-to-date.")
return None, None return None, None
+16 -6
View File
@@ -1,9 +1,12 @@
from core.interface.gui import MainWindow from core.interface.gui import MainWindow
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.network.aria2_integration import aria2server from core.network.aria2_integration import aria2server
from core.network.aria2_integration import send_notification from core.network.aria2_integration import send_notification, update_log
from core.utils.general.logs import get_download_logs
from core.utils.general.shutdown import closehelper, shutdown_event from core.utils.general.shutdown import closehelper, shutdown_event
from core.utils.general.wrappers import run_thread from core.utils.general.wrappers import run_thread
from core.utils.general.loghandler import split_data, check_completed
from core.utils.config.config import read_config from core.utils.config.config import read_config
from PySide6 import QtWidgets from PySide6 import QtWidgets
import qdarktheme import qdarktheme
@@ -25,6 +28,10 @@ def run_gui():
else: else:
app.setStyleSheet(qdarktheme.load_stylesheet("light")) app.setStyleSheet(qdarktheme.load_stylesheet("light"))
widget = MainWindow() widget = MainWindow()
from core.utils.general.logs import set_main_window
set_main_window(widget)
widget.show() widget.show()
sys.exit(app.exec()) sys.exit(app.exec())
@@ -37,13 +44,16 @@ def keyboardinterrupthandler(signum, frame):
if __name__ == "__main__": if __name__ == "__main__":
read_config() read_config()
state.debug = args.debug # override of read_config logs = get_download_logs()
if state.debug: count, downloads = split_data(logs)
print("Starting Aria2 Server") if args.debug:
state.debug = args.debug # override of read_config
consoleLog("Starting Aria2 Server")
state.aria2process = run_aria2server() state.aria2process = run_aria2server()
signal.signal(signal.SIGINT, keyboardinterrupthandler) signal.signal(signal.SIGINT, keyboardinterrupthandler)
run_thread(threading.Thread(target=send_notification, args=(shutdown_event,), daemon=True)) run_thread(threading.Thread(target=send_notification, args=(shutdown_event,), daemon=True))
if state.debug: run_thread(threading.Thread(target=update_log, args=(shutdown_event,), daemon=True))
print("Launching GUI") run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
consoleLog("Launching GUI")
run_gui() run_gui()