def _on_entering(self): """Acquire network resources and try to connect. The connection is authenticated by the mean of the server certificate, so it is trusted. """ self.logger.info(u"Connecting to server...") self._context._internal_facade.set_global_status( GStatuses.NC_CONNECTING) if self._context.acquire_network_resources(): self.logger.info(u"Server has successfully authenticated itself") self._context.num_connection_attempts = 0 self._context._internal_facade.reset_pause_timer() self._set_next_state(StateRegister.get('ConnectedState')) self._context._input_queue.put(Command('HANDSHAKE'), 'sessioncommand') else: self._context._internal_facade.set_global_status( GStatuses.NC_NOSERVER) if self._context.num_connection_attempts > self._context.max_connection_attempts: self.logger.error( u'Server has been unreachable for too long, giving up.') return self._context._internal_facade.pause_and_restart() self._set_next_state(StateRegister.get('DisconnectedState')) self._context._input_queue.put(Command('CONNECT'), 'sessioncommand')
def _handle_command_OPERATIONSFINISHED(self, command): """Receiving this command means that all the internal operations have been served. Wait until all of them are complete. """ self._set_next_state(StateRegister.get('SyncWaitingForCompletionState')) cmd = Command('GOTOONCOMPLETION') cmd.next_state = 'SyncDoneState' self._context._input_queue.put(cmd, 'sessioncommand')
def _set_next_state(self, next_state): """Change ServerSession's state. @param next_state: Instance of a subclass of ServerSessionState. """ command = Command('CHANGESTATE') command.next_state = next_state self._context._input_queue.put(command, 'sessioncommand')
def signal_download_integrity_error(self, operation, reason, expected_etag, expected_basis, actual_etag, computed_basis): """Tell ServerSession that the integrity check of a downloaded file has failed. This method is meant to be called by Workers. @param operation: Instance of PathnameOperation. Remember that it contains also its Proof object. @param reason: String shortly describing the error. @param expected_etag: The etag the file was expected to have. It's the one communicated by the server in its file list. @param expected_basis: The trusted basis. It's the one the user had accepted when the sync started. @param actual_etag: The etag the file has turned to have after being downloaded. @param computed_basis: The basis returned by the IntegrityManager when computing the given proof object. Possibly None. """ cmd = Command('INTEGRITYERRORONDOWNLOAD') cmd.operation = operation cmd.proof = operation.download_info['proof'] cmd.reason = reason cmd.expected_etag = expected_etag cmd.expected_basis = expected_basis cmd.actual_etag = actual_etag cmd.computed_basis = computed_basis self._input_queue.put(cmd, 'systemcommand')
def _exchange_keepalive_message(self): #self.logger.debug( # u"Sending KEEP_ALIVE id=%s to server" % self.keepalive_id) self.output_message_queue.put( KEEP_ALIVE('KEEP_ALIVE', {'id': self.keepalive_id})) self.keepalive_id += 1 if self.keepalive_id > 99999: self.keepalive_id = 1 try: while True: message = self.input_message_queue.get( block=True, timeout=self.timeout_time) if message is POISON_PILL: break if message.getParameter('id') == self.keepalive_id - 1: break except Queue.Empty: self.logger.warning( u"Server hasn't replied to KEEP_ALIVE in %s seconds," " assuming crash." % self.timeout_time) self._session_queue.put( Command('KEEPALIVETIMEDOUT'), 'sessioncommand') return # Have we been woken up due to suspension? if not self._check_suspension(): # No, it was a normal KEEP_ALIVE reply from the server ##self.logger.debug( ## u"Received KEEP_ALIVE reply id=%s from server" ## % (message.getParameter('id'))) pass
def _handle_message_COMMIT_DONE(self, message): """Everything went well, the server has completed the commit. Check the resulting basis sent by the server against our candidate basis, if it's valid then finalize the commit by updating all internal data structures. Finally go into the sync phase, the session can begin at last. """ self.logger.info(u'Commit done') server_basis = message.getParameter('new_basis') candidate_basis = self._load_candidate_basis() previous_basis = self._load_trusted_basis() self.logger.debug(u"Server basis: %s" % server_basis) self.logger.debug(u"Candidate basis: %s" % candidate_basis) self.logger.debug(u"Last trusted basis: %s" % previous_basis) # Check the server basis against the candidate basis self._context.integrity_manager.setCurrentBasis(candidate_basis) self._check_integrity(server_basis) # Everything OK, persist the new trusted basis and related metadata operations = self._context.transaction_cache.get_all_records() operations = [(op_id, op) for (op_id, op, _) in operations] self._persist_integrity_metadata(previous_basis, server_basis, operations) self._context.integrity_manager.setCurrentBasis(server_basis) self.logger.info(u"Pending commit successfully recovered.") self.logger.info(u"Updated basis: %s" % server_basis) self._update_user_interfaces(message) self._set_next_state(StateRegister.get('ReadyForServiceState')) self._context._input_queue.put(Command('STARTSYNCPHASE'), 'sessioncommand')
def signal_free_worker(self): """ Tell ServerSession that a worker is free to receive new tasks. This method is meant to be called by WorkerPool. """ self._input_queue.put(Command('WORKERFREE'), 'systemcommand')
def _handle_message_REPLICATION_START(self, message): """The server is ready to leave the sync phase, too. Let's start the Replication & Transfer phase. """ self._set_next_state( StateRegister.get('EnteringReplicationAndTransferState')) self._context._input_queue.put( Command('UPDATEBEFOREREPLICATION'), 'sessioncommand')
def signal_deletelocal_integrity_error(self, pathname, proof, reason, expected_basis, computed_basis): """Tell ServerSession that the integrity check of a pathname to delete locally has failed. This method is meant to be called by Workers. @param pathname: String representing the pathname. @param reason: String shortly describing the error. @param expected_basis: The trusted basis. It's the one the user had accepted when the sync started. @param computed_basis: The basis returned by the IntegrityManager when computing the given proof object. Possibly None. """ cmd = Command('INTEGRITYERRORONDELETELOCAL') cmd.pathname = pathname cmd.proof = proof cmd.reason = reason cmd.expected_basis = expected_basis cmd.computed_basis = computed_basis self._input_queue.put(cmd, 'systemcommand')
def run(self): self.started = True try: while not self._termination_requested(): ready, _, _ = select([self.sock], [], [], 1) if ready: msg = self._receive_message() if msg.name == 'KEEP_ALIVE': self.input_keepalive_queue.put(msg) else: self.input_message_queue.put(msg, 'servermessage') except ConnectionException: self.logger.info(u"Server has closed the connection, aborting") self.input_message_queue.put(Command('BROKENCONNECTION'), 'sessioncommand') except Exception as e: self.logger.warning(u"Detected a connection problem, aborting: %s", e) self.input_message_queue.put(Command('BROKENCONNECTION'), 'sessioncommand')
def signal_deletelocal_integrity_error( self, pathname, proof, reason, expected_basis, computed_basis): """Tell ServerSession that the integrity check of a pathname to delete locally has failed. This method is meant to be called by Workers. @param pathname: String representing the pathname. @param reason: String shortly describing the error. @param expected_basis: The trusted basis. It's the one the user had accepted when the sync started. @param computed_basis: The basis returned by the IntegrityManager when computing the given proof object. Possibly None. """ cmd = Command('INTEGRITYERRORONDELETELOCAL') cmd.pathname = pathname cmd.proof = proof cmd.reason = reason cmd.expected_basis = expected_basis cmd.computed_basis = computed_basis self._input_queue.put(cmd, 'systemcommand')
def run(self): """Implementation of the threading.Thread.run() method.""" self._started = True try: self.worker_pool.start_workers() self.keepalive_timer.start() self.current_state = StateRegister.get('DisconnectedState') curr_basis = self.current_state._load_trusted_basis() self.integrity_manager.setCurrentBasis(curr_basis) self.logger.info(u'Current basis is: %s' % curr_basis) self.current_state._on_entering() self._internal_facade.set_global_status(GStatuses.NC_STOPPED) if self.auto_start: self._input_queue.put(Command('CONNECT'), 'sessioncommand') self.cryptoAdapter.start() self._scheduler.schedule_action(self.check_encrypted_folder, name='check_encrypted_folder', seconds=5, repeating=True) # The event loop self._main_loop() except UnexpectedMessageException as e: self.logger.critical( u"Received an unexpected message from the Server while in " u"state '%s': %s. Forcing termination." % (self.current_state.__class__, str(e))) raise except ProtocolException as e: self.logger.critical( u"Detected an unrecoverable error, forcing termination: %s" % str(e)) # Pre-emptive release, just stop before messing up the server self.release_network_resources() raise except Exception as e: self.logger.critical( u"Forcing termination due to uncaught exception '%s': %s" % (e.__class__, e)) self.logger.debug(u"Last error stacktrace:\n%s" % traceback.format_exc()) # Pre-emptive release, just stop before messing up the server self.release_network_resources() raise
def terminate(self): """ Termination routine for this component. Stops the running thread and releases any acquired resource. """ self.logger.debug(u"Terminating Server Session...") if self._started: self.must_die.set() self.worker_pool.terminate() self._input_queue.put(Command('TERMINATE'), 'usercommand') self.transaction.can_be_committed.set() self.join() if self is not threading.current_thread() else None self.keepalive_timer.terminate() self.release_network_resources() self.cryptoAdapter.terminate() self.logger.debug(u"Server Session terminanted.")
def run(self): self.started = True try: msg = None while not self._termination_requested(): if msg == None: msg = self.output_message_queue.get() if msg == POISON_PILL: continue _, ready, _ = select([], [self.sock], [], 1) if ready: self._send_message(msg) msg = None except Exception as exception: self.logger.warning(u"Detected a connection problem, aborting: %s", exception) self._session_queue.put(Command('BROKENCONNECTION'), 'sessioncommand')
def _handle_message_CHALLENGE_VERIFY_RESPONSE(self, message): """Received a reply to our challenge. Did the server authenticate us? If not, maybe another client is already connected to this account. """ auth_result = message.getParameter('result') if auth_result: self.logger.info(u"Client has successfully authenticated itself.") self._context.output_message_queue.put( READY_FOR_SERVICE('READY_FOR_SERVICE')) self._context.session_id = message.get_session_id() self.logger.debug(u"Received Session id %s from server" % self._context.session_id) self._context.keepalive_timer.resume_execution() self._set_next_state(StateRegister.get('ReadyForServiceState')) else: self.logger.error(u"Server rejected client authentication.") self._context._internal_facade.set_global_status( GStatuses.NC_NOTAUTHORIZED) if message.is_other_client_connected(): client = message.get_other_connected_client() if client["client_id"] == 0: other_client_message = u"Your web client is already connected" else: other_client_message = \ u"Client number %s from computer %s already connected" \ % (client["client_id"], client["hostname"]) self.logger.warning(other_client_message) self._context.disconnect_other_client = True force_disconnection = self._context._ui_controller.ask_for_user_input( "other_client_connected", client["client_id"], client["hostname"]) if not force_disconnection == 'ok': self._context._internal_facade.pause() return else: self._context.current_state._set_next_state( StateRegister.get('DisconnectedState')) self._context._input_queue.put(Command('CONNECT'), 'sessioncommand') return else: relink_user(self)
def signal_download_integrity_error( self, operation, reason, expected_etag, expected_basis, actual_etag, computed_basis): """Tell ServerSession that the integrity check of a downloaded file has failed. This method is meant to be called by Workers. @param operation: Instance of PathnameOperation. Remember that it contains also its Proof object. @param reason: String shortly describing the error. @param expected_etag: The etag the file was expected to have. It's the one communicated by the server in its file list. @param expected_basis: The trusted basis. It's the one the user had accepted when the sync started. @param actual_etag: The etag the file has turned to have after being downloaded. @param computed_basis: The basis returned by the IntegrityManager when computing the given proof object. Possibly None. """ cmd = Command('INTEGRITYERRORONDOWNLOAD') cmd.operation = operation cmd.proof = operation.download_info['proof'] cmd.reason = reason cmd.expected_etag = expected_etag cmd.expected_basis = expected_basis cmd.actual_etag = actual_etag cmd.computed_basis = computed_basis self._input_queue.put(cmd, 'systemcommand')
def disconnect(self): """Make the client disconnect from the client, if connected.""" # TODO: this method has been temporarly replaced by PAUSE self._input_queue.put(Command('DISCONNECT'), 'usercommand')
def commit(self): """Make the client commit the current transaction.""" self._input_queue.put(Command('USERCOMMIT'), 'usercommand')
def _handle_command_BROKENCONNECTION(self, command): """The connection to the server is broken. """ self.logger.info(u"Detected disconnection from the server") self._set_next_state(StateRegister.get('DisconnectedState')) self._context._input_queue.put(Command('CONNECT'), 'sessioncommand')
def _handle_command_USERCOMMIT(self, message): """The user has asked to commit, the operation will be recovered on next R&T loop. """ self._set_next_state(StateRegister.get('ReplicationAndTransferState')) self._context._input_queue.put(Command('USERCOMMIT'), 'sessioncommand')
def _wake_me_up(self): """Callback for the scheduler. """ self._context._input_queue.put(Command('REDECLAREOPERATION'), 'sessioncommand')
def _check_time_to_commit(self): """If it's time to commit, so be it. """ if self._is_time_to_commit(): self._context._input_queue.put(Command('COMMIT'), 'sessioncommand')
def connect(self): """Make the client connect to the server.""" self._input_queue.put(Command('CONNECT'), 'usercommand')