2
0

Add marshalling metadata lookup & adapt iOS for new APIs.

This commit is contained in:
Maarten Billemont
2017-08-12 21:57:47 -04:00
parent c0ba96daa2
commit f5c7d11f0e
35 changed files with 821 additions and 886 deletions

View File

@@ -137,7 +137,7 @@
- (void)updatePassword {
NSString *siteName = self.siteField.text;
MPSiteType siteType = [self siteType];
MPResultType siteType = [self siteType];
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
@@ -147,8 +147,8 @@
[self.emergencyPasswordQueue addOperationWithBlock:^{
NSString *sitePassword = nil;
if (self.key && [siteName length])
sitePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:siteName ofType:siteType withCounter:siteCounter
usingKey:self.key];
sitePassword = [MPAlgorithmDefault mpwTemplateForSiteNamed:siteName ofType:siteType withCounter:siteCounter
usingKey:self.key];
PearlMainQueue( ^{
[self.activity stopAnimating];
@@ -157,21 +157,21 @@
}];
}
- (enum MPSiteType)siteType {
- (MPResultType)siteType {
switch (self.typeControl.selectedSegmentIndex) {
case 0:
return MPSiteTypeGeneratedMaximum;
return MPResultTypeTemplateMaximum;
case 1:
return MPSiteTypeGeneratedLong;
return MPResultTypeTemplateLong;
case 2:
return MPSiteTypeGeneratedMedium;
return MPResultTypeTemplateMedium;
case 3:
return MPSiteTypeGeneratedBasic;
return MPResultTypeTemplateBasic;
case 4:
return MPSiteTypeGeneratedShort;
return MPResultTypeTemplateShort;
case 5:
return MPSiteTypeGeneratedPIN;
return MPResultTypeTemplatePIN;
default:
Throw( @"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex );
}

View File

@@ -60,15 +60,15 @@
self.touchIDSwitch.on = activeUser.touchID;
self.touchIDSwitch.enabled = self.savePasswordSwitch.on && [[MPiOSAppDelegate get] isFeatureUnlocked:MPProductTouchID];
MPSiteType defaultType = activeUser.defaultType;
MPResultType defaultType = activeUser.defaultType;
self.generated1TypeControl.selectedSegmentIndex = [self generated1SegmentIndexForType:defaultType];
self.generated2TypeControl.selectedSegmentIndex = [self generated2SegmentIndexForType:defaultType];
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:defaultType];
PearlNotMainQueue( ^{
NSString *examplePassword = nil;
if (defaultType & MPSiteTypeClassGenerated)
examplePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:@"test" ofType:defaultType
withCounter:1 usingKey:[MPiOSAppDelegate get].key];
if (defaultType & MPResultTypeClassTemplate)
examplePassword = [MPAlgorithmDefault mpwTemplateForSiteNamed:@"test" ofType:defaultType
withCounter:1 usingKey:[MPiOSAppDelegate get].key];
PearlMainQueue( ^{
self.typeSamplePassword.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil;
} );
@@ -164,7 +164,7 @@
if (sender != self.storedTypeControl)
self.storedTypeControl.selectedSegmentIndex = -1;
MPSiteType defaultType = [self typeForSelectedSegment];
MPResultType defaultType = [self typeForSelectedSegment];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType = defaultType;
[context saveToStore];
@@ -242,7 +242,7 @@
return nil;
}
- (MPSiteType)typeForSelectedSegment {
- (MPResultType)typeForSelectedSegment {
NSInteger selectedGenerated1Index = self.generated1TypeControl.selectedSegmentIndex;
NSInteger selectedGenerated2Index = self.generated2TypeControl.selectedSegmentIndex;
@@ -250,30 +250,30 @@
switch (selectedGenerated1Index) {
case 0:
return MPSiteTypeGeneratedPhrase;
return MPResultTypeTemplatePhrase;
case 1:
return MPSiteTypeGeneratedName;
return MPResultTypeTemplateName;
default:
switch (selectedGenerated2Index) {
case 0:
return MPSiteTypeGeneratedMaximum;
return MPResultTypeTemplateMaximum;
case 1:
return MPSiteTypeGeneratedLong;
return MPResultTypeTemplateLong;
case 2:
return MPSiteTypeGeneratedMedium;
return MPResultTypeTemplateMedium;
case 3:
return MPSiteTypeGeneratedBasic;
return MPResultTypeTemplateBasic;
case 4:
return MPSiteTypeGeneratedShort;
return MPResultTypeTemplateShort;
case 5:
return MPSiteTypeGeneratedPIN;
return MPResultTypeTemplatePIN;
default:
switch (selectedStoredIndex) {
case 0:
return MPSiteTypeStoredPersonal;
return MPResultTypeStatefulPersonal;
case 1:
return MPSiteTypeStoredDevicePrivate;
return MPResultTypeStatefulDevice;
default:
Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld",
(long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex );
@@ -282,44 +282,44 @@
}
}
- (NSInteger)generated1SegmentIndexForType:(MPSiteType)type {
- (NSInteger)generated1SegmentIndexForType:(MPResultType)type {
switch (type) {
case MPSiteTypeGeneratedPhrase:
case MPResultTypeTemplatePhrase:
return 0;
case MPSiteTypeGeneratedName:
case MPResultTypeTemplateName:
return 1;
default:
return -1;
}
}
- (NSInteger)generated2SegmentIndexForType:(MPSiteType)type {
- (NSInteger)generated2SegmentIndexForType:(MPResultType)type {
switch (type) {
case MPSiteTypeGeneratedMaximum:
case MPResultTypeTemplateMaximum:
return 0;
case MPSiteTypeGeneratedLong:
case MPResultTypeTemplateLong:
return 1;
case MPSiteTypeGeneratedMedium:
case MPResultTypeTemplateMedium:
return 2;
case MPSiteTypeGeneratedBasic:
case MPResultTypeTemplateBasic:
return 3;
case MPSiteTypeGeneratedShort:
case MPResultTypeTemplateShort:
return 4;
case MPSiteTypeGeneratedPIN:
case MPResultTypeTemplatePIN:
return 5;
default:
return -1;
}
}
- (NSInteger)storedSegmentIndexForType:(MPSiteType)type {
- (NSInteger)storedSegmentIndexForType:(MPResultType)type {
switch (type) {
case MPSiteTypeStoredPersonal:
case MPResultTypeStatefulPersonal:
return 0;
case MPSiteTypeStoredDevicePrivate:
case MPResultTypeStatefulDevice:
return 1;
default:
return -1;

View File

@@ -284,7 +284,7 @@
[PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic
initSheet:^(UIActionSheet *sheet) {
for (NSNumber *typeNumber in [mainSite.algorithm allTypes]) {
MPSiteType type = (MPSiteType)[typeNumber unsignedIntegerValue];
MPResultType type = (MPResultType)[typeNumber unsignedIntegerValue];
NSString *typeName = [mainSite.algorithm nameOfType:type];
if (type == mainSite.type)
[sheet addButtonWithTitle:strf( @"● %@", typeName )];
@@ -295,7 +295,7 @@
if (buttonIndex == [sheet cancelButtonIndex])
return;
MPSiteType type = (MPSiteType)[[mainSite.algorithm allTypes][buttonIndex] unsignedIntegerValue]?:
MPResultType type = (MPResultType)[[mainSite.algorithm allTypes][buttonIndex] unsignedIntegerValue]?:
mainSite.user.defaultType?: mainSite.algorithm.defaultType;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
@@ -311,7 +311,7 @@
self.loginNameField.enabled = YES;
self.passwordField.enabled = YES;
if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPSiteTypeClassStored)
if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPResultTypeClassStateful)
[self.passwordField becomeFirstResponder];
else
[self.loginNameField becomeFirstResponder];
@@ -537,7 +537,7 @@
self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length];
self.modeButton.visible = !self.transientSite;
self.modeButton.alpha = settingsMode? 0.5f: 0.1f;
self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPSiteTypeClassGenerated;
self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPResultTypeClassTemplate;
self.modeButton.selected = settingsMode;
self.strengthLabel.gone = !settingsMode;
self.modeScrollView.scrollEnabled = !self.transientSite;
@@ -565,8 +565,8 @@
// Site Password
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
self.passwordField.attributedPlaceholder = stra(
mainSite.type & MPSiteTypeClassStored? strl( @"No password" ):
mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{
mainSite.type & MPResultTypeClassStateful? strl( @"No password" ):
mainSite.type & MPResultTypeClassTemplate? strl( @"..." ): @"", @{
NSForegroundColorAttributeName: [UIColor whiteColor]
} );
@@ -585,10 +585,10 @@
BOOL loginGenerated = site.loginGenerated;
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
MPSiteType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType;
if (self.transientSite && transientType & MPSiteTypeClassGenerated)
password = [MPAlgorithmDefault generatePasswordForSiteNamed:self.transientSite ofType:transientType
withCounter:1 usingKey:key];
MPResultType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPAlgorithmDefault.defaultType;
if (self.transientSite && transientType & MPResultTypeClassTemplate)
password = [MPAlgorithmDefault mpwTemplateForSiteNamed:self.transientSite ofType:transientType
withCounter:1 usingKey:key];
else if (site)
password = [site resolvePasswordUsingKey:key];

View File

@@ -23,8 +23,8 @@
@protocol MPTypeDelegate<NSObject>
@required
- (void)didSelectType:(MPSiteType)type;
- (MPSiteType)selectedType;
- (void)didSelectType:(MPResultType)type;
- (MPResultType)selectedType;
@optional
- (MPSiteEntity *)selectedSite;

View File

@@ -22,7 +22,7 @@
@interface MPTypeViewController()
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath;
- (MPResultType)typeAtIndexPath:(NSIndexPath *)indexPath;
@end
@@ -75,11 +75,11 @@
if ([self.delegate respondsToSelector:@selector( selectedSite )])
selectedSite = [self.delegate selectedSite];
MPSiteType cellType = [self typeAtIndexPath:indexPath];
MPSiteType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType];
MPResultType cellType = [self typeAtIndexPath:indexPath];
MPResultType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType];
cell.selected = (selectedType == cellType);
if (cellType != (MPSiteType)NSNotFound && cellType & MPSiteTypeClassGenerated) {
if (cellType != (MPResultType)NSNotFound && cellType & MPResultTypeClassTemplate) {
[(UITextField *)[cell viewWithTag:2] setText:@"..."];
NSString *name = selectedSite.name;
@@ -88,8 +88,8 @@
counter = ((MPGeneratedSiteEntity *)selectedSite).counter;
PearlNotMainQueue( ^{
NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType
withCounter:counter usingKey:[MPiOSAppDelegate get].key];
NSString *typeContent = [MPAlgorithmDefault mpwTemplateForSiteNamed:name ofType:cellType
withCounter:counter usingKey:[MPiOSAppDelegate get].key];
PearlMainQueue( ^{
[(UITextField *)[[tableView cellForRowAtIndexPath:indexPath] viewWithTag:2] setText:typeContent];
@@ -104,8 +104,8 @@
NSAssert( self.navigationController.topViewController == self, @"Not the currently active navigation item." );
MPSiteType type = [self typeAtIndexPath:indexPath];
if (type == (MPSiteType)NSNotFound)
MPResultType type = [self typeAtIndexPath:indexPath];
if (type == (MPResultType)NSNotFound)
// Selected a non-type row.
return;
@@ -113,28 +113,28 @@
[self.navigationController popViewControllerAnimated:YES];
}
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath {
- (MPResultType)typeAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case 0: {
// Generated
switch (indexPath.row) {
case 0:
return (MPSiteType)NSNotFound;
return (MPResultType)NSNotFound;
case 1:
return MPSiteTypeGeneratedMaximum;
return MPResultTypeTemplateMaximum;
case 2:
return MPSiteTypeGeneratedLong;
return MPResultTypeTemplateLong;
case 3:
return MPSiteTypeGeneratedMedium;
return MPResultTypeTemplateMedium;
case 4:
return MPSiteTypeGeneratedBasic;
return MPResultTypeTemplateBasic;
case 5:
return MPSiteTypeGeneratedShort;
return MPResultTypeTemplateShort;
case 6:
return MPSiteTypeGeneratedPIN;
return MPResultTypeTemplatePIN;
case 7:
return (MPSiteType)NSNotFound;
return (MPResultType)NSNotFound;
default: {
Throw( @"Unsupported row: %ld, when selecting generated site type.", (long)indexPath.row );
@@ -146,13 +146,13 @@
// Stored
switch (indexPath.row) {
case 0:
return (MPSiteType)NSNotFound;
return (MPResultType)NSNotFound;
case 1:
return MPSiteTypeStoredPersonal;
return MPResultTypeStatefulPersonal;
case 2:
return MPSiteTypeStoredDevicePrivate;
return MPResultTypeStatefulDevice;
case 3:
return (MPSiteType)NSNotFound;
return (MPResultType)NSNotFound;
default: {
Throw( @"Unsupported row: %ld, when selecting stored site type.", (long)indexPath.row );

View File

@@ -20,6 +20,7 @@
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "MPStoreViewController.h"
#import "mpw-marshall.h"
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
@@ -177,62 +178,46 @@
return YES;
}
- (void)importSites:(NSString *)importedSitesString {
- (void)importSites:(NSString *)importData {
if ([NSThread isMainThread]) {
PearlNotMainQueue( ^{
[self importSites:importedSitesString];
[self importSites:importData];
} );
return;
}
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
[self importSites:importData askImportPassword:^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
[PearlAlert showAlertWithTitle:@"Import File's Master Password"
message:strf( @"%@'s export was done using a different master password.\n"
@"Enter that master password to unlock the exported data.", userName )
[PearlAlert showAlertWithTitle:strf( @"Importing Sites For\n%@", userName )
message:@"Enter the master password used to create this export file."
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ cancelButtonIndex])
setResult( nil );
else
setResult( [alert_ textFieldAtIndex:0].text );
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
} );
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
} askUserPassword:^NSString *(NSString *userName) {
return PearlAwait( (id)^(void (^setResult)(id)) {
[PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName )
message:strf( @"Imports %lu sites, overwriting %lu.",
(unsigned long)importCount, (unsigned long)deleteCount )
[PearlAlert showAlertWithTitle:strf( @"Master Password For\n%@", userName )
message:@"Enter the current master password for this user."
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ cancelButtonIndex])
setResult( nil );
else
setResult( [alert_ textFieldAtIndex:0].text );
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
} );
} result:^(NSError *error) {
[activityOverlay cancelOverlayAnimated:YES];
if (error && !(error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError))
[PearlAlert showError:error.localizedDescription];
}];
switch (result) {
case MPImportResultSuccess:
case MPImportResultCancelled:
break;
case MPImportResultInternalError:
[PearlAlert showError:@"Import failed because of an internal error."];
break;
case MPImportResultMalformedInput:
[PearlAlert showError:@"The import doesn't look like a Master Password export."];
break;
case MPImportResultInvalidPassword:
[PearlAlert showError:@"Incorrect master password for the import sites."];
break;
}
[activityOverlay cancelOverlayAnimated:YES];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
@@ -250,10 +235,9 @@
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
PearlNotMainQueue( ^{
NSString *importHeader = @"# Master Password site export";
NSString *importedSitesString = [UIPasteboard generalPasteboard].string;
if ([importedSitesString length] > [importHeader length] &&
[[importedSitesString substringToIndex:[importHeader length]] isEqualToString:importHeader])
NSString *importData = [UIPasteboard generalPasteboard].string;
MPMarshallInfo *importInfo = mpw_marshall_read_info( importData.UTF8String );
if (importInfo->format != MPMarshallFormatNone)
[PearlAlert showAlertWithTitle:@"Import Sites?" message:
@"We've detected Master Password import sites on your pasteboard, would you like to import them?"
viewStyle:UIAlertViewStyleDefault initAlert:nil
@@ -261,9 +245,10 @@
if (buttonIndex == [alert cancelButtonIndex])
return;
[self importSites:importedSitesString];
[self importSites:importData];
[UIPasteboard generalPasteboard].string = @"";
} cancelTitle:@"No" otherTitles:@"Import Sites", nil];
mpw_marshal_info_free( importInfo );
} );
[super applicationDidBecomeActive:application];
@@ -449,62 +434,86 @@
return;
}
NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords];
NSString *message;
[self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
[PearlAlert showAlertWithTitle:@"Import File's Master Password"
message:strf( @"%@'s export was done using a different master password.\n"
@"Enter that master password to unlock the exported data.", userName )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_ == [alert_ cancelButtonIndex])
setResult( nil );
else
setResult( [alert_ textFieldAtIndex:0].text );
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
} );
} result:^(NSString *mpsites, NSError *error) {
if (!mpsites || error) {
MPError( error, @"Failed to export mpsites." );
[PearlAlert showAlertWithTitle:@"Export Error"
message:error.localizedDescription
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay
otherTitles:nil];
return;
}
if (revealPasswords)
message = strf( @"Export of Master Password sites with passwords included.\n\n"
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
else
message = strf( @"Backup of Master Password sites.\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
[PearlSheet showSheetWithTitle:@"Export Destination" viewStyle:UIActionSheetStyleBlackTranslucent initSheet:nil
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
NSString *exportFileName = strf( @"%@ (%@).mpsites",
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
NSString *exportFileName = strf( @"%@ (%@).mpsites",
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
[PearlSheet showSheetWithTitle:@"Export Destination" viewStyle:UIActionSheetStyleBlackTranslucent initSheet:nil
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
if (buttonIndex == [sheet firstOtherButtonIndex]) {
NSString *message;
if (revealPasswords)
message = strf( @"Export of Master Password sites with passwords included.\n\n"
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
else
message = strf( @"Backup of Master Password sites.\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
if (buttonIndex == [sheet firstOtherButtonIndex]) {
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc]
initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain" fileName:exportFileName],
nil];
return;
}
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc]
initWithContent:[mpsites dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain" fileName:exportFileName],
nil];
return;
}
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *exportURL = [[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:exportFileName isDirectory:NO];
NSError *error = nil;
if (![[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&error])
MPError( error, @"Failed to write export data to URL %@.", exportURL );
else {
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
self.interactionController.delegate = self;
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
}
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil];
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *exportURL = [[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:exportFileName isDirectory:NO];
NSError *writeError = nil;
if (![[mpsites dataUsingEncoding:NSUTF8StringEncoding]
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
else {
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
self.interactionController.delegate = self;
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
}
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil];
}];
}
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {