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

View File

@ -1,13 +1,15 @@
<?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">
<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="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="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"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
</entity>
@ -24,7 +26,9 @@
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" 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"/>
</entity>
<elements>

View File

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