Compare commits

...

62 Commits

Author SHA1 Message Date
KeksNino a4c6c3381a install system dependencies on linux in CI workflow 2026-07-10 16:36:19 +02:00
Vxrtrauter 68a9e870cf feat: implement middleware for VST Search subdomain HTML rewriting 2026-07-06 23:25:52 +02:00
Vxrtrauter 02de32719d Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-07-06 23:20:54 +02:00
Vxrtrauter c5a78a2da3 feat: add VST middleware for dynamic HTML content rewriting 2026-07-06 23:20:51 +02:00
shayaa 32f02d856d Merge pull request #104 from KeksPirates/feat-website-vstsearch
Add VST search functionality
2026-07-06 23:12:36 +02:00
Vxrtrauter 4edb1baa0b add vst search 2026-07-06 23:11:18 +02:00
shayaa 1ffa1cacb9 Merge pull request #99 from KeksPirates/auto/sync-readme-dependencies
Sync README dependencies
2026-05-14 23:36:19 +02:00
Vxrtrauter 207eb70f11 Sync README dependencies 2026-05-14 21:10:34 +00:00
shayaa 64144e5ed7 Merge pull request #98 from KeksPirates/dependabot/pip/requests-2.34.2
build(deps): bump requests from 2.34.1 to 2.34.2
2026-05-14 23:10:21 +02:00
dependabot[bot] d05fae7e87 build(deps): bump requests from 2.34.1 to 2.34.2
Bumps [requests](https://github.com/psf/requests) from 2.34.1 to 2.34.2.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.34.1...v2.34.2)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.34.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 19:37:56 +00:00
shayaa 9abdec6c33 Merge pull request #97 from KeksPirates/auto/sync-readme-dependencies
Sync README dependencies
2026-05-13 22:08:47 +02:00
Vxrtrauter fb5eb61d05 Sync README dependencies 2026-05-13 20:07:34 +00:00
shayaa eff2164ecc Merge pull request #89 from KeksPirates/dependabot/pip/pyinstaller-6.20.0
build(deps): bump pyinstaller from 6.19.0 to 6.20.0
2026-05-13 22:07:23 +02:00
shayaa d8aa206ea2 Merge pull request #94 from KeksPirates/dependabot/pip/pyside6-essentials-6.11.1
build(deps): bump pyside6-essentials from 6.11.0 to 6.11.1
2026-05-13 22:07:00 +02:00
dependabot[bot] 6de74303ff build(deps): bump pyside6-essentials from 6.11.0 to 6.11.1
Bumps [pyside6-essentials](https://pyside.org) from 6.11.0 to 6.11.1.

---
updated-dependencies:
- dependency-name: pyside6-essentials
  dependency-version: 6.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-13 20:06:46 +00:00
shayaa 5eb6314cb2 Merge pull request #96 from KeksPirates/auto/sync-readme-dependencies
Sync README dependencies
2026-05-13 22:05:49 +02:00
Vxrtrauter 179dd0af83 Sync README dependencies 2026-05-13 20:05:32 +00:00
shayaa 365700900f Merge pull request #95 from KeksPirates/dependabot/pip/requests-2.34.1
build(deps): bump requests from 2.34.0 to 2.34.1
2026-05-13 22:05:03 +02:00
dependabot[bot] babbccdc9a build(deps): bump requests from 2.34.0 to 2.34.1
Bumps [requests](https://github.com/psf/requests) from 2.34.0 to 2.34.1.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.34.0...v2.34.1)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.34.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-13 19:47:10 +00:00
shayaa 367ffab86a Merge pull request #93 from KeksPirates/auto/sync-readme-dependencies
Sync README dependencies
2026-05-12 10:57:57 +02:00
Vxrtrauter 71bad60617 Sync README dependencies 2026-05-12 08:46:59 +00:00
shayaa 27758f794c Merge pull request #92 from KeksPirates/dependabot/pip/requests-2.34.0
build(deps): bump requests from 2.33.1 to 2.34.0
2026-05-12 10:46:50 +02:00
dependabot[bot] d82afe8577 build(deps): bump requests from 2.33.1 to 2.34.0
Bumps [requests](https://github.com/psf/requests) from 2.33.1 to 2.34.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.33.1...v2.34.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-12 02:25:02 +00:00
Vxrtrauter 8592b3058e Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-05-04 21:51:20 +02:00
Vxrtrauter 7a82e948b9 refactor: remove unused imports 2026-05-04 21:51:18 +02:00
shayaa 782b0b6bc9 Merge pull request #91 from KeksPirates/feat/bzzhr-impl
Add bzzhr implementation and improve file preallocation on Windows
2026-05-04 21:49:51 +02:00
KeksNino 71cc0d9f1f fix: workflows (forgot to git add again) 2026-05-04 20:58:38 +02:00
KeksNino 675dead1d9 fix: readd dependency installation step for linux in workflow
gets rid of pyinstaller warnings and is needed for future appimages
2026-05-04 20:47:05 +02:00
Vxrtrauter 64ca3c76a2 Fix: update buzzheavier link detection logic and improve filename extraction from headers 2026-05-04 19:07:42 +02:00
shayaa d378ee8258 Enhance macOS build process in create-release.yml
Added installation step for create-dmg on macOS and updated artifact handling for macOS builds.
2026-04-24 11:28:56 +02:00
shayaa e9b01426f8 Merge pull request #90 from KeksPirates/feat/crossplatform-updatecheck
Feat/crossplatform updatecheck
2026-04-24 10:37:40 +02:00
Vxrtrauter 631f6592ac Refactor: remove unused imports from update dialog 2026-04-24 10:36:14 +02:00
Vxrtrauter 468faff16b Refactor: streamline update notification logic and enhance settings dialog for cross-platform compatibility 2026-04-24 10:20:26 +02:00
shayaa 6b45c40aad Update GitHub Actions workflow for macOS builds 2026-04-24 09:17:42 +02:00
dependabot[bot] d6302fc43f build(deps): bump pyinstaller from 6.19.0 to 6.20.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.19.0 to 6.20.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.19.0...v6.20.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-version: 6.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 19:37:36 +00:00
shayaa e768c7518b Remove Activity section from README
Removed the Activity section and its associated analytics image from the README.
2026-04-18 19:01:34 +02:00
shayaa 49c390131a Merge pull request #87 from KeksPirates/dependabot/github_actions/softprops/action-gh-release-3
build(deps): bump softprops/action-gh-release from 2 to 3
2026-04-18 03:28:01 +02:00
shayaa 9bc0e22ff7 Merge pull request #88 from KeksPirates/refactor/stability
fix/refactor/Improve Stability
2026-04-17 14:57:48 +02:00
Vxrtrauter 982be09f6e Refactor: implement log emitter for improved logging and signal handling 2026-04-16 11:51:11 +02:00
Vxrtrauter d5582c5c38 Refactor: enhance download_update function with cancellation handling and error messaging 2026-04-15 14:18:36 +02:00
Vxrtrauter 392d2a6f03 Refactor: enhance AppDaemons with QThread for improved background task management 2026-04-14 14:40:19 +02:00
Vxrtrauter 7310049246 Refactor: notification handling and introduce AppDaemons for background tasks 2026-04-14 14:33:21 +02:00
dependabot[bot] 04aaff3b29 build(deps): bump softprops/action-gh-release from 2 to 3
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-13 20:41:00 +00:00
Vxrtrauter f5809c466a Add timeouts, error handling & logging
Add network timeouts and more robust error handling across scrapers/hosts, improve logging concurrency, and refine download handling:

- Hosts/sources: add request timeouts (15s) and exception handling to buzzheavier, gofile, steamrip, uztracker and rutracker; use urllib.parse.quote for search queries and better parse error checks.
- GoFile: use Session with try/finally, create guest account with timeout, validate API responses and close session.
- Context menu: import DirectDownloadHandle and handle stopping direct downloads separately from libtorrent; simplify file deletion with single try/catch; improve logging on exceptions.
- GUI/search flow: move UI state changes (clear table, disable searchbar, hide empty results) into MainWindow._start_search and reduce duplicated UI changes in run_search.
- Main: remove duplicate is_running assignment.
- Libtorrent: snapshot seeded magnets in update loop to avoid concurrent access; minor indentation change in add_seed.
- State/logging: add _log_lock to AppState; protect state.log_buffer with lock in consoleLog and flush_log_buffer and fix flush semantics.
- Updater: replace busy-wait with QEventLoop/QTimer polling for DirectDownloadHandle progress, stop timer and quit loop on completion/error, and remove unnecessary sleeps.

These changes aim to make network operations more reliable, prevent UI freezes, avoid race conditions when logging, and handle direct vs libtorrent downloads more cleanly.
2026-04-13 11:03:25 +02:00
Vxrtrauter 8f19703314 Merge branch 'main' of https://github.com/KeksPirates/SoftwareManager 2026-04-11 10:17:35 +02:00
Vxrtrauter 7d1f249797 Add sitemap.xml for improved SEO and site indexing 2026-04-11 10:17:34 +02:00
shayaa c20dc32de5 Merge pull request #85 from KeksPirates/enhance/docs
Add meta description and Android favicon
2026-04-11 10:09:54 +02:00
Vxrtrauter b2bc97a6af Add meta description and Android favicon
Add a meta description tag to improve SEO and provide a brief site summary, and add a 192x192 Android favicon link to support Android/home screen icons. This clarifies site metadata and improves mobile presentation.
2026-04-11 10:09:33 +02:00
shayaa 15a771fe9f Change runner to use matrix OS in GitHub Actions 2026-04-11 09:55:36 +02:00
shayaa 976d610baf Change runner to self-hosted for test workflow 2026-04-11 09:47:22 +02:00
shayaa 42616f996d Merge pull request #84 from KeksPirates/fix/steamrip-indexing
fix: steamrip provider indexing
2026-04-11 09:26:46 +02:00
Vxrtrauter 8601a1ed50 fix: Detect SteamRip hosts by URL
Replace brittle index-based host resolution in SteamripScraper with substring checks on the chosen download URL (buzzheavier, gofile, vikingfile, megadb). Add a console log for the resolved link and adjust returned values for gofile. Remove the unnecessary links.index lookup and its exception handling.
2026-04-11 09:25:32 +02:00
shayaa a885576d8a Merge pull request #83 from KeksPirates/auto/sync-readme-dependencies
Sync README dependencies
2026-04-11 00:04:32 +02:00
Vxrtrauter a8de8461bd Sync README dependencies 2026-04-10 22:03:40 +00:00
shayaa c1e4f3a688 Merge pull request #76 from KeksPirates/dependabot/pip/pyqtdarktheme-fork-2.3.6
build(deps): bump pyqtdarktheme-fork from 2.3.4 to 2.3.6
2026-04-11 00:03:30 +02:00
dependabot[bot] ca4020970a build(deps): bump pyqtdarktheme-fork from 2.3.4 to 2.3.6
Bumps [pyqtdarktheme-fork](https://github.com/5yutan5/PyQtDarkTheme) from 2.3.4 to 2.3.6.
- [Release notes](https://github.com/5yutan5/PyQtDarkTheme/releases)
- [Commits](https://github.com/5yutan5/PyQtDarkTheme/commits)

---
updated-dependencies:
- dependency-name: pyqtdarktheme-fork
  dependency-version: 2.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 21:06:14 +00:00
shayaa 0956317eb2 Merge pull request #75 from KeksPirates/dependabot/pip/psutil-7.2.2
build(deps): bump psutil from 7.1.0 to 7.2.2
2026-04-10 23:05:07 +02:00
shayaa eed4af7705 Merge pull request #74 from KeksPirates/dependabot/github_actions/peter-evans/create-pull-request-8
build(deps): bump peter-evans/create-pull-request from 7 to 8
2026-04-10 23:04:16 +02:00
Vxrtrauter 02bb77ea1e fix: set default accent color in AppState to #fda7d7 2026-04-10 20:15:58 +02:00
shayaa 204ef0f233 Fix Workflow 2026-04-10 20:00:04 +02:00
dependabot[bot] 82a2f2d4d7 build(deps): bump psutil from 7.1.0 to 7.2.2
Bumps [psutil](https://github.com/giampaolo/psutil) from 7.1.0 to 7.2.2.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/docs/changelog.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/v7.1.0...v7.2.2)

---
updated-dependencies:
- dependency-name: psutil
  dependency-version: 7.2.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-09 22:11:56 +00:00
dependabot[bot] be1c9bfe45 build(deps): bump peter-evans/create-pull-request from 7 to 8
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7 to 8.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v7...v8)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-08 23:10:17 +00:00
30 changed files with 590 additions and 221 deletions
+38 -9
View File
@@ -46,6 +46,21 @@ jobs:
version: "0.9.7" version: "0.9.7"
enable-cache: true enable-cache: true
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libxcb-xinerama0 \
libxkbcommon-x11-0 \
libxcb-cursor0 \
libxcb-keysyms1 \
libxcb-render-util0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-shape0 \
libxcb-util1
- name: Install Python dependencies - name: Install Python dependencies
run: | run: |
uv pip install -r requirements.txt uv pip install -r requirements.txt
@@ -141,7 +156,7 @@ jobs:
if: runner.os == 'macOS' if: runner.os == 'macOS'
shell: bash shell: bash
run: | run: |
pyinstaller --onefile \ pyinstaller --windowed \
--add-data "build_info.json:." \ --add-data "build_info.json:." \
--exclude-module PyQt6 \ --exclude-module PyQt6 \
--exclude-module PyQt5 \ --exclude-module PyQt5 \
@@ -153,6 +168,10 @@ jobs:
--name ${{ env.APP_NAME }} \ --name ${{ env.APP_NAME }} \
src/main.py src/main.py
- name: Install create-dmg (macOS)
if: runner.os == 'macOS'
run: brew install create-dmg
- name: Rename artifact - name: Rename artifact
shell: bash shell: bash
run: | run: |
@@ -160,9 +179,20 @@ jobs:
if [ "$RUNNER_OS" == "Windows" ]; then if [ "$RUNNER_OS" == "Windows" ]; then
mv nuitka-build/main.dist dist-final/${{ env.APP_NAME }} mv nuitka-build/main.dist dist-final/${{ env.APP_NAME }}
ls -la dist-final/${{ env.APP_NAME }}/ ls -la dist-final/${{ env.APP_NAME }}/
else elif [ "$RUNNER_OS" == "Linux" ]; then
chmod +x dist/${{ env.APP_NAME }} chmod +x dist/${{ env.APP_NAME }}
mv dist/${{ env.APP_NAME }} dist-final/${{ matrix.artifact_name }} mv dist/${{ env.APP_NAME }} dist-final/${{ matrix.artifact_name }}
elif [ "$RUNNER_OS" == "macOS" ]; then
create-dmg \
--volname "${{ env.APP_NAME }}" \
--window-pos 200 120 \
--window-size 600 300 \
--icon-size 100 \
--icon "${{ env.APP_NAME }}.app" 150 150 \
--hide-extension "${{ env.APP_NAME }}.app" \
--app-drop-link 450 150 \
"dist-final/${{ matrix.artifact_name }}.dmg" \
"dist/"
fi fi
ls -la dist-final/ ls -la dist-final/
@@ -203,7 +233,7 @@ jobs:
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: ${{ matrix.os }}-build name: ${{ matrix.os }}-build
path: dist-final/${{ matrix.artifact_name }} path: dist-final/${{ matrix.artifact_name }}*
retention-days: 1 retention-days: 1
release: release:
@@ -225,8 +255,8 @@ jobs:
cp artifacts/ubuntu-22.04-build/SoftwareManager-stable-*-linux release/SoftwareManager-stable-${SHORT_SHA}-linux cp artifacts/ubuntu-22.04-build/SoftwareManager-stable-*-linux release/SoftwareManager-stable-${SHORT_SHA}-linux
cp artifacts/windows-latest-build/SoftwareManager-stable-*-windows.zip release/SoftwareManager-stable-${SHORT_SHA}-windows.zip cp artifacts/windows-latest-build/SoftwareManager-stable-*-windows.zip release/SoftwareManager-stable-${SHORT_SHA}-windows.zip
cp artifacts/windows-latest-build/SoftwareManager-stable-*-windows-setup.exe release/SoftwareManager-stable-${SHORT_SHA}-windows-setup.exe cp artifacts/windows-latest-build/SoftwareManager-stable-*-windows-setup.exe release/SoftwareManager-stable-${SHORT_SHA}-windows-setup.exe
cp artifacts/macos-latest-build/SoftwareManager-stable-*-macos release/SoftwareManager-stable-${SHORT_SHA}-macos cp artifacts/macos-latest-build/SoftwareManager-stable-*-macos.dmg release/SoftwareManager-stable-${SHORT_SHA}-macos.dmg
chmod +x release/SoftwareManager-stable-*-linux release/SoftwareManager-stable-*-macos chmod +x release/SoftwareManager-stable-*-linux
ls -la release/ ls -la release/
- name: Generate checksums - name: Generate checksums
@@ -236,8 +266,9 @@ jobs:
cat SHA256SUMS.txt cat SHA256SUMS.txt
- name: Create Release (Public) - name: Create Release (Public)
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v3
with: with:
token: ${{ secrets.RELEASE_TOKEN }}
tag_name: ${{ needs.build.outputs.version }} tag_name: ${{ needs.build.outputs.version }}
name: ${{ needs.build.outputs.version }} name: ${{ needs.build.outputs.version }}
generate_release_notes: true generate_release_notes: true
@@ -245,7 +276,5 @@ jobs:
release/SoftwareManager-stable-${{ needs.build.outputs.short_sha }}-linux release/SoftwareManager-stable-${{ needs.build.outputs.short_sha }}-linux
release/SoftwareManager-stable-${{ needs.build.outputs.short_sha }}-windows.zip release/SoftwareManager-stable-${{ needs.build.outputs.short_sha }}-windows.zip
release/SoftwareManager-stable-${{ needs.build.outputs.short_sha }}-windows-setup.exe release/SoftwareManager-stable-${{ needs.build.outputs.short_sha }}-windows-setup.exe
release/SoftwareManager-stable-${{ needs.build.outputs.short_sha }}-macos release/SoftwareManager-stable-${{ needs.build.outputs.short_sha }}-macos.dmg
release/SHA256SUMS.txt release/SHA256SUMS.txt
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
+26 -2
View File
@@ -51,6 +51,21 @@ jobs:
version: "0.9.7" version: "0.9.7"
enable-cache: true enable-cache: true
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libxcb-xinerama0 \
libxkbcommon-x11-0 \
libxcb-cursor0 \
libxcb-keysyms1 \
libxcb-render-util0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-shape0 \
libxcb-util1
- name: Install Python dependencies - name: Install Python dependencies
run: | run: |
uv pip install -r requirements.txt uv pip install -r requirements.txt
@@ -58,6 +73,15 @@ jobs:
env: env:
UV_SYSTEM_PYTHON: 1 UV_SYSTEM_PYTHON: 1
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libxcb-cursor0 libxcb-render-util0 libxcb-xkb1 \
libxcb-icccm4 libxcb-keysyms1 libxcb-image0 \
libxcb-shape0 libxcb-util1 libxkbcommon-x11-0
- name: Install Nuitka (Windows only) - name: Install Nuitka (Windows only)
if: runner.os == 'Windows' if: runner.os == 'Windows'
run: uv pip install nuitka run: uv pip install nuitka
@@ -266,7 +290,7 @@ jobs:
cat release_notes.md cat release_notes.md
- name: Create Release (Public) - name: Create Release (Public)
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v3
with: with:
tag_name: ${{ needs.build.outputs.version }} tag_name: ${{ needs.build.outputs.version }}
name: ${{ needs.build.outputs.version }} name: ${{ needs.build.outputs.version }}
@@ -278,4 +302,4 @@ jobs:
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-macos release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-macos
release/SHA256SUMS.txt release/SHA256SUMS.txt
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -29,7 +29,7 @@ jobs:
run: python scripts/sync_readme_dependencies.py run: python scripts/sync_readme_dependencies.py
- name: Create pull request - name: Create pull request
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v8
with: with:
commit-message: "Sync README dependencies" commit-message: "Sync README dependencies"
branch: auto/sync-readme-dependencies branch: auto/sync-readme-dependencies
+34 -4
View File
@@ -14,7 +14,7 @@ jobs:
build: build:
name: Build ${{ matrix.os }} name: Build ${{ matrix.os }}
strategy: strategy:
fail-fast: false # keep other platforms building even if one fails fail-fast: false
matrix: matrix:
include: include:
- os: ubuntu-22.04 - os: ubuntu-22.04
@@ -41,6 +41,21 @@ jobs:
version: "0.9.7" version: "0.9.7"
enable-cache: true enable-cache: true
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libxcb-xinerama0 \
libxkbcommon-x11-0 \
libxcb-cursor0 \
libxcb-keysyms1 \
libxcb-render-util0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-shape0 \
libxcb-util1
- name: Install Python dependencies - name: Install Python dependencies
run: | run: |
uv pip install -r requirements.txt uv pip install -r requirements.txt
@@ -138,7 +153,7 @@ jobs:
if: runner.os == 'macOS' if: runner.os == 'macOS'
shell: bash shell: bash
run: | run: |
pyinstaller --onefile \ pyinstaller --windowed \
--add-data "build_info.json:." \ --add-data "build_info.json:." \
--exclude-module PyQt6 \ --exclude-module PyQt6 \
--exclude-module PyQt5 \ --exclude-module PyQt5 \
@@ -150,15 +165,30 @@ jobs:
--name ${{ env.APP_NAME }} \ --name ${{ env.APP_NAME }} \
src/main.py src/main.py
- name: Install create-dmg (macOS)
if: runner.os == 'macOS'
run: brew install create-dmg
- name: Stage artifact - name: Stage artifact
shell: bash shell: bash
run: | run: |
mkdir -p dist-final mkdir -p dist-final
if [ "$RUNNER_OS" == "Windows" ]; then if [ "$RUNNER_OS" == "Windows" ]; then
mv nuitka-build/main.dist dist-final/${{ env.APP_NAME }} mv nuitka-build/main.dist dist-final/${{ env.APP_NAME }}
else elif [ "$RUNNER_OS" == "Linux" ]; then
chmod +x dist/${{ env.APP_NAME }} chmod +x dist/${{ env.APP_NAME }}
mv dist/${{ env.APP_NAME }} dist-final/${{ matrix.artifact_name }} mv dist/${{ env.APP_NAME }} dist-final/${{ matrix.artifact_name }}
elif [ "$RUNNER_OS" == "macOS" ]; then
create-dmg \
--volname "${{ env.APP_NAME }} Test" \
--window-pos 200 120 \
--window-size 600 300 \
--icon-size 100 \
--icon "${{ env.APP_NAME }}.app" 150 150 \
--hide-extension "${{ env.APP_NAME }}.app" \
--app-drop-link 450 150 \
"dist-final/${{ matrix.artifact_name }}.dmg" \
"dist/"
fi fi
ls -la dist-final/ ls -la dist-final/
@@ -181,5 +211,5 @@ jobs:
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: ${{ matrix.os }}-build name: ${{ matrix.os }}-build
path: dist-final/${{ matrix.artifact_name }} path: dist-final/${{ matrix.artifact_name }}*
retention-days: 7 retention-days: 7
+1
View File
@@ -143,6 +143,7 @@ venv/
ENV/ ENV/
env.bak/ env.bak/
venv.bak/ venv.bak/
.python-version
# Spyder project settings # Spyder project settings
.spyderproject .spyderproject
+5 -9
View File
@@ -41,24 +41,20 @@ SoftwareManager is a Python-based GUI tool that simplifies searching and downloa
<!-- python-dependencies:start --> <!-- python-dependencies:start -->
```text ```text
requests (2.33.1) requests (2.34.2)
PySide6-Essentials (6.11.0) PySide6-Essentials (6.11.1)
beautifulsoup4 (4.14.3) beautifulsoup4 (4.14.3)
darkdetect (0.7.1) darkdetect (0.7.1)
pyinstaller (6.19.0) pyinstaller (6.20.0)
PyQtDarkTheme-fork (2.3.4) PyQtDarkTheme-fork (2.3.6)
plyer (2.1.0) plyer (2.1.0)
psutil (7.1.0) psutil (7.2.2)
libtorrent (2.0.11) libtorrent (2.0.11)
libtorrent-windows-dll (0.0.3) libtorrent-windows-dll (0.0.3)
send2trash (2.1.0) send2trash (2.1.0)
``` ```
<!-- python-dependencies:end --> <!-- python-dependencies:end -->
## Activity
![Alt](https://repobeats.axiom.co/api/embed/3a61ea02ffdeb9ed5dd948d974a21e0d6cfae36c.svg "Repobeats analytics image")
## Star History ## Star History
<a href="https://www.star-history.com/#KeksPirates/SoftwareManager&type=date&legend=top-left"> <a href="https://www.star-history.com/#KeksPirates/SoftwareManager&type=date&legend=top-left">
+53 -5
View File
@@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Simply search and download software and games from various sources. Developed with ❤︎ by KeksPirates.">
<meta name="darkreader-lock"> <meta name="darkreader-lock">
<link rel="stylesheet" href="main.css"/> <link rel="stylesheet" href="main.css"/>
<title>SoftwareManager - Simply search and download software from various sources</title> <title>SoftwareManager - Simply search and download software from various sources</title>
@@ -10,6 +11,7 @@
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;700&display=swap" rel="stylesheet">
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
@@ -37,9 +39,10 @@
<h1>KeksPirates</h1> <h1>KeksPirates</h1>
</div> </div>
<nav class="tab"> <nav class="tab">
<a class="tablinks" onclick="openTab(event, 'start')" id="defaultOpen">Home</a> <a class="tablinks" onclick="openTab(event, 'start')" id="navHome">Home</a>
<a class="tablinks" onclick="openTab(event, 'api')">API</a> <a class="tablinks" onclick="openTab(event, 'api')" id="navApi">API</a>
<a class="tablinks" onclick="openTab(event, 'docs')">Docs</a> <a class="tablinks" onclick="openTab(event, 'docs')" id="navDocs">Docs</a>
<a class="tablinks" id="navVst" href="https://vstsearch.softwaremanager.xyz">VST Search</a>
<!-- <a class="tablinks" onclick="openTab(event, 'notfound')">404</a> --> <!-- <a class="tablinks" onclick="openTab(event, 'notfound')">404</a> -->
</nav> </nav>
</header> </header>
@@ -185,6 +188,17 @@
</div> </div>
</div> </div>
<div id="vstsearch" class="tabcontent">
<div class="cse-search">
<h3 class="vst-logo"><span style="color: #db65d5">VST Search</span></h3>
<div class="gcse-searchbox"></div>
<div class="gcse-searchresults"></div>
</div>
</div>
<div id="notfound" class="tabcontent"> <div id="notfound" class="tabcontent">
<div class="notfound-frame"> <div class="notfound-frame">
<h1 class="notfound-title">404</h1> <h1 class="notfound-title">404</h1>
@@ -201,6 +215,20 @@
</footer> </footer>
<script> <script>
const MAIN_ORIGIN = 'https://softwaremanager.xyz';
const VST_ORIGIN = 'https://vstsearch.softwaremanager.xyz';
const onVstDomain = window.location.hostname.startsWith('vstsearch.');
let cseLoaded = false;
function loadCSE() {
if (cseLoaded) return;
cseLoaded = true;
var s = document.createElement('script');
s.async = true;
s.src = 'https://cse.google.com/cse.js?cx=e7334cef72a8b42e7';
document.head.appendChild(s);
}
function openTab(evt, tabName, pushToHistory = true) { function openTab(evt, tabName, pushToHistory = true) {
var i, tabcontent, tablinks; var i, tabcontent, tablinks;
@@ -216,6 +244,8 @@
var target = document.getElementById(tabName); var target = document.getElementById(tabName);
target.style.display = "block"; target.style.display = "block";
if (tabName === 'vstsearch') loadCSE();
// restart animation // restart animation
target.classList.remove('tab-animate'); target.classList.remove('tab-animate');
void target.offsetWidth; void target.offsetWidth;
@@ -245,6 +275,10 @@
// Determine the correct tab immediately (before full load) to prevent flash // Determine the correct tab immediately (before full load) to prevent flash
(function() { (function() {
if (onVstDomain) {
document.getElementById('vstsearch').style.display = 'block';
return;
}
let currentPath = window.location.pathname; let currentPath = window.location.pathname;
let tabName = 'start'; let tabName = 'start';
if (currentPath === '/api') tabName = 'api'; if (currentPath === '/api') tabName = 'api';
@@ -255,9 +289,23 @@
})(); })();
window.onload = function() { window.onload = function() {
if (onVstDomain) {
document.getElementById('navHome').href = MAIN_ORIGIN + '/';
document.getElementById('navApi').href = MAIN_ORIGIN + '/api';
document.getElementById('navDocs').href = MAIN_ORIGIN + '/docs';
['navHome', 'navApi', 'navDocs'].forEach(function(id) {
document.getElementById(id).removeAttribute('onclick');
});
openTab(null, 'vstsearch', false);
document.getElementById('navVst').className += " active";
return;
}
let currentPath = window.location.pathname; let currentPath = window.location.pathname;
if (currentPath === '/api') { if (currentPath === '/vstsearch') {
window.location.replace(VST_ORIGIN);
} else if (currentPath === '/api') {
openTab(null, 'api', false); openTab(null, 'api', false);
} else if (currentPath === '/docs') { } else if (currentPath === '/docs') {
openTab(null, 'docs', false); openTab(null, 'docs', false);
+78
View File
@@ -378,6 +378,84 @@ pre.api-response {
font-size: 2rem; font-size: 2rem;
} }
.cse-search {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 70vh;
text-align: center;
}
.cse-search .vst-logo {
font-family: var(--main-font);
font-size: 3.5rem;
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 1.5rem;
user-select: none;
}
.cse-search .gsc-control-cse,
.cse-search .gsc-results-wrapper-overlay,
.cse-search .gsc-results-wrapper-visible {
background: transparent !important;
border: none !important;
}
.cse-search .gsc-input-box,
.cse-search .gsc-input-box.gsc-input-box-hover,
.cse-search .gsc-input-box.gsc-input-box-focus {
background: #1a1b1d !important;
border-color: #1a1b1d !important;
}
.cse-search .gsc-input,
.cse-search input.gsc-input,
.cse-search .gsib_a input.gsc-input {
background: transparent !important;
color: #fff !important;
}
.cse-search .gsib_a {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none'><path fill-rule='evenodd' clip-rule='evenodd' d='M14.76 13.27L20.49 19L19 20.49L13.27 14.76C12.2 15.53 10.91 16 9.5 16C5.91 16 3 13.09 3 9.5C3 5.91 5.91 3 9.5 3C13.09 3 16 5.91 16 9.5C16 10.91 15.53 12.2 14.76 13.27ZM9.5 5C7.01 5 5 7.01 5 9.5C5 11.99 7.01 14 9.5 14C11.99 14 14 11.99 14 9.5C14 7.01 11.99 5 9.5 5Z' fill='%23ffffff'/></svg>") !important;
}
.cse-search .gsc-search-button-v2 {
background: #db65d5 !important;
border-color: #db65d5 !important;
}
.cse-search .gsc-search-button-v2:hover {
background: #c94dc3 !important;
border-color: #c94dc3 !important;
}
.cse-search .gcsc-more-maybe-branding-root,
.gcsc-find-more-on-google {
display: none !important;
}
.gsc-completion-container,
.gssb_c, .gssb_e,
table.gssb_c, table.gssb_e {
background: #1a1b1d !important;
border-color: #333 !important;
color: #fff !important;
}
.gsc-completion-container td,
.gsc-completion-container .gsc-completion-suggestion,
.gssb_a, .gssb_a td, .gssb_i {
background: #1a1b1d !important;
color: #fff !important;
}
.gsc-completion-container .gsc-completion-selected,
.gsc-completion-container tr:hover td,
.gssb_a:hover td, .gssb_e-hover {
background: #2a2b2d !important;
}
@media(max-width: 560px) { @media(max-width: 560px) {
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://softwaremanager.xyz/</loc>
<priority>1.0</priority>
</url>
<url>
<loc>https://softwaremanager.xyz/api</loc>
<priority>0.8</priority>
</url>
<url>
<loc>https://softwaremanager.xyz/docs</loc>
<priority>0.8</priority>
</url>
</urlset>
+55
View File
@@ -0,0 +1,55 @@
// Cloudflare Pages middleware.
//
// Both softwaremanager.xyz and vstsearch.softwaremanager.xyz serve the same
// index.html. Crawlers (Discord, Twitter, etc.) read the raw HTML and do NOT
// run JavaScript, so we rewrite the <title> and Open Graph / Twitter meta tags
// server-side when the request is for the VST Search subdomain. This gives the
// subdomain its own link embed without a separate deploy.
const VST = {
title: "VST Search - SoftwareManager",
description: "Search for plugins easily",
url: "https://vstsearch.softwaremanager.xyz/",
};
class AttributeSetter {
constructor(attribute, value) {
this.attribute = attribute;
this.value = value;
}
element(element) {
element.setAttribute(this.attribute, this.value);
}
}
class ContentSetter {
constructor(value) {
this.value = value;
}
element(element) {
element.setInnerContent(this.value);
}
}
export async function onRequest(context) {
const { request, next } = context;
const response = await next();
const onVstDomain = new URL(request.url).hostname.startsWith("vstsearch.");
const contentType = response.headers.get("content-type") || "";
// Leave everything untouched on the main domain and for non-HTML responses.
if (!onVstDomain || !contentType.includes("text/html")) {
return response;
}
return new HTMLRewriter()
.on("title", new ContentSetter(VST.title))
.on("meta[name='description']", new AttributeSetter("content", VST.description))
.on("meta[property='og:title']", new AttributeSetter("content", VST.title))
.on("meta[property='og:description']", new AttributeSetter("content", VST.description))
.on("meta[property='og:url']", new AttributeSetter("content", VST.url))
.on("meta[name='twitter:title']", new AttributeSetter("content", VST.title))
.on("meta[name='twitter:description']", new AttributeSetter("content", VST.description))
.transform(response);
}
+5 -5
View File
@@ -1,11 +1,11 @@
requests==2.33.1 requests==2.34.2
PySide6-Essentials==6.11.0 PySide6-Essentials==6.11.1
beautifulsoup4==4.14.3 beautifulsoup4==4.14.3
darkdetect==0.7.1 darkdetect==0.7.1
pyinstaller==6.19.0 pyinstaller==6.20.0
PyQtDarkTheme-fork==2.3.4 PyQtDarkTheme-fork==2.3.6
plyer==2.1.0 plyer==2.1.0
psutil==7.1.0 psutil==7.2.2
libtorrent==2.0.11 libtorrent==2.0.11
libtorrent-windows-dll==0.0.3 libtorrent-windows-dll==0.0.3
send2trash==2.1.0 send2trash==2.1.0
+15 -7
View File
@@ -1,9 +1,13 @@
from utils.logging.logs import consoleLog
import requests import requests
def scrape_buzzheavier(url): def scrape_buzzheavier(url):
try:
response = requests.get(url) response = requests.get(url, timeout=15)
response.raise_for_status() response.raise_for_status()
except requests.RequestException as e:
consoleLog(f"Buzzheavier: Failed to fetch page - {e}")
return None
download_url = url + '/download' download_url = url + '/download'
@@ -12,7 +16,11 @@ def scrape_buzzheavier(url):
'hx-request': 'true', 'hx-request': 'true',
'referer': url 'referer': url
} }
head_response = requests.head(download_url, headers=headers, allow_redirects=False) try:
hx_redirect = head_response.headers.get('hx-redirect') head_response = requests.head(download_url, headers=headers, allow_redirects=False, timeout=15)
return hx_redirect hx_redirect = head_response.headers.get('hx-redirect')
return hx_redirect
except requests.RequestException as e:
consoleLog(f"Buzzheavier: Failed to fetch download URL - {e}")
return None
+35 -33
View File
@@ -23,42 +23,44 @@ def scrape_gofile(url):
content_id = match.group(1) content_id = match.group(1)
session = requests.Session() session = requests.Session()
try:
# Create a guest account
r = session.post(f"{_API_BASE}/accounts", headers={"User-Agent": _USER_AGENT}, timeout=15)
data = r.json()
if data.get("status") != "ok":
consoleLog("GoFile: Failed to create guest account")
return None
# Create a guest account account_token = data["data"]["token"]
r = session.post(f"{_API_BASE}/accounts", headers={"User-Agent": _USER_AGENT}) website_token = _generate_website_token(account_token)
data = r.json()
if data.get("status") != "ok":
consoleLog("GoFile: Failed to create guest account")
return None
account_token = data["data"]["token"] headers = {
website_token = _generate_website_token(account_token) "User-Agent": _USER_AGENT,
"Authorization": f"Bearer {account_token}",
"X-Website-Token": website_token,
"X-BL": "en-US",
"Referer": "https://gofile.io/",
"Origin": "https://gofile.io",
}
headers = { # Fetch folder contents
"User-Agent": _USER_AGENT, r = session.get(f"{_API_BASE}/contents/{content_id}", headers=headers, timeout=15)
"Authorization": f"Bearer {account_token}", data = r.json()
"X-Website-Token": website_token, if data.get("status") != "ok":
"X-BL": "en-US", consoleLog(f"GoFile: API error - {data.get('status')}")
"Referer": "https://gofile.io/", return None
"Origin": "https://gofile.io",
}
# Fetch folder contents children = data["data"].get("children", {})
r = session.get(f"{_API_BASE}/contents/{content_id}", headers=headers) if not children:
data = r.json() consoleLog("GoFile: No files found")
if data.get("status") != "ok": return None
consoleLog(f"GoFile: API error - {data.get('status')}")
return None
children = data["data"].get("children", {}) first_child = next(iter(children.values()))
if not children: link = first_child.get("link")
consoleLog("GoFile: No files found") if not link:
return None consoleLog("GoFile: No download link in response")
return None
first_child = next(iter(children.values())) return link, headers
link = first_child.get("link") finally:
if not link: session.close()
consoleLog("GoFile: No download link in response")
return None
return link, headers
+11 -2
View File
@@ -2,6 +2,7 @@ from utils.network.jsonhandler import split_data, format_data
from utils.data.tracker import get_magnet_link from utils.data.tracker import get_magnet_link
from utils.logging.logs import consoleLog from utils.logging.logs import consoleLog
from utils.data.state import state from utils.data.state import state
from urllib.parse import quote
from typing import Dict from typing import Dict
import requests import requests
@@ -11,10 +12,18 @@ class RutrackerScraper:
is_magnet = True is_magnet = True
def search(self, query): def search(self, query):
search = requests.get(f"{state.api_url}/search?q={query}", timeout=15) try:
search = requests.get(f"{state.api_url}/search?q={quote(query)}", timeout=15)
except requests.RequestException as e:
consoleLog(f"Failed to reach server: {e}")
return []
consoleLog("Sent request to server") consoleLog("Sent request to server")
if search: if search:
_, data, _, success, cached = split_data(search.text) try:
_, data, _, success, cached = split_data(search.text)
except (KeyError, ValueError) as e:
consoleLog(f"Failed to parse server response: {e}")
return []
if cached: if cached:
consoleLog("Server response cached") consoleLog("Server response cached")
if success: if success:
+7 -11
View File
@@ -27,7 +27,7 @@ class SteamripScraper:
try: try:
url = f"https://steamrip.com/games-list-page/" url = f"https://steamrip.com/games-list-page/"
response = requests.get(url) response = requests.get(url, timeout=15)
response.raise_for_status() response.raise_for_status()
text = response.text text = response.text
except requests.RequestException as e: except requests.RequestException as e:
@@ -61,7 +61,7 @@ class SteamripScraper:
def scrape_steamrip_game_downloads(self, url): def scrape_steamrip_game_downloads(self, url):
try: try:
response = requests.get(url) response = requests.get(url, timeout=15)
response.raise_for_status() response.raise_for_status()
except requests.RequestException as e: except requests.RequestException as e:
consoleLog(f"SteamRip: Failed to fetch game page - {e}") consoleLog(f"SteamRip: Failed to fetch game page - {e}")
@@ -110,23 +110,19 @@ class SteamripScraper:
consoleLog("SteamRip: No valid download links found") consoleLog("SteamRip: No valid download links found")
return None, None return None, None
try:
idx = links.index(best)
except ValueError:
consoleLog("SteamRip: Failed to resolve download host")
return None, None
try: try:
if idx == 0: consoleLog(f"Found download link: {best}")
if "buzzheavier" or "bzzhr" in best:
link = scrape_buzzheavier(best) link = scrape_buzzheavier(best)
return link return link
elif idx == 1: elif "gofile" in best:
link, headers = scrape_gofile(best) link, headers = scrape_gofile(best)
return link, headers return link, headers
elif idx == 2: elif "vikingfile" in best:
scrape_vikingfile(best) scrape_vikingfile(best)
return None, None return None, None
elif idx == 3: elif "megadb" in best:
scrape_megadb(best) scrape_megadb(best)
return None, None return None, None
else: else:
+1 -1
View File
@@ -19,7 +19,7 @@ class UztrackerScraper:
posts = [] posts = []
try: try:
response = requests.get(search_url) response = requests.get(search_url, timeout=15)
response.raise_for_status() response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser') soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_')) links = soup.find_all('tr', class_="tCenter hl-tr", id=lambda x: x and x.startswith('tor_'))
+19 -19
View File
@@ -1,4 +1,5 @@
from PySide6.QtCore import Qt, QPoint, Signal, QThread from PySide6.QtCore import Qt, QPoint, Signal, QThread
from network.direct_download.handle import DirectDownloadHandle
from utils.logging.logs import consoleLog, remove_download_log from utils.logging.logs import consoleLog, remove_download_log
from utils.network.download import download_selected from utils.network.download import download_selected
from utils.data.tracker import get_magnet_link from utils.data.tracker import get_magnet_link
@@ -11,7 +12,6 @@ import threading
import webbrowser import webbrowser
import subprocess import subprocess
import platform import platform
import time
import os import os
@@ -121,7 +121,12 @@ class ContextMenu_Downloads:
remove_download_log(magnet_link) remove_download_log(magnet_link)
if state.dl_session: if isinstance(magnetdl, DirectDownloadHandle):
try:
magnetdl.stop()
except Exception as e:
consoleLog(f"Exception while stopping direct download: {e}")
elif state.dl_session:
try: try:
state.dl_session.remove_torrent(magnetdl) state.dl_session.remove_torrent(magnetdl)
except Exception as e: except Exception as e:
@@ -179,29 +184,24 @@ class ContextMenu_Downloads:
remove_download_log(magnet_link) remove_download_log(magnet_link)
if state.dl_session: if isinstance(magnetdl, DirectDownloadHandle):
try:
magnetdl.stop()
except Exception as e:
consoleLog(f"Exception while stopping direct download: {e}")
elif state.dl_session:
try: try:
state.dl_session.remove_torrent(magnetdl) state.dl_session.remove_torrent(magnetdl)
time.sleep(0.5)
except Exception as e: except Exception as e:
consoleLog(f"Exception while removing download from LibTorrent: {e}") consoleLog(f"Exception while removing download from LibTorrent: {e}")
if download_path and os.path.exists(download_path): if download_path and os.path.exists(download_path):
last_error = None try:
for attempt in range(3): if os.path.isfile(download_path) or os.path.isdir(download_path):
try: send2trash(download_path)
if os.path.isfile(download_path) or os.path.isdir(download_path): consoleLog(f"Deleted files for: {torrent_name}", True)
send2trash(download_path) except Exception as e:
consoleLog(f"Deleted files for: {torrent_name}", True) consoleLog(f"Error deleting files: {e}", True)
last_error = None
break
except Exception as e:
last_error = e
if attempt < 2:
time.sleep(0.5)
if last_error:
consoleLog(f"Error deleting files: {last_error}", True)
else: else:
consoleLog(f"Removed entry (files not found): {torrent_name}", True) consoleLog(f"Removed entry (files not found): {torrent_name}", True)
+2 -3
View File
@@ -79,9 +79,8 @@ def settings_dialog(self):
# Ignore Updates checkbox # Ignore Updates checkbox
update_checkbox_container, update_checkbox = create_widget(QCheckBox, "Ignore Updates: ") update_checkbox_container, update_checkbox = create_widget(QCheckBox, "Ignore Updates: ")
if platform.system() == "Windows": update_checkbox.setChecked(state.ignore_updates)
update_checkbox.setChecked(state.ignore_updates) update_checkbox.toggled.connect(lambda checked: setattr(state, 'ignore_updates', checked))
update_checkbox.toggled.connect(lambda checked: setattr(state, 'ignore_updates', checked))
# Autoresume Container Checkbox # Autoresume Container Checkbox
autoresume_container, autoresume_checkbox = create_widget(QCheckBox, "Auto-Resume Downloads: ") autoresume_container, autoresume_checkbox = create_widget(QCheckBox, "Auto-Resume Downloads: ")
+47 -13
View File
@@ -1,13 +1,17 @@
from PySide6.QtWidgets import QDialog, QMessageBox, QCheckBox
from utils.network.update_checker import get_updates from utils.network.update_checker import get_updates
from PySide6.QtWidgets import QDialog, QMessageBox
from utils.network.updater import download_update from utils.network.updater import download_update
from utils.config.config import create_config
from utils.logging.logs import consoleLog from utils.logging.logs import consoleLog
from utils.data.state import state from utils.data.state import state
from pathlib import Path from pathlib import Path
import webbrowser
import platform import platform
import json import json
import os import os
RELEASES_URL = "https://github.com/KeksPirates/SoftwareManager/releases/latest"
def _get_build_info_path() -> (Path | None): def _get_build_info_path() -> (Path | None):
current_dir = Path(__file__).resolve().parent current_dir = Path(__file__).resolve().parent
@@ -31,19 +35,49 @@ def get_version() -> None:
else: else:
consoleLog(f"Could not find build info file (Path: {build_info_path})") consoleLog(f"Could not find build info file (Path: {build_info_path})")
def _show_notify_dialog(latest: str) -> None:
msg = QMessageBox()
msg.setIcon(QMessageBox.Icon.Information)
msg.setWindowTitle("Update Available")
msg.setText(f"A new version is available: <b>{latest}</b>")
msg.setInformativeText("Visit the releases page to download it.")
dont_show = QCheckBox("Don't show again")
msg.setCheckBox(dont_show)
open_btn = msg.addButton("Open Releases Page", QMessageBox.ButtonRole.AcceptRole)
msg.addButton("Dismiss", QMessageBox.ButtonRole.RejectRole)
msg.exec()
if dont_show.isChecked():
state.ignore_updates = True
create_config()
if msg.clickedButton() == open_btn:
webbrowser.open(RELEASES_URL)
class UpdateDialog(QDialog): class UpdateDialog(QDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
if state.ignore_updates is False and platform.system() == "Windows": if state.ignore_updates:
return
assets, latest = get_updates() assets, latest = get_updates()
if assets != None: if assets is None:
msg = QMessageBox() return
msg.setIcon(QMessageBox.Icon.Information)
msg.setWindowTitle("Update Available") if platform.system() == "Windows":
msg.setText(f"A new version is available\n({latest})") msg = QMessageBox()
msg.setInformativeText("Press Ok to download.") msg.setIcon(QMessageBox.Icon.Information)
msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore) msg.setWindowTitle("Update Available")
response = msg.exec_() msg.setText(f"A new version is available\n({latest})")
if response == QMessageBox.StandardButton.Ok: msg.setInformativeText("Press Ok to download.")
download_update(assets) msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore)
response = msg.exec_()
if response == QMessageBox.StandardButton.Ok:
download_update(assets)
else:
_show_notify_dialog(latest)
+6 -9
View File
@@ -17,6 +17,7 @@ from interface.utils.searchhelper import run_search
from interface.utils.tabhelper import create_tab from interface.utils.tabhelper import create_tab
from utils.general.shutdown import closehelper from utils.general.shutdown import closehelper
from utils.general.wrappers import run_thread from utils.general.wrappers import run_thread
from utils.logging.logs import log_emitter
from interface.dialogs.image import Image from interface.dialogs.image import Image
from utils.data.state import state from utils.data.state import state
@@ -68,14 +69,12 @@ class CenteredDelegate(QStyledItemDelegate):
class MainWindow(QtWidgets.QMainWindow, QWidget): class MainWindow(QtWidgets.QMainWindow, QWidget):
eventFilter = eventFilter eventFilter = eventFilter
log_signal = Signal(str)
search_results_signal = Signal(list) search_results_signal = Signal(list)
_instance = None _instance = None
def __init__(self): def __init__(self):
super().__init__() super().__init__()
MainWindow._instance = self MainWindow._instance = self
self.log_signal.connect(self._on_log_signal)
self.search_results_signal.connect(self._on_search_results) self.search_results_signal.connect(self._on_search_results)
pixmap = QPixmap() pixmap = QPixmap()
@@ -132,6 +131,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.consoleLog = QTextEdit() self.consoleLog = QTextEdit()
self.statusbar = QStatusBar() self.statusbar = QStatusBar()
log_emitter.new_log.connect(self._on_log_signal)
flush_log_buffer() flush_log_buffer()
self._tracker_elided_delegate = ElidedItemDelegate(lambda: self._tracker_hovered_row, self) self._tracker_elided_delegate = ElidedItemDelegate(lambda: self._tracker_hovered_row, self)
@@ -307,6 +307,10 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
table.setItemDelegateForColumn(i, self._tracker_hover_delegate) table.setItemDelegateForColumn(i, self._tracker_hover_delegate)
def _start_search(self): def _start_search(self):
self.searchbar.setEnabled(False)
state.trackertable.setRowCount(0)
self.show_empty_results(False)
def _search_thread(): def _search_thread():
try: try:
run_search(self) run_search(self)
@@ -315,13 +319,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.search_results_signal.emit([]) self.search_results_signal.emit([])
run_thread(threading.Thread(target=_search_thread)) run_thread(threading.Thread(target=_search_thread))
@staticmethod
def add_log(text):
if hasattr(MainWindow, "_instance") and MainWindow._instance:
MainWindow._instance.log_signal.emit(text)
return True
return False
def _on_log_signal(self, text): def _on_log_signal(self, text):
if hasattr(self, 'consoleLog'): if hasattr(self, 'consoleLog'):
self.consoleLog.append(text) self.consoleLog.append(text)
+2 -6
View File
@@ -6,9 +6,6 @@ for scraper in SCRAPERS:
state.trackers[scraper.name] = scraper state.trackers[scraper.name] = scraper
def run_search(self) -> None: def run_search(self) -> None:
state.trackertable.setRowCount(0)
self.show_empty_results(False)
search_text = self.searchbar.text() search_text = self.searchbar.text()
# Check for empty search # Check for empty search
if search_text == "": if search_text == "":
@@ -16,12 +13,11 @@ def run_search(self) -> None:
return return
state.posts = [] state.posts = []
self.searchbar.setEnabled(False)
consoleLog(f"User searched for: {search_text}") consoleLog(f"User searched for: {search_text}")
# Get current tracker and call its search function # Get current tracker and call its search function
scraper = state.trackers[state.currenttracker] scraper = state.trackers[state.currenttracker]
state.posts = scraper.search(search_text) state.posts = scraper.search(search_text)
# Update GUI headers to match the current scraper # Update GUI headers to match the current scraper
self.search_results_signal.emit(scraper.headers) self.search_results_signal.emit(scraper.headers)
+8 -6
View File
@@ -1,4 +1,3 @@
from network.libtorrent_misc import send_notification, update_log, check_deleted_files
from utils.logging.loghandler import split_data, check_completed, check_downloads from utils.logging.loghandler import split_data, check_completed, check_downloads
from network.interface import list_interfaces, init_interfaces from network.interface import list_interfaces, init_interfaces
from PySide6.QtNetwork import QLocalServer, QLocalSocket from PySide6.QtNetwork import QLocalServer, QLocalSocket
@@ -9,12 +8,13 @@ from utils.general.shutdown import closehelper
from utils.general.wrappers import run_thread from utils.general.wrappers import run_thread
from interface.assets.base64_icons import logo_base64 from interface.assets.base64_icons import logo_base64
from PySide6.QtWidgets import QSystemTrayIcon, QMenu from PySide6.QtWidgets import QSystemTrayIcon, QMenu
from network.libtorrent_misc import AppDaemons
from utils.general.shutdown import force_exit from utils.general.shutdown import force_exit
from utils.config.config import read_config from utils.config.config import read_config
from PySide6.QtGui import QAction, QPixmap from PySide6.QtGui import QAction, QPixmap
from utils.logging.logs import consoleLog from utils.logging.logs import consoleLog
from interface.gui import MainWindow from interface.gui import MainWindow
from PySide6.QtCore import Qt, QObject from PySide6.QtCore import Qt, QObject, QThread
from utils.data.state import state from utils.data.state import state
from PySide6 import QtWidgets from PySide6 import QtWidgets
import qdarktheme import qdarktheme
@@ -45,7 +45,6 @@ class SingleInstance(QObject):
self.server.listen(SERVER_NAME) self.server.listen(SERVER_NAME)
self.server.newConnection.connect(self.handle_connection) self.server.newConnection.connect(self.handle_connection)
self.is_running = False self.is_running = False
self.is_running = False
def handle_connection(self): def handle_connection(self):
socket = self.server.nextPendingConnection() socket = self.server.nextPendingConnection()
@@ -143,9 +142,12 @@ def main():
init_interfaces() init_interfaces()
consoleLog(f"Current Bound: {state.bound_interface}") consoleLog(f"Current Bound: {state.bound_interface}")
# Start background daemon threads # Start background daemon threads
run_thread(threading.Thread(target=send_notification, args=(state.shutdown_event,), daemon=True)) app._daemon_thread = QThread()
run_thread(threading.Thread(target=update_log, args=(state.shutdown_event,), daemon=True)) app._daemons = AppDaemons()
run_thread(threading.Thread(target=check_deleted_files, args=(state.shutdown_event,), daemon=True)) app._daemons.moveToThread(app._daemon_thread)
app._daemon_thread.started.connect(app._daemons.start_all)
app.aboutToQuit.connect(app._daemon_thread.quit)
app._daemon_thread.start()
# Start background non-daemon threads # Start background non-daemon threads
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume))) run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
run_thread(threading.Thread(target=check_downloads, args=(downloads,))) run_thread(threading.Thread(target=check_downloads, args=(downloads,)))
+3 -1
View File
@@ -20,12 +20,14 @@ def add_direct_download(url: str, title: str, dl_path: Optional[str] = None, hea
consoleLog(f"Download already active: {title}") consoleLog(f"Download already active: {title}")
return return
filename = ( raw_filename = (
detect_filename_from_headers(url, DirectDownloadHandle.USER_AGENT) detect_filename_from_headers(url, DirectDownloadHandle.USER_AGENT)
or extract_filename_from_url(url) or extract_filename_from_url(url)
or sanitize_filename(title) + ".zip" or sanitize_filename(title) + ".zip"
) )
filename = sanitize_filename(raw_filename)
handle = DirectDownloadHandle(url, filename, dl_path, headers, single_threaded) handle = DirectDownloadHandle(url, filename, dl_path, headers, single_threaded)
with state.downloads_lock: with state.downloads_lock:
state.active_downloads[url] = handle state.active_downloads[url] = handle
+18 -1
View File
@@ -10,6 +10,7 @@ import requests
import hashlib import hashlib
import json import json
import time import time
import sys
import os import os
@@ -231,8 +232,24 @@ class DirectDownloadHandle:
def _preallocate_file(self, total_size: int): def _preallocate_file(self, total_size: int):
if os.path.exists(self._file_path) and os.path.getsize(self._file_path) == total_size: if os.path.exists(self._file_path) and os.path.getsize(self._file_path) == total_size:
return return
with open(self._file_path, "wb") as f: with open(self._file_path, "wb") as f:
f.truncate(total_size) if sys.platform == "win32":
import msvcrt
import ctypes
from ctypes.wintypes import DWORD
handle = msvcrt.get_osfhandle(f.fileno())
kernel32 = ctypes.windll.kernel32
bytes_returned = DWORD()
kernel32.DeviceIoControl(
handle, 590020, None, 0, None, 0, ctypes.byref(bytes_returned), None
)
kernel32.SetFilePointerEx(handle, ctypes.c_int64(total_size), None, 0)
kernel32.SetEndOfFile(handle)
else:
f.truncate(total_size)
def _compute_chunks(self, total_size: int) -> list[ChunkSpec]: def _compute_chunks(self, total_size: int) -> list[ChunkSpec]:
num_chunks = min(self.NUM_THREADS, max(1, total_size // self.MIN_CHUNK_SIZE)) num_chunks = min(self.NUM_THREADS, max(1, total_size // self.MIN_CHUNK_SIZE))
+1 -1
View File
@@ -34,7 +34,7 @@ def detect_filename_from_headers(url: str, user_agent: str) -> Optional[str]:
if "filename=" in cd: if "filename=" in cd:
parts = cd.split("filename=") parts = cd.split("filename=")
if len(parts) > 1: if len(parts) > 1:
fname = parts[-1].strip().strip('"').strip("'") fname = parts[1].split(";")[0].strip().strip('"').strip("'")
if fname: if fname:
return fname return fname
except Exception as e: except Exception as e:
+1 -1
View File
@@ -163,7 +163,7 @@ def add_seed(magnet_uri, file_path):
return False return False
with state.downloads_lock: with state.downloads_lock:
state.active_downloads[magnet_uri] = handle state.active_downloads[magnet_uri] = handle
state.seeded_magnets.add(magnet_uri) state.seeded_magnets.add(magnet_uri)
return True return True
+54 -49
View File
@@ -1,9 +1,9 @@
from utils.logging.logs import update_download_completed_by_hash from utils.logging.logs import update_download_completed_by_hash
from utils.logging.logs import consoleLog from utils.logging.logs import consoleLog
from PySide6.QtCore import QObject, QTimer
from utils.data.state import state from utils.data.state import state
from plyer import notification from plyer import notification
import libtorrent as lt import libtorrent as lt
import time
import os import os
@@ -19,55 +19,28 @@ def cleanup_session():
with state.downloads_lock: with state.downloads_lock:
state.active_downloads.clear() state.active_downloads.clear()
def send_notification(shutdown_event): class AppDaemons(QObject):
notified = set() def __init__(self):
while not shutdown_event.is_set(): super().__init__()
try:
with state.downloads_lock:
items = list(state.active_downloads.items())
for magnet_uri, magnetdl in items:
if isinstance(magnetdl, dict):
continue
status = magnetdl.status()
if status.state == lt.torrent_status.seeding and magnet_uri not in notified:
notification.notify(
title="Download finished",
message=f"{status.name} has finished downloading.",
timeout=4
)
notified.add(magnet_uri)
except Exception as e:
consoleLog(f"Exception while sending notification: {e}")
time.sleep(5)
def update_log(shutdown_event): self.notified_magnets = set()
updated = set() self.updated_magnets = set()
while not shutdown_event.is_set():
try:
with state.downloads_lock:
items = list(state.active_downloads.items())
for magnet_uri, magnetdl in items:
if isinstance(magnetdl, dict):
continue
status = magnetdl.status()
if status.state == lt.torrent_status.seeding and magnet_uri not in updated and magnet_uri not in state.seeded_magnets:
consoleLog(f"Marking {status.name} as completed")
if hasattr(status, 'info_hashes'):
info_hash = str(status.info_hashes.v1)
else:
info_hash = str(status.info_hash)
update_download_completed_by_hash(info_hash, True)
updated.add(magnet_uri)
except Exception as e:
consoleLog(f"Exception while updating log file: {e}")
time.sleep(5)
def check_deleted_files(shutdown_event): # Create Timers
while not shutdown_event.is_set(): self.deleted_files_timer = QTimer(self)
self.completed_downloads_timer = QTimer(self)
# Connect Timer Signals
self.deleted_files_timer.timeout.connect(self.check_deleted_files)
self.completed_downloads_timer.timeout.connect(self.check_completed_downloads)
def start_all(self):
consoleLog("Starting Timer: deleted_files")
self.deleted_files_timer.start(2000)
consoleLog("Starting Timer: completed_downloads")
self.completed_downloads_timer.start(5000)
def check_deleted_files(self):
try: try:
with state.downloads_lock: with state.downloads_lock:
items = list(state.active_downloads.items()) items = list(state.active_downloads.items())
@@ -87,4 +60,36 @@ def check_deleted_files(shutdown_event):
del state.active_downloads[magnet_uri] del state.active_downloads[magnet_uri]
except Exception as e: except Exception as e:
consoleLog(f"Exception while checking for file deletions: {e}") consoleLog(f"Exception while checking for file deletions: {e}")
time.sleep(5)
def check_completed_downloads(self):
try:
with state.downloads_lock:
items = list(state.active_downloads.items())
seeded = set(state.seeded_magnets)
for magnet_uri, magnetdl in items:
if isinstance(magnetdl, dict):
continue
status = magnetdl.status()
if status.state == lt.torrent_status.seeding:
if magnet_uri not in self.updated_magnets and magnet_uri not in seeded:
consoleLog(f"Marking {status.name} as completed")
if hasattr(status, 'info_hashes'):
info_hash = str(status.info_hashes.v1)
else:
info_hash = str(status.info_hash)
update_download_completed_by_hash(info_hash, True)
self.updated_magnets.add(magnet_uri)
if magnet_uri not in self.notified_magnets:
notification.notify(
title="Download finished",
message=f"{status.name} has finished downloading.",
timeout=4
)
self.notified_magnets.add(magnet_uri)
except Exception as e:
consoleLog(f"Exception while checking downloads: {e}")
+2 -1
View File
@@ -25,7 +25,7 @@ class AppState(QObject):
# GUI # GUI
self.window_transparency: bool = False self.window_transparency: bool = False
self.accent_color: str = "" self.accent_color: str = "#fda7d7"
self.trackertable: QTableWidget self.trackertable: QTableWidget
self.interfaces: List = [] self.interfaces: List = []
self.active_interfaces: List = [] self.active_interfaces: List = []
@@ -61,6 +61,7 @@ class AppState(QObject):
self.max_connections: int = 200 self.max_connections: int = 200
self.max_downloads: int = 10 self.max_downloads: int = 10
self.downloads_lock = threading.RLock() self.downloads_lock = threading.RLock()
self._log_lock = threading.Lock()
@property @property
def image_path(self) -> str: def image_path(self) -> str:
+14 -14
View File
@@ -1,4 +1,5 @@
from utils.data.models import Download, DownloadList from utils.data.models import Download, DownloadList
from PySide6.QtCore import QObject, Signal
from utils.data.state import state from utils.data.state import state
from dataclasses import asdict from dataclasses import asdict
from datetime import datetime from datetime import datetime
@@ -7,6 +8,10 @@ import time
import os import os
import re import re
class LogSignal(QObject):
new_log = Signal(str)
log_emitter = LogSignal()
def _downloads_file_path() -> str: def _downloads_file_path() -> str:
return os.path.join(state.settings_path, "downloads.json") return os.path.join(state.settings_path, "downloads.json")
@@ -187,27 +192,22 @@ def _update_download_completed_by_hash_inner(info_hash, completed) -> DownloadLi
def set_main_window(window): def set_main_window(window):
state.main_window = window state.main_window = window
def flush_log_buffer(): # credits to claude def flush_log_buffer():
if state.log_buffer: with state._log_lock:
try: for text in state.log_buffer:
from interface.gui import MainWindow log_emitter.new_log.emit(text)
for log_entry in state.log_buffer:
MainWindow.add_log(log_entry) state.log_buffer.clear()
state.log_buffer = []
except Exception as e:
consoleLog(f"Exception while flushing log buffer: {e}")
def consoleLog(text, printAnyways = False): def consoleLog(text, printAnyways = False):
now = datetime.now() now = datetime.now()
current_time = now.strftime("%H:%M:%S") current_time = now.strftime("%H:%M:%S")
formatted_text = f"[{current_time}] {text}" formatted_text = f"[{current_time}] {text}"
try: with state._log_lock:
from interface.gui import MainWindow
if not MainWindow.add_log(formatted_text):
state.log_buffer.append(formatted_text)
except Exception:
state.log_buffer.append(formatted_text) state.log_buffer.append(formatted_text)
log_emitter.new_log.emit(formatted_text)
if state.debug or printAnyways: if state.debug or printAnyways:
print(formatted_text) print(formatted_text)
+31 -8
View File
@@ -2,13 +2,12 @@ from network.direct_download.handle import DirectDownloadHandle
from utils.general.shutdown import closehelper from utils.general.shutdown import closehelper
from utils.logging.logs import consoleLog from utils.logging.logs import consoleLog
from utils.data.state import state from utils.data.state import state
from PySide6.QtCore import Qt from PySide6.QtCore import Qt, QTimer, QEventLoop
from PySide6 import QtWidgets from PySide6 import QtWidgets
import libtorrent as lt import libtorrent as lt
import subprocess import subprocess
import tempfile import tempfile
import hashlib import hashlib
import time
import sys import sys
import os import os
@@ -42,7 +41,6 @@ def download_update(assets: list):
progress = QtWidgets.QProgressDialog("Downloading update... (0.0 MB/s)", None, 0, 100) progress = QtWidgets.QProgressDialog("Downloading update... (0.0 MB/s)", None, 0, 100)
progress.setWindowTitle("Updating") progress.setWindowTitle("Updating")
progress.setWindowModality(Qt.WindowModality.ApplicationModal) progress.setWindowModality(Qt.WindowModality.ApplicationModal)
progress.setCancelButton(None)
progress.setMinimumDuration(0) progress.setMinimumDuration(0)
progress.setAutoClose(False) progress.setAutoClose(False)
progress.setAutoReset(False) progress.setAutoReset(False)
@@ -55,14 +53,19 @@ def download_update(assets: list):
handle = DirectDownloadHandle(url, filename, tempfile.gettempdir()) handle = DirectDownloadHandle(url, filename, tempfile.gettempdir())
handle.start() handle.start()
while True: loop = QEventLoop()
QtWidgets.QApplication.processEvents() timer = QTimer()
timer.setInterval(100)
def poll_progress():
status = handle.status() status = handle.status()
if status.error: if status.error:
state.down_speed_limit = original_limit state.down_speed_limit = original_limit
progress.close() progress.close()
consoleLog(f"Update download failed: {status.error}") consoleLog(f"Update download failed: {status.error}")
timer.stop()
loop.quit()
return return
if status.total_wanted > 0: if status.total_wanted > 0:
@@ -72,9 +75,25 @@ def download_update(assets: list):
progress.setLabelText(f"Downloading update... ({speed_mb:.1f} MB/s)") progress.setLabelText(f"Downloading update... ({speed_mb:.1f} MB/s)")
if status.state == lt.torrent_status.seeding: if status.state == lt.torrent_status.seeding:
break timer.stop()
loop.quit()
time.sleep(0.1) if progress.wasCanceled():
state.down_speed_limit = original_limit
consoleLog("Update download cancelled by user.")
timer.stop()
loop.quit()
return
timer.timeout.connect(poll_progress)
timer.start()
loop.exec()
if progress.wasCanceled():
return
if handle.status().error:
return
state.down_speed_limit = original_limit state.down_speed_limit = original_limit
@@ -84,6 +103,11 @@ def download_update(assets: list):
else: else:
progress.close() progress.close()
consoleLog("Error: Invalid file hash, file may be corrupted") consoleLog("Error: Invalid file hash, file may be corrupted")
QtWidgets.QMessageBox.critical(
None,
"Update Failed",
"Error: Invalid file hash, file may be corrupted"
)
sys.exit(0) sys.exit(0)
else: else:
consoleLog("Skipping hash verification (no hash found for release)") consoleLog("Skipping hash verification (no hash found for release)")
@@ -94,5 +118,4 @@ def download_update(assets: list):
closehelper() closehelper()
subprocess.Popen([installer_path, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/SP-", "/CLOSEAPPLICATIONS"]) subprocess.Popen([installer_path, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/SP-", "/CLOSEAPPLICATIONS"])
time.sleep(1)
os._exit(0) os._exit(0)