Skip to content

Commit

Permalink
iCloud sync when eventual consistency takes a long time
Browse files Browse the repository at this point in the history
- When creating a host, it may disappear immediately even if the host was
synchronized successfully to iCloud. This was because the data may take longer
than usual to then show up in queries. This way before we clear up local data,
we check what the timestamp is for that scenario and give some margin to become
consistent on the other side.
  • Loading branch information
Carlos Cabanero committed Nov 17, 2023
1 parent cb37c2e commit c8c8209
Showing 1 changed file with 30 additions and 12 deletions.
42 changes: 30 additions & 12 deletions Settings/Network/BKiCloudSyncHandler.m
Expand Up @@ -143,18 +143,19 @@ - (void)checkForReachabilityAndSync:(NSNotification *)notification
}
}

- (void)deleteAllItems
- (void)deleteSyncScheduledItems
{

NSMutableArray *deletedHosts = [NSMutableArray arrayWithArray:[syncItems objectForKey:BKiCloudSyncDeletedHosts]];
[syncItems removeObjectForKey:BKiCloudSyncDeletedHosts];
for (CKRecordID *recordId in deletedHosts) {
[self deleteRecord:recordId ofType:BKiCloudRecordTypeHosts];
}
[syncItems removeObjectForKey:BKiCloudSyncDeletedHosts];
}

- (void)syncFromiCloud
{
[self deleteAllItems];
[self deleteSyncScheduledItems];
CKDatabase *database = [[CKContainer containerWithIdentifier:BKiCloudContainerIdentifier] privateCloudDatabase];
CKQuery *hostQuery = [[CKQuery alloc] initWithRecordType:@"BKHost" predicate:[NSPredicate predicateWithValue:YES]];
[database performQuery:hostQuery
Expand All @@ -174,18 +175,23 @@ - (void)deleteRecord:(CKRecordID *)recordId ofType:(BKiCloudRecordType)recordTyp
if (recordType == BKiCloudRecordTypeHosts) {
key = BKiCloudSyncDeletedHosts;
}

NSLog(@"Host %@ scheduled for deletion", recordId);
CKDatabase *database = [[CKContainer containerWithIdentifier:BKiCloudContainerIdentifier] privateCloudDatabase];
[database deleteRecordWithID:recordId
completionHandler:^(CKRecordID *_Nullable recordID, NSError *_Nullable error) {
if (error) {
NSLog(@"Deletion failed. Syncing later %@", recordId);

NSMutableArray *deletedItems = [NSMutableArray array];
if ([syncItems objectForKey:key]) {
deletedItems = [NSMutableArray arrayWithArray:[syncItems objectForKey:key]];
}
[deletedItems addObject:recordId];
[syncItems setObject:deletedItems forKey:key];
}
[BKiCloudSyncHandler saveSyncItems];
} else {
NSLog(@"Deleted %@", recordId);
}
}];
}

Expand All @@ -195,9 +201,15 @@ - (void)createNewHost:(BKHosts *)host
{
CKDatabase *database = [[CKContainer containerWithIdentifier:BKiCloudContainerIdentifier] privateCloudDatabase];
CKRecord *hostRecord = [BKHosts recordFromHost:host];
NSLog(@"Host %@ scheduled for creation", host.host);
[database saveRecord:hostRecord
completionHandler:^(CKRecord *_Nullable record, NSError *_Nullable error) {
[BKHosts updateHost:host.host withiCloudId:record.recordID andLastModifiedTime:record.modificationDate];
if (error == nil) {
NSLog(@"Created host %@, record %@", host.host, record.recordID);
[BKHosts updateHost:host.host withiCloudId:record.recordID andLastModifiedTime:record.modificationDate];
} else {
NSLog(@"Host %@ upload to iCloud failed: %@", host.host, error);
}
}];
}

Expand All @@ -208,10 +220,10 @@ - (void)mergeHosts:(NSArray *)hostRecords
if ([hostRecord valueForKey:@"host"]) {
NSString *host = [hostRecord valueForKey:@"host"];
BKHosts *hosts = [BKHosts withiCloudId:hostRecord.recordID];
//If host exists in system, Find which is new
//If host exists in system, Find which is newer
if (hosts) {
if ([hosts.lastModifiedTime compare:hostRecord.modificationDate] == NSOrderedDescending) {
//Local is new...Update iCloud to Local values
//Local is newer...Update iCloud to Local values
CKDatabase *database = [[CKContainer containerWithIdentifier:BKiCloudContainerIdentifier] privateCloudDatabase];
CKRecord *udpatedRecord = [BKHosts recordFromHost:hosts];
CKModifyRecordsOperation *updateOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:@[ udpatedRecord ] recordIDsToDelete:nil];
Expand All @@ -223,12 +235,12 @@ - (void)mergeHosts:(NSArray *)hostRecords
};
[database addOperation:updateOperation];
} else {
//iCloud is new, update local to reflect iCLoud values
//iCloud is newer, update local to reflect iCLoud values
[self saveHostRecord:hostRecord withHost:host];
}
} else {
//If hosts is new, see if it exists
//Check if name exists, if YES, Mark as conflict else, add to local
//Check if iCloud host exists locally by name. If YES, Mark as conflict. Otherwise store local copy.
BKHosts *existingHost = [BKHosts withHost:host];
if (existingHost) {
[BKHosts markHost:host forRecord:hostRecord withConflict:YES];
Expand All @@ -244,19 +256,25 @@ - (void)mergeHosts:(NSArray *)hostRecords
if (hosts.iCloudRecordId == nil && (!hosts.iCloudConflictDetected || !hosts.iCloudConflictDetected.boolValue)) {
[self createNewHost:hosts];
} else {
NSLog(@"Conflict detected Hence not saving to iCloud");
//Find items deleted from iCloud
if ((!hosts.iCloudConflictDetected || !hosts.iCloudConflictDetected.boolValue)) {
NSPredicate *deletedPredicate = [NSPredicate predicateWithFormat:@"SELF.recordID.recordName contains %@", hosts.iCloudRecordId.recordName];
NSArray *filteredAray = [hostRecords filteredArrayUsingPredicate:deletedPredicate];
if (filteredAray.count <= 0) {
NSTimeInterval timeDifference = [[NSDate date] timeIntervalSinceDate:hosts.lastModifiedTime];
// The DB is eventually consistent, so we cannot mark a host for deletion that may have just been created.
if (fabs(timeDifference) > 60.0 && filteredAray.count <= 0) {
NSLog(@"Host not found on iCloud, scheduled for deletion: %@, record: %@", hosts, hosts.iCloudRecordId);
[itemsDeletedFromiCloud addObject:hosts];
}
} else {
NSLog(@"Conflict detected Hence not saving to iCloud");
continue;
}
}
}
if (itemsDeletedFromiCloud.count > 0) {
[[BKHosts all] removeObjectsInArray:itemsDeletedFromiCloud];
[BKHosts saveHosts];
}


Expand Down

0 comments on commit c8c8209

Please sign in to comment.