From 2ba99cee31e7e50a08db70e98db064c7654cb897 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 6 Jan 2026 23:48:49 -0600 Subject: [PATCH] EngineManager renaming and refactoring --- src/api/api_server.py | 6 +- src/engines/engine_manager.py | 122 +++++++++++++++++++--------------- 2 files changed, 73 insertions(+), 55 deletions(-) diff --git a/src/api/api_server.py b/src/api/api_server.py index 8eb22ae..4a85a85 100755 --- a/src/api/api_server.py +++ b/src/api/api_server.py @@ -389,7 +389,7 @@ def engine_info(): def process_engine(engine): try: # Get all installed versions of the engine - installed_versions = EngineManager.all_versions_for_engine(engine.name()) + installed_versions = EngineManager.all_version_data_for_engine(engine.name()) if not installed_versions: return None @@ -420,7 +420,7 @@ def engine_info(): except Exception as e: logger.error(f"Error fetching details for engine '{engine.name()}': {e}") - raise e + return {} engine_data = {} with concurrent.futures.ThreadPoolExecutor() as executor: @@ -438,7 +438,7 @@ def engine_info(): def is_engine_available(engine_name): return {'engine': engine_name, 'available': RenderQueue.is_available_for_job(engine_name), 'cpu_count': int(psutil.cpu_count(logical=False)), - 'versions': EngineManager.all_versions_for_engine(engine_name), + 'versions': EngineManager.all_version_data_for_engine(engine_name), 'hostname': server.config['HOSTNAME']} diff --git a/src/engines/engine_manager.py b/src/engines/engine_manager.py index 4dbf345..14ca791 100644 --- a/src/engines/engine_manager.py +++ b/src/engines/engine_manager.py @@ -11,6 +11,9 @@ from src.utilities.misc_helper import system_safe_path, current_system_os, curre logger = logging.getLogger() +ENGINE_CLASSES = [Blender, FFMPEG] + + class EngineManager: """Class that manages different versions of installed render engines and handles fetching and downloading new versions, if possible. @@ -21,32 +24,36 @@ class EngineManager: @staticmethod def supported_engines(): - return [Blender, FFMPEG] + return ENGINE_CLASSES + + # --- Installed Engines --- @classmethod - def downloadable_engines(cls): - return [engine for engine in cls.supported_engines() if hasattr(engine, "downloader") and engine.downloader()] - - @classmethod - def active_downloads(cls) -> list: - return [x for x in cls.download_tasks if x.is_alive()] + def engine_for_project_path(cls, path): + _, extension = os.path.splitext(path) + extension = extension.lower().strip('.') + for engine_class in cls.supported_engines(): + engine = cls.get_latest_engine_instance(engine_class) + if extension in engine.supported_extensions(): + return engine + undefined_renderer_support = [x for x in cls.supported_engines() if not cls.get_latest_engine_instance(x).supported_extensions()] + return undefined_renderer_support[0] @classmethod def engine_with_name(cls, engine_name): for obj in cls.supported_engines(): if obj.name().lower() == engine_name.lower(): return obj + return None @classmethod - def update_all_engines(cls): - for engine in cls.downloadable_engines(): - update_available = cls.is_engine_update_available(engine) - if update_available: - update_available['name'] = engine.name() - cls.download_engine(engine.name(), update_available['version'], background=True) + def get_latest_engine_instance(cls, engine_class): + newest = cls.newest_installed_engine_data(engine_class.name()) + engine = engine_class(newest["path"]) + return engine @classmethod - def get_engines(cls, filter_name=None, include_corrupt=False, ignore_system=False): + def get_installed_engine_data(cls, filter_name=None, include_corrupt=False, ignore_system=False): if not cls.engines_path: raise FileNotFoundError("Engine path is not set") @@ -123,31 +130,41 @@ class EngineManager: return results + # --- Check for Updates --- + @classmethod - 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) + def update_all_engines(cls): + for engine in cls.downloadable_engines(): + update_available = cls.is_engine_update_available(engine) + if update_available: + update_available['name'] = engine.name() + cls.download_engine(engine.name(), update_available['version'], background=True) + + @classmethod + def all_version_data_for_engine(cls, engine_name, include_corrupt=False, ignore_system=False): + versions = cls.get_installed_engine_data(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, ignore_system=None): + def newest_installed_engine_data(cls, engine_name, 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, ignore_system=ignore_system) + filtered = [x for x in cls.all_version_data_for_engine(engine_name, 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}") + logger.error(f"Cannot find newest engine version for {engine_name}-{system_os}-{cpu}") return None @classmethod - def is_version_downloaded(cls, engine, version, system_os=None, cpu=None, ignore_system=False): + def is_version_installed(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, ignore_system=ignore_system) if + filtered = [x for x in cls.get_installed_engine_data(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 @@ -169,6 +186,28 @@ class EngineManager: logger.debug(f"Exception in find_most_recent_version: {e}") return None + @classmethod + 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() + + if not latest_version: + logger.warning(f"Could not find most recent version of {engine_class.name()} to download") + return None + + version_num = latest_version.get('version') + if cls.is_version_installed(engine_class.name(), version_num, ignore_system=ignore_system_installs): + logger.debug(f"Latest version of {engine_class.name()} ({version_num}) already downloaded") + return None + + return latest_version + + # --- Downloads --- + + @classmethod + def downloadable_engines(cls): + return [engine for engine in cls.supported_engines() if hasattr(engine, "downloader") and engine.downloader()] + @classmethod def get_existing_download_task(cls, engine, version, system_os=None, cpu=None): for task in cls.download_tasks: @@ -204,7 +243,7 @@ class EngineManager: return thread thread.join() - found_engine = cls.is_version_downloaded(engine, version, system_os, cpu, ignore_system) # Check that engine downloaded + found_engine = cls.is_version_installed(engine, version, system_os, cpu, ignore_system) # Check that engine downloaded if not found_engine: logger.error(f"Error downloading {engine}") return found_engine @@ -213,7 +252,7 @@ class EngineManager: def delete_engine_download(cls, engine, version, system_os=None, cpu=None): logger.info(f"Requested deletion of engine: {engine}-{version}") - found = cls.is_version_downloaded(engine, version, system_os, cpu) + found = cls.is_version_installed(engine, version, system_os, cpu) if found and found['type'] == 'managed': # don't delete system installs # find the root directory of the engine executable root_dir_name = '-'.join([engine, version, found['system_os'], found['cpu']]) @@ -229,22 +268,11 @@ class EngineManager: logger.error(f"Cannot find engine: {engine}-{version}") return False + # --- Background Tasks --- + @classmethod - 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() - - if not latest_version: - logger.warning(f"Could not find most recent version of {engine_class.name()} to download") - return None - - version_num = latest_version.get('version') - 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 None - - return latest_version - + def active_downloads(cls) -> list: + return [x for x in cls.download_tasks if x.is_alive()] @classmethod def create_worker(cls, engine_name, input_path, output_path, engine_version=None, args=None, parent=None, name=None): @@ -252,7 +280,7 @@ class EngineManager: worker_class = cls.engine_with_name(engine_name).worker_class() # check to make sure we have versions installed - all_versions = cls.all_versions_for_engine(engine_name) + all_versions = cls.all_version_data_for_engine(engine_name) if not all_versions: raise FileNotFoundError(f"Cannot find any installed '{engine_name}' engines") @@ -281,16 +309,6 @@ class EngineManager: return worker_class(input_path=input_path, output_path=output_path, engine_path=engine_path, args=args, parent=parent, name=name) - @classmethod - def engine_for_project_path(cls, path): - _, extension = os.path.splitext(path) - extension = extension.lower().strip('.') - for engine in cls.supported_engines(): - if extension in engine().supported_extensions(): - return engine - undefined_renderer_support = [x for x in cls.supported_engines() if not x().supported_extensions()] - return undefined_renderer_support[0] - class EngineDownloadWorker(threading.Thread): """A thread worker for downloading a specific version of a rendering engine. @@ -317,8 +335,8 @@ class EngineDownloadWorker(threading.Thread): def run(self): try: - existing_download = EngineManager.is_version_downloaded(self.engine, self.version, self.system_os, self.cpu, - ignore_system=True) + existing_download = EngineManager.is_version_installed(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 @@ -341,4 +359,4 @@ if __name__ == '__main__': # EngineManager.delete_engine_download('blender', '3.2.1', 'macos', 'a') EngineManager.engines_path = "/Users/brettwilliams/zordon-uploads/engines" # print(EngineManager.is_version_downloaded("ffmpeg", "6.0")) - print(EngineManager.get_engines()) + print(EngineManager.get_installed_engine_data())