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.
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