2
0

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.
This commit is contained in:
Maarten Billemont 2014-08-23 01:39:47 -04:00
parent a4fe13842a
commit c57bd5d5d3
8 changed files with 165 additions and 81 deletions

@ -1 +1 @@
Subproject commit 5e38f25f6e058cc52b8e41bcdc183b7966bb3ac7 Subproject commit 99dcbccee742d2bd2e5701f6b4e001138956030a

View File

@ -133,14 +133,14 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp
queue:[NSOperationQueue mainQueue] usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) { ^(NSNotification *note) {
[[self mainManagedObjectContext] saveToStore]; [[self mainManagedObjectContext] saveToStore];
}]; }];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp
queue:[NSOperationQueue mainQueue] usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) { ^(NSNotification *note) {
[[self mainManagedObjectContext] saveToStore]; [[self mainManagedObjectContext] saveToStore];
}]; }];
#else #else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp
queue:[NSOperationQueue mainQueue] usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
@ -405,11 +405,11 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
self.saveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification self.saveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification
object:privateManagedObjectContext queue:nil usingBlock: object:privateManagedObjectContext queue:nil usingBlock:
^(NSNotification *note) { ^(NSNotification *note) {
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext. // When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
[mainManagedObjectContext performBlock:^{ [mainManagedObjectContext performBlock:^{
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note]; [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}]; }];
}]; }];
self.privateManagedObjectContext = privateManagedObjectContext; self.privateManagedObjectContext = privateManagedObjectContext;
self.mainManagedObjectContext = mainManagedObjectContext; self.mainManagedObjectContext = mainManagedObjectContext;
@ -532,7 +532,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
saveInContext:(NSManagedObjectContext *)context { saveInContext:(NSManagedObjectContext *)context {
// Compile patterns. // Compile patterns.
static NSRegularExpression *headerPattern, *sitePattern; static NSRegularExpression *headerPattern;
static NSArray *sitePatterns;
NSError *error = nil; NSError *error = nil;
if (!headerPattern) { if (!headerPattern) {
headerPattern = [[NSRegularExpression alloc] headerPattern = [[NSRegularExpression alloc]
@ -543,12 +544,17 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
return MPImportResultInternalError; return MPImportResultInternalError;
} }
} }
if (!sitePattern) { if (!sitePatterns) {
sitePattern = [[NSRegularExpression alloc] sitePatterns = @[
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)(:[[:digit:]]+)?[[:space:]]+([^\t]+)\t(.*)" [[NSRegularExpression alloc] // Format 0
options:(NSRegularExpressionOptions)0 error:&error]; 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) { if (error) {
err( @"Error loading the site pattern: %@", error ); err( @"Error loading the site patterns: %@", error );
return MPImportResultInternalError; return MPImportResultInternalError;
} }
} }
@ -557,6 +563,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
inf( @"Importing sites." ); inf( @"Importing sites." );
__block MPUserEntity *user = nil; __block MPUserEntity *user = nil;
id<MPAlgorithm> importAlgorithm = nil; id<MPAlgorithm> importAlgorithm = nil;
NSUInteger importFormat = 0;
NSUInteger importAvatar = NSNotFound;
NSString *importBundleVersion = nil, *importUserName = nil; NSString *importBundleVersion = nil, *importUserName = nil;
NSData *importKeyID = nil; NSData *importKeyID = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO; BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
@ -604,8 +612,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
return MPImportResultInternalError; return MPImportResultInternalError;
} }
user = [users count]? [users lastObject]: nil; user = [users lastObject];
dbg( @"Found user: %@", [user debugDescription] ); dbg( @"Existing user? %@", [user debugDescription] );
} }
if ([headerName isEqualToString:@"Key ID"]) if ([headerName isEqualToString:@"Key ID"])
importKeyID = [headerValue decodeHex]; importKeyID = [headerValue decodeHex];
@ -613,6 +621,15 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
importBundleVersion = headerValue; importBundleVersion = headerValue;
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion ); 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 ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"]) if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES; clearText = YES;
@ -628,6 +645,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
continue; continue;
// Site // Site
NSRegularExpression *sitePattern = sitePatterns[importFormat];
if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0 if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) { range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
err( @"Invalid site format in line: %@", importedSiteLine ); err( @"Invalid site format in line: %@", importedSiteLine );
@ -635,21 +653,45 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
} }
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0 NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject]; range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]]; NSString *lastUsed, *uses, *type, *version, *counter, *siteName, *loginName, *exportContent;
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; switch (importFormat) {
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; case 0:
NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
if ([version length]) uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
version = [version substringFromIndex:1]; // Strip the leading colon. type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]]; 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. // Find existing site.
if (user) { 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]; NSArray *existingSites = [context executeFetchRequest:elementFetchRequest error:&error];
if (!existingSites) { 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; return MPImportResultInternalError;
} }
if ([existingSites count]) { if ([existingSites count]) {
@ -657,14 +699,14 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
[elementsToDelete addObjectsFromArray:existingSites]; [elementsToDelete addObjectsFromArray:existingSites];
} }
} }
[importedSiteElements addObject:@[ lastUsed, uses, type, version, name, exportContent ]]; [importedSiteElements addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, name=%@, exportContent=%@", dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@",
lastUsed, uses, type, version, name, exportContent ); lastUsed, uses, type, version, counter, loginName, siteName, exportContent );
} }
// Ask for confirmation to import these sites and the master password of the user. // 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], 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], NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteElements count],
[elementsToDelete count] ); [elementsToDelete count] );
if (!userMasterPassword) { if (!userMasterPassword) {
@ -689,10 +731,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
}]; }];
// Make sure there is a user. // 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 = [MPUserEntity insertNewObjectInContext:context];
user.name = importUserName; user.name = importUserName;
user.keyID = importKeyID; user.keyID = importKeyID;
if (importAvatar != NSNotFound)
user.avatar = importAvatar;
dbg( @"Created User: %@", [user debugDescription] ); dbg( @"Created User: %@", [user debugDescription] );
} }
@ -702,13 +750,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
NSUInteger uses = (unsigned)[siteElements[1] integerValue]; NSUInteger uses = (unsigned)[siteElements[1] integerValue];
MPElementType type = (MPElementType)[siteElements[2] integerValue]; MPElementType type = (MPElementType)[siteElements[2] integerValue];
NSUInteger version = (unsigned)[siteElements[3] integerValue]; NSUInteger version = (unsigned)[siteElements[3] integerValue];
NSString *name = siteElements[4]; NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
NSString *exportContent = siteElements[5]; NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
NSString *siteName = siteElements[6];
NSString *exportContent = siteElements[7];
// Create new site. // Create new site.
NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type]; NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context]; MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
element.name = name; element.name = siteName;
element.loginName = loginName;
element.user = user; element.user = user;
element.type = type; element.type = type;
element.uses = uses; element.uses = uses;
@ -720,6 +771,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
else else
[element.algorithm importProtectedContent:exportContent protectedByKey:importKey intoElement:element usingKey:userKey]; [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] ); 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:@"# Export of site names and stored passwords (unless device-private) encrypted with the master key.\n"];
[export appendFormat:@"# \n"]; [export appendFormat:@"# \n"];
[export appendFormat:@"##\n"]; [export appendFormat:@"##\n"];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# User Name: %@\n", activeUser.name]; [export appendFormat:@"# User Name: %@\n", activeUser.name];
[export appendFormat:@"# Avatar: %d\n", activeUser.avatar];
[export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]]; [export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]];
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]]; [export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
[export appendFormat:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
[export appendFormat:@"# Format: 1\n"];
if (revealPasswords) if (revealPasswords)
[export appendFormat:@"# Passwords: VISIBLE\n"]; [export appendFormat:@"# Passwords: VISIBLE\n"];
else else
[export appendFormat:@"# Passwords: PROTECTED\n"]; [export appendFormat:@"# Passwords: PROTECTED\n"];
[export appendFormat:@"##\n"]; [export appendFormat:@"##\n"];
[export appendFormat:@"#\n"]; [export appendFormat:@"#\n"];
[export appendFormat:@"# Last Times Password Site\tSite\n"]; [export appendFormat:@"# Last Times Password Login\t Site\tSite\n"];
[export appendFormat:@"# used used type name\tpassword\n"]; [export appendFormat:@"# used used type name\t name\tpassword\n"];
// Sites. // Sites.
for (MPElementEntity *element in activeUser.elements) { for (MPElementEntity *element in activeUser.elements) {
@ -770,9 +825,16 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
NSUInteger uses = element.uses; NSUInteger uses = element.uses;
MPElementType type = element.type; MPElementType type = element.type;
NSUInteger version = element.version; NSUInteger version = element.version;
NSString *name = element.name; NSUInteger counter = 0;
NSString *loginName = element.loginName;
NSString *siteName = element.name;
NSString *content = nil; NSString *content = nil;
// Generated-specific
if ([element isKindOfClass:[MPElementGeneratedEntity class]])
counter = ((MPElementGeneratedEntity *)element).counter;
// Determine the content to export. // Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) { if (!(type & MPElementFeatureDevicePrivate)) {
if (revealPasswords) if (revealPasswords)
@ -781,10 +843,10 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
content = [element.algorithm exportContentForElement:element usingKey:self.key]; 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, [[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses,
[strf( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content [strf( @"%lu:%lu:%lu", (long)type, (long)version, (long)counter ) UTF8String],
? content: @""]; [(loginName?: @"") UTF8String], [siteName UTF8String], content?: @""];
} }
MPCheckpoint( MPCheckpointSitesExported, @{ MPCheckpoint( MPCheckpointSitesExported, @{

View File

@ -20,7 +20,7 @@
@try { @try {
NSError *error = nil; NSError *error = nil;
if (!(success = [self save:&error])) if (!(success = [self save:&error]))
err( @"While saving: %@", error ); err( @"While saving: %@", [error fullDescription] );
} }
@catch (NSException *exception) { @catch (NSException *exception) {
success = NO; success = NO;

View File

@ -36,6 +36,5 @@
- (void)updatePasswords; - (void)updatePasswords;
- (IBAction)dismissPopdown:(id)sender; - (IBAction)dismissPopdown:(id)sender;
- (IBAction)signOut:(id)sender;
@end @end

View File

@ -32,7 +32,8 @@
@end @end
@implementation MPPasswordsViewController { @implementation MPPasswordsViewController {
__weak id _storeObserver; __weak id _storeChangingObserver;
__weak id _storeChangedObserver;
__weak id _mocObserver; __weak id _mocObserver;
NSArray *_notificationObservers; NSArray *_notificationObservers;
__weak UITapGestureRecognizer *_passwordsDismissRecognizer; __weak UITapGestureRecognizer *_passwordsDismissRecognizer;
@ -275,7 +276,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
}], }],
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedOutNotification object:nil addObserverForName:MPSignedOutNotification object:nil
queue:nil usingBlock:^(NSNotification *note) { queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self ); Strongify( self );
_fetchedResultsController = nil; _fetchedResultsController = nil;
@ -293,8 +294,8 @@ referenceSizeForHeaderInSection:(NSInteger)section {
}]; }];
}], }],
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue] addObserverForName:MPCheckConfigNotification object:nil
usingBlock:^(NSNotification *note) { queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self updateConfigKey:note.object]; [self updateConfigKey:note.object];
}], }],
]; ];
@ -314,17 +315,25 @@ referenceSizeForHeaderInSection:(NSInteger)section {
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady]; NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
if (!_mocObserver && mainContext) if (!_mocObserver && mainContext)
_mocObserver = [[NSNotificationCenter defaultCenter] _mocObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext addObserverForName:NSManagedObjectContextDidSaveNotification object:mainContext
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { queue:nil usingBlock:^(NSNotification *note) {
// Strongify(self); if (![[MPiOSAppDelegate get] activeUserInContext:mainContext])
// [self updatePasswords]; [[MPiOSAppDelegate get] signOutAnimated:YES];
}]; }];
if (!_storeObserver) if (!_storeChangingObserver)
_storeObserver = [[NSNotificationCenter defaultCenter] _storeChangingObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:USMStoreDidChangeNotification object:nil addObserverForName:USMStoreWillChangeNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { queue:nil usingBlock:^(NSNotification *note) {
Strongify( self ); 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]; [self updatePasswords];
}]; }];
} }
@ -333,8 +342,10 @@ referenceSizeForHeaderInSection:(NSInteger)section {
if (_mocObserver) if (_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver]; [[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
if (_storeObserver) if (_storeChangingObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver]; [[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver];
if (_storeChangedObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver];
} }
- (void)updateConfigKey:(NSString *)key { - (void)updateConfigKey:(NSString *)key {
@ -350,8 +361,8 @@ referenceSizeForHeaderInSection:(NSInteger)section {
NSString *query = self.query; NSString *query = self.query;
NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID; NSManagedObjectID *activeUserOID = [MPiOSAppDelegate get].activeUserOID;
if (!activeUserOID) { if (!activeUserOID) {
self.passwordsSearchBar.text = nil;
PearlMainQueue( ^{ PearlMainQueue( ^{
self.passwordsSearchBar.text = nil;
[self.passwordCollectionView reloadData]; [self.passwordCollectionView reloadData];
[self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) animated:YES]; [self.passwordCollectionView setContentOffset:CGPointMake( 0, -self.passwordCollectionView.contentInset.top ) animated:YES];
} ); } );
@ -444,9 +455,4 @@ referenceSizeForHeaderInSection:(NSInteger)section {
self.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh; self.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh;
} }
- (IBAction)signOut:(id)sender {
[[MPiOSAppDelegate get] signOutAnimated:YES];
}
@end @end

View File

@ -50,7 +50,8 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
@end @end
@implementation MPUsersViewController { @implementation MPUsersViewController {
__weak id _storeObserver; __weak id _storeChangingObserver;
__weak id _storeChangedObserver;
__weak id _mocObserver; __weak id _mocObserver;
NSArray *_notificationObservers; NSArray *_notificationObservers;
NSString *_masterPasswordChoice; NSString *_masterPasswordChoice;
@ -382,6 +383,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
[context deleteObject:user_]; [context deleteObject:user_];
[context saveToStore]; [context saveToStore];
[self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore.
}]; }];
return; return;
} }
@ -623,7 +625,7 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
if (!_mocObserver && mainContext) if (!_mocObserver && mainContext)
_mocObserver = [[NSNotificationCenter defaultCenter] _mocObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { queue:nil usingBlock:^(NSNotification *note) {
Strongify(self); Strongify(self);
NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey]; NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey];
NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey]; NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey];
@ -633,10 +635,18 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
}]] count]) }]] count])
[self reloadUsers]; [self reloadUsers];
}]; }];
if (!_storeObserver) if (!_storeChangingObserver)
_storeObserver = [[NSNotificationCenter defaultCenter] _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 addObserverForName:USMStoreDidChangeNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { queue:nil usingBlock:^(NSNotification *note) {
Strongify(self); Strongify(self);
[self reloadUsers]; [self reloadUsers];
}]; }];
@ -646,8 +656,10 @@ typedef NS_ENUM(NSUInteger, MPActiveUserState) {
if (_mocObserver) if (_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver]; [[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
if (_storeObserver) if (_storeChangingObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver]; [[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver];
if (_storeChangedObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver];
} }
- (void)reloadUsers { - (void)reloadUsers {

View File

@ -597,9 +597,14 @@
if (buttonIndex == [alert cancelButtonIndex]) if (buttonIndex == [alert cancelButtonIndex])
return; 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 - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager failedLoadingStoreWithCause:(UbiquityStoreErrorCause)cause context:(id)context

View File

@ -150,7 +150,7 @@
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="kL5-zV-zbb"/> <action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="kL5-zV-zbb"/>
</connections> </connections>
</button> </button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qp1-nX-o4i" userLabel="Entry" customClass="UIView+Touches"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qp1-nX-o4i" userLabel="Entry">
<rect key="frame" x="20" y="255" width="280" height="68"/> <rect key="frame" x="20" y="255" width="280" height="68"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
@ -236,7 +236,7 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/> <userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/>
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</view> </view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XEP-O3-ayG" userLabel="Footer" customClass="UIView+Touches"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XEP-O3-ayG" userLabel="Footer">
<rect key="frame" x="0.0" y="477" width="320" height="71"/> <rect key="frame" x="0.0" y="477" width="320" height="71"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
@ -980,11 +980,11 @@
<viewControllerLayoutGuide type="top" id="S9X-2T-e1e"/> <viewControllerLayoutGuide type="top" id="S9X-2T-e1e"/>
<viewControllerLayoutGuide type="bottom" id="c12-XI-Rv9"/> <viewControllerLayoutGuide type="bottom" id="c12-XI-Rv9"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="MSX-uI-cwS" userLabel="Root" customClass="UIView+Touches"> <view key="view" contentMode="scaleToFill" id="MSX-uI-cwS" userLabel="Root">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/> <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tI8-OT-LrO" userLabel="Passwords Root" customClass="UIView+Touches"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tI8-OT-LrO" userLabel="Passwords Root">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/> <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>