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:]]*([^:]+): (.*)"
@ -216,7 +216,7 @@
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]];
@ -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;
if (!key) {
key = [MPAlgorithmDefaultForBundleVersion(bundleVersion) keyForPassword:password ofUserNamed:userName]; key = [MPAlgorithmDefaultForBundleVersion(bundleVersion) keyForPassword:password ofUserNamed:userName];
if (![keyIDHex isEqualToString:[key.keyID encodeHex]]) if (![keyIDHex isEqualToString:[key.keyID encodeHex]])
return MPImportResultInvalidPassword; 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,19 +322,29 @@
return MPImportResultCancelled; return MPImportResultCancelled;
} }
BOOL success = NO;
[self.managedObjectContext.undoManager beginUndoGrouping];
@try {
// Delete existing sites. // Delete existing sites.
if (elementsToDelete.count)
[self.managedObjectContext performBlockAndWait:^{
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { [elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]); inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
dbg(@"Deleted Element: %@", [obj debugDescription]);
[self.managedObjectContext deleteObject:obj]; [self.managedObjectContext deleteObject:obj];
}]; }];
[self saveContext]; }];
// Import new sites. // Import new sites.
if (!user) { if (!user) {
[self.managedObjectContext performBlockAndWait:^{
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:self.managedObjectContext]; inManagedObjectContext:self.managedObjectContext];
user.name = userName; user.name = userName;
user.keyID = [keyIDHex decodeHex]; user.keyID = [keyIDHex decodeHex];
}];
dbg(@"Created User: %@", [user debugDescription]);
} }
for (NSArray *siteElements in importedSiteElements) { for (NSArray *siteElements in importedSiteElements) {
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]]; NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
@ -337,6 +355,7 @@
NSString *exportContent = [siteElements objectAtIndex:5]; NSString *exportContent = [siteElements objectAtIndex:5];
// Create new site. // Create new site.
[self.managedObjectContext performBlockAndWait:^{
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[key.algorithm classNameOfType:type] MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[key.algorithm classNameOfType:type]
inManagedObjectContext:self.managedObjectContext]; inManagedObjectContext:self.managedObjectContext];
element.name = name; element.name = name;
@ -351,9 +370,12 @@
else else
[element importProtectedContent:exportContent]; [element importProtectedContent:exportContent];
} }
dbg(@"Created Element: %@", [element debugDescription]);
}];
} }
[self saveContext];
[self saveContext];
success = YES;
inf(@"Import completed successfully."); inf(@"Import completed successfully.");
#ifdef TESTFLIGHT_SDK_VERSION #ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPCheckpointSitesImported]; [TestFlight passCheckpoint:MPCheckpointSitesImported];
@ -362,6 +384,13 @@
return MPImportResultSuccess; return MPImportResultSuccess;
} }
@finally {
[self.managedObjectContext.undoManager endUndoGrouping];
if (!success)
[self.managedObjectContext.undoManager undoNestedGroup];
}
}
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords { - (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {

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,14 +275,19 @@
- (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar { - (void)didSelectNewUserAvatar:(UIButton *)newUserAvatar {
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class]) __block MPUserEntity *newUser = nil;
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:[MPAppDelegate managedObjectContext]]; 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] performBlock:^{
[[MPAppDelegate managedObjectContext] deleteObject:newUser]; [[MPAppDelegate managedObjectContext] deleteObject:newUser];
}]; }];
}];
} }
- (void)showNewUserNameAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion { - (void)showNewUserNameAlertFor:(MPUserEntity *)newUser completion:(void (^)(BOOL finished))completion {
@ -715,7 +720,9 @@
return; return;
if (buttonIndex == [sheet destructiveButtonIndex]) { if (buttonIndex == [sheet destructiveButtonIndex]) {
[[MPAppDelegate get].managedObjectContext performBlockAndWait:^{
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser]; [[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