Adapt macOS for new APIs.
This commit is contained in:
parent
f5c7d11f0e
commit
d7193f7753
@ -23,11 +23,11 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
//#ifdef NS_ENUM
|
||||
//#define enum(_type, _name) NS_ENUM(_type, _name)
|
||||
//#else
|
||||
#ifdef NS_ENUM
|
||||
#define enum(_type, _name) NS_ENUM(_type, _name)
|
||||
#else
|
||||
#define enum(_type, _name) _type _name; enum
|
||||
//#endif
|
||||
#endif
|
||||
|
||||
//// Types.
|
||||
|
||||
|
@ -64,10 +64,10 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
||||
|
||||
- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
|
||||
- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
|
||||
withCounter:(NSUInteger)counter usingKey:(MPKey *)key;
|
||||
withCounter:(MPCounterValue)counter usingKey:(MPKey *)key;
|
||||
- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
|
||||
- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
|
||||
withCounter:(NSUInteger)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key;
|
||||
withCounter:(MPCounterValue)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key;
|
||||
|
||||
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)key;
|
||||
|
||||
|
@ -180,6 +180,9 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
case MPResultTypeStatefulDevice:
|
||||
return @"Device Private Password";
|
||||
|
||||
case MPResultTypeDeriveKey:
|
||||
return @"Crypto Key";
|
||||
}
|
||||
|
||||
Throw( @"Type not supported: %lu", (long)type );
|
||||
@ -220,6 +223,9 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
case MPResultTypeStatefulDevice:
|
||||
return @"Device";
|
||||
|
||||
case MPResultTypeDeriveKey:
|
||||
return @"Key";
|
||||
}
|
||||
|
||||
Throw( @"Type not supported: %lu", (long)type );
|
||||
@ -265,6 +271,9 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
case MPResultTypeStatefulDevice:
|
||||
return [MPStoredSiteEntity class];
|
||||
|
||||
case MPResultTypeDeriveKey:
|
||||
break;
|
||||
}
|
||||
|
||||
Throw( @"Type not supported: %lu", (long)type );
|
||||
@ -314,6 +323,8 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
return MPResultTypeStatefulDevice;
|
||||
case MPResultTypeStatefulDevice:
|
||||
return MPResultTypeTemplatePhrase;
|
||||
case MPResultTypeDeriveKey:
|
||||
break;
|
||||
}
|
||||
|
||||
return [self defaultType];
|
||||
@ -330,12 +341,12 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
- (NSString *)mpwLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
||||
|
||||
return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplateName parameter:nil withCounter:1
|
||||
return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplateName parameter:nil withCounter:MPCounterValueInitial
|
||||
variant:MPKeyPurposeIdentification context:nil usingKey:key];
|
||||
}
|
||||
|
||||
- (NSString *)mpwTemplateForSiteNamed:(NSString *)name ofType:(MPResultType)type
|
||||
withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
|
||||
withCounter:(MPCounterValue)counter usingKey:(MPKey *)key {
|
||||
|
||||
return [self mpwResultForSiteNamed:name ofType:type parameter:nil withCounter:counter
|
||||
variant:MPKeyPurposeAuthentication context:nil usingKey:key];
|
||||
@ -343,17 +354,18 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
- (NSString *)mpwAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
|
||||
|
||||
return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplatePhrase parameter:nil withCounter:1
|
||||
return [self mpwResultForSiteNamed:name ofType:MPResultTypeTemplatePhrase parameter:nil withCounter:MPCounterValueInitial
|
||||
variant:MPKeyPurposeRecovery context:question usingKey:key];
|
||||
}
|
||||
|
||||
- (NSString *)mpwResultForSiteNamed:(NSString *)name ofType:(MPResultType)type parameter:(NSString *)parameter
|
||||
withCounter:(NSUInteger)counter variant:(MPKeyPurpose)purpose context:(NSString *)context usingKey:(MPKey *)key {
|
||||
withCounter:(MPCounterValue)counter variant:(MPKeyPurpose)purpose context:(NSString *)context
|
||||
usingKey:(MPKey *)key {
|
||||
|
||||
__block NSString *result = nil;
|
||||
[self mpw_perform:^{
|
||||
char const *resultBytes = mpw_siteResult( [key keyForAlgorithm:self],
|
||||
name.UTF8String, (uint32_t)counter, purpose, context.UTF8String, type, parameter.UTF8String, [self version] );
|
||||
name.UTF8String, counter, purpose, context.UTF8String, type, parameter.UTF8String, [self version] );
|
||||
if (resultBytes) {
|
||||
result = [NSString stringWithCString:resultBytes encoding:NSUTF8StringEncoding];
|
||||
mpw_free_string( resultBytes );
|
||||
@ -393,11 +405,11 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
[PearlKeyChain deleteItemForQuery:siteQuery];
|
||||
else
|
||||
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
|
||||
(__bridge id)kSecValueData : state,
|
||||
(__bridge id)kSecValueData: state,
|
||||
#if TARGET_OS_IPHONE
|
||||
(__bridge id)kSecAttrAccessible:
|
||||
site.type & MPSiteFeatureDevicePrivate? (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
||||
: (__bridge id)kSecAttrAccessibleWhenUnlocked,
|
||||
(__bridge id)kSecAttrAccessible:
|
||||
site.type & MPSiteFeatureDevicePrivate? (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
||||
: (__bridge id)kSecAttrAccessibleWhenUnlocked,
|
||||
#endif
|
||||
}];
|
||||
((MPStoredSiteEntity *)site).contentObject = nil;
|
||||
@ -490,7 +502,7 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
break;
|
||||
}
|
||||
|
||||
NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter;
|
||||
MPCounterValue counter = ((MPGeneratedSiteEntity *)site).counter;
|
||||
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [algorithm mpwTemplateForSiteNamed:name ofType:type withCounter:counter usingKey:key] );
|
||||
@ -517,7 +529,12 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
} );
|
||||
break;
|
||||
}
|
||||
|
||||
case MPResultTypeDeriveKey:
|
||||
break;
|
||||
}
|
||||
|
||||
Throw( @"Type not supported: %lu", (long)type );
|
||||
}
|
||||
|
||||
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)key result:(void ( ^ )(NSString *result))resultBlock {
|
||||
@ -563,8 +580,8 @@ static NSOperationQueue *_mpwQueue = nil;
|
||||
NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." );
|
||||
if (cipherText && cipherText.length && site.type & MPResultTypeClassStateful) {
|
||||
NSString *plainText = [self mpwResultForSiteNamed:site.name ofType:site.type parameter:cipherText
|
||||
withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
|
||||
usingKey:importKey];
|
||||
withCounter:MPCounterValueInitial variant:MPKeyPurposeAuthentication context:nil
|
||||
usingKey:importKey];
|
||||
if (plainText)
|
||||
[self savePassword:plainText toSite:site usingKey:key];
|
||||
}
|
||||
|
@ -49,13 +49,16 @@ __END_DECLS
|
||||
} \
|
||||
error; \
|
||||
})
|
||||
#else
|
||||
#define MPError(error_, message, ...) ({ \
|
||||
NSError *error = error_; \
|
||||
err( message @"%@%@", ##__VA_ARGS__, error? @"\n": @"", [error fullDescription]?: @"" ); \
|
||||
error; \
|
||||
})
|
||||
#endif
|
||||
|
||||
#define MPMakeError(message, ...) ({ \
|
||||
MPError( [NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ \
|
||||
NSLocalizedDescriptionKey: strf( message, ##__VA_ARGS__ ) \
|
||||
}], @"" ); \
|
||||
})
|
||||
#else
|
||||
#define MPError(error_, message, ...) ({ \
|
||||
err( message @"%@%@", ##__VA_ARGS__, error_? @"\n": @"", [error_ fullDescription]?: @"" ); \
|
||||
})
|
||||
#endif
|
||||
|
@ -269,23 +269,22 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
[openPanel close];
|
||||
|
||||
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
|
||||
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
|
||||
if (error)
|
||||
MPError( error, @"While reading imported sites from %@.", url );
|
||||
^(NSData *importedSitesData, NSURLResponse *response, NSError *urlError) {
|
||||
if (urlError)
|
||||
[[NSAlert alertWithError:MPError( urlError, @"While reading imported sites from %@.", url )] runModal];
|
||||
if (!importedSitesData)
|
||||
return;
|
||||
|
||||
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
|
||||
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
|
||||
[self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
|
||||
__block NSString *masterPassword = nil;
|
||||
|
||||
PearlMainQueueWait( ^{
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Unlock"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
alert.messageText = @"Import File's Master Password";
|
||||
alert.informativeText = strf( @"%@'s export was done using a different master password.\n"
|
||||
@"Enter that master password to unlock the exported data.", userName );
|
||||
alert.messageText = strf( @"Importing Sites For\n%@", userName );
|
||||
alert.informativeText = @"Enter the master password used to create this export file.";
|
||||
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
||||
[alert layout];
|
||||
if ([alert runModal] == NSAlertFirstButtonReturn)
|
||||
@ -293,16 +292,15 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
} );
|
||||
|
||||
return masterPassword;
|
||||
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
|
||||
} askUserPassword:^NSString *(NSString *userName) {
|
||||
__block NSString *masterPassword = nil;
|
||||
|
||||
PearlMainQueueWait( ^{
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Import"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
alert.messageText = strf( @"Master Password for\n%@", userName );
|
||||
alert.informativeText = strf( @"Imports %lu sites, overwriting %lu.",
|
||||
(unsigned long)importCount, (unsigned long)deleteCount );
|
||||
alert.messageText = strf( @"Master Password For\n%@", userName );
|
||||
alert.informativeText = @"Enter the current master password for this user.";
|
||||
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
||||
[alert layout];
|
||||
if ([alert runModal] == NSAlertFirstButtonReturn)
|
||||
@ -310,37 +308,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
} );
|
||||
|
||||
return masterPassword;
|
||||
} result:^(NSError *error) {
|
||||
[self updateUsers];
|
||||
|
||||
if (error && !(error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError))
|
||||
[[NSAlert alertWithError:error] runModal];
|
||||
}];
|
||||
|
||||
PearlMainQueue( ^{
|
||||
switch (result) {
|
||||
case MPImportResultSuccess: {
|
||||
[self updateUsers];
|
||||
|
||||
NSAlert *alert = [NSAlert new];
|
||||
alert.messageText = @"Successfully imported sites.";
|
||||
[alert runModal];
|
||||
break;
|
||||
}
|
||||
case MPImportResultCancelled:
|
||||
break;
|
||||
case MPImportResultInternalError:
|
||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"Import failed because of an internal error."
|
||||
}]] runModal];
|
||||
break;
|
||||
case MPImportResultMalformedInput:
|
||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"The import doesn't look like a Master Password export."
|
||||
}]] runModal];
|
||||
break;
|
||||
case MPImportResultInvalidPassword:
|
||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"Incorrect master password for the import sites."
|
||||
}]] runModal];
|
||||
break;
|
||||
}
|
||||
} );
|
||||
}] resume];
|
||||
}
|
||||
|
||||
@ -509,25 +482,43 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
if ([savePanel runModal] == NSFileHandlingPanelCancelButton)
|
||||
return;
|
||||
|
||||
NSError *coordinateError = nil;
|
||||
NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords];
|
||||
[[[NSFileCoordinator alloc] initWithFilePresenter:nil] coordinateWritingItemAtURL:savePanel.URL options:0
|
||||
error:&coordinateError byAccessor:
|
||||
^(NSURL *newURL) {
|
||||
NSError *writeError = nil;
|
||||
if (![exportedSites writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError])
|
||||
MPError( writeError, @"Could not write to the export file." );
|
||||
[self exportSitesRevealPasswords:revealPasswords
|
||||
askExportPassword:^NSString *(NSString *userName) {
|
||||
return PearlMainQueueAwait( ^id {
|
||||
NSAlert *alert = [NSAlert new];
|
||||
[alert addButtonWithTitle:@"Import"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
alert.messageText = strf( @"Master Password For\n%@", userName );
|
||||
alert.informativeText = @"Enter the current master password for this user.";
|
||||
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
|
||||
[alert layout];
|
||||
if ([alert runModal] == NSAlertFirstButtonReturn)
|
||||
return ((NSTextField *)alert.accessoryView).stringValue;
|
||||
else
|
||||
return nil;
|
||||
} );
|
||||
} result:^(NSString *mpsites, NSError *error) {
|
||||
if (!mpsites || error) {
|
||||
PearlMainQueue( ^{
|
||||
[[NSAlert alertWithError:MPError( error, @"Failed to export mpsites." )] runModal];
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *coordinateError = nil;
|
||||
[[[NSFileCoordinator alloc] initWithFilePresenter:nil]
|
||||
coordinateWritingItemAtURL:savePanel.URL options:0 error:&coordinateError byAccessor:^(NSURL *newURL) {
|
||||
NSError *writeError = nil;
|
||||
if (![mpsites writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError])
|
||||
PearlMainQueue( ^{
|
||||
[[NSAlert alertWithError:writeError] runModal];
|
||||
[[NSAlert alertWithError:MPError( writeError, @"Could not write to the export file." )] runModal];
|
||||
} );
|
||||
}];
|
||||
if (coordinateError) {
|
||||
MPError( coordinateError, @"Write access to the export file could not be obtained." );
|
||||
PearlMainQueue( ^{
|
||||
[[NSAlert alertWithError:coordinateError] runModal];
|
||||
} );
|
||||
}
|
||||
}];
|
||||
if (coordinateError)
|
||||
PearlMainQueue( ^{
|
||||
[[NSAlert alertWithError:MPError( coordinateError, @"Could not gain access to the export file." )] runModal];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateUsers {
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
@property(nonatomic) NSString *name;
|
||||
@property(nonatomic) NSAttributedString *displayedName;
|
||||
@property(nonatomic) MPSiteType type;
|
||||
@property(nonatomic) MPResultType type;
|
||||
@property(nonatomic) NSString *typeName;
|
||||
@property(nonatomic) NSString *content;
|
||||
@property(nonatomic) NSString *displayedContent;
|
||||
@ -36,7 +36,7 @@
|
||||
@property(nonatomic) NSString *loginName;
|
||||
@property(nonatomic) BOOL loginGenerated;
|
||||
@property(nonatomic) NSNumber *uses;
|
||||
@property(nonatomic) NSUInteger counter;
|
||||
@property(nonatomic) MPCounterValue counter;
|
||||
@property(nonatomic) NSDate *lastUsed;
|
||||
@property(nonatomic) id<MPAlgorithm> algorithm;
|
||||
@property(nonatomic) MPAlgorithmVersion algorithmVersion;
|
||||
|
@ -81,7 +81,7 @@
|
||||
self.type = entity.type;
|
||||
self.typeName = entity.typeName;
|
||||
self.uses = entity.uses_;
|
||||
self.counter = [entity isKindOfClass:[MPGeneratedSiteEntity class]]? [(MPGeneratedSiteEntity *)entity counter]: 0;
|
||||
self.counter = [entity isKindOfClass:[MPGeneratedSiteEntity class]]? [(MPGeneratedSiteEntity *)entity counter]: MPCounterValueInitial;
|
||||
self.loginGenerated = entity.loginGenerated;
|
||||
|
||||
// Find all password types and the index of the current type amongst them.
|
||||
@ -104,7 +104,7 @@
|
||||
self.type = user.defaultType;
|
||||
self.typeName = [self.algorithm nameOfType:self.type];
|
||||
self.uses = @0;
|
||||
self.counter = 1;
|
||||
self.counter = MPCounterValueDefault;
|
||||
|
||||
// Find all password types and the index of the current type amongst them.
|
||||
[self updateContent];
|
||||
@ -116,14 +116,14 @@
|
||||
return nil;
|
||||
|
||||
NSError *error;
|
||||
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:self.entityOID error:&error];
|
||||
MPSiteEntity *entity = [moc existingObjectWithID:self.entityOID error:&error];
|
||||
if (!entity)
|
||||
MPError( error, @"Couldn't retrieve active site." );
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
- (void)setCounter:(NSUInteger)counter {
|
||||
- (void)setCounter:(MPCounterValue)counter {
|
||||
|
||||
if (self.counter == counter)
|
||||
return;
|
||||
@ -210,12 +210,12 @@
|
||||
|
||||
- (BOOL)generated {
|
||||
|
||||
return self.type & MPSiteTypeClassGenerated;
|
||||
return self.type & MPResultTypeClassTemplate;
|
||||
}
|
||||
|
||||
- (BOOL)stored {
|
||||
|
||||
return self.type & MPSiteTypeClassStored;
|
||||
return self.type & MPResultTypeClassStateful;
|
||||
}
|
||||
|
||||
- (BOOL)transient {
|
||||
|
@ -373,9 +373,9 @@
|
||||
NSArray *types = [site.algorithm allTypes];
|
||||
[self.passwordTypesMatrix renewRows:(NSInteger)[types count] columns:1];
|
||||
for (NSUInteger t = 0; t < [types count]; ++t) {
|
||||
MPSiteType type = (MPSiteType)[types[t] unsignedIntegerValue];
|
||||
MPResultType type = (MPResultType)[types[t] unsignedIntegerValue];
|
||||
NSString *title = [site.algorithm nameOfType:type];
|
||||
if (type & MPSiteTypeClassGenerated)
|
||||
if (type & MPResultTypeClassTemplate)
|
||||
title = strf( @"%@ – %@", [site.algorithm mpwTemplateForSiteNamed:site.name ofType:type withCounter:site.counter
|
||||
usingKey:[MPMacAppDelegate get].key], title );
|
||||
|
||||
@ -397,7 +397,7 @@
|
||||
switch (returnCode) {
|
||||
case NSAlertFirstButtonReturn: {
|
||||
// "Save" button.
|
||||
MPSiteType type = (MPSiteType)[self.passwordTypesMatrix.selectedCell tag];
|
||||
MPResultType type = (MPResultType)[self.passwordTypesMatrix.selectedCell tag];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *entity = [[MPMacAppDelegate get] changeSite:[self.selectedSite entityInContext:context]
|
||||
saveInContext:context toType:type];
|
||||
|
@ -436,17 +436,15 @@
|
||||
|
||||
[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 )
|
||||
[PearlAlert showAlertWithTitle:strf( @"Master Password For:\n%@", userName )
|
||||
message:@"Enter the user's master password to create an 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:@"Export", nil];
|
||||
} );
|
||||
} result:^(NSString *mpsites, NSError *error) {
|
||||
if (!mpsites || error) {
|
||||
|
Loading…
Reference in New Issue
Block a user