Compare commits

...

18 Commits

Author SHA1 Message Date
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
Vxrtrauter ff0e1a0798 trigger workflow" 2026-03-08 01:55:59 +01:00
Vxrtrauter 891c5a86c1 feat: enhance update process with progress dialog and install path detection 2026-03-08 01:55:23 +01:00
KeksNino a5190f151c fix: temporarily remove subprocess.Popen to stop opening softwaremanager.exe before update finished 2026-03-08 00:41:46 +01:00
Vxrtrauter 95f6640212 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-03-08 00:29:13 +01:00
Vxrtrauter 78a47e89d3 fix: remove broken image path handling from settings dialog and paths tab 2026-03-08 00:29:10 +01:00
KeksNino e47efab901 fix: remove restartapplications flag in updater 2026-03-08 00:25:32 +01:00
KeksNino dbd5e79678 trigger github workflow to test updater 2026-03-08 00:08:37 +01:00
12 changed files with 221 additions and 93 deletions
+7
View File
@@ -27,24 +27,30 @@
AppId={{8F2E4B6A-1C3D-4E5F-9A7B-0D8E6F2C4A1B} AppId={{8F2E4B6A-1C3D-4E5F-9A7B-0D8E6F2C4A1B}
AppName={#MyAppName} AppName={#MyAppName}
AppVersion={#MyAppVersion} AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher} AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL} AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}/issues AppSupportURL={#MyAppURL}/issues
AppUpdatesURL={#MyAppURL}/releases
DefaultDirName={autopf}\{#MyAppName} DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName} DefaultGroupName={#MyAppName}
AllowNoIcons=yes AllowNoIcons=yes
LicenseFile=LICENSE
OutputDir={#MyOutputDir} OutputDir={#MyOutputDir}
OutputBaseFilename={#MyOutputFilename} OutputBaseFilename={#MyOutputFilename}
Compression=lzma2 Compression=lzma2
SolidCompression=yes SolidCompression=yes
WizardStyle=modern WizardStyle=modern
DisableWelcomePage=no
PrivilegesRequired=lowest PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog PrivilegesRequiredOverridesAllowed=dialog
UninstallDisplayIcon={app}\{#MyAppExeName} UninstallDisplayIcon={app}\{#MyAppExeName}
UninstallDisplayName={#MyAppName}
ArchitecturesAllowed=x64compatible ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible ArchitecturesInstallIn64BitMode=x64compatible
CloseApplications=yes CloseApplications=yes
RestartApplications=yes RestartApplications=yes
ShowLanguageDialog=auto
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
@@ -62,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")
+29 -5
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)
@@ -136,7 +151,18 @@ def settings_dialog(self):
if file_path: if file_path:
image_path.setText(file_path) image_path.setText(file_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;
}
""")
image_path_layout.addWidget(browse_button) image_path_layout.addWidget(browse_button)
browse_button.clicked.connect(browse_image_path) browse_button.clicked.connect(browse_image_path)
@@ -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)
+113 -63
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
@@ -27,7 +26,6 @@ import requests as r
import os import os
import subprocess import subprocess
import libtorrent as lt import libtorrent as lt
import time
import sys import sys
import json import json
import base64 import base64
@@ -35,9 +33,10 @@ from core.utils.logging.logs import consoleLog, remove_download_log, flush_log_b
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,72 @@ 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 download_update(latest_version): with open(file_path, "rb") as f:
new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows-setup.exe" for chunk in iter(lambda: f.read(8192), b""):
url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows-setup.exe" sha256.update(chunk)
print("Downloading update...", True) return f"sha256:{sha256.hexdigest()}" == expected_hash
response = r.get(url, allow_redirects=True)
with open(new_filename, "wb") as f: def _download_update(assets):
f.write(response.content) import tempfile
if not os.path.exists(new_filename): import time
for asset in assets:
if "-windows-setup.exe" in asset["name"]:
filename = asset["name"]
setup_hash = asset["hash"]
url = asset ["url"]
installer_path = os.path.join(tempfile.gettempdir(), filename)
progress = QtWidgets.QProgressDialog("Downloading installer...", None, 0, 0)
progress.setWindowTitle("Updating")
progress.setWindowModality(Qt.WindowModality.ApplicationModal)
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.setAutoClose(False)
progress.setAutoReset(False)
progress.setRange(0, 100)
progress.setValue(0)
progress.show()
QtWidgets.QApplication.processEvents()
response = r.get(url, allow_redirects=True, stream=True)
total = int(response.headers.get("content-length", 0))
downloaded = 0
with open(installer_path, "wb") as f:
for chunk in response.iter_content(chunk_size=65536):
f.write(chunk)
downloaded += len(chunk)
if total > 0:
progress.setValue(int(downloaded * 100 / total))
QtWidgets.QApplication.processEvents()
if not os.path.exists(installer_path):
progress.close()
raise FileNotFoundError("Executable not found") raise FileNotFoundError("Executable not found")
subprocess.run([new_filename, "/SILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/RESTARTAPPLICATIONS", "/SP-"])
subprocess.Popen(["SoftwareManager.exe"])
time.sleep(0.5)
msg = QMessageBox() if _verify_hash(installer_path, setup_hash):
msg.setIcon(QMessageBox.Icon.Information) consoleLog(f"Sucessfully validated installer hash ({setup_hash})")
msg.setWindowTitle("New Version") else:
msg.setText("New version installed.") consoleLog("Error: Invalid Filehash, file may be corrupted")
msg.setInformativeText("Please remove the old exe.") sys.exit(0)
msg.setStandardButtons(QMessageBox.StandardButton.Ok)
progress.setLabelText("Installing update...")
progress.setValue(100)
QtWidgets.QApplication.processEvents()
subprocess.Popen([installer_path, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/SP-", "/CLOSEAPPLICATIONS"])
time.sleep(1)
sys.exit(0) sys.exit(0)
@@ -163,11 +191,13 @@ def windowCloseHelper():
class MainWindow(QtWidgets.QMainWindow, QWidget): class MainWindow(QtWidgets.QMainWindow, QWidget):
log_signal = Signal(str) # Thread-safe signal for logging log_signal = Signal(str) # Thread-safe signal for logging
search_finished_signal = Signal() # Thread-safe signal for search completion
def __init__(self): def __init__(self):
super().__init__() super().__init__()
MainWindow._instance = self MainWindow._instance = self
self.log_signal.connect(self._on_log_signal) self.log_signal.connect(self._on_log_signal)
self.search_finished_signal.connect(self._on_search_finished)
pixmap = QPixmap() pixmap = QPixmap()
image_data = base64.b64decode(logo_base64) image_data = base64.b64decode(logo_base64)
@@ -190,21 +220,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)
@@ -217,7 +245,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.searchbar.setPlaceholderText("Search for software...") self.searchbar.setPlaceholderText("Search for software...")
self.searchbar.setClearButtonEnabled(True) self.searchbar.setClearButtonEnabled(True)
self.searchbar.setMinimumHeight(30) self.searchbar.setMinimumHeight(30)
self.searchbar.returnPressed.connect(lambda: run_thread(threading.Thread(target=return_pressed, args=(self,)))) # Triggers data function thread on enter self.searchbar.returnPressed.connect(self._start_search)
self.dlbutton = QtWidgets.QPushButton("Download") self.dlbutton = QtWidgets.QPushButton("Download")
self.dlbutton.setCursor(Qt.CursorShape.PointingHandCursor) self.dlbutton.setCursor(Qt.CursorShape.PointingHandCursor)
@@ -300,7 +328,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
container = QWidget() container = QWidget()
containerLayout = QVBoxLayout() containerLayout = QVBoxLayout()
containerLayout.addWidget(self.searchbar)
search_row = QHBoxLayout()
search_row.addWidget(self.searchbar)
containerLayout.addLayout(search_row)
containerLayout.addWidget(state.tracker_list[state.tracker]) containerLayout.addWidget(state.tracker_list[state.tracker])
class DownloadModel(QAbstractTableModel): class DownloadModel(QAbstractTableModel):
@@ -425,21 +456,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)
@@ -450,11 +487,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):
@@ -468,11 +505,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)
@@ -663,6 +700,19 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.context_menu.addAction("Cancel Download", self.cancelDownloadAction) self.context_menu.addAction("Cancel Download", self.cancelDownloadAction)
self.context_menu.addAction("Delete File", self.deleteFileAction) self.context_menu.addAction("Delete File", self.deleteFileAction)
def _start_search(self):
self.searchbar.setEnabled(False)
def _search_thread():
try:
return_pressed(self)
finally:
self.search_finished_signal.emit()
run_thread(threading.Thread(target=_search_thread))
def _on_search_finished(self):
self.searchbar.setEnabled(True)
self.searchbar.setFocus()
@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)
+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")
+16 -11
View File
@@ -2,28 +2,33 @@ 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) response = requests.get(url, timeout=15)
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, 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, None
else: else:
+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()