From 59f41d054658a09bf69be1e4b4273d6d77fef889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 31 Aug 2022 09:24:27 +0200 Subject: [PATCH] Add-on: show warning when versions are not matching Before submitting a job, the add-on now checks the version of the Manager. If this is not the same version of the add-on, a warning is shown and a "Force Submit" button appears. This makes it both explicit that something is iffy and still allows for pushing forward. This is important when upgrading Flamenco, because I'm sure many people will forget to actually redownload and reinstall the add-on. --- addon/flamenco/__init__.py | 7 ++++ addon/flamenco/comms.py | 75 +++++++++++++++++++++++++++---------- addon/flamenco/gui.py | 30 +++++++++++---- addon/flamenco/operators.py | 63 ++++++++++++++++++++++++++++++- 4 files changed, 148 insertions(+), 27 deletions(-) diff --git a/addon/flamenco/__init__.py b/addon/flamenco/__init__.py index 8b7131e6..eb31d45f 100644 --- a/addon/flamenco/__init__.py +++ b/addon/flamenco/__init__.py @@ -37,6 +37,8 @@ def discard_global_flamenco_data(_): job_types.discard_flamenco_data() comms.discard_flamenco_data() + bpy.context.WindowManager.flamenco_version_mismatch = False + def redraw(self, context): if context.area is None: @@ -118,6 +120,11 @@ def register() -> None: max=100, update=redraw, ) + bpy.types.WindowManager.flamenco_version_mismatch = bpy.props.BoolProperty( + name="Flamenco Ignore Version Mismatch", + default=False, + description="Ignore version mismatch between add-on and Manager when submitting a job", + ) # Placeholder to contain the result of a 'ping' to Flamenco Manager, # so that it can be shown in the preferences panel. diff --git a/addon/flamenco/comms.py b/addon/flamenco/comms.py index 2948aeb7..d69e5b9e 100644 --- a/addon/flamenco/comms.py +++ b/addon/flamenco/comms.py @@ -3,6 +3,7 @@ # import logging +import dataclasses from typing import TYPE_CHECKING, Optional from urllib3.exceptions import HTTPError, MaxRetryError @@ -25,6 +26,23 @@ else: _ManagerConfiguration = object +@dataclasses.dataclass(frozen=True) +class ManagerInfo: + version: Optional[_FlamencoVersion] = None + config: Optional[_ManagerConfiguration] = None + error: str = "" + + @classmethod + def with_error(cls, error: str) -> "ManagerInfo": + return cls(error=error) + + @classmethod + def with_info( + cls, version: _FlamencoVersion, config: _ManagerConfiguration + ) -> "ManagerInfo": + return cls(version=version, config=config) + + def flamenco_api_client(manager_url: str) -> _ApiClient: """Returns an API client for communicating with a Manager.""" global _flamenco_client @@ -45,6 +63,18 @@ def flamenco_api_client(manager_url: str) -> _ApiClient: return _flamenco_client +def flamenco_client_version() -> str: + """Return the version of the Flamenco OpenAPI client.""" + + from . import dependencies + + dependencies.preload_modules() + + from . import manager + + return manager.__version__ + + def discard_flamenco_data(): global _flamenco_client @@ -57,7 +87,9 @@ def discard_flamenco_data(): def ping_manager_with_report( - context: bpy.types.Context, api_client: _ApiClient, prefs: _FlamencoPreferences + window_manager: bpy.types.WindowManager, + api_client: _ApiClient, + prefs: _FlamencoPreferences, ) -> tuple[str, str]: """Ping the Manager, update preferences, and return a report as string. @@ -67,26 +99,23 @@ def ping_manager_with_report( `Operator.report()`. """ - context.window_manager.flamenco_status_ping = "..." + info = ping_manager(window_manager, api_client, prefs) + if info.error: + return info.error, "ERROR" - version, _, err = ping_manager(api_client, prefs) - if err: - context.window_manager.flamenco_status_ping = err - return err, "ERROR" - - assert version is not None - report = "%s version %s found" % (version.name, version.version) - context.window_manager.flamenco_status_ping = report + assert info.version is not None + report = "%s version %s found" % (info.version.name, info.version.version) return report, "INFO" def ping_manager( - api_client: _ApiClient, prefs: _FlamencoPreferences -) -> tuple[Optional[_FlamencoVersion], Optional[_ManagerConfiguration], str]: - """Fetch Manager config & version, and update preferences. + window_manager: bpy.types.WindowManager, + api_client: _ApiClient, + prefs: _FlamencoPreferences, +) -> ManagerInfo: + """Fetch Manager config & version, and update cached preferences.""" - :returns: tuple (version, config, error). - """ + window_manager.flamenco_status_ping = "..." # Do a late import, so that the API is only imported when actually used. from flamenco.manager import ApiException @@ -94,21 +123,29 @@ def ping_manager( from flamenco.manager.models import FlamencoVersion, ManagerConfiguration meta_api = MetaApi(api_client) + error = "" try: version: FlamencoVersion = meta_api.get_version() config: ManagerConfiguration = meta_api.get_configuration() except ApiException as ex: - return (None, None, "Manager cannot be reached: %s" % ex) + error = "Manager cannot be reached: %s" % ex except MaxRetryError as ex: # This is the common error, when for example the port number is # incorrect and nothing is listening. The exception text is not included # because it's very long and confusing. - return (None, None, "Manager cannot be reached") + error = "Manager cannot be reached" except HTTPError as ex: - return (None, None, "Manager cannot be reached: %s" % ex) + error = "Manager cannot be reached: %s" % ex + + if error: + window_manager.flamenco_status_ping = error + return ManagerInfo.with_error(error) # Store whether this Manager supports the Shaman API. prefs.is_shaman_enabled = config.shaman_enabled prefs.job_storage = config.storage_location - return version, config, "" + report = "%s version %s found" % (version.name, version.version) + window_manager.flamenco_status_ping = report + + return ManagerInfo.with_info(version, config) diff --git a/addon/flamenco/gui.py b/addon/flamenco/gui.py index b0ccad88..449c51cc 100644 --- a/addon/flamenco/gui.py +++ b/addon/flamenco/gui.py @@ -133,13 +133,7 @@ class FLAMENCO_PT_job_submission(bpy.types.Panel): # Show current status of Flamenco. flamenco_status = context.window_manager.flamenco_bat_status if flamenco_status in {"IDLE", "ABORTED", "DONE"}: - ui = layout - props = ui.operator( - "flamenco.submit_job", - text="Submit to Flamenco", - icon="RENDER_ANIMATION", - ) - props.job_name = context.scene.flamenco_job_name + self.draw_submit_button(context, layout) elif flamenco_status == "INVESTIGATING": row = layout.row(align=True) row.label(text="Investigating your files") @@ -163,6 +157,28 @@ class FLAMENCO_PT_job_submission(bpy.types.Panel): ): layout.label(text=context.window_manager.flamenco_bat_status_txt) + def draw_submit_button( + self, context: bpy.types.Context, layout: bpy.types.UILayout + ) -> None: + row = layout.row(align=True) + + props = row.operator( + "flamenco.submit_job", + text="Submit to Flamenco", + icon="RENDER_ANIMATION", + ) + props.job_name = context.scene.flamenco_job_name + props.ignore_version_mismatch = False + + if context.window_manager.flamenco_version_mismatch: + props = row.operator( + "flamenco.submit_job", + text="Force Submit", + icon="NONE", + ) + props.job_name = context.scene.flamenco_job_name + props.ignore_version_mismatch = True + classes = (FLAMENCO_PT_job_submission,) register, unregister = bpy.utils.register_classes_factory(classes) diff --git a/addon/flamenco/operators.py b/addon/flamenco/operators.py index 830a37ba..a0916f1b 100644 --- a/addon/flamenco/operators.py +++ b/addon/flamenco/operators.py @@ -86,7 +86,9 @@ class FLAMENCO_OT_ping_manager(FlamencoOpMixin, bpy.types.Operator): api_client = self.get_api_client(context) prefs = preferences.get(context) - report, level = comms.ping_manager_with_report(context, api_client, prefs) + report, level = comms.ping_manager_with_report( + context.window_manager, api_client, prefs + ) self.report({level}, report) return {"FINISHED"} @@ -122,6 +124,10 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator): job_name: bpy.props.StringProperty(name="Job Name") # type: ignore job: Optional[_SubmittedJob] = None temp_blendfile: Optional[Path] = None + ignore_version_mismatch: bpy.props.BoolProperty( # type: ignore + name="Ignore Version Mismatch", + default=False, + ) timer: Optional[bpy.types.Timer] = None packthread: Optional[_PackThread] = None @@ -135,6 +141,16 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator): return job_type is not None def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set[str]: + # Before doing anything, make sure the info we cached about the Manager + # is up to date. A change in job storage directory on the Manager can + # cause nasty error messages when we submit, and it's better to just be + # ahead of the curve and refresh first. This also allows for checking + # the actual Manager version before submitting. + err = self._check_manager(context) + if err: + self.report({"WARNING"}, err) + return {"CANCELLED"} + filepath = self._save_blendfile(context) # Construct the Job locally before trying to pack. If any validations fail, better fail early. @@ -160,6 +176,51 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator): return self._on_bat_pack_msg(context, msg) + def _check_manager(self, context: bpy.types.Context) -> str: + """Check the Manager version & fetch the job storage directory. + + :return: an error string when something went wrong. + """ + from . import comms, preferences + + # Get the manager's info. This is cached in the preferences, so + # regardless of whether this function actually responds to version + # mismatches, it has to be called to also refresh the shared storage + # location. + api_client = self.get_api_client(context) + prefs = preferences.get(context) + mgrinfo = comms.ping_manager(context.window_manager, api_client, prefs) + if mgrinfo.error: + return mgrinfo.error + + # Check the Manager's version. + if not self.ignore_version_mismatch: + my_version = comms.flamenco_client_version() + assert mgrinfo.version is not None + + try: + mgrversion = mgrinfo.version.shortversion + except AttributeError: + # shortversion was introduced in Manager version 3.0-beta2, which + # may not be running here yet. + mgrversion = mgrinfo.version.version + if mgrversion != my_version: + context.window_manager.flamenco_version_mismatch = True + return ( + f"Manager ({mgrversion}) and this add-on ({my_version}) version " + + "mismatch, either update the add-on or force the submission" + ) + + # Un-set the 'flamenco_version_mismatch' when the versions match or when + # one forced submission is done. Each submission has to go through the + # same cycle of submitting, seeing the warning, then explicitly ignoring + # the mismatch, to make it a concious decision to keep going with + # potentially incompatible versions. + context.window_manager.flamenco_version_mismatch = False + + # Empty error message indicates 'ok'. + return "" + def _save_blendfile(self, context): """Save to a different file, specifically for Flamenco.