diff --git a/src/engines/engine_manager.py b/src/engines/engine_manager.py index 2bc1570..838e898 100644 --- a/src/engines/engine_manager.py +++ b/src/engines/engine_manager.py @@ -34,7 +34,7 @@ class EngineManager: return obj @classmethod - def get_engines(cls, filter_name=None, include_corrupt=False): + def get_engines(cls, filter_name=None, include_corrupt=False, ignore_system=False): if not cls.engines_path: raise FileNotFoundError("Engine path is not set") @@ -96,46 +96,47 @@ class EngineManager: 'type': 'system' } - with concurrent.futures.ThreadPoolExecutor() as executor: - futures = { - executor.submit(fetch_engine_details, eng, include_corrupt): eng.name() - for eng in cls.supported_engines() - if eng.default_renderer_path() and (not filter_name or filter_name == eng.name()) - } + if not ignore_system: + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = { + executor.submit(fetch_engine_details, eng, include_corrupt): eng.name() + for eng in cls.supported_engines() + if eng.default_renderer_path() and (not filter_name or filter_name == eng.name()) + } - for future in concurrent.futures.as_completed(futures): - result = future.result() - if result: - results.append(result) + for future in concurrent.futures.as_completed(futures): + result = future.result() + if result: + results.append(result) return results @classmethod - def all_versions_for_engine(cls, engine_name, include_corrupt=False): - versions = cls.get_engines(filter_name=engine_name, include_corrupt=include_corrupt) + def all_versions_for_engine(cls, engine_name, include_corrupt=False, ignore_system=False): + versions = cls.get_engines(filter_name=engine_name, include_corrupt=include_corrupt, ignore_system=ignore_system) sorted_versions = sorted(versions, key=lambda x: x['version'], reverse=True) return sorted_versions @classmethod - def newest_engine_version(cls, engine, system_os=None, cpu=None): + def newest_engine_version(cls, engine, system_os=None, cpu=None, ignore_system=None): system_os = system_os or current_system_os() cpu = cpu or current_system_cpu() try: - filtered = [x for x in cls.all_versions_for_engine(engine) if x['system_os'] == system_os and - x['cpu'] == cpu] + filtered = [x for x in cls.all_versions_for_engine(engine, ignore_system=ignore_system) + if x['system_os'] == system_os and x['cpu'] == cpu] return filtered[0] except IndexError: logger.error(f"Cannot find newest engine version for {engine}-{system_os}-{cpu}") return None @classmethod - def is_version_downloaded(cls, engine, version, system_os=None, cpu=None): + def is_version_downloaded(cls, engine, version, system_os=None, cpu=None, ignore_system=False): system_os = system_os or current_system_os() cpu = cpu or current_system_cpu() - filtered = [x for x in cls.get_engines(filter_name=engine) if x['system_os'] == system_os and - x['cpu'] == cpu and x['version'] == version] + filtered = [x for x in cls.get_engines(filter_name=engine, ignore_system=ignore_system) if + x['system_os'] == system_os and x['cpu'] == cpu and x['version'] == version] return filtered[0] if filtered else False @classmethod @@ -168,7 +169,7 @@ class EngineManager: return None @classmethod - def download_engine(cls, engine, version, system_os=None, cpu=None, background=False): + def download_engine(cls, engine, version, system_os=None, cpu=None, background=False, ignore_system=False): engine_to_download = cls.engine_with_name(engine) existing_task = cls.get_existing_download_task(engine, version, system_os, cpu) @@ -191,7 +192,7 @@ class EngineManager: return thread thread.join() - found_engine = cls.is_version_downloaded(engine, version, system_os, cpu) # Check that engine downloaded + found_engine = cls.is_version_downloaded(engine, version, system_os, cpu, ignore_system) # Check that engine downloaded if not found_engine: logger.error(f"Error downloading {engine}") return found_engine @@ -217,34 +218,7 @@ class EngineManager: return False @classmethod - def update_all_engines(cls): - def engine_update_task(engine_class): - logger.debug(f"Checking for updates to {engine_class.name()}") - latest_version = engine_class.downloader().find_most_recent_version() - - if not latest_version: - logger.warning(f"Could not find most recent version of {engine.name()} to download") - return - - version_num = latest_version.get('version') - if cls.is_version_downloaded(engine_class.name(), version_num): - logger.debug(f"Latest version of {engine_class.name()} ({version_num}) already downloaded") - return - - # download the engine - logger.info(f"Downloading latest version of {engine_class.name()} ({version_num})...") - cls.download_engine(engine=engine_class.name(), version=version_num, background=True) - - logger.info(f"Checking for updates for render engines...") - threads = [] - for engine in cls.supported_engines(): - if engine.downloader(): - thread = threading.Thread(target=engine_update_task, args=(engine,)) - threads.append(thread) - thread.start() - - @classmethod - def update_engine(cls, engine_class): + def is_engine_update_available(cls, engine_class, ignore_system_installs=False): logger.debug(f"Checking for updates to {engine_class.name()}") latest_version = engine_class.downloader().find_most_recent_version() @@ -253,15 +227,11 @@ class EngineManager: return version_num = latest_version.get('version') - if cls.is_version_downloaded(engine_class.name(), version_num): + if cls.is_version_downloaded(engine_class.name(), version_num, ignore_system=ignore_system_installs): logger.debug(f"Latest version of {engine_class.name()} ({version_num}) already downloaded") return - # download the engine - logger.info(f"Downloading latest version of {engine_class.name()} ({version_num})...") - download_job = cls.download_engine(engine=engine_class.name(), version=version_num, background=True) - - return {"latest": latest_version, "thread": download_job, "name": engine_class.name()} + return latest_version @classmethod @@ -330,7 +300,8 @@ class EngineDownloadWorker(threading.Thread): self.cpu = cpu def run(self): - existing_download = EngineManager.is_version_downloaded(self.engine, self.version, self.system_os, self.cpu) + existing_download = EngineManager.is_version_downloaded(self.engine, self.version, self.system_os, self.cpu, + ignore_system=True) if existing_download: logger.info(f"Requested download of {self.engine} {self.version}, but local copy already exists") return existing_download diff --git a/src/init.py b/src/init.py index 242db16..13c780e 100644 --- a/src/init.py +++ b/src/init.py @@ -5,6 +5,9 @@ import socket import sys import threading from collections import deque +from datetime import datetime + +from PyQt6.QtCore import QSettings from src.api.api_server import start_server from src.api.preview_manager import PreviewManager @@ -16,7 +19,7 @@ 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, check_for_updates) from src.utilities.zeroconf_server import ZeroconfServer -from version import APP_NAME, APP_VERSION, APP_REPO_NAME, APP_REPO_OWNER +from version import APP_NAME, APP_VERSION, APP_REPO_NAME, APP_REPO_OWNER, APP_AUTHOR logger = logging.getLogger() @@ -66,6 +69,8 @@ def run(server_only=False) -> int: APP_VERSION)) update_thread.start() + settings = QSettings(APP_AUTHOR, APP_NAME) + # main start logger.info(f"Starting {APP_NAME} Render Server") return_code = 0 @@ -92,9 +97,16 @@ def run(server_only=False) -> int: ServerProxyManager.subscribe_to_listener() DistributedJobManager.subscribe_to_listener() - # check for updates for render engines if configured or on first launch - if Config.update_engines_on_launch or not EngineManager.get_engines(): - EngineManager.update_all_engines() + # check for updates for render engines if configured + ignore_system = settings.value("engines_ignore_system_installs", False) + if settings.value('check_for_engine_updates_on_launch', False): + for engine in EngineManager.downloadable_engines(): + if settings.value(f'engine_download-{engine.name()}', False): + update_result = EngineManager.is_engine_update_available(engine, ignore_system_installs=ignore_system) + EngineManager.download_engine(engine=engine.name(), version=update_result['version'], + background=True, + ignore_system=ignore_system) + settings.setValue("engines_last_update_time", datetime.now().isoformat()) # get hostname local_hostname = socket.gethostname() diff --git a/src/ui/settings_window.py b/src/ui/settings_window.py index 7305c68..4d34261 100644 --- a/src/ui/settings_window.py +++ b/src/ui/settings_window.py @@ -184,6 +184,12 @@ class SettingsWindow(QMainWindow): installed_layout = QVBoxLayout() self.installed_engines_table = EngineTableWidget() installed_layout.addWidget(self.installed_engines_table) + + engine_ignore_system_installs_checkbox = QCheckBox("Ignore system installs") + engine_ignore_system_installs_checkbox.setChecked(settings.value("engines_ignore_system_installs", False)) + engine_ignore_system_installs_checkbox.stateChanged.connect(self.change_ignore_system_installs) + installed_layout.addWidget(engine_ignore_system_installs_checkbox) + installed_buttons_layout = QHBoxLayout() launch_engine_button = QPushButton("Launch") launch_engine_button.clicked.connect(self.launch_selected_engine) @@ -226,7 +232,7 @@ class SettingsWindow(QMainWindow): self.update_last_checked_label() self.engines_last_update_label.setEnabled(at_least_one_downloadable) engine_updates_layout.addWidget(self.engines_last_update_label) - self.check_for_new_engines_button = QPushButton("Check for New Versions") + self.check_for_new_engines_button = QPushButton("Check for New Versions...") self.check_for_new_engines_button.setEnabled(at_least_one_downloadable) self.check_for_new_engines_button.clicked.connect(self.check_for_new_engines) engine_updates_layout.addWidget(self.check_for_new_engines_button) @@ -239,6 +245,11 @@ class SettingsWindow(QMainWindow): page.setLayout(layout) return page + def change_ignore_system_installs(self, value): + settings.setValue("engines_ignore_system_installs", bool(value)) + self.installed_engines_table.update_table() + + def update_last_checked_label(self): """Retrieve the last check timestamp and return a human-friendly string.""" last_checked_str = settings.value("engines_last_update_time", None) @@ -275,22 +286,27 @@ class SettingsWindow(QMainWindow): os.path.join(os.path.join(os.path.expanduser(Config.upload_folder), 'engines'))) - results = [] + ignore_system = settings.value("engines_ignore_system_installs", False) + messagebox_shown = False for engine in EngineManager.downloadable_engines(): if settings.value(f'engine_download-{engine.name()}', False): - update_result = EngineManager.update_engine(engine) - if update_result: - results.append(update_result) + result = EngineManager.is_engine_update_available(engine, ignore_system_installs=ignore_system) + if result: + result['name'] = engine.name() + msg_box = QMessageBox() + msg_box.setWindowTitle(f"{result['name']} ({result['version']}) Available") + msg_box.setText(f"A new version of {result['name']} is available ({result['version']}).\n\n" + f"Would you like to download it now?") + msg_box.setIcon(QMessageBox.Icon.Information) + msg_box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + msg_result = msg_box.exec() + messagebox_shown = True + if msg_result == QMessageBox.StandardButton.Yes: + EngineManager.download_engine(engine=engine.name(), version=result['version'], background=True, + ignore_system=ignore_system) + self.update_engine_download_status() - if results: - for result in results: - msg_box = QMessageBox() - msg_box.setWindowTitle(f"{result['name']} {result['version']} Available") - msg_box.setText(f"A new version of {result['name']} is available ({result['version']}). It will begin downloading now.") - msg_box.setIcon(QMessageBox.Icon.Information) - msg_box.setStandardButtons(QMessageBox.StandardButton.Ok) - msg_box.exec() - else: + if not messagebox_shown: msg_box = QMessageBox() msg_box.setWindowTitle("No Updates Available") msg_box.setText("All your render engines are up-to-date.") @@ -299,7 +315,16 @@ class SettingsWindow(QMainWindow): msg_box.exec() settings.setValue("engines_last_update_time", datetime.now().isoformat()) - self.update_last_checked_label() + self.update_engine_download_status() + + def update_engine_download_status(self): + running_tasks = [x for x in EngineManager.download_tasks if x.is_alive()] + if not running_tasks: + self.update_last_checked_label() + return + + self.engines_last_update_label.setText(f"Downloading {running_tasks[0].engine} ({running_tasks[0].version})...") + class EngineTableWidget(QWidget): def __init__(self): @@ -315,20 +340,26 @@ class EngineTableWidget(QWidget): layout = QVBoxLayout(self) layout.addWidget(self.table) + self.raw_server_data = None + def showEvent(self, event): """Runs when the widget is about to be shown.""" self.update_table() super().showEvent(event) # Ensure normal event processing - def update_table(self): - raw_server_data = RenderServerProxy(socket.gethostname()).get_renderer_info() - if not raw_server_data: + def update_table(self, use_cached=True): + if not self.raw_server_data or not use_cached: + self.raw_server_data = RenderServerProxy(socket.gethostname()).get_renderer_info() + if not self.raw_server_data: return table_data = [] # convert the data into a flat list - for _, engine_data in raw_server_data.items(): + for _, engine_data in self.raw_server_data.items(): table_data.extend(engine_data['versions']) + if settings.value("engines_ignore_system_installs", False): + table_data = [x for x in table_data if x['type'] != 'system'] + self.table.clear() self.table.setRowCount(len(table_data)) self.table.setColumnCount(4) @@ -364,6 +395,7 @@ class EngineTableWidget(QWidget): return data + if __name__ == "__main__": app = QApplication([]) window = SettingsWindow()