class SyncDaemonToolProxy(object): """Platform dependent proxy to syncdaemon. Please note that most of the methods of this class are "pre-processed" by overriding __getattribute__, in a way where _call_after_connection is called before the method itself, so every public method will return a deferred that will be fired when this client is connected. """ _SIGNAL_MAPPING = { 'Event': ('events', 'on_event_cb'), 'FolderCreated': ('folders', 'on_folder_created_cb'), 'FolderCreateError': ('folders', 'on_folder_create_error_cb'), 'FolderDeleted': ('folders', 'on_folder_deleted_cb'), 'FolderDeleteError': ('folders', 'on_folder_delete_error_cb'), 'FolderSubscribed': ('folders', 'on_folder_subscribed_cb'), 'FolderSubscribeError': ('folders', 'on_folder_subscribe_error_cb'), 'FolderUnSubscribed': ('folders', 'on_folder_unsubscribed_cb'), 'FolderUnSubscribeError': ('folders', 'on_folder_unsubscribe_error_cb'), 'NewShare': ('shares', 'on_new_share_cb'), 'PublicAccessChanged': ('public_files', 'on_public_access_changed_cb'), 'PublicAccessChangeError': ('public_files', 'on_public_access_change_error_cb'), 'PublicFilesList': ('public_files', 'on_public_files_list_cb'), 'PublicFilesListError': ('public_files', 'on_public_files_list_error_cb'), 'ShareAnswerResponse': ('shares', 'on_share_answer_response_cb'), 'ShareChanges': ('shares', 'on_share_changed_cb'), 'ShareCreated': ('shares', 'on_share_created_cb'), 'ShareCreateError': ('shares', 'on_share_create_error_cb'), 'ShareDeleted': ('shares', 'on_share_deleted_cb'), 'ShareDeleteError': ('shares', 'on_share_delete_error_cb'), 'ShareSubscribed': ('shares', 'on_share_subscribed_cb'), 'ShareSubscribeError': ('shares', 'on_share_subscribe_error_cb'), 'ShareUnSubscribed': ('shares', 'on_share_unsubscribed_cb'), 'ShareUnSubscribeError': ('shares', 'on_share_unsubscribe_error_cb'), 'StatusChanged': ('status', 'on_status_changed_cb'), 'VolumesChanged': ('sync_daemon', 'on_volumes_changed_cb'), } # All methods and instance variables that should not be handled by # _call_after_connection should be put in the list below (or start with _) _DONT_VERIFY_CONNECTED = [ "wait_connected", "client", "last_event", "delayed_call", "log", "connected", "connected_signals" ] def _should_wrap(self, attr_name): """Check if this attribute should be wrapped.""" return not (attr_name in SyncDaemonToolProxy._DONT_VERIFY_CONNECTED or attr_name.startswith("_")) def __getattribute__(self, attr_name): """If the attribute is not special, verify the ipc connection.""" attr = super(SyncDaemonToolProxy, self).__getattribute__(attr_name) if SyncDaemonToolProxy._should_wrap(self, attr_name): return self._call_after_connection(attr) else: return attr def __init__(self, bus=None): self.log = logging.getLogger( 'ubuntuone.platform.tools.perspective_broker') self.client = UbuntuOneClient() self.connected = None self.connected_signals = defaultdict(set) def _call_after_connection(self, method): """Make sure Perspective Broker is connected before calling.""" if not self.client.is_connected(): self.connected = self.client.connect() @defer.inlineCallbacks def call_after_connection_inner(*args, **kwargs): """Call the given method after the connection to pb is made.""" yield self.connected try: retval = yield method(*args, **kwargs) except DeadReferenceError: self.log.debug('Got stale broker, atempting reconnect.') # might be the case where we have a stale broker yield self._reconnect_client() retval = yield method(*args, **kwargs) defer.returnValue(retval) return call_after_connection_inner @defer.inlineCallbacks def _reconnect_client(self): """Reconnect the client.""" self.connected = False yield self.client.reconnect() # do connect all the signals again for signal_name, handlers in self.connected_signals.items(): for handler in handlers: self.connect_signal(signal_name, handler) @defer.inlineCallbacks def call_method(self, client_kind, method_name, *args, **kwargs): """Call the 'method_name' passing 'args' and 'kwargs'.""" client = getattr(self.client, client_kind) method = getattr(client, method_name) try: result = yield method(*args, **kwargs) except DeadReferenceError: self.log.debug('Got stale broker, atempting reconnect.') # may happen in the case we reconnected and the server side objects # for gc yield self._reconnect_client() result = yield self.call_method( client_kind, method_name, *args, **kwargs) except RemoteError as e: # Wrap RemoteErrors in IPCError to match DBus interface's # behavior: raise IPCError(name=e.remoteType, info=[e.args], details=e.message) defer.returnValue(result) def shutdown(self): """Close connections.""" return self.client.disconnect() def _handler(self, signal_name, *args, **kwargs): """Call all the handlers connected to signal_name.""" for cb_handler in self.connected_signals[signal_name]: cb_handler(*args, **kwargs) def connect_signal(self, signal_name, handler): """Connect 'handler' with 'signal_name'.""" client_kind, callback = self._SIGNAL_MAPPING[signal_name] client = getattr(self.client, client_kind) if len(self.connected_signals[signal_name]) == 0: setattr( client, callback, lambda *args, **kw: self._handler(signal_name, *args, **kw)) # do remember the connected signal in case we need to reconnect self.connected_signals[signal_name].add(handler) return handler def disconnect_signal(self, signal_name, handler_or_match): """Disconnect 'handler_or_match' from 'signal_name'.""" client_kind, callback = self._SIGNAL_MAPPING[signal_name] client = getattr(self.client, client_kind) setattr(client, callback, None) # forget that the connection was made in case we need to reconnect del self.connected_signals[signal_name] return handler_or_match def wait_connected(self): """Wait until syncdaemon is connected to the server.""" return self.connected def start(self): """Start syncdaemon, should *not* be running.""" try: cmd = get_sd_bin_cmd() except Exception, e: defer.fail(e) p = subprocess.Popen(cmd) return defer.succeed(p)
def __init__(self, bus=None): self.log = logging.getLogger( 'ubuntuone.platform.tools.perspective_broker') self.client = UbuntuOneClient() self.connected = None self.connected_signals = defaultdict(set)