2012-05-07 20:18:01 +00:00
|
|
|
//
|
|
|
|
// MPAppDelegate.m
|
|
|
|
// MasterPassword
|
|
|
|
//
|
|
|
|
// Created by Maarten Billemont on 24/11/11.
|
|
|
|
// Copyright (c) 2011 Lyndir. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "MPAppDelegate_Store.h"
|
|
|
|
|
2013-04-07 23:05:51 +00:00
|
|
|
#if TARGET_OS_IPHONE
|
|
|
|
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
|
|
|
|
#else
|
|
|
|
#define STORE_OPTIONS
|
|
|
|
#endif
|
|
|
|
|
2013-05-10 15:13:55 +00:00
|
|
|
#define MPCloudContainerIdentifier @"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
|
2013-05-02 15:19:34 +00:00
|
|
|
#define MPMigrationLevelLocalStoreKey @"MPMigrationLevelLocalStoreKey"
|
|
|
|
#define MPMigrationLevelCloudStoreKey @"MPMigrationLevelCloudStoreKey"
|
2013-05-10 15:13:55 +00:00
|
|
|
|
|
|
|
typedef NS_ENUM(NSInteger, MPMigrationLevelLocalStore) {
|
|
|
|
MPMigrationLevelLocalStoreV1,
|
|
|
|
MPMigrationLevelLocalStoreV2,
|
|
|
|
MPMigrationLevelLocalStoreCurrent = MPMigrationLevelLocalStoreV2,
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef NS_ENUM(NSInteger, MPMigrationLevelCloudStore) {
|
|
|
|
MPMigrationLevelCloudStoreV1,
|
|
|
|
MPMigrationLevelCloudStoreV2,
|
|
|
|
MPMigrationLevelCloudStoreV3,
|
|
|
|
MPMigrationLevelCloudStoreCurrent = MPMigrationLevelCloudStoreV3,
|
|
|
|
};
|
2013-05-02 15:19:34 +00:00
|
|
|
|
2013-04-20 18:11:19 +00:00
|
|
|
@implementation MPAppDelegate_Shared(Store)
|
2013-05-10 15:13:55 +00:00
|
|
|
PearlAssociatedObjectProperty(NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext);
|
2013-04-05 04:31:05 +00:00
|
|
|
PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext);
|
2012-05-07 20:18:01 +00:00
|
|
|
|
2013-01-17 05:37:20 +00:00
|
|
|
|
2012-05-09 17:34:00 +00:00
|
|
|
#pragma mark - Core Data setup
|
2012-05-09 08:11:34 +00:00
|
|
|
|
2013-06-16 16:39:52 +00:00
|
|
|
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-06-16 16:39:52 +00:00
|
|
|
NSAssert([[NSThread currentThread] isMainThread], @"Can only access main MOC from the main thread.");
|
2013-01-31 05:42:32 +00:00
|
|
|
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
|
2013-06-16 16:39:52 +00:00
|
|
|
if (!mainManagedObjectContext || ![[NSThread currentThread] isMainThread])
|
2013-04-24 00:38:56 +00:00
|
|
|
return nil;
|
2013-01-31 05:42:32 +00:00
|
|
|
|
2013-06-16 16:39:52 +00:00
|
|
|
return mainManagedObjectContext;
|
2013-01-31 05:42:32 +00:00
|
|
|
}
|
|
|
|
|
2013-05-16 02:42:21 +00:00
|
|
|
+ (BOOL)managedObjectContextPerformBlock:(void (^)(NSManagedObjectContext *context))mocBlock {
|
2013-01-31 05:42:32 +00:00
|
|
|
|
|
|
|
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
|
|
|
|
if (!mainManagedObjectContext)
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
|
|
|
moc.parentContext = mainManagedObjectContext;
|
|
|
|
[moc performBlock:^{
|
2013-04-20 18:11:19 +00:00
|
|
|
mocBlock( moc );
|
2013-01-31 05:42:32 +00:00
|
|
|
}];
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2013-05-16 02:42:21 +00:00
|
|
|
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock {
|
2013-02-08 23:11:24 +00:00
|
|
|
|
|
|
|
NSManagedObjectContext *mainManagedObjectContext = [[self get] mainManagedObjectContextIfReady];
|
|
|
|
if (!mainManagedObjectContext)
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
|
|
|
moc.parentContext = mainManagedObjectContext;
|
|
|
|
[moc performBlockAndWait:^{
|
2013-04-20 18:11:19 +00:00
|
|
|
mocBlock( moc );
|
2013-02-08 23:11:24 +00:00
|
|
|
}];
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
- (NSManagedObjectContext *)mainManagedObjectContextIfReady {
|
2013-04-03 23:25:15 +00:00
|
|
|
|
|
|
|
[self storeManager];
|
2013-04-05 04:31:05 +00:00
|
|
|
return self.mainManagedObjectContext;
|
2012-05-07 20:18:01 +00:00
|
|
|
}
|
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
- (NSManagedObjectContext *)privateManagedObjectContextIfReady {
|
2012-08-04 08:16:58 +00:00
|
|
|
|
2013-04-03 23:25:15 +00:00
|
|
|
[self storeManager];
|
2013-04-05 04:31:05 +00:00
|
|
|
return self.privateManagedObjectContext;
|
2012-05-07 20:18:01 +00:00
|
|
|
}
|
|
|
|
|
2013-04-02 02:03:28 +00:00
|
|
|
- (UbiquityStoreManager *)storeManager {
|
|
|
|
|
|
|
|
static UbiquityStoreManager *storeManager = nil;
|
|
|
|
if (storeManager)
|
|
|
|
return storeManager;
|
|
|
|
|
|
|
|
storeManager = [[UbiquityStoreManager alloc] initStoreNamed:nil withManagedObjectModel:nil localStoreURL:nil
|
2013-05-10 15:13:55 +00:00
|
|
|
containerIdentifier:MPCloudContainerIdentifier
|
2013-04-20 18:11:19 +00:00
|
|
|
additionalStoreOptions:@{ STORE_OPTIONS }
|
2013-04-03 23:25:15 +00:00
|
|
|
delegate:self];
|
2013-04-02 02:03:28 +00:00
|
|
|
|
|
|
|
#if TARGET_OS_IPHONE
|
2013-09-18 04:21:52 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp
|
2013-06-27 00:23:02 +00:00
|
|
|
queue:[NSOperationQueue mainQueue] usingBlock:
|
2013-08-15 23:38:05 +00:00
|
|
|
^(NSNotification *note) {
|
|
|
|
[[self mainManagedObjectContext] saveToStore];
|
|
|
|
}];
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
2013-09-18 04:21:52 +00:00
|
|
|
addObserverForName:UIApplicationWillResignActiveNotification object:UIApp
|
2013-08-15 23:38:05 +00:00
|
|
|
queue:[NSOperationQueue mainQueue] usingBlock:
|
|
|
|
^(NSNotification *note) {
|
|
|
|
[[self mainManagedObjectContext] saveToStore];
|
|
|
|
}];
|
2013-04-02 02:03:28 +00:00
|
|
|
#else
|
2013-06-27 00:23:02 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp
|
|
|
|
queue:[NSOperationQueue mainQueue] usingBlock:
|
|
|
|
^(NSNotification *note) {
|
|
|
|
[self.mainManagedObjectContextIfReady saveToStore];
|
|
|
|
}];
|
2013-04-02 02:03:28 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return storeManager;
|
|
|
|
}
|
2012-05-07 20:18:01 +00:00
|
|
|
|
2013-04-07 23:05:51 +00:00
|
|
|
- (void)migrateStoreForManager:(UbiquityStoreManager *)manager isCloud:(BOOL)isCloudStore {
|
|
|
|
|
2013-05-10 15:13:55 +00:00
|
|
|
[self migrateLocalStore];
|
2013-04-07 23:05:51 +00:00
|
|
|
|
|
|
|
if (isCloudStore)
|
2013-05-10 15:13:55 +00:00
|
|
|
[self migrateCloudStore];
|
2013-04-07 23:05:51 +00:00
|
|
|
}
|
|
|
|
|
2013-05-10 15:13:55 +00:00
|
|
|
- (void)migrateLocalStore {
|
2013-04-07 23:05:51 +00:00
|
|
|
|
2013-05-10 15:13:55 +00:00
|
|
|
MPMigrationLevelLocalStore migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
|
2013-05-02 15:19:34 +00:00
|
|
|
if (migrationLevel >= MPMigrationLevelLocalStoreCurrent)
|
2013-05-10 15:13:55 +00:00
|
|
|
// Local store up-to-date.
|
|
|
|
return;
|
|
|
|
|
|
|
|
inf(@"Local store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPMigrationLevelLocalStoreCurrent);
|
2013-09-21 14:34:48 +00:00
|
|
|
if (migrationLevel <= MPMigrationLevelLocalStoreV1) if (![self migrateV1LocalStore]) {
|
|
|
|
inf(@"Failed to migrate old V1 to new local store.");
|
|
|
|
return;
|
|
|
|
}
|
2013-05-10 15:13:55 +00:00
|
|
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] setInteger:MPMigrationLevelLocalStoreCurrent forKey:MPMigrationLevelLocalStoreKey];
|
|
|
|
inf(@"Successfully migrated old to new local store.");
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)migrateCloudStore {
|
|
|
|
|
|
|
|
MPMigrationLevelCloudStore migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelCloudStoreKey];
|
|
|
|
if (migrationLevel >= MPMigrationLevelCloudStoreCurrent)
|
|
|
|
// Cloud store up-to-date.
|
2013-05-02 15:19:34 +00:00
|
|
|
return;
|
|
|
|
|
2013-05-10 15:13:55 +00:00
|
|
|
inf(@"Cloud store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPMigrationLevelCloudStoreCurrent);
|
2013-08-31 16:29:56 +00:00
|
|
|
if (migrationLevel <= MPMigrationLevelCloudStoreV1) {
|
|
|
|
if (![self migrateV1CloudStore]) {
|
|
|
|
inf(@"Failed to migrate old V1 to new cloud store.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (migrationLevel <= MPMigrationLevelCloudStoreV2) {
|
|
|
|
if (![self migrateV2CloudStore]) {
|
|
|
|
inf(@"Failed to migrate old V2 to new cloud store.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2013-05-10 15:13:55 +00:00
|
|
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] setInteger:MPMigrationLevelCloudStoreCurrent forKey:MPMigrationLevelCloudStoreKey];
|
2013-08-31 16:29:56 +00:00
|
|
|
inf(@"Successfully migrated old to new cloud store.");
|
2013-05-10 15:13:55 +00:00
|
|
|
}
|
|
|
|
|
2013-08-31 16:29:56 +00:00
|
|
|
- (BOOL)migrateV1CloudStore {
|
2013-05-10 15:13:55 +00:00
|
|
|
|
|
|
|
// Migrate cloud enabled preference.
|
|
|
|
NSNumber *oldCloudEnabled = [[NSUserDefaults standardUserDefaults] objectForKey:@"iCloudEnabledKey"];
|
|
|
|
if ([oldCloudEnabled boolValue])
|
|
|
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:USMCloudEnabledKey];
|
|
|
|
|
|
|
|
// Migrate cloud store.
|
|
|
|
NSString *uuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"LocalUUIDKey"];
|
|
|
|
if (!uuid) {
|
|
|
|
inf(@"No V1 cloud store to migrate.");
|
2013-08-31 16:29:56 +00:00
|
|
|
return YES;
|
2013-05-10 15:13:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
inf(@"Migrating V1 cloud store: %@ -> %@", uuid, [self.storeManager valueForKey:@"storeUUID"]);
|
|
|
|
NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:MPCloudContainerIdentifier];
|
|
|
|
NSURL *oldCloudContentURL = [[cloudContainerURL
|
|
|
|
URLByAppendingPathComponent:@"Data" isDirectory:YES]
|
|
|
|
URLByAppendingPathComponent:uuid isDirectory:YES];
|
|
|
|
NSURL *oldCloudStoreURL = [[[cloudContainerURL
|
|
|
|
URLByAppendingPathComponent:@"Database.nosync" isDirectory:YES]
|
|
|
|
URLByAppendingPathComponent:uuid isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
|
|
|
|
|
2013-08-31 16:29:56 +00:00
|
|
|
return [self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL contentName:uuid];
|
2013-05-10 15:13:55 +00:00
|
|
|
}
|
|
|
|
|
2013-08-31 16:29:56 +00:00
|
|
|
- (BOOL)migrateV2CloudStore {
|
2013-05-10 15:13:55 +00:00
|
|
|
|
|
|
|
// Migrate cloud store.
|
|
|
|
NSString *uuid = [[NSUbiquitousKeyValueStore defaultStore] stringForKey:@"USMStoreUUIDKey"];
|
|
|
|
if (!uuid) {
|
|
|
|
inf(@"No V2 cloud store to migrate.");
|
2013-08-31 16:29:56 +00:00
|
|
|
return YES;
|
2013-05-10 15:13:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
inf(@"Migrating V2 cloud store: %@ -> %@", uuid, [self.storeManager valueForKey:@"storeUUID"]);
|
|
|
|
NSURL *cloudContainerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:MPCloudContainerIdentifier];
|
|
|
|
NSURL *oldCloudContentURL = [[cloudContainerURL
|
|
|
|
URLByAppendingPathComponent:@"CloudLogs" isDirectory:YES]
|
|
|
|
URLByAppendingPathComponent:uuid isDirectory:YES];
|
|
|
|
NSURL *oldCloudStoreURL = [[[cloudContainerURL
|
|
|
|
URLByAppendingPathComponent:@"CloudStore.nosync" isDirectory:YES]
|
|
|
|
URLByAppendingPathComponent:uuid isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
|
|
|
|
|
2013-08-31 16:29:56 +00:00
|
|
|
return [self migrateFromCloudStore:oldCloudStoreURL cloudContent:oldCloudContentURL contentName:uuid];
|
2013-05-10 15:13:55 +00:00
|
|
|
}
|
|
|
|
|
2013-08-31 16:29:56 +00:00
|
|
|
- (BOOL)migrateV1LocalStore {
|
2013-05-10 15:13:55 +00:00
|
|
|
|
2013-04-07 23:05:51 +00:00
|
|
|
NSURL *applicationFilesDirectory = [[[NSFileManager defaultManager]
|
|
|
|
URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
|
|
|
NSURL *oldLocalStoreURL = [[applicationFilesDirectory
|
|
|
|
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO] URLByAppendingPathExtension:@"sqlite"];
|
|
|
|
if (![[NSFileManager defaultManager] fileExistsAtPath:oldLocalStoreURL.path isDirectory:NO]) {
|
2013-05-10 15:13:55 +00:00
|
|
|
inf(@"No V1 local store to migrate.");
|
2013-08-31 16:29:56 +00:00
|
|
|
return YES;
|
2013-04-07 23:05:51 +00:00
|
|
|
}
|
2013-05-10 15:13:55 +00:00
|
|
|
|
|
|
|
inf(@"Migrating V1 local store");
|
2013-08-31 16:29:56 +00:00
|
|
|
return [self migrateFromLocalStore:oldLocalStoreURL];
|
2013-05-10 15:13:55 +00:00
|
|
|
}
|
|
|
|
|
2013-08-31 16:29:56 +00:00
|
|
|
- (BOOL)migrateFromLocalStore:(NSURL *)oldLocalStoreURL {
|
2013-05-10 15:13:55 +00:00
|
|
|
|
|
|
|
NSURL *newLocalStoreURL = [self.storeManager URLForLocalStore];
|
2013-04-07 23:05:51 +00:00
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NO]) {
|
2013-05-10 15:13:55 +00:00
|
|
|
wrn(@"Can't migrate local store: A new local store already exists.");
|
2013-08-31 16:29:56 +00:00
|
|
|
return YES;
|
2013-04-07 23:05:51 +00:00
|
|
|
}
|
|
|
|
|
2013-09-17 03:18:01 +00:00
|
|
|
if (![self.storeManager migrateStore:oldLocalStoreURL withOptions:nil
|
|
|
|
toStore:newLocalStoreURL withOptions:nil
|
|
|
|
strategy:0 error:nil cause:nil context:nil]) {
|
2013-08-31 16:29:56 +00:00
|
|
|
self.storeManager.localStoreURL = oldLocalStoreURL;
|
|
|
|
return NO;
|
|
|
|
}
|
2013-04-07 23:05:51 +00:00
|
|
|
|
2013-05-10 15:13:55 +00:00
|
|
|
inf(@"Successfully migrated to new local store.");
|
2013-08-31 16:29:56 +00:00
|
|
|
return YES;
|
2013-04-07 23:05:51 +00:00
|
|
|
}
|
|
|
|
|
2013-08-31 16:29:56 +00:00
|
|
|
- (BOOL)migrateFromCloudStore:(NSURL *)oldCloudStoreURL cloudContent:(NSURL *)oldCloudContentURL contentName:(NSString *)contentName {
|
2013-04-07 23:05:51 +00:00
|
|
|
|
2013-05-10 15:13:55 +00:00
|
|
|
if (![self.storeManager cloudSafeForSeeding]) {
|
|
|
|
inf(@"Can't migrate cloud store: A new cloud store already exists.");
|
2013-08-31 16:29:56 +00:00
|
|
|
return YES;
|
2013-05-02 15:19:34 +00:00
|
|
|
}
|
2013-04-07 23:05:51 +00:00
|
|
|
|
2013-05-10 15:13:55 +00:00
|
|
|
NSURL *newCloudStoreURL = [self.storeManager URLForCloudStore];
|
2013-09-17 03:18:01 +00:00
|
|
|
if (![self.storeManager migrateStore:oldCloudStoreURL withOptions:nil
|
|
|
|
toStore:newCloudStoreURL withOptions:nil
|
|
|
|
strategy:0 error:nil cause:nil context:nil])
|
2013-08-31 16:29:56 +00:00
|
|
|
return NO;
|
2013-04-07 23:05:51 +00:00
|
|
|
|
2013-05-10 15:13:55 +00:00
|
|
|
inf(@"Successfully migrated to new cloud store.");
|
2013-08-31 16:29:56 +00:00
|
|
|
return YES;
|
2013-04-07 23:05:51 +00:00
|
|
|
}
|
|
|
|
|
2012-05-09 17:34:00 +00:00
|
|
|
#pragma mark - UbiquityStoreManagerDelegate
|
|
|
|
|
2013-05-11 23:43:41 +00:00
|
|
|
- (NSManagedObjectContext *)managedObjectContextForUbiquityChangesInManager:(UbiquityStoreManager *)manager {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-05-11 23:43:41 +00:00
|
|
|
return [self mainManagedObjectContextIfReady];
|
2012-05-09 17:34:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-09-21 14:34:48 +00:00
|
|
|
inf(@"[StoreManager] %@", message);
|
2012-05-09 17:34:00 +00:00
|
|
|
}
|
|
|
|
|
2013-04-03 23:25:15 +00:00
|
|
|
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-05-11 23:43:41 +00:00
|
|
|
NSManagedObjectContext *moc = [self mainManagedObjectContextIfReady];
|
2013-05-11 12:54:49 +00:00
|
|
|
[moc performBlockAndWait:^{
|
|
|
|
[moc saveToStore];
|
2013-10-17 12:15:29 +00:00
|
|
|
[moc reset];
|
2013-04-06 23:39:54 +00:00
|
|
|
|
2013-05-11 12:54:49 +00:00
|
|
|
self.privateManagedObjectContext = nil;
|
|
|
|
self.mainManagedObjectContext = nil;
|
|
|
|
}];
|
2013-04-03 23:25:15 +00:00
|
|
|
|
2013-04-07 23:05:51 +00:00
|
|
|
[self migrateStoreForManager:manager isCloud:isCloudStore];
|
2013-04-03 23:25:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator
|
|
|
|
isCloud:(BOOL)isCloudStore {
|
|
|
|
|
|
|
|
inf(@"Using iCloud? %@", @(isCloudStore));
|
2013-04-20 17:51:37 +00:00
|
|
|
MPCheckpoint( MPCheckpointCloud, @{
|
|
|
|
@"enabled" : @(isCloudStore)
|
|
|
|
} );
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-04-03 23:25:15 +00:00
|
|
|
// Create our contexts.
|
2013-05-10 15:13:55 +00:00
|
|
|
NSManagedObjectContext
|
|
|
|
*privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
2013-04-03 23:25:15 +00:00
|
|
|
[privateManagedObjectContext performBlockAndWait:^{
|
|
|
|
privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
|
|
|
|
privateManagedObjectContext.persistentStoreCoordinator = coordinator;
|
2013-05-10 15:13:55 +00:00
|
|
|
|
|
|
|
// dbg(@"===");
|
|
|
|
// NSError *error;
|
|
|
|
// for (NSEntityDescription *entityDescription in [coordinator.managedObjectModel entities]) {
|
|
|
|
// dbg(@"Entities: %@", entityDescription.name);
|
|
|
|
// NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:entityDescription.name];
|
|
|
|
// NSArray *entities = [privateManagedObjectContext executeFetchRequest:request error:&error];
|
|
|
|
// if (!entities)
|
|
|
|
// err(@" - Error: %@", error);
|
|
|
|
// else
|
|
|
|
// for (id entity in entities)
|
|
|
|
// dbg(@" - %@", [entity debugDescription]);
|
|
|
|
// }
|
|
|
|
// dbg(@"===");
|
2013-04-03 23:25:15 +00:00
|
|
|
}];
|
|
|
|
|
|
|
|
NSManagedObjectContext *mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
|
|
|
mainManagedObjectContext.parentContext = privateManagedObjectContext;
|
|
|
|
|
2013-04-05 04:31:05 +00:00
|
|
|
self.privateManagedObjectContext = privateManagedObjectContext;
|
|
|
|
self.mainManagedObjectContext = mainManagedObjectContext;
|
2012-05-11 20:45:05 +00:00
|
|
|
}
|
|
|
|
|
2013-04-03 23:25:15 +00:00
|
|
|
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreErrorCause)cause
|
2013-04-02 02:03:28 +00:00
|
|
|
context:(id)context {
|
|
|
|
|
2013-09-13 12:14:58 +00:00
|
|
|
err(@"[StoreManager] ERROR: cause=%@, context=%@, error=%@", NSStringFromUSMCause( cause ), context, error);
|
2013-04-20 17:51:37 +00:00
|
|
|
MPCheckpoint( MPCheckpointMPErrorUbiquity, @{
|
2013-04-03 23:25:15 +00:00
|
|
|
@"cause" : @(cause),
|
2013-09-13 12:14:58 +00:00
|
|
|
@"error.code" : @(error.code),
|
2013-09-21 14:34:48 +00:00
|
|
|
@"error.domain" : NilToNSNull(error.domain),
|
|
|
|
@"error.reason" : NilToNSNull(IfNotNilElse( [error localizedFailureReason], [error localizedDescription] )),
|
2013-04-20 17:51:37 +00:00
|
|
|
} );
|
2013-04-03 23:25:15 +00:00
|
|
|
}
|
2013-04-02 02:03:28 +00:00
|
|
|
|
2013-05-07 04:45:06 +00:00
|
|
|
#pragma mark - Utilities
|
|
|
|
|
|
|
|
- (void)addElementNamed:(NSString *)siteName completion:(void (^)(MPElementEntity *element))completion {
|
|
|
|
|
|
|
|
if (![siteName length]) {
|
|
|
|
completion( nil );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-06-16 16:39:52 +00:00
|
|
|
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
|
|
|
MPUserEntity *activeUser = [self activeUserInContext:context];
|
2013-09-13 12:14:58 +00:00
|
|
|
NSAssert(activeUser, @"Missing user.");
|
|
|
|
if (!activeUser)
|
|
|
|
return;
|
2013-05-07 04:45:06 +00:00
|
|
|
|
|
|
|
MPElementType type = activeUser.defaultType;
|
|
|
|
if (!type)
|
|
|
|
type = activeUser.defaultType = MPElementTypeGeneratedLong;
|
|
|
|
NSString *typeEntityClassName = [MPAlgorithmDefault classNameOfType:type];
|
|
|
|
|
|
|
|
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityClassName
|
2013-06-16 16:39:52 +00:00
|
|
|
inManagedObjectContext:context];
|
2013-05-07 04:45:06 +00:00
|
|
|
|
|
|
|
element.name = siteName;
|
|
|
|
element.user = activeUser;
|
|
|
|
element.type = type;
|
|
|
|
element.lastUsed = [NSDate date];
|
|
|
|
element.version = MPAlgorithmDefaultVersion;
|
2013-06-16 16:39:52 +00:00
|
|
|
[context saveToStore];
|
2013-05-07 04:45:06 +00:00
|
|
|
|
|
|
|
NSError *error = nil;
|
2013-06-16 16:39:52 +00:00
|
|
|
if (element.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ element ] error:&error])
|
2013-05-07 04:45:06 +00:00
|
|
|
err(@"Failed to obtain a permanent object ID after creating new element: %@", error);
|
|
|
|
|
|
|
|
NSManagedObjectID *elementOID = [element objectID];
|
|
|
|
dispatch_async( dispatch_get_main_queue(), ^{
|
2013-08-15 23:38:05 +00:00
|
|
|
completion(
|
|
|
|
(MPElementEntity *)[[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady] objectRegisteredForID:elementOID] );
|
2013-05-07 04:45:06 +00:00
|
|
|
} );
|
|
|
|
}];
|
|
|
|
}
|
2012-05-09 17:34:00 +00:00
|
|
|
|
2013-06-15 05:39:24 +00:00
|
|
|
- (MPElementEntity *)changeElement:(MPElementEntity *)element inContext:(NSManagedObjectContext *)context toType:(MPElementType)type {
|
|
|
|
|
|
|
|
if ([element.algorithm classOfType:type] == element.typeClass)
|
|
|
|
element.type = type;
|
|
|
|
|
|
|
|
else {
|
|
|
|
// Type requires a different class of element. Recreate the element.
|
|
|
|
MPElementEntity *newElement
|
|
|
|
= [NSEntityDescription insertNewObjectForEntityForName:[element.algorithm classNameOfType:type]
|
|
|
|
inManagedObjectContext:context];
|
|
|
|
newElement.type = type;
|
|
|
|
newElement.name = element.name;
|
|
|
|
newElement.user = element.user;
|
|
|
|
newElement.uses = element.uses;
|
|
|
|
newElement.lastUsed = element.lastUsed;
|
|
|
|
newElement.version = element.version;
|
|
|
|
newElement.loginName = element.loginName;
|
|
|
|
|
|
|
|
[context deleteObject:element];
|
|
|
|
[context saveToStore];
|
|
|
|
|
|
|
|
NSError *error;
|
|
|
|
if (![context obtainPermanentIDsForObjects:@[ newElement ] error:&error])
|
|
|
|
err(@"Failed to obtain a permanent object ID after changing object type: %@", error);
|
|
|
|
|
|
|
|
element = newElement;
|
|
|
|
}
|
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification object:element.objectID];
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
2012-08-19 07:34:49 +00:00
|
|
|
- (MPImportResult)importSites:(NSString *)importedSitesString
|
|
|
|
askImportPassword:(NSString *(^)(NSString *userName))importPassword
|
|
|
|
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword {
|
|
|
|
|
2013-05-16 02:42:21 +00:00
|
|
|
NSAssert(![[NSThread currentThread] isMainThread], @"This method should not be invoked from the main thread.");
|
|
|
|
|
|
|
|
__block MPImportResult result = MPImportResultCancelled;
|
|
|
|
do {
|
|
|
|
if ([MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
|
|
|
result = [self importSites:importedSitesString askImportPassword:importPassword askUserPassword:userPassword
|
|
|
|
saveInContext:context];
|
|
|
|
}])
|
|
|
|
break;
|
|
|
|
usleep( (useconds_t)(USEC_PER_SEC * 0.2) );
|
|
|
|
} while (YES);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (MPImportResult)importSites:(NSString *)importedSitesString
|
|
|
|
askImportPassword:(NSString *(^)(NSString *userName))importPassword
|
|
|
|
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword
|
|
|
|
saveInContext:(NSManagedObjectContext *)context {
|
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
// Compile patterns.
|
2012-05-09 08:11:34 +00:00
|
|
|
static NSRegularExpression *headerPattern, *sitePattern;
|
2013-01-31 05:42:32 +00:00
|
|
|
NSError *error = nil;
|
2012-05-09 08:11:34 +00:00
|
|
|
if (!headerPattern) {
|
2013-04-07 23:05:51 +00:00
|
|
|
headerPattern = [[NSRegularExpression alloc]
|
|
|
|
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
|
|
|
options:(NSRegularExpressionOptions)0 error:&error];
|
2013-01-31 05:42:32 +00:00
|
|
|
if (error) {
|
|
|
|
err(@"Error loading the header pattern: %@", error);
|
|
|
|
return MPImportResultInternalError;
|
|
|
|
}
|
2012-05-09 08:11:34 +00:00
|
|
|
}
|
|
|
|
if (!sitePattern) {
|
2013-04-07 23:05:51 +00:00
|
|
|
sitePattern = [[NSRegularExpression alloc]
|
|
|
|
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)(:[[:digit:]]+)?[[:space:]]+([^\t]+)\t(.*)"
|
|
|
|
options:(NSRegularExpressionOptions)0 error:&error];
|
2013-01-31 05:42:32 +00:00
|
|
|
if (error) {
|
|
|
|
err(@"Error loading the site pattern: %@", error);
|
|
|
|
return MPImportResultInternalError;
|
|
|
|
}
|
2012-05-09 08:11:34 +00:00
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
// Parse import data.
|
|
|
|
inf(@"Importing sites.");
|
2012-08-19 07:34:49 +00:00
|
|
|
__block MPUserEntity *user = nil;
|
|
|
|
id<MPAlgorithm> importAlgorithm = nil;
|
|
|
|
NSString *importBundleVersion = nil, *importUserName = nil;
|
|
|
|
NSData *importKeyID = nil;
|
|
|
|
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
2013-04-20 18:11:19 +00:00
|
|
|
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
|
|
|
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
2012-05-09 08:11:34 +00:00
|
|
|
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
|
2013-04-20 18:11:19 +00:00
|
|
|
NSFetchRequest *elementFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
|
|
|
|
for (NSString *importedSiteLine in importedSiteLines) {
|
2012-05-09 08:11:34 +00:00
|
|
|
if ([importedSiteLine hasPrefix:@"#"]) {
|
|
|
|
// Comment or header
|
|
|
|
if (!headerStarted) {
|
|
|
|
if ([importedSiteLine isEqualToString:@"##"])
|
|
|
|
headerStarted = YES;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (headerEnded)
|
|
|
|
continue;
|
|
|
|
if ([importedSiteLine isEqualToString:@"##"]) {
|
|
|
|
headerEnded = YES;
|
|
|
|
continue;
|
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
// Header
|
2013-04-07 23:05:51 +00:00
|
|
|
if ([headerPattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
|
|
|
|
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
|
2012-05-09 08:11:34 +00:00
|
|
|
err(@"Invalid header format in line: %@", importedSiteLine);
|
|
|
|
return MPImportResultMalformedInput;
|
|
|
|
}
|
2013-04-07 23:05:51 +00:00
|
|
|
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
|
2013-04-20 18:11:19 +00:00
|
|
|
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
|
|
|
|
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
|
|
|
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
2012-06-22 14:52:33 +00:00
|
|
|
if ([headerName isEqualToString:@"User Name"]) {
|
2012-08-19 07:34:49 +00:00
|
|
|
importUserName = headerValue;
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-04-20 18:11:19 +00:00
|
|
|
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
2012-08-19 07:34:49 +00:00
|
|
|
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
|
2013-05-16 02:42:21 +00:00
|
|
|
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
|
2012-08-04 08:16:58 +00:00
|
|
|
if (!users) {
|
2012-08-19 07:34:49 +00:00
|
|
|
err(@"While looking for user: %@, error: %@", importUserName, error);
|
2012-08-04 08:16:58 +00:00
|
|
|
return MPImportResultInternalError;
|
|
|
|
}
|
|
|
|
if ([users count] > 1) {
|
2012-10-31 02:54:34 +00:00
|
|
|
err(@"While looking for user: %@, found more than one: %lu", importUserName, (unsigned long)[users count]);
|
2012-08-04 08:16:58 +00:00
|
|
|
return MPImportResultInternalError;
|
|
|
|
}
|
|
|
|
|
|
|
|
user = [users count]? [users lastObject]: nil;
|
2012-07-30 05:58:18 +00:00
|
|
|
dbg(@"Found user: %@", [user debugDescription]);
|
2012-06-04 09:27:02 +00:00
|
|
|
}
|
2012-07-17 20:57:11 +00:00
|
|
|
if ([headerName isEqualToString:@"Key ID"])
|
2013-04-20 18:11:19 +00:00
|
|
|
importKeyID = [headerValue decodeHex];
|
2012-08-19 07:34:49 +00:00
|
|
|
if ([headerName isEqualToString:@"Version"]) {
|
|
|
|
importBundleVersion = headerValue;
|
2013-04-20 18:11:19 +00:00
|
|
|
importAlgorithm = MPAlgorithmDefaultForBundleVersion( importBundleVersion );
|
2012-08-19 07:34:49 +00:00
|
|
|
}
|
2012-06-22 14:52:33 +00:00
|
|
|
if ([headerName isEqualToString:@"Passwords"]) {
|
|
|
|
if ([headerValue isEqualToString:@"VISIBLE"])
|
|
|
|
clearText = YES;
|
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!headerEnded)
|
|
|
|
continue;
|
2012-08-19 07:34:49 +00:00
|
|
|
if (!importKeyID || ![importUserName length])
|
2012-05-09 08:11:34 +00:00
|
|
|
return MPImportResultMalformedInput;
|
|
|
|
if (![importedSiteLine length])
|
|
|
|
continue;
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
// Site
|
2013-04-07 23:05:51 +00:00
|
|
|
if ([sitePattern numberOfMatchesInString:importedSiteLine options:(NSMatchingOptions)0
|
|
|
|
range:NSMakeRange( 0, [importedSiteLine length] )] != 1) {
|
2012-05-09 08:11:34 +00:00
|
|
|
err(@"Invalid site format in line: %@", importedSiteLine);
|
|
|
|
return MPImportResultMalformedInput;
|
|
|
|
}
|
2013-04-20 18:11:19 +00:00
|
|
|
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
|
|
|
|
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
|
|
|
|
NSString *lastUsed = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:1]];
|
|
|
|
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
|
|
|
|
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
|
|
|
|
NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
2013-04-30 05:49:53 +00:00
|
|
|
if ([version length])
|
|
|
|
version = [version substringFromIndex:1]; // Strip the leading colon.
|
2013-04-20 18:11:19 +00:00
|
|
|
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
|
|
|
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
// Find existing site.
|
2012-06-04 09:27:02 +00:00
|
|
|
if (user) {
|
2012-08-19 07:34:49 +00:00
|
|
|
elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
|
2013-05-16 02:42:21 +00:00
|
|
|
NSArray *existingSites = [context executeFetchRequest:elementFetchRequest error:&error];
|
2012-06-14 19:56:54 +00:00
|
|
|
if (!existingSites) {
|
2012-07-30 05:58:18 +00:00
|
|
|
err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error);
|
2012-06-04 09:27:02 +00:00
|
|
|
return MPImportResultInternalError;
|
2013-04-20 18:11:19 +00:00
|
|
|
}
|
|
|
|
else if (existingSites.count)
|
|
|
|
dbg(@"Existing sites: %@", existingSites);
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-06-04 09:27:02 +00:00
|
|
|
[elementsToDelete addObjectsFromArray:existingSites];
|
2013-04-20 18:11:19 +00:00
|
|
|
[importedSiteElements addObject:@[ lastUsed, uses, type, version, name, exportContent ]];
|
2013-04-30 05:49:53 +00:00
|
|
|
dbg(@"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, name=%@, exportContent=%@",
|
|
|
|
lastUsed, uses, type, version, name, exportContent);
|
2012-06-04 09:27:02 +00:00
|
|
|
}
|
2012-05-09 08:11:34 +00:00
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-08-19 07:34:49 +00:00
|
|
|
// Ask for confirmation to import these sites and the master password of the user.
|
2012-10-31 02:54:34 +00:00
|
|
|
inf(@"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count], (unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName]);
|
2013-04-20 18:11:19 +00:00
|
|
|
NSString *userMasterPassword = userPassword( user.name, [importedSiteElements count], [elementsToDelete count] );
|
2012-08-19 07:34:49 +00:00
|
|
|
if (!userMasterPassword) {
|
2012-06-14 19:56:54 +00:00
|
|
|
inf(@"Import cancelled.");
|
2012-05-09 08:11:34 +00:00
|
|
|
return MPImportResultCancelled;
|
2012-06-14 19:56:54 +00:00
|
|
|
}
|
2012-08-19 07:34:49 +00:00
|
|
|
MPKey *userKey = [MPAlgorithmDefault keyForPassword:userMasterPassword ofUserNamed:user.name];
|
|
|
|
if (![userKey.keyID isEqualToData:user.keyID])
|
|
|
|
return MPImportResultInvalidPassword;
|
|
|
|
__block MPKey *importKey = userKey;
|
|
|
|
if ([importKey.keyID isEqualToData:importKeyID])
|
|
|
|
importKey = nil;
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
// Delete existing sites.
|
|
|
|
if (elementsToDelete.count)
|
|
|
|
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
|
|
|
inf(@"Deleting site: %@, it will be replaced by an imported site.", [obj name]);
|
2013-05-16 02:42:21 +00:00
|
|
|
[context deleteObject:obj];
|
2013-01-31 05:42:32 +00:00
|
|
|
}];
|
|
|
|
|
|
|
|
// Make sure there is a user.
|
|
|
|
if (!user) {
|
2013-04-20 18:11:19 +00:00
|
|
|
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
|
2013-05-16 02:42:21 +00:00
|
|
|
inManagedObjectContext:context];
|
2013-04-20 18:11:19 +00:00
|
|
|
user.name = importUserName;
|
2013-01-31 05:42:32 +00:00
|
|
|
user.keyID = importKeyID;
|
|
|
|
dbg(@"Created User: %@", [user debugDescription]);
|
|
|
|
}
|
2012-08-24 08:12:15 +00:00
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
// Import new sites.
|
|
|
|
for (NSArray *siteElements in importedSiteElements) {
|
2013-09-28 01:23:52 +00:00
|
|
|
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]];
|
|
|
|
NSUInteger uses = (unsigned)[siteElements[1] integerValue];
|
|
|
|
MPElementType type = (MPElementType)[siteElements[2] integerValue];
|
|
|
|
NSUInteger version = (unsigned)[siteElements[3] integerValue];
|
|
|
|
NSString *name = siteElements[4];
|
|
|
|
NSString *exportContent = siteElements[5];
|
2013-01-31 05:42:32 +00:00
|
|
|
|
|
|
|
// Create new site.
|
2013-04-20 18:11:19 +00:00
|
|
|
MPElementEntity
|
|
|
|
*element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion( version ) classNameOfType:type]
|
2013-05-16 02:42:21 +00:00
|
|
|
inManagedObjectContext:context];
|
2013-04-20 18:11:19 +00:00
|
|
|
element.name = name;
|
|
|
|
element.user = user;
|
|
|
|
element.type = type;
|
|
|
|
element.uses = uses;
|
2013-01-31 05:42:32 +00:00
|
|
|
element.lastUsed = lastUsed;
|
2013-04-20 18:11:19 +00:00
|
|
|
element.version = version;
|
2013-01-31 05:42:32 +00:00
|
|
|
if ([exportContent length]) {
|
|
|
|
if (clearText)
|
2013-09-13 12:14:58 +00:00
|
|
|
[element.algorithm importClearTextContent:exportContent intoElement:element usingKey:userKey];
|
2013-01-31 05:42:32 +00:00
|
|
|
else {
|
|
|
|
if (!importKey)
|
2013-04-20 18:11:19 +00:00
|
|
|
importKey = [importAlgorithm keyForPassword:importPassword( user.name ) ofUserNamed:user.name];
|
2013-01-31 05:42:32 +00:00
|
|
|
if (![importKey.keyID isEqualToData:importKeyID])
|
|
|
|
return MPImportResultInvalidPassword;
|
|
|
|
|
2013-09-13 12:14:58 +00:00
|
|
|
[element.algorithm importProtectedContent:exportContent protectedByKey:importKey intoElement:element usingKey:userKey];
|
2013-01-31 05:42:32 +00:00
|
|
|
}
|
2012-07-14 21:57:06 +00:00
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
dbg(@"Created Element: %@", [element debugDescription]);
|
|
|
|
}
|
|
|
|
|
2013-05-16 02:42:21 +00:00
|
|
|
if (![context saveToStore])
|
2013-01-31 05:42:32 +00:00
|
|
|
return MPImportResultInternalError;
|
|
|
|
|
|
|
|
inf(@"Import completed successfully.");
|
2013-04-20 17:51:37 +00:00
|
|
|
MPCheckpoint( MPCheckpointSitesImported, nil );
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
return MPImportResultSuccess;
|
2012-05-09 08:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-06-16 16:39:52 +00:00
|
|
|
MPUserEntity *activeUser = [self activeUserForMainThread];
|
2013-01-31 05:42:32 +00:00
|
|
|
inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", activeUser.userID);
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
// Header.
|
|
|
|
NSMutableString *export = [NSMutableString new];
|
|
|
|
[export appendFormat:@"# Master Password site export\n"];
|
|
|
|
if (showPasswords)
|
|
|
|
[export appendFormat:@"# Export of site names and passwords in clear-text.\n"];
|
|
|
|
else
|
|
|
|
[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:@"# Version: %@\n", [PearlInfoPlist get].CFBundleVersion];
|
2013-01-31 05:42:32 +00:00
|
|
|
[export appendFormat:@"# User Name: %@\n", activeUser.name];
|
|
|
|
[export appendFormat:@"# Key ID: %@\n", [activeUser.keyID encodeHex]];
|
2012-06-14 19:56:54 +00:00
|
|
|
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
|
2012-05-09 08:11:34 +00:00
|
|
|
if (showPasswords)
|
|
|
|
[export appendFormat:@"# Passwords: VISIBLE\n"];
|
|
|
|
else
|
|
|
|
[export appendFormat:@"# Passwords: PROTECTED\n"];
|
|
|
|
[export appendFormat:@"##\n"];
|
|
|
|
[export appendFormat:@"#\n"];
|
|
|
|
[export appendFormat:@"# Last Times Password Site\tSite\n"];
|
|
|
|
[export appendFormat:@"# used used type name\tpassword\n"];
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
// Sites.
|
2013-01-31 05:42:32 +00:00
|
|
|
for (MPElementEntity *element in activeUser.elements) {
|
2012-06-08 21:46:13 +00:00
|
|
|
NSDate *lastUsed = element.lastUsed;
|
2013-04-20 18:11:19 +00:00
|
|
|
NSUInteger uses = element.uses;
|
|
|
|
MPElementType type = element.type;
|
|
|
|
NSUInteger version = element.version;
|
|
|
|
NSString *name = element.name;
|
2012-06-08 21:46:13 +00:00
|
|
|
NSString *content = nil;
|
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
// Determine the content to export.
|
|
|
|
if (!(type & MPElementFeatureDevicePrivate)) {
|
|
|
|
if (showPasswords)
|
2013-09-13 12:14:58 +00:00
|
|
|
content = [element.algorithm resolveContentForElement:element usingKey:self.key];
|
2013-04-20 18:11:19 +00:00
|
|
|
else if (type & MPElementFeatureExportContent)
|
2013-09-13 12:14:58 +00:00
|
|
|
content = [element.algorithm exportContentForElement:element usingKey:self.key];
|
2012-05-09 08:11:34 +00:00
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-10-31 02:54:34 +00:00
|
|
|
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n",
|
|
|
|
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses,
|
2013-04-20 18:11:19 +00:00
|
|
|
[PearlString( @"%u:%lu", type, (unsigned long)version ) UTF8String], [name UTF8String], content
|
|
|
|
? content: @""];
|
2012-05-09 08:11:34 +00:00
|
|
|
}
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2013-04-20 17:51:37 +00:00
|
|
|
MPCheckpoint( MPCheckpointSitesExported, @{
|
|
|
|
@"showPasswords" : @(showPasswords)
|
|
|
|
} );
|
2012-06-08 21:46:13 +00:00
|
|
|
|
2012-05-09 08:11:34 +00:00
|
|
|
return export;
|
|
|
|
}
|
2012-05-07 20:18:01 +00:00
|
|
|
|
|
|
|
@end
|