Client Application RBS BeginSynchronize Recovery Example

BeginSynchronize is an asynchronous pattern, requiring the user to override the onSynchronize method of the SUPDefaultCallbackHandler class to check for the SQLE_UPLOAD_FAILED_AT_SERVER error.

The user has the same three methods as mentioned in the Client Application RBS Synchronize Recovery Example to recover the client database. Sample code uses the second method and implements the AsyncCallbackHandler:
if([AsyncCallbackHandler isRecoverFailed])
    {
        [AsyncCallbackHandler recoverClientDatabase];
    }
    else
    {
        @try
        {
            SUPObjectList* syncList = [SUPObjectList getInstance];
            [syncList add:@"default"];
            [self synchronize:syncList];
        }
        @catch (SUPPersistenceException *ex)
        {
            NSLog(@"sync failed(%d): %@", ex.errorCode, ex.reason);
        }
    }

- (void) synchronize: (SUPObjectList*) syncGroups
{
    AsyncCallbackHandler* callback = [[[AsyncCallbackHandler alloc] init] autorelease];
    SUPObjectList* sgs = [SUPObjectList getInstance];
    for (NSString* sg in syncGroups) {
        [sgs add:[end2end_rdbEnd2end_rdbDB getSynchronizationGroup:sg]];
    }
    
    callback.userContext = @"fistBeginSync";
    [end2end_rdbEnd2end_rdbDB registerCallbackHandler:callback];
    [end2end_rdbEnd2end_rdbDB beginSynchronize:sgs withContext:callback.userContext];
    
    int waitCount = 0;
    while (![callback syncDone])
    {
        if (waitCount++ > maxWaitTime)
        {
            NSString *msg = [NSString stringWithFormat:@"No response returned from server after waiting %d seconds", maxWaitTime];
            @throw [NSException exceptionWithName:@"NSException" reason:msg userInfo:nil];
        }
        sleep(1);
    }
    
    callback.recoveryInProgress = NO;
    callback.recoveryDone = NO;
    callback.syncDone = NO;
    callback.userContext = nil;
    
    if ([callback errorMessage] != nil)
    {
        NSString* errMessage = callback.errorMessage;
        callback.errorMessage = nil;
        @throw [NSException exceptionWithName:@"NSException" reason:errMessage userInfo:nil];
    }
}


// AsyncCallbackHandler.h
#import "SUPDefaultCallbackHandler.h"

@interface AsyncCallbackHandler : SUPDefaultCallbackHandler
{

}

@property(readwrite, nonatomic, assign) BOOL recoveryInProgress;
@property(readwrite, nonatomic, assign) BOOL recoveryDone;
@property(readwrite, nonatomic, assign) BOOL syncDone;
@property(readwrite, nonatomic, retain) NSString* userContext;
@property(readwrite, nonatomic, retain) NSString* errorMessage;

+ (BOOL)isRecoverFailed;
+ (void)recoverClientDatabase;

@end

// AsyncCallbackHandler.m
#import "AsyncCallbackHandler.h"
#import "end2end_rdbEnd2end_rdbDB.h"
#import "end2end_rdbCustomerWithParam.h"
#import "end2end_rdbSISSubscription.h"
#import "end2end_rdbLocalBook.h"

@implementation AsyncCallbackHandler

@synthesize recoveryInProgress;
@synthesize recoveryDone;
@synthesize syncDone;
@synthesize userContext;
@synthesize errorMessage;

- (id) init
{
    [super init];
    recoveryInProgress = NO;
    recoveryDone = NO;
    userContext = nil;
    errorMessage = nil;
    return self;
}


- (SUPSynchronizationActionType)onSynchronize:(SUPObjectList*)syncGroupList withContext:(SUPSynchronizationContext*)context
{
    NSException *ex = context.exception;
    if ((ex != nil) && [ex isKindOfClass:[SUPPersistenceException class]])
    {
        SUPPersistenceException *pe = (SUPPersistenceException*)ex;
        if (pe.errorCode == SYNC_UPLOAD_FAILED_AT_SERVER)
        {
            if (!recoveryInProgress)
            {
                recoveryInProgress = YES;
                recoveryDone = NO;
                dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
                dispatch_async(queue, ^{
                    @try {
                        [AsyncCallbackHandler recoverClientDatabase];
                    }
                    @catch (NSException *exception) {
                        errorMessage = exception.reason;
                    }
                    recoveryDone = YES;
                    recoveryInProgress = NO;
                    syncDone = YES;
                });
            }
        }
        
        return SUPSynchronizationAction_CONTINUE;
    }
    
    if (context.userContext != nil)
    {
        if ([context.userContext isKindOfClass:[NSString class]])
        {
            NSString *userCtx = (NSString*)context.userContext;
            if ([userCtx isEqualToString:@"firstBeginSync"])
            {
                if (context.status == SUPSynchronizationStatus_FINISHING || context.status == SUPSynchronizationStatus_ERROR)
                {
                    syncDone = YES;
                }
            }
        }
    }
    
    return SUPSynchronizationAction_CONTINUE;
}

+ (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 move back the DB file and do recovery.
        if ([fm isReadableFileAtPath:dbFile])
        {
            if (![fm removeItemAtPath:dbFile error:nil])
            {
                ok = NO;
            }
        }
        
        if (ok && [fm moveItemAtPath: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 BO 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];
    }

}

@end