diff --git a/pype/clockify/clockify.py b/pype/clockify/clockify.py index 17be642be5..0b84bf3953 100644 --- a/pype/clockify/clockify.py +++ b/pype/clockify/clockify.py @@ -35,6 +35,28 @@ class ClockifyModule: 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('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 + def start_timer_check(self): self.bool_thread_check_running = True if self.thread_timer_check is None: diff --git a/pype/ftrack/actions/action_clockify_start.py b/pype/clockify/ftrack_actions/action_clockify_start.py similarity index 96% rename from pype/ftrack/actions/action_clockify_start.py rename to pype/clockify/ftrack_actions/action_clockify_start.py index 594ec21b78..e09d0b76e6 100644 --- a/pype/ftrack/actions/action_clockify_start.py +++ b/pype/clockify/ftrack_actions/action_clockify_start.py @@ -1,3 +1,4 @@ +import os import sys import argparse import logging @@ -17,7 +18,9 @@ class StartClockify(BaseAction): #: Action description. description = 'Starts timer on clockify' #: roles that are allowed to register this action - icon = 'https://clockify.me/assets/images/clockify-logo.png' + icon = '{}/app_icons/clockify.png'.format( + os.environ.get('PYPE_STATICS_SERVER', '') + ) #: Clockify api clockapi = ClockifyAPI() diff --git a/pype/ftrack/actions/action_clockify_sync.py b/pype/clockify/ftrack_actions/action_clockify_sync.py similarity index 97% rename from pype/ftrack/actions/action_clockify_sync.py rename to pype/clockify/ftrack_actions/action_clockify_sync.py index 4cc00225e2..695f7581c0 100644 --- a/pype/ftrack/actions/action_clockify_sync.py +++ b/pype/clockify/ftrack_actions/action_clockify_sync.py @@ -1,3 +1,4 @@ +import os import sys import argparse import logging @@ -21,7 +22,9 @@ class SyncClocify(BaseAction): #: roles that are allowed to register this action role_list = ['Pypeclub', 'Administrator'] #: icon - icon = 'https://clockify.me/assets/images/clockify-logo-white.svg' + icon = '{}/app_icons/clockify-white.png'.format( + os.environ.get('PYPE_STATICS_SERVER', '') + ) #: CLockifyApi clockapi = ClockifyAPI() diff --git a/pype/plugins/launcher/actions/ClockifyStart.py b/pype/clockify/launcher_actions/ClockifyStart.py similarity index 86% rename from pype/plugins/launcher/actions/ClockifyStart.py rename to pype/clockify/launcher_actions/ClockifyStart.py index 9183805c7f..6a9ceaec73 100644 --- a/pype/plugins/launcher/actions/ClockifyStart.py +++ b/pype/clockify/launcher_actions/ClockifyStart.py @@ -1,9 +1,7 @@ from avalon import api, io from pype.api import Logger -try: - from pype.clockify import ClockifyAPI -except Exception: - pass +from pype.clockify import ClockifyAPI + log = Logger().get_logger(__name__, "clockify_start") @@ -14,13 +12,10 @@ class ClockifyStart(api.Action): label = "Clockify - Start Timer" icon = "clockify_icon" order = 500 - - exec("try: clockapi = ClockifyAPI()\nexcept: clockapi = None") + clockapi = ClockifyAPI() def is_compatible(self, session): """Return whether the action is compatible with the session""" - if self.clockapi is None: - return False if "AVALON_TASK" in session: return True return False diff --git a/pype/plugins/launcher/actions/ClockifySync.py b/pype/clockify/launcher_actions/ClockifySync.py similarity index 87% rename from pype/plugins/launcher/actions/ClockifySync.py rename to pype/clockify/launcher_actions/ClockifySync.py index 0895da555d..3bf389796f 100644 --- a/pype/plugins/launcher/actions/ClockifySync.py +++ b/pype/clockify/launcher_actions/ClockifySync.py @@ -1,8 +1,5 @@ from avalon import api, io -try: - from pype.clockify import ClockifyAPI -except Exception: - pass +from pype.clockify import ClockifyAPI from pype.api import Logger log = Logger().get_logger(__name__, "clockify_sync") @@ -13,16 +10,11 @@ class ClockifySync(api.Action): label = "Sync to Clockify" icon = "clockify_white_icon" order = 500 - exec( - "try:\n\tclockapi = ClockifyAPI()" - "\n\thave_permissions = clockapi.validate_workspace_perm()" - "\nexcept:\n\tclockapi = None" - ) + clockapi = ClockifyAPI() + have_permissions = clockapi.validate_workspace_perm() def is_compatible(self, session): """Return whether the action is compatible with the session""" - if self.clockapi is None: - return False return self.have_permissions def process(self, session, **kwargs): diff --git a/pype/ftrack/actions/action_application_loader.py b/pype/ftrack/actions/action_application_loader.py index 50714e4535..67a158db14 100644 --- a/pype/ftrack/actions/action_application_loader.py +++ b/pype/ftrack/actions/action_application_loader.py @@ -35,10 +35,12 @@ def registerApp(app, session): label = apptoml.get('ftrack_label', app.get('label', name)) icon = apptoml.get('ftrack_icon', None) description = apptoml.get('description', None) + preactions = apptoml.get('preactions', []) # register action AppAction( - session, label, name, executable, variant, icon, description + session, label, name, executable, variant, + icon, description, preactions ).register() diff --git a/pype/ftrack/actions/action_create_folders.py b/pype/ftrack/actions/action_create_folders.py index 4426fb9650..9c8f576e3b 100644 --- a/pype/ftrack/actions/action_create_folders.py +++ b/pype/ftrack/actions/action_create_folders.py @@ -3,11 +3,9 @@ import sys import logging import argparse import re -# import json from pype.vendor import ftrack_api from pype.ftrack import BaseAction -# from pype import api as pype, lib as pypelib from avalon import lib as avalonlib from avalon.tools.libraryloader.io_nonsingleton import DbConnector from pypeapp import config, Anatomy @@ -239,17 +237,6 @@ class CreateFolders(BaseAction): output.extend(self.get_notask_children(child)) return output - # def get_presets(self): - # fpath_items = [pypelib.get_presets_path(), 'tools', 'sw_folders.json'] - # filepath = os.path.normpath(os.path.sep.join(fpath_items)) - # presets = dict() - # try: - # with open(filepath) as data_file: - # presets = json.load(data_file) - # except Exception as e: - # self.log.warning('Wasn\'t able to load presets') - # return dict(presets) - def template_format(self, template, data): partial_data = PartialDict(data) diff --git a/pype/ftrack/actions/action_create_project_folders.py b/pype/ftrack/actions/action_create_project_folders.py index 66e2e153e6..dba2a88f02 100644 --- a/pype/ftrack/actions/action_create_project_folders.py +++ b/pype/ftrack/actions/action_create_project_folders.py @@ -42,7 +42,7 @@ class CreateProjectFolders(BaseAction): else: project = entity['project'] - presets = config.load_presets()['tools']['project_folder_structure'] + presets = config.get_presets()['tools']['project_folder_structure'] try: # Get paths based on presets basic_paths = self.get_path_items(presets) @@ -142,28 +142,6 @@ class CreateProjectFolders(BaseAction): self.session.commit() return new_ent - # def load_presets(self): - # preset_items = [ - # pypelib.get_presets_path(), - # 'tools', - # 'project_folder_structure.json' - # ] - # filepath = os.path.sep.join(preset_items) - # - # # Load folder structure template from presets - # presets = dict() - # try: - # with open(filepath) as data_file: - # presets = json.load(data_file) - # except Exception as e: - # msg = 'Unable to load Folder structure preset' - # self.log.warning(msg) - # return { - # 'success': False, - # 'message': msg - # } - # return presets - def get_path_items(self, in_dict): output = [] for key, value in in_dict.items(): diff --git a/pype/ftrack/actions/action_djvview.py b/pype/ftrack/actions/action_djvview.py index 942aa7a327..29d4604968 100644 --- a/pype/ftrack/actions/action_djvview.py +++ b/pype/ftrack/actions/action_djvview.py @@ -23,9 +23,7 @@ class DJVViewAction(BaseAction): '''Expects a ftrack_api.Session instance''' super().__init__(session) self.djv_path = None - self.config_data = None - # self.load_config_data() self.config_data = config.get_presets()['djv_view']['config'] self.set_djv_path() @@ -54,22 +52,6 @@ class DJVViewAction(BaseAction): return True return False - def load_config_data(self): - # path_items = [pypelib.get_presets_path(), 'djv_view', 'config.json'] - path_items = config.get_presets()['djv_view']['config'] - filepath = os.path.sep.join(path_items) - - data = dict() - try: - with open(filepath) as data_file: - data = json.load(data_file) - except Exception as e: - log.warning( - 'Failed to load data from DJV presets file ({})'.format(e) - ) - - self.config_data = data - def set_djv_path(self): for path in self.config_data.get("djv_paths", []): if os.path.exists(path): diff --git a/pype/ftrack/actions/action_job_killer.py b/pype/ftrack/actions/action_job_killer.py index 25c0c6a489..8cbddecb1c 100644 --- a/pype/ftrack/actions/action_job_killer.py +++ b/pype/ftrack/actions/action_job_killer.py @@ -1,6 +1,7 @@ import sys import argparse import logging +import json from pype.vendor import ftrack_api from pype.ftrack import BaseAction @@ -37,14 +38,18 @@ class JobKiller(BaseAction): ).all() items = [] - import json + item_splitter = {'type': 'label', 'value': '---'} for job in jobs: - data = json.loads(job['data']) + try: + data = json.loads(job['data']) + desctiption = data['description'] + except Exception: + desctiption = '*No description*' user = job['user']['username'] created = job['created_at'].strftime('%d.%m.%Y %H:%M:%S') label = '{} - {} - {}'.format( - data['description'], created, user + desctiption, created, user ) item_label = { 'type': 'label', diff --git a/pype/ftrack/actions/action_rv.py b/pype/ftrack/actions/action_rv.py index 15689ae811..f07d339e5c 100644 --- a/pype/ftrack/actions/action_rv.py +++ b/pype/ftrack/actions/action_rv.py @@ -39,7 +39,6 @@ class RVAction(BaseAction): ) else: # if not, fallback to config file location - # self.load_config_data() self.config_data = config.get_presets()['djv_view']['config'] self.set_rv_path() @@ -61,21 +60,6 @@ class RVAction(BaseAction): return True return False - def load_config_data(self): - path_items = config.get_presets['rv']['config.json'] - filepath = os.path.sep.join(path_items) - - data = dict() - try: - with open(filepath) as data_file: - data = json.load(data_file) - except Exception as e: - log.warning( - 'Failed to load data from RV presets file ({})'.format(e) - ) - - self.config_data = data - def set_rv_path(self): self.rv_path = self.config_data.get("rv_path") diff --git a/pype/ftrack/actions/action_start_timer.py b/pype/ftrack/actions/action_start_timer.py new file mode 100644 index 0000000000..d27908541e --- /dev/null +++ b/pype/ftrack/actions/action_start_timer.py @@ -0,0 +1,79 @@ +from pype.vendor import ftrack_api +from pype.ftrack import BaseAction + + +class StartTimer(BaseAction): + '''Starts timer.''' + + identifier = 'start.timer' + label = 'Start timer' + description = 'Starts timer' + + def discover(self, session, entities, event): + return False + + def _handle_result(*arg): + return + + def launch(self, session, entities, event): + entity = entities[0] + if entity.entity_type.lower() != 'task': + return + self.start_ftrack_timer(entity) + try: + self.start_clockify_timer(entity) + except Exception: + self.log.warning( + 'Failed starting Clockify timer for task: ' + entity['name'] + ) + return + + def start_ftrack_timer(self, task): + user_query = 'User where username is "{}"'.format(self.session.api_user) + user = self.session.query(user_query).one() + self.log.info('Starting Ftrack timer for task: ' + task['name']) + user.start_timer(task, force=True) + self.session.commit() + + def start_clockify_timer(self, task): + # Validate Clockify settings if Clockify is required + clockify_timer = os.environ.get('CLOCKIFY_WORKSPACE', None) + if clockify_timer is None: + return + + from pype.clockify import ClockifyAPI + clockapi = ClockifyAPI() + if clockapi.verify_api() is False: + return + task_type = task['type']['name'] + project_name = task['project']['full_name'] + + def get_parents(entity): + output = [] + if entity.entity_type.lower() == 'project': + return output + output.extend(get_parents(entity['parent'])) + output.append(entity['name']) + + return output + + desc_items = get_parents(task['parent']) + desc_items.append(task['name']) + description = '/'.join(desc_items) + + project_id = clockapi.get_project_id(project_name) + tag_ids = [] + tag_ids.append(clockapi.get_tag_id(task_type)) + clockapi.start_time_entry( + description, project_id, tag_ids=tag_ids + ) + self.log.info('Starting Clockify timer for task: ' + task['name']) + + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + + if not isinstance(session, ftrack_api.session.Session): + return + + StartTimer(session).register() diff --git a/pype/ftrack/actions/event_collect_entities.py b/pype/ftrack/actions/event_collect_entities.py deleted file mode 100644 index 71f2d26ff3..0000000000 --- a/pype/ftrack/actions/event_collect_entities.py +++ /dev/null @@ -1,72 +0,0 @@ -from pype.vendor import ftrack_api -from pype.ftrack import BaseEvent - - -class CollectEntities(BaseEvent): - - priority = 1 - - def _launch(self, event): - entities = self.translate_event(event) - event['data']['entities_object'] = entities - - return - - def translate_event(self, event): - selection = event['data'].get('selection', []) - - entities = list() - for entity in selection: - ent = self.session.get( - self.get_entity_type(entity), - entity.get('entityId') - ) - entities.append(ent) - - return entities - - def get_entity_type(self, entity): - '''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() - - 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 register(self): - self.session.event_hub.subscribe( - 'topic=ftrack.action.discover' - ' and source.user.username={0}'.format(self.session.api_user), - self._launch, - priority=self.priority - ) - - self.session.event_hub.subscribe( - 'topic=ftrack.action.launch' - ' and source.user.username={0}'.format(self.session.api_user), - self._launch, - priority=self.priority - ) - - -def register(session, **kw): - '''Register plugin. Called when used as an plugin.''' - if not isinstance(session, ftrack_api.session.Session): - return - - CollectEntities(session).register() diff --git a/pype/ftrack/ftrack_module.py b/pype/ftrack/ftrack_module.py index 127b39d2fc..fdce0535e8 100644 --- a/pype/ftrack/ftrack_module.py +++ b/pype/ftrack/ftrack_module.py @@ -153,6 +153,7 @@ class FtrackModule: parent_menu.addMenu(self.menu) + def tray_start(self): self.validate() # Definition of visibility of each menu actions diff --git a/pype/ftrack/lib/ftrack_action_handler.py b/pype/ftrack/lib/ftrack_action_handler.py index c6d6181c1f..7a25155718 100644 --- a/pype/ftrack/lib/ftrack_action_handler.py +++ b/pype/ftrack/lib/ftrack_action_handler.py @@ -66,6 +66,10 @@ class BaseAction(BaseHandler): self.session, event ) + preactions_launched = self._handle_preactions(self.session, event) + if preactions_launched is False: + return + interface = self._interface( self.session, *args ) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index e4075e9a19..3c2bc418a8 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -23,10 +23,11 @@ class AppAction(BaseHandler): ''' type = 'Application' + preactions = ['start.timer'] def __init__( self, session, label, name, executable, - variant=None, icon=None, description=None + variant=None, icon=None, description=None, preactions=[] ): super().__init__(session) '''Expects a ftrack_api.Session instance''' @@ -44,6 +45,7 @@ class AppAction(BaseHandler): self.variant = variant self.icon = icon self.description = description + self.preactions.extend(preactions) def register(self): '''Registers the action, subscribing the discover and launch topics.''' @@ -117,6 +119,12 @@ class AppAction(BaseHandler): self.session, event ) + preactions_launched = self._handle_preactions( + self.session, event + ) + if preactions_launched is False: + return + response = self.launch( self.session, *args ) @@ -148,25 +156,6 @@ class AppAction(BaseHandler): entity = entities[0] project_name = entity['project']['full_name'] - # Validate Clockify settings if Clockify is required - clockify_timer = os.environ.get('CLOCKIFY_WORKSPACE', None) - if clockify_timer is not None: - from pype.clockify import ClockifyAPI - clockapi = ClockifyAPI() - if clockapi.verify_api() is False: - title = 'Launch message' - header = '# You Can\'t launch **any Application**' - message = ( - '

You don\'t have set Clockify API' - ' key in Clockify settings

' - ) - items = [ - {'type': 'label', 'value': header}, - {'type': 'label', 'value': message} - ] - self.show_interface(event, items, title) - return False - database = pypelib.get_avalon_database() # Get current environments @@ -335,39 +324,6 @@ class AppAction(BaseHandler): } pass - # RUN TIMER IN FTRACK - username = event['source']['user']['username'] - user_query = 'User where username is "{}"'.format(username) - user = session.query(user_query).one() - task = session.query('Task where id is {}'.format(entity['id'])).one() - self.log.info('Starting timer for task: ' + task['name']) - user.start_timer(task, force=True) - - # RUN TIMER IN Clockify - if clockify_timer is not None: - task_type = task['type']['name'] - project_name = task['project']['full_name'] - - def get_parents(entity): - output = [] - if entity.entity_type.lower() == 'project': - return output - output.extend(get_parents(entity['parent'])) - output.append(entity['name']) - - return output - - desc_items = get_parents(task['parent']) - desc_items.append(task['name']) - description = '/'.join(desc_items) - - project_id = clockapi.get_project_id(project_name) - tag_ids = [] - tag_ids.append(clockapi.get_tag_id(task_type)) - clockapi.start_time_entry( - description, project_id, tag_ids=tag_ids - ) - # Change status of task to In progress config = get_config_data() diff --git a/pype/ftrack/lib/ftrack_base_handler.py b/pype/ftrack/lib/ftrack_base_handler.py index 7a04ba329c..24ece4f11d 100644 --- a/pype/ftrack/lib/ftrack_base_handler.py +++ b/pype/ftrack/lib/ftrack_base_handler.py @@ -25,6 +25,7 @@ class BaseHandler(object): priority = 100 # Type is just for logging purpose (e.g.: Action, Event, Application,...) type = 'No-type' + preactions = [] def __init__(self, session): '''Expects a ftrack_api.Session instance''' @@ -46,18 +47,7 @@ class BaseHandler(object): else: label = '{} {}'.format(self.label, self.variant) try: - 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 - for role in user['user_security_roles']: - if role['security_role']['name'] in self.role_list: - available = True - break - if available is False: - raise MissingPermision + self._preregister() start_time = time.perf_counter() func(*args, **kwargs) @@ -119,6 +109,36 @@ class BaseHandler(object): 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 + for role in user['user_security_roles']: + if role['security_role']['name'] in self.role_list: + available = True + break + if available is False: + raise MissingPermision + + # Custom validations + result = self.preregister() + if result is True: + return + msg = "Pre-register conditions were not met" + if isinstance(result, str): + msg = result + raise Exception(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. @@ -227,6 +247,10 @@ class BaseHandler(object): self.session, event ) + preactions_launched = self._handle_preactions(self.session, event) + if preactions_launched is False: + return + interface = self._interface( self.session, *args ) @@ -263,6 +287,47 @@ class BaseHandler(object): ''' 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: + event = ftrack_api.event.base.Event( + topic='ftrack.action.launch', + data=dict( + actionIdentifier=preaction, + selection=selection + ), + source=dict( + user=dict(username=session.api_user) + ) + ) + session.event_hub.publish(event, on_error='ignore') + # Relaunch this action + event = ftrack_api.event.base.Event( + topic='ftrack.action.launch', + data=dict( + actionIdentifier=self.identifier, + selection=selection, + preactions_launched=True + ), + source=dict( + user=dict(username=session.api_user) + ) + ) + session.event_hub.publish(event, on_error='ignore') + + return False + def _interface(self, *args): interface = self.interface(*args) if interface: diff --git a/pype/services/idle_manager/__init__.py b/pype/services/idle_manager/__init__.py index 7c07d3ebee..f1a87bef41 100644 --- a/pype/services/idle_manager/__init__.py +++ b/pype/services/idle_manager/__init__.py @@ -2,6 +2,4 @@ from .idle_manager import IdleManager def tray_init(tray_widget, main_widget): - manager = IdleManager() - manager.start() - return manager + return IdleManager() diff --git a/pype/services/idle_manager/idle_manager.py b/pype/services/idle_manager/idle_manager.py index e8ba246121..f7d7f2b34e 100644 --- a/pype/services/idle_manager/idle_manager.py +++ b/pype/services/idle_manager/idle_manager.py @@ -17,8 +17,12 @@ class IdleManager(QtCore.QThread): super(IdleManager, self).__init__() self.log = Logger().get_logger(self.__class__.__name__) self.signal_reset_timer.connect(self._reset_time) + self._failed = False self._is_running = False + def tray_start(self): + self.start() + 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 @@ -30,6 +34,10 @@ class IdleManager(QtCore.QThread): self.time_signals[emit_time] = [] self.time_signals[emit_time].append(signal) + @property + def failed(self): + return self._failed + @property def is_running(self): return self._is_running @@ -60,6 +68,8 @@ class IdleManager(QtCore.QThread): thread_keyboard.signal_stop.emit() thread_keyboard.terminate() thread_keyboard.wait() + self._failed = True + self._is_running = False self.log.info('IdleManager has stopped') diff --git a/pype/services/timers_manager/timers_manager.py b/pype/services/timers_manager/timers_manager.py index 6f10a0ec68..319e4c6910 100644 --- a/pype/services/timers_manager/timers_manager.py +++ b/pype/services/timers_manager/timers_manager.py @@ -25,6 +25,7 @@ class TimersManager(metaclass=Singleton): when user idles for a long time (set in presets). """ modules = [] + failed = False is_running = False last_task = None