Esempio n. 1
0
class AvalonApps:
    def __init__(self, main_parent=None, parent=None):
        self.log = Logger().get_logger(__name__)
        self.main_parent = main_parent
        self.parent = parent

        self.app_launcher = LauncherWindow()

        # actions.register_default_actions()
        actions.register_config_actions()
        actions.register_environment_actions()

    def process_modules(self, modules):
        if "RestApiServer" in modules:
            from .rest_api import AvalonRestApi
            self.rest_api_obj = AvalonRestApi()

    # Definition of Tray menu
    def tray_menu(self, parent_menu=None):
        # Actions
        if parent_menu is None:
            if self.parent is None:
                self.log.warning('Parent menu is not set')
                return
            elif self.parent.hasattr('menu'):
                parent_menu = self.parent.menu
            else:
                self.log.warning('Parent menu is not set')
                return

        action_launcher = QtWidgets.QAction("Launcher", parent_menu)
        action_library_loader = QtWidgets.QAction("Library loader",
                                                  parent_menu)

        action_launcher.triggered.connect(self.show_launcher)
        action_library_loader.triggered.connect(self.show_library_loader)

        parent_menu.addAction(action_launcher)
        parent_menu.addAction(action_library_loader)

    def show_launcher(self):
        # if app_launcher don't exist create it/otherwise only show main window
        self.app_launcher.show()
        self.app_launcher.raise_()
        self.app_launcher.activateWindow()

    def show_library_loader(self):
        libraryloader.show(parent=self.main_parent,
                           icon=self.parent.icon,
                           show_projects=True,
                           show_libraries=True)
Esempio n. 2
0
class AvalonApps:
    def __init__(self, main_parent=None, parent=None):
        self.log = Logger().get_logger(__name__)
        self.main_parent = main_parent
        self.parent = parent
        self.app_launcher = None

    def process_modules(self, modules):
        if "RestApiServer" in modules:
            from .rest_api import AvalonRestApi
            self.rest_api_obj = AvalonRestApi()

    # Definition of Tray menu
    def tray_menu(self, parent_menu=None):
        # Actions
        if parent_menu is None:
            if self.parent is None:
                self.log.warning('Parent menu is not set')
                return
            elif self.parent.hasattr('menu'):
                parent_menu = self.parent.menu
            else:
                self.log.warning('Parent menu is not set')
                return

        icon = QtGui.QIcon(launcher_lib.resource("icon", "main.png"))
        aShowLauncher = QtWidgets.QAction(icon, "&Launcher", parent_menu)
        aLibraryLoader = QtWidgets.QAction("Library", parent_menu)

        aShowLauncher.triggered.connect(self.show_launcher)
        aLibraryLoader.triggered.connect(self.show_library_loader)

        parent_menu.addAction(aShowLauncher)
        parent_menu.addAction(aLibraryLoader)

    def show_launcher(self):
        # if app_launcher don't exist create it/otherwise only show main window
        if self.app_launcher is None:
            io.install()
            APP_PATH = launcher_lib.resource("qml", "main.qml")
            self.app_launcher = launcher_widget.Launcher(APP_PATH)
        self.app_launcher.window.show()

    def show_library_loader(self):
        libraryloader.show(parent=self.main_parent,
                           icon=self.parent.icon,
                           show_projects=True,
                           show_libraries=True)
