Compare commits

...

32 Commits

Author SHA1 Message Date
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
Vxrtrauter 7653748599 chore: remove unused file_list utility 2026-02-28 03:34:56 +01:00
Vxrtrauter 9bec3f9189 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-02-28 03:33:13 +01:00
Vxrtrauter 04d93b870a refactor: reorganize logging functionality and remove unused log files 2026-02-28 03:33:10 +01:00
KeksNino 664e660198 fix a bunch of lsp errors 2026-02-28 03:31:17 +01:00
Vxrtrauter 60019e0d2e refactor: update log imports and restructure logging functionality 2026-02-28 03:31:05 +01:00
Vxrtrauter 8238cfbe56 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-02-28 03:05:10 +01:00
Vxrtrauter 7ccff5c5a1 Refactor monkrus scraper to implement caching and improve post fetching logic 2026-02-28 03:05:09 +01:00
KeksNino 8abe92fc4f fix: hide seeders and leechers in uztracker and m0nkrus tab 2026-02-28 02:37:35 +01:00
Vxrtrauter 0a3b5eeee2 Remove unused asyncio import in uztracker.py 2026-02-27 23:59:59 +01:00
KeksNino 7662c861ea trigger github CI 2026-02-20 01:13:13 +01:00
KeksNino 2a8f305a76 fix exe not starting on windows 2026-02-19 19:28:24 +01:00
KeksNino 12223d18de add transparency to settings dialog on linux/macos 2026-02-17 00:29:55 +01:00
KeksNino 544733ce6b fix app icon in nuitka build process and remove settings pngs 2026-02-17 00:10:30 +01:00
KeksNino 7aee2bcfe9 remove include flags for assets in github workflows 2026-02-16 23:58:34 +01:00
KeksNino d274e64841 fix assets not showing up by loading them from base64 2026-02-16 23:49:21 +01:00
KeksNino 23c74f6487 fix: wrong assets path on windows 2026-02-16 02:34:44 +01:00
KeksNino 19ea09d035 fix: build_info.json path in github CI 2026-02-11 21:28:06 +01:00
shayaa 92359d75e5 Fix asset paths in dev.yml for logo and data files 2026-02-11 21:27:01 +01:00
shayaa 315a68c000 Update asset paths in dev.yml for PyInstaller 2026-02-11 21:26:13 +01:00
shayaa 4b0ef44efd Update file paths to src/main.py in dev.yml 2026-02-11 21:23:58 +01:00
KeksNino 6dc28615a0 refactor: move main.py and core into new src folder 2026-02-11 21:19:35 +01:00
36 changed files with 305 additions and 300 deletions
+10 -15
View File
@@ -106,7 +106,7 @@ jobs:
run: |
python -c "
from PIL import Image
img = Image.open('core/interface/assets/logo.png')
img = Image.open('src/core/interface/assets/logo.png')
img.save('app_icon.ico', format='ICO', sizes=[(256,256),(128,128),(64,64),(48,48),(32,32),(16,16)])
print('Icon converted successfully')
"
@@ -126,12 +126,11 @@ jobs:
--enable-plugin=upx \
--upx-binary="${{ env.UPX_BINARY }}" \
--noinclude-qt-translations \
--include-data-dir=core/interface/assets=core/interface/assets \
--include-data-files=build_info.json=build_info.json \
--include-data-files=build_info.json=core/build_info.json \
--include-data-files=build_info.json=core/interface/build_info.json \
--include-data-files=build_info.json=src/build_info.json \
--include-data-files=build_info.json=src/core/interface/build_info.json \
--output-filename=${{ env.APP_NAME }}.exe \
main.py
src/main.py
- name: Build with PyInstaller (Linux)
if: runner.os == 'Linux'
@@ -139,10 +138,8 @@ jobs:
run: |
pyinstaller --onefile --windowed \
--add-data "build_info.json:." \
--add-data "build_info.json:core" \
--add-data "build_info.json:core/interface" \
--add-data "core/interface/assets:core/interface/assets" \
--icon "core/interface/assets/logo.png" \
--add-data "build_info.json:src/core" \
--add-data "build_info.json:src/core/interface" \
--exclude-module PyQt6 \
--exclude-module PyQt5 \
--hidden-import=backports.tarfile \
@@ -151,7 +148,7 @@ jobs:
--hidden-import=jaraco.text \
--hidden-import=jaraco.functools \
--name ${{ env.APP_NAME }} \
main.py
src/main.py
- name: Build with PyInstaller (macOS)
if: runner.os == 'macOS'
@@ -159,10 +156,8 @@ jobs:
run: |
pyinstaller --onefile \
--add-data "build_info.json:." \
--add-data "build_info.json:core" \
--add-data "build_info.json:core/interface" \
--add-data "core/interface/assets:core/interface/assets" \
--icon "core/interface/assets/logo.png" \
--add-data "build_info.json:src/core" \
--add-data "build_info.json:src/core/interface" \
--exclude-module PyQt6 \
--exclude-module PyQt5 \
--hidden-import=backports.tarfile \
@@ -171,7 +166,7 @@ jobs:
--hidden-import=jaraco.text \
--hidden-import=jaraco.functools \
--name ${{ env.APP_NAME }} \
main.py
src/main.py
- name: Rename artifact
shell: bash
-84
View File
@@ -1,84 +0,0 @@
import requests
import asyncio
import threading
from bs4 import BeautifulSoup
from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.general.wrappers import run_thread
global url_uztracker
url_uztracker = "https://uztracker.net/tracker.php?nm="
def init_uztracker():
global up
global soup
try:
response = requests.get(url_uztracker, timeout=10)
if response.status_code == 200:
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')
maintitle = soup.find(class_='tt-text')
if maintitle:
return maintitle.text
else:
consoleLog("Program not found")
else:
consoleLog("Error: Uztracker down")
return None
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

