Compare commits

...

20 Commits

Author SHA1 Message Date
KeksNino eccdb2f068 fix file deletion when seeding 2026-03-07 21:56:07 +01:00
Vxrtrauter bd5ccc7688 fix: improve download status handling and enhance thread safety in logging functions 2026-03-07 21:36:11 +01:00
Vxrtrauter b2a30af5cd fix: prevent processing of non-downloadable items in download_selected function 2026-03-07 20:46:41 +01:00
Vxrtrauter fbc2225878 refactor: standardize version key in build_info.json and enhance download selection logic in download.py
feat: add SVG icons for play, pause, and folder actions in gui.py; implement status bar for download speed display
fix: remove unnecessary newline in libtorrent_misc.py and streamline update checker logic in update_checker.py
2026-03-07 20:41:03 +01:00
KeksNino 4d1a639651 remove unused QProgressBar widget 2026-03-07 17:55:52 +01:00
Vxrtrauter 8d1b3ba68f feat: implement seeding functionality and enhance download management with seeded magnets tracking 2026-03-08 12:00:00 +01:00
Vxrtrauter dcc12c0b28 fix: LibTorrent not detecting that file has been deleted until LibTorrent Session is reinitialized / enhance download management with file existence checks and cleanup 2026-03-07 00:45:27 +01:00
Vxrtrauter 925fe21d03 Add logging and download tracking to direct downloads 2026-03-07 14:00:00 +01:00
Vxrtrauter ec462afde6 Simplify boolean checks in network interface detection 2026-03-06 12:00:00 +01:00
Vxrtrauter e50ef32c66 refactor: clean up imports and remove unused code in scrapers and download modules 2026-03-05 23:19:08 +01:00
KeksNino d6e418d045 fix: crash when there are no results in search tab 2026-03-04 21:24:56 +01:00
Vxrtrauter a7c71b37f5 refactor: remove duplicate stripping 2026-03-04 21:00:37 +01:00
Vxrtrauter 8fb1bb9e8d Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-03-04 20:58:58 +01:00
Vxrtrauter 89fd8b4ebb fix: fix scraping for uztracker posts to support displaying authors properly 2026-03-04 20:58:56 +01:00
KeksNino 3915e114a3 fix: logging in uztracker scraper and search for non-ascii letters and remove unused imports 2026-03-04 20:28:55 +01:00
Vxrtrauter 34a50e1a1b refactor: rename scraping functions for consistency and improve data handling 2026-03-04 19:11:31 +01:00
KeksNino 90124fd9c8 fix: smaller author column in search tab 2026-03-04 14:07:43 +01:00
Vxrtrauter 4175f82967 feat: add windowCloseHelper function and integrate it into keyboard interrupt handling 2026-03-03 21:56:28 +01:00
Vxrtrauter 4bf2e9af81 feat: add download existence checks and improve log removal feedback 2026-03-01 23:14:10 +01:00
Vxrtrauter a6bb879db3 feat: enhance download logging with path handling and timeout mechanism 2026-02-28 04:01:00 +01:00
17 changed files with 487 additions and 186 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"Version": "dev" "version": "dev"
} }
+1 -1
View File
@@ -57,7 +57,7 @@ def _get_telegram_posts():
return(posts) return(posts)
def scrape_monkrus_telegram(query): def scrape_m0nkrus(query):
posts = _get_telegram_posts() posts = _get_telegram_posts()
filtered_posts = [] filtered_posts = []
+3 -5
View File
@@ -2,9 +2,8 @@ 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 scrape_rutracker(query):
def scrape_rutracker(search_text): search = requests.get(f"{state.api_url}/search?q={query}")
search = requests.get(f"{state.api_url}/search?q={search_text}")
consoleLog("Sent request to server") consoleLog("Sent request to server")
if search: if search:
try: try:
@@ -12,10 +11,9 @@ def scrape_rutracker(search_text):
except Exception: except Exception:
consoleLog("No results found / No response from server") consoleLog("No results found / No response from server")
return None return None
else: else:
return None return None
# This function utilizes the SoftwareManager server - source code can be found under the "server" branch. # This function utilizes the SoftwareManager server - source code can be found under the SoftwareManager-Server repository.
+25 -73
View File
@@ -1,83 +1,35 @@
import requests import requests
import threading
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.utils.data.state import state from urllib.parse import urljoin
from core.utils.logging.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.utils.general.wrappers import run_thread
def scrape_uztracker(query):
base_url="https://uztracker.net/"
search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}"
consoleLog(search_url)
posts = []
global url_uztracker
url_uztracker = "https://uztracker.net/tracker.php?nm="
def init_uztracker():
global up
global soup
try: try:
response = requests.get(url_uztracker, timeout=10) response = requests.get(search_url)
if response.status_code == 200: response.raise_for_status()
up = True
else:
up = False
consoleLog(f"Uztracker seems down, status code: {response.status_code}")
except requests.exceptions.RequestException as e:
consoleLog(f"Request Exception on {url_uztracker}:")
consoleLog(str(e))
consoleLog("Is the Site down?")
up = False
run_thread(threading.Thread(target=init_uztracker))
def scrape_uztracker(search):
if up:
search_url = url_uztracker + search
consoleLog(search_url)
result = False
global results
global resulttitles
results = []
resulttitles = []
try:
resultCount = 0
response = requests.get(search_url)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic'))
for link in links:
if link.b:
resultCount += 1
results.append(link['href'])
resulttitles.append(link.b.text)
result = True
if result:
return resulttitles, results
if not result:
# add popup in gui for no result
return None
except requests.RequestException as e:
if result:
consoleLog(f"Failed to fetch {search_url}: {e}")
return None
else:
consoleLog("Error: Uztracker down")
return None, None
def get_post_title(post_url):
if up:
response = requests.get(post_url)
soup = BeautifulSoup(response.text, 'html.parser') soup = BeautifulSoup(response.text, 'html.parser')
maintitle = soup.find(class_='tt-text') links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_'))
if maintitle: for link in links:
return maintitle.text
else:
consoleLog("Program not found")
else:
consoleLog("Error: Uztracker down")
return None
theme_link = link.find('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic'))
url = urljoin(base_url, theme_link['href'])
title = theme_link.b.text
author_link = link.find('a', class_="med")
author = author_link.text.strip() if author_link else "Unknown"
posts.append(dict(
title=title,
url=url,
author=author
))
return posts
except requests.RequestException as e:
consoleLog(f"Failed to fetch {search_url}: {e}")
return None
+288 -32
View File
@@ -1,5 +1,5 @@
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtCore import QPersistentModelIndex, Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize from PySide6.QtCore import QPersistentModelIndex, Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize, QByteArray
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
QTableView, QTableView,
@@ -10,15 +10,16 @@ from PySide6.QtWidgets import (
QHBoxLayout, QHBoxLayout,
QComboBox, QComboBox,
QTabWidget, QTabWidget,
QProgressBar,
QHeaderView, QHeaderView,
QMessageBox, QMessageBox,
QTableWidget, QTableWidget,
QTextEdit, QTextEdit,
QStyledItemDelegate, QStyledItemDelegate,
QStatusBar
) )
from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent 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
@@ -44,6 +45,22 @@ from core.interface.assets.base64_icons import logo_base64
from core.utils.general.shutdown import closehelper from core.utils.general.shutdown import closehelper
def _svg_icon(svg_str, size=20):
renderer = QSvgRenderer(QByteArray(svg_str.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="white"/></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="white"/><rect x="15" y="3" width="4" height="18" rx="1" fill="white"/></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="white"/></svg>'
def download_update(latest_version): def download_update(latest_version):
new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe" new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe"
url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe" url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe"
@@ -67,6 +84,9 @@ def download_update(latest_version):
sys.exit(0) sys.exit(0)
def windowCloseHelper():
QGuiApplication.quit()
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
@@ -135,27 +155,82 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.download_model = None self.download_model = None
self.downloadList = QTableView() self.downloadList = QTableView()
self.downloadList.setMouseTracking(True)
self._hovered_row = -1
self._tracker_hovered_row = -1
self._tracker_hovered_table = None
self.downloadList.viewport().installEventFilter(self)
self.emptyLibrary = QLabel("No items in library.") self.emptyLibrary = QLabel("No items in library.")
self.emptyDownload = QLabel("No items in downloads.") self.emptyDownload = QLabel("No items in downloads.")
self.emptyDownload.setAlignment(Qt.AlignmentFlag.AlignCenter) self.emptyDownload.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.consoleLog = QTextEdit() self.consoleLog = QTextEdit()
self.progressbar = QProgressBar() self.statusbar = QStatusBar()
flush_log_buffer() flush_log_buffer()
class TrackerHoverDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
if index.row() == MainWindow._instance._tracker_hovered_row:
painter.save()
painter.fillRect(option.rect, QColor(255, 255, 255, 15))
painter.restore()
super().paint(painter, option, index)
self._tracker_hover_delegate = TrackerHoverDelegate(self)
def create_tracker_table(headers): def create_tracker_table(headers):
table = QTableWidget() table = QTableWidget()
table.setColumnCount(len(headers)) table.setColumnCount(len(headers))
table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
table.verticalHeader().setVisible(False) table.verticalHeader().setVisible(False)
table.setHorizontalHeaderLabels(headers) table.setHorizontalHeaderLabels(headers)
table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
header = table.horizontalHeader() header = table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
header.resizeSection(1, 500) header.resizeSection(1, 500)
header.setStretchLastSection(False) header.setStretchLastSection(False)
header.setDefaultAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
header.setHighlightSections(False)
table.setShowGrid(False)
table.setStyleSheet("""
QTableWidget {
border: none;
outline: 0;
font-size: 13px;
}
QTableWidget::item {
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
padding: 6px 14px;
}
QTableWidget::item:selected {
background: rgba(255, 255, 255, 0.04);
}
QHeaderView {
background: transparent;
}
QHeaderView::section {
background: transparent;
color: rgba(255, 255, 255, 0.5);
font-weight: normal;
border: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding: 6px 14px;
}
QHeaderView::section:checked {
background: transparent;
color: rgba(255, 255, 255, 0.5);
font-weight: normal;
}
""")
table.setMouseTracking(True)
table.viewport().setMouseTracking(True)
table.setItemDelegate(self._tracker_hover_delegate)
return table return table
@@ -172,6 +247,12 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.uztrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.uztrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.uztrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.uztrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.monkruslist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.monkruslist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
for tbl in (self.rutrackerlist, self.uztrackerlist, self.monkruslist):
tbl.viewport().installEventFilter(self)
container = QWidget() container = QWidget()
containerLayout = QVBoxLayout() containerLayout = QVBoxLayout()
containerLayout.addWidget(self.searchbar) containerLayout.addWidget(self.searchbar)
@@ -215,17 +296,18 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
elif status.state == lt.torrent_status.seeding: elif status.state == lt.torrent_status.seeding:
return "Seeding" return "Seeding"
elif status.paused: elif status.paused:
return "Paused" is_auto = bool(magnetdl.flags() & lt.torrent_flags.auto_managed)
return "Queued" if is_auto else "Paused"
else: else:
return "Queued" return "Queued"
elif col == 3: elif col == 3:
return f"{status.progress * 100:.1f}%" return f"{status.progress * 100:.1f}%"
elif col == 4: elif col == 4:
speed_kbs = status.download_rate / 1024 down_kb = status.download_rate / 1024
if speed_kbs > 1024: up_kb = status.upload_rate / 1024
return f"{speed_kbs / 1024:.1f} MB/s" down_text = f"{down_kb / 1024:.1f} MB/s" if down_kb > 1024 else f"{down_kb:.1f} kB/s"
else: up_text = f"{up_kb / 1024:.1f} MB/s" if up_kb > 1024 else f"{up_kb:.1f} kB/s"
return f"{speed_kbs:.1f} kB/s" return f"{down_text}{up_text}"
elif col == 5: elif col == 5:
downloaded_mb = status.total_wanted_done / (1024 * 1024) downloaded_mb = status.total_wanted_done / (1024 * 1024)
if downloaded_mb > 1024: if downloaded_mb > 1024:
@@ -296,9 +378,24 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
idx = self.index(row, 0) idx = self.index(row, 0)
self.dataChanged.emit(idx, idx, [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.UserRole]) self.dataChanged.emit(idx, idx, [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.UserRole])
class HoverRowDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
if index.row() == MainWindow._instance._hovered_row:
painter.save()
painter.fillRect(option.rect, QColor(255, 255, 255, 15))
painter.restore()
super().paint(painter, option, index)
class PauseResumeDelegate(QStyledItemDelegate): class PauseResumeDelegate(QStyledItemDelegate):
clicked = Signal(int) clicked = Signal(int)
def paint(self, painter, option, index):
if index.row() == MainWindow._instance._hovered_row:
painter.save()
painter.fillRect(option.rect, QColor(255, 255, 255, 15))
painter.restore()
super().paint(painter, option, index)
def setEditorData(self, editor, index): def setEditorData(self, editor, index):
button = editor.findChild(QtWidgets.QPushButton) button = editor.findChild(QtWidgets.QPushButton)
@@ -308,9 +405,12 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
if button: if button:
if status.state == lt.torrent_status.seeding: if status.state == lt.torrent_status.seeding:
button.setText("📁") button.setIcon(_svg_icon(SVG_FOLDER, 18))
button.setText("")
else: else:
button.setText("▶︎" if status.paused else "⏸︎") 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.setText("")
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()]
@@ -318,14 +418,26 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
status = magnetdl.status() status = magnetdl.status()
widget = QWidget(parent) widget = QWidget(parent)
widget.setStyleSheet("border: none;") widget.setStyleSheet("border: none; background: transparent;")
layout = QHBoxLayout(widget) layout = QHBoxLayout(widget)
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))
else: else:
btnPause = QtWidgets.QPushButton("▶︎" if status.paused else "⏸︎") btnPause = QtWidgets.QPushButton()
btnPause.setFixedSize(40, 30) 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.setIconSize(QSize(18, 18))
btnPause.setFixedSize(30, 30)
btnPause.setCursor(Qt.CursorShape.PointingHandCursor)
btnPause.setStyleSheet("""
QPushButton {
border: none;
background: transparent;
padding: 0px;
}
""")
btnPause.clicked.connect(lambda: self.clicked.emit(index.row())) btnPause.clicked.connect(lambda: self.clicked.emit(index.row()))
layout.addStretch() layout.addStretch()
layout.addWidget(btnPause) layout.addWidget(btnPause)
@@ -333,16 +445,58 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
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:
self.clicked.emit(index.row())
return True
return False return False
self.download_model = DownloadModel() self.download_model = DownloadModel()
self.downloadList.setModel(self.download_model) self.downloadList.setModel(self.download_model)
self.downloadList.horizontalHeader().setStretchLastSection(False) self.downloadList.horizontalHeader().setStretchLastSection(False)
self.downloadList.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
self.downloadList.horizontalHeader().resizeSection(0, 70)
self.downloadList.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) self.downloadList.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
self.downloadList.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
self.downloadList.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
self.downloadList.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents)
self.downloadList.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeMode.ResizeToContents)
self.downloadList.horizontalHeader().setSectionResizeMode(6, QHeaderView.ResizeMode.ResizeToContents)
self.downloadList.horizontalHeader().setMinimumSectionSize(60)
self.downloadList.verticalHeader().setDefaultSectionSize(40)
self.downloadList.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows) self.downloadList.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows)
self.downloadList.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
self.downloadList.verticalHeader().setVisible(False)
self.downloadList.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
self.downloadList.horizontalHeader().setHighlightSections(False)
self.downloadList.setMouseTracking(True)
self.downloadList.setShowGrid(False)
self.downloadList.setStyleSheet("""
QTableView {
border: none;
outline: 0;
font-size: 13px;
}
QTableView::item {
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
padding: 6px 14px;
}
QTableView::item:selected {
background: rgba(255, 255, 255, 0.04);
}
QHeaderView {
background: transparent;
}
QHeaderView::section {
background: transparent;
color: rgba(255, 255, 255, 0.5);
font-weight: normal;
border: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding: 6px 14px;
}
QHeaderView::section:checked {
background: transparent;
color: rgba(255, 255, 255, 0.5);
font-weight: normal;
}
""")
self.downloadList.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.downloadList.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.downloadList.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.downloadList.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
@@ -351,12 +505,16 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
def on_pause_resume_clicked(row): def on_pause_resume_clicked(row):
download_model.toggle_pause_resume(row) download_model.toggle_pause_resume(row)
self.downloadList.viewport().setMouseTracking(True)
hover_delegate = HoverRowDelegate(self.downloadList)
self.downloadList.setItemDelegate(hover_delegate)
delegate = PauseResumeDelegate(self.downloadList) delegate = PauseResumeDelegate(self.downloadList)
self.downloadList.setItemDelegateForColumn(0, delegate) self.downloadList.setItemDelegateForColumn(0, delegate)
delegate.clicked.connect(on_pause_resume_clicked) delegate.clicked.connect(on_pause_resume_clicked)
# download button triggers # download button triggers
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.tracker_list[state.tracker].currentItem(), state.posts, state.post_titles)))) self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.tracker_list[state.tracker].selectedItems(), state.posts, state.post_titles))))
container.setLayout(containerLayout) container.setLayout(containerLayout)
self.setCentralWidget(container) self.setCentralWidget(container)
@@ -460,8 +618,21 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.consoleLog.setReadOnly(True) self.consoleLog.setReadOnly(True)
self.consoleLog.setFixedHeight(150) self.consoleLog.setFixedHeight(150)
self.consoleLog.setStyleSheet("margin-bottom: 0px; background: transparent;")
containerLayout.addWidget(self.consoleLog) containerLayout.addWidget(self.consoleLog)
self.statusbar.show()
self.statusbar.setStyleSheet("background: transparent; border: none; padding-right: 1px; padding-left: 6px; padding-bottom: 1px; margin-top: -8px")
self.version = QLabel(f"Version: {state.version}")
self.statusbar.addPermanentWidget(self.version, Qt.AlignmentFlag.AlignLeft)
self.speed_label = QLabel("↓ 0.0 kB/s ↑ 0.0 kB/s")
self.speed_label.setStyleSheet("padding-right: 6px;")
self.statusbar.addPermanentWidget(self.speed_label)
self.setStatusBar(self.statusbar)
self.download_timer = QTimer() self.download_timer = QTimer()
self.download_timer.timeout.connect(self.download_list_update) self.download_timer.timeout.connect(self.download_list_update)
self.download_timer.start(500) self.download_timer.start(500)
@@ -497,25 +668,110 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
def download_list_update(self): def download_list_update(self):
if self.download_model: if self.download_model:
self.download_model.layoutAboutToBeChanged.emit() row_count = self.download_model.rowCount()
self.download_model.layoutChanged.emit() if row_count > 0:
for row in range(self.download_model.rowCount()): top_left = self.download_model.index(0, 0)
idx = self.download_model.index(row, 0) bottom_right = self.download_model.index(row_count - 1, self.download_model.columnCount() - 1)
self.downloadList.closePersistentEditor(idx) self.download_model.dataChanged.emit(top_left, bottom_right, [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.UserRole])
self.downloadList.openPersistentEditor(idx)
if not hasattr(self, '_last_dl_row_count'):
self._last_dl_row_count = 0
if row_count != self._last_dl_row_count:
for row in range(row_count, self._last_dl_row_count):
idx = self.download_model.index(row, 0)
self.downloadList.closePersistentEditor(idx)
self._last_dl_row_count = row_count
self.download_model.layoutAboutToBeChanged.emit()
self.download_model.layoutChanged.emit()
for row in range(row_count):
idx = self.download_model.index(row, 0)
self.downloadList.closePersistentEditor(idx)
self.downloadList.openPersistentEditor(idx)
else:
delegate = self.downloadList.itemDelegateForColumn(0)
for row in range(row_count):
idx = self.download_model.index(row, 0)
editor = self.downloadList.indexWidget(idx)
if editor and delegate:
delegate.setEditorData(editor, idx)
self._update_speed_label()
def _update_speed_label(self):
total_down = 0
total_up = 0
for handle in state.active_downloads.values():
try:
s = handle.status()
total_down += s.download_rate
total_up += s.upload_rate
except Exception:
pass
down_kb = total_down / 1024
up_kb = total_up / 1024
down_text = f"{down_kb / 1024:.1f} MB/s" if down_kb > 1024 else f"{down_kb:.1f} kB/s"
up_text = f"{up_kb / 1024:.1f} MB/s" if up_kb > 1024 else f"{up_kb:.1f} kB/s"
self.speed_label.setText(f"{down_text}{up_text}")
def mousePressEvent(self, event):
self.rutrackerlist.clearSelection()
self.uztrackerlist.clearSelection()
self.monkruslist.clearSelection()
self.downloadList.clearSelection()
super().mousePressEvent(event)
def closeEvent(self, event: QCloseEvent): def closeEvent(self, event: QCloseEvent):
closehelper() closehelper()
event.accept() event.accept()
def eventFilter(self, obj, event):
try:
for tbl in (self.rutrackerlist, self.uztrackerlist, self.monkruslist):
if obj == tbl.viewport():
if event.type() == QEvent.Type.MouseMove:
pos = event.position().toPoint() if hasattr(event, 'position') else event.pos()
idx = tbl.indexAt(pos)
new_row = idx.row() if idx.isValid() else -1
if new_row != self._tracker_hovered_row or tbl is not self._tracker_hovered_table:
self._tracker_hovered_row = new_row
self._tracker_hovered_table = tbl
tbl.viewport().update()
elif event.type() == QEvent.Type.Leave:
self._tracker_hovered_row = -1
self._tracker_hovered_table = None
tbl.viewport().update()
break
if obj == self.downloadList.viewport():
if event.type() == QEvent.Type.MouseMove:
pos = event.position().toPoint() if hasattr(event, 'position') else event.pos()
idx = self.downloadList.indexAt(pos)
new_row = idx.row() if idx.isValid() else -1
if new_row != self._hovered_row:
old_row = self._hovered_row
self._hovered_row = new_row
self._invalidate_hover_row(old_row)
self._invalidate_hover_row(new_row)
elif event.type() == QEvent.Type.Leave:
old_row = self._hovered_row
self._hovered_row = -1
self._invalidate_hover_row(old_row)
except RuntimeError:
pass
return super().eventFilter(obj, event)
def _invalidate_hover_row(self, row):
if row >= 0 and self.download_model:
self.downloadList.viewport().update()
def set_tracker(self, _): def set_tracker(self, _):
old_tracker = state.tracker
state.tracker = self.tracker_list.currentText() state.tracker = self.tracker_list.currentText()
while self.horizontal_layout.count(): old_widget = state.tracker_list[old_tracker]
item = self.horizontal_layout.takeAt(0) self.horizontal_layout.removeWidget(old_widget)
if item.widget(): old_widget.setParent(None)
item.widget().setParent(None)
tracker_widget = state.tracker_list[state.tracker] tracker_widget = state.tracker_list[state.tracker]
self.horizontal_layout.addWidget(tracker_widget) self.horizontal_layout.addWidget(tracker_widget)
@@ -625,15 +881,15 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
if download_path and os.path.exists(download_path): if download_path and os.path.exists(download_path):
try: try:
if os.path.isfile(download_path): if os.path.isfile(download_path):
remove_download_log(magnet_link)
os.remove(download_path) os.remove(download_path)
del state.active_downloads[magnet_link] del state.active_downloads[magnet_link]
remove_download_log(magnet_link)
consoleLog(f"Deleted files for: {magnetdl.status().name}", True) consoleLog(f"Deleted files for: {magnetdl.status().name}", True)
else: else:
import shutil import shutil
shutil.rmtree(download_path)
del state.active_downloads[magnet_link] del state.active_downloads[magnet_link]
remove_download_log(magnet_link) remove_download_log(magnet_link)
shutil.rmtree(download_path)
consoleLog(f"Deleted files for: {magnetdl.status().name}", True) consoleLog(f"Deleted files for: {magnetdl.status().name}", True)
except Exception as e: except Exception as e:
consoleLog(f"Error deleting files: {e}", True) consoleLog(f"Error deleting files: {e}", True)
+12 -29
View File
@@ -2,10 +2,16 @@ from PySide6.QtWidgets import QTableWidgetItem
from core.utils.logging.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.data.scrapers.uztracker import scrape_uztracker from core.data.scrapers.uztracker import scrape_uztracker
from core.data.scrapers.rutracker import scrape_rutracker from core.data.scrapers.rutracker import scrape_rutracker
from core.data.scrapers.monkrus import scrape_monkrus_telegram from core.data.scrapers.monkrus import scrape_m0nkrus
from core.utils.network.jsonhandler import split_data, format_data, format_data_m0nkrus from core.utils.network.jsonhandler import split_data, format_data, format_data_minimal
from core.utils.data.state import state from core.utils.data.state import state
scrapers = {
"uztracker": scrape_uztracker,
"rutracker": scrape_rutracker,
"m0nkrus": scrape_m0nkrus
}
def return_pressed(self): def return_pressed(self):
search_text = self.searchbar.text() search_text = self.searchbar.text()
if search_text == "": if search_text == "":
@@ -13,29 +19,7 @@ def return_pressed(self):
return return
consoleLog(f"User searched for: {search_text}") consoleLog(f"User searched for: {search_text}")
if state.tracker == "uztracker": if state.tracker == "rutracker":
response = scrape_uztracker(search_text)
if response:
state.post_titles, state.post_urls = response
state.tracker_list[state.tracker].clear()
if state.post_titles and len(state.post_titles) > 0:
state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author"])
state.tracker_list[state.tracker].setRowCount(len(state.post_titles))
for i, author in enumerate(state.post_author):
state.tracker_list[state.tracker].setItem(i, 1, QTableWidgetItem(author))
for i, title in enumerate(state.post_titles):
state.tracker_list[state.tracker].setItem(i, 0, QTableWidgetItem(title))
self.show_empty_results(False)
else:
consoleLog(f"No Results for {search_text}")
state.tracker_list[state.tracker].clear()
self.show_empty_results(True)
else:
consoleLog(f"No response from uztracker")
state.tracker_list[state.tracker].clear()
self.show_empty_results(True)
elif state.tracker == "rutracker":
response = scrape_rutracker(search_text) response = scrape_rutracker(search_text)
if response: if response:
_, state.posts, _, _, cached = split_data(response) _, state.posts, _, _, cached = split_data(response)
@@ -64,15 +48,14 @@ def return_pressed(self):
state.tracker_list[state.tracker].clear() state.tracker_list[state.tracker].clear()
self.show_empty_results(True) self.show_empty_results(True)
elif state.tracker == "m0nkrus": elif state.tracker is not None:
state.posts = scrape_monkrus_telegram(search_text) state.posts = scrapers[state.tracker](search_text)
if state.posts == []: if 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)
else: else:
state.post_titles, _, state.post_author = format_data_m0nkrus(state.posts) state.post_author, state.post_titles, state.post_urls = format_data_minimal(state.posts)
self.show_empty_results(False) self.show_empty_results(False)
state.tracker_list[state.tracker].clear() state.tracker_list[state.tracker].clear()
state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author"]) state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author"])
+2 -2
View File
@@ -19,7 +19,7 @@ def get_active_interfaces():
for addr in addr_list: for addr in addr_list:
if addr.family == 2: # ipv4 if addr.family == 2: # ipv4
ipv4 = addr.address ipv4 = addr.address
if not ipv4.startswith("127.") and not ipv4.startswith("169.254") and up == True: if not ipv4.startswith("127.") and not ipv4.startswith("169.254") and up:
active.append(interface) active.append(interface)
consoleLog(f"Found Active: {interface}") consoleLog(f"Found Active: {interface}")
@@ -33,7 +33,7 @@ def list_interfaces() -> None:
for addr in addr_list: for addr in addr_list:
if addr.family == 2: # ipv4 if addr.family == 2: # ipv4
ipv4 = addr.address ipv4 = addr.address
if not ipv4.startswith("127.") and not ipv4.startswith("169.254") and up == True: if not ipv4.startswith("127.") and not ipv4.startswith("169.254") and up:
status = "ACTIVE" status = "ACTIVE"
else: else:
status = "INACTIVE" status = "INACTIVE"
+40 -2
View File
@@ -1,4 +1,5 @@
import time import time
import os
from core.utils.general.wrappers import run_thread from core.utils.general.wrappers import run_thread
from core.network.interface import get_interface_ip from core.network.interface import get_interface_ip
import threading import threading
@@ -51,8 +52,26 @@ def add_download(magnet_uri, dl_path=state.download_path):
init_session() init_session()
if magnet_uri in state.active_downloads: if magnet_uri in state.active_downloads:
consoleLog("Skipping Downloading, download already running...") try:
return handle = state.active_downloads[magnet_uri]
status = handle.status()
except RuntimeError as e:
consoleLog(f"Error in LibTorrent Handle: {e}")
if status.has_metadata:
filepath = os.path.join(status.save_path, status.name)
if not os.path.exists(filepath):
consoleLog(f"File Deleted, redownloading: {status.name}")
state.dl_session.remove_torrent(handle)
del state.active_downloads[magnet_uri]
else:
consoleLog("Skipping Downloading, download already running...")
return False
else:
consoleLog("Skipping Downloading, download already running... ")
return False
magnetdl = lt.parse_magnet_uri(magnet_uri) magnetdl = lt.parse_magnet_uri(magnet_uri)
magnetdl.save_path = dl_path magnetdl.save_path = dl_path
@@ -62,8 +81,27 @@ def add_download(magnet_uri, dl_path=state.download_path):
consoleLog(f"Added {magnet_uri} to downloads") consoleLog(f"Added {magnet_uri} to downloads")
run_thread(threading.Thread(target=dl_status_loop)) run_thread(threading.Thread(target=dl_status_loop))
return True
def add_seed(magnet_uri, file_path):
if state.active_downloads is None:
state.active_downloads = {}
init_session()
if magnet_uri in state.active_downloads:
consoleLog("Already seeding this torrent")
return False
magnetdl = lt.parse_magnet_uri(magnet_uri)
magnetdl.save_path = os.path.dirname(file_path)
handle = state.dl_session.add_torrent(magnetdl)
state.active_downloads[magnet_uri] = handle
state.seeded_magnets.add(magnet_uri)
return True
def dl_status_loop(): def dl_status_loop():
global loop_running global loop_running
+22 -1
View File
@@ -3,6 +3,7 @@ from core.utils.logging.logs import update_download_completed_by_hash
from core.utils.logging.logs import consoleLog from core.utils.logging.logs import consoleLog
from plyer import notification from plyer import notification
import libtorrent as lt import libtorrent as lt
import os
import time import time
@@ -50,7 +51,7 @@ def update_log(shutdown_event):
status = magnetdl.status() status = magnetdl.status()
if status.state == lt.torrent_status.seeding and magnet_uri not in updated: if status.state == lt.torrent_status.seeding and magnet_uri not in updated and magnet_uri not in state.seeded_magnets:
consoleLog(f"Marking {status.name} as completed") consoleLog(f"Marking {status.name} as completed")
info_hash = str(status.info_hash) info_hash = str(status.info_hash)
update_download_completed_by_hash(info_hash, True) update_download_completed_by_hash(info_hash, True)
@@ -58,3 +59,23 @@ def update_log(shutdown_event):
except Exception: except Exception:
pass pass
time.sleep(5) time.sleep(5)
def check_deleted_files(shutdown_event):
while not shutdown_event.is_set():
try:
for magnet_uri, magnetdl in list(state.active_downloads.items()):
if isinstance(magnetdl, dict):
continue
status = magnetdl.status()
if status.state == lt.torrent_status.seeding and status.has_metadata:
file_path = os.path.join(status.save_path, status.name)
if not os.path.exists(file_path):
consoleLog(f"Registered File Deletion: {status.name}")
state.dl_session.remove_torrent(magnetdl)
del state.active_downloads[magnet_uri]
except Exception:
pass
time.sleep(5)
+1
View File
@@ -7,6 +7,7 @@ class Download:
title: str title: str
url: str url: str
magnet_uri: str magnet_uri: str
path: str
completed: bool completed: bool
@dataclass @dataclass
+2 -1
View File
@@ -8,7 +8,7 @@ class AppState(QObject):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.posts: List[Dict[str, Any]] = [] self.posts: list[Any] | None = None
self.post_titles: Optional[List] = None self.post_titles: Optional[List] = None
self.post_urls: Optional[List] = None self.post_urls: Optional[List] = None
self.post_author: List[str] = [] self.post_author: List[str] = []
@@ -30,6 +30,7 @@ class AppState(QObject):
self.settings_path: str = "" self.settings_path: str = ""
self.dl_session: Any = None self.dl_session: Any = None
self.active_downloads: Dict = {} self.active_downloads: Dict = {}
self.seeded_magnets: set = set()
self.window_transparency: bool = False self.window_transparency: bool = False
self.interfaces: List = [] self.interfaces: List = []
self.active_interfaces: List = [] self.active_interfaces: List = []
+13 -5
View File
@@ -1,5 +1,7 @@
from core.utils.logging.logs import consoleLog from core.utils.logging.logs import consoleLog, remove_download_log
from core.utils.network.download import run_download_direct from core.utils.network.download import run_download_direct, seed_magnet
import os
def split_data(data): def split_data(data):
@@ -16,6 +18,12 @@ def check_completed(downloads, resume):
run_download_direct(download.magnet_uri) run_download_direct(download.magnet_uri)
consoleLog(f"Resuming {download.title}") consoleLog(f"Resuming {download.title}")
def check_downloads(downloads):
for download in downloads:
if os.path.exists(download.path):
consoleLog(f"Existing Download: {download.title}")
seed_magnet(download.magnet_uri, download.path)
else:
consoleLog(f"Inexistent Download: {download.title}")
remove_download_log(download.magnet_uri)
+43 -3
View File
@@ -5,11 +5,33 @@ from datetime import datetime
import json import json
import os import os
import re import re
import time
import threading
_log_buffer = [] _log_buffer = []
_downloads_lock = threading.RLock()
def add_download_log(title, url, magnet_uri, completed) -> DownloadList: def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
# wait for metadata outside the lock to avoid blocking other threads
magnetdl = state.active_downloads.get(magnet_uri)
if magnetdl:
status = magnetdl.status()
timeout = 60
start = time.time()
while not status.has_metadata and (time.time() - start) < timeout:
time.sleep(0.5)
status = magnetdl.status()
torrent_name = status.name
save_path = status.save_path
consoleLog(f"Torrent name (has_metadata={status.has_metadata}): {torrent_name}")
path = os.path.join(save_path, torrent_name)
else:
path = os.path.join(state.download_path, title)
with _downloads_lock:
return _add_download_log_inner(title, url, magnet_uri, completed, path)
def _add_download_log_inner(title, url, magnet_uri, completed, path) -> DownloadList:
downloads_file = os.path.join(state.settings_path, "downloads.json") downloads_file = os.path.join(state.settings_path, "downloads.json")
if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0: if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0:
@@ -21,7 +43,6 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
downloads = [] downloads = []
else: else:
downloads = [] downloads = []
if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads): if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads):
if magnet_uri in state.active_downloads: if magnet_uri in state.active_downloads:
@@ -37,7 +58,8 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
title=title, title=title,
url=url, url=url,
magnet_uri=magnet_uri, magnet_uri=magnet_uri,
completed=completed, path=path,
completed=completed
)) ))
consoleLog(f"Added {title} to Log File") consoleLog(f"Added {title} to Log File")
@@ -49,6 +71,10 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
return download_list return download_list
def remove_download_log(magnet_uri) -> DownloadList: def remove_download_log(magnet_uri) -> DownloadList:
with _downloads_lock:
return _remove_download_log_inner(magnet_uri)
def _remove_download_log_inner(magnet_uri) -> DownloadList:
downloads_file = os.path.join(state.settings_path, "downloads.json") downloads_file = os.path.join(state.settings_path, "downloads.json")
if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0: if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0:
@@ -61,10 +87,12 @@ def remove_download_log(magnet_uri) -> DownloadList:
else: else:
downloads = [] downloads = []
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))
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 {magnet_link} from Log File") consoleLog(f"Removed {title} from Log File")
download_list = DownloadList(data=downloads, count=len(downloads)) download_list = DownloadList(data=downloads, count=len(downloads))
with open(downloads_file, "w") as file: with open(downloads_file, "w") as file:
@@ -73,6 +101,10 @@ def remove_download_log(magnet_uri) -> DownloadList:
return download_list return download_list
def update_download_completed(magnet_uri, completed) -> DownloadList: def update_download_completed(magnet_uri, completed) -> DownloadList:
with _downloads_lock:
return _update_download_completed_inner(magnet_uri, completed)
def _update_download_completed_inner(magnet_uri, completed) -> DownloadList:
downloads_file = os.path.join(state.settings_path, "downloads.json") downloads_file = os.path.join(state.settings_path, "downloads.json")
if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0: if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0:
@@ -125,6 +157,10 @@ def update_download_completed(magnet_uri, completed) -> DownloadList:
def get_download_logs() -> DownloadList: def get_download_logs() -> DownloadList:
with _downloads_lock:
return _get_download_logs_inner()
def _get_download_logs_inner() -> DownloadList:
downloads_file = os.path.join(state.settings_path, "downloads.json") downloads_file = os.path.join(state.settings_path, "downloads.json")
if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0: if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0:
@@ -148,6 +184,10 @@ def extract_hash_from_magnet(magnet_uri): # full credits to claude for this
def update_download_completed_by_hash(info_hash, completed) -> DownloadList: def update_download_completed_by_hash(info_hash, completed) -> DownloadList:
with _downloads_lock:
return _update_download_completed_by_hash_inner(info_hash, completed)
def _update_download_completed_by_hash_inner(info_hash, completed) -> DownloadList:
downloads_file = os.path.join(state.settings_path, "downloads.json") downloads_file = os.path.join(state.settings_path, "downloads.json")
if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0: if os.path.exists(downloads_file) and os.path.getsize(downloads_file) > 0:
+21 -12
View File
@@ -1,31 +1,40 @@
from core.utils.data.state import state
from core.utils.logging.logs import consoleLog from core.utils.logging.logs import consoleLog
from core.utils.data.tracker import get_item_url from core.utils.data.tracker import get_item_url
from core.utils.data.tracker import get_magnet_link from core.utils.data.tracker import get_magnet_link
from core.network.libtorrent_wrapper import add_download from core.network.libtorrent_wrapper import add_download
from core.utils.general.wrappers import run_thread from core.utils.general.wrappers import run_thread
from core.utils.logging.logs import add_download_log from core.utils.logging.logs import add_download_log
from core.network.libtorrent_int import add_seed
import threading import threading
def download_selected(item, posts, post_titles): def download_selected(items, posts, post_titles):
if item is not None: if not items:
consoleLog(f"Downloading {item.text()}")
run_thread(threading.Thread(target=run_download, args=(item.text(), posts, post_titles)))
else:
consoleLog("No item selected for download.") consoleLog("No item selected for download.")
return
seen = set()
for item in items:
if item.column() != 0:
continue
text = item.text()
if text and text not in seen:
seen.add(text)
consoleLog(f"Downloading {text}")
run_thread(threading.Thread(target=run_download, args=(text, posts, post_titles)))
def run_download(item, posts, post_titles): def run_download(item, posts, post_titles):
post_url = get_item_url(item, posts, post_titles) post_url = get_item_url(item, posts, post_titles)
consoleLog(f"Selected URL: {post_url}") consoleLog(f"Selected URL: {post_url}")
magnet_uri = get_magnet_link(post_url) magnet_uri = get_magnet_link(post_url)
add_download_log(item, post_url, magnet_uri, False) if add_download(magnet_uri):
add_download(magnet_uri) add_download_log(item, post_url, magnet_uri, False)
def run_download_direct(magnet_uri): def run_download_direct(magnet_uri):
add_download(magnet_uri) consoleLog(f"Direct download: {magnet_uri[:60]}")
add_download(magnet_uri)
add_download_log("Direct Download", "", magnet_uri, False)
def seed_magnet(magnet_uri, file_path):
consoleLog(f"Seeding: {magnet_uri[:60]}")
add_seed(magnet_uri, file_path)
+2 -2
View File
@@ -20,12 +20,12 @@ def format_data(data):
return post_titles, post_links, post_author, post_seeders, post_leechers return post_titles, post_links, post_author, post_seeders, post_leechers
def format_data_m0nkrus(data): def format_data_minimal(data):
post_author = [post["author"] for post in data] post_author = [post["author"] for post in data]
post_titles = [post["title"] for post in data] post_titles = [post["title"] for post in data]
post_links = [post["url"] for post in data] post_links = [post["url"] for post in data]
return post_titles, post_links, post_author return post_author, post_titles, post_links
+1 -5
View File
@@ -17,11 +17,7 @@ def check_for_updates():
latest_version = latest_release["name"] latest_version = latest_release["name"]
assets = latest_release["assets"] assets = latest_release["assets"]
if latest_version != state.version:
if state.version == "dev":
consoleLog("Dev release detected, skipping version check")
return None, None
elif 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:")
+10 -12
View File
@@ -1,11 +1,11 @@
from core.interface.gui import MainWindow from core.interface.gui import MainWindow, windowCloseHelper
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
from core.network.libtorrent_misc import send_notification, update_log from core.network.libtorrent_misc import send_notification, update_log, check_deleted_files
from core.utils.logging.logs import get_download_logs from core.utils.logging.logs import get_download_logs
from core.utils.general.shutdown import closehelper, shutdown_event from core.utils.general.shutdown import closehelper, shutdown_event
from core.utils.general.wrappers import run_thread from core.utils.general.wrappers import run_thread
from core.utils.logging.loghandler import split_data, check_completed from core.utils.logging.loghandler import split_data, check_completed, check_downloads
from core.network.interface import list_interfaces, init_interfaces from core.network.interface import list_interfaces, init_interfaces
from core.utils.config.config import read_config from core.utils.config.config import read_config
from PySide6 import QtWidgets from PySide6 import QtWidgets
@@ -14,15 +14,9 @@ import qdarktheme
import threading import threading
import platform import platform
import signal import signal
import argparse
import sys import sys
import time import time
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
def run_gui(): def run_gui():
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
qdarktheme.setup_theme("auto") qdarktheme.setup_theme("auto")
@@ -39,14 +33,13 @@ def run_gui():
def keyboardinterrupthandler(signum, frame): def keyboardinterrupthandler(signum, frame):
closehelper() closehelper()
windowCloseHelper()
if __name__ == "__main__": def main():
start_time = time.perf_counter() start_time = time.perf_counter()
read_config() read_config()
logs = get_download_logs() logs = get_download_logs()
_, downloads = split_data(logs) _, downloads = split_data(logs)
if args.debug:
state.debug = args.debug # override of read_config
signal.signal(signal.SIGINT, keyboardinterrupthandler) signal.signal(signal.SIGINT, keyboardinterrupthandler)
consoleLog("Starting SoftwareManager...") consoleLog("Starting SoftwareManager...")
consoleLog("Fetching Network Interfaces...") consoleLog("Fetching Network Interfaces...")
@@ -60,7 +53,12 @@ if __name__ == "__main__":
consoleLog("Started Thread: update_log") consoleLog("Started Thread: update_log")
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume))) run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
consoleLog("Started Thread: check_completed") consoleLog("Started Thread: check_completed")
run_thread(threading.Thread(target=check_deleted_files, args=(shutdown_event,), daemon=True))
consoleLog("Started Thread: check_deleted_files")
check_downloads(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()
if __name__ == "__main__":
main()