base implementation in loader tool

This commit is contained in:
Jakub Trllo 2025-08-21 16:41:30 +02:00
parent 234ac09f42
commit 12d4905b39
6 changed files with 162 additions and 58 deletions

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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):

View file

@ -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):

View file

@ -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",
)