Compare commits

...

17 Commits

Author SHA1 Message Date
Vxrtrauter 83adbb027f fix aria2 connection refused errors caused by autoresume 2026-01-19 13:49:20 +01:00
KeksNino 606c70efcc add unicode icons for pause/resume and fix action column size 2026-01-18 21:39:40 +01:00
KeksNino 9970086901 WIP: add pause/resume button in downloads tab 2026-01-18 02:03:31 +01:00
Vxrtrauter 0d631e0ab8 fix assets not showing up in build 2026-01-17 15:26:08 +01:00
Vxrtrauter 1c1ce3e69d update workflow again 2026-01-17 14:56:30 +01:00
Vxrtrauter b5931f5590 fix actions 2026-01-17 14:51:53 +01:00
Vxrtrauter 84ea030e2a Enhance asset handling: update icon loading to use dynamic path resolution 2026-01-17 14:46:07 +01:00
Vxrtrauter 356f3bf070 fix dependencies 2026-01-17 14:34:47 +01:00
Vxrtrauter 6ff29e2ea6 add logo to gui and assets 2026-01-17 14:30:48 +01:00
Vxrtrauter 847b50ab10 image overlay error message: add check for valid image path 2026-01-17 14:08:47 +01:00
Vxrtrauter 5bc81b5215 Enhance console logging: add option to print logs regardless of debug state 2026-01-17 02:00:16 +01:00
Vxrtrauter efe83613d0 Fixed Download Log Updating and add more Logging 2026-01-17 01:44:28 +01:00
KeksNino 3597cba207 update pyside6 version 2026-01-14 18:50:11 +01:00
Vxrtrauter 749051aa4f Enhance build process: add backports dependencies and generate release notes 2026-01-14 14:21:00 +01:00
Vxrtrauter bd26f073e4 fix config issues (fallback values, prevent errors) 2026-01-14 14:02:31 +01:00
Vxrtrauter 027b4aff8d improved debug & console outputs 2026-01-14 13:47:58 +01:00
Vxrtrauter a386269f29 add console logging in gui, remove progress bar from gui, add new logging function, refactor all print statements to use new log function 2026-01-14 11:27:22 +01:00
20 changed files with 322 additions and 170 deletions
+41 -2
View File
@@ -67,7 +67,7 @@ jobs:
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
pip install pyinstaller pip install pyinstaller backports.tarfile pillow
- name: Generate version - name: Generate version
id: version id: version
@@ -103,8 +103,18 @@ jobs:
run: | run: |
pyinstaller --onefile --windowed \ pyinstaller --onefile --windowed \
--add-data "build_info.json;." \ --add-data "build_info.json;." \
--add-data "build_info.json;core" \
--add-data "build_info.json;core/interface" \
--add-data "deps/aria2c.exe;." \ --add-data "deps/aria2c.exe;." \
--add-data "core/interface/assets;core/interface/assets" \ --add-data "core/interface/assets;core/interface/assets" \
--icon "core/interface/assets/logo.png" \
--exclude-module PyQt6 \
--exclude-module PyQt5 \
--hidden-import=backports.tarfile \
--hidden-import=backports \
--hidden-import=jaraco.context \
--hidden-import=jaraco.text \
--hidden-import=jaraco.functools \
--name ${{ env.APP_NAME }} \ --name ${{ env.APP_NAME }} \
main.py main.py
@@ -114,7 +124,17 @@ jobs:
run: | run: |
pyinstaller --onefile --windowed \ pyinstaller --onefile --windowed \
--add-data "build_info.json:." \ --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" \ --add-data "core/interface/assets:core/interface/assets" \
--icon "core/interface/assets/logo.png" \
--exclude-module PyQt6 \
--exclude-module PyQt5 \
--hidden-import=backports.tarfile \
--hidden-import=backports \
--hidden-import=jaraco.context \
--hidden-import=jaraco.text \
--hidden-import=jaraco.functools \
--name ${{ env.APP_NAME }} \ --name ${{ env.APP_NAME }} \
main.py main.py
@@ -158,14 +178,33 @@ jobs:
chmod +x release/SoftwareManager-dev-*-linux release/SoftwareManager-dev-*-macos chmod +x release/SoftwareManager-dev-*-linux release/SoftwareManager-dev-*-macos
ls -la release/ ls -la release/
- name: Generate release notes
run: |
SHORT_SHA="${{ needs.build.outputs.short_sha }}"
cat > release_notes.md << EOF
## Development Build
**Commit:** [\`${SHORT_SHA}\`](https://github.com/${{ github.repository }}/commit/${{ github.sha }})
### Changes
$(git log -1 --pretty=format:"- %s" ${{ github.sha }})
### Downloads
- Linux: \`SoftwareManager-dev-${SHORT_SHA}-linux\`
- Windows: \`SoftwareManager-dev-${SHORT_SHA}-windows.exe\`
- macOS: \`SoftwareManager-dev-${SHORT_SHA}-macos\`
EOF
cat release_notes.md
- name: Create Release (Public) - name: Create Release (Public)
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
tag_name: ${{ needs.build.outputs.version }} tag_name: ${{ needs.build.outputs.version }}
name: ${{ needs.build.outputs.version }} name: ${{ needs.build.outputs.version }}
body_path: release_notes.md
files: | files: |
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-linux release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-linux
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-windows.exe release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-windows.exe
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-macos release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-macos
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+4
View File
@@ -214,3 +214,7 @@ __marimo__/
*.sublime-project *.sublime-project
*.code-workspace *.code-workspace
*.swp *.swp
# Local build files
build-local.ps1
build-local.bat
+1
View File
@@ -1,5 +1,6 @@
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import requests import requests
from core.utils.general.logs import consoleLog
def scrape_monkrus_telegram(query): def scrape_monkrus_telegram(query):
post = 100 post = 100
+3 -4
View File
@@ -1,17 +1,16 @@
import requests import requests
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
def scrape_rutracker(search_text): def scrape_rutracker(search_text):
search = requests.get(f"{state.api_url}/search/{search_text}") search = requests.get(f"{state.api_url}/search/{search_text}")
if state.debug: consoleLog("Sent request to server")
print("Sent request to server")
if search: if search:
try: try:
return search.text return search.text
except Exception: except Exception:
if state.debug: consoleLog("No results found / No response from server")
print("No results found / No response from server")
return None return None
else: else:
+11 -16
View File
@@ -2,6 +2,7 @@ import requests
import asyncio import asyncio
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
global url_uztracker global url_uztracker
@@ -16,13 +17,11 @@ async def init_uztracker():
up = True up = True
else: else:
up = False up = False
if state.debug: consoleLog(f"Uztracker seems down, status code: {response.status_code}")
print(f"Uztracker seems down, status code: {response.status_code}")
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
if state.debug: consoleLog(f"\nRequest Exception on {url_uztracker}:")
print(f"\nRequest Exception on {url_uztracker}:") consoleLog(str(e))
print(e) consoleLog("\nIs the Site down?")
print("\nIs the Site down?")
up = False up = False
asyncio.run(init_uztracker()) asyncio.run(init_uztracker())
@@ -30,8 +29,7 @@ asyncio.run(init_uztracker())
def scrape_uztracker(search): def scrape_uztracker(search):
if up: if up:
search_url = url_uztracker + search search_url = url_uztracker + search
if state.debug: consoleLog(search_url)
print(search_url)
result = False result = False
global results global results
global resulttitles global resulttitles
@@ -59,12 +57,11 @@ def scrape_uztracker(search):
return None return None
except requests.RequestException as e: except requests.RequestException as e:
if result and state.debug: if result:
print(f"Failed to fetch {search_url}: {e}") consoleLog(f"Failed to fetch {search_url}: {e}")
return None return None
else: else:
if state.debug: consoleLog("Error: Uztracker down")
print("Error: Uztracker down")
return None, None return None, None
def get_post_title(post_url): def get_post_title(post_url):
@@ -75,11 +72,9 @@ def get_post_title(post_url):
if maintitle: if maintitle:
return maintitle.text return maintitle.text
else: else:
if state.debug: consoleLog("Program not found")
print("Program not found")
else: else:
if state.debug: consoleLog("Error: Uztracker down")
print("Error: Uztracker down")
return None return None
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+2 -2
View File
@@ -1,5 +1,6 @@
from core.utils.config.settings import save_settings from core.utils.config.settings import save_settings
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
@@ -16,8 +17,7 @@ from PySide6.QtWidgets import (
def settings_dialog(self): def settings_dialog(self):
if state.debug: consoleLog("Settings dialog opened")
print("Settings dialog opened")
dialog = QDialog(self) dialog = QDialog(self)
dialog.setWindowTitle("Settings") dialog.setWindowTitle("Settings")
dialog.setFixedSize(800, 400) dialog.setFixedSize(800, 400)
+140 -31
View File
@@ -1,6 +1,5 @@
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6 import QtCore from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent
from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
QTableView, QTableView,
@@ -16,6 +15,11 @@ from PySide6.QtWidgets import (
QHeaderView, QHeaderView,
QMessageBox, QMessageBox,
QTableWidget, QTableWidget,
QTextEdit,
QStyle,
QStyleOptionButton,
QStyledItemDelegate,
QApplication,
) )
from PySide6.QtGui import QIcon, QAction, QCloseEvent, QImage, QPixmap from PySide6.QtGui import QIcon, QAction, QCloseEvent, QImage, QPixmap
@@ -28,6 +32,7 @@ import subprocess
import time import time
import sys import sys
import json import json
from core.utils.general.logs import consoleLog
from core.utils.general.wrappers import run_thread from core.utils.general.wrappers import run_thread
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.network.download import download_selected from core.utils.network.download import download_selected
@@ -43,7 +48,7 @@ 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"
print("Downloading update...") consoleLog("Downloading update...", True)
response = r.get(url, allow_redirects=True) response = r.get(url, allow_redirects=True)
with open(new_filename, "wb") as f: with open(new_filename, "wb") as f:
f.write(response.content) f.write(response.content)
@@ -53,18 +58,45 @@ def download_update(latest_version):
time.sleep(0.5) time.sleep(0.5)
msg = QMessageBox() msg = QMessageBox()
msg.setIcon(QMessageBox.Information) msg.setIcon(QMessageBox.Icon.Information)
msg.setWindowTitle("New Version") msg.setWindowTitle("New Version")
msg.setText("New version installed.") msg.setText("New version installed.")
msg.setInformativeText("Please remove the old exe.") msg.setInformativeText("Please remove the old exe.")
msg.setStandardButtons(QMessageBox.Ok) msg.setStandardButtons(QMessageBox.StandardButton.Ok)
sys.exit(0) sys.exit(0)
class MainWindow(QtWidgets.QMainWindow, QWidget): class MainWindow(QtWidgets.QMainWindow, QWidget):
log_signal = Signal(str) # Thread-safe signal for logging
def __init__(self): def __init__(self):
super().__init__() super().__init__()
MainWindow._instance = self
self.log_signal.connect(self._on_log_signal)
def get_asset_path(filename):
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(__file__)
asset_path = os.path.join(base_path, '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")))
if platform.system() == "Windows":
try:
import ctypes
myappid = 'SoftwareManager.App.1'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
except Exception as e:
consoleLog(f"Could not set app ID: {e}")
build_info_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "build_info.json") build_info_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "build_info.json")
if os.path.exists(build_info_path): if os.path.exists(build_info_path):
@@ -81,14 +113,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
if assets: if assets:
msg = QMessageBox() msg = QMessageBox()
msg.setIcon(QMessageBox.Information) msg.setIcon(QMessageBox.Icon.Information)
msg.setWindowTitle("Update Available") msg.setWindowTitle("Update Available")
msg.setText("A new version is available.") msg.setText("A new version is available.")
msg.setInformativeText("Press Ok to download the update.") msg.setInformativeText("Press Ok to download the update.")
msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Ignore) msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore)
response = msg.exec_() response = msg.exec_()
if response == QMessageBox.Ok: if response == QMessageBox.StandardButton.Ok:
download_update(latest_version) download_update(latest_version)
self.setWindowTitle("Software Manager") self.setWindowTitle("Software Manager")
@@ -115,6 +147,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.downloadList = QTableView() self.downloadList = QTableView()
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.consoleLog = QTextEdit()
self.progressbar = QProgressBar() self.progressbar = QProgressBar()
# Table Widget for Item List # Table Widget for Item List
@@ -145,7 +179,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
class DownloadModel(QAbstractTableModel): class DownloadModel(QAbstractTableModel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.headers = ["Name", "Status", "Progress", "Speed", "Size", "Total Size"] self.headers = ["Action", "Name", "Status", "Progress", "Speed", "Size", "Total Size"]
def rowCount(self, parent=QModelIndex()): def rowCount(self, parent=QModelIndex()):
return len(state.downloads) return len(state.downloads)
@@ -159,32 +193,88 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
return None return None
def data(self, index, role=Qt.DisplayRole): def data(self, index, role=Qt.DisplayRole):
col = index.column()
download = state.downloads[index.row()]
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
download = state.downloads[index.row()]
col = index.column() col = index.column()
if col == 0: if col == 0:
return download.name return "▶︎" if download.is_paused else "⏸︎"
elif col == 1: elif col == 1:
return getattr(download, 'status', 'Downloading') return download.name
elif col == 2: elif col == 2:
return f"{int(download.progress)}%" return getattr(download, 'status', 'Downloading')
elif col == 3: elif col == 3:
return f"{download.download_speed_string()}" return f"{int(download.progress)}%"
elif col == 4: elif col == 4:
pass return f"{download.download_speed_string()}"
elif col == 5: elif col == 5:
pass
elif col == 6:
return f"{download.total_length_string()}" return f"{download.total_length_string()}"
if role == Qt.UserRole and col == 0:
return download.is_paused
return None return None
def toggle_pause_resume(self, row):
download = state.downloads[row]
if download.is_paused:
download.resume()
else:
download.pause()
idx = self.index(row, 0)
self.dataChanged.emit(idx, idx, [Qt.DisplayRole, Qt.UserRole])
class PauseResumeDelegate(QStyledItemDelegate):
clicked = Signal(int)
def paint(self, painter, option, index):
if index.column() != 0:
super().paint(painter, option, index)
return
paused = index.data(Qt.UserRole)
button = QStyleOptionButton()
button.rect = option.rect
button.text = "▶︎" if paused else "⏸︎"
button.state = QStyle.State_Enabled
QApplication.style().drawControl(
QStyle.CE_PushButton, button, painter
)
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
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.Stretch) self.downloadList.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
self.downloadList.setSelectionBehavior(QTableView.SelectRows) self.downloadList.setSelectionBehavior(QTableView.SelectRows)
self.downloadList.setAttribute(Qt.WA_TranslucentBackground) self.downloadList.setAttribute(Qt.WA_TranslucentBackground)
self.downloadList.viewport().setAttribute(Qt.WA_TranslucentBackground) self.downloadList.viewport().setAttribute(Qt.WA_TranslucentBackground)
download_model = self.download_model
def on_pause_resume_clicked(row):
download_model.toggle_pause_resume(row)
download = state.downloads[row]
if download.is_paused:
download.pause()
else:
download.resume()
delegate = PauseResumeDelegate(self.downloadList)
self.downloadList.setItemDelegateForColumn(0, delegate)
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=(self.qtablewidget.currentItem(), state.posts, state.post_titles)))) self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.qtablewidget.currentItem(), state.posts, state.post_titles))))
@@ -202,18 +292,20 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None) self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None)
self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None) self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None)
self.image = QImage(state.image_path) if state.image_path is not None and os.path.exists(state.image_path):
self.pixmap = QPixmap.fromImage(self.image) self.image = QImage(state.image_path)
self.overlay_label = QLabel(self) self.pixmap = QPixmap.fromImage(self.image)
self.overlay_label.setPixmap(self.pixmap) self.overlay_label = QLabel(self)
self.overlay_label.adjustSize() self.overlay_label.setPixmap(self.pixmap)
self.overlay_label.raise_() self.overlay_label.adjustSize()
self.overlay_label.raise_()
offset_x = -1350 offset_x = -1350
offset_y = -550 offset_y = -550
x = self.width() - self.overlay_label.width() - offset_x x = self.width() - self.overlay_label.width() - offset_x
y = self.height() - self.overlay_label.height() - offset_y y = self.height() - self.overlay_label.height() - offset_y
self.overlay_label.move(x, y) self.overlay_label.move(x, y)
# temporarily disabled # temporarily disabled
# state.image_changed.connect(self.update_image_overlay) # state.image_changed.connect(self.update_image_overlay)
@@ -230,9 +322,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.tracker_list.activated.connect(self.set_tracker) self.tracker_list.activated.connect(self.set_tracker)
if darkdetect.isDark(): if darkdetect.isDark():
settings_action = QAction(QIcon("core/interface/assets/settings_white.png"), "Settings", self) settings_action = QAction(QIcon(get_asset_path("settings_white.png")), "Settings", self)
else: else:
settings_action = QAction(QIcon("core/interface/assets/settings_black.png"), "Settings", self) settings_action = QAction(QIcon(get_asset_path("settings_black.png")), "Settings", self)
settings_action.triggered.connect(lambda: settings_dialog(self)) settings_action.triggered.connect(lambda: settings_dialog(self))
toolbar.addAction(settings_action) toolbar.addAction(settings_action)
@@ -240,8 +332,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
toolbar.setMovable(False) toolbar.setMovable(False)
self.progressbar.setValue(0) # self.progressbar.setValue(0)
containerLayout.addWidget(self.progressbar) self.consoleLog.setReadOnly(True)
self.consoleLog.setFixedHeight(150)
containerLayout.addWidget(self.consoleLog)
self.progress_timer = QTimer() self.progress_timer = QTimer()
self.progress_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.update_progress))) self.progress_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.update_progress)))
@@ -251,6 +345,17 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.download_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.download_list_update))) self.download_timer.timeout.connect(lambda: run_thread(threading.Thread(target=self.download_list_update)))
self.download_timer.start(500) self.download_timer.start(500)
@staticmethod
def add_log(text):
if MainWindow._instance:
MainWindow._instance.log_signal.emit(text)
def _on_log_signal(self, text):
self.consoleLog.append(text)
self.consoleLog.verticalScrollBar().setValue(
self.consoleLog.verticalScrollBar().maximum()
)
def update_image_overlay(self, new_image_path): def update_image_overlay(self, new_image_path):
self.image = QImage(new_image_path) self.image = QImage(new_image_path)
self.pixmap = QPixmap.fromImage(self.image) self.pixmap = QPixmap.fromImage(self.image)
@@ -288,3 +393,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
super().resizeEvent(event) super().resizeEvent(event)
table_width = self.qtablewidget.viewport().width() table_width = self.qtablewidget.viewport().width()
self.qtablewidget.setColumnWidth(1, int(table_width * 0.3)) self.qtablewidget.setColumnWidth(1, int(table_width * 0.3))
+9 -15
View File
@@ -1,4 +1,5 @@
from PySide6.QtWidgets import QTableWidgetItem from PySide6.QtWidgets import QTableWidgetItem
from core.utils.general.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_monkrus_telegram
@@ -8,11 +9,9 @@ from core.utils.data.state import state
def return_pressed(self): def return_pressed(self):
search_text = self.searchbar.text() search_text = self.searchbar.text()
if search_text == "": if search_text == "":
if state.debug: consoleLog("Error: Can't search for nothing")
print("Error: Can't search for nothing")
return return
if state.debug: consoleLog(f"User searched for: {search_text}")
print("User searched for:", search_text)
if state.tracker == "uztracker": if state.tracker == "uztracker":
response = scrape_uztracker(search_text) response = scrape_uztracker(search_text)
@@ -28,13 +27,11 @@ def return_pressed(self):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title)) self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
self.show_empty_results(False) self.show_empty_results(False)
else: else:
if state.debug: consoleLog(f"No Results for {search_text}")
print(f"No Results for {search_text}")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
else: else:
if state.debug: consoleLog(f"No response from uztracker")
print(f"No response from uztracker")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
@@ -43,8 +40,7 @@ def return_pressed(self):
if response: if response:
_, state.posts, _, _, cached = split_data(response) _, state.posts, _, _, cached = split_data(response)
if state.posts == []: if state.posts == []:
if state.debug: consoleLog(f"No Results for {search_text}")
print(f"No Results for {search_text}")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
else: else:
@@ -58,10 +54,9 @@ def return_pressed(self):
for i, title in enumerate(state.post_titles): for i, title in enumerate(state.post_titles):
self.qtablewidget.setItem(i, 0, QTableWidgetItem(title)) self.qtablewidget.setItem(i, 0, QTableWidgetItem(title))
if state.debug == True: if state.debug == True:
print(f"Response Cached: {cached}") consoleLog(f"Response Cached: {cached}")
else: else:
if state.debug: consoleLog(f"No response from rutracker")
print(f"No response from rutracker")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
@@ -69,8 +64,7 @@ def return_pressed(self):
state.posts = scrape_monkrus_telegram(search_text) state.posts = scrape_monkrus_telegram(search_text)
if state.posts == []: if state.posts == []:
if state.debug: consoleLog(f"No Results for {search_text}")
print(f"No Results for {search_text}")
self.qtablewidget.clear() self.qtablewidget.clear()
self.show_empty_results(True) self.show_empty_results(True)
else: else:
+18 -2
View File
@@ -2,7 +2,9 @@ import aria2p
import subprocess import subprocess
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import update_download_completed_by_hash from core.utils.general.logs import update_download_completed_by_hash
from core.utils.general.logs import consoleLog
from plyer import notification from plyer import notification
import socket
import time import time
import sys import sys
@@ -41,6 +43,21 @@ def aria2server():
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
) )
max_checks = 50
for check in range(max_checks):
try:
sock = socket.socket()
sock.settimeout(0.1)
sock.connect(("localhost", 6800))
sock.close()
break
except(ConnectionRefusedError, socket.timeout):
time.sleep(0.1)
else:
raise RuntimeError("Aria2 Server failed to start")
consoleLog("Aria2 Server started")
return aria2server return aria2server
def dlprogress(): def dlprogress():
@@ -83,8 +100,7 @@ def update_log(shutdown_event):
if d.is_metadata: if d.is_metadata:
continue continue
if d.progress == 100 and d.gid not in updated: if d.progress == 100 and d.gid not in updated:
if state.debug: consoleLog(f"Marking {d.name} as completed")
print(f"Marking {d.name} as completed")
update_download_completed_by_hash(d.info_hash, True) update_download_completed_by_hash(d.info_hash, True)
updated.add(d.gid) updated.add(d.gid)
state.downloads = [d for d in state.aria2.get_downloads() if d.is_active and not d.is_metadata] state.downloads = [d for d in state.aria2.get_downloads() if d.is_active and not d.is_metadata]
+3 -4
View File
@@ -1,16 +1,15 @@
from .aria2_integration import run_aria2p from .aria2_integration import run_aria2p
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
def start_client(): def start_client():
global aria2 global aria2
aria2 = run_aria2p() aria2 = run_aria2p()
if state.debug: consoleLog("Started Aria2p")
print("Started Aria2p")
def add_magnet(uri): def add_magnet(uri):
aria2.add_magnet(uri) aria2.add_magnet(uri)
if state.debug: consoleLog("Magnet URI added to Aria2")
print("Magnet URI added to Aria2")
+11 -16
View File
@@ -34,22 +34,17 @@ def read_config():
if not os.path.exists(config_file): if not os.path.exists(config_file):
create_config() create_config()
return
config.read(config_file) config.read(config_file)
if state.debug is not None: state.debug = config.getboolean("General", "debug", fallback=state.debug)
state.debug = config.getboolean("General", "debug") state.api_url = config.get("General", "api_url", fallback=state.api_url)
if state.api_url is not None: state.aria2_threads = config.getint("General", "aria2_threads", fallback=state.aria2_threads)
state.api_url = config.get("General", "api_url") state.download_path = config.get("General", "download_path", fallback=state.download_path)
if state.aria2_threads is not None: state.speed_limit = config.getint("General", "speed_limit", fallback=state.speed_limit)
state.aria2_threads = config.getint("General", "aria2_threads") state.ignore_updates = config.getboolean("General", "ignore_updates", fallback=state.ignore_updates)
if state.download_path is not None: state.image_path = config.get("General", "image_path", fallback=state.image_path)
state.download_path = config.get("General", "download_path") state.autoresume = config.getboolean("General", "autoresume", fallback=state.autoresume)
if state.speed_limit is not None:
state.speed_limit = config.getint("General", "speed_limit") create_config()
if state.ignore_updates is not None:
state.ignore_updates = config.getboolean("General", "ignore_updates")
if state.image_path is not None:
state.image_path = config.get("General", "image_path")
if state.autoresume is not None:
state.autoresume = config.getboolean("General", "autoresume")
+8 -14
View File
@@ -1,6 +1,7 @@
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.network.jsonhandler import format_data from core.utils.network.jsonhandler import format_data
@@ -10,20 +11,17 @@ def get_item_url(item, posts, post_titles): # softwarelist currentitem, post lis
if 0 <= post_index < len(post_titles): if 0 <= post_index < len(post_titles):
if state.tracker == "uztracker": if state.tracker == "uztracker":
item = "https://uztracker.net/" + state.post_urls[post_index].lstrip("./") item = "https://uztracker.net/" + state.post_urls[post_index].lstrip("./")
if state.debug: consoleLog(f"Found post URL: {item}")
print("Found post URL: ", item)
return item return item
if state.tracker == "rutracker": if state.tracker == "rutracker":
item_dict = posts[post_index] item_dict = posts[post_index]
_, post_links, _ = format_data([item_dict]) _, post_links, _ = format_data([item_dict])
if state.debug: consoleLog(f"Found post URL: {post_links[0]}")
print("Found post URL: ", post_links[0])
return post_links[0] return post_links[0]
if state.tracker == "m0nkrus": if state.tracker == "m0nkrus":
item_dict = posts[post_index] item_dict = posts[post_index]
_, post_links, _ = format_data([item_dict]) _, post_links, _ = format_data([item_dict])
if state.debug: consoleLog(f"Found post URL: {post_links[0]}")
print("Found post URL: ", post_links[0])
return post_links[0] return post_links[0]
return None return None
@@ -33,20 +31,16 @@ def get_item_url(item, posts, post_titles): # softwarelist currentitem, post lis
def get_magnet_link(post_url): def get_magnet_link(post_url):
try: try:
response = requests.get(post_url) # eventually impl. cloudscraper response = requests.get(post_url) # eventually impl. cloudscraper
if state.debug: consoleLog("Sent Request to retrieve Magnet Link...")
print("Sent Request to retrieve Magnet Link...")
response.raise_for_status() response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser') soup = BeautifulSoup(response.text, 'html.parser')
magnet_link = soup.find('a', href=lambda x: x and x.startswith('magnet:')) magnet_link = soup.find('a', href=lambda x: x and x.startswith('magnet:'))
if magnet_link: if magnet_link:
if state.debug: consoleLog(f"Magnet Link Retrieved: {magnet_link['href']}")
print("Magnet Link Retrieved: ", magnet_link['href'])
return magnet_link['href'] return magnet_link['href']
else: else:
if state.debug: consoleLog("Magnet Link not Found!")
print("Magnet Link not Found!")
except requests.RequestException as e: except requests.RequestException as e:
if state.debug: consoleLog(f"Failed to fetch {post_url}: {e}")
print(f"Failed to fetch {post_url}: {e}")
return None return None
+3 -4
View File
@@ -1,4 +1,5 @@
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.utils.network.download import run_download_direct from core.utils.network.download import run_download_direct
def split_data(data): def split_data(data):
@@ -11,12 +12,10 @@ def split_data(data):
def check_completed(downloads, resume): def check_completed(downloads, resume):
for download in downloads: for download in downloads:
if download.completed == False: if download.completed == False:
if state.debug: consoleLog(f"Found unfinished download: {download.title}")
print(f"Found unfinished download: {download.title}")
if resume == True: if resume == True:
run_download_direct(download.magnet_uri) run_download_direct(download.magnet_uri)
if state.debug: consoleLog(f"Resuming {download.title}")
print(f"Resuming {download.title}")
+40 -32
View File
@@ -1,6 +1,7 @@
from core.utils.data.models import Download, DownloadList from core.utils.data.models import Download, DownloadList
from core.utils.data.state import state from core.utils.data.state import state
from dataclasses import asdict from dataclasses import asdict
from datetime import datetime
import json import json
import os import os
import re import re
@@ -21,8 +22,7 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
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 state.debug: consoleLog("Skipped Logging, download already in file")
print("Skipped Logging, download already in file")
return DownloadList(data=downloads, count=len(downloads)) # thanks again claude (im stupid) return DownloadList(data=downloads, count=len(downloads)) # thanks again claude (im stupid)
@@ -33,8 +33,7 @@ def add_download_log(title, url, magnet_uri, completed) -> DownloadList:
completed=completed, completed=completed,
)) ))
if state.debug: consoleLog(f"Added {title} to Log File")
print(f"Added {title} to 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:
@@ -51,51 +50,45 @@ def update_download_completed(magnet_uri, completed) -> DownloadList:
existing_data = json.load(file) existing_data = json.load(file)
downloads = [Download(**d) for d in existing_data.get("data", [])] downloads = [Download(**d) for d in existing_data.get("data", [])]
except (json.JSONDecodeError, TypeError) as e: except (json.JSONDecodeError, TypeError) as e:
if state.debug: consoleLog(f"Error loading downloads.json: {e}")
print(f"Error loading downloads.json: {e}")
downloads = [] downloads = []
else: else:
downloads = [] downloads = []
identifier = (magnet_uri or "").strip() identifier = (magnet_uri or "").strip()
if state.debug: if state.debug:
print(f"Updating download completed for identifier: {identifier!r}") consoleLog(f"Updating download completed for identifier: {identifier!r}")
print(f"Total downloads in file: {len(downloads)}") consoleLog(f"Total downloads in file: {len(downloads)}")
for i, d in enumerate(downloads): for i, d in enumerate(downloads):
try: try:
mag = (getattr(d, 'magnet_uri', None) or '')[:50] mag = (getattr(d, 'magnet_uri', None) or '')[:50]
url = (getattr(d, 'url', None) or '')[:50] url = (getattr(d, 'url', None) or '')[:50]
print(f" [{i}] magnet: {mag}... url: {url}...") consoleLog(f" [{i}] magnet: {mag}... url: {url}...")
except Exception as e: except Exception as e:
print(f" [{i}] Error reading download: {e}") consoleLog(f" [{i}] Error reading download: {e}")
found = False found = False
for download in downloads: for download in downloads:
try: try:
stored_magnet = (getattr(download, 'magnet_uri', None) or "").strip() stored_magnet = (getattr(download, 'magnet_uri', None) or "").strip()
stored_url = (getattr(download, 'url', None) or "").strip() stored_url = (getattr(download, 'url', None) or "").strip()
if state.debug: consoleLog(f"Comparing with magnet: {stored_magnet[:50] if stored_magnet else 'None'}...")
print(f"Comparing with magnet: {stored_magnet[:50] if stored_magnet else 'None'}...")
if identifier and (stored_magnet == identifier or stored_url == identifier): if identifier and (stored_magnet == identifier or stored_url == identifier):
if state.debug: consoleLog(f"Match found! Setting completed={completed}")
print(f"Match found! Setting completed={completed}")
download.completed = completed download.completed = completed
found = True found = True
except Exception as e: except Exception as e:
if state.debug: consoleLog(f"Error comparing download: {e}")
print(f"Error comparing download: {e}")
if not found: if not found:
if state.debug: consoleLog("No matching download found to update")
print("No matching download found to update")
return DownloadList(data=downloads, count=len(downloads)) return DownloadList(data=downloads, count=len(downloads))
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:
json.dump(asdict(download_list), file, indent=4) json.dump(asdict(download_list), file, indent=4)
if state.debug: consoleLog("Updated download log")
print("Updated download log")
return download_list return download_list
@@ -132,16 +125,14 @@ def update_download_completed_by_hash(info_hash, completed) -> DownloadList:
existing_data = json.load(file) existing_data = json.load(file)
downloads = [Download(**d) for d in existing_data.get("data", [])] downloads = [Download(**d) for d in existing_data.get("data", [])]
except (json.JSONDecodeError, TypeError) as e: except (json.JSONDecodeError, TypeError) as e:
if state.debug: consoleLog(f"Error loading downloads.json: {e}")
print(f"Error loading downloads.json: {e}")
downloads = [] downloads = []
else: else:
downloads = [] downloads = []
info_hash_upper = (info_hash or "").upper().strip() info_hash_upper = (info_hash or "").upper().strip()
if state.debug: consoleLog(f"Updating download by hash: {info_hash_upper}")
print(f"Updating download by hash: {info_hash_upper}") consoleLog(f"Total downloads in file: {len(downloads)}")
print(f"Total downloads in file: {len(downloads)}")
found = False found = False
for download in downloads: for download in downloads:
@@ -153,19 +144,36 @@ def update_download_completed_by_hash(info_hash, completed) -> DownloadList:
download.completed = completed download.completed = completed
found = True found = True
except Exception as e: except Exception as e:
if state.debug: consoleLog(f"Error comparing download: {e}")
print(f"Error comparing download: {e}")
if not found: if not found:
if state.debug: consoleLog("No matching download found to update")
print("No matching download found to update")
return DownloadList(data=downloads, count=len(downloads)) return DownloadList(data=downloads, count=len(downloads))
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:
json.dump(asdict(download_list), file, indent=4) json.dump(asdict(download_list), file, indent=4)
if state.debug: consoleLog("Updated download log")
print("Updated download log")
return download_list return download_list
_main_window = None
def set_main_window(window):
global _main_window
_main_window = window
def consoleLog(text, printAnyways = False):
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
try:
from core.interface.gui import MainWindow
MainWindow.add_log(f"[{current_time}] {text}")
except Exception:
pass
if state.debug or printAnyways:
print(f"[{current_time}] {text}")
+3 -4
View File
@@ -1,4 +1,5 @@
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
import threading import threading
import psutil import psutil
import sys import sys
@@ -14,15 +15,13 @@ def kill_aria2server():
if state.aria2process: if state.aria2process:
state.aria2process.kill() state.aria2process.kill()
if state.debug: consoleLog("Killed Aria2")
print("Killed Aria2")
try: try:
for proc in psutil.process_iter(): for proc in psutil.process_iter():
if proc.name() == process: if proc.name() == process:
proc.kill() proc.kill()
if state.debug: consoleLog(f"Killed Aria2c (PID {proc.pid})")
print(f"Killed Aria2c (PID {proc.pid})")
except psutil.NoSuchProcess: except psutil.NoSuchProcess:
pass pass
+4 -6
View File
@@ -1,4 +1,5 @@
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.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.aria2_wrapper import start_client from core.network.aria2_wrapper import start_client
@@ -11,17 +12,14 @@ import threading
def download_selected(item, posts, post_titles): def download_selected(item, posts, post_titles):
if item is not None: if item is not None:
if state.debug: consoleLog(f"Downloading {item.text()}")
print(f"Downloading {item.text()}")
run_thread(threading.Thread(target=run_download, args=(item.text(), posts, post_titles))) run_thread(threading.Thread(target=run_download, args=(item.text(), posts, post_titles)))
else: else:
if state.debug: consoleLog("No item selected for download.")
print("No item selected for download.")
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)
if state.debug: consoleLog(f"Selected URL: {post_url}")
print("Selected URL: ", post_url)
magnet_uri = get_magnet_link(post_url) magnet_uri = get_magnet_link(post_url)
start_client() start_client()
add_download_log(item, post_url, magnet_uri, False) add_download_log(item, post_url, magnet_uri, False)
+9 -12
View File
@@ -1,12 +1,13 @@
import requests import requests
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
def check_for_updates(): def check_for_updates():
url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases" url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases"
response = requests.get(url) response = requests.get(url)
if response.status_code != 200: if response.status_code != 200:
print(f"Failed to fetch releases: {response.status_code}") consoleLog(f"Failed to fetch releases: {response.status_code}")
exit(1) exit(1)
releases = response.json() releases = response.json()
@@ -18,21 +19,17 @@ def check_for_updates():
if state.version == "dev": if state.version == "dev":
if state.debug: consoleLog("Dev release detected, skipping version check")
print("Dev release detected, skipping version check") return None, None
return None, None
elif latest_version != state.version: elif latest_version != state.version:
if state.debug: consoleLog(f"New release available: {latest_version}")
print(f"New release available: {latest_version}")
if assets: if assets:
if state.debug: consoleLog("Assets:")
print("Assets:") for asset in assets:
for asset in assets: consoleLog(f" - {asset['name']}: {asset['browser_download_url']}")
print(f" - {asset['name']}: {asset['browser_download_url']}")
return assets, latest_version return assets, latest_version
else: else:
return None, None return None, None
else: else:
if state.debug: consoleLog("Already up-to-date.")
print("Already up-to-date.")
return None, None return None, None
+10 -4
View File
@@ -1,5 +1,6 @@
from core.interface.gui import MainWindow from core.interface.gui import MainWindow
from core.utils.data.state import state from core.utils.data.state import state
from core.utils.general.logs import consoleLog
from core.network.aria2_integration import aria2server from core.network.aria2_integration import aria2server
from core.network.aria2_integration import send_notification, update_log from core.network.aria2_integration import send_notification, update_log
from core.utils.general.logs import get_download_logs from core.utils.general.logs import get_download_logs
@@ -27,6 +28,10 @@ def run_gui():
else: else:
app.setStyleSheet(qdarktheme.load_stylesheet("light")) app.setStyleSheet(qdarktheme.load_stylesheet("light"))
widget = MainWindow() widget = MainWindow()
from core.utils.general.logs import set_main_window
set_main_window(widget)
widget.show() widget.show()
sys.exit(app.exec()) sys.exit(app.exec())
@@ -43,14 +48,15 @@ if __name__ == "__main__":
count, downloads = split_data(logs) count, downloads = split_data(logs)
if args.debug: if args.debug:
state.debug = args.debug # override of read_config state.debug = args.debug # override of read_config
if state.debug: consoleLog("Starting Aria2 Server")
print("Starting Aria2 Server")
state.aria2process = run_aria2server() state.aria2process = run_aria2server()
signal.signal(signal.SIGINT, keyboardinterrupthandler) signal.signal(signal.SIGINT, keyboardinterrupthandler)
run_thread(threading.Thread(target=send_notification, args=(shutdown_event,), daemon=True)) run_thread(threading.Thread(target=send_notification, args=(shutdown_event,), daemon=True))
consoleLog("Started send_notification thread")
run_thread(threading.Thread(target=update_log, args=(shutdown_event,), daemon=True)) run_thread(threading.Thread(target=update_log, args=(shutdown_event,), daemon=True))
consoleLog("Started update_log thread")
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume))) run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
if state.debug: consoleLog("Started check_completed thread")
print("Launching GUI") consoleLog("Launching GUI")
run_gui() run_gui()
+2 -2
View File
@@ -1,5 +1,5 @@
requests==2.32.2 requests==2.32.2
PySide6==6.9.2 PySide6==6.10.1
beautifulsoup4==4.13.5 beautifulsoup4==4.13.5
darkdetect==0.8.0 darkdetect==0.8.0
aria2p==0.12.1 aria2p==0.12.1
@@ -7,4 +7,4 @@ pyinstaller==6.15.0
pyqtdarktheme==0.1.7 pyqtdarktheme==0.1.7
aiohttp==3.13.0 aiohttp==3.13.0
plyer==2.1.0 plyer==2.1.0
psutil==7.1.0 psutil==7.1.0