mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge pull request #804 from pypeclub/feature/modules_manager
Pype modules manager
This commit is contained in:
commit
8226a73802
49 changed files with 1517 additions and 1079 deletions
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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 |
|
||||
|
|
|
|||
6
pype/modules/deadline/__init__.py
Normal file
6
pype/modules/deadline/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from .deadline_module import DeadlineModule
|
||||
|
||||
|
||||
__all__ = (
|
||||
"DeadlineModule",
|
||||
)
|
||||
20
pype/modules/deadline/deadline_module.py
Normal file
20
pype/modules/deadline/deadline_module.py
Normal 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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
102
pype/modules/ftrack/ftrack_module.py
Normal file
102
pype/modules/ftrack/ftrack_module.py
Normal 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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
6
pype/modules/logging/__init__.py
Normal file
6
pype/modules/logging/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from .logging_module import LoggingModule
|
||||
|
||||
|
||||
__all__ = (
|
||||
"LoggingModule",
|
||||
)
|
||||
|
|
@ -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):
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
from .logging_module import LoggingModule
|
||||
|
||||
|
||||
def tray_init(tray_widget, main_widget):
|
||||
return LoggingModule(main_widget, tray_widget)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from .muster import MusterModule
|
||||
|
||||
|
||||
def tray_init(tray_widget, main_widget):
|
||||
return MusterModule(main_widget, tray_widget)
|
||||
__all__ = (
|
||||
"MusterModule",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from .standalonepublish_module import StandAlonePublishModule
|
||||
|
||||
|
||||
def tray_init(tray_widget, main_widget):
|
||||
return StandAlonePublishModule(main_widget, tray_widget)
|
||||
__all__ = (
|
||||
"StandAlonePublishModule",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}, {
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue