Created
December 7, 2014 20:48
-
-
Save scionwest/589da62bb2614cd96b44 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // LBTagSync.m | |
| // Life Blog | |
| // | |
| // Created by Sully on 3/1/14. | |
| // Copyright (c) 2014 AllocateThis! Studios. All rights reserved. | |
| // | |
| #import "LBTagSync.h" | |
| #import "LBTagRepository.h" | |
| #import "EvernoteSDK.h" | |
| #import "NSDate+EDAMAdditions.h" | |
| #import "Tag.h" | |
| @interface LBTagSync () | |
| @property (strong, nonatomic) NSManagedObjectContext *managedContext; | |
| // Sync properties | |
| @property (nonatomic) NSInteger lastUpdateCount; | |
| @property (nonatomic) NSDate *lastSyncTime; | |
| @property (nonatomic) int currentChunkUSN; | |
| @property (strong, nonatomic) NSArray *selectedTagsForSync; | |
| @end | |
| @implementation LBTagSync | |
| #pragma mark - Property lazy instantiations. | |
| @synthesize lastUpdateCount = _lastUpdateCount; | |
| @synthesize lastSyncTime = _lastSyncTime; | |
| - (void)setLastUpdateCount:(NSInteger)lastUpdateCount { | |
| [[NSUserDefaults standardUserDefaults] setInteger:lastUpdateCount forKey:@"lastUpdateCount"]; | |
| [[NSUserDefaults standardUserDefaults] synchronize]; | |
| } | |
| - (NSInteger)lastUpdateCount { | |
| return [[NSUserDefaults standardUserDefaults] integerForKey:@"lastUpdateCount"]; | |
| } | |
| - (void)setLastSyncTime:(NSDate *)lastSyncTime { | |
| if (lastSyncTime) { | |
| [[NSUserDefaults standardUserDefaults] setObject:lastSyncTime forKey:@"lastSyncTime"]; | |
| } else { | |
| [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"lastSyncTime"]; | |
| } | |
| [[NSUserDefaults standardUserDefaults] synchronize]; | |
| } | |
| - (NSDate *)lastSyncTime { | |
| return (NSDate *)[[NSUserDefaults standardUserDefaults] objectForKey:@"lastSyncTime"]; | |
| } | |
| #pragma mark - Initializers | |
| - (id)initWithManagedContext:(NSManagedObjectContext *)context { | |
| self = [super init]; | |
| if (self) { | |
| self.managedContext = context; | |
| self.selectedTagsForSync = [[NSArray alloc] init]; | |
| } | |
| return self; | |
| } | |
| #pragma mark - Synchronization | |
| - (void)syncSelectedTags:(NSArray *)tags WithCompletion:(void (^)(NSArray *))completion failure:(void (^)(NSError *))failure { | |
| // Begin syncing tags. | |
| // Verify that the user is authenticated. If not, we return the error. | |
| if (![[EvernoteSession sharedSession] isAuthenticated]) { | |
| NSLog(@"Failed to sync. User is not authenticated."); | |
| NSDictionary *authenticationErrorInfo = @{ @"Failure" : @"Could not begin tag synchronization.", | |
| @"Reason" : @"The user is not currently authenticated with the Evernote server." }; | |
| NSError *authenticationError = [NSError errorWithDomain:@"com.AllocateThis.LifeBlog.LBTagRepository.syncWithCompletion" code:1 userInfo:authenticationErrorInfo]; | |
| failure(authenticationError); | |
| } else { | |
| // Convert our last sync time stamp to an Evernote time stamp. | |
| EDAMTimestamp lastSyncTime = [self.lastSyncTime enedamTimestamp]; | |
| // Cache the tag list that the user has selected for syncing. | |
| self.selectedTagsForSync = tags; | |
| NSLog(@"Saving any non-committed changes to Core Data."); | |
| NSError *error = nil; | |
| [self.managedContext save:&error]; | |
| // If saving failed, we perform our callback, set syncing to false and abort. | |
| if (error) { | |
| failure(error); | |
| } else { | |
| NSLog(@"Synchronization of Tags beginning."); | |
| // Get the current sync state from the server. | |
| [[EvernoteNoteStore noteStore] getSyncStateWithSuccess:^(EDAMSyncState *syncState) { | |
| // Check if our last time stamp is earlier than the required time to perform a full sync. | |
| // If we are good, we check if our update count is equal to the server's. If not, we update. | |
| if ((syncState.fullSyncBefore > lastSyncTime) || (syncState.updateCount > self.lastUpdateCount)) { | |
| NSLog(@"Parsing sync state."); | |
| // Fetch and cache all of the tags that have been modified or created since last sync. | |
| [self fetchUpdatedTagsFromServer:syncState withChunkUSN:(int)self.lastUpdateCount withCompletion:^(NSArray *serverTags, NSArray *expungedTags) { | |
| // Process all of the downloaded tags and push any new or changed tags back to the server. | |
| [self applyServerTags:serverTags andExpungedTags:expungedTags fromServerWithCompletion:^(NSArray *serverTags) { | |
| // We save the time stamp and last update count prior to pushing changes. | |
| // In the event that the push fails, we don't need to worry about pulling down the same tags again. | |
| // PushUpdatedTags: will update these values after each new/modified tag is pushed. | |
| self.lastSyncTime = [NSDate date]; | |
| self.lastUpdateCount = syncState.updateCount; | |
| [self pushUpdatedTags:serverTags toServerWithCompletion:^(NSArray *syncedTags) { | |
| NSLog(@"Synchronization completed. %lu tags in sync.", (unsigned long)[syncedTags count]); | |
| completion(syncedTags); | |
| } withFailure:^(NSError *error) { | |
| failure(error); | |
| }]; | |
| // processing tag (the actual sync process) failed. | |
| } withFailure:^(NSError *error) { | |
| NSLog(@"Failed to process tags during synchronization. Synchronization failed."); | |
| failure(error); | |
| }]; | |
| // fetching server tags failed. | |
| } failure:^(NSError *error) { | |
| NSLog(@"Failed to properly fetch updated tags from the server. Sync canceled."); | |
| failure(error); | |
| }]; | |
| } else { | |
| [[EvernoteNoteStore noteStore] listTagsWithSuccess:^(NSArray *tags) { | |
| [self pushUpdatedTags:tags toServerWithCompletion:^(NSArray *syncedTags) { | |
| NSLog(@"Synchronization completed. %lu tags in sync.", (unsigned long)[syncedTags count]); | |
| completion(syncedTags); | |
| } withFailure:^(NSError *error) { | |
| NSLog(@"Failed to push updated tags to the server. Sync process aborted."); | |
| failure(error); | |
| }]; | |
| } failure:^(NSError *error) { | |
| NSLog(@"Failed to obtain the current set of server tags. Sync process aborted."); | |
| failure(error); | |
| }]; | |
| // Push all of our local changes to the server. | |
| } | |
| } failure:^(NSError *error) { | |
| NSLog(@"Failed to obtain the current sync state. Sync process aborted."); | |
| failure(error); | |
| }]; | |
| } | |
| } | |
| } | |
| - (void)fetchUpdatedTagsFromServer:(EDAMSyncState *)syncState withChunkUSN:(int)chunkUSN | |
| withCompletion:(void(^)(NSArray *serverTags, NSArray *expungedTags))completion | |
| failure:(void(^)(NSError *error))failure { | |
| // If we have never updated, perform full sync. | |
| BOOL fullSync = NO; | |
| if (!self.lastUpdateCount) | |
| fullSync = YES; | |
| NSLog(@"Fetching new, modified or expunged tags from the server."); | |
| [[EvernoteNoteStore noteStore] getSyncChunkAfterUSN:chunkUSN maxEntries:200 fullSyncOnly:fullSync success:^(EDAMSyncChunk *syncChunk) { | |
| // Mutable array to cache our tags in. | |
| __block NSMutableArray *modifiedTagsCache = [[NSMutableArray alloc] init]; | |
| __block NSMutableArray *expungedTagsCache = [[NSMutableArray alloc] init]; | |
| NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@", @"zlifeblog_deleted"]; | |
| NSArray *deletedTags = [syncChunk.tags filteredArrayUsingPredicate:predicate]; | |
| [syncChunk.tags removeObjectsInArray:deletedTags]; | |
| // Cache changed server tags | |
| for (EDAMTag *tag in syncChunk.tags) { | |
| // Only sync tags that are specified in our sync list. | |
| if (!self.selectedTagsForSync || [self.selectedTagsForSync containsObject:tag.guid]) {// Can use instead of Guid due to tags being unique. | |
| [modifiedTagsCache addObject:tag]; | |
| } | |
| NSLog(@"Caching server tag %@", tag.name); | |
| } | |
| // Cached expunged server tags. | |
| NSLog(@"Caching %ld expunged Tag guid's.", (unsigned long)[syncChunk.expungedTags count]); | |
| for (NSString *expungedTag in syncChunk.expungedTags) { | |
| [expungedTagsCache addObject:expungedTag]; | |
| } | |
| // We have finished caching all of the new, modified or expunged tags. | |
| // Now we need to see if we have processed all of the server changes or if there are more to send us. | |
| if (syncChunk.chunkHighUSN < syncChunk.updateCount) { | |
| // There are more updates, so let's process the next batch from the server. | |
| [self fetchUpdatedTagsFromServer:syncState withChunkUSN:syncChunk.chunkHighUSN withCompletion:^(NSArray *cachedTags, NSArray *expungedTags) { | |
| // Insert the results from our nested invocation into our current tags collection. | |
| [modifiedTagsCache addObjectsFromArray:cachedTags]; | |
| [expungedTagsCache addObjectsFromArray:expungedTags]; | |
| // Callback with the joined array. | |
| completion([modifiedTagsCache copy], [expungedTagsCache copy]); | |
| } failure:^(NSError *error) { | |
| NSLog(@"Failed to properly fetch updated tags from the server. Sync canceled."); | |
| failure(error); | |
| }]; | |
| } else { | |
| // All good to go, everything is cached and there are no more on the server. | |
| completion([modifiedTagsCache copy], [expungedTagsCache copy]); | |
| } | |
| } failure:^(NSError *error) { | |
| NSLog(@"Caching server tags failed."); | |
| failure(error); | |
| }]; | |
| } | |
| - (void)applyServerTags:(NSArray *)serverTags andExpungedTags:(NSArray *)expungedTags fromServerWithCompletion:(void(^)(NSArray *syncedTags))completion withFailure:(void(^)(NSError *error))failure { | |
| NSLog(@"Fetching tags completed."); | |
| [self saveNewTags:serverTags]; | |
| [self updateExistingTags:serverTags]; | |
| [self renameExistingTags:serverTags]; | |
| [self expungeTags:expungedTags]; | |
| // Save our local changes. | |
| NSError *saveError = nil; | |
| //if ([self.managedContext.updatedObjects count]) { | |
| [self.managedContext save:&saveError]; | |
| //} | |
| if (saveError) { | |
| NSLog(@"Failed to save newly cached tags."); | |
| failure(saveError); | |
| } else { | |
| LBTagRepository *repository = [[LBTagRepository alloc] initWithManagedContext:self.managedContext]; | |
| completion([repository fetchAll]); | |
| } | |
| } | |
| - (void)pushUpdatedTags:(NSArray *)serverTags toServerWithCompletion:(void(^)(NSArray *syncedTags))completion withFailure:(void(^)(NSError *error))failure { | |
| [self pushNewTags:serverTags withCompletion:^{ | |
| //[self pushUpdatedTags:serverTags toServerWithCompletion:^(NSArray *syncedTags) { | |
| [self pushLocallyExpungedTags:serverTags withCompletion:^{ | |
| LBTagRepository *repository = [[LBTagRepository alloc] initWithManagedContext:self.managedContext]; | |
| NSError *saveError = nil; | |
| if ([self.managedContext.updatedObjects count]) { | |
| [self.managedContext save:&saveError]; | |
| } | |
| if (saveError) { | |
| NSLog(@"Failed to save newly cached tags."); | |
| failure(saveError); | |
| } else { | |
| completion([repository fetchAll]); | |
| } | |
| } withFailure:^(NSError *error) { | |
| failure(error); // expunge tags. | |
| }]; | |
| //} withFailure:^(NSError *error) { | |
| // failure(error); // updatedTags. | |
| //}]; | |
| } withFailure:^(NSError *error) { | |
| failure(error); // newTags. | |
| }]; | |
| } | |
| #pragma mark - Sync Server tags to Local Tags | |
| - (void)saveNewTags:(NSArray *)serverTags { | |
| LBTagRepository *repository = [[LBTagRepository alloc] initWithManagedContext:self.managedContext]; | |
| int numberOfNewTags = 0; | |
| for (EDAMTag *serverTag in serverTags) { | |
| // Make sure it is not a tag we already deleted. | |
| NSRange rangeOfName = [serverTag.name rangeOfString:@"zlifeblog_deleted_"]; | |
| if (rangeOfName.location != NSNotFound) { | |
| continue; // Move to next item. | |
| } | |
| NSPredicate *filter = [NSPredicate predicateWithFormat:@"name like %@ AND guid like %@", serverTag.name, serverTag.guid]; | |
| NSArray *array = [repository fetchTagsWithPredicate:filter]; | |
| // If we do not have a tag matching the name and guid of this server tag, we know it is a new tag. | |
| if (![array firstObject]) { | |
| [repository createNewTagFromEvernoteTag:serverTag andAutomaticallySave:YES]; | |
| numberOfNewTags++; | |
| NSLog(@"%@ tag created in Core Data.", serverTag.name); | |
| } | |
| } | |
| } | |
| - (void)updateExistingTags:(NSArray *)serverTags { | |
| LBTagRepository *repository = [[LBTagRepository alloc] initWithManagedContext:self.managedContext]; | |
| int numberOfUpdates = 0; | |
| for (EDAMTag *serverTag in serverTags) { | |
| NSPredicate *filter = [NSPredicate predicateWithFormat:@"name like %@ AND guid like %@", serverTag.name, serverTag.guid]; | |
| Tag *localTag = [repository fetchTagWithPredicate:filter]; | |
| // If we have a local tag, we need to determine if it was edited on the serve side. | |
| if (localTag) { | |
| // We only update our existing tags. If the local tag is newer, we ignore it. | |
| // The server updates will happen in another method, later on during the sync process. | |
| if ([localTag.updateSequenceNum intValue] < serverTag.updateSequenceNum) { | |
| [repository convertEvernoteTag:serverTag toManagedTag:localTag convertingOnlyChangedProperties:YES]; | |
| numberOfUpdates++; | |
| NSLog(@"%@ tag updated in Core Data.", serverTag.name); | |
| } | |
| } | |
| } | |
| } | |
| - (void)renameExistingTags:(NSArray *)serverTags { | |
| LBTagRepository *repository = [[LBTagRepository alloc] initWithManagedContext:self.managedContext]; | |
| for (EDAMTag *serverTag in serverTags) { | |
| NSPredicate *filter = [NSPredicate predicateWithFormat:@"guid like %@ AND name != %@", serverTag.guid, serverTag.name]; | |
| Tag *localTag = [repository fetchTagWithPredicate:filter]; | |
| // If we have a local tag, the tag was renamed on the server side. | |
| if (localTag) { | |
| NSLog(@"%@ renamed to %@ in Core Data.", localTag.name, serverTag.name); | |
| [repository convertManagedTag:localTag toEvernoteTag:serverTag convertingOnlyChangedProperties:YES]; | |
| } | |
| } | |
| } | |
| - (void)expungeTags:(NSArray *)expungedTags { | |
| LBTagRepository *repository = [[LBTagRepository alloc] initWithManagedContext:self.managedContext]; | |
| for (NSString *expungedTag in expungedTags) { | |
| NSPredicate *filter = [NSPredicate predicateWithFormat:@"guid like %@", expungedTag]; | |
| Tag *localTag = [repository fetchTagWithPredicate:filter]; | |
| if (localTag) { | |
| NSLog(@"%@ deleted in Core Data.", localTag.name); | |
| [repository deleteTag:localTag]; | |
| } | |
| } | |
| } | |
| #pragma mark - Sync local tags to the Server | |
| - (void)pushNewTags:(NSArray *)serverTags withCompletion:(void(^)())completion withFailure:(void(^)(NSError *error))failure { | |
| LBTagRepository *repository = [[LBTagRepository alloc] initWithManagedContext:self.managedContext]; | |
| NSArray *localTags = [repository fetchAll]; | |
| NSOperationQueue *queue = [[NSOperationQueue alloc] init]; | |
| queue.maxConcurrentOperationCount = 4; | |
| NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ | |
| completion(); | |
| }]; | |
| for (Tag *localTag in localTags) { | |
| NSPredicate *filter = [NSPredicate predicateWithFormat:@"name like %@ AND guid like %@", localTag.name, localTag.guid]; | |
| NSArray *array = [serverTags filteredArrayUsingPredicate:filter]; | |
| // If we do not have a tag matching the name and guid of this server tag, we know it is a new tag. | |
| if (![array firstObject]) { | |
| EDAMTag *serverTag = [[EDAMTag alloc] initWithGuid:nil name:localTag.name parentGuid:nil updateSequenceNum:0]; | |
| NSLog(@"Pushing new %@ tag to the server.", localTag.name); | |
| NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ | |
| dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); | |
| [[EvernoteNoteStore noteStore] createTag:serverTag success:^(EDAMTag *tag) { | |
| NSLog(@"Upload of %@ tag (%@) completed.", serverTag.name, [localTag description]); | |
| // We force a complete update to all changed properties on the local tag to match the server's. | |
| [repository convertEvernoteTag:tag toManagedTag:localTag convertingOnlyChangedProperties:YES]; | |
| self.lastSyncTime = [NSDate date]; | |
| self.lastUpdateCount = tag.updateSequenceNum; | |
| NSLog(@"Server Update Count: %d", serverTag.updateSequenceNum); | |
| NSLog(@"Client Update Cound: %@", [localTag description]); | |
| dispatch_semaphore_signal(semaphore); | |
| } failure:^(NSError *error) { | |
| NSLog(@"Failed to upload %@ tag.", serverTag.name); | |
| dispatch_semaphore_signal(semaphore); | |
| [queue cancelAllOperations]; | |
| failure(error); | |
| }]; | |
| dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); | |
| }]; | |
| [completionOperation addDependency:operation]; | |
| [queue addOperation:operation]; | |
| } | |
| } | |
| [queue addOperation:completionOperation]; | |
| } | |
| - (void)pushUpdatesToTags:(NSArray *)serverTags withCompletion:(void(^)())completion withFailure:(void(^)(NSError *error))failure { | |
| LBTagRepository *repository = [[LBTagRepository alloc] initWithManagedContext:self.managedContext]; | |
| NSArray *localTags = [repository fetchAll]; | |
| NSOperationQueue *queue = [[NSOperationQueue alloc] init]; | |
| queue.maxConcurrentOperationCount = 4; | |
| NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ | |
| completion(); | |
| }]; | |
| for (Tag *localTag in localTags) { | |
| NSPredicate *filter = [NSPredicate predicateWithFormat:@"name like %@ AND guid like %@", localTag.name, localTag.guid]; | |
| NSArray *array = [serverTags filteredArrayUsingPredicate:filter]; | |
| // If we found a matching server tag, we check if the server's version is outdated. If so, we updated it. | |
| if ([array firstObject]) { | |
| EDAMTag *serverTag = [repository convertManagedTag:localTag toEvernoteTag:[array firstObject] convertingOnlyChangedProperties:YES]; | |
| NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ | |
| dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); | |
| [[EvernoteNoteStore noteStore] updateTag:serverTag success:^(int32_t usn) { | |
| NSLog(@"Uploaded an updated version of %@ tag.", serverTag.name); | |
| [repository convertEvernoteTag:serverTag toManagedTag:localTag convertingOnlyChangedProperties:YES]; | |
| self.lastSyncTime = [NSDate date]; | |
| self.lastUpdateCount = usn; | |
| dispatch_semaphore_signal(semaphore); | |
| } failure:^(NSError *error) { | |
| }]; | |
| [[EvernoteNoteStore noteStore] createTag:serverTag success:^(EDAMTag *tag) { | |
| NSLog(@"Upload of %@ tag completed.", serverTag.name); | |
| // We force a complete update to all changed properties on the local tag to match the server's. | |
| [repository convertEvernoteTag:tag toManagedTag:localTag convertingOnlyChangedProperties:YES]; | |
| dispatch_semaphore_signal(semaphore); | |
| } failure:^(NSError *error) { | |
| NSLog(@"Failed to upload %@ tag.", serverTag.name); | |
| dispatch_semaphore_signal(semaphore); | |
| [queue cancelAllOperations]; | |
| failure(error); | |
| }]; | |
| dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); | |
| }]; | |
| [completionOperation addDependency:operation]; | |
| [queue addOperation:operation]; | |
| } | |
| } | |
| [queue addOperation:completionOperation]; | |
| } | |
| - (void)pushLocallyExpungedTags:(NSArray *)serverTags withCompletion:(void(^)())completion withFailure:(void(^)(NSError *error))failure { | |
| LBTagRepository *repository = [[LBTagRepository alloc] initWithManagedContext:self.managedContext]; | |
| NSPredicate *expungedFilter = [NSPredicate predicateWithFormat:@"expunged == YES"]; | |
| NSArray *localTags = [[repository fetchAllIncludingExpunged:YES] filteredArrayUsingPredicate:expungedFilter]; | |
| NSOperationQueue *queue = [[NSOperationQueue alloc] init]; | |
| queue.maxConcurrentOperationCount = 4; | |
| __block NSMutableArray *queuedLocalTagsToDelete = [[NSMutableArray alloc] init]; | |
| __block bool hasError = NO; | |
| NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ | |
| for (Tag *localTag in queuedLocalTagsToDelete) { | |
| NSLog(@"Deleting local %@ tag", localTag.name); | |
| [self.managedContext deleteObject:localTag]; | |
| } | |
| // Save our deletions. | |
| NSError *error = nil; | |
| [self.managedContext save:&error]; | |
| if (error || hasError) { | |
| failure(error); | |
| } else { | |
| completion(); | |
| } | |
| }]; | |
| for (EDAMTag *st in serverTags) { | |
| NSLog(@"Server Tag - %@", st.name); | |
| } | |
| for (Tag *localTag in localTags) { | |
| // We abort the rest if we already have a itteration that failed. | |
| if (hasError) { | |
| break; | |
| } | |
| NSLog(@"%@", [localTag description]); | |
| NSPredicate *serverFilter = [NSPredicate predicateWithFormat:@"guid == %@", localTag.guid]; | |
| if ([[serverTags filteredArrayUsingPredicate:serverFilter] firstObject]) { | |
| EDAMTag *serverTag = [[serverTags filteredArrayUsingPredicate:serverFilter] firstObject]; | |
| int number = 0; | |
| NSPredicate *renamedTagPredicate = nil; | |
| do { | |
| NSString *newName = [NSString stringWithFormat:@"zlifeblog_deleted_%@_%d", serverTag.name, number]; | |
| serverTag.name = newName; | |
| renamedTagPredicate = [NSPredicate predicateWithFormat:@"name != %@", serverTag.name]; | |
| } while (![[serverTags filteredArrayUsingPredicate:renamedTagPredicate] firstObject]); | |
| NSLog(@"Pushing deletion of %@ tag to the server.", localTag.name); | |
| NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ | |
| dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); | |
| [[EvernoteNoteStore noteStore] updateTag:serverTag success:^(int32_t usn) { | |
| self.lastSyncTime = [NSDate date]; | |
| self.lastUpdateCount = usn; | |
| NSLog(@"%@ tag flagged as deleted on server.", serverTag.name); | |
| // Queue this local tag for deletion. We do not keep local copies of expunged tags. | |
| [queuedLocalTagsToDelete addObject:localTag]; | |
| dispatch_semaphore_signal(semaphore); | |
| } failure:^(NSError *error) { | |
| NSLog(@"Failed to flag %@ tag as deleted on the server.", serverTag.name); | |
| hasError = YES; | |
| dispatch_semaphore_signal(semaphore); | |
| return; | |
| }]; | |
| dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); | |
| }]; | |
| [completionOperation addDependency:operation]; | |
| [queue addOperation:operation]; | |
| } | |
| } | |
| [queue addOperation:completionOperation]; | |
| } | |
| @end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment