Esempio n. 1
0
class SocketThread(threading.Thread):
    """Thread that checks suprocess of storer of processor of events"""

    MAX_TIMEOUT = int(os.environ.get("PYPE_FTRACK_SOCKET_TIMEOUT", 45))

    def __init__(self, name, port, filepath, additional_args=[]):
        super(SocketThread, self).__init__()
        self.log = Logger().get_logger(self.__class__.__name__)
        self.setName(name)
        self.name = name
        self.port = port
        self.filepath = filepath
        self.additional_args = additional_args

        self.sock = None
        self.subproc = None
        self.connection = None
        self._is_running = False
        self.finished = False

        self.mongo_error = False

        self._temp_data = {}

    def stop(self):
        self._is_running = False

    def run(self):
        self._is_running = True
        time_socket = time.time()
        # Create a TCP/IP socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock = sock

        # Bind the socket to the port - skip already used ports
        while True:
            try:
                server_address = ("localhost", self.port)
                sock.bind(server_address)
                break
            except OSError:
                self.port += 1

        self.log.debug(
            "Running Socked thread on {}:{}".format(*server_address))

        env = os.environ.copy()
        env["PYPE_PROCESS_MONGO_ID"] = str(Logger.mongo_process_id)
        # Pype executable (with path to start script if not build)
        args = get_pype_execute_args(
            # Add `run` command
            "run",
            self.filepath,
            *self.additional_args,
            str(self.port))
        self.subproc = subprocess.Popen(args, env=env, stdin=subprocess.PIPE)

        # Listen for incoming connections
        sock.listen(1)
        sock.settimeout(1.0)
        while True:
            if not self._is_running:
                break
            try:
                connection, client_address = sock.accept()
                time_socket = time.time()
                connection.settimeout(1.0)
                self.connection = connection

            except socket.timeout:
                if (time.time() - time_socket) > self.MAX_TIMEOUT:
                    self.log.error("Connection timeout passed. Terminating.")
                    self._is_running = False
                    self.subproc.terminate()
                    break
                continue

            try:
                time_con = time.time()
                # Receive the data in small chunks and retransmit it
                while True:
                    try:
                        if not self._is_running:
                            break
                        data = None
                        try:
                            data = self.get_data_from_con(connection)
                            time_con = time.time()

                        except socket.timeout:
                            if (time.time() - time_con) > self.MAX_TIMEOUT:
                                self.log.error(
                                    "Connection timeout passed. Terminating.")
                                self._is_running = False
                                self.subproc.terminate()
                                break
                            continue

                        except ConnectionResetError:
                            self._is_running = False
                            break

                        self._handle_data(connection, data)

                    except Exception as exc:
                        self.log.error("Event server process failed",
                                       exc_info=True)

            finally:
                # Clean up the connection
                connection.close()
                if self.subproc.poll() is None:
                    self.subproc.terminate()

                self.finished = True

    def get_data_from_con(self, connection):
        return connection.recv(16)

    def _handle_data(self, connection, data):
        if not data:
            return

        if data == b"MongoError":
            self.mongo_error = True
        connection.sendall(data)
Esempio n. 2
0
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

    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

        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 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