Esempio n. 3
0
class ClockifyModule:
    def __init__(self, main_parent=None, parent=None):
        self.log = Logger().get_logger(self.__class__.__name__, "PypeTray")

        self.main_parent = main_parent
        self.parent = parent
        self.clockapi = ClockifyAPI()
        self.message_widget = None
        self.widget_settings = ClockifySettings(main_parent, self)
        self.widget_settings_required = None

        self.thread_timer_check = None
        # Bools
        self.bool_thread_check_running = False
        self.bool_api_key_set = False
        self.bool_workspace_set = False
        self.bool_timer_run = False

        self.clockapi.set_master(self)
        self.bool_api_key_set = self.clockapi.set_api()

    def tray_start(self):
        if self.bool_api_key_set is False:
            self.show_settings()
            return

        self.bool_workspace_set = self.clockapi.workspace_id is not None
        if self.bool_workspace_set is False:
            return

        self.start_timer_check()

        self.set_menu_visibility()

    def process_modules(self, modules):
        if 'FtrackModule' in modules:
            actions_path = os.path.sep.join(
                [os.path.dirname(__file__), 'ftrack_actions'])
            current = os.environ.get('FTRACK_ACTIONS_PATH', '')
            if current:
                current += os.pathsep
            os.environ['FTRACK_ACTIONS_PATH'] = current + actions_path

        if 'AvalonApps' in modules:
            from launcher import lib
            actions_path = os.path.sep.join(
                [os.path.dirname(__file__), 'launcher_actions'])
            current = os.environ.get('AVALON_ACTIONS', '')
            if current:
                current += os.pathsep
            os.environ['AVALON_ACTIONS'] = current + actions_path

        if 'TimersManager' in modules:
            self.timer_manager = modules['TimersManager']
            self.timer_manager.add_module(self)

    def start_timer_manager(self, data):
        self.start_timer(data)

    def stop_timer_manager(self):
        self.stop_timer()

    def timer_started(self, data):
        if hasattr(self, 'timer_manager'):
            self.timer_manager.start_timers(data)

    def timer_stopped(self):
        self.bool_timer_run = False
        if hasattr(self, 'timer_manager'):
            self.timer_manager.stop_timers()

    def start_timer_check(self):
        self.bool_thread_check_running = True
        if self.thread_timer_check is None:
            self.thread_timer_check = threading.Thread(
                target=self.check_running)
            self.thread_timer_check.daemon = True
            self.thread_timer_check.start()

    def stop_timer_check(self):
        self.bool_thread_check_running = True
        if self.thread_timer_check is not None:
            self.thread_timer_check.join()
            self.thread_timer_check = None

    def check_running(self):
        import time
        while self.bool_thread_check_running is True:
            bool_timer_run = False
            if self.clockapi.get_in_progress() is not None:
                bool_timer_run = True

            if self.bool_timer_run != bool_timer_run:
                if self.bool_timer_run is True:
                    self.timer_stopped()
                elif self.bool_timer_run is False:
                    actual_timer = self.clockapi.get_in_progress()
                    if not actual_timer:
                        continue

                    actual_proj_id = actual_timer["projectId"]
                    if not actual_proj_id:
                        continue

                    project = self.clockapi.get_project_by_id(actual_proj_id)
                    if project and project.get("code") == 501:
                        continue

                    project_name = project["name"]

                    actual_timer_hierarchy = actual_timer["description"]
                    hierarchy_items = actual_timer_hierarchy.split("/")
                    # Each pype timer must have at least 2 items!
                    if len(hierarchy_items) < 2:
                        continue
                    task_name = hierarchy_items[-1]
                    hierarchy = hierarchy_items[:-1]

                    task_type = None
                    if len(actual_timer.get("tags", [])) > 0:
                        task_type = actual_timer["tags"][0].get("name")
                    data = {
                        "task_name": task_name,
                        "hierarchy": hierarchy,
                        "project_name": project_name,
                        "task_type": task_type
                    }

                    self.timer_started(data)

                self.bool_timer_run = bool_timer_run
                self.set_menu_visibility()
            time.sleep(5)

    def stop_timer(self):
        self.clockapi.finish_time_entry()
        if self.bool_timer_run:
            self.timer_stopped()

    def signed_in(self):
        if hasattr(self, 'timer_manager'):
            if not self.timer_manager:
                return

            if not self.timer_manager.last_task:
                return

            if self.timer_manager.is_running:
                self.start_timer_manager(self.timer_manager.last_task)

    def start_timer(self, input_data):
        # If not api key is not entered then skip
        if not self.clockapi.get_api_key():
            return

        actual_timer = self.clockapi.get_in_progress()
        actual_timer_hierarchy = None
        actual_project_id = None
        if actual_timer is not None:
            actual_timer_hierarchy = actual_timer.get("description")
            actual_project_id = actual_timer.get("projectId")

        # Concatenate hierarchy and task to get description
        desc_items = [val for val in input_data.get("hierarchy", [])]
        desc_items.append(input_data["task_name"])
        description = "/".join(desc_items)

        # Check project existence
        project_name = input_data["project_name"]
        project_id = self.clockapi.get_project_id(project_name)
        if not project_id:
            self.log.warning(
                ("Project \"{}\" was not found in Clockify. Timer won't start."
                 ).format(project_name))

            msg = (
                "Project <b>\"{}\"</b> is not in Clockify Workspace <b>\"{}\"</b>."
                "<br><br>Please inform your Project Manager.").format(
                    project_name, str(self.clockapi.workspace))

            self.message_widget = MessageWidget(self.main_parent, msg,
                                                "Clockify - Info Message")
            self.message_widget.closed.connect(self.on_message_widget_close)
            self.message_widget.show()

            return

        if (actual_timer is not None and description == actual_timer_hierarchy
                and project_id == actual_project_id):
            return

        tag_ids = []
        task_tag_id = self.clockapi.get_tag_id(input_data["task_type"])
        if task_tag_id is not None:
            tag_ids.append(task_tag_id)

        self.clockapi.start_time_entry(description,
                                       project_id,
                                       tag_ids=tag_ids)

    def on_message_widget_close(self):
        self.message_widget = None

    # Definition of Tray menu
    def tray_menu(self, parent_menu):
        # Menu for Tray App
        self.menu = QtWidgets.QMenu('Clockify', parent_menu)
        self.menu.setProperty('submenu', 'on')
        self.menu.setStyleSheet(style.load_stylesheet())

        # Actions
        self.aShowSettings = QtWidgets.QAction("Settings", self.menu)
        self.aStopTimer = QtWidgets.QAction("Stop timer", self.menu)

        self.menu.addAction(self.aShowSettings)
        self.menu.addAction(self.aStopTimer)

        self.aShowSettings.triggered.connect(self.show_settings)
        self.aStopTimer.triggered.connect(self.stop_timer)

        self.set_menu_visibility()

        parent_menu.addMenu(self.menu)

    def show_settings(self):
        self.widget_settings.input_api_key.setText(self.clockapi.get_api_key())
        self.widget_settings.show()

    def set_menu_visibility(self):
        self.aStopTimer.setVisible(self.bool_timer_run)
