# -*- coding: utf-8 -*- ## Copyright (c) 2012 Nexedi SARL and Contributors. All Rights Reserved. # Aurélien Calonne <aurel@nexedi.com> # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # garantees and support are strongly adviced to contract a Free Software # Service Company # # This program is Free Software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################## from logging import getLogger from Products.ERP5SyncML.Engine.EngineMixin import EngineMixin from Products.ERP5SyncML.SyncMLConstant import SynchronizationError syncml_logger = getLogger('ERP5SyncML') class SyncMLSynchronousEngine(EngineMixin): """ Implement a synchronous engine wait for IO """ def processClientSynchronization(self, syncml_request, subscription): """ Global method that process the package 3 of SyncML DS Protocol """ syncml_logger.info("xxx Client processing data from server xxx") syncml_logger.info("\tstatus %s, sync %s, final %s" % (len(syncml_request.status_list), len(syncml_request.sync_command_list), syncml_request.isFinal)) # Process sync logged in subscription._loginUser() if syncml_request.alert_list: syncml_logger.warning("Got an alert from server not processed : %s" % (syncml_request.alert_list,)) # Must check what server tell about database synchronization # and update the mode if required syncml_response = self._generateBaseResponse(subscription) # Read & apply status about databases & synchronizations try: self._readStatusList(syncml_request, subscription, syncml_response) except SynchronizationError: # Looks like we process an already received message syncml_logger.error("%s does no process packet due to error" % (subscription.getRelativeUrl())) return if syncml_request.isFinal and \ subscription.getSynchronizationState() == "initializing": # Client sends its modifications first # before getting the one from server subscription.sendModifications() # Worfklow action # Do action according to synchronization state if subscription.getSynchronizationState() == "initializing": raise ValueError("Subscription still initializing, must not get here") elif subscription.getSynchronizationState() == "sending_modifications": # Client always sent its modifications first if subscription.getSyncmlAlertCode() in ("one_way_from_server", "refresh_from_server_only"): # We only get data from server finished = True else: finished = subscription._getSyncMLData( syncml_response=syncml_response, ) syncml_logger.info("-> Client sendind modification, finished %s" % (finished,)) if finished: # Add deleted objets subscription._getDeletedData(syncml_response=syncml_response) # Notify that all modifications were sent syncml_response.addFinal() # Will then start processing sync commands from server subscription.processSyncRequest() elif subscription.getSynchronizationState() == "processing_sync_requests": # In a second time, clients applied modifications from server if subscription.getSyncmlAlertCode() == "refresh_from_server_only": syncml_response=None subscription.applyActionList( syncml_request=syncml_request, syncml_response=syncml_response, simulate=False) syncml_logger.info("-> Client sending %s notification of object synchronized" % (syncml_response.sync_confirmation_counter)) if syncml_request.isFinal: # Notify that all modifications were applied syncml_response.addFinal() # Synchronization process is now finished subscription.finish() else: raise ValueError("Unmanaged state of synchronization %s : %s" % (subscription.getRelativeUrl(), subscription.getSynchronizationState())) if subscription.getSynchronizationState() == "finished": # We do not expect anymore message from server # XXX Map of UID is not implemented syncml_logger.info('--- synchronization ended on the client side ---') # Remove authentication if subscription.getAuthenticationState() == 'logged_in': subscription.logout() subscription._edit(authenticated_user=None) # Send the message subscription.sendMessage(xml=str(syncml_response)) return str(syncml_response) def processServerSynchronization(self, subscriber, syncml_request): """ Process the package 4 of the SyncML DS exchange """ if not subscriber.checkCorrectRemoteMessageId( syncml_request.header['message_id']): syncml_logger.warning("Resending last message") syncml_response = subscriber.getLastSentMessage("") # XXX else: syncml_logger.info("xxx Server processing data from client xxx") syncml_logger.info("\tstatus %s, sync %s, final %s" % (len(syncml_request.status_list), len(syncml_request.sync_command_list), syncml_request.isFinal)) # we log the user authenticated to do the synchronization with him if subscriber.getAuthenticationState() == 'logged_in': subscriber._loginUser() else: # Do not run sync if not authenticated raise ValueError("Authentication failed, impossible to sync data") # Apply command & send modifications syncml_response = self._generateBaseResponse(subscriber) # Apply status about object send & synchronized if any self._readStatusList(syncml_request, subscriber, syncml_response, True) if syncml_request.isFinal: if subscriber.getSynchronizationState() == \ "waiting_notifications": # We got the last notifications from clients subscriber.finish() elif subscriber.getSynchronizationState() != \ "processing_sync_requests": raise SynchronizationError("Got final request although not waiting for it") # XXX We compute gid list so that we do not get issue with catalog # XXX This is a hack, if real datasynchronization is implemented # diff of objects must be computed before any process of data from # clients, which is not the case here # XXX To avoid issue with multiple message, this must be stored # in memcached instead of this variable if subscriber.getSynchronizationState() == "initializing": raise ValueError("Subscription still initializing, must not get here") if subscriber.getSynchronizationState() == "processing_sync_requests": # First server process sync commands from client if subscriber.getSyncmlAlertCode() == "refresh_from_client_only": # No need to send back hack XXX this is erp5 specific optimisation # as no signature is created subscriber.applyActionList( syncml_request=syncml_request, syncml_response=None, simulate=True) else: subscriber.applyActionList( syncml_request=syncml_request, syncml_response=syncml_response, simulate=True) syncml_logger.info("-> Server sending %s notification of sync" % (syncml_response.sync_confirmation_counter)) if syncml_request.isFinal: # Server will now send its modifications subscriber.sendModifications() if subscriber.getSyncmlAlertCode() not in ("one_way_from_client", "refresh_from_client_only"): # Reset signature only if we have to check modifications on server side subscriber.initialiseSynchronization() # Do not continue in elif, as sending modifications is done in the same # package as sending notifications if subscriber.getSynchronizationState() == "sending_modifications": # In a second time, server send its modifications if subscriber.getSyncmlAlertCode() in ("one_way_from_client", "refresh_from_client_only"): # We only get data from client finished = True else: finished = subscriber._getSyncMLData( syncml_response=syncml_response) syncml_logger.info("-> Server sendind data, finished %s" % (finished,)) if finished: subscriber._getDeletedData(syncml_response=syncml_response) syncml_response.addFinal() subscriber.waitNotifications() # Do not go into finished here as we must wait for # notifications from client if subscriber.getSynchronizationState() == "finished": syncml_logger.info('--- synchronization ended on the server side ---') if subscriber.getAuthenticationState() == 'logged_in': subscriber.logout() subscriber._edit(authenticated_user=None, remaining_object_path_list=None) syncml_response = "" # XXX This is expected by unit test only # Body must be sent even when there is no data to notify client subscriber.sendMessage(xml=str(syncml_response)) # Return message for unit test purpose return str(syncml_response)