Esempio n. 3
0
class BaseHandler(object):
    '''Custom Action base class

    <label> - a descriptive string identifing your action.
    <varaint>   - To group actions together, give them the same
                  label and specify a unique variant per action.
    <identifier>  - a unique identifier for app.
    <description>   - a verbose descriptive text for you action
    <icon>  - icon in ftrack
    '''
    # Default priority is 100
    priority = 100
    # Type is just for logging purpose (e.g.: Action, Event, Application,...)
    type = 'No-type'
    ignore_me = False
    preactions = []

    def __init__(self, session, plugins_presets=None):
        '''Expects a ftrack_api.Session instance'''
        self.log = Logger().get_logger(self.__class__.__name__)
        if not (isinstance(session, ftrack_api.session.Session)
                or isinstance(session, SocketSession)):
            raise Exception(
                ("Session object entered with args is instance of \"{}\""
                 " but expected instances are \"{}\" and \"{}\"").format(
                     str(type(session)), str(ftrack_api.session.Session),
                     str(SocketSession)))

        self._session = session

        # Using decorator
        self.register = self.register_decorator(self.register)
        self.launch = self.launch_log(self.launch)
        if plugins_presets is None:
            plugins_presets = {}
        self.plugins_presets = plugins_presets

    # Decorator
    def register_decorator(self, func):
        @functools.wraps(func)
        def wrapper_register(*args, **kwargs):

            presets_data = self.plugins_presets.get(self.__class__.__name__)
            if presets_data:
                for key, value in presets_data.items():
                    if not hasattr(self, key):
                        continue
                    setattr(self, key, value)

            if self.ignore_me:
                return

            label = self.__class__.__name__
            if hasattr(self, 'label'):
                if self.variant is None:
                    label = self.label
                else:
                    label = '{} {}'.format(self.label, self.variant)
            try:
                self._preregister()

                start_time = time.perf_counter()
                func(*args, **kwargs)
                end_time = time.perf_counter()
                run_time = end_time - start_time
                self.log.info(
                    ('{} "{}" - Registered successfully ({:.4f}sec)').format(
                        self.type, label, run_time))
            except MissingPermision as MPE:
                self.log.info(
                    ('!{} "{}" - You\'re missing required {} permissions'
                     ).format(self.type, label, str(MPE)))
            except AssertionError as ae:
                self.log.warning(
                    ('!{} "{}" - {}').format(self.type, label, str(ae)))
            except NotImplementedError:
                self.log.error(
                    ('{} "{}" - Register method is not implemented').format(
                        self.type, label))
            except PreregisterException as exc:
                self.log.warning(
                    ('{} "{}" - {}').format(self.type, label, str(exc)))
            except Exception as e:
                self.log.error('{} "{}" - Registration failed ({})'.format(
                    self.type, label, str(e)))

        return wrapper_register

    # Decorator
    def launch_log(self, func):
        @functools.wraps(func)
        def wrapper_launch(*args, **kwargs):
            label = self.__class__.__name__
            if hasattr(self, 'label'):
                label = self.label
                if hasattr(self, 'variant'):
                    if self.variant is not None:
                        label = '{} {}'.format(self.label, self.variant)

            self.log.info(('{} "{}": Launched').format(self.type, label))
            try:
                return func(*args, **kwargs)
            except Exception as exc:
                self.session.rollback()
                msg = '{} "{}": Failed ({})'.format(self.type, label, str(exc))
                self.log.error(msg, exc_info=True)
                return {'success': False, 'message': msg}
            finally:
                self.log.info(('{} "{}": Finished').format(self.type, label))

        return wrapper_launch

    @property
    def session(self):
        '''Return current session.'''
        return self._session

    def reset_session(self):
        self.session.reset()

    def _preregister(self):
        if hasattr(self, "role_list") and len(self.role_list) > 0:
            username = self.session.api_user
            user = self.session.query(
                'User where username is "{}"'.format(username)).one()
            available = False
            lowercase_rolelist = [x.lower() for x in self.role_list]
            for role in user['user_security_roles']:
                if role['security_role']['name'].lower() in lowercase_rolelist:
                    available = True
                    break
            if available is False:
                raise MissingPermision

        # Custom validations
        result = self.preregister()
        if result is None:
            self.log.debug(
                ("\"{}\" 'preregister' method returned 'None'. Expected it"
                 " didn't fail and continue as preregister returned True."
                 ).format(self.__class__.__name__))
            return

        if result is True:
            return
        msg = None
        if isinstance(result, str):
            msg = result
        raise PreregisterException(msg)

    def preregister(self):
        '''
        Preregister conditions.
        Registration continues if returns True.
        '''
        return True

    def register(self):
        '''
        Registers the action, subscribing the discover and launch topics.
        Is decorated by register_log
        '''

        raise NotImplementedError()

    def _translate_event(self, event, session=None):
        '''Return *event* translated structure to be used with the API.'''
        if session is None:
            session = self.session

        _entities = event['data'].get('entities_object', None)
        if (_entities is None or _entities[0].get('link', None)
                == ftrack_api.symbol.NOT_SET):
            _entities = self._get_entities(event)
            event['data']['entities_object'] = _entities

        return _entities

    def _get_entities(self, event, session=None, ignore=None):
        entities = []
        selection = event['data'].get('selection')
        if not selection:
            return entities

        if ignore is None:
            ignore = []
        elif isinstance(ignore, str):
            ignore = [ignore]

        filtered_selection = []
        for entity in selection:
            if entity['entityType'] not in ignore:
                filtered_selection.append(entity)

        if not filtered_selection:
            return entities

        if session is None:
            session = self.session
            session._local_cache.clear()

        for entity in filtered_selection:
            entities.append(
                session.get(self._get_entity_type(entity, session),
                            entity.get('entityId')))

        return entities

    def _get_entity_type(self, entity, session=None):
        '''Return translated entity type tht can be used with API.'''
        # Get entity type and make sure it is lower cased. Most places except
        # the component tab in the Sidebar will use lower case notation.
        entity_type = entity.get('entityType').replace('_', '').lower()

        if session is None:
            session = self.session

        for schema in self.session.schemas:
            alias_for = schema.get('alias_for')

            if (alias_for and isinstance(alias_for, str)
                    and alias_for.lower() == entity_type):
                return schema['id']

        for schema in self.session.schemas:
            if schema['id'].lower() == entity_type:
                return schema['id']

        raise ValueError(
            'Unable to translate entity type: {0}.'.format(entity_type))

    def _launch(self, event):
        self.session.rollback()
        self.session._local_cache.clear()

        self.launch(self.session, event)

    def launch(self, session, event):
        '''Callback method for the custom action.

        return either a bool ( True if successful or False if the action failed )
        or a dictionary with they keys `message` and `success`, the message should be a
        string and will be displayed as feedback to the user, success should be a bool,
        True if successful or False if the action failed.

        *session* is a `ftrack_api.Session` instance

        *entities* is a list of tuples each containing the entity type and the entity id.
        If the entity is a hierarchical you will always get the entity
        type TypedContext, once retrieved through a get operation you
        will have the "real" entity type ie. example Shot, Sequence
        or Asset Build.

        *event* the unmodified original event

        '''
        raise NotImplementedError()

    def _handle_preactions(self, session, event):
        # If preactions are not set
        if len(self.preactions) == 0:
            return True
        # If no selection
        selection = event.get('data', {}).get('selection', None)
        if (selection is None):
            return False
        # If preactions were already started
        if event['data'].get('preactions_launched', None) is True:
            return True

        # Launch preactions
        for preaction in self.preactions:
            self.trigger_action(preaction, event)

        # Relaunch this action
        additional_data = {"preactions_launched": True}
        self.trigger_action(self.identifier,
                            event,
                            additional_event_data=additional_data)

        return False

    def _handle_result(self, result):
        '''Validate the returned result from the action callback'''
        if isinstance(result, bool):
            if result is True:
                result = {
                    'success': result,
                    'message':
                    ('{0} launched successfully.'.format(self.label))
                }
            else:
                result = {
                    'success': result,
                    'message': ('{0} launch failed.'.format(self.label))
                }

        elif isinstance(result, dict):
            items = 'items' in result
            if items is False:
                for key in ('success', 'message'):
                    if key in result:
                        continue

                    raise KeyError('Missing required key: {0}.'.format(key))

        return result

    def show_message(self, event, input_message, result=False):
        """
        Shows message to user who triggered event
        - event - just source of user id
        - input_message - message that is shown to user
        - result - changes color of message (based on ftrack settings)
            - True = Violet
            - False = Red
        """
        if not isinstance(result, bool):
            result = False

        try:
            message = str(input_message)
        except Exception:
            return

        user_id = event['source']['user']['id']
        target = ('applicationId=ftrack.client.web and user.id="{0}"'
                  ).format(user_id)
        self.session.event_hub.publish(ftrack_api.event.base.Event(
            topic='ftrack.action.trigger-user-interface',
            data=dict(type='message', success=result, message=message),
            target=target),
                                       on_error='ignore')

    def show_interface(self,
                       items,
                       title='',
                       event=None,
                       user=None,
                       username=None,
                       user_id=None):
        """
        Shows interface to user
        - to identify user must be entered one of args:
            event, user, username, user_id
        - 'items' must be list containing Ftrack interface items
        """
        if not any([event, user, username, user_id]):
            raise TypeError(
                ('Missing argument `show_interface` requires one of args:'
                 ' event (ftrack_api Event object),'
                 ' user (ftrack_api User object)'
                 ' username (string) or user_id (string)'))

        if event:
            user_id = event['source']['user']['id']
        elif user:
            user_id = user['id']
        else:
            if user_id:
                key = 'id'
                value = user_id
            else:
                key = 'username'
                value = username

            user = self.session.query('User where {} is "{}"'.format(
                key, value)).first()

            if not user:
                raise TypeError(
                    ('Ftrack user with {} "{}" was not found!').format(
                        key, value))

            user_id = user['id']

        target = ('applicationId=ftrack.client.web and user.id="{0}"'
                  ).format(user_id)

        self.session.event_hub.publish(ftrack_api.event.base.Event(
            topic='ftrack.action.trigger-user-interface',
            data=dict(type='widget', items=items, title=title),
            target=target),
                                       on_error='ignore')

    def show_interface_from_dict(self,
                                 messages,
                                 title="",
                                 event=None,
                                 user=None,
                                 username=None,
                                 user_id=None):
        if not messages:
            self.log.debug("No messages to show! (messages dict is empty)")
            return
        items = []
        splitter = {'type': 'label', 'value': '---'}
        first = True
        for key, value in messages.items():
            if not first:
                items.append(splitter)
            else:
                first = False

            subtitle = {'type': 'label', 'value': '<h3>{}</h3>'.format(key)}
            items.append(subtitle)
            if isinstance(value, list):
                for item in value:
                    message = {
                        'type': 'label',
                        'value': '<p>{}</p>'.format(item)
                    }
                    items.append(message)
            else:
                message = {'type': 'label', 'value': '<p>{}</p>'.format(value)}
                items.append(message)

        self.show_interface(items, title, event, user, username, user_id)

    def trigger_action(self,
                       action_name,
                       event=None,
                       session=None,
                       selection=None,
                       user_data=None,
                       topic="ftrack.action.launch",
                       additional_event_data={},
                       on_error="ignore"):
        self.log.debug("Triggering action \"{}\" Begins".format(action_name))

        if not session:
            session = self.session

        # Getting selection and user data
        _selection = None
        _user_data = None

        if event:
            _selection = event.get("data", {}).get("selection")
            _user_data = event.get("source", {}).get("user")

        if selection is not None:
            _selection = selection

        if user_data is not None:
            _user_data = user_data

        # Without selection and user data skip triggering
        msg = "Can't trigger \"{}\" action without {}."
        if _selection is None:
            self.log.error(msg.format(action_name, "selection"))
            return

        if _user_data is None:
            self.log.error(msg.format(action_name, "user data"))
            return

        _event_data = {
            "actionIdentifier": action_name,
            "selection": _selection
        }

        # Add additional data
        if additional_event_data:
            _event_data.update(additional_event_data)

        # Create and trigger event
        session.event_hub.publish(ftrack_api.event.base.Event(
            topic=topic, data=_event_data, source=dict(user=_user_data)),
                                  on_error=on_error)
        self.log.debug(
            "Action \"{}\" Triggered successfully".format(action_name))

    def trigger_event(self,
                      topic,
                      event_data={},
                      session=None,
                      source=None,
                      event=None,
                      on_error="ignore"):
        if session is None:
            session = self.session

        if not source and event:
            source = event.get("source")
        # Create and trigger event
        event = ftrack_api.event.base.Event(topic=topic,
                                            data=event_data,
                                            source=source)
        session.event_hub.publish(event, on_error=on_error)

        self.log.debug(("Publishing event: {}").format(str(event.__dict__)))

    def get_project_from_entity(self, entity):
        low_entity_type = entity.entity_type.lower()
        if low_entity_type == "project":
            return entity

        if "project" in entity:
            # reviewsession, task(Task, Shot, Sequence,...)
            return entity["project"]

        if low_entity_type == "filecomponent":
            entity = entity["version"]
            low_entity_type = entity.entity_type.lower()

        if low_entity_type == "assetversion":
            asset = entity["asset"]
            if asset:
                parent = asset["parent"]
                if parent:
                    return parent["project"]

        project_data = entity["link"][0]
        return self.session.query("Project where id is {}".format(
            project_data["id"])).one()
