mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
moved ftrack module one hierarchy level higher
This commit is contained in:
parent
ad6ab7934e
commit
4306a618a0
325 changed files with 41913 additions and 8 deletions
6
openpype/modules/ftrack/tray/__init__.py
Normal file
6
openpype/modules/ftrack/tray/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from .ftrack_tray import FtrackTrayWrapper
|
||||
|
||||
|
||||
__all__ = (
|
||||
"FtrackTrayWrapper",
|
||||
)
|
||||
470
openpype/modules/ftrack/tray/ftrack_tray.py
Normal file
470
openpype/modules/ftrack/tray/ftrack_tray.py
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
import os
|
||||
import time
|
||||
import datetime
|
||||
import threading
|
||||
from Qt import QtCore, QtWidgets, QtGui
|
||||
|
||||
import ftrack_api
|
||||
from ..ftrack_server.lib import check_ftrack_url
|
||||
from ..ftrack_server import socket_thread
|
||||
from ..lib import credentials
|
||||
from ..ftrack_module import FTRACK_MODULE_DIR
|
||||
from . import login_dialog
|
||||
|
||||
from openpype.api import Logger, resources
|
||||
|
||||
|
||||
log = Logger().get_logger("FtrackModule")
|
||||
|
||||
|
||||
class FtrackTrayWrapper:
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
self.thread_action_server = None
|
||||
self.thread_socket_server = None
|
||||
self.thread_timer = None
|
||||
|
||||
self.bool_logged = False
|
||||
self.bool_action_server_running = False
|
||||
self.bool_action_thread_running = False
|
||||
self.bool_timer_event = False
|
||||
|
||||
self.widget_login = login_dialog.CredentialsDialog(module)
|
||||
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")
|
||||
)
|
||||
self.icon_not_logged = QtGui.QIcon(
|
||||
resources.get_resource("icons", "circle_orange.png")
|
||||
)
|
||||
|
||||
def show_login_widget(self):
|
||||
self.widget_login.show()
|
||||
self.widget_login.activateWindow()
|
||||
self.widget_login.raise_()
|
||||
|
||||
def validate(self):
|
||||
validation = False
|
||||
cred = credentials.get_credentials()
|
||||
ft_user = cred.get("username")
|
||||
ft_api_key = cred.get("api_key")
|
||||
validation = credentials.check_credentials(ft_user, ft_api_key)
|
||||
if validation:
|
||||
self.widget_login.set_credentials(ft_user, ft_api_key)
|
||||
self.module.set_credentials_to_env(ft_user, ft_api_key)
|
||||
log.info("Connected to Ftrack successfully")
|
||||
self.on_login_change()
|
||||
|
||||
return validation
|
||||
|
||||
if not validation and ft_user and ft_api_key:
|
||||
log.warning(
|
||||
"Current Ftrack credentials are not valid. {}: {} - {}".format(
|
||||
str(os.environ.get("FTRACK_SERVER")), ft_user, ft_api_key
|
||||
)
|
||||
)
|
||||
|
||||
log.info("Please sign in to Ftrack")
|
||||
self.bool_logged = False
|
||||
self.show_login_widget()
|
||||
self.set_menu_visibility()
|
||||
|
||||
return validation
|
||||
|
||||
# Necessary - login_dialog works with this method after logging in
|
||||
def on_login_change(self):
|
||||
self.bool_logged = True
|
||||
|
||||
if self.action_credentials:
|
||||
self.action_credentials.setIcon(self.icon_logged)
|
||||
self.action_credentials.setToolTip(
|
||||
"Logged as user \"{}\"".format(
|
||||
self.widget_login.user_input.text()
|
||||
)
|
||||
)
|
||||
|
||||
self.set_menu_visibility()
|
||||
self.start_action_server()
|
||||
|
||||
def on_logout(self):
|
||||
credentials.clear_credentials()
|
||||
self.stop_action_server()
|
||||
|
||||
if self.action_credentials:
|
||||
self.action_credentials.setIcon(self.icon_not_logged)
|
||||
self.action_credentials.setToolTip("Logged out")
|
||||
|
||||
log.info("Logged out of Ftrack")
|
||||
self.bool_logged = False
|
||||
self.set_menu_visibility()
|
||||
|
||||
# Actions part
|
||||
def start_action_server(self):
|
||||
if self.thread_action_server is None:
|
||||
self.thread_action_server = threading.Thread(
|
||||
target=self.set_action_server
|
||||
)
|
||||
self.thread_action_server.start()
|
||||
|
||||
def set_action_server(self):
|
||||
if self.bool_action_server_running:
|
||||
return
|
||||
|
||||
self.bool_action_server_running = True
|
||||
self.bool_action_thread_running = False
|
||||
|
||||
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__))
|
||||
)
|
||||
|
||||
min_fail_seconds = 5
|
||||
max_fail_count = 3
|
||||
wait_time_after_max_fail = 10
|
||||
|
||||
# Threads data
|
||||
thread_name = "ActionServerThread"
|
||||
thread_port = 10021
|
||||
subprocess_path = (
|
||||
"{}/scripts/sub_user_server.py".format(FTRACK_MODULE_DIR)
|
||||
)
|
||||
if self.thread_socket_server is not None:
|
||||
self.thread_socket_server.stop()
|
||||
self.thread_socket_server.join()
|
||||
self.thread_socket_server = None
|
||||
|
||||
last_failed = datetime.datetime.now()
|
||||
failed_count = 0
|
||||
|
||||
ftrack_accessible = False
|
||||
printed_ftrack_error = False
|
||||
|
||||
# Main loop
|
||||
while True:
|
||||
if not self.bool_action_server_running:
|
||||
log.debug("Action server was pushed to stop.")
|
||||
break
|
||||
|
||||
# Check if accessible Ftrack and Mongo url
|
||||
if not ftrack_accessible:
|
||||
ftrack_accessible = check_ftrack_url(ftrack_url)
|
||||
|
||||
# Run threads only if Ftrack is accessible
|
||||
if not ftrack_accessible:
|
||||
if not printed_ftrack_error:
|
||||
log.warning("Can't access Ftrack {}".format(ftrack_url))
|
||||
|
||||
if self.thread_socket_server is not None:
|
||||
self.thread_socket_server.stop()
|
||||
self.thread_socket_server.join()
|
||||
self.thread_socket_server = None
|
||||
self.bool_action_thread_running = False
|
||||
self.set_menu_visibility()
|
||||
|
||||
printed_ftrack_error = True
|
||||
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
printed_ftrack_error = False
|
||||
|
||||
# Run backup thread which does not requeire mongo to work
|
||||
if self.thread_socket_server is None:
|
||||
if failed_count < max_fail_count:
|
||||
self.thread_socket_server = socket_thread.SocketThread(
|
||||
thread_name, thread_port, subprocess_path
|
||||
)
|
||||
self.thread_socket_server.start()
|
||||
self.bool_action_thread_running = True
|
||||
self.set_menu_visibility()
|
||||
|
||||
elif failed_count == max_fail_count:
|
||||
log.warning((
|
||||
"Action server failed {} times."
|
||||
" I'll try to run again {}s later"
|
||||
).format(
|
||||
str(max_fail_count), str(wait_time_after_max_fail))
|
||||
)
|
||||
failed_count += 1
|
||||
|
||||
elif ((
|
||||
datetime.datetime.now() - last_failed
|
||||
).seconds > wait_time_after_max_fail):
|
||||
failed_count = 0
|
||||
|
||||
# If thread failed test Ftrack and Mongo connection
|
||||
elif not self.thread_socket_server.isAlive():
|
||||
self.thread_socket_server.join()
|
||||
self.thread_socket_server = None
|
||||
ftrack_accessible = False
|
||||
|
||||
self.bool_action_thread_running = False
|
||||
self.set_menu_visibility()
|
||||
|
||||
_last_failed = datetime.datetime.now()
|
||||
delta_time = (_last_failed - last_failed).seconds
|
||||
if delta_time < min_fail_seconds:
|
||||
failed_count += 1
|
||||
else:
|
||||
failed_count = 0
|
||||
last_failed = _last_failed
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
self.bool_action_thread_running = False
|
||||
self.bool_action_server_running = False
|
||||
self.set_menu_visibility()
|
||||
|
||||
def reset_action_server(self):
|
||||
self.stop_action_server()
|
||||
self.start_action_server()
|
||||
|
||||
def stop_action_server(self):
|
||||
try:
|
||||
self.bool_action_server_running = False
|
||||
if self.thread_socket_server is not None:
|
||||
self.thread_socket_server.stop()
|
||||
self.thread_socket_server.join()
|
||||
self.thread_socket_server = None
|
||||
|
||||
if self.thread_action_server is not None:
|
||||
self.thread_action_server.join()
|
||||
self.thread_action_server = None
|
||||
|
||||
log.info("Ftrack action server was forced to stop")
|
||||
|
||||
except Exception:
|
||||
log.warning(
|
||||
"Error has happened during Killing action server",
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
# Definition of Tray menu
|
||||
def tray_menu(self, parent_menu):
|
||||
# Menu for Tray App
|
||||
tray_menu = QtWidgets.QMenu("Ftrack", parent_menu)
|
||||
|
||||
# Actions - basic
|
||||
action_credentials = QtWidgets.QAction("Credentials", tray_menu)
|
||||
action_credentials.triggered.connect(self.show_login_widget)
|
||||
if self.bool_logged:
|
||||
icon = self.icon_logged
|
||||
else:
|
||||
icon = self.icon_not_logged
|
||||
action_credentials.setIcon(icon)
|
||||
tray_menu.addAction(action_credentials)
|
||||
self.action_credentials = action_credentials
|
||||
|
||||
# Actions - server
|
||||
tray_server_menu = tray_menu.addMenu("Action server")
|
||||
|
||||
self.action_server_run = QtWidgets.QAction(
|
||||
"Run action server", tray_server_menu
|
||||
)
|
||||
self.action_server_reset = QtWidgets.QAction(
|
||||
"Reset action server", tray_server_menu
|
||||
)
|
||||
self.action_server_stop = QtWidgets.QAction(
|
||||
"Stop action server", tray_server_menu
|
||||
)
|
||||
|
||||
self.action_server_run.triggered.connect(self.start_action_server)
|
||||
self.action_server_reset.triggered.connect(self.reset_action_server)
|
||||
self.action_server_stop.triggered.connect(self.stop_action_server)
|
||||
|
||||
tray_server_menu.addAction(self.action_server_run)
|
||||
tray_server_menu.addAction(self.action_server_reset)
|
||||
tray_server_menu.addAction(self.action_server_stop)
|
||||
|
||||
self.tray_server_menu = tray_server_menu
|
||||
self.bool_logged = False
|
||||
self.set_menu_visibility()
|
||||
|
||||
parent_menu.addMenu(tray_menu)
|
||||
|
||||
def tray_exit(self):
|
||||
self.stop_action_server()
|
||||
self.stop_timer_thread()
|
||||
|
||||
# Definition of visibility of each menu actions
|
||||
def set_menu_visibility(self):
|
||||
self.tray_server_menu.menuAction().setVisible(self.bool_logged)
|
||||
if self.bool_logged is False:
|
||||
if self.bool_timer_event is True:
|
||||
self.stop_timer_thread()
|
||||
return
|
||||
|
||||
self.action_server_run.setVisible(not self.bool_action_server_running)
|
||||
self.action_server_reset.setVisible(self.bool_action_thread_running)
|
||||
self.action_server_stop.setVisible(self.bool_action_server_running)
|
||||
|
||||
if self.bool_timer_event is False:
|
||||
self.start_timer_thread()
|
||||
|
||||
def start_timer_thread(self):
|
||||
try:
|
||||
if self.thread_timer is None:
|
||||
self.thread_timer = FtrackEventsThread(self)
|
||||
self.bool_timer_event = True
|
||||
self.thread_timer.signal_timer_started.connect(
|
||||
self.timer_started
|
||||
)
|
||||
self.thread_timer.signal_timer_stopped.connect(
|
||||
self.timer_stopped
|
||||
)
|
||||
self.thread_timer.start()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def stop_timer_thread(self):
|
||||
try:
|
||||
if self.thread_timer is not None:
|
||||
self.thread_timer.terminate()
|
||||
self.thread_timer.wait()
|
||||
self.thread_timer = None
|
||||
|
||||
except Exception as e:
|
||||
log.error("During Killing Timer event server: {0}".format(e))
|
||||
|
||||
def changed_user(self):
|
||||
self.stop_action_server()
|
||||
self.module.set_credentials_to_env(None, None)
|
||||
self.validate()
|
||||
|
||||
def start_timer_manager(self, data):
|
||||
if self.thread_timer is not None:
|
||||
self.thread_timer.ftrack_start_timer(data)
|
||||
|
||||
def stop_timer_manager(self):
|
||||
if self.thread_timer is not None:
|
||||
self.thread_timer.ftrack_stop_timer()
|
||||
|
||||
def timer_started(self, data):
|
||||
self.module.timer_started(data)
|
||||
|
||||
def timer_stopped(self):
|
||||
self.module.timer_stopped()
|
||||
|
||||
|
||||
class FtrackEventsThread(QtCore.QThread):
|
||||
# Senders
|
||||
signal_timer_started = QtCore.Signal(object)
|
||||
signal_timer_stopped = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent):
|
||||
super(FtrackEventsThread, self).__init__()
|
||||
cred = credentials.get_credentials()
|
||||
self.username = cred['username']
|
||||
self.user = None
|
||||
self.last_task = None
|
||||
|
||||
def run(self):
|
||||
self.timer_session = ftrack_api.Session(auto_connect_event_hub=True)
|
||||
self.timer_session.event_hub.subscribe(
|
||||
'topic=ftrack.update and source.user.username={}'.format(
|
||||
self.username
|
||||
),
|
||||
self.event_handler)
|
||||
|
||||
user_query = 'User where username is "{}"'.format(self.username)
|
||||
self.user = self.timer_session.query(user_query).one()
|
||||
|
||||
timer_query = 'Timer where user.username is "{}"'.format(self.username)
|
||||
timer = self.timer_session.query(timer_query).first()
|
||||
if timer is not None:
|
||||
self.last_task = timer['context']
|
||||
self.signal_timer_started.emit(
|
||||
self.get_data_from_task(self.last_task)
|
||||
)
|
||||
|
||||
self.timer_session.event_hub.wait()
|
||||
|
||||
def get_data_from_task(self, task_entity):
|
||||
data = {}
|
||||
data['task_name'] = task_entity['name']
|
||||
data['task_type'] = task_entity['type']['name']
|
||||
data['project_name'] = task_entity['project']['full_name']
|
||||
data['hierarchy'] = self.get_parents(task_entity['parent'])
|
||||
|
||||
return data
|
||||
|
||||
def get_parents(self, entity):
|
||||
output = []
|
||||
if entity.entity_type.lower() == 'project':
|
||||
return output
|
||||
output.extend(self.get_parents(entity['parent']))
|
||||
output.append(entity['name'])
|
||||
|
||||
return output
|
||||
|
||||
def event_handler(self, event):
|
||||
try:
|
||||
if event['data']['entities'][0]['objectTypeId'] != 'timer':
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
new = event['data']['entities'][0]['changes']['start']['new']
|
||||
old = event['data']['entities'][0]['changes']['start']['old']
|
||||
|
||||
if old is None and new is None:
|
||||
return
|
||||
|
||||
timer_query = 'Timer where user.username is "{}"'.format(self.username)
|
||||
timer = self.timer_session.query(timer_query).first()
|
||||
if timer is not None:
|
||||
self.last_task = timer['context']
|
||||
|
||||
if old is None:
|
||||
self.signal_timer_started.emit(
|
||||
self.get_data_from_task(self.last_task)
|
||||
)
|
||||
elif new is None:
|
||||
self.signal_timer_stopped.emit()
|
||||
|
||||
def ftrack_stop_timer(self):
|
||||
actual_timer = self.timer_session.query(
|
||||
'Timer where user_id = "{0}"'.format(self.user['id'])
|
||||
).first()
|
||||
|
||||
if actual_timer is not None:
|
||||
self.user.stop_timer()
|
||||
self.timer_session.commit()
|
||||
self.signal_timer_stopped.emit()
|
||||
|
||||
def ftrack_start_timer(self, input_data):
|
||||
if self.user is None:
|
||||
return
|
||||
|
||||
actual_timer = self.timer_session.query(
|
||||
'Timer where user_id = "{0}"'.format(self.user['id'])
|
||||
).first()
|
||||
if (
|
||||
actual_timer is not None and
|
||||
input_data['task_name'] == self.last_task['name'] and
|
||||
input_data['hierarchy'][-1] == self.last_task['parent']['name']
|
||||
):
|
||||
return
|
||||
|
||||
input_data['entity_name'] = input_data['hierarchy'][-1]
|
||||
|
||||
task_query = (
|
||||
'Task where name is "{task_name}"'
|
||||
' and parent.name is "{entity_name}"'
|
||||
' and project.full_name is "{project_name}"'
|
||||
).format(**input_data)
|
||||
|
||||
task = self.timer_session.query(task_query).one()
|
||||
self.last_task = task
|
||||
self.user.start_timer(task)
|
||||
self.timer_session.commit()
|
||||
self.signal_timer_started.emit(
|
||||
self.get_data_from_task(self.last_task)
|
||||
)
|
||||
344
openpype/modules/ftrack/tray/login_dialog.py
Normal file
344
openpype/modules/ftrack/tray/login_dialog.py
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
import os
|
||||
import requests
|
||||
from openpype import style
|
||||
from openpype_modules.ftrack.lib import credentials
|
||||
from . import login_tools
|
||||
from openpype import resources
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class CredentialsDialog(QtWidgets.QDialog):
|
||||
SIZE_W = 300
|
||||
SIZE_H = 230
|
||||
|
||||
login_changed = QtCore.Signal()
|
||||
logout_signal = QtCore.Signal()
|
||||
|
||||
def __init__(self, module, parent=None):
|
||||
super(CredentialsDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("OpenPype - Ftrack Login")
|
||||
|
||||
self._module = module
|
||||
|
||||
self._login_server_thread = None
|
||||
self._is_logged = False
|
||||
self._in_advance_mode = False
|
||||
|
||||
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowCloseButtonHint |
|
||||
QtCore.Qt.WindowMinimizeButtonHint
|
||||
)
|
||||
|
||||
self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H))
|
||||
self.setMaximumSize(QtCore.QSize(self.SIZE_W + 100, self.SIZE_H + 100))
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
self.login_changed.connect(self._on_login)
|
||||
|
||||
self.ui_init()
|
||||
|
||||
def ui_init(self):
|
||||
self.ftsite_label = QtWidgets.QLabel("Ftrack URL:")
|
||||
self.user_label = QtWidgets.QLabel("Username:")
|
||||
self.api_label = QtWidgets.QLabel("API Key:")
|
||||
|
||||
self.ftsite_input = QtWidgets.QLabel()
|
||||
self.ftsite_input.setTextInteractionFlags(
|
||||
QtCore.Qt.TextBrowserInteraction
|
||||
)
|
||||
# self.ftsite_input.setReadOnly(True)
|
||||
self.ftsite_input.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
|
||||
|
||||
self.user_input = QtWidgets.QLineEdit()
|
||||
self.user_input.setPlaceholderText("user.name")
|
||||
self.user_input.textChanged.connect(self._user_changed)
|
||||
|
||||
self.api_input = QtWidgets.QLineEdit()
|
||||
self.api_input.setPlaceholderText(
|
||||
"e.g. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
)
|
||||
self.api_input.textChanged.connect(self._api_changed)
|
||||
|
||||
input_layout = QtWidgets.QFormLayout()
|
||||
input_layout.setContentsMargins(10, 15, 10, 5)
|
||||
|
||||
input_layout.addRow(self.ftsite_label, self.ftsite_input)
|
||||
input_layout.addRow(self.user_label, self.user_input)
|
||||
input_layout.addRow(self.api_label, self.api_input)
|
||||
|
||||
self.btn_advanced = QtWidgets.QPushButton("Advanced")
|
||||
self.btn_advanced.clicked.connect(self._on_advanced_clicked)
|
||||
|
||||
self.btn_simple = QtWidgets.QPushButton("Simple")
|
||||
self.btn_simple.clicked.connect(self._on_simple_clicked)
|
||||
|
||||
self.btn_login = QtWidgets.QPushButton("Login")
|
||||
self.btn_login.setToolTip(
|
||||
"Set Username and API Key with entered values"
|
||||
)
|
||||
self.btn_login.clicked.connect(self._on_login_clicked)
|
||||
|
||||
self.btn_ftrack_login = QtWidgets.QPushButton("Ftrack login")
|
||||
self.btn_ftrack_login.setToolTip("Open browser for Login to Ftrack")
|
||||
self.btn_ftrack_login.clicked.connect(self._on_ftrack_login_clicked)
|
||||
|
||||
self.btn_logout = QtWidgets.QPushButton("Logout")
|
||||
self.btn_logout.clicked.connect(self._on_logout_clicked)
|
||||
|
||||
self.btn_close = QtWidgets.QPushButton("Close")
|
||||
self.btn_close.setToolTip("Close this window")
|
||||
self.btn_close.clicked.connect(self._close_widget)
|
||||
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
btns_layout.addWidget(self.btn_advanced)
|
||||
btns_layout.addWidget(self.btn_simple)
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(self.btn_ftrack_login)
|
||||
btns_layout.addWidget(self.btn_login)
|
||||
btns_layout.addWidget(self.btn_logout)
|
||||
btns_layout.addWidget(self.btn_close)
|
||||
|
||||
self.note_label = QtWidgets.QLabel((
|
||||
"NOTE: Click on \"{}\" button to log with your default browser"
|
||||
" or click on \"{}\" button to enter API key manually."
|
||||
).format(self.btn_ftrack_login.text(), self.btn_advanced.text()))
|
||||
|
||||
self.note_label.setWordWrap(True)
|
||||
self.note_label.hide()
|
||||
|
||||
self.error_label = QtWidgets.QLabel("")
|
||||
self.error_label.setWordWrap(True)
|
||||
self.error_label.hide()
|
||||
|
||||
label_layout = QtWidgets.QVBoxLayout()
|
||||
label_layout.setContentsMargins(10, 5, 10, 5)
|
||||
label_layout.addWidget(self.note_label)
|
||||
label_layout.addWidget(self.error_label)
|
||||
|
||||
main = QtWidgets.QVBoxLayout(self)
|
||||
main.addLayout(input_layout)
|
||||
main.addLayout(label_layout)
|
||||
main.addStretch(1)
|
||||
main.addLayout(btns_layout)
|
||||
|
||||
self.fill_ftrack_url()
|
||||
|
||||
self.set_is_logged(self._is_logged)
|
||||
|
||||
self.setLayout(main)
|
||||
|
||||
def show(self, *args, **kwargs):
|
||||
super(CredentialsDialog, self).show(*args, **kwargs)
|
||||
self.fill_ftrack_url()
|
||||
|
||||
def fill_ftrack_url(self):
|
||||
url = os.getenv("FTRACK_SERVER")
|
||||
checked_url = self.check_url(url)
|
||||
if checked_url == self.ftsite_input.text():
|
||||
return
|
||||
|
||||
self.ftsite_input.setText(checked_url or "< Not set >")
|
||||
|
||||
enabled = bool(checked_url)
|
||||
|
||||
self.btn_login.setEnabled(enabled)
|
||||
self.btn_ftrack_login.setEnabled(enabled)
|
||||
|
||||
self.api_input.setEnabled(enabled)
|
||||
self.user_input.setEnabled(enabled)
|
||||
|
||||
if not url:
|
||||
self.btn_advanced.hide()
|
||||
self.btn_simple.hide()
|
||||
self.btn_ftrack_login.hide()
|
||||
self.btn_login.hide()
|
||||
self.note_label.hide()
|
||||
self.api_input.hide()
|
||||
self.user_input.hide()
|
||||
|
||||
def set_advanced_mode(self, is_advanced):
|
||||
self._in_advance_mode = is_advanced
|
||||
|
||||
self.error_label.setVisible(False)
|
||||
|
||||
is_logged = self._is_logged
|
||||
|
||||
self.note_label.setVisible(not is_logged and not is_advanced)
|
||||
self.btn_ftrack_login.setVisible(not is_logged and not is_advanced)
|
||||
self.btn_advanced.setVisible(not is_logged and not is_advanced)
|
||||
|
||||
self.btn_login.setVisible(not is_logged and is_advanced)
|
||||
self.btn_simple.setVisible(not is_logged and is_advanced)
|
||||
|
||||
self.user_label.setVisible(is_logged or is_advanced)
|
||||
self.user_input.setVisible(is_logged or is_advanced)
|
||||
self.api_label.setVisible(is_logged or is_advanced)
|
||||
self.api_input.setVisible(is_logged or is_advanced)
|
||||
if is_advanced:
|
||||
self.user_input.setFocus()
|
||||
else:
|
||||
self.btn_ftrack_login.setFocus()
|
||||
|
||||
def set_is_logged(self, is_logged):
|
||||
self._is_logged = is_logged
|
||||
|
||||
self.user_input.setReadOnly(is_logged)
|
||||
self.api_input.setReadOnly(is_logged)
|
||||
self.user_input.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
|
||||
self.api_input.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
|
||||
|
||||
self.btn_logout.setVisible(is_logged)
|
||||
|
||||
self.set_advanced_mode(self._in_advance_mode)
|
||||
|
||||
def set_error(self, msg):
|
||||
self.error_label.setText(msg)
|
||||
self.error_label.show()
|
||||
|
||||
def _on_logout_clicked(self):
|
||||
self.user_input.setText("")
|
||||
self.api_input.setText("")
|
||||
self.set_is_logged(False)
|
||||
self.logout_signal.emit()
|
||||
|
||||
def _on_simple_clicked(self):
|
||||
self.set_advanced_mode(False)
|
||||
|
||||
def _on_advanced_clicked(self):
|
||||
self.set_advanced_mode(True)
|
||||
|
||||
def _user_changed(self):
|
||||
self._not_invalid_input(self.user_input)
|
||||
|
||||
def _api_changed(self):
|
||||
self._not_invalid_input(self.api_input)
|
||||
|
||||
def _not_invalid_input(self, input_widget):
|
||||
input_widget.setStyleSheet("")
|
||||
|
||||
def _invalid_input(self, input_widget):
|
||||
input_widget.setStyleSheet("border: 1px solid red;")
|
||||
|
||||
def _on_login(self):
|
||||
self.set_is_logged(True)
|
||||
self._close_widget()
|
||||
|
||||
def _on_login_clicked(self):
|
||||
username = self.user_input.text().strip()
|
||||
api_key = self.api_input.text().strip()
|
||||
missing = []
|
||||
if username == "":
|
||||
missing.append("Username")
|
||||
self._invalid_input(self.user_input)
|
||||
|
||||
if api_key == "":
|
||||
missing.append("API Key")
|
||||
self._invalid_input(self.api_input)
|
||||
|
||||
if len(missing) > 0:
|
||||
self.set_error("You didn't enter {}".format(" and ".join(missing)))
|
||||
return
|
||||
|
||||
if not self.login_with_credentials(username, api_key):
|
||||
self._invalid_input(self.user_input)
|
||||
self._invalid_input(self.api_input)
|
||||
self.set_error(
|
||||
"We're unable to sign in to Ftrack with these credentials"
|
||||
)
|
||||
|
||||
def _on_ftrack_login_clicked(self):
|
||||
url = self.check_url(self.ftsite_input.text())
|
||||
if not url:
|
||||
return
|
||||
|
||||
# If there is an existing server thread running we need to stop it.
|
||||
if self._login_server_thread:
|
||||
if self._login_server_thread.isAlive():
|
||||
self._login_server_thread.stop()
|
||||
self._login_server_thread.join()
|
||||
self._login_server_thread = None
|
||||
|
||||
# If credentials are not properly set, try to get them using a http
|
||||
# server.
|
||||
self._login_server_thread = login_tools.LoginServerThread(
|
||||
url, self._result_of_ftrack_thread
|
||||
)
|
||||
self._login_server_thread.start()
|
||||
|
||||
def _result_of_ftrack_thread(self, username, api_key):
|
||||
if not self.login_with_credentials(username, api_key):
|
||||
self._invalid_input(self.api_input)
|
||||
self.set_error((
|
||||
"Somthing happened with Ftrack login."
|
||||
" Try enter Username and API key manually."
|
||||
))
|
||||
|
||||
def login_with_credentials(self, username, api_key):
|
||||
verification = credentials.check_credentials(username, api_key)
|
||||
if verification:
|
||||
credentials.save_credentials(username, api_key, False)
|
||||
self._module.set_credentials_to_env(username, api_key)
|
||||
self.set_credentials(username, api_key)
|
||||
self.login_changed.emit()
|
||||
return verification
|
||||
|
||||
def set_credentials(self, username, api_key, is_logged=True):
|
||||
self.user_input.setText(username)
|
||||
self.api_input.setText(api_key)
|
||||
|
||||
self.error_label.hide()
|
||||
|
||||
self._not_invalid_input(self.ftsite_input)
|
||||
self._not_invalid_input(self.user_input)
|
||||
self._not_invalid_input(self.api_input)
|
||||
|
||||
if is_logged is not None:
|
||||
self.set_is_logged(is_logged)
|
||||
|
||||
def check_url(self, url):
|
||||
if url is not None:
|
||||
url = url.strip("/ ")
|
||||
|
||||
if not url:
|
||||
self.set_error(
|
||||
"Ftrack URL is not defined in settings!"
|
||||
)
|
||||
return
|
||||
|
||||
if "http" not in url:
|
||||
if url.endswith("ftrackapp.com"):
|
||||
url = "https://" + url
|
||||
else:
|
||||
url = "https://{}.ftrackapp.com".format(url)
|
||||
try:
|
||||
result = requests.get(
|
||||
url,
|
||||
# Old python API will not work with redirect.
|
||||
allow_redirects=False
|
||||
)
|
||||
except requests.exceptions.RequestException:
|
||||
self.set_error(
|
||||
"Specified URL could not be reached."
|
||||
)
|
||||
return
|
||||
|
||||
if (
|
||||
result.status_code != 200
|
||||
or "FTRACK_VERSION" not in result.headers
|
||||
):
|
||||
self.set_error(
|
||||
"Specified URL does not lead to a valid Ftrack server."
|
||||
)
|
||||
return
|
||||
return url
|
||||
|
||||
def closeEvent(self, event):
|
||||
event.ignore()
|
||||
self._close_widget()
|
||||
|
||||
def _close_widget(self):
|
||||
self.hide()
|
||||
103
openpype/modules/ftrack/tray/login_tools.py
Normal file
103
openpype/modules/ftrack/tray/login_tools.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from urllib import parse
|
||||
import webbrowser
|
||||
import functools
|
||||
import threading
|
||||
from openpype import resources
|
||||
|
||||
|
||||
class LoginServerHandler(BaseHTTPRequestHandler):
|
||||
'''Login server handler.'''
|
||||
|
||||
message_filepath = resources.get_resource("ftrack", "sign_in_message.html")
|
||||
|
||||
def __init__(self, login_callback, *args, **kw):
|
||||
'''Initialise handler.'''
|
||||
self.login_callback = login_callback
|
||||
BaseHTTPRequestHandler.__init__(self, *args, **kw)
|
||||
|
||||
def log_message(self, format_str, *args):
|
||||
"""Override method of BaseHTTPRequestHandler.
|
||||
|
||||
Goal is to use `print` instead of `sys.stderr.write`
|
||||
"""
|
||||
# Change
|
||||
print("%s - - [%s] %s\n" % (
|
||||
self.client_address[0],
|
||||
self.log_date_time_string(),
|
||||
format_str % args
|
||||
))
|
||||
|
||||
def do_GET(self):
|
||||
'''Override to handle requests ourselves.'''
|
||||
parsed_path = parse.urlparse(self.path)
|
||||
query = parsed_path.query
|
||||
|
||||
api_user = None
|
||||
api_key = None
|
||||
login_credentials = None
|
||||
if 'api_user' and 'api_key' in query:
|
||||
login_credentials = parse.parse_qs(query)
|
||||
api_user = login_credentials['api_user'][0]
|
||||
api_key = login_credentials['api_key'][0]
|
||||
|
||||
with open(self.message_filepath, "r") as message_file:
|
||||
sign_in_message = message_file.read()
|
||||
|
||||
# formatting html code for python
|
||||
replacements = (
|
||||
("{", "{{"),
|
||||
("}", "}}"),
|
||||
("{{}}", "{}")
|
||||
)
|
||||
for replacement in (replacements):
|
||||
sign_in_message = sign_in_message.replace(*replacement)
|
||||
message = sign_in_message.format(api_user)
|
||||
else:
|
||||
message = "<h1>Failed to sign in</h1>"
|
||||
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(message.encode())
|
||||
|
||||
if login_credentials:
|
||||
self.login_callback(
|
||||
api_user,
|
||||
api_key
|
||||
)
|
||||
|
||||
|
||||
class LoginServerThread(threading.Thread):
|
||||
'''Login server thread.'''
|
||||
|
||||
def __init__(self, url, callback):
|
||||
self.url = url
|
||||
self.callback = callback
|
||||
self._server = None
|
||||
super(LoginServerThread, self).__init__()
|
||||
|
||||
def _handle_login(self, api_user, api_key):
|
||||
'''Login to server with *api_user* and *api_key*.'''
|
||||
self.callback(api_user, api_key)
|
||||
|
||||
def stop(self):
|
||||
if self._server:
|
||||
self._server.server_close()
|
||||
|
||||
def run(self):
|
||||
'''Listen for events.'''
|
||||
self._server = HTTPServer(
|
||||
('localhost', 0),
|
||||
functools.partial(
|
||||
LoginServerHandler, self._handle_login
|
||||
)
|
||||
)
|
||||
unformated_url = (
|
||||
'{0}/user/api_credentials?''redirect_url=http://localhost:{1}'
|
||||
)
|
||||
webbrowser.open_new_tab(
|
||||
unformated_url.format(
|
||||
self.url, self._server.server_port
|
||||
)
|
||||
)
|
||||
self._server.handle_request()
|
||||
Loading…
Add table
Add a link
Reference in a new issue