Merge pull request #804 from pypeclub/feature/modules_manager

Pype modules manager
This commit is contained in:
Milan Kolar 2020-12-11 12:29:35 +01:00 committed by GitHub
commit 8226a73802
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1517 additions and 1079 deletions

View file

@ -73,7 +73,7 @@ def compose_url(scheme=None,
def get_default_components():
mongo_url = os.environ.get("AVALON_MONGO")
mongo_url = os.environ.get("PYPE_MONGO")
if mongo_url is None:
raise MongoEnvNotSet(
"URL for Mongo logging connection is not set."

View file

@ -1,6 +1,71 @@
# -*- coding: utf-8 -*-
from .base import PypeModule
from .base import (
PypeModule,
ITrayModule,
ITrayService,
IPluginPaths,
ModulesManager,
TrayModulesManager
)
from .rest_api import (
RestApiModule,
IRestApi
)
from .user import (
UserModule,
IUserModule
)
from .idle_manager import (
IdleManager,
IIdleManager
)
from .timers_manager import (
TimersManager,
ITimersManager
)
from .avalon_apps import AvalonModule
from .ftrack import (
FtrackModule,
IFtrackEventHandlerPaths
)
from .clockify import ClockifyModule
from .logging import LoggingModule
from .muster import MusterModule
from .standalonepublish import StandAlonePublishModule
from .websocket_server import WebsocketModule
__all__ = (
"PypeModule",
"ITrayModule",
"ITrayService",
"IPluginPaths",
"ModulesManager",
"TrayModulesManager",
"UserModule",
"IUserModule",
"IdleManager",
"IIdleManager",
"TimersManager",
"ITimersManager",
"RestApiModule",
"IRestApi",
"AvalonModule",
"FtrackModule",
"IFtrackEventHandlerPaths",
"ClockifyModule",
"IdleManager",
"LoggingModule",
"MusterModule",
"StandAlonePublishModule",
"WebsocketModule"
)

View file

@ -1,5 +1,6 @@
from .avalon_app import AvalonApps
from .avalon_app import AvalonModule
def tray_init(tray_widget, main_widget):
return AvalonApps(main_widget, tray_widget)
__all__ = (
"AvalonModule",
)

View file

@ -1,61 +1,153 @@
from pype.api import Logger
import os
import pype
from pype import resources
from .. import (
PypeModule,
ITrayModule,
IRestApi
)
class AvalonApps:
def __init__(self, main_parent=None, parent=None):
self.log = Logger().get_logger(__name__)
class AvalonModule(PypeModule, ITrayModule, IRestApi):
name = "Avalon"
self.tray_init(main_parent, parent)
def initialize(self, modules_settings):
# This module is always enabled
self.enabled = True
def tray_init(self, main_parent, parent):
from avalon.tools.libraryloader import app
from avalon import style
from pype.tools.launcher import LauncherWindow, actions
avalon_settings = modules_settings[self.name]
self.parent = parent
self.main_parent = main_parent
# Check if environment is already set
avalon_mongo_url = os.environ.get("AVALON_MONGO")
if not avalon_mongo_url:
avalon_mongo_url = avalon_settings["AVALON_MONGO"]
# Use pype mongo if Avalon's mongo not defined
if not avalon_mongo_url:
avalon_mongo_url = os.environ["PYPE_MONGO"]
self.app_launcher = LauncherWindow()
self.libraryloader = app.Window(
icon=self.parent.icon,
show_projects=True,
show_libraries=True
thumbnail_root = os.environ.get("AVALON_THUMBNAIL_ROOT")
if not thumbnail_root:
thumbnail_root = avalon_settings["AVALON_THUMBNAIL_ROOT"]
# Mongo timeout
avalon_mongo_timeout = os.environ.get("AVALON_TIMEOUT")
if not avalon_mongo_timeout:
avalon_mongo_timeout = avalon_settings["AVALON_TIMEOUT"]
self.thumbnail_root = thumbnail_root
self.avalon_mongo_url = avalon_mongo_url
self.avalon_mongo_timeout = avalon_mongo_timeout
self.schema_path = os.path.join(
os.path.dirname(pype.PACKAGE_DIR),
"schema"
)
self.libraryloader.setStyleSheet(style.load_stylesheet())
# actions.register_default_actions()
actions.register_config_actions()
actions.register_environment_actions()
# Tray attributes
self.app_launcher = None
self.libraryloader = None
self.rest_api_obj = None
def process_modules(self, modules):
if "RestApiServer" in modules:
def get_global_environments(self):
"""Avalon global environments for pype implementation."""
mongodb_data_dir = os.environ.get("AVALON_DB_DATA")
if not mongodb_data_dir:
mongodb_data_dir = os.path.join(
os.path.dirname(os.environ["PYPE_ROOT"]),
"mongo_db_data"
)
return {
# 100% hardcoded
"AVALON_SCHEMA": self.schema_path,
"AVALON_CONFIG": "pype",
"AVALON_LABEL": "Pype",
# Modifiable by settings
# - mongo ulr for avalon projects
"AVALON_MONGO": self.avalon_mongo_url,
# TODO thumbnails root should be multiplafrom
# - thumbnails root
"AVALON_THUMBNAIL_ROOT": self.thumbnail_root,
# - mongo timeout in ms
"AVALON_TIMEOUT": str(self.avalon_mongo_timeout),
# May be modifiable?
# - mongo database name where projects are stored
"AVALON_DB": "avalon",
# Not even connected to Avalon
# TODO remove - pype's variable for local mongo
"AVALON_DB_DATA": mongodb_data_dir
}
def tray_init(self):
# Add library tool
try:
from avalon.tools.libraryloader import app
from avalon import style
from Qt import QtGui
self.libraryloader = app.Window(
icon=QtGui.QIcon(resources.pype_icon_filepath()),
show_projects=True,
show_libraries=True
)
self.libraryloader.setStyleSheet(style.load_stylesheet())
except Exception:
self.log.warning(
"Couldn't load Library loader tool for tray.",
exc_info=True
)
# Add launcher
try:
from pype.tools.launcher import LauncherWindow
self.app_launcher = LauncherWindow()
except Exception:
self.log.warning(
"Couldn't load Launch for tray.",
exc_info=True
)
def connect_with_modules(self, _enabled_modules):
plugin_paths = self.manager.collect_plugin_paths()["actions"]
if plugin_paths:
env_paths_str = os.environ.get("AVALON_ACTIONS") or ""
env_paths = env_paths_str.split(os.pathsep)
env_paths.extend(plugin_paths)
os.environ["AVALON_ACTIONS"] = os.pathsep.join(env_paths)
if self.tray_initialized:
from pype.tools.launcher import actions
# actions.register_default_actions()
actions.register_config_actions()
actions.register_environment_actions()
def rest_api_initialization(self, rest_api_module):
if self.tray_initialized:
from .rest_api import AvalonRestApi
self.rest_api_obj = AvalonRestApi()
# Definition of Tray menu
def tray_menu(self, parent_menu=None):
def tray_menu(self, tray_menu):
from Qt import QtWidgets
# 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_launcher = QtWidgets.QAction("Launcher", tray_menu)
action_library_loader = QtWidgets.QAction(
"Library loader", parent_menu
"Library loader", tray_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)
tray_menu.addAction(action_launcher)
tray_menu.addAction(action_library_loader)
def tray_start(self, *_a, **_kw):
return
def tray_exit(self, *_a, **_kw):
return
def show_launcher(self):
# if app_launcher don't exist create it/otherwise only show main window

View file

@ -1,38 +1,486 @@
# -*- coding: utf-8 -*-
"""Base class for Pype Modules."""
import inspect
import logging
from uuid import uuid4
from abc import ABC, abstractmethod
from pype.api import Logger
from abc import ABCMeta, abstractmethod
import six
import pype
from pype.settings import get_system_settings
from pype.lib import PypeLogger
from pype import resources
class PypeModule(ABC):
@six.add_metaclass(ABCMeta)
class PypeModule:
"""Base class of pype module.
Attributes:
id (UUID): Module id.
id (UUID): Module's id.
enabled (bool): Is module enabled.
name (str): Module name.
manager (ModulesManager): Manager that created the module.
"""
# Disable by default
enabled = False
name = None
_id = None
def __init__(self, settings):
if self.name is None:
self.name = self.__class__.__name__
@property
@abstractmethod
def name(self):
"""Module's name."""
pass
self.log = Logger().get_logger(self.name)
def __init__(self, manager, settings):
self.manager = manager
self.settings = settings.get(self.name)
self.enabled = settings.get("enabled", False)
self._id = uuid4()
self.log = PypeLogger().get_logger(self.name)
self.initialize(settings)
@property
def id(self):
if self._id is None:
self._id = uuid4()
return self._id
@abstractmethod
def startup_environments(self):
"""Get startup environments for module."""
def initialize(self, module_settings):
"""Initialization of module attributes.
It is not recommended to override __init__ that's why specific method
was implemented.
"""
pass
@abstractmethod
def connect_with_modules(self, enabled_modules):
"""Connect with other enabled modules."""
pass
def get_global_environments(self):
"""Get global environments values of module.
Environment variables that can be get only from system settings.
"""
return {}
@six.add_metaclass(ABCMeta)
class IPluginPaths:
"""Module has plugin paths to return.
Expected result is dictionary with keys "publish", "create", "load" or
"actions" and values as list or string.
{
"publish": ["path/to/publish_plugins"]
}
"""
# TODO validation of an output
@abstractmethod
def get_plugin_paths(self):
pass
@six.add_metaclass(ABCMeta)
class ITrayModule:
"""Module has special procedures when used in Pype Tray.
IMPORTANT:
The module still must be usable if is not used in tray even if
would do nothing.
"""
tray_initialized = False
@abstractmethod
def tray_init(self):
"""Initialization part of tray implementation.
Triggered between `initialization` and `connect_with_modules`.
This is where GUIs should be loaded or tray specific parts should be
prepared.
"""
pass
@abstractmethod
def tray_menu(self, tray_menu):
"""Add module's action to tray menu."""
pass
@abstractmethod
def tray_start(self):
"""Start procedure in Pype tray."""
pass
@abstractmethod
def tray_exit(self):
"""Cleanup method which is executed on tray shutdown.
This is place where all threads should be shut.
"""
pass
class ITrayService(ITrayModule):
# Module's property
menu_action = None
# Class properties
_services_submenu = None
_icon_failed = None
_icon_running = None
_icon_idle = None
@property
@abstractmethod
def label(self):
"""Service label showed in menu."""
pass
# TODO be able to get any sort of information to show/print
# @abstractmethod
# def get_service_info(self):
# pass
@staticmethod
def services_submenu(tray_menu):
if ITrayService._services_submenu is None:
from Qt import QtWidgets
services_submenu = QtWidgets.QMenu("Services", tray_menu)
services_submenu.setVisible(False)
ITrayService._services_submenu = services_submenu
return ITrayService._services_submenu
@staticmethod
def add_service_action(action):
ITrayService._services_submenu.addAction(action)
if not ITrayService._services_submenu.isVisible():
ITrayService._services_submenu.setVisible(True)
@staticmethod
def _load_service_icons():
from Qt import QtGui
ITrayService._failed_icon = QtGui.QIcon(
resources.get_resource("icons", "circle_red.png")
)
ITrayService._icon_running = QtGui.QIcon(
resources.get_resource("icons", "circle_green.png")
)
ITrayService._icon_idle = QtGui.QIcon(
resources.get_resource("icons", "circle_orange.png")
)
@staticmethod
def get_icon_running():
if ITrayService._icon_running is None:
ITrayService._load_service_icons()
return ITrayService._icon_running
@staticmethod
def get_icon_idle():
if ITrayService._icon_idle is None:
ITrayService._load_service_icons()
return ITrayService._icon_idle
@staticmethod
def get_icon_failed():
if ITrayService._failed_icon is None:
ITrayService._load_service_icons()
return ITrayService._failed_icon
def tray_menu(self, tray_menu):
from Qt import QtWidgets
action = QtWidgets.QAction(
self.label,
self.services_submenu(tray_menu)
)
self.menu_action = action
self.add_service_action(action)
self.set_service_running_icon()
def set_service_running_icon(self):
"""Change icon of an QAction to green circle."""
if self.menu_action:
self.menu_action.setIcon(self.get_icon_running())
def set_service_failed_icon(self):
"""Change icon of an QAction to red circle."""
if self.menu_action:
self.menu_action.setIcon(self.get_icon_failed())
def set_service_idle_icon(self):
"""Change icon of an QAction to orange circle."""
if self.menu_action:
self.menu_action.setIcon(self.get_icon_idle())
class ModulesManager:
def __init__(self):
self.log = logging.getLogger(self.__class__.__name__)
self.modules = []
self.modules_by_id = {}
self.modules_by_name = {}
self.initialize_modules()
self.connect_modules()
def initialize_modules(self):
"""Import and initialize modules."""
self.log.debug("*** Pype modules initialization.")
# Prepare settings for modules
modules_settings = get_system_settings()["modules"]
# Go through globals in `pype.modules`
for name in dir(pype.modules):
modules_item = getattr(pype.modules, name, None)
# Filter globals that are not classes which inherit from PypeModule
if (
not inspect.isclass(modules_item)
or modules_item is pype.modules.PypeModule
or not issubclass(modules_item, pype.modules.PypeModule)
):
continue
# Check if class is abstract (Developing purpose)
if inspect.isabstract(modules_item):
# Find missing implementations by convetion on `abc` module
not_implemented = []
for attr_name in dir(modules_item):
attr = getattr(modules_item, attr_name, None)
if attr and getattr(attr, "__isabstractmethod__", None):
not_implemented.append(attr_name)
# Log missing implementations
self.log.warning((
"Skipping abstract Class: {}. Missing implementations: {}"
).format(name, ", ".join(not_implemented)))
continue
try:
# Try initialize module
module = modules_item(self, modules_settings)
# Store initialized object
self.modules.append(module)
self.modules_by_id[module.id] = module
self.modules_by_name[module.name] = module
enabled_str = "X"
if not module.enabled:
enabled_str = " "
self.log.debug("[{}] {}".format(enabled_str, name))
except Exception:
self.log.warning(
"Initialization of module {} failed.".format(name),
exc_info=True
)
def connect_modules(self):
"""Trigger connection with other enabled modules.
Modules should handle their interfaces in `connect_with_modules`.
"""
enabled_modules = self.get_enabled_modules()
self.log.debug("Has {} enabled modules.".format(len(enabled_modules)))
for module in enabled_modules:
module.connect_with_modules(enabled_modules)
def get_enabled_modules(self):
"""Enabled modules initialized by the manager.
Returns:
list: Initialized and enabled modules.
"""
return [
module
for module in self.modules
if module.enabled
]
def collect_global_environments(self):
"""Helper to collect global enviornment variabled from modules.
Returns:
dict: Global environment variables from enabled modules.
Raises:
AssertionError: Gobal environment variables must be unique for
all modules.
"""
module_envs = {}
for module in self.get_enabled_modules():
# Collect global module's global environments
_envs = module.get_global_environments()
for key, value in _envs.items():
if key in module_envs:
# TODO better error message
raise AssertionError(
"Duplicated environment key {}".format(key)
)
module_envs[key] = value
return module_envs
def collect_plugin_paths(self):
"""Helper to collect all plugins from modules inherited IPluginPaths.
Unknown keys are logged out.
Returns:
dict: Output is dictionary with keys "publish", "create", "load"
and "actions" each containing list of paths.
"""
# Output structure
output = {
"publish": [],
"create": [],
"load": [],
"actions": []
}
unknown_keys_by_module = {}
for module in self.get_enabled_modules():
# Skip module that do not inherit from `IPluginPaths`
if not isinstance(module, IPluginPaths):
continue
plugin_paths = module.get_plugin_paths()
for key, value in plugin_paths.items():
# Filter unknown keys
if key not in output:
if module.name not in unknown_keys_by_module:
unknown_keys_by_module[module.name] = []
unknown_keys_by_module[module.name].append(key)
continue
# Skip if value is empty
if not value:
continue
# Convert to list if value is not list
if not isinstance(value, (list, tuple, set)):
value = [value]
output[key].extend(value)
# Report unknown keys (Developing purposes)
if unknown_keys_by_module:
expected_keys = ", ".join([
"\"{}\"".format(key) for key in output.keys()
])
msg_template = "Module: \"{}\" - got key {}"
msg_items = []
for module_name, keys in unknown_keys_by_module.items():
joined_keys = ", ".join([
"\"{}\"".format(key) for key in keys
])
msg_items.append(msg_template.format(module_name, joined_keys))
self.log.warning((
"Expected keys from `get_plugin_paths` are {}. {}"
).format(expected_keys, " | ".join(msg_items)))
return output
class TrayModulesManager(ModulesManager):
# Define order of modules in menu
modules_menu_order = (
"User setting",
"Ftrack",
"muster",
"Avalon",
"Clockify",
"Standalone Publish",
"Logging"
)
def __init__(self):
self.log = PypeLogger().get_logger(self.__class__.__name__)
self.modules = []
self.modules_by_id = {}
self.modules_by_name = {}
def initialize(self, tray_menu):
self.initialize_modules()
self.tray_init()
self.connect_modules()
self.tray_menu(tray_menu)
def get_enabled_tray_modules(self):
output = []
for module in self.modules:
if module.enabled and isinstance(module, ITrayModule):
output.append(module)
return output
def tray_init(self):
for module in self.get_enabled_tray_modules():
try:
module.tray_init()
module.tray_initialized = True
except Exception:
self.log.warning(
"Module \"{}\" crashed on `tray_init`.".format(
module.name
),
exc_info=True
)
def tray_menu(self, tray_menu):
ordered_modules = []
enabled_by_name = {
module.name: module
for module in self.get_enabled_tray_modules()
}
for name in self.modules_menu_order:
module_by_name = enabled_by_name.pop(name, None)
if module_by_name:
ordered_modules.append(module_by_name)
ordered_modules.extend(enabled_by_name.values())
for module in ordered_modules:
if not module.tray_initialized:
continue
try:
module.tray_menu(tray_menu)
except Exception:
# Unset initialized mark
module.tray_initialized = False
self.log.warning(
"Module \"{}\" crashed on `tray_menu`.".format(
module.name
),
exc_info=True
)
def start_modules(self):
for module in self.get_enabled_tray_modules():
if not module.tray_initialized:
if isinstance(module, ITrayService):
module.set_service_failed_icon()
continue
try:
module.tray_start()
except Exception:
self.log.warning(
"Module \"{}\" crashed on `tray_start`.".format(
module.name
),
exc_info=True
)
def on_exit(self):
for module in self.get_enabled_tray_modules():
if module.tray_initialized:
try:
module.tray_exit()
except Exception:
self.log.warning(
"Module \"{}\" crashed on `tray_exit`.".format(
module.name
),
exc_info=True
)

View file

@ -1,7 +1,5 @@
from .clockify import ClockifyModule
from .clockify_module import ClockifyModule
CLASS_DEFINIION = ClockifyModule
def tray_init(tray_widget, main_widget):
return ClockifyModule(main_widget, tray_widget)
__all__ = (
"ClockifyModule",
)

View file

@ -2,37 +2,55 @@ import os
import threading
import time
from pype.api import Logger
from .clockify_api import ClockifyAPI
from .constants import CLOCKIFY_FTRACK_USER_PATH
from .constants import (
CLOCKIFY_FTRACK_USER_PATH,
CLOCKIFY_FTRACK_SERVER_PATH
)
from pype.modules import (
PypeModule,
ITrayModule,
IPluginPaths,
IFtrackEventHandlerPaths,
ITimersManager
)
class ClockifyModule:
workspace_name = None
class ClockifyModule(
PypeModule,
ITrayModule,
IPluginPaths,
IFtrackEventHandlerPaths,
ITimersManager
):
name = "Clockify"
def __init__(self, main_parent=None, parent=None):
if not self.workspace_name:
raise Exception("Clockify Workspace is not set in config.")
def initialize(self, modules_settings):
clockify_settings = modules_settings[self.name]
self.enabled = clockify_settings["enabled"]
self.workspace_name = clockify_settings["workspace_name"]
os.environ["CLOCKIFY_WORKSPACE"] = self.workspace_name
if self.enabled and not self.workspace_name:
raise Exception("Clockify Workspace is not set in settings.")
self.timer_manager = None
self.MessageWidgetClass = None
self.message_widget = None
self.clockapi = ClockifyAPI(master_parent=self)
self.log = Logger().get_logger(self.__class__.__name__, "PypeTray")
self.tray_init(main_parent, parent)
def get_global_environments(self):
return {
"CLOCKIFY_WORKSPACE": self.workspace_name
}
def tray_init(self, main_parent, parent):
def tray_init(self):
from .widgets import ClockifySettings, MessageWidget
self.MessageWidgetClass = MessageWidget
self.main_parent = main_parent
self.parent = parent
self.message_widget = None
self.widget_settings = ClockifySettings(main_parent, self)
self.widget_settings = ClockifySettings(self.clockapi)
self.widget_settings_required = None
self.thread_timer_check = None
@ -56,43 +74,33 @@ class ClockifyModule:
self.set_menu_visibility()
def process_modules(self, modules):
if 'FtrackModule' in modules:
current = os.environ.get('FTRACK_ACTIONS_PATH', '')
if current:
current += os.pathsep
os.environ['FTRACK_ACTIONS_PATH'] = (
current + CLOCKIFY_FTRACK_USER_PATH
)
def tray_exit(self, *_a, **_kw):
return
if 'AvalonApps' in modules:
actions_path = os.path.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 get_plugin_paths(self):
"""Implementaton of IPluginPaths to get plugin paths."""
actions_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"launcher_actions"
)
return {
"actions": [actions_path]
}
if 'TimersManager' in modules:
self.timer_manager = modules['TimersManager']
self.timer_manager.add_module(self)
def get_event_handler_paths(self):
"""Implementaton of IFtrackEventHandlerPaths to get plugin paths."""
return {
"user": [CLOCKIFY_FTRACK_USER_PATH],
"server": [CLOCKIFY_FTRACK_SERVER_PATH]
}
def start_timer_manager(self, data):
self.start_timer(data)
def connect_with_modules(self, *_a, **_kw):
return
def stop_timer_manager(self):
self.stop_timer()
def timer_started(self, data):
if self.timer_manager:
self.timer_manager.start_timers(data)
def timer_stopped(self):
def clockify_timer_stopped(self):
self.bool_timer_run = False
if self.timer_manager:
self.timer_manager.stop_timers()
# Call `ITimersManager` method
self.timer_stopped()
def start_timer_check(self):
self.bool_thread_check_running = True
@ -110,7 +118,6 @@ class ClockifyModule:
self.thread_timer_check = None
def check_running(self):
while self.bool_thread_check_running is True:
bool_timer_run = False
if self.clockapi.get_in_progress() is not None:
@ -118,7 +125,7 @@ class ClockifyModule:
if self.bool_timer_run != bool_timer_run:
if self.bool_timer_run is True:
self.timer_stopped()
self.clockify_timer_stopped()
elif self.bool_timer_run is False:
actual_timer = self.clockapi.get_in_progress()
if not actual_timer:
@ -151,7 +158,7 @@ class ClockifyModule:
"project_name": project_name,
"task_type": task_type
}
# Call `ITimersManager` method
self.timer_started(data)
self.bool_timer_run = bool_timer_run
@ -159,9 +166,8 @@ class ClockifyModule:
time.sleep(5)
def stop_timer(self):
"""Implementation of ITimersManager."""
self.clockapi.finish_time_entry()
if self.bool_timer_run:
self.timer_stopped()
def signed_in(self):
if not self.timer_manager:
@ -174,6 +180,7 @@ class ClockifyModule:
self.start_timer_manager(self.timer_manager.last_task)
def start_timer(self, input_data):
"""Implementation of ITimersManager."""
# If not api key is not entered then skip
if not self.clockapi.get_api_key():
return
@ -198,20 +205,20 @@ class ClockifyModule:
"Project \"{}\" was not found in Clockify. Timer won't start."
).format(project_name))
if not self.MessageWidgetClass:
return
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_name))
if self.MessageWidgetClass:
self.message_widget = self.MessageWidgetClass(
self.main_parent, msg, "Clockify - Info Message"
)
self.message_widget.closed.connect(
self.on_message_widget_close
)
self.message_widget.show()
self.message_widget = self.MessageWidgetClass(
msg, "Clockify - Info Message"
)
self.message_widget.closed.connect(self.on_message_widget_close)
self.message_widget.show()
return

