Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 113 additions & 108 deletions client/ayon_ftrack/event_handlers_user/action_applications.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
import os
import time
from typing import Optional
from urllib.parse import urlparse, urlunparse
from urllib.parse import urlencode

import ayon_api

from ayon_core.addon import AddonsManager
from ayon_core.lib import get_settings_variant
from ayon_core.lib.execute import (
run_detached_ayon_launcher_process,
clean_envs_for_ayon_process,
)

from ayon_ftrack.common import (
is_ftrack_enabled_in_settings,
get_folder_path_for_entities,
BaseAction,
)
from ayon_applications import (
ApplicationLaunchFailed,
ApplicationExecutableNotFound,
)
try:
from ayon_applications.utils import get_applications_for_context
from ayon_applications.utils import get_applications_action_info_for_task
except ImportError:
# Backwards compatibility for older ayon-applications addon
def get_applications_for_context(
project_name,
folder_entity,
task_entity,
project_settings,
project_entity,
):
ayon_project_apps = project_entity["attrib"].get("applications")
if ayon_project_apps:
return ayon_project_apps
return []
get_applications_action_info_for_task = None

IDENTIFIER_PREFIX = "application.launch."


class AppplicationsAction(BaseAction):
Expand Down Expand Up @@ -139,6 +132,14 @@ def _discover(self, event):
if items:
return {"items": items}

def _convert_icon(self, icon):
if icon["type"] != "url":
return None
icon_url = icon["url"]
if not icon_url.startswith("/"):
return icon_url
return f"{ayon_api.get_base_url()}/{icon_url.lstrip('/')}"

def discover(self, session, entities, event):
"""Return true if we can handle the selected entities.

Expand Down Expand Up @@ -183,68 +184,62 @@ def discover(self, session, entities, event):
task_entity = ayon_api.get_task_by_name(
project_name, folder_entity["id"], task_name
)

only_available = project_settings["applications"].get(
"only_available", False
)

app_names = get_applications_for_context(
project_name,
folder_entity,
task_entity,
project_settings=project_settings,
project_entity=ayon_project_entity
)
items = []
for app_name in app_names:
app = self.applications_manager.applications.get(app_name)
if not app or not app.enabled:
if get_applications_action_info_for_task is not None:
for app_info in get_applications_action_info_for_task(
project_name,
task_entity["id"],
task_entity["taskType"],
):
items.append({
"label": app_info.group_label,
"variant": app_info.variant_label,
"description": None,
"actionIdentifier": "|".join((
self.launch_identifier_with_id,
app_info.addon_name,
app_info.addon_version,
app_info.identifier,
)),
"icon": self._convert_icon(app_info.icon),
})
return items

variant = get_settings_variant()
for action in ayon_api.get_actions(
project_name,
entity_type="task",
entity_ids=[task_entity["id"]],
entity_subtypes=[task_entity["taskType"]],
variant=variant,
mode="simple",
):
if not action["identifier"].startswith(IDENTIFIER_PREFIX):
continue

# Skip applications without valid executables
if only_available and not app.find_executable():
continue
identifier = action["identifier"]
variant_label = action["label"]
group_label = action.get("groupLabel")
if not group_label:
group_label = variant_label or identifier
variant_label = None

app_icon = self.applications_addon.get_app_icon_url(
app.icon, server=False
)
addon_name = action["addonName"]
addon_version = action["addonVersion"]
items.append({
"label": app.group.label,
"variant": app.label,
"label": group_label,
"variant": variant_label,
"description": None,
"actionIdentifier": "{}.{}".format(
self.launch_identifier_with_id, app_name
),
"icon": self._get_icon_mapping(app_icon),
"actionIdentifier": "|".join((
self.launch_identifier_with_id,
addon_name,
addon_version,
identifier,
)),
"icon": self._convert_icon(action["icon"]),
})

return items

def _get_icon_mapping(self, icon: Optional[str]):
"""Get icon mapping.

This function does create and store mapping of icon url. Urls with
'127.0.0.1' IP address are replaced with 'localhost'. Otherwise
is icon kept as was.

"""
if not icon:
return icon

if icon not in self._icons_mapping:
# ftrack frontend does not allow redirect to IP address, but
# allows redirect to 'localhost'
result = urlparse(icon)
if result.hostname == "127.0.0.1":
port = ""
if result.port:
port = f":{result.port}"
icon = urlunparse(
result._replace(netloc=f"localhost{port}")
)
self._icons_mapping[icon] = icon
return self._icons_mapping[icon]

def _launch(self, event):
event_identifier = event["data"]["actionIdentifier"]
# Check if identifier is same
Expand Down Expand Up @@ -279,54 +274,64 @@ def launch(self, session, entities, event):

*event* the unmodified original event
"""
identifier = event["data"]["actionIdentifier"]
id_identifier_len = len(self.launch_identifier_with_id) + 1
app_name = identifier[id_identifier_len:]
parts = event["data"]["actionIdentifier"].split("|")
_ = parts.pop(0)
addon_name = parts.pop(0)
addon_version = parts.pop(0)
action_id = "|".join(parts)

conn = ayon_api.get_server_api_connection()
headers = conn.get_headers()
if "referer" in headers:
headers = None
else:
headers["referer"] = conn.get_base_url()

entity = entities[0]

task_name = entity["name"]
ft_project = self.get_project_from_entity(entity)
project_name = ft_project["full_name"]
folder_path = self._get_folder_path(session, entity["parent"])
project_name = entity["project"]["full_name"]
self.log.info(
f"ftrack launch app: \"{app_name}\""
f" on {project_name}{folder_path}/{task_name}"
task_name = entity["name"]
folder_entity = ayon_api.get_folder_by_path(project_name, folder_path)
task_entity = ayon_api.get_task_by_name(
project_name, folder_entity["id"], task_name
)
try:
self.applications_manager.launch(
app_name,
project_name=project_name,
folder_path=folder_path,
task_name=task_name
)

except ApplicationExecutableNotFound as exc:
self.log.warning(exc.exc_msg)
return {
"success": False,
"message": exc.msg
}
variant = get_settings_variant()
query = {
"addonName": addon_name,
"addonVersion": addon_version,
"identifier": action_id,
"variant": variant,
}
url = f"actions/execute?{urlencode(query)}"
request_data = {
"projectName": project_name,
"entityType": "task",
"entityIds": [task_entity["id"]],
}
response = ayon_api.raw_post(
url, headers=headers, json=request_data
)
response.raise_for_status()

except ApplicationLaunchFailed as exc:
self.log.error(str(exc))
data = response.data
if data["type"] != "launcher":
return {
"success": False,
"message": str(exc)
"message": "Not launched. Unknown action type."
}
uri = data["payload"]["uri"]

except Exception:
msg = "Unexpected failure of application launch {}".format(
self.label
)
self.log.error(msg, exc_info=True)
return {
"success": False,
"message": msg
}
# Remove bundles from environment variables
env = os.environ.copy()
env.pop("AYON_BUNDLE_NAME", None)
env.pop("AYON_STUDIO_BUNDLE_NAME", None)
env = clean_envs_for_ayon_process(env)
run_detached_ayon_launcher_process(uri, env=env)

return {
"success": True,
"message": "Launching {0}".format(self.label)
"message": "Application launched",
}

def _get_folder_path(self, session, entity):
Expand Down
Loading