55import logging
66import uuid
77from collections import defaultdict
8+ from functools import partial
9+ import atexit
810
911from six import string_types
1012
13+ import ftrack_api
14+
1115from ftrack_framework_core .widget .dialog import FrameworkDialog
1216import ftrack_constants .framework as constants
1317
1418from ftrack_framework_core .client .host_connection import HostConnection
1519
16- from ftrack_utils .decorators import track_framework_usage
20+ from ftrack_utils .decorators import track_framework_usage , run_in_main_thread
1721
1822from ftrack_utils .framework .config .tool import get_tool_config_by_name
1923
24+ from ftrack_framework_core .event import EventManager
25+
2026
2127class Client (object ):
2228 '''
@@ -179,10 +185,22 @@ def registry(self):
179185 def tool_config_options (self ):
180186 return self ._tool_config_options
181187
188+ @property
189+ def remote_event_manager (self ):
190+ # TODO: this is a temporal solution, 1 session should be able to act as local and remote at the same time
191+ if self ._remote_event_manager :
192+ return self ._remote_event_manager
193+ else :
194+ _remote_session = ftrack_api .Session (auto_connect_event_hub = False )
195+ self ._remote_event_manager = EventManager (
196+ session = _remote_session , mode = constants .event .REMOTE_EVENT_MODE
197+ )
198+ # Make sure it is shutdown
199+ atexit .register (self .close )
200+ return self ._remote_event_manager
201+
182202 def __init__ (
183- self ,
184- event_manager ,
185- registry ,
203+ self , event_manager , registry , run_in_main_thread_wrapper = None
186204 ):
187205 '''
188206 Initialise Client with instance of
@@ -206,6 +224,10 @@ def __init__(
206224 self .__instanced_dialogs = {}
207225 self ._dialog = None
208226 self ._tool_config_options = defaultdict (defaultdict )
227+ self ._remote_event_manager = None
228+
229+ # Set up the run_in_main_thread decorator
230+ self .run_in_main_thread_wrapper = run_in_main_thread_wrapper
209231
210232 self .logger .debug ('Initialising Client {}' .format (self ))
211233
@@ -267,6 +289,7 @@ def on_host_changed(self, host_connection):
267289 self .event_manager .publish .client_signal_host_changed (self .id )
268290
269291 # Context
292+ @run_in_main_thread
270293 def _host_context_changed_callback (self , event ):
271294 '''Set the new context ID based on data provided in *event*'''
272295 # Feed the new context to the client
@@ -302,6 +325,7 @@ def run_tool_config(self, tool_config_reference):
302325 )
303326
304327 # Plugin
328+ @run_in_main_thread
305329 def on_log_item_added_callback (self , event ):
306330 '''
307331 Called when a log item has added in the host.
@@ -321,6 +345,7 @@ def on_log_item_added_callback(self, event):
321345 self .id , event ['data' ]['log_item' ]
322346 )
323347
348+ @run_in_main_thread
324349 def on_ui_hook_callback (self , event ):
325350 '''
326351 Called ui_hook has been executed on host and needs to notify UI with
@@ -339,24 +364,111 @@ def reset_all_tool_configs(self):
339364 '''
340365 self .host_connection .reset_all_tool_configs ()
341366
367+ @run_in_main_thread
368+ def _on_discover_action_callback (
369+ self , name , label , dialog_name , options , session_identifier_func , event
370+ ):
371+ '''Discover *event*.'''
372+ if session_identifier_func :
373+ session_id = session_identifier_func ()
374+ label = label + " @" + session_id
375+ selection = event ['data' ].get ('selection' , [])
376+ if len (selection ) == 1 and selection [0 ]['entityType' ] == 'Component' :
377+ return {
378+ 'items' : [
379+ {
380+ 'name' : name ,
381+ 'label' : label ,
382+ 'host_id' : self .host_id ,
383+ 'dialog_name' : dialog_name ,
384+ 'options' : options ,
385+ }
386+ ]
387+ }
388+
389+ @run_in_main_thread
390+ def _on_launch_action_callback (self , event ):
391+ '''Handle *event*.
392+
393+ event['data'] should contain:
394+
395+ *applicationIdentifier* to identify which application to start.
396+
397+ '''
398+ selection = event ['data' ]['selection' ]
399+
400+ name = event ['data' ]['name' ]
401+ label = event ['data' ]['label' ]
402+ dialog_name = event ['data' ]['dialog_name' ]
403+ options = event ['data' ]['options' ]
404+ options ['event_data' ] = {'selection' : selection }
405+
406+ self .run_tool (name , dialog_name , options )
407+
408+ def subscribe_action_tool (
409+ self ,
410+ name ,
411+ label = None ,
412+ dialog_name = None ,
413+ options = None ,
414+ session_identifier_func = None ,
415+ ):
416+ '''
417+ Subscribe the given tool to the ftrack.action.discover and
418+ ftrack.action.launch events.
419+ '''
420+ if not options :
421+ options = dict ()
422+ # TODO: The event should be added to the event manager to be accesible
423+ # through subscribe and publish classes
424+ self .remote_event_manager .session .event_hub .subscribe (
425+ u'topic=ftrack.action.discover and '
426+ u'source.user.username="{0}"' .format (self .session .api_user ),
427+ partial (
428+ self ._on_discover_action_callback ,
429+ name ,
430+ label ,
431+ dialog_name ,
432+ options ,
433+ session_identifier_func ,
434+ ),
435+ )
436+
437+ self .remote_event_manager .session .event_hub .subscribe (
438+ u'topic=ftrack.action.launch and '
439+ u'data.name={0} and '
440+ u'source.user.username="{1}" and '
441+ u'data.host_id={2}' .format (
442+ name , self .session .api_user , self .host_id
443+ ),
444+ self ._on_launch_action_callback ,
445+ )
446+
342447 @track_framework_usage (
343448 'FRAMEWORK_RUN_TOOL' ,
344449 {'module' : 'client' },
345450 ['name' ],
346451 )
347- def run_tool (self , name , dialog_name = None , options = dict , dock_func = False ):
452+ def run_tool (
453+ self ,
454+ name ,
455+ dialog_name = None ,
456+ options = None ,
457+ dock_func = False ,
458+ ):
348459 '''
349460 Client runs the tool passed from the DCC config, can run run_dialog
350461 if the tool has UI or directly run_tool_config if it doesn't.
351462 '''
463+
352464 self .logger .info (f"Running { name } tool" )
465+ if not options :
466+ options = dict ()
467+
353468 if dialog_name :
354469 self .run_dialog (
355470 dialog_name ,
356- dialog_options = {
357- 'tool_config_names' : options .get ('tool_configs' ),
358- 'docked' : options .get ('docked' , False ),
359- },
471+ dialog_options = options ,
360472 dock_func = dock_func ,
361473 )
362474 else :
@@ -375,6 +487,11 @@ def run_tool(self, name, dialog_name=None, options=dict, dock_func=False):
375487 f"Couldn't find any tool config matching the name { tool_config_name } "
376488 )
377489 continue
490+
491+ self .set_config_options (
492+ tool_config ['reference' ], options = options
493+ )
494+
378495 self .run_tool_config (tool_config ['reference' ])
379496
380497 # UI
@@ -500,16 +617,24 @@ def _connect_getter_property_callback(self, property_name):
500617 return self .__getattribute__ (property_name )
501618
502619 def set_config_options (
503- self , tool_config_reference , plugin_config_reference , plugin_options
620+ self , tool_config_reference , plugin_config_reference = None , options = None
504621 ):
505- if not isinstance (plugin_options , dict ):
622+ if not options :
623+ options = dict ()
624+ # TODO_ mayabe we should rename this one to make sure this is just for plugins
625+ if not isinstance (options , dict ):
506626 raise Exception (
507627 "plugin_options should be a dictionary. "
508- "Current given type: {}" .format (plugin_options )
628+ "Current given type: {}" .format (options )
509629 )
510- self ._tool_config_options [tool_config_reference ][
511- plugin_config_reference
512- ] = plugin_options
630+ if not plugin_config_reference :
631+ self ._tool_config_options [tool_config_reference ][
632+ 'options'
633+ ] = options
634+ else :
635+ self ._tool_config_options [tool_config_reference ][
636+ plugin_config_reference
637+ ] = options
513638
514639 def run_ui_hook (
515640 self , tool_config_reference , plugin_config_reference , payload
@@ -539,3 +664,11 @@ def verify_plugins(self, plugin_names):
539664 self .host_id , plugin_names
540665 )[0 ]
541666 return unregistered_plugins
667+
668+ def close (self ):
669+ self .logger .debug ('Shutting down client' )
670+
671+ if self ._remote_event_manager :
672+ self .logger .debug ('Stopping remote_event_manager' )
673+ self .remote_event_manager .close ()
674+ self ._remote_event_manager = None
0 commit comments