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:
parent
a4fe13842a
commit
c57bd5d5d3
2
External/UbiquityStoreManager
vendored
2
External/UbiquityStoreManager
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 5e38f25f6e058cc52b8e41bcdc183b7966bb3ac7
|
Subproject commit 99dcbccee742d2bd2e5701f6b4e001138956030a
|
@ -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, @{
|
||||||
|
@ -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;
|
||||||
|
@ -36,6 +36,5 @@
|
|||||||
- (void)updatePasswords;
|
- (void)updatePasswords;
|
||||||
|
|
||||||
- (IBAction)dismissPopdown:(id)sender;
|
- (IBAction)dismissPopdown:(id)sender;
|
||||||
- (IBAction)signOut:(id)sender;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user