diff --git a/openpype/modules/default_modules/clockify/clockify_module.py b/openpype/modules/default_modules/clockify/clockify_module.py index f82ae0d55db..0de62d8ba48 100644 --- a/openpype/modules/default_modules/clockify/clockify_module.py +++ b/openpype/modules/default_modules/clockify/clockify_module.py @@ -11,8 +11,7 @@ from openpype_interfaces import ( ITrayModule, IPluginPaths, - IFtrackEventHandlerPaths, - ITimersManager + IFtrackEventHandlerPaths ) @@ -20,8 +19,7 @@ class ClockifyModule( OpenPypeModule, ITrayModule, IPluginPaths, - IFtrackEventHandlerPaths, - ITimersManager + IFtrackEventHandlerPaths ): name = "clockify" @@ -39,6 +37,11 @@ def initialize(self, modules_settings): self.clockapi = ClockifyAPI(master_parent=self) + # TimersManager attributes + # - set `timers_manager_connector` only in `tray_init` + self.timers_manager_connector = None + self._timers_manager_module = None + def get_global_environments(self): return { "CLOCKIFY_WORKSPACE": self.workspace_name @@ -61,6 +64,9 @@ def tray_init(self): self.bool_timer_run = False self.bool_api_key_set = self.clockapi.set_api() + # Define itself as TimersManager connector + self.timers_manager_connector = self + def tray_start(self): if self.bool_api_key_set is False: self.show_settings() @@ -162,10 +168,6 @@ def check_running(self): self.set_menu_visibility() time.sleep(5) - def stop_timer(self): - """Implementation of ITimersManager.""" - self.clockapi.finish_time_entry() - def signed_in(self): if not self.timer_manager: return @@ -176,8 +178,60 @@ def signed_in(self): if self.timer_manager.is_running: self.start_timer_manager(self.timer_manager.last_task) + def on_message_widget_close(self): + self.message_widget = None + + # Definition of Tray menu + def tray_menu(self, parent_menu): + # Menu for Tray App + from Qt import QtWidgets + menu = QtWidgets.QMenu("Clockify", parent_menu) + menu.setProperty("submenu", "on") + + # Actions + action_show_settings = QtWidgets.QAction("Settings", menu) + action_stop_timer = QtWidgets.QAction("Stop timer", menu) + + menu.addAction(action_show_settings) + menu.addAction(action_stop_timer) + + action_show_settings.triggered.connect(self.show_settings) + action_stop_timer.triggered.connect(self.stop_timer) + + self.action_stop_timer = action_stop_timer + + self.set_menu_visibility() + + parent_menu.addMenu(menu) + + def show_settings(self): + self.widget_settings.input_api_key.setText(self.clockapi.get_api_key()) + self.widget_settings.show() + + def set_menu_visibility(self): + self.action_stop_timer.setVisible(self.bool_timer_run) + + # --- TimersManager connection methods --- + def register_timers_manager(self, timer_manager_module): + """Store TimersManager for future use.""" + self._timers_manager_module = timer_manager_module + + def timer_started(self, data): + """Tell TimersManager that timer started.""" + if self._timers_manager_module is not None: + self._timers_manager_module.timer_started(self._module.id, data) + + def timer_stopped(self): + """Tell TimersManager that timer stopped.""" + if self._timers_manager_module is not None: + self._timers_manager_module.timer_stopped(self._module.id) + + def stop_timer(self): + """Called from TimersManager to stop timer.""" + self.clockapi.finish_time_entry() + def start_timer(self, input_data): - """Implementation of ITimersManager.""" + """Called from TimersManager to start timer.""" # If not api key is not entered then skip if not self.clockapi.get_api_key(): return @@ -234,36 +288,3 @@ def start_timer(self, input_data): self.clockapi.start_time_entry( description, project_id, tag_ids=tag_ids ) - - def on_message_widget_close(self): - self.message_widget = None - - # Definition of Tray menu - def tray_menu(self, parent_menu): - # Menu for Tray App - from Qt import QtWidgets - menu = QtWidgets.QMenu("Clockify", parent_menu) - menu.setProperty("submenu", "on") - - # Actions - action_show_settings = QtWidgets.QAction("Settings", menu) - action_stop_timer = QtWidgets.QAction("Stop timer", menu) - - menu.addAction(action_show_settings) - menu.addAction(action_stop_timer) - - action_show_settings.triggered.connect(self.show_settings) - action_stop_timer.triggered.connect(self.stop_timer) - - self.action_stop_timer = action_stop_timer - - self.set_menu_visibility() - - parent_menu.addMenu(menu) - - def show_settings(self): - self.widget_settings.input_api_key.setText(self.clockapi.get_api_key()) - self.widget_settings.show() - - def set_menu_visibility(self): - self.action_stop_timer.setVisible(self.bool_timer_run) diff --git a/openpype/modules/default_modules/ftrack/ftrack_module.py b/openpype/modules/default_modules/ftrack/ftrack_module.py index 1de152535cd..3732e762b46 100644 --- a/openpype/modules/default_modules/ftrack/ftrack_module.py +++ b/openpype/modules/default_modules/ftrack/ftrack_module.py @@ -7,7 +7,6 @@ from openpype_interfaces import ( ITrayModule, IPluginPaths, - ITimersManager, ILaunchHookPaths, ISettingsChangeListener, IFtrackEventHandlerPaths @@ -21,7 +20,6 @@ class FtrackModule( OpenPypeModule, ITrayModule, IPluginPaths, - ITimersManager, ILaunchHookPaths, ISettingsChangeListener ): @@ -61,6 +59,10 @@ def initialize(self, settings): self.user_event_handlers_paths = user_event_handlers_paths self.tray_module = None + # TimersManager connection + self.timers_manager_connector = None + self._timers_manager_module = None + def get_global_environments(self): """Ftrack's global environments.""" return { @@ -102,16 +104,6 @@ def connect_with_modules(self, enabled_modules): elif key == "user": self.user_event_handlers_paths.extend(value) - def start_timer(self, data): - """Implementation of ITimersManager interface.""" - if self.tray_module: - self.tray_module.start_timer_manager(data) - - def stop_timer(self): - """Implementation of ITimersManager interface.""" - if self.tray_module: - self.tray_module.stop_timer_manager() - def on_system_settings_save( self, old_value, new_value, changes, new_value_metadata ): @@ -343,7 +335,10 @@ def create_ftrack_session(self, **session_kwargs): def tray_init(self): from .tray import FtrackTrayWrapper + self.tray_module = FtrackTrayWrapper(self) + # Module is it's own connector to TimersManager + self.timers_manager_connector = self def tray_menu(self, parent_menu): return self.tray_module.tray_menu(parent_menu) @@ -357,3 +352,23 @@ def tray_exit(self): def set_credentials_to_env(self, username, api_key): os.environ["FTRACK_API_USER"] = username or "" os.environ["FTRACK_API_KEY"] = api_key or "" + + # --- TimersManager connection methods --- + def start_timer(self, data): + if self.tray_module: + self.tray_module.start_timer_manager(data) + + def stop_timer(self): + if self.tray_module: + self.tray_module.stop_timer_manager() + + def register_timers_manager(self, timer_manager_module): + self._timers_manager_module = timer_manager_module + + def timer_started(self, data): + if self._timers_manager_module is not None: + self._timers_manager_module.timer_started(self.id, data) + + def timer_stopped(self): + if self._timers_manager_module is not None: + self._timers_manager_module.timer_stopped(self.id) diff --git a/openpype/modules/default_modules/timers_manager/interfaces.py b/openpype/modules/default_modules/timers_manager/interfaces.py deleted file mode 100644 index 179013cffe6..00000000000 --- a/openpype/modules/default_modules/timers_manager/interfaces.py +++ /dev/null @@ -1,26 +0,0 @@ -from abc import abstractmethod -from openpype.modules import OpenPypeInterface - - -class ITimersManager(OpenPypeInterface): - timer_manager_module = None - - @abstractmethod - def stop_timer(self): - pass - - @abstractmethod - def start_timer(self, data): - pass - - def timer_started(self, data): - if not self.timer_manager_module: - return - - self.timer_manager_module.timer_started(self.id, data) - - def timer_stopped(self): - if not self.timer_manager_module: - return - - self.timer_manager_module.timer_stopped(self.id) diff --git a/openpype/modules/default_modules/timers_manager/timers_manager.py b/openpype/modules/default_modules/timers_manager/timers_manager.py index 90c8a9c7db4..e2c421bcfe9 100644 --- a/openpype/modules/default_modules/timers_manager/timers_manager.py +++ b/openpype/modules/default_modules/timers_manager/timers_manager.py @@ -9,20 +9,90 @@ from avalon.api import AvalonMongoDB +class ExampleTimersManagerConnector: + """Timers manager can handle timers of multiple modules/addons. + + Module must have object under `timers_manager_connector` attribute with + few methods. This is example class of the object that could be stored under + module. + + Required methods are 'stop_timer' and 'start_timer'. + + # TODO pass asset document instead of `hierarchy` + Example of `data` that are passed during changing timer: + ``` + data = { + "project_name": project_name, + "task_name": task_name, + "task_type": task_type, + "hierarchy": hierarchy + } + ``` + """ + # Not needed at all + def __init__(self, module): + # Store timer manager module to be able call it's methods when needed + self._timers_manager_module = None + + # Store module which want to use timers manager to have access + self._module = module + + # Required + def stop_timer(self): + """Called by timers manager when module should stop timer.""" + self._module.stop_timer() + + # Required + def start_timer(self, data): + """Method called by timers manager when should start timer.""" + self._module.start_timer(data) + + # Optional + def register_timers_manager(self, timer_manager_module): + """Method called by timers manager where it's object is passed. + + This is moment when timers manager module can be store to be able + call it's callbacks (e.g. timer started). + """ + self._timers_manager_module = timer_manager_module + + # Custom implementation + def timer_started(self, data): + """This is example of possibility to trigger callbacks on manager.""" + if self._timers_manager_module is not None: + self._timers_manager_module.timer_started(self._module.id, data) + + # Custom implementation + def timer_stopped(self): + if self._timers_manager_module is not None: + self._timers_manager_module.timer_stopped(self._module.id) + + class TimersManager(OpenPypeModule, ITrayService, IIdleManager): """ Handles about Timers. Should be able to start/stop all timers at once. - If IdleManager is imported then is able to handle about stop timers - when user idles for a long time (set in presets). + + To be able use this advantage module has to have attribute with name + `timers_manager_connector` which has two methods 'stop_timer' + and 'start_timer'. Optionally may have `register_timers_manager` where + object of TimersManager module is passed to be able call it's callbacks. + + See `ExampleTimersManagerConnector`. """ name = "timers_manager" label = "Timers Service" + _required_methods = ( + "stop_timer", + "start_timer" + ) + def initialize(self, modules_settings): timers_settings = modules_settings[self.name] self.enabled = timers_settings["enabled"] + auto_stop = timers_settings["auto_stop"] # When timer will stop if idle manager is running (minutes) full_time = int(timers_settings["full_time"] * 60) @@ -41,7 +111,8 @@ def initialize(self, modules_settings): self.widget_user_idle = None self.signal_handler = None - self.modules = [] + self._connectors_by_module_id = {} + self._modules_by_id = {} def tray_init(self): from .widget_user_idle import WidgetUserIdle, SignalHandler @@ -96,17 +167,35 @@ def start_timer(self, project_name, asset_name, task_name, hierarchy): self.timer_started(None, data) def timer_started(self, source_id, data): - for module in self.modules: - if module.id != source_id: - module.start_timer(data) + for module_id, connector in self._connectors_by_module_id.items(): + if module_id == source_id: + continue + + try: + connector.start_timer(data) + except Exception: + self.log.info( + "Failed to start timer on connector {}".format( + str(connector) + ) + ) self.last_task = data self.is_running = True def timer_stopped(self, source_id): - for module in self.modules: - if module.id != source_id: - module.stop_timer() + for module_id, connector in self._connectors_by_module_id.items(): + if module_id == source_id: + continue + + try: + connector.stop_timer() + except Exception: + self.log.info( + "Failed to stop timer on connector {}".format( + str(connector) + ) + ) def restart_timers(self): if self.last_task is not None: @@ -120,15 +209,40 @@ def stop_timers(self): self.widget_user_idle.refresh_context() self.is_running = False - for module in self.modules: - module.stop_timer() + self.timer_stopper(None) def connect_with_modules(self, enabled_modules): for module in enabled_modules: - if not isinstance(module, ITimersManager): + connector = getattr(module, "timers_manager_connector", None) + if connector is None: + continue + + missing_methods = set() + for method_name in self._required_methods: + if not hasattr(connector, method_name): + missing_methods.add(method_name) + + if missing_methods: + joined = ", ".join( + ['"{}"'.format(name for name in missing_methods)] + ) + self.log.info(( + "Module \"{}\" has missing required methods {}." + ).format(module.name, joined)) continue - module.timer_manager_module = self - self.modules.append(module) + + self._connectors_by_module_id[module.id] = connector + self._modules_by_id[module.id] = module + + # Optional method + if hasattr(connector, "register_timers_manager"): + try: + connector.register_timers_manager(self) + except Exception: + self.log.info(( + "Failed to register timers manager" + " for connector of module \"{}\"." + ).format(module.name)) def callbacks_by_idle_time(self): """Implementation of IIdleManager interface."""