mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #2418 from pypeclub/feature/OP-1172_Start-timer-post-launch-hook
TimersManager: Start timer post launch hook
This commit is contained in:
commit
9c39f8fff0
7 changed files with 214 additions and 69 deletions
|
|
@ -640,6 +640,10 @@ class LaunchHook:
|
|||
def app_name(self):
|
||||
return getattr(self.application, "full_name", None)
|
||||
|
||||
@property
|
||||
def modules_manager(self):
|
||||
return getattr(self.launch_context, "modules_manager", None)
|
||||
|
||||
def validate(self):
|
||||
"""Optional validation of launch hook on initialization.
|
||||
|
||||
|
|
@ -702,9 +706,13 @@ class ApplicationLaunchContext:
|
|||
"""
|
||||
|
||||
def __init__(self, application, executable, **data):
|
||||
from openpype.modules import ModulesManager
|
||||
|
||||
# Application object
|
||||
self.application = application
|
||||
|
||||
self.modules_manager = ModulesManager()
|
||||
|
||||
# Logger
|
||||
logger_name = "{}-{}".format(self.__class__.__name__, self.app_name)
|
||||
self.log = PypeLogger.get_logger(logger_name)
|
||||
|
|
@ -812,10 +820,7 @@ class ApplicationLaunchContext:
|
|||
paths.append(path)
|
||||
|
||||
# Load modules paths
|
||||
from openpype.modules import ModulesManager
|
||||
|
||||
manager = ModulesManager()
|
||||
paths.extend(manager.collect_launch_hook_paths())
|
||||
paths.extend(self.modules_manager.collect_launch_hook_paths())
|
||||
|
||||
return paths
|
||||
|
||||
|
|
|
|||
|
|
@ -1433,7 +1433,11 @@ def get_creator_by_name(creator_name, case_sensitive=False):
|
|||
|
||||
@with_avalon
|
||||
def change_timer_to_current_context():
|
||||
"""Called after context change to change timers"""
|
||||
"""Called after context change to change timers.
|
||||
|
||||
TODO:
|
||||
- use TimersManager's static method instead of reimplementing it here
|
||||
"""
|
||||
webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL")
|
||||
if not webserver_url:
|
||||
log.warning("Couldn't find webserver url")
|
||||
|
|
@ -1448,8 +1452,7 @@ def change_timer_to_current_context():
|
|||
data = {
|
||||
"project_name": avalon.io.Session["AVALON_PROJECT"],
|
||||
"asset_name": avalon.io.Session["AVALON_ASSET"],
|
||||
"task_name": avalon.io.Session["AVALON_TASK"],
|
||||
"hierarchy": get_hierarchy()
|
||||
"task_name": avalon.io.Session["AVALON_TASK"]
|
||||
}
|
||||
|
||||
requests.post(rest_api_url, json=data)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class PostFtrackHook(PostLaunchHook):
|
|||
)
|
||||
if entity:
|
||||
self.ftrack_status_change(session, entity, project_name)
|
||||
self.start_timer(session, entity, ftrack_api)
|
||||
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"Couldn't finish Ftrack procedure.", exc_info=True
|
||||
|
|
@ -160,26 +160,3 @@ class PostFtrackHook(PostLaunchHook):
|
|||
" on Ftrack entity type \"{}\""
|
||||
).format(next_status_name, entity.entity_type)
|
||||
self.log.warning(msg)
|
||||
|
||||
def start_timer(self, session, entity, _ftrack_api):
|
||||
"""Start Ftrack timer on task from context."""
|
||||
self.log.debug("Triggering timer start.")
|
||||
|
||||
user_entity = session.query("User where username is \"{}\"".format(
|
||||
os.environ["FTRACK_API_USER"]
|
||||
)).first()
|
||||
if not user_entity:
|
||||
self.log.warning(
|
||||
"Couldn't find user with username \"{}\" in Ftrack".format(
|
||||
os.environ["FTRACK_API_USER"]
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
user_entity.start_timer(entity, force=True)
|
||||
session.commit()
|
||||
self.log.debug("Timer start triggered successfully.")
|
||||
|
||||
except Exception:
|
||||
self.log.warning("Couldn't trigger Ftrack timer.", exc_info=True)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
class InvalidContextError(ValueError):
|
||||
"""Context for which the timer should be started is invalid."""
|
||||
pass
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
from openpype.lib import PostLaunchHook
|
||||
|
||||
|
||||
class PostStartTimerHook(PostLaunchHook):
|
||||
"""Start timer with TimersManager module.
|
||||
|
||||
This module requires enabled TimerManager module.
|
||||
"""
|
||||
order = None
|
||||
|
||||
def execute(self):
|
||||
project_name = self.data.get("project_name")
|
||||
asset_name = self.data.get("asset_name")
|
||||
task_name = self.data.get("task_name")
|
||||
|
||||
missing_context_keys = set()
|
||||
if not project_name:
|
||||
missing_context_keys.add("project_name")
|
||||
if not asset_name:
|
||||
missing_context_keys.add("asset_name")
|
||||
if not task_name:
|
||||
missing_context_keys.add("task_name")
|
||||
|
||||
if missing_context_keys:
|
||||
missing_keys_str = ", ".join([
|
||||
"\"{}\"".format(key) for key in missing_context_keys
|
||||
])
|
||||
self.log.debug("Hook {} skipped. Missing data keys: {}".format(
|
||||
self.__class__.__name__, missing_keys_str
|
||||
))
|
||||
return
|
||||
|
||||
timers_manager = self.modules_manager.modules_by_name.get(
|
||||
"timers_manager"
|
||||
)
|
||||
if not timers_manager or not timers_manager.enabled:
|
||||
self.log.info((
|
||||
"Skipping starting timer because"
|
||||
" TimersManager is not available."
|
||||
))
|
||||
return
|
||||
|
||||
timers_manager.start_timer_with_webserver(
|
||||
project_name, asset_name, task_name, logger=self.log
|
||||
)
|
||||
|
|
@ -39,17 +39,23 @@ class TimersManagerModuleRestApi:
|
|||
async def start_timer(self, request):
|
||||
data = await request.json()
|
||||
try:
|
||||
project_name = data['project_name']
|
||||
asset_name = data['asset_name']
|
||||
task_name = data['task_name']
|
||||
hierarchy = data['hierarchy']
|
||||
project_name = data["project_name"]
|
||||
asset_name = data["asset_name"]
|
||||
task_name = data["task_name"]
|
||||
except KeyError:
|
||||
log.error("Payload must contain fields 'project_name, " +
|
||||
"'asset_name', 'task_name', 'hierarchy'")
|
||||
return Response(status=400)
|
||||
msg = (
|
||||
"Payload must contain fields 'project_name,"
|
||||
" 'asset_name' and 'task_name'"
|
||||
)
|
||||
log.error(msg)
|
||||
return Response(status=400, message=msg)
|
||||
|
||||
self.module.stop_timers()
|
||||
self.module.start_timer(project_name, asset_name, task_name, hierarchy)
|
||||
try:
|
||||
self.module.start_timer(project_name, asset_name, task_name)
|
||||
except Exception as exc:
|
||||
return Response(status=404, message=str(exc))
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
async def stop_timer(self, request):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import os
|
||||
import platform
|
||||
from openpype.modules import OpenPypeModule
|
||||
from openpype_interfaces import ITrayService
|
||||
|
||||
from avalon.api import AvalonMongoDB
|
||||
|
||||
from openpype.modules import OpenPypeModule
|
||||
from openpype_interfaces import (
|
||||
ITrayService,
|
||||
ILaunchHookPaths
|
||||
)
|
||||
from .exceptions import InvalidContextError
|
||||
|
||||
|
||||
class ExampleTimersManagerConnector:
|
||||
"""Timers manager can handle timers of multiple modules/addons.
|
||||
|
|
@ -64,7 +70,7 @@ class ExampleTimersManagerConnector:
|
|||
self._timers_manager_module.timer_stopped(self._module.id)
|
||||
|
||||
|
||||
class TimersManager(OpenPypeModule, ITrayService):
|
||||
class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths):
|
||||
""" Handles about Timers.
|
||||
|
||||
Should be able to start/stop all timers at once.
|
||||
|
|
@ -151,47 +157,112 @@ class TimersManager(OpenPypeModule, ITrayService):
|
|||
self._idle_manager.stop()
|
||||
self._idle_manager.wait()
|
||||
|
||||
def start_timer(self, project_name, asset_name, task_name, hierarchy):
|
||||
"""
|
||||
Start timer for 'project_name', 'asset_name' and 'task_name'
|
||||
def get_timer_data_for_path(self, task_path):
|
||||
"""Convert string path to a timer data.
|
||||
|
||||
Called from REST api by hosts.
|
||||
|
||||
Args:
|
||||
project_name (string)
|
||||
asset_name (string)
|
||||
task_name (string)
|
||||
hierarchy (string)
|
||||
It is expected that first item is project name, last item is task name
|
||||
and parent asset name is before task name.
|
||||
"""
|
||||
path_items = task_path.split("/")
|
||||
if len(path_items) < 3:
|
||||
raise InvalidContextError("Invalid path \"{}\"".format(task_path))
|
||||
task_name = path_items.pop(-1)
|
||||
asset_name = path_items.pop(-1)
|
||||
project_name = path_items.pop(0)
|
||||
return self.get_timer_data_for_context(
|
||||
project_name, asset_name, task_name, self.log
|
||||
)
|
||||
|
||||
def get_launch_hook_paths(self):
|
||||
"""Implementation of `ILaunchHookPaths`."""
|
||||
return os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"launch_hooks"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_timer_data_for_context(
|
||||
project_name, asset_name, task_name, logger=None
|
||||
):
|
||||
"""Prepare data for timer related callbacks.
|
||||
|
||||
TODO:
|
||||
- return predefined object that has access to asset document etc.
|
||||
"""
|
||||
if not project_name or not asset_name or not task_name:
|
||||
raise InvalidContextError((
|
||||
"Missing context information got"
|
||||
" Project: \"{}\" Asset: \"{}\" Task: \"{}\""
|
||||
).format(str(project_name), str(asset_name), str(task_name)))
|
||||
|
||||
dbconn = AvalonMongoDB()
|
||||
dbconn.install()
|
||||
dbconn.Session["AVALON_PROJECT"] = project_name
|
||||
|
||||
asset_doc = dbconn.find_one({
|
||||
"type": "asset", "name": asset_name
|
||||
})
|
||||
asset_doc = dbconn.find_one(
|
||||
{
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
},
|
||||
{
|
||||
"data.tasks": True,
|
||||
"data.parents": True
|
||||
}
|
||||
)
|
||||
if not asset_doc:
|
||||
raise ValueError("Uknown asset {}".format(asset_name))
|
||||
dbconn.uninstall()
|
||||
raise InvalidContextError((
|
||||
"Asset \"{}\" not found in project \"{}\""
|
||||
).format(asset_name, project_name))
|
||||
|
||||
task_type = ''
|
||||
asset_data = asset_doc.get("data") or {}
|
||||
asset_tasks = asset_data.get("tasks") or {}
|
||||
if task_name not in asset_tasks:
|
||||
dbconn.uninstall()
|
||||
raise InvalidContextError((
|
||||
"Task \"{}\" not found on asset \"{}\" in project \"{}\""
|
||||
).format(task_name, asset_name, project_name))
|
||||
|
||||
task_type = ""
|
||||
try:
|
||||
task_type = asset_doc["data"]["tasks"][task_name]["type"]
|
||||
task_type = asset_tasks[task_name]["type"]
|
||||
except KeyError:
|
||||
self.log.warning("Couldn't find task_type for {}".
|
||||
format(task_name))
|
||||
msg = "Couldn't find task_type for {}".format(task_name)
|
||||
if logger is not None:
|
||||
logger.warning(msg)
|
||||
else:
|
||||
print(msg)
|
||||
|
||||
hierarchy = hierarchy.split("\\")
|
||||
hierarchy.append(asset_name)
|
||||
hierarchy_items = asset_data.get("parents") or []
|
||||
hierarchy_items.append(asset_name)
|
||||
|
||||
data = {
|
||||
dbconn.uninstall()
|
||||
return {
|
||||
"project_name": project_name,
|
||||
"task_name": task_name,
|
||||
"task_type": task_type,
|
||||
"hierarchy": hierarchy
|
||||
"hierarchy": hierarchy_items
|
||||
}
|
||||
|
||||
def start_timer(self, project_name, asset_name, task_name):
|
||||
"""Start timer for passed context.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name
|
||||
asset_name (str): Asset name
|
||||
task_name (str): Task name
|
||||
"""
|
||||
data = self.get_timer_data_for_context(
|
||||
project_name, asset_name, task_name, self.log
|
||||
)
|
||||
self.timer_started(None, data)
|
||||
|
||||
def get_task_time(self, project_name, asset_name, task_name):
|
||||
"""Get total time for passed context.
|
||||
|
||||
TODO:
|
||||
- convert context to timer data
|
||||
"""
|
||||
times = {}
|
||||
for module_id, connector in self._connectors_by_module_id.items():
|
||||
if hasattr(connector, "get_task_time"):
|
||||
|
|
@ -202,6 +273,10 @@ class TimersManager(OpenPypeModule, ITrayService):
|
|||
return times
|
||||
|
||||
def timer_started(self, source_id, data):
|
||||
"""Connector triggered that timer has started.
|
||||
|
||||
New timer has started for context in data.
|
||||
"""
|
||||
for module_id, connector in self._connectors_by_module_id.items():
|
||||
if module_id == source_id:
|
||||
continue
|
||||
|
|
@ -219,6 +294,14 @@ class TimersManager(OpenPypeModule, ITrayService):
|
|||
self.is_running = True
|
||||
|
||||
def timer_stopped(self, source_id):
|
||||
"""Connector triggered that hist timer has stopped.
|
||||
|
||||
Should stop all other timers.
|
||||
|
||||
TODO:
|
||||
- pass context for which timer has stopped to validate if timers are
|
||||
same and valid
|
||||
"""
|
||||
for module_id, connector in self._connectors_by_module_id.items():
|
||||
if module_id == source_id:
|
||||
continue
|
||||
|
|
@ -237,6 +320,7 @@ class TimersManager(OpenPypeModule, ITrayService):
|
|||
self.timer_started(None, self.last_task)
|
||||
|
||||
def stop_timers(self):
|
||||
"""Stop all timers."""
|
||||
if self.is_running is False:
|
||||
return
|
||||
|
||||
|
|
@ -295,18 +379,40 @@ class TimersManager(OpenPypeModule, ITrayService):
|
|||
self, server_manager
|
||||
)
|
||||
|
||||
def change_timer_from_host(self, project_name, asset_name, task_name):
|
||||
"""Prepared method for calling change timers on REST api"""
|
||||
@staticmethod
|
||||
def start_timer_with_webserver(
|
||||
project_name, asset_name, task_name, logger=None
|
||||
):
|
||||
"""Prepared method for calling change timers on REST api.
|
||||
|
||||
Webserver must be active. At the moment is Webserver running only when
|
||||
OpenPype Tray is used.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
asset_name (str): Asset name.
|
||||
task_name (str): Task name.
|
||||
logger (logging.Logger): Logger object. Using 'print' if not
|
||||
passed.
|
||||
"""
|
||||
webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL")
|
||||
if not webserver_url:
|
||||
self.log.warning("Couldn't find webserver url")
|
||||
msg = "Couldn't find webserver url"
|
||||
if logger is not None:
|
||||
logger.warning(msg)
|
||||
else:
|
||||
print(msg)
|
||||
return
|
||||
|
||||
rest_api_url = "{}/timers_manager/start_timer".format(webserver_url)
|
||||
try:
|
||||
import requests
|
||||
except Exception:
|
||||
self.log.warning("Couldn't start timer")
|
||||
msg = "Couldn't start timer ('requests' is not available)"
|
||||
if logger is not None:
|
||||
logger.warning(msg)
|
||||
else:
|
||||
print(msg)
|
||||
return
|
||||
data = {
|
||||
"project_name": project_name,
|
||||
|
|
@ -314,4 +420,4 @@ class TimersManager(OpenPypeModule, ITrayService):
|
|||
"task_name": task_name
|
||||
}
|
||||
|
||||
requests.post(rest_api_url, json=data)
|
||||
return requests.post(rest_api_url, json=data)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue