Beispiel #1
0
    def test_console_output(self, capsys, monkeypatch, printer):
        monkeypatch.setitem(os.environ, 'PYPE_DEBUG', '3')
        lf = Logger()
        assert lf.PYPE_DEBUG == 3
        logger = Logger().get_logger('test_output', 'tests')

        printer("DEBUG LEVEL SET: {}".format(os.environ.get('PYPE_DEBUG')))

        # critical
        printer("testing critical level")
        logger.critical("CRITICAL TEST")
        cap = capsys.readouterr()
        cri_regex = re.compile(
            r'\x1b\[1m\x1b\[31m!!! CRI: \x1b\[0m.* \x1b\[1m\x1b\[32m>>> \x1b\[0m\x1b\[92m{ test_output }\x1b\[0m: \x1b\[1m\x1b\[92m\[ \x1b\[0mCRITICAL TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n'
        )  # noqa: E501
        assert cri_regex.match(cap[1])

        # error
        printer("testing error level")
        logger.error("ERROR TEST")
        cap = capsys.readouterr()
        err_regex = re.compile(
            r'\x1b\[1m\x1b\[91m!!! ERR: \x1b\[0m.* \x1b\[1m\x1b\[32m>>> \x1b\[0m\x1b\[92m{ test_output }\x1b\[0m: \x1b\[1m\x1b\[92m\[ \x1b\[0m\x1b\[1m\x1b\[91mERROR\x1b\[0m TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n'
        )  # noqa: E501
        assert err_regex.match(cap[1])

        # warn
        printer("testing warning level")
        logger.warning("WARNING TEST")
        cap = capsys.readouterr()
        warn_regex = re.compile(
            r'\x1b\[1m\x1b\[93m\*\*\* WRN\x1b\[0m: \x1b\[1m\x1b\[32m>>> \x1b\[0m\x1b\[92m{ test_output }\x1b\[0m: \x1b\[1m\x1b\[92m\[ \x1b\[0mWARNING TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n'
        )  # noqa: E501
        assert warn_regex.match(cap[1])

        # info
        printer("testing info level")
        logger.info("INFO TEST")
        cap = capsys.readouterr()
        info_regex = re.compile(
            r'\x1b\[1m\x1b\[32m>>> \x1b\[0m\x1b\[1m\x1b\[92m\[ \x1b\[0mINFO TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n'
        )  # noqa: E501
        assert info_regex.match(cap[1])

        # debug
        printer("testing debug level")
        logger.debug("DEBUG TEST")
        cap = capsys.readouterr()
        debug_regex = re.compile(
            r'\x1b\[1m\x1b\[33m  - \x1b\[0m\x1b\[92m{ test_output }\x1b\[0m: \x1b\[1m\x1b\[92m\[ \x1b\[0mDEBUG TEST \x1b\[1m\x1b\[92m]\x1b\[0m \x1b\[0m\n'
        )  # noqa: E501
        assert debug_regex.match(cap[1])
Beispiel #2
0
class SocketThread(threading.Thread):
    """Thread that checks suprocess of storer of processor of events"""

    MAX_TIMEOUT = 35

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

        self.subproc = subprocess.Popen([
            sys.executable, self.filepath, *self.additional_args,
            str(self.port)
        ],
                                        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)
Beispiel #3
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 = 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__))
Beispiel #4
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={}):
        '''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)
        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()
Beispiel #5
0
class SocketThread(threading.Thread):
    """Thread that checks suprocess of storer of processor of events"""
    MAX_TIMEOUT = 35

    def __init__(self, name, port, filepath):
        super(SocketThread, self).__init__()
        self.log = Logger().get_logger("SocketThread", "Event Thread")
        self.setName(name)
        self.name = name
        self.port = port
        self.filepath = filepath
        self.sock = None
        self.subproc = None
        self.connection = None
        self._is_running = False
        self.finished = False

        self.mongo_error = False

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

        self.subproc = subprocess.Popen(
            ["python", self.filepath, "-port",
             str(self.port)],
            stdout=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
                        try:
                            data = connection.recv(16)
                            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

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

                lines = self.subproc.stdout.readlines()
                if lines:
                    print("*** Socked Thread stdout ***")
                    for line in lines:
                        os.write(1, line)
                self.finished = True