diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py deleted file mode 100644 index c8541a9bc3..0000000000 --- a/pype/hooks/celaction/prelaunch.py +++ /dev/null @@ -1,208 +0,0 @@ -import logging -import os -import winreg -import shutil -from pype.lib import PypeHook -from pype.api import ( - Anatomy, - Logger, - get_last_version_from_path -) - -from avalon import io, api, lib - -log = logging.getLogger(__name__) - - -class CelactionPrelaunchHook(PypeHook): - """ - This hook will check if current workfile path has Unreal - project inside. IF not, it initialize it and finally it pass - path to the project by environment variable to Unreal launcher - shell script. - """ - workfile_ext = "scn" - - def __init__(self, logger=None): - if not logger: - self.log = Logger().get_logger(self.__class__.__name__) - else: - self.log = logger - - self.signature = "( {} )".format(self.__class__.__name__) - - def execute(self, *args, env: dict = None) -> bool: - if not env: - env = os.environ - - # initialize - self._S = api.Session - - # get publish version of celaction - app = "celaction_publish" - - # get context variables - project = self._S["AVALON_PROJECT"] = env["AVALON_PROJECT"] - asset = self._S["AVALON_ASSET"] = env["AVALON_ASSET"] - task = self._S["AVALON_TASK"] = env["AVALON_TASK"] - workdir = self._S["AVALON_WORKDIR"] = env["AVALON_WORKDIR"] - - # get workfile path - anatomy_filled = self.get_anatomy_filled() - workfile = anatomy_filled["work"]["file"] - version = anatomy_filled["version"] - - # create workdir if doesn't exist - os.makedirs(workdir, exist_ok=True) - self.log.info(f"Work dir is: `{workdir}`") - - # get last version of workfile - workfile_last = env.get("AVALON_LAST_WORKFILE") - self.log.debug(f"_ workfile_last: `{workfile_last}`") - - if workfile_last: - workfile = workfile_last - - workfile_path = os.path.join(workdir, workfile) - - # copy workfile from template if doesnt exist any on path - if not os.path.isfile(workfile_path): - # try to get path from environment or use default - # from `pype.celation` dir - template_path = env.get("CELACTION_TEMPLATE") or os.path.join( - env.get("PYPE_MODULE_ROOT"), - "pype/hosts/celaction/celaction_template_scene.scn" - ) - self.log.info( - f"Creating workfile from template: `{template_path}`") - shutil.copy2( - os.path.normpath(template_path), - os.path.normpath(workfile_path) - ) - - self.log.info(f"Workfile to open: `{workfile_path}`") - - # adding compulsory environment var for openting file - env["PYPE_CELACTION_PROJECT_FILE"] = workfile_path - - # setting output parameters - path = r"Software\CelAction\CelAction2D\User Settings" - winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) - hKey = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - "Software\\CelAction\\CelAction2D\\User Settings", 0, - winreg.KEY_ALL_ACCESS) - - # TODO: change to root path and pyblish standalone to premiere way - pype_root_path = os.getenv("PYPE_SETUP_PATH") - path = os.path.join(pype_root_path, - "pype.bat") - - winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, path) - - parameters = [ - "launch", - f"--app {app}", - f"--project {project}", - f"--asset {asset}", - f"--task {task}", - "--currentFile \\\"\"*SCENE*\"\\\"", - "--chunk 10", - "--frameStart *START*", - "--frameEnd *END*", - "--resolutionWidth *X*", - "--resolutionHeight *Y*", - # "--programDir \"'*PROGPATH*'\"" - ] - winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, - " ".join(parameters)) - - # setting resolution parameters - path = r"Software\CelAction\CelAction2D\User Settings\Dialogs" - path += r"\SubmitOutput" - winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) - hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, - winreg.KEY_ALL_ACCESS) - winreg.SetValueEx(hKey, "SaveScene", 0, winreg.REG_DWORD, 1) - winreg.SetValueEx(hKey, "CustomX", 0, winreg.REG_DWORD, 1920) - winreg.SetValueEx(hKey, "CustomY", 0, winreg.REG_DWORD, 1080) - - # making sure message dialogs don't appear when overwriting - path = r"Software\CelAction\CelAction2D\User Settings\Messages" - path += r"\OverwriteScene" - winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) - hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, - winreg.KEY_ALL_ACCESS) - winreg.SetValueEx(hKey, "Result", 0, winreg.REG_DWORD, 6) - winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1) - - path = r"Software\CelAction\CelAction2D\User Settings\Messages" - path += r"\SceneSaved" - winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) - hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, - winreg.KEY_ALL_ACCESS) - winreg.SetValueEx(hKey, "Result", 0, winreg.REG_DWORD, 1) - winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1) - - return True - - def get_anatomy_filled(self): - root_path = api.registered_root() - project_name = self._S["AVALON_PROJECT"] - asset_name = self._S["AVALON_ASSET"] - - io.install() - project_entity = io.find_one({ - "type": "project", - "name": project_name - }) - assert project_entity, ( - "Project '{0}' was not found." - ).format(project_name) - log.debug("Collected Project \"{}\"".format(project_entity)) - - asset_entity = io.find_one({ - "type": "asset", - "name": asset_name, - "parent": project_entity["_id"] - }) - assert asset_entity, ( - "No asset found by the name '{0}' in project '{1}'" - ).format(asset_name, project_name) - - project_name = project_entity["name"] - - log.info( - "Anatomy object collected for project \"{}\".".format(project_name) - ) - - hierarchy_items = asset_entity["data"]["parents"] - hierarchy = "" - if hierarchy_items: - hierarchy = os.path.join(*hierarchy_items) - - template_data = { - "root": root_path, - "project": { - "name": project_name, - "code": project_entity["data"].get("code") - }, - "asset": asset_entity["name"], - "hierarchy": hierarchy.replace("\\", "/"), - "task": self._S["AVALON_TASK"], - "ext": self.workfile_ext, - "version": 1, - "username": os.getenv("PYPE_USERNAME", "").strip() - } - - avalon_app_name = os.environ.get("AVALON_APP_NAME") - if avalon_app_name: - application_def = lib.get_application(avalon_app_name) - app_dir = application_def.get("application_dir") - if app_dir: - template_data["app"] = app_dir - - anatomy = Anatomy(project_name) - anatomy_filled = anatomy.format_all(template_data).get_solved() - - return anatomy_filled diff --git a/pype/hooks/photoshop/prelaunch.py b/pype/hooks/photoshop/prelaunch.py deleted file mode 100644 index 4f00e4cd83..0000000000 --- a/pype/hooks/photoshop/prelaunch.py +++ /dev/null @@ -1,23 +0,0 @@ -import pype.lib -from pype.api import Logger - - -class PhotoshopPrelaunch(pype.lib.PypeHook): - """This hook will check for the existence of PyWin - - PyWin is a requirement for the Photoshop integration. - """ - project_code = None - - def __init__(self, logger=None): - if not logger: - self.log = Logger().get_logger(self.__class__.__name__) - else: - self.log = logger - - self.signature = "( {} )".format(self.__class__.__name__) - - def execute(self, *args, env: dict = None) -> bool: - output = pype.lib._subprocess(["pip", "install", "pywin32==227"]) - self.log.info(output) - return True diff --git a/pype/hooks/premiere/prelaunch.py b/pype/hooks/premiere/prelaunch.py deleted file mode 100644 index c0a65c0bf2..0000000000 --- a/pype/hooks/premiere/prelaunch.py +++ /dev/null @@ -1,161 +0,0 @@ -import os -import traceback -import winreg -from avalon import api, io, lib -from pype.lib import PypeHook -from pype.api import Logger, Anatomy -from pype.hosts.premiere import lib as prlib - - -class PremierePrelaunch(PypeHook): - """ - This hook will check if current workfile path has Adobe Premiere - project inside. IF not, it initialize it and finally it pass - path to the project by environment variable to Premiere launcher - shell script. - """ - project_code = None - reg_string_value = [{ - "path": r"Software\Adobe\CSXS.9", - "name": "PlayerDebugMode", - "type": winreg.REG_SZ, - "value": "1" - }] - - def __init__(self, logger=None): - if not logger: - self.log = Logger().get_logger(self.__class__.__name__) - else: - self.log = logger - - self.signature = "( {} )".format(self.__class__.__name__) - - def execute(self, *args, env: dict = None) -> bool: - - if not env: - env = os.environ - - # initialize - self._S = api.Session - - # get context variables - self._S["AVALON_PROJECT"] = env["AVALON_PROJECT"] - self._S["AVALON_ASSET"] = env["AVALON_ASSET"] - task = self._S["AVALON_TASK"] = env["AVALON_TASK"] - - # get workfile path - anatomy_filled = self.get_anatomy_filled() - - # if anatomy template should have different root for particular task - # just add for example > work[conforming]: - workfile_search_key = f"work[{task.lower()}]" - workfile_key = anatomy_filled.get( - workfile_search_key, - anatomy_filled.get("work") - ) - workdir = env["AVALON_WORKDIR"] = workfile_key["folder"] - - # create workdir if doesn't exist - os.makedirs(workdir, exist_ok=True) - self.log.info(f"Work dir is: `{workdir}`") - - # adding project code to env - env["AVALON_PROJECT_CODE"] = self.project_code - - # add keys to registry - self.modify_registry() - - # start avalon - try: - __import__("pype.hosts.premiere") - __import__("pyblish") - - except ImportError as e: - print(traceback.format_exc()) - print("pyblish: Could not load integration: %s " % e) - - else: - # Premiere Setup integration - prlib.setup(env) - - return True - - def modify_registry(self): - # adding key to registry - for key in self.reg_string_value: - winreg.CreateKey(winreg.HKEY_CURRENT_USER, key["path"]) - rg_key = winreg.OpenKey( - key=winreg.HKEY_CURRENT_USER, - sub_key=key["path"], - reserved=0, - access=winreg.KEY_ALL_ACCESS) - - winreg.SetValueEx( - rg_key, - key["name"], - 0, - key["type"], - key["value"] - ) - - def get_anatomy_filled(self): - root_path = api.registered_root() - project_name = self._S["AVALON_PROJECT"] - asset_name = self._S["AVALON_ASSET"] - - io.install() - project_entity = io.find_one({ - "type": "project", - "name": project_name - }) - assert project_entity, ( - "Project '{0}' was not found." - ).format(project_name) - self.log.debug("Collected Project \"{}\"".format(project_entity)) - - asset_entity = io.find_one({ - "type": "asset", - "name": asset_name, - "parent": project_entity["_id"] - }) - assert asset_entity, ( - "No asset found by the name '{0}' in project '{1}'" - ).format(asset_name, project_name) - - project_name = project_entity["name"] - self.project_code = project_entity["data"].get("code") - - self.log.info( - "Anatomy object collected for project \"{}\".".format(project_name) - ) - - hierarchy_items = asset_entity["data"]["parents"] - hierarchy = "" - if hierarchy_items: - hierarchy = os.path.join(*hierarchy_items) - - template_data = { - "root": root_path, - "project": { - "name": project_name, - "code": self.project_code - }, - "asset": asset_entity["name"], - "hierarchy": hierarchy.replace("\\", "/"), - "task": self._S["AVALON_TASK"], - "ext": "ppro", - "version": 1, - "username": os.getenv("PYPE_USERNAME", "").strip() - } - - avalon_app_name = os.environ.get("AVALON_APP_NAME") - if avalon_app_name: - application_def = lib.get_application(avalon_app_name) - app_dir = application_def.get("application_dir") - if app_dir: - template_data["app"] = app_dir - - anatomy = Anatomy(project_name) - anatomy_filled = anatomy.format_all(template_data).get_solved() - - return anatomy_filled diff --git a/pype/hooks/tvpaint/prelaunch.py b/pype/hooks/tvpaint/prelaunch.py deleted file mode 100644 index 0b3899f555..0000000000 --- a/pype/hooks/tvpaint/prelaunch.py +++ /dev/null @@ -1,136 +0,0 @@ -import os -import shutil -import platform -import pype.lib -from pype.api import Anatomy, Logger -import getpass -import avalon.api - - -class TvpaintPrelaunchHook(pype.lib.PypeHook): - """ - Workfile preparation hook - """ - host_name = "tvpaint" - - def __init__(self, logger=None): - if not logger: - self.log = Logger().get_logger(self.__class__.__name__) - else: - self.log = logger - - self.signature = "( {} )".format(self.__class__.__name__) - - def install_pywin(self): - if platform.system().lower() != "windows": - return - - try: - from win32com.shell import shell - except Exception: - output = pype.lib._subprocess(["pip", "install", "pywin32==227"]) - self.log.info(output) - - def execute(self, *args, env: dict = None) -> bool: - if not env: - env = os.environ - - self.install_pywin() - - # get context variables - project_name = env["AVALON_PROJECT"] - asset_name = env["AVALON_ASSET"] - task_name = env["AVALON_TASK"] - workdir = env["AVALON_WORKDIR"] - extension = avalon.api.HOST_WORKFILE_EXTENSIONS[self.host_name][0] - - # get workfile path - workfile_path = self.get_anatomy_filled( - workdir, project_name, asset_name, task_name) - - # create workdir if doesn't exist - os.makedirs(workdir, exist_ok=True) - self.log.info(f"Work dir is: `{workdir}`") - - # get last version of workfile - workfile_last = env.get("AVALON_LAST_WORKFILE") - self.log.debug(f"_ workfile_last: `{workfile_last}`") - - if workfile_last: - workfile = workfile_last - workfile_path = os.path.join(workdir, workfile) - - # copy workfile from template if doesnt exist any on path - if not os.path.isfile(workfile_path): - # try to get path from environment or use default - # from `pype.hosts.tvpaint` dir - template_path = env.get("TVPAINT_TEMPLATE") or os.path.join( - env.get("PYPE_MODULE_ROOT"), - "pype/hosts/tvpaint/template.tvpp" - ) - - # try to get template from project config folder - proj_config_path = os.path.join( - env["PYPE_PROJECT_CONFIGS"], project_name) - if os.path.exists(proj_config_path): - - template_file = None - for f in os.listdir(proj_config_path): - if extension in os.path.splitext(f): - template_file = f - - if template_file: - template_path = os.path.join( - proj_config_path, template_file) - self.log.info( - f"Creating workfile from template: `{template_path}`") - - # copy template to new destinantion - shutil.copy2( - os.path.normpath(template_path), - os.path.normpath(workfile_path) - ) - - self.log.info(f"Workfile to open: `{workfile_path}`") - - # adding compulsory environment var for openting file - env["PYPE_TVPAINT_PROJECT_FILE"] = workfile_path - - return True - - def get_anatomy_filled(self, workdir, project_name, asset_name, task_name): - dbcon = avalon.api.AvalonMongoDB() - dbcon.install() - dbcon.Session["AVALON_PROJECT"] = project_name - project_document = dbcon.find_one({"type": "project"}) - asset_document = dbcon.find_one({ - "type": "asset", - "name": asset_name - }) - dbcon.uninstall() - - asset_doc_parents = asset_document["data"].get("parents") - hierarchy = "/".join(asset_doc_parents) - - data = { - "project": { - "name": project_document["name"], - "code": project_document["data"].get("code") - }, - "task": task_name, - "asset": asset_name, - "app": self.host_name, - "hierarchy": hierarchy - } - anatomy = Anatomy(project_name) - extensions = avalon.api.HOST_WORKFILE_EXTENSIONS[self.host_name] - file_template = anatomy.templates["work"]["file"] - data.update({ - "version": 1, - "user": os.environ.get("PYPE_USERNAME") or getpass.getuser(), - "ext": extensions[0] - }) - - return avalon.api.last_workfile( - workdir, file_template, data, extensions, True - ) diff --git a/pype/hooks/unreal/unreal_prelaunch.py b/pype/hooks/unreal/unreal_prelaunch.py deleted file mode 100644 index 9f0cc45b96..0000000000 --- a/pype/hooks/unreal/unreal_prelaunch.py +++ /dev/null @@ -1,83 +0,0 @@ -import logging -import os - -from pype.lib import PypeHook -from pype.hosts.unreal import lib as unreal_lib -from pype.api import Logger - -log = logging.getLogger(__name__) - - -class UnrealPrelaunch(PypeHook): - """ - This hook will check if current workfile path has Unreal - project inside. IF not, it initialize it and finally it pass - path to the project by environment variable to Unreal launcher - shell script. - """ - - def __init__(self, logger=None): - if not logger: - self.log = Logger().get_logger(self.__class__.__name__) - else: - self.log = logger - - self.signature = "( {} )".format(self.__class__.__name__) - - def execute(self, *args, env: dict = None) -> bool: - if not env: - env = os.environ - asset = env["AVALON_ASSET"] - task = env["AVALON_TASK"] - workdir = env["AVALON_WORKDIR"] - engine_version = env["AVALON_APP_NAME"].split("_")[-1] - project_name = f"{asset}_{task}" - - # Unreal is sensitive about project names longer then 20 chars - if len(project_name) > 20: - self.log.warning((f"Project name exceed 20 characters " - f"({project_name})!")) - - # Unreal doesn't accept non alphabet characters at the start - # of the project name. This is because project name is then used - # in various places inside c++ code and there variable names cannot - # start with non-alpha. We append 'P' before project name to solve it. - # 😱 - if not project_name[:1].isalpha(): - self.log.warning(f"Project name doesn't start with alphabet " - f"character ({project_name}). Appending 'P'") - project_name = f"P{project_name}" - - project_path = os.path.join(workdir, project_name) - - self.log.info((f"{self.signature} requested UE4 version: " - f"[ {engine_version} ]")) - - detected = unreal_lib.get_engine_versions() - detected_str = ', '.join(detected.keys()) or 'none' - self.log.info((f"{self.signature} detected UE4 versions: " - f"[ {detected_str} ]")) - del(detected_str) - engine_version = ".".join(engine_version.split(".")[:2]) - if engine_version not in detected.keys(): - self.log.error((f"{self.signature} requested version not " - f"detected [ {engine_version} ]")) - return False - - os.makedirs(project_path, exist_ok=True) - - project_file = os.path.join(project_path, f"{project_name}.uproject") - engine_path = detected[engine_version] - if not os.path.isfile(project_file): - self.log.info((f"{self.signature} creating unreal " - f"project [ {project_name} ]")) - if env.get("AVALON_UNREAL_PLUGIN"): - os.environ["AVALON_UNREAL_PLUGIN"] = env.get("AVALON_UNREAL_PLUGIN") # noqa: E501 - unreal_lib.create_unreal_project(project_name, - engine_version, - project_path, - engine_path=engine_path) - - env["PYPE_UNREAL_PROJECT_FILE"] = project_file - env["AVALON_CURRENT_UNREAL_ENGINE"] = engine_path - return True diff --git a/pype/lib/__init__.py b/pype/lib/__init__.py index 29ed375fe1..02b5178311 100644 --- a/pype/lib/__init__.py +++ b/pype/lib/__init__.py @@ -41,8 +41,6 @@ from .avalon_context import ( BuildWorkfile ) -from .hooks import PypeHook, execute_hook - from .applications import ( ApplicationLaunchFailed, ApplictionExecutableNotFound, diff --git a/pype/lib/hooks.py b/pype/lib/hooks.py deleted file mode 100644 index bb5406572e..0000000000 --- a/pype/lib/hooks.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package containing code for handling hooks.""" -import os -import sys -import types -import logging -from abc import ABCMeta, abstractmethod - -import six - - -log = logging.getLogger(__name__) - - -@six.add_metaclass(ABCMeta) -class PypeHook: - """Abstract class from all hooks should inherit.""" - - def __init__(self): - """Constructor.""" - pass - - @abstractmethod - def execute(self, *args, **kwargs): - """Abstract execute method.""" - pass - - -def execute_hook(hook, *args, **kwargs): - """Execute hook with arguments. - - This will load hook file, instantiate class and call - :meth:`PypeHook.execute` method on it. Hook must be in a form:: - - $PYPE_SETUP_PATH/repos/pype/path/to/hook.py/HookClass - - This will load `hook.py`, instantiate HookClass and then execute_hook - `execute(*args, **kwargs)` - - Args: - hook (str): path to hook class. - - """ - class_name = hook.split("/")[-1] - - abspath = os.path.join(os.getenv('PYPE_SETUP_PATH'), - 'repos', 'pype', *hook.split("/")[:-1]) - - mod_name, mod_ext = os.path.splitext(os.path.basename(abspath)) - - if not mod_ext == ".py": - return False - - module = types.ModuleType(mod_name) - module.__file__ = abspath - - try: - with open(abspath, errors='ignore') as f: - six.exec_(f.read(), module.__dict__) - - sys.modules[abspath] = module - - except Exception as exp: - log.exception("loading hook failed: {}".format(exp), - exc_info=True) - return False - - obj = getattr(module, class_name) - hook_obj = obj() - ret_val = hook_obj.execute(*args, **kwargs) - return ret_val diff --git a/pype/modules/ftrack/ftrack_server/event_server_cli.py b/pype/modules/ftrack/ftrack_server/event_server_cli.py index bf51c37290..dbf2a2dc10 100644 --- a/pype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/pype/modules/ftrack/ftrack_server/event_server_cli.py @@ -14,9 +14,13 @@ import uuid import ftrack_api import pymongo -from pype.modules.ftrack.lib import credentials +from pype.modules.ftrack.lib import ( + credentials, + get_ftrack_url_from_settings +) from pype.modules.ftrack.ftrack_server.lib import ( - check_ftrack_url, get_ftrack_event_mongo_info + check_ftrack_url, + get_ftrack_event_mongo_info ) import socket_thread @@ -87,25 +91,6 @@ def validate_credentials(url, user, api): return True -def process_event_paths(event_paths): - print('DEBUG: Processing event paths: {}.'.format(str(event_paths))) - return_paths = [] - not_found = [] - if not event_paths: - return return_paths, not_found - - if isinstance(event_paths, str): - event_paths = event_paths.split(os.pathsep) - - for path in event_paths: - if os.path.exists(path): - return_paths.append(path) - else: - not_found.append(path) - - return os.pathsep.join(return_paths), not_found - - def legacy_server(ftrack_url): # Current file file_path = os.path.dirname(os.path.realpath(__file__)) @@ -537,16 +522,20 @@ def main(argv): "environment: $CLOCKIFY_WORKSPACE)" ) ) - ftrack_url = os.environ.get('FTRACK_SERVER') - username = os.environ.get('FTRACK_API_USER') - api_key = os.environ.get('FTRACK_API_KEY') - event_paths = os.environ.get('FTRACK_EVENTS_PATH') + ftrack_url = os.environ.get("FTRACK_SERVER") + username = os.environ.get("FTRACK_API_USER") + api_key = os.environ.get("FTRACK_API_KEY") kwargs, args = parser.parse_known_args(argv) if kwargs.ftrackurl: ftrack_url = kwargs.ftrackurl + # Load Ftrack url from settings if not set + if not ftrack_url: + ftrack_url = get_ftrack_url_from_settings() + + event_paths = None if kwargs.ftrackeventpaths: event_paths = kwargs.ftrackeventpaths @@ -568,6 +557,7 @@ def main(argv): os.environ["CLOCKIFY_API_KEY"] = kwargs.clockifyapikey legacy = kwargs.legacy + # Check url regex and accessibility ftrack_url = check_ftrack_url(ftrack_url) if not ftrack_url: @@ -579,19 +569,6 @@ def main(argv): print('Exiting! < Please enter valid credentials >') return 1 - # Process events path - event_paths, not_found = process_event_paths(event_paths) - if not_found: - print( - 'WARNING: These paths were not found: {}'.format(str(not_found)) - ) - if not event_paths: - if not_found: - print('ERROR: Any of entered paths is valid or can be accesible.') - else: - print('ERROR: Paths to events are not set. Exiting.') - return 1 - if kwargs.storecred: credentials.save_credentials(username, api_key, ftrack_url) @@ -599,7 +576,10 @@ def main(argv): os.environ["FTRACK_SERVER"] = ftrack_url os.environ["FTRACK_API_USER"] = username os.environ["FTRACK_API_KEY"] = api_key - os.environ["FTRACK_EVENTS_PATH"] = event_paths + if event_paths: + if isinstance(event_paths, (list, tuple)): + event_paths = os.pathsep.join(event_paths) + os.environ["FTRACK_EVENTS_PATH"] = event_paths if legacy: return legacy_server(ftrack_url) diff --git a/pype/modules/ftrack/ftrack_server/ftrack_server.py b/pype/modules/ftrack/ftrack_server/ftrack_server.py index ec39bdc50f..af48bfadc8 100644 --- a/pype/modules/ftrack/ftrack_server/ftrack_server.py +++ b/pype/modules/ftrack/ftrack_server/ftrack_server.py @@ -32,7 +32,7 @@ PYTHONPATH # Path to ftrack_api and paths to all modules used in actions class FtrackServer: - def __init__(self, server_type='action'): + def __init__(self, handler_paths=None, server_type='action'): """ - 'type' is by default set to 'action' - Runs Action server - enter 'event' for Event server @@ -47,14 +47,15 @@ class FtrackServer: ftrack_log = logging.getLogger("ftrack_api") ftrack_log.setLevel(logging.WARNING) - env_key = "FTRACK_ACTIONS_PATH" - if server_type.lower() == 'event': - env_key = "FTRACK_EVENTS_PATH" + self.stopped = True + self.is_running = False + + self.handler_paths = handler_paths or [] self.server_type = server_type - self.env_key = env_key def stop_session(self): + self.stopped = True if self.session.event_hub.connected is True: self.session.event_hub.disconnect() self.session.close() @@ -107,10 +108,6 @@ class FtrackServer: " in registered paths: \"{}\"" ).format("| ".join(paths))) - # Load presets for setting plugins - key = "user" - if self.server_type.lower() == "event": - key = "server" # TODO replace with settings or get rid of passing the dictionary plugins_presets = {} @@ -132,25 +129,37 @@ class FtrackServer: ) log.warning(msg, exc_info=True) + def set_handler_paths(self, paths): + self.handler_paths = paths + if self.is_running: + self.stop_session() + self.run_server() + + elif not self.stopped: + self.run_server() + def run_server(self, session=None, load_files=True): + self.stopped = False + self.is_running = True if not session: session = ftrack_api.Session(auto_connect_event_hub=True) self.session = session - if load_files: - paths_str = os.environ.get(self.env_key) - if paths_str is None: - log.error(( - "Env var \"{}\" is not set, \"{}\" server won\'t launch" - ).format(self.env_key, self.server_type)) + if not self.handler_paths: + log.warning(( + "Paths to event handlers are not set." + " Ftrack server won't launch." + )) + self.is_running = False return - paths = paths_str.split(os.pathsep) - self.set_files(paths) + self.set_files(self.handler_paths) - log.info(60*"*") - log.info('Registration of actions/events has finished!') + msg = "Registration of event handlers has finished!" + log.info(len(msg) * "*") + log.info(msg) # keep event_hub on session running self.session.event_hub.wait() + self.is_running = False diff --git a/pype/modules/ftrack/ftrack_server/lib.py b/pype/modules/ftrack/ftrack_server/lib.py index 79b708b17a..436e15f497 100644 --- a/pype/modules/ftrack/ftrack_server/lib.py +++ b/pype/modules/ftrack/ftrack_server/lib.py @@ -19,39 +19,15 @@ import ftrack_api._centralized_storage_scenario import ftrack_api.event from ftrack_api.logging import LazyLogMessage as L -from pype.api import ( - Logger, - get_default_components, - decompose_url, - compose_url -) +from pype.modules.ftrack.lib import get_ftrack_event_mongo_info from .custom_db_connector import CustomDbConnector - +from pype.api import Logger TOPIC_STATUS_SERVER = "pype.event.server.status" TOPIC_STATUS_SERVER_RESULT = "pype.event.server.status.result" -def get_ftrack_event_mongo_info(): - database_name = ( - os.environ.get("FTRACK_EVENTS_MONGO_DB") or "pype" - ) - collection_name = ( - os.environ.get("FTRACK_EVENTS_MONGO_COL") or "ftrack_events" - ) - - mongo_url = os.environ.get("FTRACK_EVENTS_MONGO_URL") - if mongo_url is not None: - components = decompose_url(mongo_url) - else: - components = get_default_components() - - uri = compose_url(**components) - - return uri, components["port"], database_name, collection_name - - def check_ftrack_url(url, log_errors=True): """Checks if Ftrack server is responding""" if not url: diff --git a/pype/modules/ftrack/ftrack_server/sub_event_processor.py b/pype/modules/ftrack/ftrack_server/sub_event_processor.py index c719c8fd08..6cf0ccf63b 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_processor.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_processor.py @@ -6,11 +6,16 @@ import datetime from ftrack_server import FtrackServer from pype.modules.ftrack.ftrack_server.lib import ( - SocketSession, ProcessEventHub, TOPIC_STATUS_SERVER + SocketSession, + ProcessEventHub, + TOPIC_STATUS_SERVER ) -import ftrack_api +from pype.modules.ftrack.lib import get_server_event_handler_paths + from pype.api import Logger +import ftrack_api + log = Logger().get_logger("Event processor") subprocess_started = datetime.datetime.now() @@ -55,26 +60,6 @@ def register(session): ) -def clockify_module_registration(): - api_key = os.environ.get("CLOCKIFY_API_KEY") - if not api_key: - log.warning("Clockify API key is not set.") - return - - workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") - if not workspace_name: - log.warning("Clockify Workspace is not set.") - return - - from pype.modules.clockify.constants import CLOCKIFY_FTRACK_SERVER_PATH - - current = os.environ.get("FTRACK_EVENTS_PATH") or "" - if current: - current += os.pathsep - os.environ["FTRACK_EVENTS_PATH"] = current + CLOCKIFY_FTRACK_SERVER_PATH - return True - - def main(args): port = int(args[-1]) # Create a TCP/IP socket @@ -86,11 +71,8 @@ def main(args): sock.connect(server_address) sock.sendall(b"CreatedProcess") - try: - clockify_module_registration() - except Exception: - log.info("Clockify registration failed.", exc_info=True) + returncode = 0 try: session = SocketSession( auto_connect_event_hub=True, sock=sock, Eventhub=ProcessEventHub @@ -98,17 +80,19 @@ def main(args): register(session) SessionFactory.session = session - server = FtrackServer("event") + event_handler_paths = get_server_event_handler_paths() + server = FtrackServer(event_handler_paths, "event") log.debug("Launched Ftrack Event processor") server.run_server(session) except Exception: + returncode = 1 log.error("Event server crashed. See traceback below", exc_info=True) finally: log.debug("First closing socket") sock.close() - return 1 + return returncode if __name__ == "__main__": diff --git a/pype/modules/ftrack/ftrack_server/sub_event_status.py b/pype/modules/ftrack/ftrack_server/sub_event_status.py index 00a6687de3..a398b019eb 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_status.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_status.py @@ -9,8 +9,10 @@ import datetime import ftrack_api from ftrack_server import FtrackServer from pype.modules.ftrack.ftrack_server.lib import ( - SocketSession, StatusEventHub, - TOPIC_STATUS_SERVER, TOPIC_STATUS_SERVER_RESULT + SocketSession, + StatusEventHub, + TOPIC_STATUS_SERVER, + TOPIC_STATUS_SERVER_RESULT ) from pype.api import Logger @@ -368,7 +370,7 @@ def main(args): ObjectFactory.session = session session.event_hub.heartbeat_callbacks.append(heartbeat) register(session) - server = FtrackServer("event") + server = FtrackServer(server_type="event") log.debug("Launched Ftrack Event statuser") server.run_server(session, load_files=False) diff --git a/pype/modules/ftrack/ftrack_server/sub_event_storer.py b/pype/modules/ftrack/ftrack_server/sub_event_storer.py index 2f4395c8db..3523e5701f 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_storer.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_storer.py @@ -8,10 +8,12 @@ import pymongo import ftrack_api from ftrack_server import FtrackServer from pype.modules.ftrack.ftrack_server.lib import ( - SocketSession, StorerEventHub, - get_ftrack_event_mongo_info, - TOPIC_STATUS_SERVER, TOPIC_STATUS_SERVER_RESULT + SocketSession, + StorerEventHub, + TOPIC_STATUS_SERVER, + TOPIC_STATUS_SERVER_RESULT ) +from pype.modules.ftrack.lib import get_ftrack_event_mongo_info from pype.modules.ftrack.ftrack_server.custom_db_connector import ( CustomDbConnector ) @@ -193,7 +195,7 @@ def main(args): ) SessionFactory.session = session register(session) - server = FtrackServer("event") + server = FtrackServer(server_type="event") log.debug("Launched Ftrack Event storer") server.run_server(session, load_files=False) diff --git a/pype/modules/ftrack/ftrack_server/sub_legacy_server.py b/pype/modules/ftrack/ftrack_server/sub_legacy_server.py index c37727a784..bd5962d04a 100644 --- a/pype/modules/ftrack/ftrack_server/sub_legacy_server.py +++ b/pype/modules/ftrack/ftrack_server/sub_legacy_server.py @@ -7,6 +7,9 @@ import threading from ftrack_server import FtrackServer import ftrack_api from pype.api import Logger +from pype.modules.ftrack.ftrack_server.lib import ( + get_server_event_handler_paths +) log = Logger().get_logger("Event Server Legacy") @@ -62,7 +65,8 @@ class TimerChecker(threading.Thread): def main(args): check_thread = None try: - server = FtrackServer("event") + event_handler_paths = get_server_event_handler_paths() + server = FtrackServer(event_handler_paths, "event") session = ftrack_api.Session(auto_connect_event_hub=True) check_thread = TimerChecker(server, session) diff --git a/pype/modules/ftrack/ftrack_server/sub_user_server.py b/pype/modules/ftrack/ftrack_server/sub_user_server.py index f4b81922eb..2686c74c2e 100644 --- a/pype/modules/ftrack/ftrack_server/sub_user_server.py +++ b/pype/modules/ftrack/ftrack_server/sub_user_server.py @@ -2,13 +2,12 @@ import sys import signal import socket -import traceback - from ftrack_server import FtrackServer from pype.modules.ftrack.ftrack_server.lib import ( SocketSession, SocketBaseEventHub ) +from pype.modules.ftrack.lib import get_user_event_handler_paths from pype.api import Logger @@ -33,11 +32,13 @@ def main(args): session = SocketSession( auto_connect_event_hub=True, sock=sock, Eventhub=SocketBaseEventHub ) - server = FtrackServer("action") - log.debug("Launched User Ftrack Server") + event_handler_paths = get_user_event_handler_paths() + server = FtrackServer(event_handler_paths, "action") + log.debug("Launching User Ftrack Server") server.run_server(session=session) + except Exception: - traceback.print_exception(*sys.exc_info()) + log.warning("Ftrack session server failed.", exc_info=True) finally: log.debug("Closing socket") diff --git a/pype/modules/ftrack/lib/__init__.py b/pype/modules/ftrack/lib/__init__.py index 3890eacf90..bb79faf0cb 100644 --- a/pype/modules/ftrack/lib/__init__.py +++ b/pype/modules/ftrack/lib/__init__.py @@ -1,14 +1,36 @@ +from . settings import ( + FTRACK_MODULE_DIR, + SERVER_HANDLERS_DIR, + USER_HANDLERS_DIR, + get_ftrack_url_from_settings, + get_server_event_handler_paths, + get_user_event_handler_paths, + get_ftrack_event_mongo_info +) from . import avalon_sync from . import credentials from .ftrack_base_handler import BaseHandler from .ftrack_event_handler import BaseEvent from .ftrack_action_handler import BaseAction, ServerAction, statics_icon + __all__ = ( + "FTRACK_MODULE_DIR", + "SERVER_HANDLERS_DIR", + "USER_HANDLERS_DIR", + "get_ftrack_url_from_settings", + "get_server_event_handler_paths", + "get_user_event_handler_paths", + "get_ftrack_event_mongo_info", + "avalon_sync", + "credentials", + "BaseHandler", + "BaseEvent", + "BaseAction", "ServerAction", "statics_icon" diff --git a/pype/modules/ftrack/lib/settings.py b/pype/modules/ftrack/lib/settings.py new file mode 100644 index 0000000000..e8cff482db --- /dev/null +++ b/pype/modules/ftrack/lib/settings.py @@ -0,0 +1,109 @@ +import os +from pype.api import ( + Logger, + get_system_settings, + get_default_components, + decompose_url, + compose_url +) + +log = Logger().get_logger(__name__) + +FTRACK_MODULE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SERVER_HANDLERS_DIR = os.path.join(FTRACK_MODULE_DIR, "events") +USER_HANDLERS_DIR = os.path.join(FTRACK_MODULE_DIR, "actions") + + +def get_ftrack_settings(): + return get_system_settings()["modules"]["Ftrack"] + + +def get_ftrack_url_from_settings(): + return get_ftrack_settings()["ftrack_server"] + + +def get_server_event_handler_paths(): + paths = [] + # Environment variable overrides settings + if "FTRACK_EVENTS_PATH" in os.environ: + env_paths = os.environ.get("FTRACK_EVENTS_PATH") + paths.extend(env_paths.split(os.pathsep)) + return paths + + # Add pype's default dir + paths.append(SERVER_HANDLERS_DIR) + # Add additional paths from settings + paths.extend( + get_ftrack_settings()["ftrack_events_path"] + ) + try: + clockify_path = clockify_event_path() + if clockify_path: + paths.append(clockify_path) + except Exception: + log.warning("Clockify paths function failed.", exc_info=True) + + # Filter only existing paths + _paths = [] + for path in paths: + if os.path.exists(path): + _paths.append(path) + else: + log.warning(( + "Registered event handler path is not accessible: {}" + ).format(path)) + return _paths + + +def get_user_event_handler_paths(): + paths = [] + # Add pype's default dir + paths.append(USER_HANDLERS_DIR) + # Add additional paths from settings + paths.extend( + get_ftrack_settings()["ftrack_actions_path"] + ) + + # Filter only existing paths + _paths = [] + for path in paths: + if os.path.exists(path): + _paths.append(path) + else: + log.warning(( + "Registered event handler path is not accessible: {}" + ).format(path)) + return _paths + + +def clockify_event_path(): + api_key = os.environ.get("CLOCKIFY_API_KEY") + if not api_key: + log.warning("Clockify API key is not set.") + return + + workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") + if not workspace_name: + log.warning("Clockify Workspace is not set.") + return + + from pype.modules.clockify.constants import CLOCKIFY_FTRACK_SERVER_PATH + + return CLOCKIFY_FTRACK_SERVER_PATH + + +def get_ftrack_event_mongo_info(): + ftrack_settings = get_ftrack_settings() + database_name = ftrack_settings["mongo_database_name"] + collection_name = ftrack_settings["mongo_collection_name"] + + # TODO add possibility to set in settings and use PYPE_MONGO_URL if not set + mongo_url = os.environ.get("FTRACK_EVENTS_MONGO_URL") + if mongo_url is not None: + components = decompose_url(mongo_url) + else: + components = get_default_components() + + uri = compose_url(**components) + + return uri, components["port"], database_name, collection_name diff --git a/pype/modules/ftrack/tray/ftrack_module.py b/pype/modules/ftrack/tray/ftrack_module.py index 0b011c5b33..36ce1eec9f 100644 --- a/pype/modules/ftrack/tray/ftrack_module.py +++ b/pype/modules/ftrack/tray/ftrack_module.py @@ -10,7 +10,7 @@ from ..ftrack_server import socket_thread from ..lib import credentials from . import login_dialog -from pype.api import Logger, resources +from pype.api import Logger, resources, get_system_settings log = Logger().get_logger("FtrackModule", "ftrack") @@ -29,6 +29,8 @@ class FtrackModule: self.bool_action_thread_running = False self.bool_timer_event = False + self.load_ftrack_url() + self.widget_login = login_dialog.CredentialsDialog() self.widget_login.login_changed.connect(self.on_login_change) self.widget_login.logout_signal.connect(self.on_logout) @@ -292,6 +294,15 @@ class FtrackModule: def tray_exit(self): self.stop_action_server() + def load_ftrack_url(self): + ftrack_url = ( + get_system_settings() + ["modules"] + ["Ftrack"] + ["ftrack_server"] + ) + os.environ["FTRACK_SERVER"] = ftrack_url + # Definition of visibility of each menu actions def set_menu_visibility(self): self.tray_server_menu.menuAction().setVisible(self.bool_logged) diff --git a/pype/modules/ftrack/tray/login_dialog.py b/pype/modules/ftrack/tray/login_dialog.py index 94ad29e478..6c7373e337 100644 --- a/pype/modules/ftrack/tray/login_dialog.py +++ b/pype/modules/ftrack/tray/login_dialog.py @@ -126,20 +126,26 @@ class CredentialsDialog(QtWidgets.QDialog): self.setLayout(main) + def show(self, *args, **kwargs): + super(CredentialsDialog, self).show(*args, **kwargs) + self.fill_ftrack_url() + def fill_ftrack_url(self): url = os.getenv("FTRACK_SERVER") + if url == self.ftsite_input.text(): + return + checked_url = self.check_url(url) + self.ftsite_input.setText(checked_url or "") - if checked_url is None: - checked_url = "" - self.btn_login.setEnabled(False) - self.btn_ftrack_login.setEnabled(False) + enabled = bool(checked_url) - self.api_input.setEnabled(False) - self.user_input.setEnabled(False) - self.ftsite_input.setEnabled(False) + self.btn_login.setEnabled(enabled) + self.btn_ftrack_login.setEnabled(enabled) - self.ftsite_input.setText(checked_url) + self.api_input.setEnabled(enabled) + self.user_input.setEnabled(enabled) + self.ftsite_input.setEnabled(enabled) def set_advanced_mode(self, is_advanced): self._in_advance_mode = is_advanced diff --git a/pype/settings/defaults/system_settings/general.json b/pype/settings/defaults/system_settings/general.json index 636d86331b..4e0358f447 100644 --- a/pype/settings/defaults/system_settings/general.json +++ b/pype/settings/defaults/system_settings/general.json @@ -20,7 +20,8 @@ "PYPE_PROJECT_CONFIGS", "PYPE_PYTHON_EXE", "PYPE_OCIO_CONFIG", - "PYBLISH_GUI" + "PYBLISH_GUI", + "PYBLISHPLUGINPATH" ] }, "FFMPEG_PATH": { @@ -45,6 +46,9 @@ "darwin": "{VIRTUAL_ENV}/bin/python" }, "PYPE_OCIO_CONFIG": "{STUDIO_SOFT}/OpenColorIO-Configs", - "PYBLISH_GUI": "pyblish_pype" + "PYBLISH_GUI": "pyblish_pype", + "PYBLISHPLUGINPATH": [ + "{PYPE_MODULE_ROOT}/pype/plugins/ftrack/publish" + ] } } \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/modules.json b/pype/settings/defaults/system_settings/modules.json index 74268c9254..93c099a43e 100644 --- a/pype/settings/defaults/system_settings/modules.json +++ b/pype/settings/defaults/system_settings/modules.json @@ -43,8 +43,8 @@ "ftrack_server": "https://pype.ftrackapp.com", "ftrack_actions_path": [], "ftrack_events_path": [], - "FTRACK_EVENTS_MONGO_DB": "pype", - "FTRACK_EVENTS_MONGO_COL": "ftrack_events", + "mongo_database_name": "pype", + "mongo_collection_name": "ftrack_events", "intent": { "items": { "-": "-", @@ -131,29 +131,6 @@ "read_security_role": [] } } - }, - "environment": { - "__environment_keys__": { - "ftrack": [ - "FTRACK_ACTIONS_PATH", - "FTRACK_EVENTS_PATH", - "PYBLISHPLUGINPATH", - "PYTHONPATH" - ] - }, - "FTRACK_ACTIONS_PATH": [ - "{PYPE_MODULE_ROOT}/pype/modules/ftrack/actions" - ], - "FTRACK_EVENTS_PATH": [ - "{PYPE_MODULE_ROOT}/pype/modules/ftrack/events" - ], - "PYBLISHPLUGINPATH": [ - "{PYPE_MODULE_ROOT}/pype/plugins/ftrack/publish" - ], - "PYTHONPATH": [ - "{PYPE_MODULE_ROOT}/pype/vendor", - "{PYTHONPATH}" - ] } }, "Rest Api": { @@ -202,4 +179,4 @@ "Idle Manager": { "enabled": true } -} +} \ No newline at end of file diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/module_settings/schema_ftrack.json b/pype/tools/settings/settings/gui_schemas/system_schema/module_settings/schema_ftrack.json index 58cd81f544..5459379bcb 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/module_settings/schema_ftrack.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/module_settings/schema_ftrack.json @@ -43,12 +43,12 @@ }, { "type": "text", - "key": "FTRACK_EVENTS_MONGO_DB", + "key": "mongo_database_name", "label": "Event Mongo DB" }, { "type": "text", - "key": "FTRACK_EVENTS_MONGO_COL", + "key": "mongo_collection_name", "label": "Events Mongo Collection" }, { @@ -151,14 +151,5 @@ }] } }] - }, - { - "type": "splitter" - }, - { - "key": "environment", - "label": "Environment", - "type": "raw-json", - "env_group_key": "ftrack" }] }