Compare commits

...

8 Commits

Author SHA1 Message Date
shayaa 8132d789ef Add activity section to README
Added activity section with analytics image to README.
2026-02-05 17:35:38 +01:00
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
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
7 changed files with 160 additions and 47 deletions
+3
View File
@@ -27,6 +27,9 @@ SoftwareManager is a Python-based GUI/TUI tool that simplifies downloading and m
- If you want to host your own server, clone the server branch of this repo, enter your cookie for rutracker.org and run server.py. Detailed Instructions on how to get your rutracker cookie are in the README of its branch.
- This tool is for educational purposes only. Use it responsibly.
## Activity
![Alt](https://repobeats.axiom.co/api/embed/3a61ea02ffdeb9ed5dd948d974a21e0d6cfae36c.svg "Repobeats analytics image")
## Star History
+77 -42
View File
@@ -74,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__)
@@ -134,18 +134,18 @@ 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.PointingHandCursor)
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()
@@ -154,22 +154,22 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
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()
@@ -188,19 +188,19 @@ 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()
@@ -210,9 +210,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
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"
@@ -256,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
@@ -268,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 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:
@@ -298,18 +299,18 @@ 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()
@@ -317,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 "⏸︎")
@@ -337,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
@@ -460,7 +461,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
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)
self.context_menu.addAction("Cancel Download", self.cancelDownloadAction)
self.context_menu.addAction("Delete File", self.deleteFileAction)
@staticmethod
def add_log(text):
@@ -539,7 +541,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
if row < 0 or row >= len(state.active_downloads):
return
magnet_link = list(state.active_downloads.keys())[row]
magnet_link = list(state.active_downloads.keys())[row]
magnetdl = state.active_downloads[magnet_link]
download_path = magnetdl.save_path()
@@ -560,12 +562,29 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
if row < 0 or row >= len(state.active_downloads):
return
magnet_link = list(state.active_downloads.keys())[row]
magnet_link = list(state.active_downloads.keys())[row]
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(magnet_link)
def cancelDownload(self):
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
@@ -575,9 +594,25 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
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)
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:
del state.active_downloads[magnet_link]
remove_download_log(magnet_link)
consoleLog(f"Cancelled download: {magnetdl.status().name}", True)
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)
+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 -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}")
+1 -1
View File
@@ -23,7 +23,7 @@ class AppState(QObject):
self.down_speed_limit: int = 0
self.max_connections: int = 200
self.max_downloads: int = 10
self.settings_path: str = None
self.settings_path: str = ""
self.dl_session: Any = None
self.active_downloads: List[str] = {}
self.window_transparency: bool = False
+3
View File
@@ -6,6 +6,7 @@ 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
@@ -51,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()
+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)