Compare commits

...

27 Commits

Author SHA1 Message Date
Vxrtrauter 3f39e67933 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-02-05 17:28:50 +01:00
Vxrtrauter d43d6e57cc add more interface stuff 2026-02-05 00:36:12 +01:00
KeksNino 28cffe59f5 feat: add context menu option to delete file 2026-02-04 21:34:03 +01:00
Vxrtrauter f1726f4e9a Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-02-04 20:44:51 +01:00
KeksNino b7ca895aeb fix: open folder because state.download_path is None in gui.py 2026-02-04 19:35:35 +01:00
KeksNino e95ad11672 add context menu to cancel downloads, open folder and copy magnet link 2026-02-04 18:56:03 +01:00
Vxrtrauter f2a79e5eed test more stuff 2026-02-04 04:04:31 +01:00
Vxrtrauter 990135450f test network stuff 2026-02-04 03:04:24 +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
14 changed files with 366 additions and 141 deletions
-8
View File
@@ -82,13 +82,6 @@ jobs:
}
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)
if: runner.os == 'Windows'
shell: bash
@@ -97,7 +90,6 @@ jobs:
--add-data "build_info.json;." \
--add-data "build_info.json;core" \
--add-data "build_info.json;core/interface" \
--add-data "deps/aria2c.exe;." \
--add-data "core/interface/assets;core/interface/assets" \
--icon "core/interface/assets/logo.png" \
--exclude-module PyQt6 \
+35 -46
View File
@@ -60,30 +60,20 @@ def settings_dialog(self):
autoresume_layout.addWidget(autoresume_checkbox)
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
thread_container = QWidget()
thread_layout = QHBoxLayout()
# transparent window checkbox
transparent_window_container = QWidget()
transparent_window_layout = QHBoxLayout()
thread_layout.addWidget(QLabel("Threads:"))
thread_layout.addWidget(thread_box)
thread_container.setLayout(thread_layout)
thread_container.setMaximumHeight(80)
transparent_window_checkbox = QCheckBox()
transparent_window_container.setLayout(transparent_window_layout)
transparent_window_layout.addWidget(QLabel("Window Transparency (requires restart) (Linux/MacOS only): "))
# Dimensions
thread_box.setFixedWidth(90)
thread_box.setFixedHeight(30)
dialog.layout().addWidget(thread_container)
transparent_window_layout.addStretch()
transparent_window_checkbox.setChecked(state.window_transparency)
transparent_window_checkbox.toggled.connect(lambda checked: setattr(state, 'window_transparency', checked))
transparent_window_layout.addWidget(transparent_window_checkbox)
dialog.layout().addWidget(transparent_window_container)
##################
# SERVER SETTING #
@@ -124,6 +114,30 @@ def settings_dialog(self):
download_path_layout.addWidget(browse_button)
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 #
##################
@@ -192,31 +206,6 @@ def settings_dialog(self):
max_downloads.setFixedHeight(30)
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 #
###############
+193 -61
View File
@@ -1,12 +1,11 @@
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 (
QLineEdit,
QTableView,
QWidget,
QVBoxLayout,
QListWidget,
QToolBar,
QLabel,
QHBoxLayout,
QComboBox,
@@ -16,13 +15,10 @@ from PySide6.QtWidgets import (
QMessageBox,
QTableWidget,
QTextEdit,
QStyle,
QStyleOptionButton,
QStyledItemDelegate,
QApplication,
)
from PySide6.QtGui import QIcon, QAction, QCloseEvent, QImage, QPixmap
from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent
import darkdetect
import threading
import platform
@@ -33,7 +29,7 @@ import libtorrent as lt
import time
import sys
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.data.state import state
from core.utils.network.download import download_selected
@@ -78,7 +74,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
def get_asset_path(filename):
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(__file__)
@@ -138,39 +134,42 @@ 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.dlbutton = QtWidgets.QPushButton("Download")
self.dlbutton.setCursor(Qt.CursorShape.PointingHandCursor)
self.libraryList = QListWidget()
self.emptyResults = QLabel("No Results")
self.emptyResults.setAlignment(Qt.AlignCenter)
self.emptyResults.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.emptyResults.hide()
self.download_model = None
self.downloadList = QTableView()
self.emptyLibrary = QLabel("No items in library.")
self.emptyDownload = QLabel("No items in downloads.")
self.emptyDownload.setAlignment(Qt.AlignCenter)
self.emptyDownload.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.consoleLog = QTextEdit()
self.progressbar = QProgressBar()
# Table Widget for Item List
self.qtablewidget = QTableWidget()
self.qtablewidget.setColumnCount(2)
self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.qtablewidget.verticalHeader().setVisible(False)
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"])
header = self.qtablewidget.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.Fixed)
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
header.setStretchLastSection(False)
self.qtablewidget.setAttribute(Qt.WA_TranslucentBackground)
self.qtablewidget.viewport().setAttribute(Qt.WA_TranslucentBackground)
self.qtablewidget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.qtablewidget.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
header = self.qtablewidget.horizontalHeader()
header.setSectionResizeMode(1, QHeaderView.Fixed)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
header.resizeSection(1, 500)
container = QWidget()
@@ -189,32 +188,31 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
def columnCount(self, parent=QModelIndex()):
return len(self.headers)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.headers[section]
return None
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
col = index.column()
if index.row() >= len(state.active_downloads) or index.row() < 0:
return None
magnet_link = list(state.active_downloads.keys())[index.row()]
magnet_link = list(state.active_downloads.keys())[index.row()]
magnetdl = state.active_downloads[magnet_link]
status = magnetdl.status()
if col == 0:
is_paused = magnetdl.status().paused
return "▶︎" if is_paused else "⏸︎"
pass
elif col == 1:
return status.name if status.has_metadata else "Fetching metadata..."
elif col == 2:
if status.state == lt.torrent_status.downloading:
if status.state == lt.torrent_status.downloading:
return "Downloading"
elif status.state == lt.torrent_status.seeding:
elif status.state == lt.torrent_status.seeding:
return "Seeding"
elif status.paused:
return "Paused"
@@ -258,8 +256,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
else:
return "" if status.paused else "Stalled"
if role == Qt.UserRole and index.column() == 0:
magnet_link = list(state.active_downloads.keys())[index.row()]
if role == Qt.UserRole and index.column() == 0:
magnet_link = list(state.active_downloads.keys())[index.row()]
magnetdl = state.active_downloads[magnet_link]
return magnetdl.status().paused
@@ -270,18 +268,19 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
if row >= len(state.active_downloads) or row < 0:
return
magnet_link = list(state.active_downloads.keys())[row]
magnet_link = list(state.active_downloads.keys())[row]
magnetdl = state.active_downloads[magnet_link]
status = magnetdl.status()
if status.state == lt.torrent_status.seeding:
if state.download_path is not None and os.path.exists(state.download_path):
if status.state == lt.torrent_status.seeding:
save_path = magnetdl.save_path()
if save_path and os.path.exists(save_path):
if platform.system() == "Windows":
os.startfile(os.path.normpath(state.download_path))
os.startfile(os.path.normpath(save_path))
elif platform.system() == "Linux":
subprocess.Popen(["xdg-open", state.download_path])
subprocess.Popen(["xdg-open", save_path])
elif platform.system() == "Darwin":
subprocess.Popen(["open", state.download_path])
subprocess.Popen(["open", save_path])
return
if status.paused:
@@ -299,23 +298,19 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
def setEditorData(self, editor, index):
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]
status = magnetdl.status()
if button:
if status.state == lt.torrent_status.seeding:
if status.state == lt.torrent_status.seeding:
button.setText("📁")
else:
button.setText("▶︎" if status.paused else "⏸︎")
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]
status = magnetdl.status()
@@ -323,7 +318,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
widget.setStyleSheet("border: none;")
layout = QHBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 0)
if status.state == lt.torrent_status.seeding:
if status.state == lt.torrent_status.seeding:
btnPause = QtWidgets.QPushButton("📁")
else:
btnPause = QtWidgets.QPushButton("▶︎" if status.paused else "⏸︎")
@@ -334,7 +329,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
layout.addStretch()
widget.setLayout(layout)
return widget
def editorEvent(self, event, model, option, index):
if index.column() == 0 and event.type() == QEvent.Type.MouseButtonPress:
self.clicked.emit(index.row())
@@ -344,10 +338,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.download_model = DownloadModel()
self.downloadList.setModel(self.download_model)
self.downloadList.horizontalHeader().setStretchLastSection(False)
self.downloadList.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
self.downloadList.setSelectionBehavior(QTableView.SelectRows)
self.downloadList.setAttribute(Qt.WA_TranslucentBackground)
self.downloadList.viewport().setAttribute(Qt.WA_TranslucentBackground)
self.downloadList.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
self.downloadList.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows)
self.downloadList.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.downloadList.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
download_model = self.download_model
@@ -366,13 +360,24 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
# Tabs
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.addWidget(self.emptyResults, stretch=3)
self.horizontal_layout.addWidget(self.qtablewidget)
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)
if state.image_path is not None and os.path.exists(state.image_path):
@@ -389,33 +394,58 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
y = self.height() - self.overlay_label.height() - offset_y
self.overlay_label.move(x, y)
# temporarily disabled
# 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.addItems(["rutracker", "uztracker", "m0nkrus"])
self.tracker_list.setCursor(Qt.CursorShape.PointingHandCursor)
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():
settings_action = QAction(QIcon(get_asset_path("settings_white.png")), "Settings", self)
self.settings_btn.setIcon(QIcon(get_asset_path("settings_white.png")))
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))
toolbar.addAction(settings_action)
toolbar.addWidget(self.tracker_list)
self.tab_wrapper = QWidget()
self.tab_layout = QVBoxLayout()
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.setFixedHeight(150)
containerLayout.addWidget(self.consoleLog)
@@ -428,6 +458,12 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.active_timer.timeout.connect(self.show_empty_downloads)
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.cancelDownloadAction)
self.context_menu.addAction("Delete File", self.deleteFileAction)
@staticmethod
def add_log(text):
if MainWindow._instance:
@@ -484,3 +520,99 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
super().resizeEvent(event)
table_width = self.qtablewidget.viewport().width()
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 cancelDownloadAction(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)
def deleteFileAction(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]
status = magnetdl.status()
save_path = status.save_path
torrent_name = status.name
download_path = os.path.join(save_path, torrent_name)
confirm = QMessageBox.question(self, "Delete Files", f"Are you sure you want to delete the downloaded files of '{magnetdl.status().name}'? This action cannot be undone.", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if confirm == QMessageBox.StandardButton.Yes:
if download_path and os.path.exists(download_path):
try:
if os.path.isfile(download_path):
os.remove(download_path)
del state.active_downloads[magnet_link]
remove_download_log(magnet_link)
consoleLog(f"Deleted files for: {magnetdl.status().name}", True)
else:
import shutil
shutil.rmtree(download_path)
del state.active_downloads[magnet_link]
remove_download_log(magnet_link)
consoleLog(f"Deleted files for: {magnetdl.status().name}", True)
except Exception as e:
consoleLog(f"Error deleting files: {e}", True)
+1 -1
View File
@@ -12,6 +12,6 @@ def create_tab(title, searchbar, software_list, tabs, dlbutton, layout2):
layout.addWidget(software_list)
if dlbutton:
layout.addWidget(dlbutton)
tab.setLayout(layout)
tab.setLayout(layout)
tabs.addTab(tab, title)
return tab
+40
View File
@@ -0,0 +1,40 @@
from core.utils.general.logs import consoleLog
import psutil
addrs = psutil.net_if_addrs()
stats = psutil.net_if_stats()
def get_net_interfaces():
for interface in addrs.keys():
consoleLog(f"Found Interface: {interface}")
return addrs.keys()
def get_active_interfaces():
active = []
for interface, addr_list in addrs.items():
up = stats[interface].isup
for addr in addr_list:
if addr.family == 2: # ipv4
ipv4 = addr.address
if not ipv4.startswith("127.") and not ipv4.startswith("169.254") and up == True:
active.append(interface)
consoleLog(f"Found Active: {interface}")
return active
def list_interfaces() -> None:
for interface, addr_list in addrs.items():
up = stats[interface].isup
for addr in addr_list:
if addr.family == 2: # ipv4
ipv4 = addr.address
if not ipv4.startswith("127.") and not ipv4.startswith("169.254") and up == True:
status = "ACTIVE"
else:
status = "INACTIVE"
consoleLog(f"Found Interface: {interface} [{status}]")
+1 -1
View File
@@ -100,4 +100,4 @@ def update_settings():
}
state.dl_session.apply_settings(settings)
consoleLog("Updated Settings")
+1 -4
View File
@@ -6,7 +6,4 @@ def add_magnet(uri):
add_download(uri)
consoleLog("Magnet URI added to LibTorrent")
else:
consoleLog(f"Invalid Magnet Link: {uri}")
consoleLog(f"Invalid Magnet Link: {uri}")
+15 -3
View File
@@ -6,8 +6,19 @@ from core.utils.data.state import state
def create_config():
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}",
"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}"}
config["General"] = {
"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":
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.max_connections = config.getint("General", "max_connections", fallback=state.max_connections)
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.utils.data.state import state
from core.utils.general.logs import consoleLog
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:
state.max_downloads = max_downloads
update_settings()
consoleLog("Saved Settings")
create_config()
close()
+2 -5
View File
@@ -23,13 +23,10 @@ class AppState(QObject):
self.down_speed_limit: int = 0
self.max_connections: int = 200
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 = ""
self.dl_session: Any = None
self.active_downloads: List[str] = {}
self.window_transparency: bool = False
@property
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
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:
downloads_file = os.path.join(state.settings_path, "downloads.json")
@@ -176,4 +200,4 @@ def consoleLog(text, printAnyways = False):
pass
if state.debug or printAnyways:
print(f"[{current_time}] {text}")
print(f"[{current_time}] {text}")
+12 -8
View File
@@ -6,31 +6,33 @@ from core.utils.general.logs import get_download_logs
from core.utils.general.shutdown import closehelper, shutdown_event
from core.utils.general.wrappers import run_thread
from core.utils.general.loghandler import split_data, check_completed
from core.network.interface import list_interfaces
from core.utils.config.config import read_config
from PySide6 import QtWidgets
from PySide6.QtCore import Qt
import qdarktheme
import darkdetect
import threading
import platform
import signal
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
def run_gui():
app = QtWidgets.QApplication([])
if darkdetect.isDark:
app.setStyleSheet(qdarktheme.load_stylesheet("dark"))
else:
app.setStyleSheet(qdarktheme.load_stylesheet("light"))
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
qdarktheme.setup_theme("auto")
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
set_main_window(widget)
widget.show()
sys.exit(app.exec())
@@ -50,6 +52,8 @@ if __name__ == "__main__":
consoleLog("Started update_log thread")
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
consoleLog("Started check_completed thread")
consoleLog("Fetching Network Interfaces...")
list_interfaces()
consoleLog("Launching GUI")
run_gui()
+3 -3
View File
@@ -1,11 +1,11 @@
requests==2.32.2
PySide6==6.10.1
beautifulsoup4==4.13.5
darkdetect==0.8.0
darkdetect==0.7.1
pyinstaller==6.15.0
pyqtdarktheme==0.1.7
PyQtDarkTheme-fork==2.3.4
aiohttp==3.13.0
plyer==2.1.0
psutil==7.1.0
libtorrent==2.0.11
libtorrent-windows-dll==0.0.3
libtorrent-windows-dll==0.0.3
+35
View File
@@ -0,0 +1,35 @@
import psutil
addrs = psutil.net_if_addrs()
stats = psutil.net_if_stats()
def get_active_interfaces():
active = []
for interface, addr_list in addrs.items():
up = stats[interface].isup
for addr in addr_list:
if addr.family == 2: # ipv4
ipv4 = addr.address
if not ipv4.startswith("127.") and not ipv4.startswith("169.254") and up == True:
print(f'"{interface}": True')
active.append(interface)
print(active)
return active
get_active_interfaces()
# print(addrs)
# print()
# sigmer = list()
# for interface in stats.keys():
# print(f"{interface}: UP = {stats[interface].isup}")
# sigmer.append(f"{interface}: {stats[interface].isup}")
# print(sigmer)