Compare commits

...

13 Commits

Author SHA1 Message Date
shayaa 2e2e526d3a Update gui.py 2026-03-10 00:23:08 +01:00
shayaa 11b8f3f265 Update update_checker.py 2026-03-10 00:21:57 +01:00
Vxrtrauter c5e6608ae2 feat: enhance update handling by verifying installer hash and refactoring asset retrieval 2026-03-09 19:03:28 +01:00
Vxrtrauter 214d9a0e23 feat: implement SVG icon handling and update button styles in settings dialog 2026-03-09 18:09:17 +01:00
Vxrtrauter a9f229926b feat: readd image path configuration in settings dialog and update paths tab 2026-03-09 15:16:06 +01:00
Vxrtrauter 8db9f3d9e2 feat: improve hover row delegate rendering by adjusting focus and selection states 2026-03-08 19:49:38 +01:00
Vxrtrauter 80d3ad208d feat: enhance table item focus styles by adding border properties 2026-03-08 19:20:18 +01:00
Vxrtrauter 66217ff9b6 feat: update settings dialog to handle image path as None; improve table item focus styles 2026-03-08 13:58:23 +01:00
Vxrtrauter 9ba11bfa78 feat: enhance error handling and logging in various modules; improve timeout settings for network requests / fix basic issues 2026-03-08 13:32:30 +01:00
Vxrtrauter 43cbfe056c feat: remove ugly spinner label and related functionality from search process 2026-03-08 13:19:39 +01:00
Vxrtrauter 12df32f72a feat: remove unused install path detection function and clean up update process 2026-03-08 13:15:51 +01:00
Vxrtrauter 9e0ee2a36d trigger workflow 2026-03-08 02:16:23 +01:00
Vxrtrauter 5b9bdd2a2a feat: update download process to use temporary installer path and improve silent installation 2026-03-08 02:16:16 +01:00
13 changed files with 203 additions and 166 deletions
+1
View File
@@ -68,3 +68,4 @@ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de
[Run] [Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
Filename: "{app}\{#MyAppExeName}"; Flags: nowait skipifnotsilent
+2
View File
@@ -17,6 +17,8 @@ def scrape_uztracker(query):
for link in links: for link in links:
theme_link = link.find('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic')) theme_link = link.find('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic'))
if not theme_link or not theme_link.b:
continue
url = urljoin(base_url, theme_link['href']) url = urljoin(base_url, theme_link['href'])
title = theme_link.b.text title = theme_link.b.text
author_link = link.find('a', class_="med") author_link = link.find('a', class_="med")
+45 -21
View File
@@ -4,8 +4,9 @@ from core.utils.logging.logs import consoleLog
from core.interface.utils.tabhelper import general_tab from core.interface.utils.tabhelper import general_tab
from core.interface.utils.tabhelper import paths_tab from core.interface.utils.tabhelper import paths_tab
from core.interface.utils.tabhelper import network_tab from core.interface.utils.tabhelper import network_tab
from core.interface.utils.svghelper import svg_icon
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtCore import Qt from PySide6.QtCore import Qt, QSize
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
QPushButton, QPushButton,
@@ -22,6 +23,8 @@ from PySide6.QtWidgets import (
) )
import platform import platform
SVG_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2 6c0-1.1.9-2 2-2h5l2 2h7c1.1 0 2 .9 2 2v10c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6z" fill="{color}"/></svg>'
def settings_dialog(self): def settings_dialog(self):
@@ -113,7 +116,19 @@ def settings_dialog(self):
if dir_path: if dir_path:
download_path.setText(dir_path) download_path.setText(dir_path)
browse_button = QPushButton("📁") browse_button = QPushButton()
browse_button.setFixedSize(36, 36)
browse_button.setIconSize(QSize(24, 24))
browse_button.setIcon(svg_icon(SVG_FOLDER, 24))
browse_button.setCursor(Qt.CursorShape.PointingHandCursor)
browse_button.setStyleSheet("""
QPushButton {
border: none;
background: transparent;
padding: 0px;
}
""")
download_path_layout.addWidget(browse_button) download_path_layout.addWidget(browse_button)
browse_button.clicked.connect(browse_download_path) browse_button.clicked.connect(browse_download_path)
@@ -121,24 +136,35 @@ def settings_dialog(self):
# IMAGE PATH # # IMAGE PATH #
############### ###############
# image_path_container = QWidget() image_path_container = QWidget()
# image_path_layout = QHBoxLayout() image_path_layout = QHBoxLayout()
# image_path = QLineEdit() image_path = QLineEdit()
# image_path_layout.addWidget(QLabel("Image Path (requires restart):")) image_path_layout.addWidget(QLabel("Image Path (requires restart):"))
# image_path_layout.addWidget(image_path) image_path_layout.addWidget(image_path)
# image_path_container.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) image_path_container.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
# image_path_container.setLayout(image_path_layout) image_path_container.setLayout(image_path_layout)
# image_path.setText(state.image_path) image_path.setText(state.image_path)
# def browse_image_path(): def browse_image_path():
# file_path = QFileDialog.getOpenFileName(dialog, "Select Image File", state.image_path, "Image Files (*.png *.jpg)")[0] file_path = QFileDialog.getOpenFileName(dialog, "Select Image File", state.image_path, "Image Files (*.png *.jpg)")[0]
# if file_path: if file_path:
# image_path.setText(file_path) image_path.setText(file_path)
# browse_button = QPushButton("📁") browse_button = QPushButton()
# image_path_layout.addWidget(browse_button) browse_button.setFixedSize(36, 36)
# browse_button.clicked.connect(browse_image_path) browse_button.setIconSize(QSize(24, 24))
browse_button.setIcon(svg_icon(SVG_FOLDER, 24))
browse_button.setCursor(Qt.CursorShape.PointingHandCursor)
browse_button.setStyleSheet("""
QPushButton {
border: none;
background: transparent;
padding: 0px;
}
""")
image_path_layout.addWidget(browse_button)
browse_button.clicked.connect(browse_image_path)
################## ##################
# SPEED LIMITING # # SPEED LIMITING #
@@ -224,8 +250,6 @@ def settings_dialog(self):
else: else:
interface_select.setCurrentIndex(0) interface_select.setCurrentIndex(0)
interface_select.setFixedWidth(180)
interface_select.setFixedHeight(30)
interface_select.setFixedWidth(180) interface_select.setFixedWidth(180)
interface_select.setFixedHeight(30) interface_select.setFixedHeight(30)
interface_layout.addWidget(interface_select) interface_layout.addWidget(interface_select)
@@ -246,7 +270,7 @@ def settings_dialog(self):
download_path.text(), download_path.text(),
down_speed_limit.value(), down_speed_limit.value(),
up_speed_limit.value(), up_speed_limit.value(),
# image_path.text(), image_path.text(),
autoresume_checkbox.isChecked(), autoresume_checkbox.isChecked(),
max_connections.value(), max_connections.value(),
max_downloads.value(), max_downloads.value(),
@@ -258,7 +282,7 @@ def settings_dialog(self):
self.tabs = QTabWidget() self.tabs = QTabWidget()
self.tab1 = general_tab("General", autoresume_container, update_checkbox_container, transparent_window_container, self.tabs) self.tab1 = general_tab("General", autoresume_container, update_checkbox_container, transparent_window_container, self.tabs)
self.tab2 = paths_tab("Paths", download_path_container, self.tabs) self.tab2 = paths_tab("Paths", download_path_container, image_path_container, self.tabs)
self.tab3 = network_tab("Network", interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container, self.tabs) self.tab3 = network_tab("Network", interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container, self.tabs)
dialog.layout().addWidget(self.tabs) dialog.layout().addWidget(self.tabs)
+75 -115
View File
@@ -19,7 +19,6 @@ from PySide6.QtWidgets import (
) )
from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent, QGuiApplication, QColor, QBrush from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent, QGuiApplication, QColor, QBrush
from PySide6.QtSvg import QSvgRenderer
import darkdetect import darkdetect
import threading import threading
import platform import platform
@@ -30,14 +29,14 @@ import libtorrent as lt
import sys import sys
import json import json
import base64 import base64
import winreg
from core.utils.logging.logs import consoleLog, remove_download_log, flush_log_buffer from core.utils.logging.logs import consoleLog, remove_download_log, flush_log_buffer
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
from core.utils.network.update_checker import check_for_updates from core.utils.network.update_checker import get_updates
from core.interface.utils.tabhelper import create_tab from core.interface.utils.tabhelper import create_tab
from core.interface.utils.searchhelper import return_pressed from core.interface.utils.searchhelper import return_pressed
from core.interface.utils.svghelper import svg_icon
from core.interface.dialogs.settings import settings_dialog from core.interface.dialogs.settings import settings_dialog
from core.interface.assets.base64_icons import settings_black_base64 from core.interface.assets.base64_icons import settings_black_base64
from core.interface.assets.base64_icons import settings_white_base64 from core.interface.assets.base64_icons import settings_white_base64
@@ -85,10 +84,19 @@ def _table_stylesheet(view_type="QTableWidget"):
{view_type}::item {{ {view_type}::item {{
border-bottom: 1px solid {c["border"]}; border-bottom: 1px solid {c["border"]};
padding: 6px 14px; padding: 6px 14px;
outline: none;
{color_rule} {color_rule}
}} }}
{view_type}::item:selected {{ {view_type}::item:selected {{
background: {c["selected"]}; background: {c["selected"]};
outline: none;
border: none;
border-bottom: 1px solid {c["border"]};
}}
{view_type}::item:focus {{
outline: none;
border: none;
border-bottom: 1px solid {c["border"]};
}} }}
QHeaderView {{ QHeaderView {{
background: transparent; background: transparent;
@@ -109,52 +117,37 @@ def _table_stylesheet(view_type="QTableWidget"):
""" """
def _svg_icon(svg_str, size=20):
app = QtWidgets.QApplication.instance()
if app:
if _is_dark_mode():
color = "white"
else:
color = "#555555"
else:
color = "white"
svg = svg_str.replace("{color}", color)
renderer = QSvgRenderer(QByteArray(svg.encode()))
pixmap = QPixmap(size, size)
pixmap.fill(Qt.GlobalColor.transparent)
from PySide6.QtGui import QPainter
painter = QPainter(pixmap)
renderer.render(painter)
painter.end()
return QIcon(pixmap)
SVG_PLAY = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><polygon points="6,3 20,12 6,21" fill="{color}"/></svg>' SVG_PLAY = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><polygon points="6,3 20,12 6,21" fill="{color}"/></svg>'
SVG_PAUSE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="5" y="3" width="4" height="18" rx="1" fill="{color}"/><rect x="15" y="3" width="4" height="18" rx="1" fill="{color}"/></svg>' SVG_PAUSE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="5" y="3" width="4" height="18" rx="1" fill="{color}"/><rect x="15" y="3" width="4" height="18" rx="1" fill="{color}"/></svg>'
SVG_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2 6c0-1.1.9-2 2-2h5l2 2h7c1.1 0 2 .9 2 2v10c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6z" fill="{color}"/></svg>' SVG_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2 6c0-1.1.9-2 2-2h5l2 2h7c1.1 0 2 .9 2 2v10c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6z" fill="{color}"/></svg>'
def _verify_hash(file_path, expected_hash):
import hashlib
sha256 = hashlib.sha256()
def _find_install_path(): with open(file_path, "rb") as f:
uninstall_key = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" for chunk in iter(lambda: f.read(8192), b""):
for hive in (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE): sha256.update(chunk)
try:
with winreg.OpenKey(hive, uninstall_key) as key: return f"sha256:{sha256.hexdigest()}" == expected_hash
for i in range(winreg.QueryInfoKey(key)[0]):
with winreg.OpenKey(key, winreg.EnumKey(key, i)) as subkey: def _download_update(assets):
try: import tempfile
name = winreg.QueryValueEx(subkey, "DisplayName")[0] import time
if "SoftwareManager" in name:
return winreg.QueryValueEx(subkey, "InstallLocation")[0]
except OSError:
continue
except OSError:
continue
return None
def download_update(latest_version): for asset in assets:
new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows-setup.exe" if "-windows-setup.exe" in asset["name"]:
url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows-setup.exe" filename = asset["name"]
setup_hash = asset["hash"]
url = asset["url"]
break
if not filename:
consoleLog("Error: No Windows installer found in release assets")
return
installer_path = os.path.join(tempfile.gettempdir(), filename)
progress = QtWidgets.QProgressDialog("Downloading installer...", None, 0, 0) progress = QtWidgets.QProgressDialog("Downloading installer...", None, 0, 0)
progress.setWindowTitle("Updating") progress.setWindowTitle("Updating")
@@ -171,44 +164,30 @@ def download_update(latest_version):
response = r.get(url, allow_redirects=True, stream=True) response = r.get(url, allow_redirects=True, stream=True)
total = int(response.headers.get("content-length", 0)) total = int(response.headers.get("content-length", 0))
downloaded = 0 downloaded = 0
chunks = [] with open(installer_path, "wb") as f:
for chunk in response.iter_content(chunk_size=65536): for chunk in response.iter_content(chunk_size=65536):
chunks.append(chunk)
downloaded += len(chunk)
if total > 0:
progress.setValue(int(downloaded * 100 / total))
QtWidgets.QApplication.processEvents()
with open(new_filename, "wb") as f:
for chunk in chunks:
f.write(chunk) f.write(chunk)
downloaded += len(chunk)
if total > 0:
progress.setValue(int(downloaded * 100 / total))
QtWidgets.QApplication.processEvents()
if not os.path.exists(new_filename): if not os.path.exists(installer_path):
progress.close() progress.close()
raise FileNotFoundError("Executable not found") raise FileNotFoundError("Executable not found")
if _verify_hash(installer_path, setup_hash):
consoleLog(f"Sucessfully validated installer hash ({setup_hash})")
else:
consoleLog("Error: Invalid Filehash, file may be corrupted")
sys.exit(0)
progress.setLabelText("Installing update...") progress.setLabelText("Installing update...")
progress.setValue(100) progress.setValue(100)
QtWidgets.QApplication.processEvents() QtWidgets.QApplication.processEvents()
proc = subprocess.Popen([new_filename, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/SP-", "/CLOSEAPPLICATIONS", "/NORESTART"]) subprocess.Popen([installer_path, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/SP-", "/CLOSEAPPLICATIONS"])
while proc.poll() is None: time.sleep(1)
QtWidgets.QApplication.processEvents()
import time
time.sleep(0.1)
progress.close()
try:
os.remove(new_filename)
except OSError:
pass
install_path = _find_install_path()
if not install_path:
raise FileNotFoundError("Could not find SoftwareManager install location in registry")
subprocess.Popen([os.path.join(install_path, "SoftwareManager.exe")])
sys.exit(0) sys.exit(0)
@@ -246,21 +225,19 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
# Check for updates on Windows # Check for updates on Windows
if state.ignore_updates is False and platform.system() == "Windows": if state.ignore_updates is False and platform.system() == "Windows":
result = check_for_updates() assets = get_updates()
if result != (None, None): if assets != None:
assets, latest_version = result
if assets: msg = QMessageBox()
msg = QMessageBox() msg.setIcon(QMessageBox.Icon.Information)
msg.setIcon(QMessageBox.Icon.Information) msg.setWindowTitle("Update Available")
msg.setWindowTitle("Update Available") msg.setText("A new version is available.")
msg.setText("A new version is available.") msg.setInformativeText("Press Ok to download the update.")
msg.setInformativeText("Press Ok to download the update.") msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore)
msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore)
response = msg.exec_() response = msg.exec_()
if response == QMessageBox.StandardButton.Ok: if response == QMessageBox.StandardButton.Ok:
download_update(latest_version) _download_update(assets)
self.setWindowTitle("Software Manager") self.setWindowTitle("Software Manager")
self.setGeometry(100, 100, 800, 600) self.setGeometry(100, 100, 800, 600)
@@ -275,15 +252,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.searchbar.setMinimumHeight(30) self.searchbar.setMinimumHeight(30)
self.searchbar.returnPressed.connect(self._start_search) self.searchbar.returnPressed.connect(self._start_search)
self._spinner_label = QLabel()
self._spinner_label.setFixedSize(20, 20)
self._spinner_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._spinner_label.hide()
self._spinner_angle = 0
self._spinner_timer = QTimer()
self._spinner_timer.setInterval(80)
self._spinner_timer.timeout.connect(self._update_spinner)
self.dlbutton = QtWidgets.QPushButton("Download") self.dlbutton = QtWidgets.QPushButton("Download")
self.dlbutton.setCursor(Qt.CursorShape.PointingHandCursor) self.dlbutton.setCursor(Qt.CursorShape.PointingHandCursor)
self.libraryList = QListWidget() self.libraryList = QListWidget()
@@ -368,7 +336,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
search_row = QHBoxLayout() search_row = QHBoxLayout()
search_row.addWidget(self.searchbar) search_row.addWidget(self.searchbar)
search_row.addWidget(self._spinner_label)
containerLayout.addLayout(search_row) containerLayout.addLayout(search_row)
containerLayout.addWidget(state.tracker_list[state.tracker]) containerLayout.addWidget(state.tracker_list[state.tracker])
@@ -494,21 +461,27 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
class HoverRowDelegate(QStyledItemDelegate): class HoverRowDelegate(QStyledItemDelegate):
def paint(self, painter, option, index): def paint(self, painter, option, index):
opt = QtWidgets.QStyleOptionViewItem(option)
opt.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus
opt.state &= ~QtWidgets.QStyle.StateFlag.State_Selected
if index.row() == MainWindow._instance._hovered_row: if index.row() == MainWindow._instance._hovered_row:
painter.save() painter.save()
painter.fillRect(option.rect, _theme_colors()["hover"]) painter.fillRect(opt.rect, _theme_colors()["hover"])
painter.restore() painter.restore()
super().paint(painter, option, index) super().paint(painter, opt, index)
class PauseResumeDelegate(QStyledItemDelegate): class PauseResumeDelegate(QStyledItemDelegate):
clicked = Signal(int) clicked = Signal(int)
def paint(self, painter, option, index): def paint(self, painter, option, index):
opt = QtWidgets.QStyleOptionViewItem(option)
opt.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus
opt.state &= ~QtWidgets.QStyle.StateFlag.State_Selected
if index.row() == MainWindow._instance._hovered_row: if index.row() == MainWindow._instance._hovered_row:
painter.save() painter.save()
painter.fillRect(option.rect, _theme_colors()["hover"]) painter.fillRect(opt.rect, _theme_colors()["hover"])
painter.restore() painter.restore()
super().paint(painter, option, index) super().paint(painter, opt, index)
def setEditorData(self, editor, index): def setEditorData(self, editor, index):
button = editor.findChild(QtWidgets.QPushButton) button = editor.findChild(QtWidgets.QPushButton)
@@ -519,11 +492,11 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
if button: if button:
if status.state == lt.torrent_status.seeding: if status.state == lt.torrent_status.seeding:
button.setIcon(_svg_icon(SVG_FOLDER, 18)) button.setIcon(svg_icon(SVG_FOLDER, 18))
button.setText("") button.setText("")
else: else:
is_user_paused = status.paused and not bool(magnetdl.flags() & lt.torrent_flags.auto_managed) is_user_paused = status.paused and not bool(magnetdl.flags() & lt.torrent_flags.auto_managed)
button.setIcon(_svg_icon(SVG_PLAY if is_user_paused else SVG_PAUSE, 18)) button.setIcon(svg_icon(SVG_PLAY if is_user_paused else SVG_PAUSE, 18))
button.setText("") button.setText("")
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
@@ -537,11 +510,11 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)
if status.state == lt.torrent_status.seeding: if status.state == lt.torrent_status.seeding:
btnPause = QtWidgets.QPushButton() btnPause = QtWidgets.QPushButton()
btnPause.setIcon(_svg_icon(SVG_FOLDER, 18)) btnPause.setIcon(svg_icon(SVG_FOLDER, 18))
else: else:
btnPause = QtWidgets.QPushButton() btnPause = QtWidgets.QPushButton()
is_user_paused = status.paused and not bool(magnetdl.flags() & lt.torrent_flags.auto_managed) is_user_paused = status.paused and not bool(magnetdl.flags() & lt.torrent_flags.auto_managed)
btnPause.setIcon(_svg_icon(SVG_PLAY if is_user_paused else SVG_PAUSE, 18)) btnPause.setIcon(svg_icon(SVG_PLAY if is_user_paused else SVG_PAUSE, 18))
btnPause.setIconSize(QSize(18, 18)) btnPause.setIconSize(QSize(18, 18))
btnPause.setFixedSize(30, 30) btnPause.setFixedSize(30, 30)
btnPause.setCursor(Qt.CursorShape.PointingHandCursor) btnPause.setCursor(Qt.CursorShape.PointingHandCursor)
@@ -733,10 +706,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.context_menu.addAction("Delete File", self.deleteFileAction) self.context_menu.addAction("Delete File", self.deleteFileAction)
def _start_search(self): def _start_search(self):
self._spinner_label.show()
self._spinner_angle = 0
self._spinner_timer.start()
self._update_spinner()
self.searchbar.setEnabled(False) self.searchbar.setEnabled(False)
def _search_thread(): def _search_thread():
try: try:
@@ -746,18 +715,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
run_thread(threading.Thread(target=_search_thread)) run_thread(threading.Thread(target=_search_thread))
def _on_search_finished(self): def _on_search_finished(self):
self._spinner_timer.stop()
self._spinner_label.hide()
self.searchbar.setEnabled(True) self.searchbar.setEnabled(True)
self.searchbar.setFocus() self.searchbar.setFocus()
def _update_spinner(self):
frames = ["", "", "", ""]
idx = (self._spinner_angle // 1) % len(frames)
self._spinner_label.setText(frames[idx])
self._spinner_label.setStyleSheet("font-size: 16px; color: gray;")
self._spinner_angle += 1
@staticmethod @staticmethod
def add_log(text): def add_log(text):
if MainWindow._instance: if MainWindow._instance:
+1 -1
View File
@@ -50,7 +50,7 @@ def return_pressed(self):
elif state.tracker is not None: elif state.tracker is not None:
state.posts = scrapers[state.tracker](search_text) state.posts = scrapers[state.tracker](search_text)
if state.posts == []: if not state.posts:
consoleLog(f"No Results for {search_text}") consoleLog(f"No Results for {search_text}")
state.tracker_list[state.tracker].clear() state.tracker_list[state.tracker].clear()
self.show_empty_results(True) self.show_empty_results(True)
+30
View File
@@ -0,0 +1,30 @@
from PySide6 import QtWidgets
from PySide6.QtCore import Qt, QByteArray
from PySide6.QtGui import QIcon, QPixmap, QPainter
from PySide6.QtSvg import QSvgRenderer
import darkdetect
def _is_dark_mode():
return darkdetect.isDark()
def svg_icon(svg_str, size=20):
app = QtWidgets.QApplication.instance()
if app:
if _is_dark_mode():
color = "white"
else:
color = "#555555"
else:
color = "white"
svg = svg_str.replace("{color}", color)
renderer = QSvgRenderer(QByteArray(svg.encode()))
pixmap = QPixmap(size, size)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
renderer.render(painter)
painter.end()
return QIcon(pixmap)
+2 -2
View File
@@ -30,11 +30,11 @@ def general_tab(title, autoresume, update_checkbox, transparent_window, tabs):
tabs.addTab(tab, title) tabs.addTab(tab, title)
return tab return tab
def paths_tab(title, download_path, tabs): def paths_tab(title, download_path, image_path, tabs):
tab = QWidget() tab = QWidget()
layout = QVBoxLayout() layout = QVBoxLayout()
layout.addWidget(download_path) layout.addWidget(download_path)
# layout.addWidget(image_path) layout.addWidget(image_path)
layout.addStretch() layout.addStretch()
tab.setLayout(layout) tab.setLayout(layout)
tabs.addTab(tab, title) tabs.addTab(tab, title)
+14 -8
View File
@@ -74,10 +74,13 @@ def add_download(magnet_uri, dl_path=state.download_path):
consoleLog("Skipping Downloading, download already running... ") consoleLog("Skipping Downloading, download already running... ")
return False return False
magnetdl = lt.parse_magnet_uri(magnet_uri) try:
magnetdl.save_path = dl_path magnetdl = lt.parse_magnet_uri(magnet_uri)
magnetdl.save_path = dl_path
download = state.dl_session.add_torrent(magnetdl) download = state.dl_session.add_torrent(magnetdl)
except Exception as e:
consoleLog(f"Failed to add torrent: {e}")
return False
state.active_downloads[magnet_uri] = download state.active_downloads[magnet_uri] = download
consoleLog(f"Added {magnet_uri} to downloads") consoleLog(f"Added {magnet_uri} to downloads")
@@ -95,10 +98,13 @@ def add_seed(magnet_uri, file_path):
consoleLog("Already seeding this torrent") consoleLog("Already seeding this torrent")
return False return False
magnetdl = lt.parse_magnet_uri(magnet_uri) try:
magnetdl.save_path = os.path.dirname(file_path) magnetdl = lt.parse_magnet_uri(magnet_uri)
magnetdl.save_path = os.path.dirname(file_path)
handle = state.dl_session.add_torrent(magnetdl) handle = state.dl_session.add_torrent(magnetdl)
except Exception as e:
consoleLog(f"Failed to add seed: {e}")
return False
state.active_downloads[magnet_uri] = handle state.active_downloads[magnet_uri] = handle
state.seeded_magnets.add(magnet_uri) state.seeded_magnets.add(magnet_uri)
return True return True
+1 -1
View File
@@ -30,7 +30,7 @@ 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, timeout=15) # eventually impl. cloudscraper
consoleLog("Sent Request to retrieve Magnet Link...") consoleLog("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')
+4 -1
View File
@@ -22,7 +22,10 @@ def check_downloads(downloads):
for download in downloads: for download in downloads:
if os.path.exists(download.path): if os.path.exists(download.path):
consoleLog(f"Existing Download: {download.title}") consoleLog(f"Existing Download: {download.title}")
seed_magnet(download.magnet_uri, download.path) try:
seed_magnet(download.magnet_uri, download.path)
except Exception as e:
consoleLog(f"Failed to seed {download.title}: {e}")
else: else:
consoleLog(f"Inexistent Download: {download.title}") consoleLog(f"Inexistent Download: {download.title}")
remove_download_log(download.magnet_uri) remove_download_log(download.magnet_uri)
+1 -1
View File
@@ -89,7 +89,7 @@ def _remove_download_log_inner(magnet_uri) -> DownloadList:
magnet_link = (magnet_uri or "").strip() magnet_link = (magnet_uri or "").strip()
title = next((getattr(d, 'title', 'Unknown') for d in downloads if (getattr(d, 'magnet_uri', None) or '').strip() == magnet_link or (getattr(d, 'url', None) or '').strip() == magnet_link)) title = next((getattr(d, 'title', 'Unknown') for d in downloads if (getattr(d, 'magnet_uri', None) or '').strip() == magnet_link or (getattr(d, 'url', None) or '').strip() == magnet_link), 'Unknown')
downloads = [d for d in downloads if (getattr(d, 'magnet_uri', None) or "").strip() != magnet_link and (getattr(d, 'url', None) or "").strip() != magnet_link] downloads = [d for d in downloads if (getattr(d, 'magnet_uri', None) or "").strip() != magnet_link and (getattr(d, 'url', None) or "").strip() != magnet_link]
consoleLog(f"Removed {title} from Log File") consoleLog(f"Removed {title} from Log File")
+23 -13
View File
@@ -2,30 +2,40 @@ import requests
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.logging.logs import consoleLog from core.utils.logging.logs import consoleLog
def check_for_updates(): def get_updates():
url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases" url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases/latest"
response = requests.get(url)
try:
response = requests.get(url, timeout=15)
except requests.RequestException as e:
consoleLog(f"Failed to fetch releases: {e}")
return None
if response.status_code != 200: if response.status_code != 200:
consoleLog(f"Failed to fetch releases: {response.status_code}") consoleLog(f"Failed to fetch releases: {response.status_code}")
exit(1) return None
releases = response.json() release = response.json()
releases.sort(key=lambda r: r["published_at"], reverse=True)
latest_release = releases[0] latest_version = release["name"]
latest_version = latest_release["name"] assets = release["assets"]
assets = latest_release["assets"]
release_assets = []
if latest_version != state.version: if latest_version != state.version:
consoleLog(f"New release available: {latest_version}") consoleLog(f"New release available: {latest_version}")
if assets: if assets:
consoleLog("Assets:") consoleLog("Assets:")
for asset in assets: for asset in assets:
consoleLog(f" - {asset['name']}: {asset['browser_download_url']}") consoleLog(f"{asset['name']}")
return assets, latest_version release_assets.append(dict(
name=asset['name'],
url=asset['browser_download_url'],
hash=asset['digest']
))
return release_assets
else: else:
return None, None return None
else: else:
consoleLog("Already up-to-date.") consoleLog("Already up-to-date.")
return None, None return None
+2 -1
View File
@@ -56,7 +56,8 @@ def main():
consoleLog("Started Thread: check_completed") consoleLog("Started Thread: check_completed")
run_thread(threading.Thread(target=check_deleted_files, args=(shutdown_event,), daemon=True)) run_thread(threading.Thread(target=check_deleted_files, args=(shutdown_event,), daemon=True))
consoleLog("Started Thread: check_deleted_files") consoleLog("Started Thread: check_deleted_files")
check_downloads(downloads) run_thread(threading.Thread(target=check_downloads, args=(downloads,)))
consoleLog("Started Thread: check_downloads")
elapsed = time.perf_counter() - start_time elapsed = time.perf_counter() - start_time
consoleLog(f"Initialization completed in {elapsed:.2f}s. Launching GUI") consoleLog(f"Initialization completed in {elapsed:.2f}s. Launching GUI")
run_gui() run_gui()