-83
View File
@@ -1,83 +0,0 @@
from PySide6.QtWidgets import QTableWidgetItem
from core.utils.general.logs import consoleLog
from core.data.scrapers.uztracker import scrape_uztracker
from core.data.scrapers.rutracker import scrape_rutracker
from core.data.scrapers.monkrus import scrape_monkrus_telegram
from core.utils.network.jsonhandler import split_data, format_data, format_data_m0nkrus
from core.utils.data.state import state
def return_pressed(self):
search_text = self.searchbar.text()
if search_text == "":
consoleLog("Error: Can't search for nothing")
return
consoleLog(f"User searched for: {search_text}")
if state.tracker == "uztracker":
response = scrape_uztracker(search_text)
if response:
state.post_titles, state.post_urls = response
self.qtablewidget.clear()
if state.post_titles and len(state.post_titles) > 0:
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"])
self.qtablewidget.setRowCount(len(state.post_titles))
for i, author in enumerate(state.post_author):
self.qtablewidget.setItem(i, 1, QTableWidgetItem(author))
for i, title in enumerate(state.post_titles):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
self.show_empty_results(False)
else:
consoleLog(f"No Results for {search_text}")
self.qtablewidget.clear()
self.show_empty_results(True)
else:
consoleLog(f"No response from uztracker")
self.qtablewidget.clear()
self.show_empty_results(True)
elif state.tracker == "rutracker":
response = scrape_rutracker(search_text)
if response:
_, state.posts, _, _, cached = split_data(response)
if state.posts == []:
consoleLog(f"No Results for {search_text}")
self.qtablewidget.clear()
self.show_empty_results(True)
else:
state.post_titles, _, state.post_author, state.post_seeders, state.post_leechers = format_data(state.posts)
self.show_empty_results(False)
self.qtablewidget.clear()
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"])
self.qtablewidget.setRowCount(len(state.post_titles))
for i, author in enumerate(state.post_author):
self.qtablewidget.setItem(i, 1, QTableWidgetItem(author))
for i, title in enumerate(state.post_titles):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
for i, seeders in enumerate(state.post_seeders):
self.qtablewidget.setItem(i, 2, QTableWidgetItem(seeders))
for i, leechers in enumerate(state.post_leechers):
self.qtablewidget.setItem(i, 3, QTableWidgetItem(leechers))
if state.debug == True:
consoleLog(f"Response Cached: {cached}")
else:
consoleLog(f"No response from rutracker")
self.qtablewidget.clear()
self.show_empty_results(True)
elif state.tracker == "m0nkrus":
state.posts = scrape_monkrus_telegram(search_text)
if state.posts == []:
consoleLog(f"No Results for {search_text}")
self.qtablewidget.clear()
self.show_empty_results(True)
else:
state.post_titles, _, state.post_author = format_data_m0nkrus(state.posts)
self.show_empty_results(False)
self.qtablewidget.clear()
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"])
self.qtablewidget.setRowCount(len(state.post_titles))
for i, author in enumerate(state.post_author):
self.qtablewidget.setItem(i, 1, QTableWidgetItem(author))
for i, title in enumerate(state.post_titles):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
@@ -1,16 +1,28 @@
from bs4 import BeautifulSoup
import requests
from core.utils.general.logs import consoleLog
import time
def scrape_monkrus_telegram(query):
cache = {
"data": [],
"last_fetched": 0
}
cache_expiry = 300
def _get_telegram_posts():
post = 100
max_posts = 250
posts: list[dict] = []
posts = []
added = set()
current_time = time.time()
if cache["data"] and (current_time - cache["last_fetched"] < cache_expiry):
return cache["data"]
while post <= max_posts:
url = f"https://t.me/s/real_monkrus/{post}"
response = requests.get(url)
response = requests.get(url, timeout=10)
soup = BeautifulSoup(response.text, "html.parser")
bubbles = soup.find_all("div", class_="tgme_widget_message_bubble")
@@ -25,7 +37,7 @@ def scrape_monkrus_telegram(query):
title = post_txt.b.text
link = bubble.find("a", href=lambda x: x and x.startswith("https://uztracker.net"))
if query.lower() in title.lower() and link:
if link:
post_url = link["href"]
if post_url not in added:
added.add(post_url)
@@ -39,4 +51,20 @@ def scrape_monkrus_telegram(query):
post += 22
posts.reverse()
cache["data"] = posts
cache["last_fetched"] = current_time
return(posts)
def scrape_m0nkrus(query):
posts = _get_telegram_posts()
filtered_posts = []
for post in posts:
if query.lower() in post["title"].lower():
filtered_post = post.copy()
filtered_post["id"] = len(filtered_posts) + 1
filtered_posts.append(filtered_post)
return filtered_posts
@@ -1,10 +1,9 @@
import requests
from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import consoleLog
def scrape_rutracker(search_text):
search = requests.get(f"{state.api_url}/search?q={search_text}")
def scrape_rutracker(query):
search = requests.get(f"{state.api_url}/search?q={query}")
consoleLog("Sent request to server")
if search:
try:
@@ -12,10 +11,9 @@ def scrape_rutracker(search_text):
except Exception:
consoleLog("No results found / No response from server")
return None
else:
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.
+35
View File
@@ -0,0 +1,35 @@
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from core.utils.logging.logs import consoleLog
def scrape_uztracker(query):
base_url="https://uztracker.net/"
search_url = f"{base_url.rstrip('/')}/tracker.php?nm={query}"
consoleLog(search_url)
posts = []
try:
response = requests.get(search_url)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_'))
for link in links:
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
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

