From 2548280dcc8ddd837642497f45c86143a7ae3ac9 Mon Sep 17 00:00:00 2001 From: Brett Date: Sat, 24 Aug 2024 12:12:30 -0500 Subject: [PATCH] Add check for available software updates (#118) * Add feature to check github repo for available updates * Add Check for Updates to Help menu --- src/init.py | 11 ++++++-- src/ui/widgets/menubar.py | 50 +++++++++++++++++++++++++++++++++--- src/utilities/misc_helper.py | 27 +++++++++++++++++++ version.py | 2 ++ 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/src/init.py b/src/init.py index 8be5ba9..242db16 100644 --- a/src/init.py +++ b/src/init.py @@ -13,9 +13,10 @@ from src.distributed_job_manager import DistributedJobManager from src.engines.engine_manager import EngineManager from src.render_queue import RenderQueue from src.utilities.config import Config -from src.utilities.misc_helper import system_safe_path, current_system_cpu, current_system_os, current_system_os_version +from src.utilities.misc_helper import (system_safe_path, current_system_cpu, current_system_os, + current_system_os_version, check_for_updates) from src.utilities.zeroconf_server import ZeroconfServer -from version import APP_NAME +from version import APP_NAME, APP_VERSION, APP_REPO_NAME, APP_REPO_OWNER logger = logging.getLogger() @@ -60,6 +61,12 @@ def run(server_only=False) -> int: # Setup logging for console ui buffer_handler = __setup_buffer_handler() if not server_only else None + # check for updates + update_thread = threading.Thread(target=check_for_updates, args=(APP_REPO_NAME, APP_REPO_OWNER, APP_NAME, + APP_VERSION)) + update_thread.start() + + # main start logger.info(f"Starting {APP_NAME} Render Server") return_code = 0 try: diff --git a/src/ui/widgets/menubar.py b/src/ui/widgets/menubar.py index fcff6cb..5b061d6 100644 --- a/src/ui/widgets/menubar.py +++ b/src/ui/widgets/menubar.py @@ -1,8 +1,6 @@ ''' app/ui/widgets/menubar.py ''' -import sys - from PyQt6.QtGui import QAction -from PyQt6.QtWidgets import QMenuBar, QApplication +from PyQt6.QtWidgets import QMenuBar, QApplication, QMessageBox, QDialog, QVBoxLayout, QLabel, QPushButton class MenuBar(QMenuBar): @@ -43,6 +41,9 @@ class MenuBar(QMenuBar): about_action = QAction("About", self) about_action.triggered.connect(self.show_about) help_menu.addAction(about_action) + update_action = QAction("Check for Updates...", self) + update_action.triggered.connect(self.check_for_updates) + help_menu.addAction(update_action) def new_job(self): self.parent().new_job() @@ -55,3 +56,46 @@ class MenuBar(QMenuBar): from src.ui.about_window import AboutDialog dialog = AboutDialog() dialog.exec() + + @staticmethod + def check_for_updates(): + from src.utilities.misc_helper import check_for_updates + from version import APP_NAME, APP_VERSION, APP_REPO_NAME, APP_REPO_OWNER + found_update = check_for_updates(APP_REPO_NAME, APP_REPO_OWNER, APP_NAME, APP_VERSION) + if found_update: + dialog = UpdateDialog(found_update, APP_VERSION) + dialog.exec() + else: + QMessageBox.information(None, "No Update", "No updates available.") + + +class UpdateDialog(QDialog): + def __init__(self, release_info, current_version, parent=None): + super().__init__(parent) + self.setWindowTitle(f"Update Available ({current_version} -> {release_info['tag_name']})") + + layout = QVBoxLayout() + label = QLabel(f"A new version ({release_info['tag_name']}) is available! Current version: {current_version}") + layout.addWidget(label) + + # Label to show the release notes + description = QLabel(release_info["body"]) + layout.addWidget(description) + + # Button to download the latest version + download_button = QPushButton(f"Download Latest Version ({release_info['tag_name']})") + download_button.clicked.connect(lambda: self.open_url(release_info["html_url"])) + layout.addWidget(download_button) + + # OK button to dismiss the dialog + ok_button = QPushButton("Dismiss") + ok_button.clicked.connect(self.accept) # Close the dialog when clicked + layout.addWidget(ok_button) + + self.setLayout(layout) + + def open_url(self, url): + from PyQt6.QtCore import QUrl + from PyQt6.QtGui import QDesktopServices + QDesktopServices.openUrl(QUrl(url)) + self.accept() diff --git a/src/utilities/misc_helper.py b/src/utilities/misc_helper.py index ae17899..1278e3c 100644 --- a/src/utilities/misc_helper.py +++ b/src/utilities/misc_helper.py @@ -159,6 +159,33 @@ def copy_directory_contents(src_dir, dst_dir): shutil.copy2(src_path, dst_path) +def check_for_updates(repo_name, repo_owner, app_name, current_version): + def get_github_releases(owner, repo): + import requests + url = f"https://api.github.com/repos/{owner}/{repo}/releases" + try: + response = requests.get(url, timeout=3) + response.raise_for_status() + releases = response.json() + return releases + except Exception as e: + logger.error(f"Error checking for updates: {e}") + return [] + + releases = get_github_releases(repo_owner, repo_name) + if not releases: + return + + latest_version = releases[0] + latest_version_tag = latest_version['tag_name'] + + from packaging import version + if version.parse(latest_version_tag) > version.parse(current_version): + logger.info(f"Newer version of {app_name} available. " + f"Latest: {latest_version_tag}, Current: {current_version}") + return latest_version + + def is_localhost(comparison_hostname): # this is necessary because socket.gethostname() does not always include '.local' - This is a sanitized comparison try: diff --git a/version.py b/version.py index 40038b8..f1c9abd 100644 --- a/version.py +++ b/version.py @@ -4,3 +4,5 @@ APP_AUTHOR = "Brett Williams" APP_DESCRIPTION = "Distributed Render Farm Tools" APP_COPYRIGHT_YEAR = "2024" APP_LICENSE = "MIT License" +APP_REPO_NAME = APP_NAME +APP_REPO_OWNER = "blw1138"