2
0

Fixes to import code.

[FIXED]     Don't recalculate key for each entry in import list.
[FIXED]     Use correct fetch request to find user entity for import.
[FIXED]     Properly schedule all use of MOC with performBlock*
[ADDED]     Use undoManager to revert failed import changes.
This commit is contained in:
Maarten Billemont 2012-07-30 07:58:18 +02:00
parent b0b6dcc56b
commit 647235616e
3 changed files with 109 additions and 66 deletions

View File

@ -197,7 +197,7 @@
inf(@"Importing sites."); inf(@"Importing sites.");
static NSRegularExpression *headerPattern, *sitePattern; static NSRegularExpression *headerPattern, *sitePattern;
__autoreleasing NSError *error; __autoreleasing __block NSError *error;
if (!headerPattern) { if (!headerPattern) {
headerPattern = [[NSRegularExpression alloc] headerPattern = [[NSRegularExpression alloc]
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)" initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
@ -215,10 +215,10 @@
if (!headerPattern || !sitePattern) if (!headerPattern || !sitePattern)
return MPImportResultInternalError; return MPImportResultInternalError;
MPKey *key = nil; MPKey *key = nil;
MPUserEntity *user = nil; __block MPUserEntity *user = nil;
NSString *bundleVersion = nil, *keyIDHex = nil, *userName = nil; NSString *bundleVersion = nil, *keyIDHex = nil, *userName = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO; BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set]; NSMutableSet *elementsToDelete = [NSMutableSet set];
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]]; NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
@ -252,7 +252,10 @@
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName]; userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject]; [self.managedObjectContext performBlockAndWait:^{
user = [[self.managedObjectContext executeFetchRequest:userFetchRequest error:&error] lastObject];
}];
dbg(@"Found user: %@", [user debugDescription]);
} }
if ([headerName isEqualToString:@"Key ID"]) if ([headerName isEqualToString:@"Key ID"])
keyIDHex = headerValue; keyIDHex = headerValue;
@ -269,9 +272,11 @@
continue; continue;
if (!keyIDHex || ![userName length]) if (!keyIDHex || ![userName length])
return MPImportResultMalformedInput; return MPImportResultMalformedInput;
key = [MPAlgorithmDefaultForBundleVersion(bundleVersion) keyForPassword:password ofUserNamed:userName]; if (!key) {
if (![keyIDHex isEqualToString:[key.keyID encodeHex]]) key = [MPAlgorithmDefaultForBundleVersion(bundleVersion) keyForPassword:password ofUserNamed:userName];
return MPImportResultInvalidPassword; if (![keyIDHex isEqualToString:[key.keyID encodeHex]])
return MPImportResultInvalidPassword;
}
if (![importedSiteLine length]) if (![importedSiteLine length])
continue; continue;
@ -292,13 +297,16 @@
// Find existing site. // Find existing site.
if (user) { if (user) {
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error]; __block NSArray *existingSites = nil;
if (error) [self.managedObjectContext performBlockAndWait:^{
err(@"Couldn't search existing sites: %@", error); existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
}];
if (!existingSites) { if (!existingSites) {
err(@"Lookup of existing sites failed for site: %@, user: %@", name, user.userID); err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error);
return MPImportResultInternalError; return MPImportResultInternalError;
} } else
if (existingSites.count)
dbg(@"Existing sites: %@", existingSites);
[elementsToDelete addObjectsFromArray:existingSites]; [elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, version, name, exportContent, nil]]; [importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, version, name, exportContent, nil]];
@ -314,53 +322,74 @@
return MPImportResultCancelled; return MPImportResultCancelled;
} }
// Delete existing sites. BOOL success = NO;
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { [self.managedObjectContext.undoManager beginUndoGrouping];
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]); @try {
[self.managedObjectContext deleteObject:obj];
}];
[self saveContext];
// Import new sites. // Delete existing sites.
if (!user) { if (elementsToDelete.count)
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) [self.managedObjectContext performBlockAndWait:^{
inManagedObjectContext:self.managedObjectContext]; [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
user.name = userName; inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
user.keyID = [keyIDHex decodeHex]; dbg(@"Deleted Element: %@", [obj debugDescription]);
} [self.managedObjectContext deleteObject:obj];
for (NSArray *siteElements in importedSiteElements) { }];
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]]; }];
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
NSUInteger version = (unsigned)[[siteElements objectAtIndex:3] integerValue];
NSString *name = [siteElements objectAtIndex:4];
NSString *exportContent = [siteElements objectAtIndex:5];
// Create new site. // Import new sites.
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[key.algorithm classNameOfType:type] if (!user) {
inManagedObjectContext:self.managedObjectContext]; [self.managedObjectContext performBlockAndWait:^{
element.name = name; user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
element.user = user; inManagedObjectContext:self.managedObjectContext];
element.type = type; user.name = userName;
element.uses = uses; user.keyID = [keyIDHex decodeHex];
element.lastUsed = lastUsed; }];
element.version = version; dbg(@"Created User: %@", [user debugDescription]);
if ([exportContent length]) {
if (clearText)
[element importClearTextContent:exportContent usingKey:key];
else
[element importProtectedContent:exportContent];
} }
} for (NSArray *siteElements in importedSiteElements) {
[self saveContext]; NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
MPElementType type = (MPElementType)[[siteElements objectAtIndex:2] integerValue];
NSUInteger version = (unsigned)[[siteElements objectAtIndex:3] integerValue];
NSString *name = [siteElements objectAtIndex:4];
NSString *exportContent = [siteElements objectAtIndex:5];
inf(@"Import completed successfully."); // Create new site.
[self.managedObjectContext performBlockAndWait:^{
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[key.algorithm classNameOfType:type]
inManagedObjectContext:self.managedObjectContext];
element.name = name;
element.user = user;
element.type = type;
element.uses = uses;
element.lastUsed = lastUsed;
element.version = version;
if ([exportContent length]) {
if (clearText)
[element importClearTextContent:exportContent usingKey:key];
else
[element importProtectedContent:exportContent];
}
dbg(@"Created Element: %@", [element debugDescription]);
}];
}
[self saveContext];
success = YES;
inf(@"Import completed successfully.");
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSitesImported]; [TestFlight passCheckpoint:MPCheckpointSitesImported];
#endif #endif
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported attributes:nil]; [[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported attributes:nil];
return MPImportResultSuccess; return MPImportResultSuccess;
}
@finally {
[self.managedObjectContext.undoManager endUndoGrouping];
if (!success)
[self.managedObjectContext.undoManager undoNestedGroup];
}
} }
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords { - (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
@ -392,9 +421,9 @@
// Sites. // Sites.
for (MPElementEntity *element in self.activeUser.elements) { for (MPElementEntity *element in self.activeUser.elements) {
NSDate *lastUsed = element.lastUsed; NSDate *lastUsed = element.lastUsed;
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; NSString *name = element.name;
NSString *content = nil; NSString *content = nil;

View File

@ -1,13 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic"> <model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1487" systemVersion="12A269" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES"> <entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/> <attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" syncable="YES"/> <attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/> <attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
<attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO"/> <attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO">
<userInfo/>
</attribute>
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/> <attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="userName" optional="YES" attributeType="String" syncable="YES"/> <attribute name="userName" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/> <attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/> <attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/> <relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
</entity> </entity>
@ -24,7 +26,9 @@
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/> <attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/> <attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/> <attribute name="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO"/> <attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
<userInfo/>
</attribute>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/> <relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
</entity> </entity>
<elements> <elements>

View File

@ -275,13 +275,18 @@
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar { - (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) __block MPUserEntity *newUser = nil;
inManagedObjectContext:[MPAppDelegate managedObjectContext]]; [[MPAppDelegate managedObjectContext] performBlockAndWait:^{
newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
}];
[self showNewUserNameAlertFor:newUser completion:^(BOOL finished) { [self showNewUserNameAlertFor:newUser completion:^(BOOL finished) {
newUserAvatar.selected = NO; newUserAvatar.selected = NO;
if (!finished) if (!finished)
[[MPAppDelegate managedObjectContext] deleteObject:newUser]; [[MPAppDelegate managedObjectContext] performBlock:^{
[[MPAppDelegate managedObjectContext] deleteObject:newUser];
}];
}]; }];
} }
@ -715,7 +720,9 @@
return; return;
if (buttonIndex == [sheet destructiveButtonIndex]) { if (buttonIndex == [sheet destructiveButtonIndex]) {
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser]; [[MPAppDelegate get].managedObjectContext performBlockAndWait:^{
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser];
}];
[[MPAppDelegate get] saveContext]; [[MPAppDelegate get] saveContext];
[self updateUsers]; [self updateUsers];
} else } else
@ -724,7 +731,10 @@
[[self avatarForUser:targetedUser] setSelected:YES]; [[self avatarForUser:targetedUser] setSelected:YES];
}]; }];
} }
else
[[MPAppDelegate get] saveContext];
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password", } cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password",
@"Save",
nil]; nil];
} }
@end @end