View file

@ -1,6 +1,6 @@
from Qt import QtCore, QtGui, QtWidgets
from avalon import style
from pype.api import resources
from pype import resources
class MessageWidget(QtWidgets.QWidget):
@ -10,18 +10,12 @@ class MessageWidget(QtWidgets.QWidget):
closed = QtCore.Signal()
def __init__(self, parent=None, messages=[], title="Message"):
def __init__(self, messages, title):
super(MessageWidget, self).__init__()
self._parent = parent
# Icon
if parent and hasattr(parent, 'icon'):
self.setWindowIcon(parent.icon)
else:
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
self.setWindowFlags(
QtCore.Qt.WindowCloseButtonHint |
@ -93,30 +87,21 @@ class MessageWidget(QtWidgets.QWidget):
class ClockifySettings(QtWidgets.QWidget):
SIZE_W = 300
SIZE_H = 130
loginSignal = QtCore.Signal(object, object, object)
def __init__(self, main_parent=None, parent=None, optional=True):
def __init__(self, clockapi, optional=True):
super(ClockifySettings, self).__init__()
self.parent = parent
self.main_parent = main_parent
self.clockapi = parent.clockapi
self.clockapi = clockapi
self.optional = optional
self.validated = False
# Icon
if hasattr(parent, 'icon'):
self.setWindowIcon(self.parent.icon)
elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'):
self.setWindowIcon(self.parent.parent.icon)
else:
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
self.setWindowFlags(
QtCore.Qt.WindowCloseButtonHint |

View file

@ -0,0 +1,6 @@
from .deadline_module import DeadlineModule
__all__ = (
"DeadlineModule",
)

View file

@ -0,0 +1,20 @@
from .. import PypeModule
class DeadlineModule(PypeModule):
name = "deadline"
def initialize(self, modules_settings):
# This module is always enabled
deadline_settings = modules_settings[self.name]
self.enabled = deadline_settings["enabled"]
self.deadline_url = deadline_settings["DEADLINE_REST_URL"]
def get_global_environments(self):
"""Deadline global environments for pype implementation."""
return {
"DEADLINE_REST_URL": self.deadline_url
}
def connect_with_modules(self, *_a, **_kw):
return

View file

@ -1,16 +1,15 @@
import os
from .ftrack_module import (
FtrackModule,
IFtrackEventHandlerPaths
)
from . import ftrack_server
from .ftrack_server import FtrackServer, check_ftrack_url
from .lib import BaseHandler, BaseEvent, BaseAction, ServerAction
from pype.api import get_system_settings
# TODO: set in ftrack module
os.environ["FTRACK_SERVER"] = (
get_system_settings()["modules"]["Ftrack"]["ftrack_server"]
)
__all__ = (
"FtrackModule",
"IFtrackEventHandlerPaths",
"ftrack_server",
"FtrackServer",
"check_ftrack_url",

View file

@ -0,0 +1,102 @@
import os
from abc import ABCMeta, abstractmethod
import six
import pype
from pype.modules import (
PypeModule, ITrayModule, IPluginPaths, ITimersManager, IUserModule
)
@six.add_metaclass(ABCMeta)
class IFtrackEventHandlerPaths:
"""Other modules interface to return paths to ftrack event handlers.
Expected output is dictionary with "server" and "user" keys.
"""
@abstractmethod
def get_event_handler_paths(self):
pass
class FtrackModule(
PypeModule, ITrayModule, IPluginPaths, ITimersManager, IUserModule
):
name = "Ftrack"
def initialize(self, settings):
ftrack_settings = settings[self.name]
self.enabled = ftrack_settings["enabled"]
self.ftrack_url = ftrack_settings["ftrack_server"]
# TODO load from settings
self.server_event_handlers_paths = []
self.user_event_handlers_paths = []
# Prepare attribute
self.tray_module = None
def get_global_environments(self):
"""Ftrack's global environments."""
return {
"FTRACK_SERVER": self.ftrack_url
}
def get_plugin_paths(self):
"""Ftrack plugin paths."""
return {
"publish": [os.path.join(pype.PLUGINS_DIR, "ftrack", "publish")]
}
def connect_with_modules(self, enabled_modules):
for module in enabled_modules:
if not isinstance(module, IFtrackEventHandlerPaths):
continue
paths_by_type = module.get_event_handler_paths() or {}
for key, value in paths_by_type.items():
if not value:
continue
if key not in ("server", "user"):
self.log.warning(
"Unknown event handlers key \"{}\" skipping.".format(
key
)
)
continue
if not isinstance(value, (list, tuple, set)):
value = [value]
if key == "server":
self.server_event_handlers_paths.extend(value)
elif key == "user":
self.user_event_handlers_paths.extend(value)
def start_timer(self, data):
"""Implementation of ITimersManager interface."""
if self.tray_module:
self.tray_module.start_timer_manager(data)
def stop_timer(self):
"""Implementation of ITimersManager interface."""
if self.tray_module:
self.tray_module.stop_timer_manager()
def on_pype_user_change(self, username):
"""Implementation of IUserModule interface."""
if self.tray_module:
self.tray_module.changed_user()
def tray_init(self):
from .tray import FtrackTrayWrapper
self.tray_module = FtrackTrayWrapper(self)
def tray_menu(self, parent_menu):
return self.tray_module.tray_menu(parent_menu)
def tray_start(self):
return self.tray_module.validate()
def tray_exit(self):
return self.tray_module.stop_action_server()

View file

@ -52,11 +52,11 @@ def check_active_collection(func):
class CustomDbConnector:
log = logging.getLogger(__name__)
timeout = int(os.environ["AVALON_TIMEOUT"])
def __init__(
self, uri, database_name, port=None, collection_name=None
):
self.timeout = int(os.environ["AVALON_TIMEOUT"])
self._mongo_client = None
self._sentry_client = None
self._sentry_logging_handler = None

View file

@ -127,14 +127,15 @@ class StorerEventHub(SocketBaseEventHub):
class ProcessEventHub(SocketBaseEventHub):
hearbeat_msg = b"processor"
uri, port, database, collection_name = get_ftrack_event_mongo_info()
is_collection_created = False
pypelog = Logger().get_logger("Session Processor")
def __init__(self, *args, **kwargs):
self.uri, self.port, self.database, self.collection_name = (
get_ftrack_event_mongo_info()
)
self.dbcon = CustomDbConnector(
self.uri,
self.database,

View file

@ -1,5 +1,6 @@
from .ftrack_module import FtrackModule
from .ftrack_tray import FtrackTrayWrapper
def tray_init(tray_widget, main_widget):
return FtrackModule(main_widget, tray_widget)
__all__ = (
"FtrackTrayWrapper",
)

View file

@ -10,15 +10,15 @@ from ..ftrack_server import socket_thread
from ..lib import credentials
from . import login_dialog
from pype.api import Logger, resources, get_system_settings
from pype.api import Logger, resources
log = Logger().get_logger("FtrackModule", "ftrack")
class FtrackModule:
def __init__(self, main_parent=None, parent=None):
self.parent = parent
class FtrackTrayWrapper:
def __init__(self, module):
self.module = module
self.thread_action_server = None
self.thread_socket_server = None
@ -29,13 +29,12 @@ class FtrackModule:
self.bool_action_thread_running = False
self.bool_timer_event = False
self.load_ftrack_url()
self.widget_login = login_dialog.CredentialsDialog()
self.widget_login.login_changed.connect(self.on_login_change)
self.widget_login.logout_signal.connect(self.on_logout)
self.action_credentials = None
self.tray_server_menu = None
self.icon_logged = QtGui.QIcon(
resources.get_resource("icons", "circle_green.png")
)
@ -118,7 +117,8 @@ class FtrackModule:
self.bool_action_server_running = True
self.bool_action_thread_running = False
ftrack_url = os.environ['FTRACK_SERVER']
ftrack_url = self.module.ftrack_url
os.environ["FTRACK_SERVER"] = ftrack_url
parent_file_path = os.path.dirname(
os.path.dirname(os.path.realpath(__file__))
@ -294,15 +294,6 @@ class FtrackModule:
def tray_exit(self):
self.stop_action_server()
def load_ftrack_url(self):
ftrack_url = (
get_system_settings()
["modules"]
["Ftrack"]
["ftrack_server"]
)
os.environ["FTRACK_SERVER"] = ftrack_url
# Definition of visibility of each menu actions
def set_menu_visibility(self):
self.tray_server_menu.menuAction().setVisible(self.bool_logged)
@ -348,18 +339,6 @@ class FtrackModule:
credentials.set_env()
self.validate()
def process_modules(self, modules):
if 'TimersManager' in modules:
self.timer_manager = modules['TimersManager']
self.timer_manager.add_module(self)
if "UserModule" in modules:
credentials.USER_GETTER = modules["UserModule"].get_user
modules["UserModule"].register_callback_on_user_change(
self.changed_user
)
def start_timer_manager(self, data):
if self.thread_timer is not None:
self.thread_timer.ftrack_start_timer(data)
@ -369,12 +348,10 @@ class FtrackModule:
self.thread_timer.ftrack_stop_timer()
def timer_started(self, data):
if hasattr(self, 'timer_manager'):
self.timer_manager.start_timers(data)
self.module.timer_started(data)
def timer_stopped(self):
if hasattr(self, 'timer_manager'):
self.timer_manager.stop_timers()
self.module.timer_stopped()
class FtrackEventsThread(QtCore.QThread):

View file

@ -3,7 +3,7 @@ import requests
from avalon import style
from pype.modules.ftrack.lib import credentials
from . import login_tools
from pype.api import resources
from pype import resources
from Qt import QtCore, QtGui, QtWidgets

View file

@ -3,7 +3,7 @@ from urllib import parse
import webbrowser
import functools
import threading
from pype.api import resources
from pype import resources
class LoginServerHandler(BaseHTTPRequestHandler):

View file

@ -1,5 +1,10 @@
from .idle_manager import IdleManager
from .idle_manager import (
IdleManager,
IIdleManager
)
def tray_init(tray_widget, main_widget):
return IdleManager()
__all__ = (
"IdleManager",
"IIdleManager"
)

View file

@ -1,65 +1,131 @@
import time
import collections
import threading
from abc import ABCMeta, abstractmethod
import six
from pynput import mouse, keyboard
from pype.api import Logger
from pype.lib import PypeLogger
from pype.modules import PypeModule, ITrayService
class IdleManager(threading.Thread):
@six.add_metaclass(ABCMeta)
class IIdleManager:
"""Other modules interface to return callbacks by idle time in seconds.
Expected output is dictionary with seconds <int> as keys and callback/s
as value, value may be callback of list of callbacks.
EXAMPLE:
```
{
60: self.on_minute_idle
}
```
"""
idle_manager = None
@abstractmethod
def callbacks_by_idle_time(self):
pass
@property
def idle_time(self):
if self.idle_manager:
return self.idle_manager.idle_time
class IdleManager(PypeModule, ITrayService):
""" 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
label = "Idle Service"
name = "Idle Manager"
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 initialize(self, module_settings):
idle_man_settings = module_settings[self.name]
self.enabled = idle_man_settings["enabled"]
def set_qaction(self, qaction, failed_icon):
self.qaction = qaction
self.failed_icon = failed_icon
self.time_callbacks = collections.defaultdict(list)
self.idle_thread = None
def tray_init(self):
return
def tray_start(self):
self.start()
self.start_thread()
def tray_exit(self):
self.stop()
self.stop_thread()
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.
def connect_with_modules(self, enabled_modules):
for module in enabled_modules:
if not isinstance(module, IIdleManager):
continue
Args:
emit_time(int): Time when callback will be triggered.
callback(func): Callback that will be triggered.
"""
self.time_callbacks[emit_time].append(callback)
module.idle_manager = self
callbacks_items = module.callbacks_by_idle_time() or {}
for emit_time, callbacks in callbacks_items.items():
if not isinstance(callbacks, (tuple, list, set)):
callbacks = [callbacks]
self.time_callbacks[emit_time].extend(callbacks)
@property
def is_running(self):
return self._is_running
def idle_time(self):
if self.idle_thread and self.idle_thread.is_running:
return self.idle_thread.idle_time
def _reset_time(self):
def start_thread(self):
if self.idle_thread:
self.idle_thread.stop()
self.idle_thread.join()
self.idle_thread = IdleManagerThread(self)
self.idle_thread.start()
def stop_thread(self):
if self.idle_thread:
self.idle_thread.stop()
self.idle_thread.join()
def on_thread_stop(self):
self.set_service_failed_icon()
class IdleManagerThread(threading.Thread):
def __init__(self, module, *args, **kwargs):
super(IdleManagerThread, self).__init__(*args, **kwargs)
self.log = PypeLogger().get_logger(self.__class__.__name__)
self.module = module
self.threads = []
self.is_running = False
self.idle_time = 0
def stop(self):
self._is_running = False
self.is_running = False
def reset_time(self):
self.idle_time = 0
@property
def time_callbacks(self):
return self.module.time_callbacks
def on_stop(self):
self.is_running = False
self.log.info("IdleManagerThread has stopped")
self.module.on_thread_stop()
def run(self):
self.log.info('IdleManager has started')
self._is_running = True
thread_mouse = MouseThread(self._reset_time)
self.log.info("IdleManagerThread has started")
self.is_running = True
thread_mouse = MouseThread(self.reset_time)
thread_mouse.start()
thread_keyboard = KeyboardThread(self._reset_time)
thread_keyboard = KeyboardThread(self.reset_time)
thread_keyboard.start()
try:
while self.is_running:
@ -82,9 +148,6 @@ class IdleManager(threading.Thread):
'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()
@ -98,8 +161,7 @@ class IdleManager(threading.Thread):
except AttributeError:
pass
self._is_running = False
self.log.info('IdleManager has stopped')
self.on_stop()
class MouseThread(mouse.Listener):

View file

@ -0,0 +1,6 @@
from .logging_module import LoggingModule
__all__ = (
"LoggingModule",
)

View file

@ -1,41 +1,46 @@
from pype.api import Logger
from .. import PypeModule, ITrayModule
class LoggingModule:
def __init__(self, main_parent=None, parent=None):
self.parent = parent
self.log = Logger().get_logger(self.__class__.__name__, "logging")
class LoggingModule(PypeModule, ITrayModule):
name = "Logging"
def initialize(self, modules_settings):
logging_settings = modules_settings[self.name]
self.enabled = logging_settings["enabled"]
# Tray attributes
self.window = None
self.tray_init(main_parent, parent)
def tray_init(self, main_parent, parent):
def tray_init(self):
try:
from .gui.app import LogsWindow
from .tray.app import LogsWindow
self.window = LogsWindow()
self.tray_menu = self._tray_menu
except Exception:
self.log.warning(
"Couldn't set Logging GUI due to error.", exc_info=True
)
# Definition of Tray menu
def _tray_menu(self, parent_menu):
def tray_menu(self, tray_menu):
from Qt import QtWidgets
# Menu for Tray App
menu = QtWidgets.QMenu('Logging', parent_menu)
menu = QtWidgets.QMenu('Logging', tray_menu)
show_action = QtWidgets.QAction("Show Logs", menu)
show_action.triggered.connect(self._show_logs_gui)
menu.addAction(show_action)
parent_menu.addMenu(menu)
tray_menu.addMenu(menu)
def tray_start(self):
pass
return
def process_modules(self, modules):
def tray_exit(self):
return
def connect_with_modules(self, _enabled_modules):
"""Nothing special."""
return
def _show_logs_gui(self):

View file

@ -1,5 +0,0 @@
from .logging_module import LoggingModule
def tray_init(tray_widget, main_widget):
return LoggingModule(main_widget, tray_widget)

View file

@ -1,5 +1,6 @@
from .muster import MusterModule
def tray_init(tray_widget, main_widget):
return MusterModule(main_widget, tray_widget)
__all__ = (
"MusterModule",
)

View file

@ -2,9 +2,10 @@ import os
import json
import appdirs
import requests
from .. import PypeModule, ITrayModule, IRestApi
class MusterModule:
class MusterModule(PypeModule, ITrayModule, IRestApi):
"""
Module handling Muster Render credentials. This will display dialog
asking for user credentials for Muster if not already specified.
@ -14,38 +15,42 @@ class MusterModule:
)
cred_filename = 'muster_cred.json'
def __init__(self, main_parent=None, parent=None):
name = "muster"
def initialize(self, modules_settings):
muster_settings = modules_settings[self.name]
self.enabled = muster_settings["enabled"]
self.muster_url = muster_settings["MUSTER_REST_URL"]
self.cred_path = os.path.join(
self.cred_folder_path, self.cred_filename
)
self.tray_init(main_parent, parent)
# Tray attributes
self.widget_login = None
self.action_show_login = None
def tray_init(self, main_parent, parent):
def get_global_environments(self):
return {
"MUSTER_REST_URL": self.muster_url
}
def tray_init(self):
from .widget_login import MusterLogin
self.main_parent = main_parent
self.parent = parent
self.widget_login = MusterLogin(main_parent, self)
self.widget_login = MusterLogin(self)
def tray_start(self):
"""
Show login dialog if credentials not found.
"""
"""Show login dialog if credentials not found."""
# This should be start of module in tray
cred = self.load_credentials()
if not cred:
self.show_login()
else:
# nothing to do
pass
def process_modules(self, modules):
if "RestApiServer" in modules:
def api_show_login():
self.aShowLogin.trigger()
modules["RestApiServer"].register_callback(
"/show_login", api_show_login, "muster", "post"
)
def tray_exit(self):
"""Nothing special for Muster."""
return
def connect_with_modules(self, *_a, **_kw):
return
# Definition of Tray menu
def tray_menu(self, parent):
@ -53,18 +58,26 @@ class MusterModule:
from Qt import QtWidgets
# Menu for Tray App
self.menu = QtWidgets.QMenu('Muster', parent)
self.menu.setProperty('submenu', 'on')
menu = QtWidgets.QMenu('Muster', parent)
menu.setProperty('submenu', 'on')
# Actions
self.aShowLogin = QtWidgets.QAction(
"Change login", self.menu
self.action_show_login = QtWidgets.QAction(
"Change login", menu
)
self.menu.addAction(self.aShowLogin)
self.aShowLogin.triggered.connect(self.show_login)
menu.addAction(self.action_show_login)
self.action_show_login.triggered.connect(self.show_login)
parent.addMenu(self.menu)
parent.addMenu(menu)
def rest_api_initialization(self, rest_api_module):
"""Implementation of IRestApi interface."""
def api_show_login():
self.action_show_login.trigger()
rest_api_module.register_callback(
"/show_login", api_show_login, "muster", "post"
)
def load_credentials(self):
"""
@ -84,8 +97,7 @@ class MusterModule:
"""
Authenticate user with Muster and get authToken from server.
"""
MUSTER_REST_URL = os.environ.get("MUSTER_REST_URL")
if not MUSTER_REST_URL:
if not self.muster_url:
raise AttributeError("Muster REST API url not set")
params = {
'username': username,
@ -93,7 +105,7 @@ class MusterModule:
}
api_entry = '/api/login'
response = self._requests_post(
MUSTER_REST_URL + api_entry, params=params)
self.muster_url + api_entry, params=params)
if response.status_code != 200:
self.log.error(
'Cannot log into Muster: {}'.format(response.status_code))
@ -123,7 +135,8 @@ class MusterModule:
"""
Show dialog to enter credentials
"""
self.widget_login.show()
if self.widget_login:
self.widget_login.show()
def _requests_post(self, *args, **kwargs):
""" Wrapper for requests, disabling SSL certificate validation if

View file

@ -1,7 +1,7 @@
import os
from Qt import QtCore, QtGui, QtWidgets
from avalon import style
from pype.api import resources
from pype import resources
class MusterLogin(QtWidgets.QWidget):
@ -11,21 +11,15 @@ class MusterLogin(QtWidgets.QWidget):
loginSignal = QtCore.Signal(object, object, object)
def __init__(self, main_parent=None, parent=None):
def __init__(self, module, parent=None):
super(MusterLogin, self).__init__()
super(MusterLogin, self).__init__(parent)
self.parent_widget = parent
self.main_parent = main_parent
self.module = module
# Icon
if hasattr(parent, 'icon'):
self.setWindowIcon(parent.icon)
elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'):
self.setWindowIcon(parent.parent.icon)
else:
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
self.setWindowFlags(
QtCore.Qt.WindowCloseButtonHint |
@ -153,7 +147,7 @@ class MusterLogin(QtWidgets.QWidget):
self._close_widget()
def save_credentials(self, username, password):
self.parent_widget.get_auth_token(username, password)
self.module.get_auth_token(username, password)
def closeEvent(self, event):
event.ignore()

View file

@ -1,9 +1,27 @@
from .rest_api import RestApiServer
from .base_class import RestApi, abort, route, register_statics
from .lib import RestMethods, CallbackResult
from .rest_api import (
RestApiModule,
IRestApi
)
from .base_class import (
RestApi,
abort,
route,
register_statics
)
from .lib import (
RestMethods,
CallbackResult
)
CLASS_DEFINIION = RestApiServer
__all__ = (
"RestApiModule",
"IRestApi",
"RestApi",
"abort",
"route",
"register_statics",
def tray_init(tray_widget, main_widget):
return RestApiServer()
"RestMethods",
"CallbackResult"
)

View file

@ -1,21 +1,32 @@
import os
import socket
import threading
from abc import ABCMeta, abstractmethod
from socketserver import ThreadingMixIn
from http.server import HTTPServer
import six
from pype.lib import PypeLogger
from pype import resources
from .lib import RestApiFactory, Handler
from .base_class import route, register_statics
from pype.api import Logger
log = Logger().get_logger("RestApiServer")
from .. import PypeModule, ITrayService
class ThreadingSimpleServer(ThreadingMixIn, HTTPServer):
pass
@six.add_metaclass(ABCMeta)
class IRestApi:
"""Other modules interface to return paths to ftrack event handlers.
Expected output is dictionary with "server" and "user" keys.
"""
@abstractmethod
def rest_api_initialization(self, rest_api_module):
pass
class RestApiServer:
class RestApiModule(PypeModule, ITrayService):
"""Rest Api allows to access statics or callbacks with http requests.
To register statics use `register_statics`.
@ -85,30 +96,18 @@ class RestApiServer:
Callback may return many types. For more information read docstring of
`_handle_callback_result` defined in handler.
"""
default_port = 8011
exclude_ports = []
label = "Rest API Service"
name = "Rest Api"
def __init__(self):
self.qaction = None
self.failed_icon = None
self._is_running = False
def initialize(self, modules_settings):
rest_api_settings = modules_settings[self.name]
self.enabled = True
self.default_port = rest_api_settings["default_port"]
self.exclude_ports = rest_api_settings["exclude_ports"]
port = self.find_port()
self.rest_api_thread = RestApiThread(self, port)
statics_dir = os.path.join(
os.environ["PYPE_MODULE_ROOT"],
"pype",
"resources"
)
self.register_statics("/res", statics_dir)
os.environ["PYPE_STATICS_SERVER"] = "{}/res".format(
os.environ["PYPE_REST_API_URL"]
)
def set_qaction(self, qaction, failed_icon):
self.qaction = qaction
self.failed_icon = failed_icon
self.rest_api_url = None
self.rest_api_thread = None
self.resources_url = None
def register_callback(
self, path, callback, url_prefix="", methods=[], strict_match=False
@ -123,6 +122,15 @@ class RestApiServer:
def register_obj(self, obj):
RestApiFactory.register_obj(obj)
def connect_with_modules(self, enabled_modules):
# Do not register restapi callbacks out of tray
if self.tray_initialized:
for module in enabled_modules:
if not isinstance(module, IRestApi):
continue
module.rest_api_initialization(self)
def find_port(self):
start_port = self.default_port
exclude_ports = self.exclude_ports
@ -139,15 +147,23 @@ class RestApiServer:
break
if found_port is None:
return None
os.environ["PYPE_REST_API_URL"] = "http://localhost:{}".format(
found_port
)
return found_port
def tray_init(self):
port = self.find_port()
self.rest_api_url = "http://localhost:{}".format(port)
self.rest_api_thread = RestApiThread(self, port)
self.register_statics("/res", resources.RESOURCES_DIR)
self.resources_url = "{}/res".format(self.rest_api_url)
# Set rest api environments
os.environ["PYPE_REST_API_URL"] = self.rest_api_url
os.environ["PYPE_STATICS_SERVER"] = self.resources_url
def tray_start(self):
RestApiFactory.prepare_registered()
if not RestApiFactory.has_handlers():
log.debug("There are not registered any handlers for RestApi")
self.log.debug("There are not registered any handlers for RestApi")
return
self.rest_api_thread.start()
@ -163,6 +179,10 @@ class RestApiServer:
self.rest_api_thread.join()
class ThreadingSimpleServer(ThreadingMixIn, HTTPServer):
pass
class RestApiThread(threading.Thread):
""" Listener for REST requests.
@ -176,6 +196,7 @@ class RestApiThread(threading.Thread):
self.module = module
self.port = port
self.httpd = None
self.log = PypeLogger().get_logger("RestApiThread")
def stop(self):
self.is_running = False
@ -186,7 +207,7 @@ class RestApiThread(threading.Thread):
self.is_running = True
try:
log.debug(
self.log.debug(
"Running Rest Api server on URL:"
" \"http://localhost:{}\"".format(self.port)
)
@ -197,7 +218,7 @@ class RestApiThread(threading.Thread):
httpd.handle_request()
except Exception:
log.warning(
self.log.warning(
"Rest Api Server service has failed", exc_info=True
)

View file

@ -1,5 +1,5 @@
from .standalonepublish_module import StandAlonePublishModule
def tray_init(tray_widget, main_widget):
return StandAlonePublishModule(main_widget, tray_widget)
__all__ = (
"StandAlonePublishModule",
)

View file

@ -2,36 +2,45 @@ import os
import sys
import subprocess
import pype
from .. import PypeModule, ITrayModule
class StandAlonePublishModule:
def __init__(self, main_parent=None, parent=None):
self.main_parent = main_parent
self.parent_widget = parent
class StandAlonePublishModule(PypeModule, ITrayModule):
menu_label = "Publish"
name = "Standalone Publish"
def initialize(self, modules_settings):
self.enabled = modules_settings[self.name]["enabled"]
self.publish_paths = [
os.path.join(
pype.PLUGINS_DIR, "standalonepublisher", "publish"
)
]
def tray_init(self):
return
def tray_start(self):
return
def tray_exit(self):
return
def tray_menu(self, parent_menu):
from Qt import QtWidgets
self.run_action = QtWidgets.QAction(
"Publish", parent_menu
)
self.run_action.triggered.connect(self.show)
parent_menu.addAction(self.run_action)
run_action = QtWidgets.QAction(self.menu_label, parent_menu)
run_action.triggered.connect(self.run_standalone_publisher)
parent_menu.addAction(run_action)
def process_modules(self, modules):
if "FtrackModule" in modules:
self.publish_paths.append(os.path.join(
pype.PLUGINS_DIR, "ftrack", "publish"
))
def connect_with_modules(self, enabled_modules):
"""Collect publish paths from other modules."""
publish_paths = self.manager.collect_plugin_paths()["publish"]
self.publish_paths.extend(publish_paths)
def show(self):
def run_standalone_publisher(self):
from pype import tools
standalone_publisher_tool_path = os.path.join(
os.path.dirname(tools.__file__),
os.path.dirname(os.path.abspath(tools.__file__)),
"standalonepublish"
)
subprocess.Popen([

View file

@ -1,7 +1,9 @@
from .timers_manager import TimersManager
from .timers_manager import (
ITimersManager,
TimersManager
)
CLASS_DEFINIION = TimersManager
def tray_init(tray_widget, main_widget):
return TimersManager(tray_widget, main_widget)
__all__ = (
"ITimersManager",
"TimersManager"
)

View file

@ -1,118 +1,137 @@
from pype.api import Logger
from abc import ABCMeta, abstractmethod
import six
from .. import PypeModule, ITrayService, IIdleManager
class TimersManager:
@six.add_metaclass(ABCMeta)
class ITimersManager:
timer_manager_module = None
@abstractmethod
def stop_timer(self):
pass
@abstractmethod
def start_timer(self, data):
pass
def timer_started(self, data):
if not self.timer_manager_module:
return
self.timer_manager_module.timer_started(self.id, data)
def timer_stopped(self):
if not self.timer_manager_module:
return
self.timer_manager_module.timer_stopped(self.id)
class TimersManager(PypeModule, ITrayService, IIdleManager):
""" 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).
"""
name = "Timers Manager"
label = "Timers Service"
# Presetable attributes
# - when timer will stop if idle manager is running (minutes)
full_time = 15
# - how many minutes before the timer is stopped will popup the message
message_time = 0.5
def initialize(self, modules_settings):
timers_settings = modules_settings[self.name]
def __init__(self, tray_widget, main_widget):
self.log = Logger().get_logger(self.__class__.__name__)
self.enabled = timers_settings["enabled"]
# When timer will stop if idle manager is running (minutes)
full_time = int(timers_settings["full_time"] * 60)
# How many minutes before the timer is stopped will popup the message
message_time = int(timers_settings["message_time"] * 60)
self.time_show_message = full_time - message_time
self.time_stop_timer = full_time
self.modules = []
self.is_running = False
self.last_task = None
self.tray_widget = tray_widget
self.main_widget = main_widget
self.idle_man = None
# Tray attributes
self.signal_handler = None
self.widget_user_idle = None
self.signal_handler = None
self.trat_init(tray_widget, main_widget)
self.modules = []
def trat_init(self, tray_widget, main_widget):
def tray_init(self):
from .widget_user_idle import WidgetUserIdle, SignalHandler
self.widget_user_idle = WidgetUserIdle(self, tray_widget)
self.widget_user_idle = WidgetUserIdle(self)
self.signal_handler = SignalHandler(self)
def set_signal_times(self):
try:
full_time = int(self.full_time * 60)
message_time = int(self.message_time * 60)
self.time_show_message = full_time - message_time
self.time_stop_timer = full_time
return True
except Exception:
self.log.error("Couldn't set timer signals.", exc_info=True)
def tray_start(self, *_a, **_kw):
return
def add_module(self, module):
""" Adds module to context
def tray_exit(self):
"""Nothing special for TimersManager."""
return
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
def timer_started(self, source_id, data):
for module in self.modules:
if module.id != source_id:
module.start_timer(data)
self.last_task = data
for module in self.modules:
module.start_timer_manager(data)
self.is_running = True
def timer_stopped(self, source_id):
for module in self.modules:
if module.id != source_id:
module.stop_timer()
def restart_timers(self):
if self.last_task is not None:
self.start_timers(self.last_task)
self.timer_started(None, 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.
for module in self.modules:
module.stop_timer()
:param modules: All imported modules from TrayManager
:type modules: dict
"""
def connect_with_modules(self, enabled_modules):
for module in enabled_modules:
if not isinstance(module, ITimersManager):
continue
module.timer_manager_module = self
self.modules.append(module)
if 'IdleManager' in modules:
if self.set_signal_times() is True:
self.register_to_idle_manager(modules['IdleManager'])
def callbacks_by_idle_time(self):
"""Implementation of IIdleManager interface."""
# Time when message is shown
callbacks = {
self.time_show_message: lambda: self.time_callback(0)
}
# 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:
callbacks[num] = lambda: self.time_callback(1)
# 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:
callbacks[num] = lambda: self.time_callback(1)
# Time when timers are stopped
callbacks[self.time_stop_timer] = lambda: self.time_callback(2)
return callbacks
def time_callback(self, int_def):
if not self.signal_handler:
@ -125,51 +144,23 @@ class TimersManager:
elif int_def == 2:
self.signal_handler.signal_stop_timers.emit()
def register_to_idle_manager(self, man_obj):
self.idle_man = man_obj
# Time when message is shown
self.idle_man.add_time_callback(
self.time_show_message,
lambda: self.time_callback(0)
)
# 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_callback(
num, lambda: self.time_callback(1)
)
# 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_callback(
num, lambda: self.time_callback(1)
)
# Time when timers are stopped
self.idle_man.add_time_callback(
self.time_stop_timer,
lambda: self.time_callback(2)
)
def change_label(self):
if self.is_running is False:
return
if not self.idle_man or self.widget_user_idle.bool_is_showed is False:
if (
not self.idle_manager
or self.widget_user_idle.bool_is_showed is False
):
return
if self.idle_man.idle_time > self.time_show_message:
value = self.time_stop_timer - self.idle_man.idle_time
if self.idle_manager.idle_time > self.time_show_message:
value = self.time_stop_timer - self.idle_manager.idle_time
else:
value = 1 + (
self.time_stop_timer -
self.time_show_message -
self.idle_man.idle_time
self.idle_manager.idle_time
)
self.widget_user_idle.change_count_widget(value)

View file

@ -1,5 +1,6 @@
from avalon import style
from Qt import QtCore, QtGui, QtWidgets
from pype import resources
class WidgetUserIdle(QtWidgets.QWidget):
@ -7,7 +8,7 @@ class WidgetUserIdle(QtWidgets.QWidget):
SIZE_W = 300
SIZE_H = 160
def __init__(self, module, tray_widget):
def __init__(self, module):
super(WidgetUserIdle, self).__init__()
@ -15,7 +16,9 @@ class WidgetUserIdle(QtWidgets.QWidget):
self.bool_not_stopped = True
self.module = module
self.setWindowIcon(tray_widget.icon)
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
self.setWindowFlags(
QtCore.Qt.WindowCloseButtonHint
| QtCore.Qt.WindowMinimizeButtonHint

View file

@ -1,5 +1,10 @@
from .user_module import UserModule
from .user_module import (
UserModule,
IUserModule
)
def tray_init(tray_widget, main_widget):
return UserModule(main_widget, tray_widget)
__all__ = (
"UserModule",
"IUserModule"
)

View file

@ -2,38 +2,55 @@ import os
import json
import getpass
from abc import ABCMeta, abstractmethod
import six
import appdirs
from pype.api import Logger
from .. import PypeModule, ITrayModule, IRestApi
class UserModule:
@six.add_metaclass(ABCMeta)
class IUserModule:
"""Interface for other modules to use user change callbacks."""
@abstractmethod
def on_pype_user_change(self, username):
"""What should happen on Pype user change."""
pass
class UserModule(PypeModule, ITrayModule, IRestApi):
cred_folder_path = os.path.normpath(
appdirs.user_data_dir('pype-app', 'pype')
)
cred_filename = 'user_info.json'
env_name = "PYPE_USERNAME"
log = Logger().get_logger("UserModule", "user")
name = "User setting"
def __init__(self, main_parent=None, parent=None):
self._callbacks_on_user_change = []
def initialize(self, modules_settings):
user_settings = modules_settings[self.name]
self.enabled = user_settings["enabled"]
self.callbacks_on_user_change = []
self.cred = {}
self.cred_path = os.path.normpath(os.path.join(
self.cred_folder_path, self.cred_filename
))
# Tray attributes
self.widget_login = None
self.action_show_widget = None
self.tray_init(main_parent, parent)
def tray_init(self, main_parent=None, parent=None):
def tray_init(self):
from .widget_user import UserWidget
self.widget_login = UserWidget(self)
self.load_credentials()
def register_callback_on_user_change(self, callback):
self._callbacks_on_user_change.append(callback)
self.callbacks_on_user_change.append(callback)
def tray_start(self):
"""Store credentials to env and preset them to widget"""
@ -44,29 +61,34 @@ class UserModule:
os.environ[self.env_name] = username
self.widget_login.set_user(username)
def tray_exit(self):
"""Nothing special for User."""
return
def get_user(self):
return self.cred.get("username") or getpass.getuser()
def process_modules(self, modules):
""" Gives ability to connect with imported modules from TrayManager.
def rest_api_initialization(self, rest_api_module):
def api_get_username():
return self.cred
:param modules: All imported modules from TrayManager
:type modules: dict
"""
rest_api_module.register_callback(
"user/username", api_get_username, "get"
)
if "RestApiServer" in modules:
def api_get_username():
return self.cred
def api_show_widget():
self.action_show_widget.trigger()
def api_show_widget():
self.action_show_widget.trigger()
rest_api_module.register_callback(
"user/show_widget", api_show_widget, "post"
)
modules["RestApiServer"].register_callback(
"user/username", api_get_username, "get"
)
modules["RestApiServer"].register_callback(
"user/show_widget", api_show_widget, "post"
)
def connect_with_modules(self, enabled_modules):
for module in enabled_modules:
if isinstance(module, IUserModule):
self.callbacks_on_user_change.append(
module.on_pype_user_change
)
# Definition of Tray menu
def tray_menu(self, parent_menu):
@ -108,12 +130,14 @@ class UserModule:
def change_credentials(self, username):
self.save_credentials(username)
for callback in self._callbacks_on_user_change:
for callback in self.callbacks_on_user_change:
try:
callback()
callback(username)
except Exception:
self.log.warning(
"Failed to execute callback \"{}\".".format(str(callback)),
"Failed to execute callback \"{}\".".format(
str(callback)
),
exc_info=True
)
@ -135,7 +159,9 @@ class UserModule:
self.log.debug("Username \"{}\" stored".format(username))
except Exception:
self.log.error(
"Could not store username to file \"{}\"".format(self.cred_path),
"Could not store username to file \"{}\"".format(
self.cred_path
),
exc_info=True
)

View file

@ -1,6 +1,6 @@
from Qt import QtCore, QtGui, QtWidgets
from avalon import style
from pype.api import resources
from pype import resources
class UserWidget(QtWidgets.QWidget):

View file

@ -1,5 +1,10 @@
from .websocket_server import WebSocketServer
from .websocket_server import (
WebsocketModule,
WebSocketServer
)
def tray_init(tray_widget, main_widget):
return WebSocketServer()
__all__ = (
"WebsocketModule",
"WebSocketServer"
)

View file

@ -1,17 +1,53 @@
from pype.api import Logger
import threading
from aiohttp import web
import asyncio
from wsrpc_aiohttp import STATIC_DIR, WebSocketAsync
import os
import sys
import pyclbr
import importlib
import urllib
import threading
log = Logger().get_logger("WebsocketServer")
import six
from pype.lib import PypeLogger
from .. import PypeModule, ITrayService
if six.PY2:
web = asyncio = STATIC_DIR = WebSocketAsync = None
else:
from aiohttp import web
import asyncio
from wsrpc_aiohttp import STATIC_DIR, WebSocketAsync
log = PypeLogger().get_logger("WebsocketServer")
class WebsocketModule(PypeModule, ITrayService):
name = "Websocket server"
label = "Websocket server"
def initialize(self, module_settings):
if asyncio is None:
raise AssertionError(
"WebSocketServer module requires Python 3.5 or higher."
)
self.enabled = True
self.websocket_server = None
def connect_with_modules(self, *_a, **kw):
return
def tray_init(self):
self.websocket_server = WebSocketServer()
self.websocket_server.on_stop_callbacks.append(
self.set_service_failed_icon
)
def tray_start(self):
if self.websocket_server:
self.websocket_server.module_start()
def tray_exit(self):
if self.websocket_server:
self.websocket_server.module_stop()
class WebSocketServer():
@ -24,12 +60,11 @@ class WebSocketServer():
_instance = None
def __init__(self):
self.qaction = None
self.failed_icon = None
self._is_running = False
WebSocketServer._instance = self
self.client = None
self.handlers = {}
self.on_stop_callbacks = []
port = None
websocket_url = os.getenv("WEBSOCKET_URL")
@ -51,6 +86,14 @@ class WebSocketServer():
self.websocket_thread = WebsocketServerThread(self, port)
def module_start(self):
if self.websocket_thread:
self.websocket_thread.start()
def module_stop(self):
if self.websocket_thread:
self.websocket_thread.stop()
def add_routes_for_directories(self, directories_with_routes):
""" Loops through selected directories to find all modules and
in them all classes implementing 'WebSocketRoute' that could be
@ -106,14 +149,7 @@ class WebSocketServer():
WebSocketServer()
return WebSocketServer._instance
def tray_start(self):
self.websocket_thread.start()
def tray_exit(self):
self.stop()
def stop_websocket_server(self):
self.stop()
@property
@ -134,7 +170,8 @@ class WebSocketServer():
)
def thread_stopped(self):
self._is_running = False
for callback in self.on_stop_callbacks:
callback()
class WebsocketServerThread(threading.Thread):
@ -145,7 +182,13 @@ class WebsocketServerThread(threading.Thread):
it creates separate thread and separate asyncio event loop
"""
def __init__(self, module, port):
if asyncio is None:
raise AssertionError(
"WebSocketServer module requires Python 3.5 or higher."
)
super(WebsocketServerThread, self).__init__()
self.is_running = False
self.port = port
self.module = module

View file

@ -1,102 +0,0 @@
import os
import inspect
import pype.modules
from pype.modules import PypeModule
from pype.settings import get_system_settings
from pype.api import Logger
class PypeModuleManager:
skip_module_names = ("__pycache__", )
def __init__(self):
self.log = Logger().get_logger(
"{}.{}".format(__name__, self.__class__.__name__)
)
self.pype_modules = self.find_pype_modules()
def modules_environments(self):
environments = {}
for pype_module in self.pype_modules.values():
environments.update(pype_module.startup_environments())
return environments
def find_pype_modules(self):
settings = get_system_settings()
modules = []
dirpath = os.path.dirname(pype.modules.__file__)
for module_name in os.listdir(dirpath):
# Check if path lead to a folder
full_path = os.path.join(dirpath, module_name)
if not os.path.isdir(full_path):
continue
# Skip known invalid names
if module_name in self.skip_module_names:
continue
import_name = "pype.modules.{}".format(module_name)
try:
modules.append(
__import__(import_name, fromlist=[""])
)
except Exception:
self.log.warning(
"Couldn't import {}".format(import_name), exc_info=True
)
pype_module_classes = []
for module in modules:
try:
pype_module_classes.extend(
self._classes_from_module(PypeModule, module)
)
except Exception:
self.log.warning(
"Couldn't import {}".format(import_name), exc_info=True
)
pype_modules = {}
for pype_module_class in pype_module_classes:
try:
pype_module = pype_module_class(settings)
if pype_module.enabled:
pype_modules[pype_module.id] = pype_module
except Exception:
self.log.warning(
"Couldn't create instance of {}".format(
pype_module_class.__class__.__name__
),
exc_info=True
)
return pype_modules
def _classes_from_module(self, superclass, module):
classes = list()
def recursive_bases(klass):
output = []
output.extend(klass.__bases__)
for base in klass.__bases__:
output.extend(recursive_bases(base))
return output
for name in dir(module):
# It could be anything at this point
obj = getattr(module, name)
if not inspect.isclass(obj) or not len(obj.__bases__) > 0:
continue
# Use string comparison rather than `issubclass`
# in order to support reloading of this module.
bases = recursive_bases(obj)
if not any(base.__name__ == superclass.__name__ for base in bases):
continue
classes.append(obj)
return classes

View file

@ -20,8 +20,7 @@
"PYPE_PROJECT_CONFIGS",
"PYPE_PYTHON_EXE",
"PYPE_OCIO_CONFIG",
"PYBLISH_GUI",
"PYBLISHPLUGINPATH"
"PYBLISH_GUI"
]
},
"FFMPEG_PATH": {
@ -30,7 +29,6 @@
"linux": "{VIRTUAL_ENV}/localized/ffmpeg_exec/linux:{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/linux"
},
"PATH": [
"{PYPE_CONFIG}/launchers",
"{FFMPEG_PATH}",
"{PATH}"
],
@ -46,9 +44,6 @@
"darwin": "{VIRTUAL_ENV}/bin/python"
},
"PYPE_OCIO_CONFIG": "{STUDIO_SOFT}/OpenColorIO-Configs",
"PYBLISH_GUI": "pyblish_pype",
"PYBLISHPLUGINPATH": [
"{PYPE_MODULE_ROOT}/pype/plugins/ftrack/publish"
]
"PYBLISH_GUI": "pyblish_pype"
}
}

View file

@ -1,42 +1,13 @@
{
"Avalon": {
"AVALON_MONGO": "mongodb://localhost:2707",
"AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data",
"AVALON_THUMBNAIL_ROOT": "{PYPE_SETUP_PATH}/../avalon_thumails",
"environment": {
"__environment_keys__": {
"avalon": [
"AVALON_CONFIG",
"AVALON_PROJECTS",
"AVALON_USERNAME",
"AVALON_PASSWORD",
"AVALON_DEBUG",
"AVALON_MONGO",
"AVALON_DB",
"AVALON_DB_DATA",
"AVALON_EARLY_ADOPTER",
"AVALON_SCHEMA",
"AVALON_LOCATION",
"AVALON_LABEL",
"AVALON_TIMEOUT",
"AVALON_THUMBNAIL_ROOT"
]
},
"AVALON_CONFIG": "pype",
"AVALON_PROJECTS": "{PYPE_PROJECTS_PATH}",
"AVALON_USERNAME": "avalon",
"AVALON_PASSWORD": "secret",
"AVALON_DEBUG": "1",
"AVALON_MONGO": "mongodb://localhost:2707",
"AVALON_DB": "avalon",
"AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data",
"AVALON_EARLY_ADOPTER": "1",
"AVALON_SCHEMA": "{PYPE_MODULE_ROOT}/schema",
"AVALON_LOCATION": "http://127.0.0.1",
"AVALON_LABEL": "Pype",
"AVALON_TIMEOUT": "1000",
"AVALON_THUMBNAIL_ROOT": "{PYPE_SETUP_PATH}/../avalon_thumails"
}
"AVALON_MONGO": "",
"AVALON_TIMEOUT": 1000,
"AVALON_THUMBNAIL_ROOT": {
"windows": "",
"darwin": "",
"linux": ""
},
"AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data"
},
"Ftrack": {
"enabled": true,

View file

@ -12,23 +12,26 @@
"children": [{
"type": "text",
"key": "AVALON_MONGO",
"label": "Avalon Mongo URL"
"label": "Avalon Mongo URL",
"placeholder": "Pype Mongo is used if not filled."
},
{
"type": "number",
"key": "AVALON_TIMEOUT",
"minimum": 0,
"label": "Avalon Mongo Timeout (ms)"
},
{
"type": "path-widget",
"label": "Thumbnail Storage Location",
"key": "AVALON_THUMBNAIL_ROOT",
"multiplatform": true,
"multipath": false
},
{
"type": "text",
"key": "AVALON_DB_DATA",
"label": "Avalon Mongo Data Location"
},
{
"type": "text",
"key": "AVALON_THUMBNAIL_ROOT",
"label": "Thumbnail Storage Location"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json",
"env_group_key": "avalon"
}
]
}, {

View file

@ -1,68 +0,0 @@
[
{
"title": "User settings",
"type": "module",
"import_path": "pype.modules.user",
"fromlist": ["pype", "modules"]
}, {
"title": "Ftrack",
"type": "module",
"import_path": "pype.modules.ftrack.tray",
"fromlist": ["pype", "modules", "ftrack"]
}, {
"title": "Muster",
"type": "module",
"import_path": "pype.modules.muster",
"fromlist": ["pype", "modules"]
}, {
"title": "Avalon",
"type": "module",
"import_path": "pype.modules.avalon_apps",
"fromlist": ["pype", "modules"]
}, {
"title": "Clockify",
"type": "module",
"import_path": "pype.modules.clockify",
"fromlist": ["pype", "modules"]
}, {
"title": "Standalone Publish",
"type": "module",
"import_path": "pype.modules.standalonepublish",
"fromlist": ["pype", "modules"]
}, {
"title": "Logging",
"type": "module",
"import_path": "pype.modules.logging.tray",
"fromlist": ["pype", "modules", "logging"]
}, {
"title": "Idle Manager",
"type": "module",
"import_path": "pype.modules.idle_manager",
"fromlist": ["pype","modules"]
}, {
"title": "Timers Manager",
"type": "module",
"import_path": "pype.modules.timers_manager",
"fromlist": ["pype","modules"]
}, {
"title": "Rest Api",
"type": "module",
"import_path": "pype.modules.rest_api",
"fromlist": ["pype","modules"]
}, {
"title": "Adobe Communicator",
"type": "module",
"import_path": "pype.modules.adobe_communicator",
"fromlist": ["pype", "modules"]
}, {
"title": "Websocket Server",
"type": "module",
"import_path": "pype.modules.websocket_server",
"fromlist": ["pype", "modules"]
}, {
"title": "Sync Server",
"type": "module",
"import_path": "pype.modules.sync_server",
"fromlist": ["pype","modules"]
}
]

View file

@ -5,7 +5,8 @@ import platform
from avalon import style
from Qt import QtCore, QtGui, QtWidgets, QtSvg
from pype.api import Logger, resources
from pype.settings.lib import get_system_settings, load_json_file
from pype.modules import TrayModulesManager, ITrayService
from pype.settings.lib import get_system_settings
import pype.version
try:
import configparser
@ -26,89 +27,35 @@ class TrayManager:
self.log = Logger().get_logger(self.__class__.__name__)
self.modules = {}
self.services = {}
self.services_submenu = None
self.module_settings = get_system_settings()["modules"]
self.modules_manager = TrayModulesManager()
self.errors = []
CURRENT_DIR = os.path.dirname(__file__)
self.modules_imports = load_json_file(
os.path.join(CURRENT_DIR, "modules_imports.json")
)
module_settings = get_system_settings()["modules"]
self.module_settings = module_settings
def initialize_modules(self):
"""Add modules to tray."""
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")
module_data = self.module_settings.get(title)
if not module_data:
if not title:
title = import_path
self.log.warning("{} - Module data not found".format(title))
continue
enabled = module_data.pop("enabled", True)
if not enabled:
self.log.debug("{} - Module is disabled".format(title))
continue
item["attributes"] = module_data
items.append(item)
if items:
self.process_items(items, self.tray_widget.menu)
self.modules_manager.initialize(self.tray_widget.menu)
# Add services if they are
if self.services_submenu is not None:
self.tray_widget.menu.addMenu(self.services_submenu)
services_submenu = ITrayService.services_submenu(self.tray_widget.menu)
self.tray_widget.menu.addMenu(services_submenu)
# Add separator
if items and self.services_submenu is not None:
self.add_separator(self.tray_widget.menu)
self.tray_widget.menu.addSeparator()
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)
exit_action = QtWidgets.QAction("Exit", self.tray_widget)
exit_action.triggered.connect(self.tray_widget.exit)
self.tray_widget.menu.addAction(exit_action)
# Tell each module which modules were imported
self.connect_modules()
self.start_modules()
self.modules_manager.start_modules()
def _add_version_item(self):
subversion = os.environ.get("PYPE_SUBVERSION")
client_name = os.environ.get("PYPE_CLIENT")
@ -121,246 +68,10 @@ class TrayManager:
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.debug((
"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()
self.tray_widget.menu.addSeparator()
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__
))
self.modules_manager.on_exit()
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
@ -384,7 +95,7 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
# Set modules
self.tray_man = TrayManager(self, self.parent)
self.tray_man.process_presets()
self.tray_man.initialize_modules()
# Catch activate event
self.activated.connect(self.on_systray_activated)