mirror of
https://github.com/KeksPirates/SoftwareManager.git
synced 2026-08-04 01:49:42 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0eaf250e5 | |||
| 1c4c010e4e | |||
| c1509f9172 | |||
| 97eabf6ec6 | |||
| 5eca1a678e | |||
| 7e59fdc2d3 | |||
| ad6fa428a0 | |||
| 56e8fada4d | |||
| e950d2f90d |
@@ -81,14 +81,14 @@ jobs:
|
|||||||
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||||
echo "Generated version: $VERSION"
|
echo "Generated version: $VERSION"
|
||||||
|
|
||||||
- name: Inject commit hash into code (Windows)
|
- name: Inject commit hash into code
|
||||||
if: runner.os == 'Windows'
|
shell: bash
|
||||||
shell: pwsh
|
|
||||||
run: |
|
run: |
|
||||||
$json = '{
|
cat > build_info.json << EOF
|
||||||
"version": "' + $env:VERSION + '"
|
{
|
||||||
}'
|
"version": "${{ env.VERSION }}"
|
||||||
$json | Out-File -Encoding utf8 build_info.json
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
- name: Download aria2c (Windows)
|
- name: Download aria2c (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
@@ -99,11 +99,24 @@ jobs:
|
|||||||
|
|
||||||
- name: Build executable with PyInstaller (Windows)
|
- name: Build executable with PyInstaller (Windows)
|
||||||
if: runner.os == '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)
|
- name: Build executable with PyInstaller (Unix)
|
||||||
if: runner.os != 'Windows'
|
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)
|
- name: Make executable (Unix)
|
||||||
if: runner.os != 'Windows'
|
if: runner.os != 'Windows'
|
||||||
|
|||||||
@@ -230,8 +230,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)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -73,3 +74,23 @@ def send_notification(shutdown_event):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
time.sleep(5)
|
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 as e:
|
||||||
|
if state.debug:
|
||||||
|
print(f"Error in update_log: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
time.sleep(5)
|
||||||
@@ -14,10 +14,10 @@ def create_config():
|
|||||||
else:
|
else:
|
||||||
config_dir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
config_dir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
||||||
|
|
||||||
settings_path = os.path.join(config_dir, "SoftwareManager")
|
state.settings_path = os.path.join(config_dir, "SoftwareManager")
|
||||||
os.makedirs(settings_path, exist_ok=True)
|
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)
|
config.write(cf)
|
||||||
|
|
||||||
|
|
||||||
@@ -29,8 +29,8 @@ def read_config():
|
|||||||
config_dir = os.environ.get("APPDATA", os.path.expanduser("~\\AppData\\Roaming"))
|
config_dir = os.environ.get("APPDATA", os.path.expanduser("~\\AppData\\Roaming"))
|
||||||
else:
|
else:
|
||||||
config_dir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
config_dir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
||||||
settings_path = os.path.join(config_dir, "SoftwareManager")
|
state.settings_path = os.path.join(config_dir, "SoftwareManager")
|
||||||
config_file = os.path.join(settings_path, "config.yml")
|
config_file = os.path.join(state.settings_path, "config.yml")
|
||||||
|
|
||||||
if not os.path.exists(config_file):
|
if not os.path.exists(config_file):
|
||||||
create_config()
|
create_config()
|
||||||
|
|||||||
@@ -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]
|
||||||
@@ -24,6 +24,7 @@ class AppState(QObject):
|
|||||||
self.aria2: Any = None
|
self.aria2: Any = None
|
||||||
self.aria2p: Any = None
|
self.aria2p: Any = None
|
||||||
self.aria2_threads: int = 4
|
self.aria2_threads: int = 4
|
||||||
|
self.settings_path: str = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image_path(self) -> str:
|
def image_path(self) -> str:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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 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
|
||||||
|
|
||||||
|
|
||||||
@@ -12,7 +14,6 @@ def download_selected(item, posts, post_titles):
|
|||||||
if state.debug:
|
if state.debug:
|
||||||
print(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:
|
if state.debug:
|
||||||
print("No item selected for download.")
|
print("No item selected for download.")
|
||||||
@@ -23,6 +24,7 @@ def run_download(item, posts, post_titles):
|
|||||||
print("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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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.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.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.config.config import read_config
|
from core.utils.config.config import read_config
|
||||||
@@ -37,12 +37,14 @@ def keyboardinterrupthandler(signum, frame):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
read_config()
|
read_config()
|
||||||
|
if args.debug:
|
||||||
state.debug = args.debug # override of read_config
|
state.debug = args.debug # override of read_config
|
||||||
if state.debug:
|
if state.debug:
|
||||||
print("Starting Aria2 Server")
|
print("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))
|
||||||
|
run_thread(threading.Thread(target=update_log, args=(shutdown_event,), daemon=True))
|
||||||
if state.debug:
|
if state.debug:
|
||||||
print("Launching GUI")
|
print("Launching GUI")
|
||||||
run_gui()
|
run_gui()
|
||||||
|
|||||||
Reference in New Issue
Block a user