Esempio n. 4
0
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
Esempio n. 5
0
class TrayManager:
    """Cares about context of application.

    Load submenus, actions, separators and modules into tray's context.
    """
    modules = {}
    services = {}
    services_submenu = None

    errors = []
    items = (config.get_presets(first_run=True).get('tray',
                                                    {}).get('menu_items', []))
    available_sourcetypes = ['python', 'file']

    def __init__(self, tray_widget, main_window):
        self.tray_widget = tray_widget
        self.main_window = main_window
        self.log = Logger().get_logger(self.__class__.__name__)

        self.icon_run = QtGui.QIcon(get_resource('circle_green.png'))
        self.icon_stay = QtGui.QIcon(get_resource('circle_orange.png'))
        self.icon_failed = QtGui.QIcon(get_resource('circle_red.png'))

        self.services_thread = None

    def process_presets(self):
        """Add modules to tray by presets.

        This is start up method for TrayManager. Loads presets and import
        modules described in "menu_items.json". In `item_usage` key you can
        specify by item's title or import path if you want to import it.
        Example of "menu_items.json" file:
            {
                "item_usage": {
                    "Statics Server": false
                }
            }, {
                "item_import": [{
                    "title": "Ftrack",
                    "type": "module",
                    "import_path": "pype.ftrack.tray",
                    "fromlist": ["pype", "ftrack"]
                }, {
                    "title": "Statics Server",
                    "type": "module",
                    "import_path": "pype.services.statics_server",
                    "fromlist": ["pype","services"]
                }]
            }
        In this case `Statics Server` won't be used.
        """
        # Backwards compatible presets loading
        if isinstance(self.items, list):
            items = self.items
        else:
            items = []
            # Get booleans is module should be used
            usages = self.items.get("item_usage") or {}
            for item in self.items.get("item_import", []):
                import_path = item.get("import_path")
                title = item.get("title")

                item_usage = usages.get(title)
                if item_usage is None:
                    item_usage = usages.get(import_path, True)

                if item_usage:
                    items.append(item)
                else:
                    if not title:
                        title = import_path
                    self.log.debug("{} - Module ignored".format(title))

        if items:
            self.process_items(items, self.tray_widget.menu)

        # Add services if they are
        if self.services_submenu is not None:
            self.tray_widget.menu.addMenu(self.services_submenu)

        # Add separator
        if items and self.services_submenu is not None:
            self.add_separator(self.tray_widget.menu)

        # Add Exit action to menu
        aExit = QtWidgets.QAction("&Exit", self.tray_widget)
        aExit.triggered.connect(self.tray_widget.exit)
        self.tray_widget.menu.addAction(aExit)

        # Tell each module which modules were imported
        self.connect_modules()
        self.start_modules()

    def process_items(self, items, parent_menu):
        """ Loop through items and add them to parent_menu.

        :param items: contains dictionary objects representing each item
        :type items: list
        :param parent_menu: menu where items will be add
        :type parent_menu: QtWidgets.QMenu
        """
        for item in items:
            i_type = item.get('type', None)
            result = False
            if i_type is None:
                continue
            elif i_type == 'module':
                result = self.add_module(item, parent_menu)
            elif i_type == 'action':
                result = self.add_action(item, parent_menu)
            elif i_type == 'menu':
                result = self.add_menu(item, parent_menu)
            elif i_type == 'separator':
                result = self.add_separator(parent_menu)

            if result is False:
                self.errors.append(item)

    def add_module(self, item, parent_menu):
        """Inicialize object of module and add it to context.

        :param item: item from presets containing information about module
        :type item: dict
        :param parent_menu: menu where module's submenus/actions will be add
        :type parent_menu: QtWidgets.QMenu
        :returns: success of module implementation
        :rtype: bool

        REQUIRED KEYS (item):
            :import_path (*str*):
                - full import path as python's import
                - e.g. *"path.to.module"*
            :fromlist (*list*):
                - subparts of import_path (as from is used)
                - e.g. *["path", "to"]*
        OPTIONAL KEYS (item):
            :title (*str*):
                - represents label shown in services menu
                - import_path is used if title is not set
                - title is not used at all if module is not a service

        .. note::
            Module is added as **service** if object does not have
            *tray_menu* method.
        """
        import_path = item.get('import_path', None)
        title = item.get('title', import_path)
        fromlist = item.get('fromlist', [])
        try:
            module = __import__("{}".format(import_path), fromlist=fromlist)
            obj = module.tray_init(self.tray_widget, self.main_window)
            name = obj.__class__.__name__
            if hasattr(obj, 'tray_menu'):
                obj.tray_menu(parent_menu)
            else:
                if self.services_submenu is None:
                    self.services_submenu = QtWidgets.QMenu(
                        'Services', self.tray_widget.menu)
                action = QtWidgets.QAction(title, self.services_submenu)
                action.setIcon(self.icon_run)
                self.services_submenu.addAction(action)
                if hasattr(obj, 'set_qaction'):
                    obj.set_qaction(action, self.icon_failed)
            self.modules[name] = obj
            self.log.info("{} - Module imported".format(title))
        except ImportError as ie:
            if self.services_submenu is None:
                self.services_submenu = QtWidgets.QMenu(
                    'Services', self.tray_widget.menu)
            action = QtWidgets.QAction(title, self.services_submenu)
            action.setIcon(self.icon_failed)
            self.services_submenu.addAction(action)
            self.log.warning("{} - Module import Error: {}".format(
                title, str(ie)),
                             exc_info=True)
            return False
        return True

    def add_action(self, item, parent_menu):
        """Adds action to parent_menu.

        :param item: item from presets containing information about action
        :type item: dictionary
        :param parent_menu: menu where action will be added
        :type parent_menu: QtWidgets.QMenu
        :returns: success of adding item to parent_menu
        :rtype: bool

        REQUIRED KEYS (item):
            :title (*str*):
                - represents label shown in menu
            :sourcetype (*str*):
                - type of action *enum["file", "python"]*
            :command (*str*):
                - filepath to script *(sourcetype=="file")*
                - python code as string *(sourcetype=="python")*
        OPTIONAL KEYS (item):
            :tooltip (*str*):
                - will be shown when hover over action
        """
        sourcetype = item.get('sourcetype', None)
        command = item.get('command', None)
        title = item.get('title', '*ERROR*')
        tooltip = item.get('tooltip', None)

        if sourcetype not in self.available_sourcetypes:
            self.log.error('item "{}" has invalid sourcetype'.format(title))
            return False
        if command is None or command.strip() == '':
            self.log.error('item "{}" has invalid command'.format(title))
            return False

        new_action = QtWidgets.QAction(title, parent_menu)
        if tooltip is not None and tooltip.strip() != '':
            new_action.setToolTip(tooltip)

        if sourcetype == 'python':
            new_action.triggered.connect(lambda: exec(command))
        elif sourcetype == 'file':
            command = os.path.normpath(command)
            if '$' in command:
                command_items = command.split(os.path.sep)
                for i in range(len(command_items)):
                    if command_items[i].startswith('$'):
                        # TODO: raise error if environment was not found?
                        command_items[i] = os.environ.get(
                            command_items[i].replace('$', ''),
                            command_items[i])
                command = os.path.sep.join(command_items)

            new_action.triggered.connect(
                lambda: exec(open(command).read(), globals()))

        parent_menu.addAction(new_action)

    def add_menu(self, item, parent_menu):
        """ Adds submenu to parent_menu.

        :param item: item from presets containing information about menu
        :type item: dictionary
        :param parent_menu: menu where submenu will be added
        :type parent_menu: QtWidgets.QMenu
        :returns: success of adding item to parent_menu
        :rtype: bool

        REQUIRED KEYS (item):
            :title (*str*):
                - represents label shown in menu
            :items (*list*):
                - list of submenus / actions / separators / modules *(dict)*
        """
        try:
            title = item.get('title', None)
            if title is None or title.strip() == '':
                self.log.error('Missing title in menu from presets')
                return False
            new_menu = QtWidgets.QMenu(title, parent_menu)
            new_menu.setProperty('submenu', 'on')
            parent_menu.addMenu(new_menu)

            self.process_items(item.get('items', []), new_menu)
            return True
        except Exception:
            return False

    def add_separator(self, parent_menu):
        """ Adds separator to parent_menu.

        :param parent_menu: menu where submenu will be added
        :type parent_menu: QtWidgets.QMenu
        :returns: success of adding item to parent_menu
        :rtype: bool
        """
        try:
            parent_menu.addSeparator()
            return True
        except Exception:
            return False

    def connect_modules(self):
        """Sends all imported modules to imported modules
        which have process_modules method.
        """
        for obj in self.modules.values():
            if hasattr(obj, 'process_modules'):
                obj.process_modules(self.modules)

    def start_modules(self):
        """Modules which can be modified by another modules and
        must be launched after *connect_modules* should have tray_start
        to start their process afterwards. (e.g. Ftrack actions)
        """
        for obj in self.modules.values():
            if hasattr(obj, 'tray_start'):
                obj.tray_start()

    def on_exit(self):
        for obj in self.modules.values():
            if hasattr(obj, 'tray_exit'):
                try:
                    obj.tray_exit()
                except Exception:
                    self.log.error("Failed to exit module {}".format(
                        obj.__class__.__name__))