diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index baf6aabb69..9bff8dbb2d 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -317,6 +317,7 @@ class ActionItem: use 'identifier' and context, it necessary also use 'options'. Args: + plugin_identifier (str): Action identifier. identifier (str): Action identifier. entity_ids (set[str]): Entity ids. entity_type (str): Entity type. @@ -330,6 +331,7 @@ class ActionItem: """ def __init__( self, + plugin_identifier, identifier, entity_ids, entity_type, @@ -339,6 +341,7 @@ class ActionItem: options, order, ): + self.plugin_identifier = plugin_identifier self.identifier = identifier self.entity_ids = entity_ids self.entity_type = entity_type @@ -367,6 +370,7 @@ class ActionItem: def to_data(self): options = self._options_to_data() return { + "plugin_identifier": self.plugin_identifier, "identifier": self.identifier, "entity_ids": list(self.entity_ids), "entity_type": self.entity_type, @@ -992,11 +996,14 @@ class FrontendLoaderController(_BaseLoaderController): @abstractmethod def trigger_action_item( self, + plugin_identifier: str, identifier: str, options: dict[str, Any], project_name: str, entity_ids: set[str], entity_type: str, + selected_ids: set[str], + selected_entity_type: str, ): """Trigger action item. @@ -1014,11 +1021,14 @@ class FrontendLoaderController(_BaseLoaderController): } Args: - identifier (str): Action identifier. + plugin_identifier (sttr): Plugin identifier. + identifier (sttr): Action identifier. options (dict[str, Any]): Action option values from UI. project_name (str): Project name. - entity_ids (set[str]): Selected entity ids. - entity_type (str): Selected entity type. + entity_ids (set[str]): Entity ids stored on action item. + entity_type (str): Entity type stored on action item. + selected_ids (set[str]): Selected entity ids. + selected_entity_type (str): Selected entity type. """ pass diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index f05914da17..900eaf7656 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging import uuid +from typing import Any import ayon_api @@ -297,22 +298,25 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): action_items = self._loader_actions_model.get_action_items( project_name, entity_ids, entity_type ) - if entity_type == "representation": - site_sync_items = self._sitesync_model.get_sitesync_action_items( - project_name, entity_ids - ) - action_items.extend(site_sync_items) + + site_sync_items = self._sitesync_model.get_sitesync_action_items( + project_name, entity_ids, entity_type + ) + action_items.extend(site_sync_items) return action_items def trigger_action_item( self, - identifier, - options, - project_name, - entity_ids, - entity_type, + plugin_identifier: str, + identifier: str, + options: dict[str, Any], + project_name: str, + entity_ids: set[str], + entity_type: str, + selected_ids: set[str], + selected_entity_type: str, ): - if self._sitesync_model.is_sitesync_action(identifier): + if self._sitesync_model.is_sitesync_action(plugin_identifier): self._sitesync_model.trigger_action_item( identifier, project_name, @@ -321,11 +325,14 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): return self._loader_actions_model.trigger_action_item( + plugin_identifier, identifier, options, project_name, entity_ids, entity_type, + selected_ids, + selected_entity_type, ) # Selection model wrappers diff --git a/client/ayon_core/tools/loader/models/actions.py b/client/ayon_core/tools/loader/models/actions.py index 5dda2ef51f..e6ac328f92 100644 --- a/client/ayon_core/tools/loader/models/actions.py +++ b/client/ayon_core/tools/loader/models/actions.py @@ -9,7 +9,12 @@ from typing import Callable, Any import ayon_api -from ayon_core.lib import NestedCacheItem +from ayon_core.lib import NestedCacheItem, Logger +from ayon_core.pipeline.actions import ( + LoaderActionsContext, + LoaderActionSelection, + SelectionEntitiesCache, +) from ayon_core.pipeline.load import ( discover_loader_plugins, ProductLoaderPlugin, @@ -24,6 +29,7 @@ from ayon_core.pipeline.load import ( from ayon_core.tools.loader.abstract import ActionItem ACTIONS_MODEL_SENDER = "actions.model" +LOADER_PLUGIN_ID = "__loader_plugin__" NOT_SET = object() @@ -45,6 +51,7 @@ class LoaderActionsModel: loaders_cache_lifetime = 30 def __init__(self, controller): + self._log = Logger.get_logger(self.__class__.__name__) self._controller = controller self._current_context_project = NOT_SET self._loaders_by_identifier = NestedCacheItem( @@ -53,6 +60,7 @@ class LoaderActionsModel: levels=1, lifetime=self.loaders_cache_lifetime) self._repre_loaders = NestedCacheItem( levels=1, lifetime=self.loaders_cache_lifetime) + self._loader_actions = LoaderActionsContext() self._projects_cache = NestedCacheItem(levels=1, lifetime=60) self._folders_cache = NestedCacheItem(levels=2, lifetime=300) @@ -69,6 +77,7 @@ class LoaderActionsModel: self._loaders_by_identifier.reset() self._product_loaders.reset() self._repre_loaders.reset() + self._loader_actions.reset() self._folders_cache.reset() self._tasks_cache.reset() @@ -102,16 +111,25 @@ class LoaderActionsModel: version_context_by_id, repre_context_by_id ) + action_items.extend(self._get_loader_action_items( + project_name, + entity_ids, + entity_type, + )) + action_items.sort(key=self._actions_sorter) return action_items def trigger_action_item( self, + plugin_identifier: str, identifier: str, options: dict[str, Any], project_name: str, entity_ids: set[str], entity_type: str, + selected_ids: set[str], + selected_entity_type: str, ): """Trigger action by identifier. @@ -122,14 +140,18 @@ class LoaderActionsModel: happened. Args: - identifier (str): Loader identifier. + plugin_identifier (str): Plugin identifier. + identifier (str): Action identifier. options (dict[str, Any]): Loader option values. project_name (str): Project name. - entity_ids (set[str]): Entity ids. - entity_type (str): Entity type. + entity_ids (set[str]): Entity ids on action item. + entity_type (str): Entity type on action item. + selected_ids (set[str]): Selected entity ids. + selected_entity_type (str): Selected entity type. """ event_data = { + "plugin_identifier": plugin_identifier, "identifier": identifier, "id": uuid.uuid4().hex, } @@ -138,27 +160,52 @@ class LoaderActionsModel: event_data, ACTIONS_MODEL_SENDER, ) - loader = self._get_loader_by_identifier(project_name, identifier) + if plugin_identifier != LOADER_PLUGIN_ID: + # TODO fill error infor if any happens + error_info = [] + try: + self._loader_actions.execute_action( + plugin_identifier, + identifier, + entity_ids, + entity_type, + LoaderActionSelection( + project_name, + selected_ids, + selected_entity_type, + ), + {}, + ) - if entity_type == "version": - error_info = self._trigger_version_loader( - loader, - options, - project_name, - entity_ids, - ) - elif entity_type == "representation": - error_info = self._trigger_representation_loader( - loader, - options, - project_name, - entity_ids, - ) + except Exception: + self._log.warning( + f"Failed to execute action '{identifier}'", + exc_info=True, + ) else: - raise NotImplementedError( - f"Invalid entity type '{entity_type}' to trigger action item" + loader = self._get_loader_by_identifier( + project_name, identifier ) + if entity_type == "version": + error_info = self._trigger_version_loader( + loader, + options, + project_name, + entity_ids, + ) + elif entity_type == "representation": + error_info = self._trigger_representation_loader( + loader, + options, + project_name, + entity_ids, + ) + else: + raise NotImplementedError( + f"Invalid entity type '{entity_type}' to trigger action item" + ) + event_data["error_info"] = error_info self._controller.emit_event( "load.finished", @@ -278,8 +325,9 @@ class LoaderActionsModel: ): label = self._get_action_label(loader) if repre_name: - label = "{} ({})".format(label, repre_name) + label = f"{label} ({repre_name})" return ActionItem( + LOADER_PLUGIN_ID, get_loader_identifier(loader), entity_ids=entity_ids, entity_type=entity_type, @@ -456,8 +504,8 @@ class LoaderActionsModel: Returns: tuple[list[dict[str, Any]], list[dict[str, Any]]]: Version and representation contexts. - """ + """ version_context_by_id = {} repre_context_by_id = {} if not project_name and not repre_ids: @@ -710,6 +758,39 @@ class LoaderActionsModel: action_items.append(item) return action_items + + def _get_loader_action_items( + self, + project_name: str, + entity_ids: set[str], + entity_type: str, + ) -> list[ActionItem]: + # TODO prepare cached entities + # entities_cache = SelectionEntitiesCache(project_name) + selection = LoaderActionSelection( + project_name, + entity_ids, + entity_type, + # entities_cache=entities_cache + ) + items = [] + for action in self._loader_actions.get_action_items(selection): + label = action.label + if action.group_label: + label = f"{action.group_label} ({label})" + items.append(ActionItem( + action.plugin_identifier, + action.identifier, + action.entity_ids, + action.entity_type, + label, + action.icon, + None, # action.tooltip, + None, # action.options, + action.order, + )) + return items + def _trigger_version_loader( self, loader, diff --git a/client/ayon_core/tools/loader/models/sitesync.py b/client/ayon_core/tools/loader/models/sitesync.py index 3a54a1b5f8..bab8a68132 100644 --- a/client/ayon_core/tools/loader/models/sitesync.py +++ b/client/ayon_core/tools/loader/models/sitesync.py @@ -246,26 +246,32 @@ class SiteSyncModel: output[repre_id] = repre_cache.get_data() return output - def get_sitesync_action_items(self, project_name, representation_ids): + def get_sitesync_action_items( + self, project_name, entity_ids, entity_type + ): """ Args: project_name (str): Project name. - representation_ids (Iterable[str]): Representation ids. + entity_ids (set[str]): Selected entity ids. + entity_type (str): Selected entity type. Returns: list[ActionItem]: Actions that can be shown in loader. + """ + if entity_type != "representation": + return [] if not self.is_sitesync_enabled(project_name): return [] repres_status = self.get_representations_sync_status( - project_name, representation_ids + project_name, entity_ids ) repre_ids_per_identifier = collections.defaultdict(set) - for repre_id in representation_ids: + for repre_id in entity_ids: repre_status = repres_status[repre_id] local_status, remote_status = repre_status @@ -293,27 +299,23 @@ class SiteSyncModel: return action_items - def is_sitesync_action(self, identifier): + def is_sitesync_action(self, plugin_identifier: str) -> bool: """Should be `identifier` handled by SiteSync. Args: - identifier (str): Action identifier. + plugin_identifier (str): Plugin identifier. Returns: bool: Should action be handled by SiteSync. - """ - return identifier in { - UPLOAD_IDENTIFIER, - DOWNLOAD_IDENTIFIER, - REMOVE_IDENTIFIER, - } + """ + return plugin_identifier == "sitesync.loader.action" def trigger_action_item( self, - identifier, - project_name, - representation_ids + identifier: str, + project_name: str, + representation_ids: set[str], ): """Resets status for site_name or remove local files. @@ -321,8 +323,8 @@ class SiteSyncModel: identifier (str): Action identifier. project_name (str): Project name. representation_ids (Iterable[str]): Representation ids. - """ + """ active_site = self.get_active_site(project_name) remote_site = self.get_remote_site(project_name) @@ -482,6 +484,7 @@ class SiteSyncModel: icon_name ): return ActionItem( + "sitesync.loader.action", identifier, label, icon={ @@ -492,11 +495,8 @@ class SiteSyncModel: tooltip=tooltip, options={}, order=1, - project_name=project_name, - folder_ids=[], - product_ids=[], - version_ids=[], - representation_ids=representation_ids, + entity_ids=representation_ids, + entity_type="representation", ) def _add_site(self, project_name, repre_entity, site_name, product_type): diff --git a/client/ayon_core/tools/loader/ui/products_widget.py b/client/ayon_core/tools/loader/ui/products_widget.py index 4ed4368ab4..29bab7d0c5 100644 --- a/client/ayon_core/tools/loader/ui/products_widget.py +++ b/client/ayon_core/tools/loader/ui/products_widget.py @@ -438,11 +438,14 @@ class ProductsWidget(QtWidgets.QWidget): return self._controller.trigger_action_item( + action_item.plugin_identifier, action_item.identifier, options, project_name, action_item.entity_ids, action_item.entity_type, + version_ids, + "version", ) def _on_selection_change(self): diff --git a/client/ayon_core/tools/loader/ui/repres_widget.py b/client/ayon_core/tools/loader/ui/repres_widget.py index c0957d186c..d1d9c73a2b 100644 --- a/client/ayon_core/tools/loader/ui/repres_widget.py +++ b/client/ayon_core/tools/loader/ui/repres_widget.py @@ -399,9 +399,12 @@ class RepresentationsWidget(QtWidgets.QWidget): return self._controller.trigger_action_item( + action_item.plugin_identifier, action_item.identifier, options, self._selected_project_name, action_item.entity_ids, action_item.entity_type, + repre_ids, + "representation", )