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.
This commit is contained in:
Sybren A. Stüvel 2022-08-31 09:24:27 +02:00
parent 2eae682b9a
commit 59f41d0546
4 changed files with 148 additions and 27 deletions

View File

@ -37,6 +37,8 @@ def discard_global_flamenco_data(_):
job_types.discard_flamenco_data() job_types.discard_flamenco_data()
comms.discard_flamenco_data() comms.discard_flamenco_data()
bpy.context.WindowManager.flamenco_version_mismatch = False
def redraw(self, context): def redraw(self, context):
if context.area is None: if context.area is None:
@ -118,6 +120,11 @@ def register() -> None:
max=100, max=100,
update=redraw, 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, # Placeholder to contain the result of a 'ping' to Flamenco Manager,
# so that it can be shown in the preferences panel. # so that it can be shown in the preferences panel.

View File

@ -3,6 +3,7 @@
# <pep8 compliant> # <pep8 compliant>
import logging import logging
import dataclasses
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from urllib3.exceptions import HTTPError, MaxRetryError from urllib3.exceptions import HTTPError, MaxRetryError
@ -25,6 +26,23 @@ else:
_ManagerConfiguration = object _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: def flamenco_api_client(manager_url: str) -> _ApiClient:
"""Returns an API client for communicating with a Manager.""" """Returns an API client for communicating with a Manager."""
global _flamenco_client global _flamenco_client
@ -45,6 +63,18 @@ def flamenco_api_client(manager_url: str) -> _ApiClient:
return _flamenco_client 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(): def discard_flamenco_data():
global _flamenco_client global _flamenco_client
@ -57,7 +87,9 @@ def discard_flamenco_data():
def ping_manager_with_report( 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]: ) -> tuple[str, str]:
"""Ping the Manager, update preferences, and return a report as string. """Ping the Manager, update preferences, and return a report as string.
@ -67,26 +99,23 @@ def ping_manager_with_report(
`Operator.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) assert info.version is not None
if err: report = "%s version %s found" % (info.version.name, info.version.version)
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
return report, "INFO" return report, "INFO"
def ping_manager( def ping_manager(
api_client: _ApiClient, prefs: _FlamencoPreferences window_manager: bpy.types.WindowManager,
) -> tuple[Optional[_FlamencoVersion], Optional[_ManagerConfiguration], str]: api_client: _ApiClient,
"""Fetch Manager config & version, and update preferences. 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. # Do a late import, so that the API is only imported when actually used.
from flamenco.manager import ApiException from flamenco.manager import ApiException
@ -94,21 +123,29 @@ def ping_manager(
from flamenco.manager.models import FlamencoVersion, ManagerConfiguration from flamenco.manager.models import FlamencoVersion, ManagerConfiguration
meta_api = MetaApi(api_client) meta_api = MetaApi(api_client)
error = ""
try: try:
version: FlamencoVersion = meta_api.get_version() version: FlamencoVersion = meta_api.get_version()
config: ManagerConfiguration = meta_api.get_configuration() config: ManagerConfiguration = meta_api.get_configuration()
except ApiException as ex: except ApiException as ex:
return (None, None, "Manager cannot be reached: %s" % ex) error = "Manager cannot be reached: %s" % ex
except MaxRetryError as ex: except MaxRetryError as ex:
# This is the common error, when for example the port number is # This is the common error, when for example the port number is
# incorrect and nothing is listening. The exception text is not included # incorrect and nothing is listening. The exception text is not included
# because it's very long and confusing. # because it's very long and confusing.
return (None, None, "Manager cannot be reached") error = "Manager cannot be reached"
except HTTPError as ex: 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. # Store whether this Manager supports the Shaman API.
prefs.is_shaman_enabled = config.shaman_enabled prefs.is_shaman_enabled = config.shaman_enabled
prefs.job_storage = config.storage_location 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)

View File

@ -133,13 +133,7 @@ class FLAMENCO_PT_job_submission(bpy.types.Panel):
# Show current status of Flamenco. # Show current status of Flamenco.
flamenco_status = context.window_manager.flamenco_bat_status flamenco_status = context.window_manager.flamenco_bat_status
if flamenco_status in {"IDLE", "ABORTED", "DONE"}: if flamenco_status in {"IDLE", "ABORTED", "DONE"}:
ui = layout self.draw_submit_button(context, layout)
props = ui.operator(
"flamenco.submit_job",
text="Submit to Flamenco",
icon="RENDER_ANIMATION",
)
props.job_name = context.scene.flamenco_job_name
elif flamenco_status == "INVESTIGATING": elif flamenco_status == "INVESTIGATING":
row = layout.row(align=True) row = layout.row(align=True)
row.label(text="Investigating your files") 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) 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,) classes = (FLAMENCO_PT_job_submission,)
register, unregister = bpy.utils.register_classes_factory(classes) register, unregister = bpy.utils.register_classes_factory(classes)

View File

@ -86,7 +86,9 @@ class FLAMENCO_OT_ping_manager(FlamencoOpMixin, bpy.types.Operator):
api_client = self.get_api_client(context) api_client = self.get_api_client(context)
prefs = preferences.get(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) self.report({level}, report)
return {"FINISHED"} 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_name: bpy.props.StringProperty(name="Job Name") # type: ignore
job: Optional[_SubmittedJob] = None job: Optional[_SubmittedJob] = None
temp_blendfile: Optional[Path] = 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 timer: Optional[bpy.types.Timer] = None
packthread: Optional[_PackThread] = None packthread: Optional[_PackThread] = None
@ -135,6 +141,16 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):
return job_type is not None return job_type is not None
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set[str]: 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) filepath = self._save_blendfile(context)
# Construct the Job locally before trying to pack. If any validations fail, better fail early. # 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) 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): def _save_blendfile(self, context):
"""Save to a different file, specifically for Flamenco. """Save to a different file, specifically for Flamenco.