Esempio n. 4
0
class IdleManager(threading.Thread):
    """ Measure user's idle time in seconds.
    Idle time resets on keyboard/mouse input.
    Is able to emit signals at specific time idle.
    """
    time_callbacks = collections.defaultdict(list)
    idle_time = 0

    def __init__(self):
        super(IdleManager, self).__init__()
        self.log = Logger().get_logger(self.__class__.__name__)
        self.qaction = None
        self.failed_icon = None
        self._is_running = False
        self.threads = []

    def set_qaction(self, qaction, failed_icon):
        self.qaction = qaction
        self.failed_icon = failed_icon

    def tray_start(self):
        self.start()

    def tray_exit(self):
        self.stop()
        try:
            self.time_callbacks = {}
        except Exception:
            pass

    def add_time_callback(self, emit_time, callback):
        """If any module want to use IdleManager, need to use this method.

        Args:
            emit_time(int): Time when callback will be triggered.
            callback(func): Callback that will be triggered.
        """
        self.time_callbacks[emit_time].append(callback)

    @property
    def is_running(self):
        return self._is_running

    def _reset_time(self):
        self.idle_time = 0

    def stop(self):
        self._is_running = False

    def run(self):
        self.log.info('IdleManager has started')
        self._is_running = True
        thread_mouse = MouseThread(self._reset_time)
        thread_mouse.start()
        thread_keyboard = KeyboardThread(self._reset_time)
        thread_keyboard.start()
        try:
            while self.is_running:
                if self.idle_time in self.time_callbacks:
                    for callback in self.time_callbacks[self.idle_time]:
                        thread = threading.Thread(target=callback)
                        thread.start()
                        self.threads.append(thread)

                for thread in tuple(self.threads):
                    if not thread.isAlive():
                        thread.join()
                        self.threads.remove(thread)

                self.idle_time += 1
                time.sleep(1)

        except Exception:
            self.log.warning(
                'Idle Manager service has failed', exc_info=True
            )

        if self.qaction and self.failed_icon:
            self.qaction.setIcon(self.failed_icon)

        # Threads don't have their attrs when Qt application already finished
        try:
            thread_mouse.stop()
            thread_mouse.join()
        except AttributeError:
            pass

        try:
            thread_keyboard.stop()
            thread_keyboard.join()
        except AttributeError:
            pass

        self._is_running = False
        self.log.info('IdleManager has stopped')
