Compare commits

...

23 Commits

Author SHA1 Message Date
KeksNino e95ad11672 add context menu to cancel downloads, open folder and copy magnet link 2026-02-04 18:56:03 +01:00
KeksNino 62c6afe350 disable library tab (may be added again in the future) 2026-02-03 17:41:59 +01:00
KeksNino 1aaf56a4dc fix: window transparency on linux/macos 2026-02-03 17:04:09 +01:00
Vxrtrauter 47b643d68b revert all workflow changes 2026-02-02 02:31:00 +01:00
Vxrtrauter de1c8577d6 test new workflow 2026-02-02 02:26:41 +01:00
Vxrtrauter c835b8375c test new workflow 2026-02-02 02:23:52 +01:00
Vxrtrauter e7291e8e99 revert workflow changes 2026-02-02 02:23:22 +01:00
Vxrtrauter a4efe9185a update workflows 2026-02-02 02:18:33 +01:00
Vxrtrauter 86b4f023ac update QApplication instance handling (switch to pyqtdarktheme-fork) 2026-02-02 00:50:32 +01:00
KeksNino 006da55aa0 fix: pause/resume button showing up twice 2026-02-01 20:28:24 +01:00
KeksNino 214a389ad3 fix: if statement to disable windows transparency 2026-02-01 20:14:49 +01:00
KeksNino d4005d7e86 fix: disable transparency on windows as it is broken 2026-01-31 19:17:30 +01:00
KeksNino 08a45cb422 feat: add qt window transparency 2026-01-31 18:46:40 +01:00
Vxrtrauter c49726fe42 feat: improve cursor styles for buttons and combo box in GUI 2026-01-31 07:51:00 +01:00
Vxrtrauter ecc117dedc feat: enhance GUI layout and styling for better usability 2026-01-31 07:36:40 +01:00
KeksNino c51fc9ea4a feat: remove toolbar, will be improved later 2026-01-31 05:49:30 +01:00
KeksNino 48ed11c248 remove leftover aria2 code 2026-01-31 05:18:45 +01:00
Vxrtrauter d672e3d85f Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-01-31 05:09:36 +01:00
Vxrtrauter 564c0f2ac5 refactor: enhance settings update logging and ensure console output on save 2026-01-31 05:09:34 +01:00
KeksNino 8a48c6c730 remove aria2 from github CI 2026-01-31 05:08:49 +01:00
Vxrtrauter 6432511639 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-01-31 05:04:28 +01:00
Vxrtrauter 054e1d0aa5 add error handling for update_settings 2026-01-31 05:04:26 +01:00
KeksNino 6f7a9e13f4 switch to uv for github workflow to improve workflow time 2026-01-31 05:00:47 +01:00
11 changed files with 233 additions and 123 deletions
+10 -26
View File
@@ -44,30 +44,22 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v6
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Install system dependencies (Linux) - name: Install uv
if: runner.os == 'Linux' uses: astral-sh/setup-uv@v7
run: | with:
sudo apt-get update version: "0.9.7"
sudo apt-get install -y \ enable-cache: true
libxcb-xinerama0 \
libxkbcommon-x11-0 \
libxcb-cursor0 \
libxcb-keysyms1 \
libxcb-render-util0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-shape0 \
libxcb-util1
- name: Install Python dependencies - name: Install Python dependencies
run: | run: |
python -m pip install --upgrade pip uv pip install -r requirements.txt
pip install -r requirements.txt uv pip install pyinstaller backports.tarfile pillow
pip install pyinstaller backports.tarfile pillow env:
UV_SYSTEM_PYTHON: 1
- name: Generate version - name: Generate version
id: version id: version
@@ -90,13 +82,6 @@ jobs:
} }
EOF EOF
- name: Download aria2c (Windows)
if: runner.os == 'Windows'
run: |
mkdir -p deps
C:\msys64\usr\bin\wget.exe https://github.com/aria2/aria2/releases/download/release-1.37.0/aria2-1.37.0-win-64bit-build1.zip
unzip -j aria2-1.37.0-win-64bit-build1.zip aria2-1.37.0-win-64bit-build1/aria2c.exe -d deps/
- name: Build executable with PyInstaller (Windows) - name: Build executable with PyInstaller (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
shell: bash shell: bash
@@ -105,7 +90,6 @@ jobs:
--add-data "build_info.json;." \ --add-data "build_info.json;." \
--add-data "build_info.json;core" \ --add-data "build_info.json;core" \
--add-data "build_info.json;core/interface" \ --add-data "build_info.json;core/interface" \
--add-data "deps/aria2c.exe;." \
--add-data "core/interface/assets;core/interface/assets" \ --add-data "core/interface/assets;core/interface/assets" \
--icon "core/interface/assets/logo.png" \ --icon "core/interface/assets/logo.png" \
--exclude-module PyQt6 \ --exclude-module PyQt6 \
+35 -46
View File
@@ -60,30 +60,20 @@ def settings_dialog(self):
autoresume_layout.addWidget(autoresume_checkbox) autoresume_layout.addWidget(autoresume_checkbox)
dialog.layout().addWidget(autoresume_container) dialog.layout().addWidget(autoresume_container)
#####################
# DOWNLOAD SETTINGS #
#####################
thread_box = QSpinBox()
thread_box.setMinimum(1)
thread_box.setMaximum(16)
thread_box.setValue(state.aria2_threads)
# container for tight space # transparent window checkbox
thread_container = QWidget() transparent_window_container = QWidget()
thread_layout = QHBoxLayout() transparent_window_layout = QHBoxLayout()
thread_layout.addWidget(QLabel("Threads:")) transparent_window_checkbox = QCheckBox()
thread_layout.addWidget(thread_box) transparent_window_container.setLayout(transparent_window_layout)
thread_container.setLayout(thread_layout) transparent_window_layout.addWidget(QLabel("Window Transparency (requires restart) (Linux/MacOS only): "))
thread_container.setMaximumHeight(80)
# Dimensions transparent_window_layout.addStretch()
thread_box.setFixedWidth(90) transparent_window_checkbox.setChecked(state.window_transparency)
thread_box.setFixedHeight(30) transparent_window_checkbox.toggled.connect(lambda checked: setattr(state, 'window_transparency', checked))
transparent_window_layout.addWidget(transparent_window_checkbox)
dialog.layout().addWidget(thread_container) dialog.layout().addWidget(transparent_window_container)
################## ##################
# SERVER SETTING # # SERVER SETTING #
@@ -124,6 +114,30 @@ def settings_dialog(self):
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)
###############
# IMAGE PATH #
###############
image_path_container = QWidget()
image_path_layout = QHBoxLayout()
image_path = QLineEdit()
image_path_layout.addWidget(QLabel("Image Path (requires restart):"))
image_path_layout.addWidget(image_path)
image_path_container.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
image_path_container.setLayout(image_path_layout)
image_path.setText(state.image_path)
dialog.layout().addWidget(image_path_container)
def browse_image_path():
file_path = QFileDialog.getOpenFileName(dialog, "Select Image File", state.image_path, "Image Files (*.png *.jpg)")[0]
if file_path:
image_path.setText(file_path)
browse_button = QPushButton("📁")
image_path_layout.addWidget(browse_button)
browse_button.clicked.connect(browse_image_path)
################## ##################
# SPEED LIMITING # # SPEED LIMITING #
################## ##################
@@ -192,31 +206,6 @@ def settings_dialog(self):
max_downloads.setFixedHeight(30) max_downloads.setFixedHeight(30)
dialog.layout().addWidget(max_downloads_container) dialog.layout().addWidget(max_downloads_container)
###############
# IMAGE PATH #
###############
image_path_container = QWidget()
image_path_layout = QHBoxLayout()
image_path = QLineEdit()
image_path_layout.addWidget(QLabel("Image Path (requires restart):"))
image_path_layout.addWidget(image_path)
image_path_container.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
image_path_container.setLayout(image_path_layout)
image_path.setText(state.image_path)
dialog.layout().addWidget(image_path_container)
def browse_image_path():
file_path = QFileDialog.getOpenFileName(dialog, "Select Image File", state.image_path, "Image Files (*.png *.jpg)")[0]
if file_path:
image_path.setText(file_path)
browse_button = QPushButton("📁")
image_path_layout.addWidget(browse_button)
browse_button.clicked.connect(browse_image_path)
############### ###############
# SAVE/CANCEL # # SAVE/CANCEL #
############### ###############
+127 -30
View File
@@ -1,12 +1,11 @@
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
QTableView, QTableView,
QWidget, QWidget,
QVBoxLayout, QVBoxLayout,
QListWidget, QListWidget,
QToolBar,
QLabel, QLabel,
QHBoxLayout, QHBoxLayout,
QComboBox, QComboBox,
@@ -16,13 +15,10 @@ from PySide6.QtWidgets import (
QMessageBox, QMessageBox,
QTableWidget, QTableWidget,
QTextEdit, QTextEdit,
QStyle,
QStyleOptionButton,
QStyledItemDelegate, QStyledItemDelegate,
QApplication,
) )
from PySide6.QtGui import QIcon, QAction, QCloseEvent, QImage, QPixmap from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent
import darkdetect import darkdetect
import threading import threading
import platform import platform
@@ -33,7 +29,7 @@ import libtorrent as lt
import time import time
import sys import sys
import json import json
from core.utils.general.logs import consoleLog from core.utils.general.logs import consoleLog, remove_download_log
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
@@ -138,6 +134,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.searchbar.returnPressed.connect(lambda: run_thread(threading.Thread(target=return_pressed, args=(self,)))) # Triggers data function thread on enter self.searchbar.returnPressed.connect(lambda: run_thread(threading.Thread(target=return_pressed, args=(self,)))) # Triggers data function thread on enter
self.dlbutton = QtWidgets.QPushButton("Download") self.dlbutton = QtWidgets.QPushButton("Download")
self.dlbutton.setCursor(Qt.PointingHandCursor)
self.libraryList = QListWidget() self.libraryList = QListWidget()
self.emptyResults = QLabel("No Results") self.emptyResults = QLabel("No Results")
@@ -155,12 +152,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
# Table Widget for Item List # Table Widget for Item List
self.qtablewidget = QTableWidget() self.qtablewidget = QTableWidget()
self.qtablewidget.setColumnCount(2) self.qtablewidget.setColumnCount(2)
self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.qtablewidget.verticalHeader().setVisible(False) self.qtablewidget.verticalHeader().setVisible(False)
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"]) self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"])
header = self.qtablewidget.horizontalHeader() header = self.qtablewidget.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.Fixed) header.setSectionResizeMode(1, QHeaderView.Fixed)
@@ -207,8 +206,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
status = magnetdl.status() status = magnetdl.status()
if col == 0: if col == 0:
is_paused = magnetdl.status().paused pass
return "▶︎" if is_paused else "⏸︎"
elif col == 1: elif col == 1:
return status.name if status.has_metadata else "Fetching metadata..." return status.name if status.has_metadata else "Fetching metadata..."
elif col == 2: elif col == 2:
@@ -275,7 +273,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
status = magnetdl.status() status = magnetdl.status()
if status.state == lt.torrent_status.seeding: if status.state == lt.torrent_status.seeding:
if state.download_path is not None and os.path.exists(state.download_path): if state.download_path and os.path.exists(state.download_path):
if platform.system() == "Windows": if platform.system() == "Windows":
os.startfile(os.path.normpath(state.download_path)) os.startfile(os.path.normpath(state.download_path))
elif platform.system() == "Linux": elif platform.system() == "Linux":
@@ -299,8 +297,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
def setEditorData(self, editor, index): def setEditorData(self, editor, index):
button = editor.findChild(QtWidgets.QPushButton) button = editor.findChild(QtWidgets.QPushButton)
magnet_link = list(state.active_downloads.keys())[index.row()] magnet_link = list(state.active_downloads.keys())[index.row()]
magnetdl = state.active_downloads[magnet_link] magnetdl = state.active_downloads[magnet_link]
@@ -312,8 +308,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
else: else:
button.setText("▶︎" if status.paused else "⏸︎") button.setText("▶︎" if status.paused else "⏸︎")
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
magnet_link = list(state.active_downloads.keys())[index.row()] magnet_link = list(state.active_downloads.keys())[index.row()]
magnetdl = state.active_downloads[magnet_link] magnetdl = state.active_downloads[magnet_link]
@@ -334,7 +328,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
layout.addStretch() layout.addStretch()
widget.setLayout(layout) widget.setLayout(layout)
return widget return widget
def editorEvent(self, event, model, option, index): def editorEvent(self, event, model, option, index):
if index.column() == 0 and event.type() == QEvent.Type.MouseButtonPress: if index.column() == 0 and event.type() == QEvent.Type.MouseButtonPress:
self.clicked.emit(index.row()) self.clicked.emit(index.row())
@@ -366,13 +359,24 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
# Tabs # Tabs
self.tabs = QTabWidget() self.tabs = QTabWidget()
self.tabs = QTabWidget()
self.tabs.setStyleSheet("""
QTabBar::tab {
font-size: 11px;
padding: 2px 8px;
height: 25px;
}
QTabWidget::pane {
margin-top: -20px;
}
""")
self.horizontal_layout = QHBoxLayout() self.horizontal_layout = QHBoxLayout()
self.horizontal_layout.addWidget(self.emptyResults, stretch=3) self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
self.horizontal_layout.addWidget(self.qtablewidget) self.horizontal_layout.addWidget(self.qtablewidget)
self.tab1 = create_tab("Search", self.searchbar, self.qtablewidget, self.tabs, self.dlbutton, self.horizontal_layout) self.tab1 = create_tab("Search", self.searchbar, self.qtablewidget, self.tabs, self.dlbutton, self.horizontal_layout)
self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None) # self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None)
self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None) self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None)
if state.image_path is not None and os.path.exists(state.image_path): if state.image_path is not None and os.path.exists(state.image_path):
@@ -389,33 +393,58 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
y = self.height() - self.overlay_label.height() - offset_y y = self.height() - self.overlay_label.height() - offset_y
self.overlay_label.move(x, y) self.overlay_label.move(x, y)
# temporarily disabled # temporarily disabled
# state.image_changed.connect(self.update_image_overlay) # state.image_changed.connect(self.update_image_overlay)
containerLayout.addWidget(self.tabs)
self.corner_widget = QWidget()
self.corner_layout = QHBoxLayout(self.corner_widget)
self.corner_layout.setContentsMargins(0, 0, 0, 0)
containerLayout.setAlignment(Qt.AlignmentFlag.AlignBottom)
toolbar = QToolBar("Main Toolbar")
self.addToolBar(toolbar)
toolbar.setLayoutDirection(Qt.RightToLeft)
self.tracker_list = QComboBox() self.tracker_list = QComboBox()
self.tracker_list.addItems(["rutracker", "uztracker", "m0nkrus"]) self.tracker_list.addItems(["rutracker", "uztracker", "m0nkrus"])
self.tracker_list.setCursor(Qt.CursorShape.PointingHandCursor)
self.tracker_list.activated.connect(self.set_tracker) self.tracker_list.activated.connect(self.set_tracker)
self.corner_layout.addWidget(self.tracker_list)
self.settings_btn = QtWidgets.QToolButton()
self.settings_btn.setIconSize(QSize(21, 21))
self.settings_btn.setStyleSheet("background: transparent;")
if darkdetect.isDark(): if darkdetect.isDark():
settings_action = QAction(QIcon(get_asset_path("settings_white.png")), "Settings", self) self.settings_btn.setIcon(QIcon(get_asset_path("settings_white.png")))
else: else:
settings_action = QAction(QIcon(get_asset_path("settings_black.png")), "Settings", self) self.settings_btn.setIcon(QIcon(get_asset_path("settings_black.png")))
self.settings_btn.setToolTip("Settings")
self.settings_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.settings_btn.clicked.connect(lambda: settings_dialog(self))
self.corner_layout.addWidget(self.settings_btn)
settings_action.triggered.connect(lambda: settings_dialog(self)) self.tab_wrapper = QWidget()
toolbar.addAction(settings_action) self.tab_layout = QVBoxLayout()
toolbar.addWidget(self.tracker_list) self.tab_layout.setContentsMargins(0, 0, 0, 0)
self.tab_layout.setSpacing(0)
toolbar.setMovable(False) self.tab_bar_layout = QHBoxLayout()
self.tab_bar_layout.setContentsMargins(0, 0, 0, 0)
self.tab_bar_layout.setSpacing(0)
self.tab_bar_layout.addWidget(self.tabs.tabBar(), stretch=0)
self.tab_bar_layout.addStretch()
self.tab_bar_layout.addWidget(self.corner_widget, stretch=0)
self.tab_layout.addLayout(self.tab_bar_layout)
self.tab_layout.addWidget(self.tabs)
self.tab_wrapper.setLayout(self.tab_layout)
containerLayout.addWidget(self.tab_wrapper)
containerLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
# self.progressbar.setValue(0)
self.consoleLog.setReadOnly(True) self.consoleLog.setReadOnly(True)
self.consoleLog.setFixedHeight(150) self.consoleLog.setFixedHeight(150)
containerLayout.addWidget(self.consoleLog) containerLayout.addWidget(self.consoleLog)
@@ -428,6 +457,11 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.active_timer.timeout.connect(self.show_empty_downloads) self.active_timer.timeout.connect(self.show_empty_downloads)
self.active_timer.start(500) self.active_timer.start(500)
self.context_menu = QtWidgets.QMenu(self)
self.context_menu.addAction("Open Containing Folder", self.openFolderAction)
self.context_menu.addAction("Copy Magnet URI", self.copyMagnetURIAction)
self.context_menu.addAction("Cancel Download", self.cancelDownload)
@staticmethod @staticmethod
def add_log(text): def add_log(text):
if MainWindow._instance: if MainWindow._instance:
@@ -484,3 +518,66 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
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))
def contextMenuEvent(self, event: QContextMenuEvent):
if self.downloadList.underMouse():
pos = self.downloadList.viewport().mapFromGlobal(event.globalPos())
index = self.downloadList.indexAt(pos)
if index.isValid():
row = index.row()
self._context_menu_row = row
self.context_menu.exec(event.globalPos())
else:
super().contextMenuEvent(event)
def openFolderAction(self):
if not hasattr(self, '_context_menu_row'):
return
row = self._context_menu_row
if row < 0 or row >= len(state.active_downloads):
return
magnet_link = list(state.active_downloads.keys())[row]
magnetdl = state.active_downloads[magnet_link]
download_path = magnetdl.save_path()
if download_path and os.path.exists(download_path):
if platform.system() == "Windows":
os.startfile(os.path.normpath(download_path))
elif platform.system() == "Linux":
subprocess.Popen(["xdg-open", download_path])
elif platform.system() == "Darwin":
subprocess.Popen(["open", download_path])
def copyMagnetURIAction(self):
if not hasattr(self, '_context_menu_row'):
return
row = self._context_menu_row
if row < 0 or row >= len(state.active_downloads):
return
magnet_link = list(state.active_downloads.keys())[row]
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(magnet_link)
def cancelDownload(self):
if not hasattr(self, '_context_menu_row'):
return
row = self._context_menu_row
if row < 0 or row >= len(state.active_downloads):
return
magnet_link = list(state.active_downloads.keys())[row]
magnetdl = state.active_downloads[magnet_link]
confirm = QMessageBox.question(self, "Cancel Download", f"Are you sure you want to cancel the download of '{magnetdl.status().name}'?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if confirm == QMessageBox.StandardButton.Yes:
del state.active_downloads[magnet_link]
remove_download_log(magnet_link)
consoleLog(f"Cancelled download: {magnetdl.status().name}", True)
+1 -1
View File
@@ -12,6 +12,6 @@ def create_tab(title, searchbar, software_list, tabs, dlbutton, layout2):
layout.addWidget(software_list) layout.addWidget(software_list)
if dlbutton: if dlbutton:
layout.addWidget(dlbutton) layout.addWidget(dlbutton)
tab.setLayout(layout) tab.setLayout(layout)
tabs.addTab(tab, title) tabs.addTab(tab, title)
return tab return tab
+4 -1
View File
@@ -89,6 +89,9 @@ def dl_status_loop():
def update_settings(): def update_settings():
if state.dl_session is None:
return
settings = { settings = {
"upload_rate_limit": state.up_speed_limit, "upload_rate_limit": state.up_speed_limit,
"download_rate_limit": state.down_speed_limit, "download_rate_limit": state.down_speed_limit,
@@ -97,4 +100,4 @@ def update_settings():
} }
state.dl_session.apply_settings(settings) state.dl_session.apply_settings(settings)
consoleLog("Updated Settings")
+15 -3
View File
@@ -6,8 +6,19 @@ from core.utils.data.state import state
def create_config(): def create_config():
config = configparser.ConfigParser() config = configparser.ConfigParser()
config["General"] = {"debug": True, "api_url": f"{state.api_url}", "download_path": f"{state.download_path}", f"download_speed_limit": f"{state.down_speed_limit}", f"upload_speed_limit": f"{state.up_speed_limit}", config["General"] = {
"ignore_updates": f"{state.ignore_updates}", "image_path": f"{state.image_path}", "autoresume": f"{state.autoresume}", "max_connections": f"{state.max_connections}", "max_downloads": f"{state.max_downloads}"} "debug": True,
"api_url": f"{state.api_url}",
"download_path": f"{state.download_path}",
"download_speed_limit": f"{state.down_speed_limit}",
"upload_speed_limit": f"{state.up_speed_limit}",
"ignore_updates": f"{state.ignore_updates}",
"image_path": f"{state.image_path}",
"autoresume": f"{state.autoresume}",
"max_connections": f"{state.max_connections}",
"max_downloads": f"{state.max_downloads}",
"window_transparency": f"{state.window_transparency}",
}
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"))
@@ -48,5 +59,6 @@ def read_config():
state.autoresume = config.getboolean("General", "autoresume", fallback=state.autoresume) state.autoresume = config.getboolean("General", "autoresume", fallback=state.autoresume)
state.max_connections = config.getint("General", "max_connections", fallback=state.max_connections) state.max_connections = config.getint("General", "max_connections", fallback=state.max_connections)
state.max_downloads = config.getint("General", "max_downloads", fallback=state.max_downloads) state.max_downloads = config.getint("General", "max_downloads", fallback=state.max_downloads)
state.window_transparency = config.getboolean("General", "window_transparency", fallback=state.window_transparency)
create_config() create_config()
+3
View File
@@ -1,5 +1,6 @@
from core.network.libtorrent_int import update_settings from core.network.libtorrent_int import update_settings
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from .config import create_config from .config import create_config
@@ -21,6 +22,8 @@ def save_settings(close=lambda: None, apiurl=None, download_path=None, down_spee
if max_downloads is not None: if max_downloads is not None:
state.max_downloads = max_downloads state.max_downloads = max_downloads
update_settings() update_settings()
consoleLog("Saved Settings")
create_config() create_config()
close() close()
+1 -4
View File
@@ -23,13 +23,10 @@ class AppState(QObject):
self.down_speed_limit: int = 0 self.down_speed_limit: int = 0
self.max_connections: int = 200 self.max_connections: int = 200
self.max_downloads: int = 10 self.max_downloads: int = 10
self.aria2process: Optional[Any] = None
self.aria2: Any = None
self.aria2p: Any = None
self.aria2_threads: int = 4
self.settings_path: str = None self.settings_path: str = None
self.dl_session: Any = None self.dl_session: Any = None
self.active_downloads: List[str] = {} self.active_downloads: List[str] = {}
self.window_transparency: bool = False
@property @property
def image_path(self) -> str: def image_path(self) -> str:
+25 -1
View File
@@ -41,6 +41,30 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
return download_list return download_list
def remove_download_log(magnet_uri) -> 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 = []
magnet_link = (magnet_uri or "").strip()
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 {magnet_link} from 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: def update_download_completed(magnet_uri, completed) -> DownloadList:
downloads_file = os.path.join(state.settings_path, "downloads.json") downloads_file = os.path.join(state.settings_path, "downloads.json")
@@ -176,4 +200,4 @@ def consoleLog(text, printAnyways = False):
pass pass
if state.debug or printAnyways: if state.debug or printAnyways:
print(f"[{current_time}] {text}") print(f"[{current_time}] {text}")
+9 -8
View File
@@ -8,29 +8,30 @@ from core.utils.general.wrappers import run_thread
from core.utils.general.loghandler import split_data, check_completed 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
from PySide6.QtCore import Qt
import qdarktheme import qdarktheme
import darkdetect
import threading import threading
import platform
import signal import signal
import argparse import argparse
import sys import sys
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true") parser.add_argument("--debug", action="store_true")
args = parser.parse_args() args = parser.parse_args()
def run_gui(): def run_gui():
app = QtWidgets.QApplication([]) app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
if darkdetect.isDark: qdarktheme.setup_theme("auto")
app.setStyleSheet(qdarktheme.load_stylesheet("dark"))
else:
app.setStyleSheet(qdarktheme.load_stylesheet("light"))
widget = MainWindow() widget = MainWindow()
if state.window_transparency and platform.system() != "Windows":
widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
qdarktheme.setup_theme("auto", custom_colors={"background": "#00000000"})
from core.utils.general.logs import set_main_window from core.utils.general.logs import set_main_window
set_main_window(widget) set_main_window(widget)
widget.show() widget.show()
sys.exit(app.exec()) sys.exit(app.exec())
+3 -3
View File
@@ -1,11 +1,11 @@
requests==2.32.2 requests==2.32.2
PySide6==6.10.1 PySide6==6.10.1
beautifulsoup4==4.13.5 beautifulsoup4==4.13.5
darkdetect==0.8.0 darkdetect==0.7.1
pyinstaller==6.15.0 pyinstaller==6.15.0
pyqtdarktheme==0.1.7 PyQtDarkTheme-fork==2.3.4
aiohttp==3.13.0 aiohttp==3.13.0
plyer==2.1.0 plyer==2.1.0
psutil==7.1.0 psutil==7.1.0
libtorrent==2.0.11 libtorrent==2.0.11
libtorrent-windows-dll==0.0.3 libtorrent-windows-dll==0.0.3