diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 976f996..e0cda20 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -35,6 +35,9 @@ jobs: asset_name: SoftwareManager-dev-${{ github.sha }}-macos runs-on: ${{ matrix.os }} + outputs: + version: ${{ steps.version.outputs.version }} + short_sha: ${{ steps.version.outputs.short_sha }} steps: - name: Checkout code @@ -66,7 +69,33 @@ jobs: pip install -r requirements.txt pip install pyinstaller - - name: Build executable with PyInstaller + - name: Generate version + id: version + shell: bash + run: | + COMMIT_SHA=${{ github.sha }} + SHORT_SHA=${COMMIT_SHA:0:7} + VERSION="${SHORT_SHA}-dev" + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT + echo "Generated version: $VERSION" + + - name: Inject commit hash into code (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $json = '{ + "version": "' + $env:VERSION + '" + }' + $json | Out-File -Encoding utf8 build_info.json + + - name: Build executable with PyInstaller (Windows) + if: runner.os == 'Windows' + run: pyinstaller --onefile --windowed --add-data "build_info.json:." --name ${{ env.APP_NAME }} main.py + + - name: Build executable with PyInstaller (Unix) + if: runner.os != 'Windows' run: pyinstaller --onefile --windowed --name ${{ env.APP_NAME }} main.py - name: Make executable (Unix) @@ -94,50 +123,29 @@ jobs: if: success() steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Download all artifacts - uses: actions/download-artifact@v4 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: path: artifacts - - name: Display downloaded artifacts - run: | - echo "Downloaded artifacts:" - find artifacts -type f - ls -la artifacts/ - - - name: Generate version - id: version - run: | - COMMIT_SHA=${{ github.sha }} - SHORT_SHA=${COMMIT_SHA:0:7} - VERSION="${SHORT_SHA}-dev" - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "Generated version: ${VERSION}" - - name: Prepare release files run: | mkdir -p release - SHORT_SHA="${{ steps.version.outputs.short_sha }}" + SHORT_SHA="${{ needs.build.outputs.short_sha }}" cp artifacts/ubuntu-latest-build/SoftwareManager-dev-*-linux release/SoftwareManager-dev-${SHORT_SHA}-linux cp artifacts/windows-latest-build/SoftwareManager-dev-*-windows.exe release/SoftwareManager-dev-${SHORT_SHA}-windows.exe cp artifacts/macos-latest-build/SoftwareManager-dev-*-macos release/SoftwareManager-dev-${SHORT_SHA}-macos chmod +x release/SoftwareManager-dev-*-linux release/SoftwareManager-dev-*-macos ls -la release/ - - name: Create Prerelease (Public) + - name: Create Release (Public) uses: softprops/action-gh-release@v2 with: - tag_name: ${{ steps.version.outputs.version }} - name: ${{ steps.version.outputs.version }} - draft: false - prerelease: true + tag_name: ${{ needs.build.outputs.version }} + name: ${{ needs.build.outputs.version }} files: | - release/SoftwareManager-dev-${{ steps.version.outputs.short_sha }}-linux - release/SoftwareManager-dev-${{ steps.version.outputs.short_sha }}-windows.exe - release/SoftwareManager-dev-${{ steps.version.outputs.short_sha }}-macos + 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 }}-macos env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/core/data/scrapers/monkrus.py b/core/data/scrapers/monkrus.py new file mode 100644 index 0000000..6bfd1ea --- /dev/null +++ b/core/data/scrapers/monkrus.py @@ -0,0 +1,41 @@ +from bs4 import BeautifulSoup +import requests + +def scrape_monkrus_telegram(query): + post = 100 + max_posts = 250 + posts: list[dict] = [] + added = set() + + while post <= max_posts: + url = f"https://t.me/s/real_monkrus/{post}" + response = requests.get(url) + soup = BeautifulSoup(response.text, "html.parser") + bubbles = soup.find_all("div", class_="tgme_widget_message_bubble") + + if not bubbles: + break + + for bubble in bubbles: + post_txt = bubble.find("div", class_="tgme_widget_message_text js-message_text") + if not post_txt or not post_txt.b: + continue + + 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: + post_url = link["href"] + if post_url not in added: + added.add(post_url) + posts.append(dict( + author="m0nkrus", + id=len(posts) + 1, + title=title, + url=post_url + )) + + post += 22 + + posts.reverse() + return(posts) diff --git a/core/data/scrapers/rutracker.py b/core/data/scrapers/rutracker.py index 84e702f..5baf263 100644 --- a/core/data/scrapers/rutracker.py +++ b/core/data/scrapers/rutracker.py @@ -4,6 +4,8 @@ from core.utils.data.state import state def scrape_rutracker(search_text): search = requests.get(f"{state.api_url}/search/{search_text}") + if state.debug: + print("Sent request to server") if search: try: return search.text diff --git a/core/data/scrapers/uztracker.py b/core/data/scrapers/uztracker.py index 00cf00f..d39bbaf 100644 --- a/core/data/scrapers/uztracker.py +++ b/core/data/scrapers/uztracker.py @@ -17,62 +17,56 @@ async def init_uztracker(): up = True else: up = False - print(f"Uztracker seems down, status code: {response.status_code}") + if state.debug: + print(f"Uztracker seems down, status code: {response.status_code}") except requests.exceptions.RequestException as e: - print(f"\nRequest Exception on {url_uztracker}:") - print(e) - print("\nIs the Site down?") + if state.debug: + print(f"\nRequest Exception on {url_uztracker}:") + print(e) + print("\nIs the Site down?") up = False asyncio.run(init_uztracker()) -async def scrape_uztracker(search, max_results=450): +def scrape_uztracker(search): if up: + search_url = url_uztracker + search + if state.debug: + print(search_url) result = False global results global resulttitles - results = [] + results = [] resulttitles = [] - per_page = 50 + 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 - for start in range(0, max_results, per_page): - search_url = url_uztracker + search + f"&start={start}" - if state.debug: - print(search_url) + if result: + return resulttitles, results - async with aiohttp.ClientSession() as session: - try: - async with session.get(search_url) as response: - response.raise_for_status() - text = await response.text() - soup = BeautifulSoup(text, 'html.parser') - links = soup.find_all('a', class_="genmed tLink", href=lambda x: x and x.startswith('./viewtopic')) - resultCount = 0 - for link in links: - if link.b: - resultCount += 1 - results.append(link['href']) - resulttitles.append(link.b.text) - result = True - - if resultCount == 0: - # add popup in gui for no result - break - - except aiohttp.ClientError as e: - if result: - print(f"Failed to fetch {search_url}: {e}") - return None + if not result: + # add popup in gui for no result + return None - else: + except requests.RequestException as e: + if result and state.debug: + print(f"Failed to fetch {search_url}: {e}") + return None + else: + if state.debug: print("Error: Uztracker down") - return None, None - - if not results: - return None, None - - return resulttitles, results + return None, None def get_post_title(post_url): if up: @@ -85,7 +79,8 @@ def get_post_title(post_url): if state.debug: print("Program not found") else: - print("Error: Uztracker down") + if state.debug: + print("Error: Uztracker down") return None diff --git a/core/interface/dialogs/settings.py b/core/interface/dialogs/settings.py index fb16321..038af6d 100644 --- a/core/interface/dialogs/settings.py +++ b/core/interface/dialogs/settings.py @@ -10,12 +10,10 @@ from PySide6.QtWidgets import ( QLabel, QHBoxLayout, QSpinBox, + QCheckBox, ) - - - def settings_dialog(self): if state.debug: @@ -29,7 +27,20 @@ def settings_dialog(self): def close_settings(): dialog.reject() - + + checkbox_container = QWidget() + checkbox_layout = QHBoxLayout() + + # ignore updates checkbox + checkbox = QCheckBox() + checkbox_container.setLayout(checkbox_layout) + checkbox_layout.addWidget(QLabel("Ignore Updates: ")) + checkbox_layout.addStretch() + checkbox.setChecked(state.ignore_updates) + checkbox.toggled.connect(lambda checked: setattr(state, 'ignore_updates', checked)) + checkbox_layout.addWidget(checkbox) + dialog.layout().addWidget(checkbox_container) + ################## # THREAD SETTING # ################## @@ -114,10 +125,9 @@ def settings_dialog(self): save_btn.clicked.connect(lambda: save_settings(thread_box.value(), close_settings, api_url.text(), download_path.text(), speed_limit.value())) cancel_btn.clicked.connect(dialog.reject) - print(thread_box.value()) layout.addWidget(cancel_btn) layout.addWidget(save_btn) dialog.layout().addLayout(layout) - dialog.exec() \ No newline at end of file + dialog.exec() diff --git a/core/interface/gui.py b/core/interface/gui.py index 1c8caf7..8822028 100644 --- a/core/interface/gui.py +++ b/core/interface/gui.py @@ -14,25 +14,76 @@ from PySide6.QtWidgets import ( QTabWidget, QProgressBar, QHeaderView, + QMessageBox, + QTableWidget, ) + from PySide6.QtGui import QIcon, QAction, QCloseEvent import darkdetect import threading +import platform +import requests as r +import os +import subprocess +import time +import sys +import json from core.utils.general.wrappers import run_thread from core.utils.data.state import state from core.utils.network.download import download_selected +from core.utils.network.update_checker import check_for_updates from core.utils.general.shutdown import closehelper 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.aria2_integration import dlprogress +print(os.path.abspath(__file__)) +def download_update(latest_version): + old_filename = f"SoftwareManager-dev-{state.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" + + print("Downloading update...") + if os.path.exists(old_filename): + os.remove(old_filename) + response = r.get(url, allow_redirects=True) + with open(new_filename, "wb") as f: + f.write(response.content) + if not os.path.exists(new_filename): + raise FileNotFoundError(f"Executable not found") + subprocess.Popen([new_filename], shell=True) + time.sleep(0.5) + sys.exit(0) class MainWindow(QtWidgets.QMainWindow, QWidget): def __init__(self): super().__init__() - searchresults = [] + + 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): + with open(build_info_path, "r") as f: + build_info = json.load(f) + state.version = build_info.get("version") + + # Check for updates on Windows + if state.ignore_updates is False: + if platform.system() == "Windows": + result = check_for_updates() + assets, latest_version = result + + if assets: + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setWindowTitle("Update Available") + msg.setText(f"A new version is available.") + msg.setInformativeText("Please visit the GitHub releases page to download the latest version.") + msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Ignore) + + response = msg.exec_() + if response == QMessageBox.Ok: + download_update(latest_version) self.setWindowTitle("Software Manager") self.setGeometry(100, 100, 800, 600) @@ -48,8 +99,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.searchbar.returnPressed.connect(lambda: run_thread(threading.Thread(target=return_pressed, args=(self,)))) # Triggers data function thread on enter self.dlbutton = QtWidgets.QPushButton("Download") - self.softwareList = QListWidget() - self.post_author_list = QListWidget() self.libraryList = QListWidget() self.emptyResults = QLabel("No Results") @@ -62,14 +111,24 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.emptyLibrary = QLabel("No items in library.") self.emptyDownload = QLabel("No items in downloads.") self.progressbar = QProgressBar() + self.qtablewidget = QTableWidget() + + self.qtablewidget.setColumnCount(2) + self.qtablewidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.qtablewidget.verticalHeader().setVisible(False) + self.qtablewidget.setHorizontalHeaderLabels(["Post Title", "Author"]) + self.qtablewidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) + self.qtablewidget.setAttribute(Qt.WA_TranslucentBackground) + self.qtablewidget.viewport().setAttribute(Qt.WA_TranslucentBackground) + + header = self.qtablewidget.horizontalHeader() + header.setSectionResizeMode(1, QHeaderView.Fixed) + header.resizeSection(1, 500) container = QWidget() containerLayout = QVBoxLayout() containerLayout.addWidget(self.searchbar) - - containerLayout.addWidget(self.softwareList) - containerLayout.addWidget(self.post_author_list) - self.softwareList.addItems(searchresults) + containerLayout.addWidget(self.qtablewidget) class DownloadModel(QAbstractTableModel): def __init__(self): @@ -111,9 +170,11 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.downloadList.horizontalHeader().setStretchLastSection(False) self.downloadList.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.downloadList.setSelectionBehavior(QTableView.SelectRows) + self.downloadList.setAttribute(Qt.WA_TranslucentBackground) + self.downloadList.viewport().setAttribute(Qt.WA_TranslucentBackground) # download button triggers - self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.softwareList.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)))) container.setLayout(containerLayout) self.setCentralWidget(container) @@ -122,13 +183,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): self.tabs = QTabWidget() self.horizontal_layout = QHBoxLayout() - self.horizontal_layout.addWidget(self.softwareList, stretch=3) self.horizontal_layout.addWidget(self.emptyResults, stretch=3) - self.horizontal_layout.addWidget(self.post_author_list) + self.horizontal_layout.addWidget(self.qtablewidget) - self.tab1 = create_tab("Search", self.searchbar, self.softwareList, self.tabs, self.dlbutton, self.horizontal_layout) + self.tab1 = create_tab("Search", self.searchbar, self.qtablewidget, self.tabs, self.dlbutton, self.horizontal_layout) self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None) self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None) + + containerLayout.addWidget(self.tabs) @@ -139,14 +201,13 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): toolbar.setLayoutDirection(Qt.RightToLeft) self.tracker_list = QComboBox() - self.tracker_list.addItems(["rutracker", "uztracker"]) + self.tracker_list.addItems(["rutracker", "uztracker", "m0nkrus"]) self.tracker_list.activated.connect(self.set_tracker) - if darkdetect.isDark(): - settings_action = QAction(QIcon("core/interface/assets/settings_dark.png"), "Settings", self) + settings_action = QAction(QIcon("core/interface/assets/settings_white.png"), "Settings", self) else: - settings_action = QAction(QIcon("core/interface/assets/settings.png"), "Settings", self) + settings_action = QAction(QIcon("core/interface/assets/settings_black.png"), "Settings", self) settings_action.triggered.connect(lambda: settings_dialog(self)) toolbar.addAction(settings_action) @@ -183,8 +244,8 @@ class MainWindow(QtWidgets.QMainWindow, QWidget): def show_empty_results(self, show: bool): if show: - self.softwareList.hide() + self.qtablewidget.hide() self.emptyResults.show() else: self.emptyResults.hide() - self.softwareList.show() + self.qtablewidget.show() diff --git a/core/interface/utils/searchhelper.py b/core/interface/utils/searchhelper.py index 73e2754..ac3d2bc 100644 --- a/core/interface/utils/searchhelper.py +++ b/core/interface/utils/searchhelper.py @@ -1,42 +1,85 @@ -import asyncio +from PySide6.QtWidgets import QTableWidgetItem 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 from core.utils.data.state import state def return_pressed(self): - search_text = self.searchbar.text() - if search_text == "": - if state.debug: - print("Error: Can't search for nothing") - return + search_text = self.searchbar.text() + if search_text == "": if state.debug: - print("User searched for:", search_text) - if state.tracker == "uztracker": - response = asyncio.run(scrape_uztracker(search_text)) - if state.tracker == "rutracker": - response = scrape_rutracker(search_text) + print("Error: Can't search for nothing") + return + if state.debug: + print("User searched for:", search_text) + + if state.tracker == "uztracker": + response = scrape_uztracker(search_text) if response: - if state.tracker == "uztracker": - state.post_titles, state.post_urls, = response - self.softwareList.clear() - if state.post_titles: - self.softwareList.addItems(state.post_titles) - self.post_author_list.addItems(state.post_author) - if state.tracker == "rutracker": - _, state.posts, _, _, cached = split_data(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: + if state.debug: + print(f"No Results for {search_text}") + self.qtablewidget.clear() + self.show_empty_results(True) + else: + if state.debug: + print(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 == []: if state.debug: - print(f"No Results found for \"{search_text}\"") - self.softwareList.clear() + print(f"No Results for {search_text}") + self.qtablewidget.clear() self.show_empty_results(True) else: state.post_titles, _, state.post_author = format_data(state.posts) self.show_empty_results(False) - self.post_author_list.clear() - self.post_author_list.addItems(state.post_author) - self.softwareList.clear() - self.softwareList.addItems(state.post_titles) + 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)) if state.debug == True: - print(f"Cached: {cached}") + print(f"Response Cached: {cached}") + else: + if state.debug: + print(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 == []: + if state.debug: + print(f"No Results for {search_text}") + self.qtablewidget.clear() + self.show_empty_results(True) + else: + state.post_titles, _, state.post_author = format_data(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)) diff --git a/core/network/aria2_wrapper.py b/core/network/aria2_wrapper.py index c3be92c..9bf5d8b 100644 --- a/core/network/aria2_wrapper.py +++ b/core/network/aria2_wrapper.py @@ -1,11 +1,16 @@ from .aria2_integration import run_aria2p +from core.utils.data.state import state def start_client(): global aria2 aria2 = run_aria2p() + if state.debug: + print("Started Aria2p") def add_magnet(uri): aria2.add_magnet(uri) + if state.debug: + print("Magnet URI added to Aria2") diff --git a/core/utils/config/config.py b/core/utils/config/config.py index 1bc75f7..7da80cf 100644 --- a/core/utils/config/config.py +++ b/core/utils/config/config.py @@ -3,11 +3,11 @@ import platform import configparser from core.utils.data.state import state - def create_config(): config = configparser.ConfigParser() - config["General"] = {"debug": True, "api_url": f"{state.api_url}", "aria2_threads": f"{state.aria2_threads}", "download_path": f"{state.download_path}", f"speed_limit": f"{state.speed_limit}"} + config["General"] = {"debug": True, "api_url": f"{state.api_url}", "aria2_threads": f"{state.aria2_threads}", "download_path": f"{state.download_path}", f"speed_limit": f"{state.speed_limit}", + "ignore_updates": f"{state.ignore_updates}"} if platform.system() == "Windows": config_dir = os.environ.get("APPDATA", os.path.expanduser("~\\AppData\\Roaming")) @@ -42,4 +42,4 @@ def read_config(): state.aria2_threads = config.getint("General", "aria2_threads") state.download_path = config.get("General", "download_path") state.speed_limit = config.getint("General", "speed_limit") - + state.ignore_updates = config.getboolean("General", "ignore_updates") diff --git a/core/utils/config/settings.py b/core/utils/config/settings.py index 2095581..25bb68a 100644 --- a/core/utils/config/settings.py +++ b/core/utils/config/settings.py @@ -1,22 +1,27 @@ from core.utils.data.state import state +from core.utils.general.shutdown import kill_aria2server from .config import create_config def restart_aria2c(): import main # had to do this because of circle import :( import signal import atexit - main.kill_aria2server() + kill_aria2server() state.aria2process.wait() state.aria2process = main.run_aria2server() signal.signal(signal.SIGINT, main.keyboardinterrupthandler) - atexit.unregister(main.kill_aria2server) - atexit.register(main.kill_aria2server) + atexit.unregister(kill_aria2server) + atexit.register(kill_aria2server) -def save_settings(thread_count, close, apiurl, download_path, speed_limit): - state.aria2_threads = thread_count - state.api_url = apiurl - state.download_path = download_path - state.speed_limit = speed_limit +def save_settings(thread_count=None, close=lambda: None, apiurl=None, download_path=None, speed_limit=None): + if thread_count is not None: + state.aria2_threads = thread_count + if apiurl is not None: + state.api_url = apiurl + if download_path is not None: + state.download_path = download_path + if speed_limit is not None: + state.speed_limit = speed_limit create_config() restart_aria2c() diff --git a/core/utils/data/state.py b/core/utils/data/state.py index 633415e..19159fe 100644 --- a/core/utils/data/state.py +++ b/core/utils/data/state.py @@ -9,6 +9,8 @@ class AppState: post_urls: List[str] post_author: List[str] downloads: List[str] + version: str + ignore_updates: bool = False debug: bool = False tracker: str = "rutracker" api_url: str = "https://api.michijackson.xyz" @@ -19,4 +21,4 @@ class AppState: aria2p: Any = None aria2_threads: int = 4 -state = AppState(posts=[], post_titles=[], post_urls=[], post_author=[], downloads=[], download_path="") +state = AppState(posts=[], post_titles=[], post_urls=[], post_author=[], downloads=[], download_path="", version="") diff --git a/core/utils/data/tracker.py b/core/utils/data/tracker.py index 27e7f27..172788d 100644 --- a/core/utils/data/tracker.py +++ b/core/utils/data/tracker.py @@ -5,16 +5,27 @@ from core.utils.network.jsonhandler import format_data -def get_item_url(item, posts, post_titles): # oftwarelist currentitem, post list (dict), post titles list +def get_item_url(item, posts, post_titles): # softwarelist currentitem, post list (dict), post titles list post_index = post_titles.index(item) if 0 <= post_index < len(post_titles): if state.tracker == "uztracker": item = "https://uztracker.net/" + state.post_urls[post_index].lstrip("./") + if state.debug: + print("Found post URL: ", item) return item if state.tracker == "rutracker": item_dict = posts[post_index] _, post_links, _ = format_data([item_dict]) + if state.debug: + print("Found post URL: ", post_links[0]) return post_links[0] + if state.tracker == "m0nkrus": + item_dict = posts[post_index] + _, post_links, _ = format_data([item_dict]) + if state.debug: + print("Found post URL: ", post_links[0]) + return post_links[0] + return None @@ -22,6 +33,8 @@ def get_item_url(item, posts, post_titles): # oftwarelist currentitem, post list def get_magnet_link(post_url): try: response = requests.get(post_url) # eventually impl. cloudscraper + if state.debug: + print("Sent Request to retrieve Magnet Link...") response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') magnet_link = soup.find('a', href=lambda x: x and x.startswith('magnet:')) diff --git a/core/utils/general/shutdown.py b/core/utils/general/shutdown.py index 38bf9fe..7642e4f 100644 --- a/core/utils/general/shutdown.py +++ b/core/utils/general/shutdown.py @@ -22,7 +22,7 @@ def kill_aria2server(): if proc.name() == process: proc.kill() if state.debug: - print("Killed Aria2c") + print(f"Killed Aria2c (PID {proc.pid})") except psutil.NoSuchProcess: pass diff --git a/core/utils/network/download.py b/core/utils/network/download.py index 9c6f719..dbf7ef5 100644 --- a/core/utils/network/download.py +++ b/core/utils/network/download.py @@ -10,7 +10,7 @@ import threading def download_selected(item, posts, post_titles): if item is not None: if state.debug: - print(f"network {item.text()}") + print(f"Downloading {item.text()}") run_thread(threading.Thread(target=run_download, args=(item.text(), posts, post_titles))) else: diff --git a/core/utils/network/update_checker.py b/core/utils/network/update_checker.py new file mode 100644 index 0000000..24ac0b2 --- /dev/null +++ b/core/utils/network/update_checker.py @@ -0,0 +1,36 @@ +import requests +from core.utils.data.state import state + +def check_for_updates(): + url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases" + response = requests.get(url) + + if response.status_code != 200: + print(f"Failed to fetch releases: {response.status_code}") + exit(1) + + releases = response.json() + releases.sort(key=lambda r: r["published_at"], reverse=True) + + latest_release = releases[0] + latest_version = latest_release["name"] + assets = latest_release["assets"] + + + if state.version == "dev": + if state.debug: + print("Dev release detected, skipping version check") + return None, None + elif latest_version != state.version: + if state.debug: + print(f"New release available: {latest_version}") + if assets: + if state.debug: + print("Assets:") + for asset in assets: + print(f" - {asset['name']}: {asset['browser_download_url']}") + return assets, latest_version + else: + if state.debug: + print("Already up-to-date.") + return None, None diff --git a/main.py b/main.py index 7db412e..baab2d4 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ from core.interface.gui import MainWindow from core.utils.data.state import state from core.network.aria2_integration import aria2server from core.network.aria2_integration import send_notification -from core.utils.general.shutdown import kill_aria2server, closehelper, shutdown_event +from core.utils.general.shutdown import closehelper, shutdown_event from core.utils.general.wrappers import run_thread from core.utils.config.config import read_config from PySide6 import QtWidgets @@ -10,11 +10,13 @@ import qdarktheme import darkdetect import threading import signal +import argparse import sys -# Debug Output -state.debug = True +parser = argparse.ArgumentParser() +parser.add_argument("--debug", action="store_true") +args = parser.parse_args() def run_gui(): app = QtWidgets.QApplication([]) @@ -35,6 +37,7 @@ def keyboardinterrupthandler(signum, frame): if __name__ == "__main__": read_config() + state.debug = args.debug # override of read_config if state.debug: print("Starting Aria2 Server") state.aria2process = run_aria2server()