Esempio n. 5
0
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
Esempio n. 6
0
class TrayManager:
    """Cares about context of application.

    Load submenus, actions, separators and modules into tray's context.
    """
    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.modules = {}
        self.services = {}
        self.services_submenu = None

        self.errors = []

        CURRENT_DIR = os.path.dirname(__file__)
        self.modules_imports = config.load_json(
            os.path.join(CURRENT_DIR, "modules_imports.json"))
        presets = config.get_presets(first_run=True)
        menu_items = presets["tray"]["menu_items"]
        try:
            self.modules_usage = menu_items["item_usage"]
        except Exception:
            self.modules_usage = {}
            self.log.critical("Couldn't find modules usage data.")

        self.module_attributes = menu_items.get("attributes") or {}

        self.icon_run = QtGui.QIcon(
            resources.get_resource("icons", "circle_green.png"))
        self.icon_stay = QtGui.QIcon(
            resources.get_resource("icons", "circle_orange.png"))
        self.icon_failed = QtGui.QIcon(
            resources.get_resource("icons", "circle_red.png"))

    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
                }
            }
        In this case `Statics Server` won't be used.
        """

        items = []
        # Get booleans is module should be used
        for item in self.modules_imports:
            import_path = item.get("import_path")
            title = item.get("title")

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

            if not item_usage:
                if not title:
                    title = import_path
                self.log.info("{} - Module ignored".format(title))
                continue

            _attributes = self.module_attributes.get(title)
            if _attributes is None:
                _attributes = self.module_attributes.get(import_path)

            if _attributes:
                item["attributes"] = _attributes

            items.append(item)

        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)

        self._add_version_item()

        # 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 _add_version_item(self):
        config_file_path = os.path.join(os.environ["PYPE_SETUP_PATH"],
                                        "pypeapp", "config.ini")

        default_config = {}
        if os.path.exists(config_file_path):
            config = configparser.ConfigParser()
            config.read(config_file_path)
            try:
                default_config = config["CLIENT"]
            except Exception:
                pass

        subversion = default_config.get("subversion")
        client_name = default_config.get("client_name")

        version_string = pype.version.__version__
        if subversion:
            version_string += " ({})".format(subversion)

        if client_name:
            version_string += ", {}".format(client_name)

        version_action = QtWidgets.QAction(version_string, self.tray_widget)
        self.tray_widget.menu.addAction(version_action)
        self.add_separator(self.tray_widget.menu)

    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', [])
        attributes = item.get("attributes", {})
        try:
            module = __import__("{}".format(import_path), fromlist=fromlist)
            klass = getattr(module, "CLASS_DEFINIION", None)
            if not klass and attributes:
                self.log.error(
                    ("There are defined attributes for module \"{}\" but"
                     "module does not have defined \"CLASS_DEFINIION\"."
                     ).format(import_path))

            elif klass and attributes:
                for key, value in attributes.items():
                    if hasattr(klass, key):
                        setattr(klass, key, value)
                    else:
                        self.log.error(
                            ("Module \"{}\" does not have attribute \"{}\"."
                             " Check your settings please.").format(
                                 import_path, key))

            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 Exception as exc:
            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(exc)),
                             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__))
Esempio n. 7
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. 8
0
class IdleManager(QtCore.QThread):
    """ Measure user's idle time in seconds.
    Idle time resets on keyboard/mouse input.
    Is able to emit signals at specific time idle.
    """
    time_signals = collections.defaultdict(list)
    idle_time = 0
    signal_reset_timer = QtCore.Signal()

    def __init__(self):
        super(IdleManager, self).__init__()
        self.log = Logger().get_logger(self.__class__.__name__)
        self.signal_reset_timer.connect(self._reset_time)
        self.qaction = None
        self.failed_icon = None
        self._is_running = False

    def set_qaction(self, qaction, failed_icon):
        self.qaction = qaction
        self.failed_icon = failed_icon

    def tray_start(self):
        self.start()

    def tray_exit(self):
        self.stop()
        try:
            self.time_signals = {}
        except Exception:
            pass

    def add_time_signal(self, emit_time, signal):
        """ If any module want to use IdleManager, need to use add_time_signal
        :param emit_time: time when signal will be emitted
        :type emit_time: int
        :param signal: signal that will be emitted (without objects)
        :type signal: QtCore.Signal
        """
        self.time_signals[emit_time].append(signal)

    @property
    def is_running(self):
        return self._is_running

    def _reset_time(self):
        self.idle_time = 0

    def stop(self):
        self._is_running = False

    def run(self):
        self.log.info('IdleManager has started')
        self._is_running = True
        thread_mouse = MouseThread(self.signal_reset_timer)
        thread_mouse.start()
        thread_keyboard = KeyboardThread(self.signal_reset_timer)
        thread_keyboard.start()
        try:
            while self.is_running:
                self.idle_time += 1
                if self.idle_time in self.time_signals:
                    for signal in self.time_signals[self.idle_time]:
                        signal.emit()
                time.sleep(1)
        except Exception:
            self.log.warning('Idle Manager service has failed', exc_info=True)

        if self.qaction and self.failed_icon:
            self.qaction.setIcon(self.failed_icon)

        # Threads don't have their attrs when Qt application already finished
        try:
            thread_mouse.signal_stop.emit()
            thread_mouse.terminate()
            thread_mouse.wait()
        except AttributeError:
            pass

        try:
            thread_keyboard.signal_stop.emit()
            thread_keyboard.terminate()
            thread_keyboard.wait()
        except AttributeError:
            pass

        self._is_running = False
        self.log.info('IdleManager has stopped')
Esempio n. 9
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__))
Esempio n. 10
0
class TimersManager(metaclass=Singleton):
    """ Handles about Timers.

    Should be able to start/stop all timers at once.
    If IdleManager is imported then is able to handle about stop timers
        when user idles for a long time (set in presets).
    """
    modules = []
    is_running = False
    last_task = None

    def __init__(self, tray_widget, main_widget):
        self.log = Logger().get_logger(self.__class__.__name__)
        self.tray_widget = tray_widget
        self.main_widget = main_widget
        self.widget_user_idle = WidgetUserIdle(self)

    def set_signal_times(self):
        try:
            timer_info = (config.get_presets().get('services').get(
                'timers_manager').get('timer'))
            full_time = int(float(timer_info['full_time']) * 60)
            message_time = int(float(timer_info['message_time']) * 60)
            self.time_show_message = full_time - message_time
            self.time_stop_timer = full_time
            return True
        except Exception:
            self.log.warning('Was not able to load presets for TimersManager')
            return False

    def add_module(self, module):
        """ Adds module to context

        Module must have implemented methods:
            - ``start_timer_manager(data)``
            - ``stop_timer_manager()``
        """
        self.modules.append(module)

    def start_timers(self, data):
        '''
        :param data: basic information needed to start any timer
        :type data: dict
        ..note::
            Dictionary "data" should contain:
            - project_name(str) - Name of Project
            - hierarchy(list/tuple) - list of parents(except project)
            - task_type(str)
            - task_name(str)

        Example:
            - to run timers for task in
                'C001_BackToPast/assets/characters/villian/Lookdev BG'
            - input data should contain:
            .. code-block:: Python
                data = {
                    'project_name': 'C001_BackToPast',
                    'hierarchy': ['assets', 'characters', 'villian'],
                    'task_type': 'lookdev',
                    'task_name': 'Lookdev BG'
                }
        '''
        if len(data['hierarchy']) < 1:
            self.log.error((
                'Not allowed action in Pype!!'
                ' Timer has been launched on task which is child of Project.'))
            return

        self.last_task = data

        for module in self.modules:
            module.start_timer_manager(data)
        self.is_running = True

    def restart_timers(self):
        if self.last_task is not None:
            self.start_timers(self.last_task)

    def stop_timers(self):
        if self.is_running is False:
            return
        self.widget_user_idle.bool_not_stopped = False
        self.widget_user_idle.refresh_context()
        for module in self.modules:
            module.stop_timer_manager()
        self.is_running = False

    def process_modules(self, modules):
        """ Gives ability to connect with imported modules from TrayManager.

        :param modules: All imported modules from TrayManager
        :type modules: dict
        """
        self.s_handler = SignalHandler(self)

        if 'IdleManager' in modules:
            if self.set_signal_times() is True:
                self.register_to_idle_manager(modules['IdleManager'])

    def register_to_idle_manager(self, man_obj):
        self.idle_man = man_obj
        # Times when idle is between show widget and stop timers
        show_to_stop_range = range(self.time_show_message - 1,
                                   self.time_stop_timer)
        for num in show_to_stop_range:
            self.idle_man.add_time_signal(num,
                                          self.s_handler.signal_change_label)
        # Times when widget is already shown and user restart idle
        shown_and_moved_range = range(self.time_stop_timer -
                                      self.time_show_message)
        for num in shown_and_moved_range:
            self.idle_man.add_time_signal(num,
                                          self.s_handler.signal_change_label)
        # Time when message is shown
        self.idle_man.add_time_signal(self.time_show_message,
                                      self.s_handler.signal_show_message)
        # Time when timers are stopped
        self.idle_man.add_time_signal(self.time_stop_timer,
                                      self.s_handler.signal_stop_timers)

    def change_label(self):
        if self.is_running is False:
            return
        if self.widget_user_idle.bool_is_showed is False:
            return
        if not hasattr(self, 'idle_man'):
            return

        if self.idle_man.idle_time > self.time_show_message:
            value = self.time_stop_timer - self.idle_man.idle_time
        else:
            value = 1 + (self.time_stop_timer - self.time_show_message -
                         self.idle_man.idle_time)
        self.widget_user_idle.change_count_widget(value)

    def show_message(self):
        if self.is_running is False:
            return
        if self.widget_user_idle.bool_is_showed is False:
            self.widget_user_idle.show()