Compare commits

...

10 Commits

Author SHA1 Message Date
Vxrtrauter 5a9d9067b9 fix exception spam 2026-01-13 21:35:03 +01:00
Vxrtrauter e0eaf250e5 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-01-13 21:32:07 +01:00
Vxrtrauter 1c4c010e4e Add download completion tracking and update logging functionality 2026-01-13 21:31:33 +01:00
shayaa c1509f9172 fix crash because of me being retard 2026-01-13 20:36:15 +01:00
Vxrtrauter 97eabf6ec6 Add basic download logging + fix debug 2026-01-09 23:23:16 +01:00
Vxrtrauter 5eca1a678e Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-01-07 23:07:14 +01:00
Vxrtrauter 7e59fdc2d3 add (almost) final download log logic 2026-01-07 23:07:08 +01:00
Vxrtrauter ad6fa428a0 add basic download log json structure 2026-01-07 22:41:11 +01:00
Vxrtrauter 56e8fada4d add settings_path to AppState and add models for download logging 2026-01-07 22:23:29 +01:00
shayaa e950d2f90d Refactor dev.yml to use bash for build steps
Updated the workflow to use bash for injecting commit hash and building executables on Windows and Unix. (done with claude)
2025-12-20 04:29:22 +01:00
9 changed files with 244 additions and 21 deletions
+22 -9
View File
@@ -81,14 +81,14 @@ jobs:
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "Generated version: $VERSION"
- name: Inject commit hash into code (Windows)
if: runner.os == 'Windows'
shell: pwsh
- name: Inject commit hash into code
shell: bash
run: |
$json = '{
"version": "' + $env:VERSION + '"
}'
$json | Out-File -Encoding utf8 build_info.json
cat > build_info.json << EOF
{
"version": "${{ env.VERSION }}"
}
EOF
- name: Download aria2c (Windows)
if: runner.os == 'Windows'
@@ -99,11 +99,24 @@ jobs:
- name: Build executable with PyInstaller (Windows)
if: runner.os == 'Windows'
run: pyinstaller --onefile --windowed --add-data "build_info.json:." --add-data "deps/aria2c.exe:." --name ${{ env.APP_NAME }} main.py
shell: bash
run: |
pyinstaller --onefile --windowed \
--add-data "build_info.json;." \
--add-data "deps/aria2c.exe;." \
--add-data "core/interface/assets;core/interface/assets" \
--name ${{ env.APP_NAME }} \
main.py
- name: Build executable with PyInstaller (Unix)
if: runner.os != 'Windows'
run: pyinstaller --onefile --windowed --name ${{ env.APP_NAME }} main.py
shell: bash
run: |
pyinstaller --onefile --windowed \
--add-data "build_info.json:." \
--add-data "core/interface/assets:core/interface/assets" \
--name ${{ env.APP_NAME }} \
main.py
- name: Make executable (Unix)
if: runner.os != 'Windows'
+3 -2
View File
@@ -230,8 +230,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.tracker_list.activated.connect(self.set_tracker)
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)
else:
settings_action = QAction(QIcon("core/interface/assets/settings_black.png"), "Settings", self)
settings_action.triggered.connect(lambda: settings_dialog(self))
toolbar.addAction(settings_action)
@@ -286,4 +287,4 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
def resizeEvent(self, event):
super().resizeEvent(event)
table_width = self.qtablewidget.viewport().width()
self.qtablewidget.setColumnWidth(1, int(table_width * 0.3))
self.qtablewidget.setColumnWidth(1, int(table_width * 0.3))
+18
View File
@@ -1,6 +1,7 @@
import aria2p
import subprocess
from core.utils.data.state import state
from core.utils.general.logs import update_download_completed_by_hash
from plyer import notification
import time
import sys
@@ -72,4 +73,21 @@ def send_notification(shutdown_event):
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)
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:
if state.debug:
print(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)
+7 -7
View File
@@ -10,14 +10,14 @@ def create_config():
"ignore_updates": f"{state.ignore_updates}", "image_path": f"{state.image_path}"}
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"))
else:
config_dir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
settings_path = os.path.join(config_dir, "SoftwareManager")
os.makedirs(settings_path, exist_ok=True)
state.settings_path = os.path.join(config_dir, "SoftwareManager")
os.makedirs(state.settings_path, exist_ok=True)
with open(os.path.join(settings_path, "config.yml"), 'w') as cf:
with open(os.path.join(state.settings_path, "config.yml"), 'w') as cf:
config.write(cf)
@@ -26,11 +26,11 @@ def read_config():
config = configparser.ConfigParser()
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"))
else:
config_dir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
settings_path = os.path.join(config_dir, "SoftwareManager")
config_file = os.path.join(settings_path, "config.yml")
state.settings_path = os.path.join(config_dir, "SoftwareManager")
config_file = os.path.join(state.settings_path, "config.yml")
if not os.path.exists(config_file):
create_config()
+15
View File
@@ -0,0 +1,15 @@
from dataclasses import dataclass
from typing import List
@dataclass
class Download:
title: str
url: str
magnet_uri: str
completed: bool
@dataclass
class DownloadList:
count: int
data: List[Download]
+1
View File
@@ -24,6 +24,7 @@ class AppState(QObject):
self.aria2: Any = None
self.aria2p: Any = None
self.aria2_threads: int = 4
self.settings_path: str = None
@property
def image_path(self) -> str:
+171
View File
@@ -0,0 +1,171 @@
from core.utils.data.models import Download, DownloadList
from core.utils.data.state import state
from dataclasses import asdict
import json
import os
import re
def add_download_log(title, url, 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:
downloads = []
else:
downloads = []
if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads):
if state.debug:
print("Skipped Logging, download already in file")
return DownloadList(data=downloads, count=len(downloads)) # thanks again claude (im stupid)
downloads.append(Download(
title=title,
url=url,
magnet_uri=magnet_uri,
completed=completed,
))
if state.debug:
print(f"Added {title} to Log File")
download_list = DownloadList(data=downloads, count=len(downloads))
with open(downloads_file, "w") as file:
json.dump(asdict(download_list), file, indent=4)
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:
if state.debug:
print(f"Error loading downloads.json: {e}")
downloads = []
else:
downloads = []
identifier = (magnet_uri or "").strip()
if state.debug:
print(f"Updating download completed for identifier: {identifier!r}")
print(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]
print(f" [{i}] magnet: {mag}... url: {url}...")
except Exception as e:
print(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()
if state.debug:
print(f"Comparing with magnet: {stored_magnet[:50] if stored_magnet else 'None'}...")
if identifier and (stored_magnet == identifier or stored_url == identifier):
if state.debug:
print(f"Match found! Setting completed={completed}")
download.completed = completed
found = True
except Exception as e:
if state.debug:
print(f"Error comparing download: {e}")
if not found:
if state.debug:
print("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)
if state.debug:
print("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:
if state.debug:
print(f"Error loading downloads.json: {e}")
downloads = []
else:
downloads = []
info_hash_upper = (info_hash or "").upper().strip()
if state.debug:
print(f"Updating download by hash: {info_hash_upper}")
print(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:
if state.debug:
print(f"Error comparing download: {e}")
if not found:
if state.debug:
print("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)
if state.debug:
print("Updated download log")
return download_list
+3 -1
View File
@@ -4,6 +4,8 @@ from core.utils.data.tracker import get_magnet_link
from core.network.aria2_wrapper import start_client
from core.network.aria2_wrapper import add_magnet
from core.utils.general.wrappers import run_thread
from core.utils.general.logs import add_download_log
import threading
@@ -12,7 +14,6 @@ def download_selected(item, posts, post_titles):
if state.debug:
print(f"Downloading {item.text()}")
run_thread(threading.Thread(target=run_download, args=(item.text(), posts, post_titles)))
else:
if state.debug:
print("No item selected for download.")
@@ -23,6 +24,7 @@ def run_download(item, posts, post_titles):
print("Selected URL: ", post_url)
magnet_uri = get_magnet_link(post_url)
start_client()
add_download_log(item, post_url, magnet_uri, False)
add_magnet(magnet_uri)
+4 -2
View File
@@ -1,7 +1,7 @@
from core.interface.gui import MainWindow
from core.utils.data.state import state
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.shutdown import closehelper, shutdown_event
from core.utils.general.wrappers import run_thread
from core.utils.config.config import read_config
@@ -37,12 +37,14 @@ def keyboardinterrupthandler(signum, frame):
if __name__ == "__main__":
read_config()
state.debug = args.debug # override of read_config
if args.debug:
state.debug = args.debug # override of read_config
if state.debug:
print("Starting Aria2 Server")
state.aria2process = run_aria2server()
signal.signal(signal.SIGINT, keyboardinterrupthandler)
run_thread(threading.Thread(target=send_notification, args=(shutdown_event,), daemon=True))
run_thread(threading.Thread(target=update_log, args=(shutdown_event,), daemon=True))
if state.debug:
print("Launching GUI")
run_gui()