From c57bd5d5d3c13dfd6b5facfc9047446169376722 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sat, 23 Aug 2014 01:39:47 -0400 Subject: [PATCH] A few fixes to site import/export. [FIXED] A few core data issues. [ADDED] Avatar to export file. [ADDED] Generated site counter to export file. [ADDED] Site login name to export file. --- External/UbiquityStoreManager | 2 +- MasterPassword/ObjC/MPAppDelegate_Store.m | 150 +++++++++++++----- MasterPassword/ObjC/MPEntities.m | 2 +- .../ObjC/iOS/MPPasswordsViewController.h | 1 - .../ObjC/iOS/MPPasswordsViewController.m | 48 +++--- .../ObjC/iOS/MPUsersViewController.m | 26 ++- MasterPassword/ObjC/iOS/MPiOSAppDelegate.m | 9 +- MasterPassword/ObjC/iOS/Storyboard.storyboard | 8 +- 8 files changed, 165 insertions(+), 81 deletions(-) diff --git a/External/UbiquityStoreManager b/External/UbiquityStoreManager index 5e38f25f..99dcbcce 160000 --- a/External/UbiquityStoreManager +++ b/External/UbiquityStoreManager @@ -1 +1 @@ -Subproject commit 5e38f25f6e058cc52b8e41bcdc183b7966bb3ac7 +Subproject commit 99dcbccee742d2bd2e5701f6b4e001138956030a diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index ede27300..95b42ebe 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -133,14 +133,14 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext #if TARGET_OS_IPHONE [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { - [[self mainManagedObjectContext] saveToStore]; - }]; + ^(NSNotification *note) { + [[self mainManagedObjectContext] saveToStore]; + }]; [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { - [[self mainManagedObjectContext] saveToStore]; - }]; + ^(NSNotification *note) { + [[self mainManagedObjectContext] saveToStore]; + }]; #else [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp queue:[NSOperationQueue mainQueue] usingBlock: @@ -405,11 +405,11 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext self.saveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:privateManagedObjectContext queue:nil usingBlock: ^(NSNotification *note) { - // When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext. - [mainManagedObjectContext performBlock:^{ - [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note]; - }]; - }]; + // When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext. + [mainManagedObjectContext performBlock:^{ + [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note]; + }]; + }]; self.privateManagedObjectContext = privateManagedObjectContext; self.mainManagedObjectContext = mainManagedObjectContext; @@ -532,7 +532,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext saveInContext:(NSManagedObjectContext *)context { // Compile patterns. - static NSRegularExpression *headerPattern, *sitePattern; + static NSRegularExpression *headerPattern; + static NSArray *sitePatterns; NSError *error = nil; if (!headerPattern) { headerPattern = [[NSRegularExpression alloc] @@ -543,12 +544,17 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext return MPImportResultInternalError; } } - if (!sitePattern) { - sitePattern = [[NSRegularExpression alloc] - initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)(:[[:digit:]]+)?[[:space:]]+([^\t]+)\t(.*)" - options:(NSRegularExpressionOptions)0 error:&error]; + if (!sitePatterns) { + sitePatterns = @[ + [[NSRegularExpression alloc] // Format 0 + initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)" + options:(NSRegularExpressionOptions)0 error:&error], + [[NSRegularExpression alloc] // Format 1 + initWithPattern:@"^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)" + options:(NSRegularExpressionOptions)0 error:&error] + ]; if (error) { - err( @"Error loading the site pattern: %@", error ); + err( @"Error loading the site patterns: %@", error ); return MPImportResultInternalError; } } @@ -557,6 +563,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext inf( @"Importing sites." ); __block MPUserEntity *user = nil; id importAlgorithm = nil; + NSUInteger importFormat = 0; + NSUInteger importAvatar = NSNotFound; NSString *importBundleVersion = nil, *importUserName = nil; NSData *importKeyID = nil; BOOL headerStarted = NO, headerEnded = NO, clearText = NO; @@ -604,8 +612,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext return MPImportResultInternalError; } - user = [users count]? [users lastObject]: nil; - dbg( @"Found user: %@", [user debugDescription] ); + user = [users lastObject]; + dbg( @"Existing user? %@", [user debugDescription] ); } if ([headerName isEqualToString:@"Key ID"]) importKeyID = [headerValue decodeHex]; @@ -613,6 +621,15 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext importBundleVersion = headerValue; importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion ); } + if ([headerName isEqualToString:@"Format"]) { + importFormat = [headerValue integerValue]; + if (importFormat >= [sitePatterns count]) { + err( @"Unsupported import format: %d", importFormat ); + return MPImportResultInternalError; + } + } + if ([headerName isEqualToString:@"Avatar"]) + importAvatar = [headerValue integerValue]; if ([headerName isEqualToString:@"Passwords"]) { if ([headerValue isEqualToString:@"VISIBLE"]) clearText = YES; @@ -628,6 +645,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext continue; // Site + NSRegularExpression *sitePattern = sitePatterns[importFormat]; if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0 range:NSMakeRange( 0, [importedSiteLine length] )] != 1) { err( @"Invalid site format in line: %@", importedSiteLine ); @@ -635,21 +653,45 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext } NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0 range:NSMakeRange( 0, [importedSiteLine length] )] lastObject]; - NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; - NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; - NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; - NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; - if ([version length]) - version = [version substringFromIndex:1]; // Strip the leading colon. - NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; - NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]]; + NSString *lastUsed, *uses, *type, *version, *counter, *siteName, *loginName, *exportContent; + switch (importFormat) { + case 0: + lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; + uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; + type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; + version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; + if ([version length]) + version = [version substringFromIndex:1]; // Strip the leading colon. + counter = @""; + loginName = @""; + siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; + exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]]; + break; + case 1: + lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; + uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; + type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; + version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; + if ([version length]) + version = [version substringFromIndex:1]; // Strip the leading colon. + counter = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; + if ([counter length]) + counter = [counter substringFromIndex:1]; // Strip the leading colon. + loginName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]]; + siteName = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:7]]; + exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:8]]; + break; + default: + err( @"Unexpected import format: %d", importFormat ); + return MPImportResultInternalError; + } // Find existing site. if (user) { - elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user]; + elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user]; NSArray *existingSites = [context executeFetchRequest:elementFetchRequest error:&error]; if (!existingSites) { - err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error ); + err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", siteName, user.userID, error ); return MPImportResultInternalError; } if ([existingSites count]) { @@ -657,14 +699,14 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext [elementsToDelete addObjectsFromArray:existingSites]; } } - [importedSiteElements addObject:@[ lastUsed, uses, type, version, name, exportContent ]]; - dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, name=%@, exportContent=%@", - lastUsed, uses, type, version, name, exportContent ); + [importedSiteElements addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]]; + dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@", + lastUsed, uses, type, version, counter, loginName, siteName, exportContent ); } // Ask for confirmation to import these sites and the master password of the user. inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count], - (unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName] ); + (unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName] ); NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteElements count], [elementsToDelete count] ); if (!userMasterPassword) { @@ -689,10 +731,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext }]; // Make sure there is a user. - if (!user) { + if (user) { + if (importAvatar != NSNotFound) + user.avatar = importAvatar; + dbg( @"Updating User: %@", [user debugDescription] ); + } else { user = [MPUserEntity insertNewObjectInContext:context]; user.name = importUserName; user.keyID = importKeyID; + if (importAvatar != NSNotFound) + user.avatar = importAvatar; dbg( @"Created User: %@", [user debugDescription] ); } @@ -702,13 +750,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext NSUInteger uses = (unsigned)[siteElements[1] integerValue]; MPElementType type = (MPElementType)[siteElements[2] integerValue]; NSUInteger version = (unsigned)[siteElements[3] integerValue]; - NSString *name = siteElements[4]; - NSString *exportContent = siteElements[5]; + NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound; + NSString *loginName = [siteElements[5] length]? siteElements[5]: nil; + NSString *siteName = siteElements[6]; + NSString *exportContent = siteElements[7]; // Create new site. NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type]; MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context]; - element.name = name; + element.name = siteName; + element.loginName = loginName; element.user = user; element.type = type; element.uses = uses; @@ -720,6 +771,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext else [element.algorithm importProtectedContent:exportContent protectedByKey:importKey intoElement:element usingKey:userKey]; } + if ([element isKindOfClass:[MPElementGeneratedEntity class]] && counter != NSNotFound) + ((MPElementGeneratedEntity *)element).counter = counter; dbg( @"Created Element: %@", [element debugDescription] ); } @@ -751,18 +804,20 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext [export appendFormat:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"]; [export appendFormat:@"# \n"]; [export appendFormat:@"##\n"]; - [export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion]; [export appendFormat:@"# User Name: %@\n", activeUser.name]; + [export appendFormat:@"# Avatar: %d\n", activeUser.avatar]; [export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]]; [export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]]; + [export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion]; + [export appendFormat:@"# Format: 1\n"]; if (revealPasswords) [export appendFormat:@"# Passwords: VISIBLE\n"]; else [export appendFormat:@"# Passwords: PROTECTED\n"]; [export appendFormat:@"##\n"]; [export appendFormat:@"#\n"]; - [export appendFormat:@"# Last Times Password Site\tSite\n"]; - [export appendFormat:@"# used used type name\tpassword\n"]; + [export appendFormat:@"# Last Times Password Login\t Site\tSite\n"]; + [export appendFormat:@"# used used type name\t name\tpassword\n"]; // Sites. for (MPElementEntity *element in activeUser.elements) { @@ -770,9 +825,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext NSUInteger uses = element.uses; MPElementType type = element.type; NSUInteger version = element.version; - NSString *name = element.name; + NSUInteger counter = 0; + NSString *loginName = element.loginName; + NSString *siteName = element.name; NSString *content = nil; + // Generated-specific + if ([element isKindOfClass:[MPElementGeneratedEntity class]]) + counter = ((MPElementGeneratedEntity *)element).counter; + + // Determine the content to export. if (!(type & MPElementFeatureDevicePrivate)) { if (revealPasswords) @@ -781,10 +843,10 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext content = [element.algorithm exportContentForElement:element usingKey:self.key]; } - [export appendFormat:@"%@ %8ld %8s %20s\t%@\n", + [export appendFormat:@"%@ %8ld %8s %25s\t%25s\t%@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses, - [strf( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content - ? content: @""]; + [strf( @"%lu:%lu:%lu", (long)type, (long)version, (long)counter ) UTF8String], + [(loginName?: @"") UTF8String], [siteName UTF8String], content?: @""]; } MPCheckpoint( MPCheckpointSitesExported, @{ diff --git a/MasterPassword/ObjC/MPEntities.m b/MasterPassword/ObjC/MPEntities.m index c9b37552..33ec96e4 100644 --- a/MasterPassword/ObjC/MPEntities.m +++ b/MasterPassword/ObjC/MPEntities.m @@ -20,7 +20,7 @@ @try { NSError *error = nil; if (!(success = [self save:&error])) - err( @"While saving: %@", error ); + err( @"While saving: %@", [error fullDescription] ); } @catch (NSException *exception) { success = NO; diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.h b/MasterPassword/ObjC/iOS/MPPasswordsViewController.h index 5457d92d..46eddb1d 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.h +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.h @@ -36,6 +36,5 @@ - (void)updatePasswords; - (IBAction)dismissPopdown:(id)sender; -- (IBAction)signOut:(id)sender; @end diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m index 5fd0f25b..4751c9e4 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m @@ -32,7 +32,8 @@ @end @implementation MPPasswordsViewController { - __weak id _storeObserver; + __weak id _storeChangingObserver; + __weak id _storeChangedObserver; __weak id _mocObserver; NSArray *_notificationObservers; __weak UITapGestureRecognizer *_passwordsDismissRecognizer; @@ -275,7 +276,7 @@ referenceSizeForHeaderInSection:(NSInteger)section { }], [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil - queue:nil usingBlock:^(NSNotification *note) { + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Strongify( self ); _fetchedResultsController = nil; @@ -293,8 +294,8 @@ referenceSizeForHeaderInSection:(NSInteger)section { }]; }], [[NSNotificationCenter defaultCenter] - addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification *note) { + addObserverForName:MPCheckConfigNotification object:nil + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { [self updateConfigKey:note.object]; }], ]; @@ -314,17 +315,25 @@ referenceSizeForHeaderInSection:(NSInteger)section { NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady]; if (!_mocObserver && mainContext) _mocObserver = [[NSNotificationCenter defaultCenter] - addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext - queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { -// Strongify(self); -// [self updatePasswords]; + addObserverForName:NSManagedObjectContextDidSaveNotification object:mainContext + queue:nil usingBlock:^(NSNotification *note) { + if (![[MPiOSAppDelegate get] activeUserInContext:mainContext]) + [[MPiOSAppDelegate get] signOutAnimated:YES]; }]; - if (!_storeObserver) - _storeObserver = [[NSNotificationCenter defaultCenter] - addObserverForName:USMStoreDidChangeNotification object:nil - queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + if (!_storeChangingObserver) + _storeChangingObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:USMStoreWillChangeNotification object:nil + queue:nil usingBlock:^(NSNotification *note) { Strongify( self ); - _fetchedResultsController = nil; + if (self->_mocObserver) + [[NSNotificationCenter defaultCenter] removeObserver:self->_mocObserver]; + }]; + if (!_storeChangedObserver) + _storeChangedObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:USMStoreDidChangeNotification object:nil + queue:nil usingBlock:^(NSNotification *note) { + Strongify( self ); + self->_fetchedResultsController = nil; [self updatePasswords]; }]; } @@ -333,8 +342,10 @@ referenceSizeForHeaderInSection:(NSInteger)section { if (_mocObserver) [[NSNotificationCenter defaultCenter] removeObserver:_mocObserver]; - if (_storeObserver) - [[NSNotificationCenter defaultCenter] removeObserver:_storeObserver]; + if (_storeChangingObserver) + [[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver]; + if (_storeChangedObserver) + [[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver]; } - (void)updateConfigKey:(NSString *)key { @@ -350,8 +361,8 @@ referenceSizeForHeaderInSection:(NSInteger)section { NSString *query = self.query; NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID; if (!activeUserOID) { - self.passwordsSearchBar.text = nil; PearlMainQueue( ^{ + self.passwordsSearchBar.text = nil; [self.passwordCollectionView reloadData]; [self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) animated:YES]; } ); @@ -444,9 +455,4 @@ referenceSizeForHeaderInSection:(NSInteger)section { self.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh; } -- (IBAction)signOut:(id)sender { - - [[MPiOSAppDelegate get] signOutAnimated:YES]; -} - @end diff --git a/MasterPassword/ObjC/iOS/MPUsersViewController.m b/MasterPassword/ObjC/iOS/MPUsersViewController.m index ffbb129b..94e86ef4 100644 --- a/MasterPassword/ObjC/iOS/MPUsersViewController.m +++ b/MasterPassword/ObjC/iOS/MPUsersViewController.m @@ -50,7 +50,8 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { @end @implementation MPUsersViewController { - __weak id _storeObserver; + __weak id _storeChangingObserver; + __weak id _storeChangedObserver; __weak id _mocObserver; NSArray *_notificationObservers; NSString *_masterPasswordChoice; @@ -382,6 +383,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { [context deleteObject:user_]; [context saveToStore]; + [self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore. }]; return; } @@ -623,7 +625,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { if (!_mocObserver && mainContext) _mocObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext - queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + queue:nil usingBlock:^(NSNotification *note) { Strongify(self); NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey]; NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey]; @@ -633,10 +635,18 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { }]] count]) [self reloadUsers]; }]; - if (!_storeObserver) - _storeObserver = [[NSNotificationCenter defaultCenter] + if (!_storeChangingObserver) + _storeChangingObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:USMStoreWillChangeNotification object:nil + queue:nil usingBlock:^(NSNotification *note) { + Strongify(self); + if (self->_mocObserver) + [[NSNotificationCenter defaultCenter] removeObserver:self->_mocObserver]; + }]; + if (!_storeChangedObserver) + _storeChangedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil - queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + queue:nil usingBlock:^(NSNotification *note) { Strongify(self); [self reloadUsers]; }]; @@ -646,8 +656,10 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) { if (_mocObserver) [[NSNotificationCenter defaultCenter] removeObserver:_mocObserver]; - if (_storeObserver) - [[NSNotificationCenter defaultCenter] removeObserver:_storeObserver]; + if (_storeChangingObserver) + [[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver]; + if (_storeChangedObserver) + [[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver]; } - (void)reloadUsers { diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index fd27f3e7..c609b0c8 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -597,9 +597,14 @@ if (buttonIndex == [alert cancelButtonIndex]) return; - [[self storeManager] migrateCloudToLocal]; + if (buttonIndex == [alert firstOtherButtonIndex]) + [UIApp openURL:[NSURL URLWithString: + @"http://support.lyndir.com/topic/486731-why-doesnt-the-mac-version-have-icloud-support/#comment-755394"]]; + + if (buttonIndex == [alert firstOtherButtonIndex] + 1) + [MPiOSConfig get].iCloudEnabled = @NO; } - cancelTitle:@"Ignore For Now" otherTitles:@"Disable iCloud", nil]; + cancelTitle:@"Ignore For Now" otherTitles:@"Why?", @"Disable iCloud", nil]; } - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager failedLoadingStoreWithCause:(UbiquityStoreErrorCause)cause context:(id)context diff --git a/MasterPassword/ObjC/iOS/Storyboard.storyboard b/MasterPassword/ObjC/iOS/Storyboard.storyboard index eba349cf..7f78b9b8 100644 --- a/MasterPassword/ObjC/iOS/Storyboard.storyboard +++ b/MasterPassword/ObjC/iOS/Storyboard.storyboard @@ -150,7 +150,7 @@ - + @@ -236,7 +236,7 @@ - + @@ -980,11 +980,11 @@ - + - +