mirror of
https://github.com/KeksPirates/SoftwareManager.git
synced 2026-08-03 17:39:42 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a56969f19d | |||
| 5bb732072e | |||
| f50bfd0aa0 | |||
| 71adbfcd96 | |||
| 270ee027f7 | |||
| a443f64501 | |||
| 0e05f42741 |
@@ -125,8 +125,6 @@ jobs:
|
||||
--upx-binary="${{ env.UPX_BINARY }}" \
|
||||
--noinclude-qt-translations \
|
||||
--include-data-files=build_info.json=build_info.json \
|
||||
--include-data-files=build_info.json=src/build_info.json \
|
||||
--include-data-files=build_info.json=src/core/interface/build_info.json \
|
||||
--output-filename=${{ env.APP_NAME }}.exe \
|
||||
--output-dir=nuitka-build \
|
||||
src/main.py
|
||||
@@ -137,8 +135,6 @@ jobs:
|
||||
run: |
|
||||
pyinstaller --onefile --windowed \
|
||||
--add-data "build_info.json:." \
|
||||
--add-data "build_info.json:src/core" \
|
||||
--add-data "build_info.json:src/core/interface" \
|
||||
--exclude-module PyQt6 \
|
||||
--exclude-module PyQt5 \
|
||||
--hidden-import=backports.tarfile \
|
||||
@@ -155,8 +151,6 @@ jobs:
|
||||
run: |
|
||||
pyinstaller --onefile \
|
||||
--add-data "build_info.json:." \
|
||||
--add-data "build_info.json:src/core" \
|
||||
--add-data "build_info.json:src/core/interface" \
|
||||
--exclude-module PyQt6 \
|
||||
--exclude-module PyQt5 \
|
||||
--hidden-import=backports.tarfile \
|
||||
@@ -287,4 +281,4 @@ jobs:
|
||||
release/SoftwareManager-dev-${{ needs.build.outputs.short_sha }}-macos
|
||||
release/SHA256SUMS.txt
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,185 @@
|
||||
name: Build Executables (Test)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.12"
|
||||
APP_NAME: "SoftwareManager"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false # keep other platforms building even if one fails
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
artifact_name: SoftwareManager-test-${{ github.sha }}-linux
|
||||
- os: windows-latest
|
||||
artifact_name: SoftwareManager-test-${{ github.sha }}-windows
|
||||
- os: macos-latest
|
||||
artifact_name: SoftwareManager-test-${{ github.sha }}-macos
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.9.7"
|
||||
enable-cache: true
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
uv pip install -r requirements.txt
|
||||
uv pip install Pillow
|
||||
env:
|
||||
UV_SYSTEM_PYTHON: 1
|
||||
|
||||
- name: Install Nuitka (Windows only)
|
||||
if: runner.os == 'Windows'
|
||||
run: uv pip install nuitka
|
||||
env:
|
||||
UV_SYSTEM_PYTHON: 1
|
||||
|
||||
- name: Generate version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
SHORT_SHA="${{ github.sha }}"
|
||||
SHORT_SHA="${SHORT_SHA:0:7}"
|
||||
BRANCH="${{ github.head_ref || github.ref_name }}"
|
||||
BRANCH_SLUG="${BRANCH//\//-}" # replace slashes with dashes
|
||||
VERSION="${BRANCH_SLUG}-${SHORT_SHA}-test"
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
echo "Generated version: $VERSION"
|
||||
|
||||
- name: Inject build info
|
||||
shell: bash
|
||||
run: |
|
||||
cat > build_info.json << EOF
|
||||
{
|
||||
"version": "${{ env.VERSION }}"
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Install UPX (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
curl -sL https://github.com/upx/upx/releases/download/v5.1.0/upx-5.1.0-win64.zip -o upx.zip
|
||||
7z x upx.zip -oupx-dir
|
||||
UPX_DIR="$(pwd)/upx-dir/upx-5.1.0-win64"
|
||||
echo "$UPX_DIR" >> $GITHUB_PATH
|
||||
export PATH="$UPX_DIR:$PATH"
|
||||
echo "UPX_BINARY=$UPX_DIR/upx.exe" >> $GITHUB_ENV
|
||||
upx --version | head -1
|
||||
|
||||
- name: Convert icon for Windows
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
python -c "
|
||||
from PIL import Image
|
||||
img = Image.open('src/core/interface/assets/logo.png')
|
||||
img.save('app_icon.ico', format='ICO', sizes=[(256,256),(128,128),(64,64),(48,48),(32,32),(16,16)])
|
||||
print('Icon converted successfully')
|
||||
"
|
||||
|
||||
- name: Build with Nuitka (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
python -m nuitka \
|
||||
--standalone \
|
||||
--assume-yes-for-downloads \
|
||||
--windows-console-mode=disable \
|
||||
--windows-icon-from-ico=app_icon.ico \
|
||||
--enable-plugin=pyside6 \
|
||||
--enable-plugin=upx \
|
||||
--upx-binary="${{ env.UPX_BINARY }}" \
|
||||
--noinclude-qt-translations \
|
||||
--include-data-files=build_info.json=build_info.json \
|
||||
--output-filename=${{ env.APP_NAME }}.exe \
|
||||
--output-dir=nuitka-build \
|
||||
src/main.py
|
||||
|
||||
- name: Build with PyInstaller (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
pyinstaller --onefile --windowed \
|
||||
--add-data "build_info.json:." \
|
||||
--exclude-module PyQt6 \
|
||||
--exclude-module PyQt5 \
|
||||
--hidden-import=backports.tarfile \
|
||||
--hidden-import=backports \
|
||||
--hidden-import=jaraco.context \
|
||||
--hidden-import=jaraco.text \
|
||||
--hidden-import=jaraco.functools \
|
||||
--name ${{ env.APP_NAME }} \
|
||||
src/main.py
|
||||
|
||||
- name: Build with PyInstaller (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
pyinstaller --onefile \
|
||||
--add-data "build_info.json:." \
|
||||
--exclude-module PyQt6 \
|
||||
--exclude-module PyQt5 \
|
||||
--hidden-import=backports.tarfile \
|
||||
--hidden-import=backports \
|
||||
--hidden-import=jaraco.context \
|
||||
--hidden-import=jaraco.text \
|
||||
--hidden-import=jaraco.functools \
|
||||
--name ${{ env.APP_NAME }} \
|
||||
src/main.py
|
||||
|
||||
- name: Stage artifact
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p dist-final
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
mv nuitka-build/main.dist dist-final/${{ env.APP_NAME }}
|
||||
else
|
||||
chmod +x dist/${{ env.APP_NAME }}
|
||||
mv dist/${{ env.APP_NAME }} dist-final/${{ matrix.artifact_name }}
|
||||
fi
|
||||
ls -la dist-final/
|
||||
|
||||
- name: Create ZIP (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "dist-final/${{ env.APP_NAME }}/*" -DestinationPath "dist-final/${{ matrix.artifact_name }}.zip"
|
||||
|
||||
- name: Upload artifact (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-build
|
||||
path: dist-final/${{ matrix.artifact_name }}.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload artifact (Linux/macOS)
|
||||
if: runner.os != 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-build
|
||||
path: dist-final/${{ matrix.artifact_name }}
|
||||
retention-days: 7
|
||||
@@ -1,18 +1,35 @@
|
||||
from core.utils.network.update_checker import get_updates
|
||||
from core.utils.network.updater import download_update
|
||||
from PySide6.QtWidgets import QDialog, QMessageBox
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import json
|
||||
import os
|
||||
|
||||
def check_version():
|
||||
build_info_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "build_info.json")
|
||||
def _get_build_info_path() -> (Path | None):
|
||||
current_dir = Path(__file__).resolve().parent
|
||||
|
||||
for _ in range(6): # Climb max 6 directories
|
||||
file = current_dir / "build_info.json"
|
||||
if file.exists():
|
||||
return file
|
||||
# Go up one directory if file isn't found
|
||||
current_dir = current_dir.parent
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_version() -> None:
|
||||
# Get build info filepath
|
||||
build_info_path = _get_build_info_path()
|
||||
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")
|
||||
|
||||
else:
|
||||
consoleLog(f"Could not find build info file (Path: {build_info_path})")
|
||||
|
||||
class UpdateDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
|
||||
@@ -5,6 +5,7 @@ from core.interface.dialogs.downloadlist import _create_download_list
|
||||
from core.interface.assets.base64_icons import settings_white_base64
|
||||
from core.interface.assets.base64_icons import settings_black_base64
|
||||
from core.interface.dialogs.downloadlist import download_list_update
|
||||
from core.interface.dialogs.update import get_version, UpdateDialog
|
||||
from core.utils.logging.logs import consoleLog, flush_log_buffer
|
||||
from core.interface.dialogs.downloadmodel import DownloadModel
|
||||
from core.interface.utils.searchhelper import return_pressed
|
||||
@@ -18,9 +19,7 @@ from core.utils.general.wrappers import run_thread
|
||||
from core.interface.dialogs.image import Image
|
||||
from core.utils.data.state import state
|
||||
|
||||
import core.interface.dialogs.hoverrowdelegate
|
||||
import core.interface.dialogs.contextmenu
|
||||
import core.interface.dialogs.update
|
||||
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtCore import (
|
||||
@@ -77,7 +76,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
pixmap.loadFromData(image_data)
|
||||
self.setWindowIcon(QIcon(pixmap))
|
||||
|
||||
# set appid on windows
|
||||
# Set AppID on Windows
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
import ctypes
|
||||
@@ -86,23 +85,30 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
except Exception as e:
|
||||
consoleLog(f"Could not set app ID: {e}")
|
||||
|
||||
# Get current version
|
||||
# core.interface.dialogs.update
|
||||
get_version()
|
||||
UpdateDialog()
|
||||
|
||||
# Initialize Window
|
||||
self.setWindowTitle("Software Manager")
|
||||
self.setGeometry(100, 100, 800, 600)
|
||||
|
||||
self.controls = QWidget()
|
||||
self.controlsLayout = QVBoxLayout()
|
||||
|
||||
# Searchbar
|
||||
self.searchbar = QLineEdit()
|
||||
self.searchbar.setPlaceholderText("Search for software...")
|
||||
self.searchbar.setClearButtonEnabled(True)
|
||||
self.searchbar.setMinimumHeight(30)
|
||||
self.searchbar.returnPressed.connect(self._start_search)
|
||||
|
||||
# Download Button
|
||||
self.dlbutton = QtWidgets.QPushButton("Download")
|
||||
self.dlbutton.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
|
||||
# Dialog for empty results
|
||||
self.emptyResults = QLabel("No Results")
|
||||
self.emptyResults.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.emptyResults.hide()
|
||||
@@ -157,23 +163,32 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.horizontal_layout.addWidget(self.emptyResults, stretch=3)
|
||||
self.horizontal_layout.addWidget(state.trackertable)
|
||||
|
||||
# Tabs
|
||||
self.tab1 = create_tab("Search", self.searchbar, state.trackertable, self.tabs, self.dlbutton, self.horizontal_layout)
|
||||
self.tab2 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None)
|
||||
|
||||
# Corner Widget (Settings Button, Tracker list, Tab button container)
|
||||
self.corner_widget = QWidget()
|
||||
self.corner_layout = QHBoxLayout(self.corner_widget)
|
||||
self.corner_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Tracker list
|
||||
self.tracker_list = QComboBox()
|
||||
self.tracker_list.addItems(list(state.trackers.keys()))
|
||||
self.tracker_list.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.tracker_list.activated.connect(self.set_tracker)
|
||||
self.corner_layout.addWidget(self.tracker_list)
|
||||
|
||||
# Settings button
|
||||
self.settings_btn = QtWidgets.QToolButton()
|
||||
self.settings_btn.setIconSize(QSize(21, 21))
|
||||
self.settings_btn.setStyleSheet("background: transparent;")
|
||||
self.settings_btn.setToolTip("Settings")
|
||||
self.settings_btn.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.settings_btn.clicked.connect(lambda: settings_dialog(self))
|
||||
self.corner_layout.addWidget(self.settings_btn)
|
||||
|
||||
# Adjust theme based on system preferences
|
||||
if darkdetect.isDark():
|
||||
settings_white_pixmap = QPixmap()
|
||||
settings_white = base64.b64decode(settings_white_base64)
|
||||
@@ -185,11 +200,6 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
settings_black_pixmap.loadFromData(settings_black)
|
||||
self.settings_btn.setIcon(QIcon(settings_black_pixmap))
|
||||
|
||||
self.settings_btn.setToolTip("Settings")
|
||||
self.settings_btn.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.settings_btn.clicked.connect(lambda: settings_dialog(self))
|
||||
self.corner_layout.addWidget(self.settings_btn)
|
||||
|
||||
self.tab_wrapper = QWidget()
|
||||
self.tab_layout = QVBoxLayout()
|
||||
self.tab_layout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -221,6 +231,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
|
||||
self.version = QLabel(f"Version: {state.version}")
|
||||
self.statusbar.addPermanentWidget(self.version, Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
# (\u2193) Arrow down, (\u2191) Arrow up
|
||||
self.speed_label = QLabel("\u2193 0.0 kB/s \u2191 0.0 kB/s")
|
||||
self.speed_label.setStyleSheet("padding-right: 6px;")
|
||||
self.statusbar.addPermanentWidget(self.speed_label)
|
||||
|
||||
@@ -29,6 +29,7 @@ def get_active_interfaces():
|
||||
|
||||
|
||||
def list_interfaces() -> None:
|
||||
consoleLog("Fetching Network Interfaces...")
|
||||
addrs = psutil.net_if_addrs()
|
||||
stats = psutil.net_if_stats()
|
||||
|
||||
@@ -45,6 +46,7 @@ def list_interfaces() -> None:
|
||||
consoleLog(f"Found Interface: {interface} [{status}]")
|
||||
|
||||
def init_interfaces():
|
||||
consoleLog("Initializing Interface variables...")
|
||||
addrs = psutil.net_if_addrs()
|
||||
state.interfaces = list(addrs.keys())
|
||||
state.active_interfaces = get_active_interfaces()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from core.network.libtorrent_misc import cleanup_session
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.utils.data.state import state
|
||||
import os
|
||||
@@ -5,7 +6,6 @@ import os
|
||||
def closehelper():
|
||||
state.shutdown_event.set()
|
||||
try:
|
||||
from core.network.libtorrent_misc import cleanup_session
|
||||
cleanup_session()
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while cleaning up LibTorrent Session: {e}")
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
from core.utils.logging.logs import consoleLog
|
||||
|
||||
def run_thread(thread):
|
||||
thread.start()
|
||||
target_name = thread._target.__name__
|
||||
try:
|
||||
thread.start()
|
||||
consoleLog(f"Started Thread: {target_name}")
|
||||
except Exception as e:
|
||||
consoleLog(f"Exception while starting thread {target_name}: {e}")
|
||||
+13
-10
@@ -2,8 +2,10 @@ from core.network.libtorrent_misc import send_notification, update_log, check_de
|
||||
from core.utils.logging.loghandler import split_data, check_completed, check_downloads
|
||||
from core.network.interface import list_interfaces, init_interfaces
|
||||
from core.utils.logging.logs import get_download_logs
|
||||
from core.utils.logging.logs import set_main_window
|
||||
from core.utils.general.shutdown import closehelper
|
||||
from core.utils.general.wrappers import run_thread
|
||||
from core.utils.general.shutdown import force_exit
|
||||
from core.utils.config.config import read_config
|
||||
from core.utils.logging.logs import consoleLog
|
||||
from core.interface.gui import MainWindow
|
||||
@@ -22,44 +24,45 @@ def run_gui():
|
||||
qdarktheme.setup_theme("auto")
|
||||
widget = MainWindow()
|
||||
|
||||
# Check OS for window transparency compatibility and apply
|
||||
if state.window_transparency and platform.system() != "Windows":
|
||||
widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||
qdarktheme.setup_theme("auto", custom_colors={"background": "#00000000"})
|
||||
|
||||
from core.utils.logging.logs import set_main_window
|
||||
set_main_window(widget)
|
||||
widget.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
def keyboardinterrupthandler(signum, frame):
|
||||
closehelper()
|
||||
from core.utils.general.shutdown import force_exit
|
||||
force_exit()
|
||||
|
||||
|
||||
def main():
|
||||
# Begin counting startup time
|
||||
start_time = time.perf_counter()
|
||||
# Parse saved files
|
||||
read_config()
|
||||
logs = get_download_logs()
|
||||
_, downloads = split_data(logs)
|
||||
# Initialize Keyboardinterrupt signal
|
||||
signal.signal(signal.SIGINT, keyboardinterrupthandler)
|
||||
consoleLog("Starting SoftwareManager...")
|
||||
consoleLog("Fetching Network Interfaces...")
|
||||
# Get network interface info
|
||||
list_interfaces()
|
||||
consoleLog("Initializing Interface variables...")
|
||||
init_interfaces()
|
||||
consoleLog(f"Current Bound: {state.bound_interface}")
|
||||
# Start background daemon threads
|
||||
run_thread(threading.Thread(target=send_notification, args=(state.shutdown_event,), daemon=True))
|
||||
consoleLog("Started Thread: send_notification")
|
||||
run_thread(threading.Thread(target=update_log, args=(state.shutdown_event,), daemon=True))
|
||||
consoleLog("Started Thread: update_log")
|
||||
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
|
||||
consoleLog("Started Thread: check_completed")
|
||||
run_thread(threading.Thread(target=check_deleted_files, args=(state.shutdown_event,), daemon=True))
|
||||
consoleLog("Started Thread: check_deleted_files")
|
||||
# Start background non-daemon threads
|
||||
run_thread(threading.Thread(target=check_completed, args=(downloads, state.autoresume)))
|
||||
run_thread(threading.Thread(target=check_downloads, args=(downloads,)))
|
||||
consoleLog("Started Thread: check_downloads")
|
||||
# Finish counting startup time
|
||||
elapsed = time.perf_counter() - start_time
|
||||
consoleLog(f"Initialization completed in {elapsed:.2f}s. Launching GUI")
|
||||
# Launch GUI
|
||||
run_gui()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user