Compare commits

...

4 Commits

5 changed files with 142 additions and 77 deletions
+45 -19
View File
@@ -4,8 +4,9 @@ from core.utils.logging.logs import consoleLog
from core.interface.utils.tabhelper import general_tab
from core.interface.utils.tabhelper import paths_tab
from core.interface.utils.tabhelper import network_tab
from core.interface.utils.svghelper import svg_icon
from PySide6 import QtWidgets
from PySide6.QtCore import Qt
from PySide6.QtCore import Qt, QSize
from PySide6.QtWidgets import (
QLineEdit,
QPushButton,
@@ -22,6 +23,8 @@ from PySide6.QtWidgets import (
)
import platform
SVG_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2 6c0-1.1.9-2 2-2h5l2 2h7c1.1 0 2 .9 2 2v10c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6z" fill="{color}"/></svg>'
def settings_dialog(self):
@@ -113,7 +116,19 @@ def settings_dialog(self):
if dir_path:
download_path.setText(dir_path)
browse_button = QPushButton("📁")
browse_button = QPushButton()
browse_button.setFixedSize(36, 36)
browse_button.setIconSize(QSize(24, 24))
browse_button.setIcon(svg_icon(SVG_FOLDER, 24))
browse_button.setCursor(Qt.CursorShape.PointingHandCursor)
browse_button.setStyleSheet("""
QPushButton {
border: none;
background: transparent;
padding: 0px;
}
""")
download_path_layout.addWidget(browse_button)
browse_button.clicked.connect(browse_download_path)
@@ -121,24 +136,35 @@ def settings_dialog(self):
# IMAGE PATH #
###############
# image_path_container = QWidget()
# image_path_layout = QHBoxLayout()
image_path_container = QWidget()
image_path_layout = QHBoxLayout()
# image_path = QLineEdit()
# image_path_layout.addWidget(QLabel("Image Path (requires restart):"))
# image_path_layout.addWidget(image_path)
# image_path_container.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
# image_path_container.setLayout(image_path_layout)
# image_path.setText(state.image_path)
image_path = QLineEdit()
image_path_layout.addWidget(QLabel("Image Path (requires restart):"))
image_path_layout.addWidget(image_path)
image_path_container.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
image_path_container.setLayout(image_path_layout)
image_path.setText(state.image_path)
# def browse_image_path():
# file_path = QFileDialog.getOpenFileName(dialog, "Select Image File", state.image_path, "Image Files (*.png *.jpg)")[0]
# if file_path:
# image_path.setText(file_path)
def browse_image_path():
file_path = QFileDialog.getOpenFileName(dialog, "Select Image File", state.image_path, "Image Files (*.png *.jpg)")[0]
if file_path:
image_path.setText(file_path)
# browse_button = QPushButton("📁")
# image_path_layout.addWidget(browse_button)
# browse_button.clicked.connect(browse_image_path)
browse_button = QPushButton()
browse_button.setFixedSize(36, 36)
browse_button.setIconSize(QSize(24, 24))
browse_button.setIcon(svg_icon(SVG_FOLDER, 24))
browse_button.setCursor(Qt.CursorShape.PointingHandCursor)
browse_button.setStyleSheet("""
QPushButton {
border: none;
background: transparent;
padding: 0px;
}
""")
image_path_layout.addWidget(browse_button)
browse_button.clicked.connect(browse_image_path)
##################
# SPEED LIMITING #
@@ -244,7 +270,7 @@ def settings_dialog(self):
download_path.text(),
down_speed_limit.value(),
up_speed_limit.value(),
None,
image_path.text(),
autoresume_checkbox.isChecked(),
max_connections.value(),
max_downloads.value(),
@@ -256,7 +282,7 @@ def settings_dialog(self):
self.tabs = QTabWidget()
self.tab1 = general_tab("General", autoresume_container, update_checkbox_container, transparent_window_container, self.tabs)
self.tab2 = paths_tab("Paths", download_path_container, self.tabs)
self.tab2 = paths_tab("Paths", download_path_container, image_path_container, self.tabs)
self.tab3 = network_tab("Network", interface_container, max_connections_container, max_downloads_container, up_speed_limit_container, down_speed_limit_container, api_url_container, self.tabs)
dialog.layout().addWidget(self.tabs)
+51 -47
View File
@@ -19,7 +19,6 @@ from PySide6.QtWidgets import (
)
from PySide6.QtGui import QIcon, QCloseEvent, QImage, QPixmap, QContextMenuEvent, QGuiApplication, QColor, QBrush
from PySide6.QtSvg import QSvgRenderer
import darkdetect
import threading
import platform
@@ -34,9 +33,10 @@ from core.utils.logging.logs import consoleLog, remove_download_log, flush_log_b
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.network.update_checker import get_updates
from core.interface.utils.tabhelper import create_tab
from core.interface.utils.searchhelper import return_pressed
from core.interface.utils.svghelper import svg_icon
from core.interface.dialogs.settings import settings_dialog
from core.interface.assets.base64_icons import settings_black_base64
from core.interface.assets.base64_icons import settings_white_base64
@@ -117,38 +117,32 @@ def _table_stylesheet(view_type="QTableWidget"):
"""
def _svg_icon(svg_str, size=20):
app = QtWidgets.QApplication.instance()
if app:
if _is_dark_mode():
color = "white"
else:
color = "#555555"
else:
color = "white"
svg = svg_str.replace("{color}", color)
renderer = QSvgRenderer(QByteArray(svg.encode()))
pixmap = QPixmap(size, size)
pixmap.fill(Qt.GlobalColor.transparent)
from PySide6.QtGui import QPainter
painter = QPainter(pixmap)
renderer.render(painter)
painter.end()
return QIcon(pixmap)
SVG_PLAY = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><polygon points="6,3 20,12 6,21" fill="{color}"/></svg>'
SVG_PAUSE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="5" y="3" width="4" height="18" rx="1" fill="{color}"/><rect x="15" y="3" width="4" height="18" rx="1" fill="{color}"/></svg>'
SVG_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2 6c0-1.1.9-2 2-2h5l2 2h7c1.1 0 2 .9 2 2v10c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6z" fill="{color}"/></svg>'
def _verify_hash(file_path, expected_hash):
import hashlib
sha256 = hashlib.sha256()
def download_update(latest_version):
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha256.update(chunk)
return f"sha256:{sha256.hexdigest()}" == expected_hash
def _download_update(assets):
import tempfile
import time
new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows-setup.exe"
url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows-setup.exe"
installer_path = os.path.join(tempfile.gettempdir(), new_filename)
for asset in assets:
if "-windows-setup.exe" in asset["name"]:
filename = asset["name"]
setup_hash = asset["hash"]
url = asset ["url"]
installer_path = os.path.join(tempfile.gettempdir(), filename)
progress = QtWidgets.QProgressDialog("Downloading installer...", None, 0, 0)
progress.setWindowTitle("Updating")
@@ -177,6 +171,12 @@ def download_update(latest_version):
progress.close()
raise FileNotFoundError("Executable not found")
if _verify_hash(installer_path, setup_hash):
consoleLog(f"Sucessfully validated installer hash ({setup_hash})")
else:
consoleLog("Error: Invalid Filehash, file may be corrupted")
sys.exit(0)
progress.setLabelText("Installing update...")
progress.setValue(100)
QtWidgets.QApplication.processEvents()
@@ -220,21 +220,19 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
# Check for updates on Windows
if state.ignore_updates is False and platform.system() == "Windows":
result = check_for_updates()
if result != (None, None):
assets, latest_version = result
assets = get_updates()
if assets != None:
if assets:
msg = QMessageBox()
msg.setIcon(QMessageBox.Icon.Information)
msg.setWindowTitle("Update Available")
msg.setText("A new version is available.")
msg.setInformativeText("Press Ok to download the update.")
msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore)
msg = QMessageBox()
msg.setIcon(QMessageBox.Icon.Information)
msg.setWindowTitle("Update Available")
msg.setText("A new version is available.")
msg.setInformativeText("Press Ok to download the update.")
msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore)
response = msg.exec_()
if response == QMessageBox.StandardButton.Ok:
download_update(latest_version)
response = msg.exec_()
if response == QMessageBox.StandardButton.Ok:
_download_update(assets)
self.setWindowTitle("Software Manager")
self.setGeometry(100, 100, 800, 600)
@@ -458,21 +456,27 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
class HoverRowDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
opt = QtWidgets.QStyleOptionViewItem(option)
opt.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus
opt.state &= ~QtWidgets.QStyle.StateFlag.State_Selected
if index.row() == MainWindow._instance._hovered_row:
painter.save()
painter.fillRect(option.rect, _theme_colors()["hover"])
painter.fillRect(opt.rect, _theme_colors()["hover"])
painter.restore()
super().paint(painter, option, index)
super().paint(painter, opt, index)
class PauseResumeDelegate(QStyledItemDelegate):
clicked = Signal(int)
def paint(self, painter, option, index):
opt = QtWidgets.QStyleOptionViewItem(option)
opt.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus
opt.state &= ~QtWidgets.QStyle.StateFlag.State_Selected
if index.row() == MainWindow._instance._hovered_row:
painter.save()
painter.fillRect(option.rect, _theme_colors()["hover"])
painter.fillRect(opt.rect, _theme_colors()["hover"])
painter.restore()
super().paint(painter, option, index)
super().paint(painter, opt, index)
def setEditorData(self, editor, index):
button = editor.findChild(QtWidgets.QPushButton)
@@ -483,11 +487,11 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
if button:
if status.state == lt.torrent_status.seeding:
button.setIcon(_svg_icon(SVG_FOLDER, 18))
button.setIcon(svg_icon(SVG_FOLDER, 18))
button.setText("")
else:
is_user_paused = status.paused and not bool(magnetdl.flags() & lt.torrent_flags.auto_managed)
button.setIcon(_svg_icon(SVG_PLAY if is_user_paused else SVG_PAUSE, 18))
button.setIcon(svg_icon(SVG_PLAY if is_user_paused else SVG_PAUSE, 18))
button.setText("")
def createEditor(self, parent, option, index):
@@ -501,11 +505,11 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
layout.setContentsMargins(0, 0, 0, 0)
if status.state == lt.torrent_status.seeding:
btnPause = QtWidgets.QPushButton()
btnPause.setIcon(_svg_icon(SVG_FOLDER, 18))
btnPause.setIcon(svg_icon(SVG_FOLDER, 18))
else:
btnPause = QtWidgets.QPushButton()
is_user_paused = status.paused and not bool(magnetdl.flags() & lt.torrent_flags.auto_managed)
btnPause.setIcon(_svg_icon(SVG_PLAY if is_user_paused else SVG_PAUSE, 18))
btnPause.setIcon(svg_icon(SVG_PLAY if is_user_paused else SVG_PAUSE, 18))
btnPause.setIconSize(QSize(18, 18))
btnPause.setFixedSize(30, 30)
btnPause.setCursor(Qt.CursorShape.PointingHandCursor)
+30
View File
@@ -0,0 +1,30 @@
from PySide6 import QtWidgets
from PySide6.QtCore import Qt, QByteArray
from PySide6.QtGui import QIcon, QPixmap, QPainter
from PySide6.QtSvg import QSvgRenderer
import darkdetect
def _is_dark_mode():
return darkdetect.isDark()
def svg_icon(svg_str, size=20):
app = QtWidgets.QApplication.instance()
if app:
if _is_dark_mode():
color = "white"
else:
color = "#555555"
else:
color = "white"
svg = svg_str.replace("{color}", color)
renderer = QSvgRenderer(QByteArray(svg.encode()))
pixmap = QPixmap(size, size)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
renderer.render(painter)
painter.end()
return QIcon(pixmap)
+2 -2
View File
@@ -30,11 +30,11 @@ def general_tab(title, autoresume, update_checkbox, transparent_window, tabs):
tabs.addTab(tab, title)
return tab
def paths_tab(title, download_path, tabs):
def paths_tab(title, download_path, image_path, tabs):
tab = QWidget()
layout = QVBoxLayout()
layout.addWidget(download_path)
# layout.addWidget(image_path)
layout.addWidget(image_path)
layout.addStretch()
tab.setLayout(layout)
tabs.addTab(tab, title)
+14 -9
View File
@@ -2,28 +2,33 @@ import requests
from core.utils.data.state import state
from core.utils.logging.logs import consoleLog
def check_for_updates():
url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases"
def get_updates():
url = f"https://api.github.com/repos/KeksPirates/SoftwareManager/releases/latest"
response = requests.get(url, timeout=15)
if response.status_code != 200:
consoleLog(f"Failed to fetch releases: {response.status_code}")
return None, None
releases = response.json()
releases.sort(key=lambda r: r["published_at"], reverse=True)
release = response.json()
latest_release = releases[0]
latest_version = latest_release["name"]
assets = latest_release["assets"]
latest_version = release["name"]
assets = release["assets"]
release_assets = []
if latest_version != state.version:
consoleLog(f"New release available: {latest_version}")
if assets:
consoleLog("Assets:")
for asset in assets:
consoleLog(f" - {asset['name']}: {asset['browser_download_url']}")
return assets, latest_version
consoleLog(f"{asset['name']}")
release_assets.append(dict(
name=asset['name'],
url=asset['browser_download_url'],
hash=asset['digest']
))
return release_assets
else:
return None, None
else: