Client Application RBS Synchronize Recovery Example

if SAP Mobile Server is restored to a point in time of previous application synchronization, the client synchronization gets Sybase.Persistence.SynchronizeException with error code SQLE_UPLOAD_FAILED_AT_SERVER. This error code indicates the need to recover the client database.

If SAP Mobile Server is restored to a point in time of the application's last synchronization or the application has never synchronized, the client application can synchronize as normal without the exception. For example:
Time1: application registered and has not synchronized. 
Time2: application synchronized for the first time. 
Time3: application synchronized for the second time. 
Time4: application synchronized for the last time.
If SAP Mobile Server is restored to time1 or time4, the client can synchronize successfully. If SAP Mobile Server is restored to time2 or time3, client synchronization fails with the exception. There are three ways to recover the client database and successfully synchronize. Before synchronization recovery, the application needs to complete application registration recovery if nessasary. Once the client application starts (startup), the application should check if the last recovery failed by checking the saved flag. If the last recovery failed, the application needs to resume the recovery first. The user can mark the recovery state any number of ways, for example, the application can save the recovery state to a file. As illustrated in example two below, copy the old client database and use it as a recovery state flag.
  1. Recreate database without copying old data (all data lost) - the simplest way to recover, but the old data, such as synchronization parameter, SIS, Local MBO are not copied to the new database. The end user needs to re-input those again in the application UI.
     @try
        {
            [end2end_rdbEnd2end_rdbDB synchronize];
        }
        @catch (SUPSynchronizeException *ex)
        {
            if (ex.errorCode == SYNC_UPLOAD_FAILED_AT_SERVER)
            {
                [self recoverClientDatabase];
            }
            else
            {
                @throw ex;
            }
        }
    
    - (void) recoverClientDatabase
    {
        [self setRecoveringInPlaceFlag]; // Such as saving a flag in application sandbox.
        [end2end_rdbEnd2end_rdbDB closeConnection];
        [end2end_rdbEnd2end_rdbDB deleteDatabase];
        [self clearRecoveringInPlaceFlag];
    }
    
  2. Recreate database and copy old database (local transaction lost) - the old database data is copied to the new database; this example includes personalization keys, subscription information, SIS info, local MBO. But un-submitted transactions like MBO’s pending state are lost. This sample code checks that a copy of the database is available to see if recovering was interrupted.
    if([self isRecoverFailed])
        {
            [self recoverClientDatabase];
        }
        else
        {
            @try
            {
                [end2end_rdbEnd2end_rdbDB synchronize];
            }
            @catch (SUPPersistenceException *ex)
            {
                if (ex.errorCode == SYNC_UPLOAD_FAILED_AT_SERVER)
                {
                    [self recoverClientDatabase];
                }
                else
                {
                    @throw ex;
                }
            }
        }
    
    - (BOOL)isRecoverFailed
    {
        NSString* dbFile = [end2end_rdbEnd2end_rdbDB getDbPath];
        NSString* recoverDbFile = [NSString stringWithFormat:@"%@.recover.udb", dbFile];
        NSFileManager *fm = [NSFileManager defaultManager];
        BOOL ok = YES;
        
        if ([fm isReadableFileAtPath:recoverDbFile])
        {
            // Recover DB exists, indicating the last recover was not finished
            // need to copy back the DB file and do recovery.
            if ([fm isReadableFileAtPath:dbFile])
            {
                if (![fm removeItemAtPath:dbFile error:nil])
                {
                    ok = NO;
                }
            }
            
            if (ok && [fm copyItemAtPath:recoverDbFile toPath:dbFile error:nil])
            {
                return YES;
            }
            else
            {
                @throw [NSException exceptionWithName:@"NSException" reason:@"Failed to copy the recover database back." userInfo:nil];
            }
        }
    
        return NO;
    }
    
    - (void) recoverClientDatabase
    {
        NSString* dbFile = [end2end_rdbEnd2end_rdbDB getDbPath];
        NSString* recoverDbFile = [NSString stringWithFormat:@"%@.recover.udb", dbFile];
        NSFileManager *fm = [NSFileManager defaultManager];
        
        if (![fm copyItemAtPath:dbFile toPath:recoverDbFile error:nil])
        {
            @throw [NSException exceptionWithName:@"NSException" reason:@"Failed to copy the recover database." userInfo:nil];
        }
        
        //retrieve all the subscriptions from client database
        SUPObjectList *customerWithParamSubscriptions = [end2end_rdbCustomerWithParam getSubscriptions]; 
        SUPObjectList *sisSubscriptions = [[end2end_rdbSISSubscription getInstance] findAll];
        NSMutableArray *syncedPublications = [NSMutableArray arrayWithCapacity:2];
        
        // check all the synchronization group, if is synchronized, add to new sync group to synchronize
        if ([end2end_rdbEnd2end_rdbDB isSynchronized:@"synchronizationGroup"])
        {
            [syncedPublications addObject:@"synchronizationGroup"];
        }
        
        //retrieve  all local MBO from client database
        SUPObjectList* localBookList = [end2end_rdbLocalBook findAll];
        
        
        // Done with saving information, close connection and delete the database
        [end2end_rdbEnd2end_rdbDB closeConnection];
        [end2end_rdbEnd2end_rdbDB deleteDatabase];
        
        // new subscription
        [end2end_rdbEnd2end_rdbDB subscribe];
        
        // merge old local BO data to new database
        for (id lbo in localBookList)
        {
            end2end_rdbLocalBook *savedLocalBook = (end2end_rdbLocalBook*)lbo;
            end2end_rdbLocalBook *localBook = [end2end_rdbLocalBook getInstance];
            [localBook copyAll:savedLocalBook];
            [localBook create];
        }
        
        // add all the subscriptions from old database to new database
        for (id sub in customerWithParamSubscriptions)
        {
            end2end_rdbCustomerWithParamSubscription *csub = (end2end_rdbCustomerWithParamSubscription*)sub;
            [end2end_rdbCustomerWithParam addSubscription:csub];
        }
    
        for (id sub in sisSubscriptions)
        {
            end2end_rdbSISSubscription* ssub = (end2end_rdbSISSubscription*)sub;
            id<SUPSynchronizationGroup> sg = [end2end_rdbEnd2end_rdbDB getSynchronizationGroup:ssub.syncGroup];
            
            sg.enableSIS = ssub.enable;
            [sg save];
        }
    
        // synchronize for the synchronized publications
        NSString* syncGroups = [syncedPublications componentsJoinedByString:@","];
        [end2end_rdbEnd2end_rdbDB synchronize:syncGroups];
        
        // finally delete the backup recover database file
        if (![fm removeItemAtPath:recoverDbFile error:nil])
        {
             @throw [NSException exceptionWithName:@"NSException" reason:@"Failed to remove the recover database." userInfo:nil];
        }
    }
  3. Recover with local transactions - a complete way to recover. Both one and two above lose local transactions. If you do not want to lose the local transaction when encountering the SQLE_UPLOAD_FAILED_AT_SERVER exception, have the SAP Mobile Server Administrator remove the client remote id info, which can be found in the mlsrv_err.log, by calling the ml_delete_remote_id procedure in the CDB to remove the remote id. Then the user can continue to synchronize using the old database to upload all pending operations. Once uploaded, user\application must recreate the database using example one or two, and must not reuse the old database anymore. The mlsrv_err.log logs remote id error entries similar to this:
     I. 2013-04-14 14:13:39. <3> The sync sequence ID in the consolidated database:
            95bd47691098419cbf8539e8151bcf00; the remote previous sequence ID:
            95bd47691098419cbf8539e8151bcf97, and the current sequence ID:
            401be536e6e7417fb01b196276ec11c2E. 2013-04-14 14:13:39. <3> [-10400] Invalid sync sequence ID for remote ID
            'ed2ae448-a597-4f17-ad72-c6c61a6075a5'