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:
parent
b0b6dcc56b
commit
647235616e
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user