@@ -1,10 +1,11 @@
from core.utils.config.settings import save_settings
from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import consoleLog
from core.interface.utils.tabhelper import general_tab
from core.interface.utils.tabhelper import paths_tab
from core.interface.utils.tabhelper import network_tab
from PySide6 import QtWidgets
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
QLineEdit,
QPushButton,
@@ -31,6 +32,9 @@ def settings_dialog(self):
dialog.setLayout(QVBoxLayout())
if state.window_transparency and platform.system() != "Windows" and dialog:
dialog.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
def close_settings():
dialog.reject()
@@ -1,5 +1,5 @@
from PySide6 import QtWidgets
from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize
from PySide6.QtCore import QPersistentModelIndex, Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent, QSize
from PySide6.QtWidgets import (
QLineEdit,
QTableView,
@@ -18,7 +18,7 @@ from PySide6.QtWidgets import (
QStyledItemDelegate,
)
from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent
from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent, QGuiApplication
import darkdetect
import threading
import platform
@@ -29,8 +29,8 @@ import libtorrent as lt
import time
import sys
import json
import asyncio
from core.utils.general.logs import consoleLog, remove_download_log, flush_log_buffer
import base64
from core.utils.logging.logs import consoleLog, remove_download_log, flush_log_buffer
from core.utils.general.wrappers import run_thread
from core.utils.data.state import state
from core.utils.network.download import download_selected
@@ -38,7 +38,9 @@ from core.utils.network.update_checker import check_for_updates
from core.interface.utils.tabhelper import create_tab
from core.interface.utils.searchhelper import return_pressed
from core.interface.dialogs.settings import settings_dialog
from core.network.libtorrent_misc import cleanup_session
from core.interface.assets.base64_icons import settings_black_base64
from core.interface.assets.base64_icons import settings_white_base64
from core.interface.assets.base64_icons import logo_base64
from core.utils.general.shutdown import closehelper
@@ -65,6 +67,9 @@ def download_update(latest_version):
sys.exit(0)
def windowCloseHelper():
QGuiApplication.quit()
class MainWindow(QtWidgets.QMainWindow, QWidget):
log_signal = Signal(str) # Thread-safe signal for logging
@@ -73,21 +78,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
MainWindow._instance = self
self.log_signal.connect(self._on_log_signal)
def get_asset_path(filename):
base_path = os.path.dirname(os.path.abspath(__file__))
asset_path = os.path.join(base_path, 'assets', filename)
if os.path.exists(asset_path):
return asset_path
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
asset_path = os.path.join(sys._MEIPASS, 'core', 'interface', 'assets', filename)
if os.path.exists(asset_path):
return asset_path
return os.path.join('core', 'interface', 'assets', filename)
self.setWindowIcon(QIcon(get_asset_path("logo.png")))
pixmap = QPixmap()
image_data = base64.b64decode(logo_base64)
pixmap.loadFromData(image_data)
self.setWindowIcon(QIcon(pixmap))
if platform.system() == "Windows":
try:
@@ -153,51 +147,57 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
flush_log_buffer()
# Table Widget for Item List
self.qtablewidget = QTableWidget()
self.qtablewidget.setColumnCount(4)
self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.qtablewidget.verticalHeader().setVisible(False)
self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"])
def create_tracker_table(headers):
table = QTableWidget()
table.setColumnCount(len(headers))
table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
table.verticalHeader().setVisible(False)
table.setHorizontalHeaderLabels(headers)
header = table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
header.resizeSection(1, 500)
header.setStretchLastSection(False)
header = self.qtablewidget.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
header.setStretchLastSection(False)
return table
self.qtablewidget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.qtablewidget.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.rutrackerlist = create_tracker_table(["Post Title", "Author", "Seeders", "Leechers"])
self.uztrackerlist = create_tracker_table(["Post Title", "Author"])
self.monkruslist = create_tracker_table(["Post Title", "Author"])
header = self.qtablewidget.horizontalHeader()
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
header.resizeSection(1, 500)
state.tracker_list.update({"rutracker": self.rutrackerlist, "uztracker": self.uztrackerlist, "m0nkrus": self.monkruslist})
self.rutrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.rutrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.uztrackerlist.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.uztrackerlist.viewport().setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
container = QWidget()
containerLayout = QVBoxLayout()
containerLayout.addWidget(self.searchbar)
containerLayout.addWidget(self.qtablewidget)
containerLayout.addWidget(state.tracker_list[state.tracker])
class DownloadModel(QAbstractTableModel):
def __init__(self):
super().__init__()
self.headers = ["Action", "Name", "Status", "Progress", "Speed", "Size", "Total Size"]
def rowCount(self, parent=QModelIndex()):
def rowCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()) -> int:
return len(state.active_downloads)
def columnCount(self, parent=QModelIndex()):
def columnCount(self, parent: QModelIndex | QPersistentModelIndex = 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: int = Qt.ItemDataRole.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return self.headers[section]
return None
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
def data(self, index: QModelIndex | QPersistentModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole:
col = index.column()
if index.row() >= len(state.active_downloads) or index.row() < 0:
@@ -213,9 +213,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"
@@ -259,7 +259,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
else:
return "" if status.paused else "Stalled"
if role == Qt.UserRole and index.column() == 0:
if role == Qt.ItemDataRole.UserRole and index.column() == 0:
magnet_link = list(state.active_downloads.keys())[index.row()]
magnetdl = state.active_downloads[magnet_link]
return magnetdl.status().paused
@@ -359,7 +359,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
delegate.clicked.connect(on_pause_resume_clicked)
# download button triggers
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.qtablewidget.currentItem(), state.posts, state.post_titles))))
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(state.tracker_list[state.tracker].currentItem(), state.posts, state.post_titles))))
container.setLayout(containerLayout)
self.setCentralWidget(container)
@@ -379,15 +379,16 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.horizontal_layout = QHBoxLayout()
self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
self.horizontal_layout.addWidget(self.qtablewidget)
self.tracker_widget = state.tracker_list[state.tracker]
self.horizontal_layout.addWidget(self.tracker_widget)
self.tab1 = create_tab("Search", self.searchbar, self.qtablewidget, self.tabs, self.dlbutton, self.horizontal_layout)
self.tab1 = create_tab("Search", self.searchbar, state.tracker_list[state.tracker], self.tabs, self.dlbutton, self.horizontal_layout)
# 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):
self.image = QImage(state.image_path)
self.image = self.image.scaledToWidth(300, Qt.SmoothTransformation)
self.image = self.image.scaledToWidth(300, Qt.TransformationMode.SmoothTransformation)
self.pixmap = QPixmap.fromImage(self.image)
self.overlay_label = QLabel(self)
self.overlay_label.setPixmap(self.pixmap)
@@ -418,10 +419,18 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.settings_btn = QtWidgets.QToolButton()
self.settings_btn.setIconSize(QSize(21, 21))
self.settings_btn.setStyleSheet("background: transparent;")
if darkdetect.isDark():
self.settings_btn.setIcon(QIcon(get_asset_path("settings_white.png")))
settings_white_pixmap = QPixmap()
settings_white = base64.b64decode(settings_white_base64)
settings_white_pixmap.loadFromData(settings_white)
self.settings_btn.setIcon(QIcon(settings_white_pixmap))
else:
self.settings_btn.setIcon(QIcon(get_asset_path("settings_black.png")))
settings_black_pixmap = QPixmap()
settings_black = base64.b64decode(settings_black_base64)
settings_black_pixmap.loadFromData(settings_black)
self.settings_btn.setIcon(QIcon(settings_black_pixmap))
self.settings_btn.setToolTip("Settings")
self.settings_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.settings_btn.clicked.connect(lambda: settings_dialog(self))
@@ -504,14 +513,22 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
event.accept()
def set_tracker(self, _):
old_tracker = state.tracker
state.tracker = self.tracker_list.currentText()
old_widget = state.tracker_list[old_tracker]
self.horizontal_layout.removeWidget(old_widget)
old_widget.setParent(None)
tracker_widget = state.tracker_list[state.tracker]
self.horizontal_layout.addWidget(tracker_widget)
def show_empty_results(self, show: bool):
if show:
self.qtablewidget.hide()
state.tracker_list[state.tracker].hide()
self.emptyResults.show()
else:
self.qtablewidget.show()
state.tracker_list[state.tracker].show()
self.emptyResults.hide()
def show_empty_downloads(self):
@@ -525,8 +542,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
# thank you claude
def resizeEvent(self, event):
super().resizeEvent(event)
table_width = self.qtablewidget.viewport().width()
self.qtablewidget.setColumnWidth(1, int(table_width * 0.3))
table_width = state.tracker_list[state.tracker].viewport().width()
state.tracker_list[state.tracker].setColumnWidth(1, int(table_width * 0.3))
def contextMenuEvent(self, event: QContextMenuEvent):
if self.downloadList.underMouse():
+66
View File
@@ -0,0 +1,66 @@
from PySide6.QtWidgets import QTableWidgetItem
from core.utils.logging.logs import consoleLog
from core.data.scrapers.uztracker import scrape_uztracker
from core.data.scrapers.rutracker import scrape_rutracker
from core.data.scrapers.monkrus import scrape_m0nkrus
from core.utils.network.jsonhandler import split_data, format_data, format_data_minimal
from core.utils.data.state import state
scrapers = {
"uztracker": scrape_uztracker,
"rutracker": scrape_rutracker,
"m0nkrus": scrape_m0nkrus
}
def return_pressed(self):
search_text = self.searchbar.text()
if search_text == "":
consoleLog("Error: Can't search for nothing")
return
consoleLog(f"User searched for: {search_text}")
if state.tracker == "rutracker":
response = scrape_rutracker(search_text)
if response:
_, state.posts, _, _, cached = split_data(response)
if state.posts == []:
consoleLog(f"No Results for {search_text}")
state.tracker_list[state.tracker].clear()
self.show_empty_results(True)
else:
state.post_titles, _, state.post_author, state.post_seeders, state.post_leechers = format_data(state.posts)
self.show_empty_results(False)
state.tracker_list[state.tracker].clear()
state.tracker_list[state.tracker].setHorizontalHeaderLabels(["Post Title", "Author", "Seeders", "Leechers"])
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))
for i, seeders in enumerate(state.post_seeders):
state.tracker_list[state.tracker].setItem(i, 2, QTableWidgetItem(seeders))
for i, leechers in enumerate(state.post_leechers):
state.tracker_list[state.tracker].setItem(i, 3, QTableWidgetItem(leechers))
if state.debug == True:
consoleLog(f"Response Cached: {cached}")
else:
consoleLog(f"No response from rutracker")
state.tracker_list[state.tracker].clear()
self.show_empty_results(True)
elif state.tracker is not None:
state.posts = scrapers[state.tracker](search_text)
if state.posts == []:
consoleLog(f"No Results for {search_text}")
state.tracker_list[state.tracker].clear()
self.show_empty_results(True)
else:
state.post_author, state.post_titles, state.post_urls = format_data_minimal(state.posts)
self.show_empty_results(False)
state.tracker_list[state.tracker].clear()
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))
@@ -1,4 +1,4 @@
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import consoleLog
from core.utils.data.state import state
import psutil
@@ -4,7 +4,7 @@ from core.network.interface import get_interface_ip
import threading
import libtorrent as lt
from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import consoleLog
global loop_running
@@ -1,6 +1,6 @@
from core.utils.data.state import state
from core.utils.general.logs import update_download_completed_by_hash
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import update_download_completed_by_hash
from core.utils.logging.logs import consoleLog
from plyer import notification
import libtorrent as lt
import time
@@ -57,4 +57,4 @@ def update_log(shutdown_event):
updated.add(magnet_uri)
except Exception:
pass
time.sleep(5)
time.sleep(5)
@@ -1,4 +1,4 @@
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import consoleLog
from core.network.libtorrent_int import add_download
def add_magnet(uri):
@@ -1,6 +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 core.utils.logging.logs import consoleLog
from .config import create_config
@@ -7,6 +7,7 @@ class Download:
title: str
url: str
magnet_uri: str
path: str
completed: bool
@dataclass
@@ -1,4 +1,5 @@
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QTableWidget
from typing import Optional, Any, List, Dict
from pathlib import Path
@@ -7,9 +8,9 @@ class AppState(QObject):
def __init__(self):
super().__init__()
self.posts: List[Dict[str, Any]] = []
self.post_titles: List[str] = []
self.post_urls: List[str] = []
self.posts: list[Any] | None = None
self.post_titles: Optional[List] = None
self.post_urls: Optional[List] = None
self.post_author: List[str] = []
self.post_seeders: List[str] = []
self.post_leechers: List[str] = []
@@ -19,6 +20,7 @@ class AppState(QObject):
self.debug: bool = False
self.autoresume: bool = True
self.tracker: str = "rutracker"
self.tracker_list: dict[str, QTableWidget] = {}
self.api_url: str = "https://api.michijackson.xyz"
self.download_path: str = str(Path.home() / "Downloads")
self.up_speed_limit: int = 0
@@ -27,11 +29,11 @@ class AppState(QObject):
self.max_downloads: int = 10
self.settings_path: str = ""
self.dl_session: Any = None
self.active_downloads: List[str] = {}
self.active_downloads: Dict = {}
self.window_transparency: bool = False
self.interfaces: List = []
self.active_interfaces: List = []
self.bound_interface: str = None
self.bound_interface: Any = None
@property
def image_path(self) -> str:
@@ -1,7 +1,7 @@
import requests
from bs4 import BeautifulSoup
from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import consoleLog
from core.utils.network.jsonhandler import format_data
@@ -1,6 +1,8 @@
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import consoleLog, remove_download_log
from core.utils.network.download import run_download_direct
import os
def split_data(data):
count = data.count
@@ -16,6 +18,11 @@ def check_completed(downloads, resume):
run_download_direct(download.magnet_uri)
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}")
else:
consoleLog(f"Inexistent Download: {download.title}")
remove_download_log(download.magnet_uri)
@@ -5,6 +5,7 @@ from datetime import datetime
import json
import os
import re
import time
_log_buffer = []
@@ -21,7 +22,21 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
downloads = []
else:
downloads = []
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)
if any(d.magnet_uri == magnet_uri or d.url == url for d in downloads):
if magnet_uri in state.active_downloads:
@@ -37,7 +52,8 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
title=title,
url=url,
magnet_uri=magnet_uri,
completed=completed,
path=path,
completed=completed
))
consoleLog(f"Added {title} to Log File")
@@ -61,10 +77,12 @@ def remove_download_log(magnet_uri) -> DownloadList:
else:
downloads = []
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]
consoleLog(f"Removed {magnet_link} from Log File")
consoleLog(f"Removed {title} from Log File")
download_list = DownloadList(data=downloads, count=len(downloads))
with open(downloads_file, "w") as file:
@@ -1,11 +1,9 @@
from core.utils.data.state import state
from core.utils.general.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_magnet_link
from core.network.libtorrent_wrapper import add_download
from core.utils.general.wrappers import run_thread
from core.utils.general.logs import add_download_log
from core.utils.logging.logs import add_download_log
import threading
@@ -21,11 +19,8 @@ def run_download(item, posts, post_titles):
consoleLog(f"Selected URL: {post_url}")
magnet_uri = get_magnet_link(post_url)
add_download_log(item, post_url, magnet_uri, False)
add_download(magnet_uri)
add_download_log(item, post_url, magnet_uri, False)
def run_download_direct(magnet_uri):
add_download(magnet_uri)
add_download(magnet_uri)
@@ -20,16 +20,16 @@ def format_data(data):
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_titles = [post["title"] 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
# https://open.spotify.com/track/70vjXI7oBou5buTtgiJeRW?si=25640cc4243c4bf3
# https://open.spotify.com/track/0F0fMcIdL2FmcC9eYqwazX
@@ -1,6 +1,6 @@
import requests
from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import consoleLog
def check_for_updates():
url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases"
+10 -15
View File
@@ -1,13 +1,12 @@
from core.interface.gui import MainWindow
from core.interface.gui import MainWindow, windowCloseHelper
from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.logging.logs import consoleLog
from core.network.libtorrent_misc import send_notification, update_log
from core.utils.general.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.wrappers import run_thread
from core.utils.general.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.libtorrent_int import update_bound_interface
from core.utils.config.config import read_config
from PySide6 import QtWidgets
from PySide6.QtCore import Qt
@@ -15,15 +14,9 @@ import qdarktheme
import threading
import platform
import signal
import argparse
import sys
import time
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
def run_gui():
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
qdarktheme.setup_theme("auto")
@@ -33,21 +26,20 @@ def run_gui():
widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
qdarktheme.setup_theme("auto", custom_colors={"background": "#00000000"})
from core.utils.general.logs import set_main_window
from core.utils.logging.logs import set_main_window
set_main_window(widget)
widget.show()
sys.exit(app.exec())
def keyboardinterrupthandler(signum, frame):
closehelper()
windowCloseHelper()
if __name__ == "__main__":
def main():
start_time = time.perf_counter()
read_config()
logs = get_download_logs()
_, downloads = split_data(logs)
if args.debug:
state.debug = args.debug # override of read_config
signal.signal(signal.SIGINT, keyboardinterrupthandler)
consoleLog("Starting SoftwareManager...")
consoleLog("Fetching Network Interfaces...")
@@ -61,7 +53,10 @@ if __name__ == "__main__":
consoleLog("Started Thread: update_log")
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
consoleLog("Started Thread: check_completed")
check_downloads(downloads)
elapsed = time.perf_counter() - start_time
consoleLog(f"Initialization completed in {elapsed:.2f}s. Launching GUI")
run_gui()
if __name__ == "__main__":
main()