Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dba4c87ba | ||
|
|
6841ae2b0d | ||
|
|
a3698b9e47 | ||
|
|
5b65c8c6bd | ||
|
|
94c9d50a12 | ||
|
|
5849c9668f | ||
|
|
4d4ba3425e | ||
|
|
b7e91358be | ||
|
|
da860d74c4 | ||
|
|
4e2ceb33a0 | ||
|
|
d14bde07bd | ||
|
|
fa2dc822d3 | ||
|
|
d4adafb448 | ||
|
|
a67d9676ba | ||
|
|
bc2da6a99b | ||
|
|
50b5c87f61 | ||
|
|
d4bcad2658 | ||
|
|
13bca309ba | ||
|
|
83bdb9d626 | ||
|
|
5f1fc453d4 | ||
|
|
9c875d4311 | ||
|
|
afafa473a7 | ||
|
|
4d5f609d72 | ||
|
|
6f1d53ea35 | ||
|
|
a8bf74a925 | ||
|
|
d59f77720c | ||
|
|
92be7f7267 | ||
|
|
09d5e64c55 | ||
|
|
f796888901 | ||
|
|
679990dc4b | ||
|
|
b472c85c9d | ||
|
|
77306e0046 | ||
|
|
0491ba3f97 | ||
|
|
ba299d4674 | ||
|
|
3de9a0c67e | ||
|
|
7d0ea4b3f5 | ||
|
|
ac14b10752 | ||
|
|
2d8f2943e5 | ||
|
|
4c0348b5c8 | ||
|
|
a5d2f82db4 | ||
|
|
ec8eff2117 | ||
|
|
b7e72e98ac | ||
|
|
a0b1e63c74 | ||
|
|
42704ab504 | ||
|
|
6cc74c8898 | ||
|
|
44911f1d9e | ||
|
|
16fc32ee30 | ||
|
|
ac9534c680 | ||
|
|
2020637f0e | ||
|
|
ada6d6b36a | ||
|
|
b0875beb36 | ||
|
|
94818bb242 | ||
|
|
1f68a4cb09 | ||
|
|
f7e64fe4b8 | ||
|
|
301366f1f1 | ||
|
|
941b428cfc | ||
|
|
0d638a4c3f | ||
|
|
cdfafa55cf | ||
|
|
82b8de5e23 | ||
|
|
6bbd183ac9 | ||
|
|
7d9131cdca | ||
|
|
12fb7243d6 | ||
|
|
eb4e25c0ba | ||
|
|
587461144b | ||
|
|
9968491e3b | ||
|
|
db7d68a091 | ||
|
|
c8f9f79bb2 | ||
|
|
21c0565619 | ||
|
|
04bc7a497c | ||
|
|
7c5cea9c8d | ||
|
|
1b90c9bfa3 | ||
|
|
f622b2c7d4 | ||
|
|
98080ceb51 | ||
|
|
f5d9334b06 | ||
|
|
f882d0fb53 | ||
|
|
dfc62fa8a8 | ||
|
|
f5551d4823 | ||
|
|
424479dada | ||
|
|
376953ae56 | ||
|
|
26f8e086bb | ||
|
|
950ce888e2 | ||
|
|
657fef6249 | ||
|
|
0d19202ca7 | ||
|
|
1755bd1607 | ||
|
|
dc8b6aa86c | ||
|
|
0c536ef640 | ||
|
|
fa3680f9c4 | ||
|
|
c8e03ff016 | ||
|
|
3fcac696bb | ||
|
|
d0ae954dbf | ||
|
|
f3c24fd96f | ||
|
|
02ffa9611a | ||
|
|
6bda70920b | ||
|
|
2761355180 | ||
|
|
cbf624b3da | ||
|
|
9899104891 | ||
|
|
d34ec96b94 | ||
|
|
ac735f3ccb | ||
|
|
c508ed8c46 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -4,3 +4,9 @@
|
||||
[submodule "External/InAppSettingsKit"]
|
||||
path = External/InAppSettingsKit
|
||||
url = git://github.com/futuretap/InAppSettingsKit.git
|
||||
[submodule "External/iCloudStoreManager"]
|
||||
path = External/iCloudStoreManager
|
||||
url = git://github.com/lhunath/iCloudStoreManager.git
|
||||
[submodule "External/FontReplacer"]
|
||||
path = External/FontReplacer
|
||||
url = git://github.com/0xced/FontReplacer.git
|
||||
|
||||
9
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
9
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0" is_locked="false">
|
||||
<option name="myName" value="Project Default" />
|
||||
<option name="myLocal" value="false" />
|
||||
<inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Project Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="true" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
1
Crashlytics/Crashlytics.framework/Crashlytics
vendored
Symbolic link
1
Crashlytics/Crashlytics.framework/Crashlytics
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
Versions/Current/Crashlytics
|
||||
1
Crashlytics/Crashlytics.framework/Headers
vendored
Symbolic link
1
Crashlytics/Crashlytics.framework/Headers
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
Versions/Current/Headers
|
||||
1
Crashlytics/Crashlytics.framework/Resources
vendored
Symbolic link
1
Crashlytics/Crashlytics.framework/Resources
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
Versions/Current/Resources
|
||||
BIN
Crashlytics/Crashlytics.framework/Versions/A/Crashlytics
vendored
Normal file
BIN
Crashlytics/Crashlytics.framework/Versions/A/Crashlytics
vendored
Normal file
Binary file not shown.
172
Crashlytics/Crashlytics.framework/Versions/A/Headers/Crashlytics.h
vendored
Normal file
172
Crashlytics/Crashlytics.framework/Versions/A/Headers/Crashlytics.h
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// Crashlytics.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright 2012 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
*
|
||||
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
|
||||
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
|
||||
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
|
||||
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
|
||||
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
|
||||
*
|
||||
* Example output:
|
||||
* -[AppDelegate login:] line 134 $ login start
|
||||
*
|
||||
* If you would like to change this macro, create a new header file, unset our define and then define
|
||||
* your own version. Make sure this new header file is imported after the Crashlytics header file.
|
||||
*
|
||||
* #undef CLS_LOG
|
||||
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
|
||||
*
|
||||
**/
|
||||
#ifdef DEBUG
|
||||
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
|
||||
#else
|
||||
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
|
||||
* and will only be visible in your Crashlytics dashboard.
|
||||
*
|
||||
**/
|
||||
void CLSLog(NSString *format, ...);
|
||||
|
||||
/**
|
||||
*
|
||||
* Add logging that will be sent with your crash data. This logging will show up in the system.log
|
||||
* and your Crashlytics dashboard. It is not reccomended for Release builds.
|
||||
*
|
||||
**/
|
||||
void CLSNSLog(NSString *format, ...);
|
||||
|
||||
@protocol CrashlyticsDelegate;
|
||||
|
||||
@interface Crashlytics : NSObject {
|
||||
@private
|
||||
NSString *_apiKey;
|
||||
NSString *_dataDirectory;
|
||||
NSString *_bundleIdentifier;
|
||||
BOOL _installed;
|
||||
NSMutableDictionary *_customAttributes;
|
||||
id _user;
|
||||
NSInteger _sendButtonIndex;
|
||||
NSInteger _alwaysSendButtonIndex;
|
||||
NSObject <CrashlyticsDelegate> *_delegate;
|
||||
}
|
||||
|
||||
@property (nonatomic, readonly, copy) NSString *apiKey;
|
||||
@property (nonatomic, readonly, copy) NSString *version;
|
||||
@property (nonatomic, assign) BOOL debugMode;
|
||||
|
||||
@property (nonatomic, assign) NSObject <CrashlyticsDelegate> *delegate;
|
||||
|
||||
/**
|
||||
*
|
||||
* The recommended way to install Crashlytics into your application is to place a call
|
||||
* to +startWithAPIKey: in your -application:didFinishLaunchingWithOptions: method.
|
||||
*
|
||||
* This delay defaults to 1 second in order to generally give the application time to
|
||||
* fully finish launching.
|
||||
*
|
||||
**/
|
||||
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey;
|
||||
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey afterDelay:(NSTimeInterval)delay;
|
||||
|
||||
/**
|
||||
*
|
||||
* If you need the functionality provided by the CrashlyticsDelegate protocol, you can use
|
||||
* these convenience methods to activate the framework and set the delegate in one call.
|
||||
*
|
||||
**/
|
||||
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(NSObject <CrashlyticsDelegate> *)delegate;
|
||||
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(NSObject <CrashlyticsDelegate> *)delegate afterDelay:(NSTimeInterval)delay;
|
||||
|
||||
/**
|
||||
*
|
||||
* Access the singleton Crashlytics instance.
|
||||
*
|
||||
**/
|
||||
+ (Crashlytics *)sharedInstance;
|
||||
|
||||
/**
|
||||
*
|
||||
* The easiest way to cause a crash - great for testing!
|
||||
*
|
||||
**/
|
||||
- (void)crash;
|
||||
|
||||
/**
|
||||
*
|
||||
* Many of our customers have requested the ability to tie crashes to specific end-users of their
|
||||
* application in order to facilitate responses to support requests or permit the ability to reach
|
||||
* out for more information. We allow you to specify up to three separate values for display within
|
||||
* the Crashlytics UI - but please be mindful of your end-user's privacy.
|
||||
*
|
||||
* We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record
|
||||
* in your system. This could be a database id, hash, or other value that is meaningless to a
|
||||
* third-party observer but can be indexed and queried by you.
|
||||
*
|
||||
* Optionally, you may also specify the end-user's name or username, as well as email address if you
|
||||
* do not have a system that works well with obscured identifiers.
|
||||
*
|
||||
* Pursuant to our EULA, this data is transferred securely throughout our system and we will not
|
||||
* disseminate end-user data unless required to by law. That said, if you choose to provide end-user
|
||||
* contact information, we strongly recommend that you disclose this in your application's privacy
|
||||
* policy. Data privacy is of our utmost concern.
|
||||
*
|
||||
**/
|
||||
- (void)setUserIdentifier:(NSString *)identifier;
|
||||
- (void)setUserName:(NSString *)name;
|
||||
- (void)setUserEmail:(NSString *)email;
|
||||
|
||||
+ (void)setUserIdentifier:(NSString *)identifier;
|
||||
+ (void)setUserName:(NSString *)name;
|
||||
+ (void)setUserEmail:(NSString *)email;
|
||||
|
||||
/**
|
||||
*
|
||||
* Set a value for a key to be associated with your crash data.
|
||||
*
|
||||
**/
|
||||
- (void)setObjectValue:(id)value forKey:(NSString *)key;
|
||||
- (void)setIntValue:(int)value forKey:(NSString *)key;
|
||||
- (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
|
||||
- (void)setFloatValue:(float)value forKey:(NSString *)key;
|
||||
|
||||
+ (void)setObjectValue:(id)value forKey:(NSString *)key;
|
||||
+ (void)setIntValue:(int)value forKey:(NSString *)key;
|
||||
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
|
||||
+ (void)setFloatValue:(float)value forKey:(NSString *)key;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
*
|
||||
* The CrashlyticsDelegate protocol provides a mechanism for your application to take
|
||||
* action on events that occur in the Crashlytics crash reporting system. You can make
|
||||
* use of these calls by assigning an object to the Crashlytics' delegate property directly,
|
||||
* or through the convenience startWithAPIKey:delegate:... methods.
|
||||
*
|
||||
**/
|
||||
@protocol CrashlyticsDelegate <NSObject>
|
||||
@optional
|
||||
|
||||
/**
|
||||
*
|
||||
* Called once a Crashlytics instance has determined that the last execution of the
|
||||
* application ended in a crash. This is called some time after the crash reporting
|
||||
* process has begun. If you have specified a delay in one of the
|
||||
* startWithAPIKey:... calls, this will take at least that long to be invoked.
|
||||
*
|
||||
**/
|
||||
- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics;
|
||||
|
||||
@end
|
||||
54
Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist
vendored
Normal file
54
Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>11C74</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Crashlytics</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.crashlytics.ios</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Crashlytics</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0101.02.00</string>
|
||||
<key>CrashlyticsAPIKey</key>
|
||||
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>8H7</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>4.3</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>8H7</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>iphoneos4.3</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0410</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>4B110</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>3.1</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
1
Crashlytics/Crashlytics.framework/Versions/A/Resources/Runner
vendored
Symbolic link
1
Crashlytics/Crashlytics.framework/Versions/A/Resources/Runner
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../run
|
||||
BIN
Crashlytics/Crashlytics.framework/Versions/A/Resources/en.lproj/InfoPlist.strings
vendored
Normal file
BIN
Crashlytics/Crashlytics.framework/Versions/A/Resources/en.lproj/InfoPlist.strings
vendored
Normal file
Binary file not shown.
15
Crashlytics/Crashlytics.framework/Versions/A/Resources/runner.rb
vendored
Normal file
15
Crashlytics/Crashlytics.framework/Versions/A/Resources/runner.rb
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/ruby
|
||||
|
||||
#
|
||||
# WARNING: DO NOT MODIFY THIS FILE.
|
||||
#
|
||||
# Crashlytics
|
||||
# Crashlytics Version: 1.0.0.1
|
||||
#
|
||||
# Copyright Crashlytics, Inc. 2012. All rights reserved.
|
||||
#
|
||||
|
||||
require 'pathname'
|
||||
|
||||
path = Pathname.new(__FILE__).parent
|
||||
`#{path}/../../../run`
|
||||
1
Crashlytics/Crashlytics.framework/Versions/Current
vendored
Symbolic link
1
Crashlytics/Crashlytics.framework/Versions/Current
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
A
|
||||
BIN
Crashlytics/Crashlytics.framework/run
vendored
Executable file
BIN
Crashlytics/Crashlytics.framework/run
vendored
Executable file
Binary file not shown.
8
Crashlytics/Crashlytics.plist
Normal file
8
Crashlytics/Crashlytics.plist
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API Key</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
Default.png
BIN
Default.png
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB |
1
External/FontReplacer
vendored
Submodule
1
External/FontReplacer
vendored
Submodule
Submodule External/FontReplacer added at 4e3dea0870
2
External/InAppSettingsKit
vendored
2
External/InAppSettingsKit
vendored
Submodule External/InAppSettingsKit updated: 5fd23fd728...3ae828f48a
2
External/Pearl
vendored
2
External/Pearl
vendored
Submodule External/Pearl updated: bb9d8d10cb...01f6c67559
1
External/iCloudStoreManager
vendored
Submodule
1
External/iCloudStoreManager
vendored
Submodule
Submodule External/iCloudStoreManager added at 84c718a57d
10
Localytics/Localytics.plist
Normal file
10
Localytics/Localytics.plist
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Key.development</key>
|
||||
<string>e6238ceba8ec92832e77b1b-9ccd60bc-c39b-11e0-06e4-007f58cb3154</string>
|
||||
<key>Key.distribution</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
57
Localytics/LocalyticsDatabase.h
Normal file
57
Localytics/LocalyticsDatabase.h
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// LocalyticsDatabase.h
|
||||
// LocalyticsDemo
|
||||
//
|
||||
// Created by jkaufman on 5/26/11.
|
||||
// Copyright 2011 Localytics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <sqlite3.h>
|
||||
|
||||
#define MAX_DATABASE_SIZE 500000 // The maximum allowed disk size of the primary database file at open, in bytes
|
||||
#define VACUUM_THRESHOLD 0.8 // The database is vacuumed after its size exceeds this proportion of the maximum.
|
||||
|
||||
@interface LocalyticsDatabase : NSObject {
|
||||
sqlite3 *_databaseConnection;
|
||||
}
|
||||
|
||||
+ (LocalyticsDatabase *)sharedLocalyticsDatabase;
|
||||
|
||||
- (NSUInteger)databaseSize;
|
||||
- (int)eventCount;
|
||||
- (NSTimeInterval)createdTimestamp;
|
||||
|
||||
- (BOOL)beginTransaction:(NSString *)name;
|
||||
- (BOOL)releaseTransaction:(NSString *)name;
|
||||
- (BOOL)rollbackTransaction:(NSString *)name;
|
||||
|
||||
- (BOOL)incrementLastUploadNumber:(int *)uploadNumber;
|
||||
- (BOOL)incrementLastSessionNumber:(int *)sessionNumber;
|
||||
|
||||
- (BOOL)addEventWithBlobString:(NSString *)blob;
|
||||
- (BOOL)addCloseEventWithBlobString:(NSString *)blob;
|
||||
- (BOOL)addFlowEventWithBlobString:(NSString *)blob;
|
||||
- (BOOL)removeLastCloseAndFlowEvents;
|
||||
|
||||
- (BOOL)addHeaderWithSequenceNumber:(int)number blobString:(NSString *)blob rowId:(sqlite3_int64 *)insertedRowId;
|
||||
- (int)unstagedEventCount;
|
||||
- (BOOL)stageEventsForUpload:(sqlite3_int64)headerId;
|
||||
- (BOOL)updateAppKey:(NSString *)appKey;
|
||||
- (NSString *)uploadBlobString;
|
||||
- (BOOL)deleteUploadedData;
|
||||
- (BOOL)resetAnalyticsData;
|
||||
- (BOOL)vacuumIfRequired;
|
||||
|
||||
- (NSTimeInterval)lastSessionStartTimestamp;
|
||||
- (BOOL)setLastsessionStartTimestamp:(NSTimeInterval)timestamp;
|
||||
|
||||
- (BOOL)isOptedOut;
|
||||
- (BOOL)setOptedOut:(BOOL)optOut;
|
||||
- (NSString *)installId;
|
||||
- (NSString *)appKey; // Most recent app key-- may not be that used to open the session.
|
||||
|
||||
- (NSString *)customDimension:(int)dimension;
|
||||
- (BOOL)setCustomDimension:(int)dimension value:(NSString *)value;
|
||||
|
||||
@end
|
||||
743
Localytics/LocalyticsDatabase.m
Normal file
743
Localytics/LocalyticsDatabase.m
Normal file
@@ -0,0 +1,743 @@
|
||||
//
|
||||
// LocalyticsDatabase.m
|
||||
// LocalyticsDemo
|
||||
//
|
||||
// Created by jkaufman on 5/26/11.
|
||||
// Copyright 2011 Localytics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LocalyticsDatabase.h"
|
||||
|
||||
#define LOCALYTICS_DIR @".localytics" // Name for the directory in which Localytics database is stored
|
||||
#define LOCALYTICS_DB @"localytics" // File name for the database (without extension)
|
||||
#define BUSY_TIMEOUT 30 // Maximum time SQlite will busy-wait for the database to unlock before returning SQLITE_BUSY
|
||||
|
||||
@interface LocalyticsDatabase ()
|
||||
- (int)schemaVersion;
|
||||
- (void)createSchema;
|
||||
- (void)upgradeToSchemaV2;
|
||||
- (void)upgradeToSchemaV3;
|
||||
- (void)moveDbToCaches;
|
||||
- (NSString *)randomUUID;
|
||||
@end
|
||||
|
||||
@implementation LocalyticsDatabase
|
||||
|
||||
// The singleton database object.
|
||||
static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
|
||||
|
||||
+ (NSString *)localyticsDirectoryPath {
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||
return [[paths objectAtIndex:0] stringByAppendingPathComponent:LOCALYTICS_DIR];
|
||||
}
|
||||
|
||||
+ (NSString *)localyticsDatabasePath {
|
||||
NSString *path = [[LocalyticsDatabase localyticsDirectoryPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.sqlite", LOCALYTICS_DB]];
|
||||
return path;
|
||||
}
|
||||
|
||||
#pragma mark Singleton Class
|
||||
+ (LocalyticsDatabase *)sharedLocalyticsDatabase {
|
||||
@synchronized(self) {
|
||||
if (_sharedLocalyticsDatabase == nil) {
|
||||
_sharedLocalyticsDatabase = [[self alloc] init];
|
||||
}
|
||||
}
|
||||
return _sharedLocalyticsDatabase;
|
||||
}
|
||||
|
||||
- (LocalyticsDatabase *)init {
|
||||
if((self = [super init])) {
|
||||
|
||||
// Mover any data that a previous library may have left in the documents directory
|
||||
[self moveDbToCaches];
|
||||
|
||||
// Create directory structure for Localytics.
|
||||
NSString *directoryPath = [LocalyticsDatabase localyticsDirectoryPath];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:directoryPath]) {
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
}
|
||||
|
||||
// Attempt to open database. It will be created if it does not exist, already.
|
||||
NSString *dbPath = [LocalyticsDatabase localyticsDatabasePath];
|
||||
int code = sqlite3_open([dbPath UTF8String], &_databaseConnection);
|
||||
|
||||
// If we were unable to open the database, it is likely corrupted. Clobber it and move on.
|
||||
if (code != SQLITE_OK) {
|
||||
[[NSFileManager defaultManager] removeItemAtPath:dbPath error:nil];
|
||||
code = sqlite3_open([dbPath UTF8String], &_databaseConnection);
|
||||
}
|
||||
|
||||
// Check db connection, creating schema if necessary.
|
||||
if (code == SQLITE_OK) {
|
||||
sqlite3_busy_timeout(_databaseConnection, BUSY_TIMEOUT); // Defaults to 0, otherwise.
|
||||
if ([self schemaVersion] == 0) {
|
||||
[self createSchema];
|
||||
}
|
||||
}
|
||||
|
||||
// Perform any Migrations if necessary
|
||||
if ([self schemaVersion] < 2) {
|
||||
[self upgradeToSchemaV2];
|
||||
}
|
||||
if ([self schemaVersion] < 3) {
|
||||
[self upgradeToSchemaV3];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Database
|
||||
|
||||
- (BOOL)beginTransaction:(NSString *)name {
|
||||
const char *sql = [[NSString stringWithFormat:@"SAVEPOINT %@", name] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
int code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
- (BOOL)releaseTransaction:(NSString *)name {
|
||||
const char *sql = [[NSString stringWithFormat:@"RELEASE SAVEPOINT %@", name] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
int code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
- (BOOL)rollbackTransaction:(NSString *)name {
|
||||
const char *sql = [[NSString stringWithFormat:@"ROLLBACK SAVEPOINT %@", name] cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
int code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
- (int)schemaVersion {
|
||||
int version = 0;
|
||||
const char *sql = "SELECT MAX(schema_version) FROM localytics_info";
|
||||
sqlite3_stmt *selectSchemaVersion;
|
||||
if(sqlite3_prepare_v2(_databaseConnection, sql, -1, &selectSchemaVersion, NULL) == SQLITE_OK) {
|
||||
if(sqlite3_step(selectSchemaVersion) == SQLITE_ROW) {
|
||||
version = sqlite3_column_int(selectSchemaVersion, 0);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(selectSchemaVersion);
|
||||
return version;
|
||||
}
|
||||
|
||||
- (NSString *)installId {
|
||||
NSString *installId = nil;
|
||||
|
||||
sqlite3_stmt *selectInstallId;
|
||||
sqlite3_prepare_v2(_databaseConnection, "SELECT install_id FROM localytics_info", -1, &selectInstallId, NULL);
|
||||
int code = sqlite3_step(selectInstallId);
|
||||
if (code == SQLITE_ROW && sqlite3_column_text(selectInstallId, 0)) {
|
||||
installId = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectInstallId, 0)];
|
||||
}
|
||||
sqlite3_finalize(selectInstallId);
|
||||
|
||||
return installId;
|
||||
}
|
||||
|
||||
- (NSString *)appKey {
|
||||
NSString *appKey = nil;
|
||||
|
||||
sqlite3_stmt *selectAppKey;
|
||||
sqlite3_prepare_v2(_databaseConnection, "SELECT app_key FROM localytics_info", -1, &selectAppKey, NULL);
|
||||
int code = sqlite3_step(selectAppKey);
|
||||
if (code == SQLITE_ROW && sqlite3_column_text(selectAppKey, 0)) {
|
||||
appKey = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectAppKey, 0)];
|
||||
}
|
||||
sqlite3_finalize(selectAppKey);
|
||||
|
||||
return appKey;
|
||||
}
|
||||
|
||||
// Due to the new iOS storage guidelines it is necessary to move the database out of the documents directory
|
||||
// and into the /library/caches directory
|
||||
- (void)moveDbToCaches {
|
||||
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
NSString *localyticsDocumentsDirectory = [[documentPaths objectAtIndex:0] stringByAppendingPathComponent:LOCALYTICS_DIR];
|
||||
NSArray *cachesPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||
NSString *localyticsCachesDirectory = [[cachesPaths objectAtIndex:0] stringByAppendingPathComponent:LOCALYTICS_DIR];
|
||||
|
||||
// If the old directory doesn't exist, there is nothing else to do here
|
||||
if([[NSFileManager defaultManager] fileExistsAtPath:localyticsDocumentsDirectory] == NO)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to move the directory
|
||||
if(NO == [[NSFileManager defaultManager] moveItemAtPath:localyticsDocumentsDirectory
|
||||
toPath:localyticsCachesDirectory
|
||||
error:nil])
|
||||
{
|
||||
// If the move failed try and, delete the old directory
|
||||
[ [NSFileManager defaultManager] removeItemAtPath:localyticsDocumentsDirectory error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)createSchema {
|
||||
int code = SQLITE_OK;
|
||||
|
||||
// Execute schema creation within a single transaction.
|
||||
code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"CREATE TABLE upload_headers ("
|
||||
"sequence_number INTEGER PRIMARY KEY, "
|
||||
"blob_string TEXT)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"CREATE TABLE events ("
|
||||
"event_id INTEGER PRIMARY KEY AUTOINCREMENT, " // In case foreign key constraints are reintroduced.
|
||||
"upload_header INTEGER, "
|
||||
"blob_string TEXT NOT NULL)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"CREATE TABLE localytics_info ("
|
||||
"schema_version INTEGER PRIMARY KEY, "
|
||||
"last_upload_number INTEGER, "
|
||||
"last_session_number INTEGER, "
|
||||
"opt_out BOOLEAN, "
|
||||
"last_close_event INTEGER, "
|
||||
"last_flow_event INTEGER, "
|
||||
"last_session_start REAL, "
|
||||
"app_key CHAR(64), "
|
||||
"custom_d0 CHAR(64), "
|
||||
"custom_d1 CHAR(64), "
|
||||
"custom_d2 CHAR(64), "
|
||||
"custom_d3 CHAR(64) "
|
||||
")",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"INSERT INTO localytics_info (schema_version, last_upload_number, last_session_number, opt_out) "
|
||||
"VALUES (3, 0, 0, 0)", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
// Commit transaction.
|
||||
if (code == SQLITE_OK || code == SQLITE_DONE) {
|
||||
sqlite3_exec(_databaseConnection, "COMMIT", NULL, NULL, NULL);
|
||||
} else {
|
||||
sqlite3_exec(_databaseConnection, "ROLLBACK", NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// V2 adds a unique identifier for each installation
|
||||
// This identifier has been moved to user preferences so the database an live in the caches directory
|
||||
// Also adds storage for custom dimensions
|
||||
- (void)upgradeToSchemaV2 {
|
||||
int code = SQLITE_OK;
|
||||
|
||||
code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"ALTER TABLE localytics_info ADD install_id CHAR(40)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"ALTER TABLE localytics_info ADD custom_d0 CHAR(64)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"ALTER TABLE localytics_info ADD custom_d1 CHAR(64)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"ALTER TABLE localytics_info ADD custom_d2 CHAR(64)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
sqlite3_exec(_databaseConnection,
|
||||
"ALTER TABLE localytics_info ADD custom_d3 CHAR(64)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
// Attempt to set schema version and install_id regardless of the result code following the ALTER statements above.
|
||||
// This is necessary because a previous version of the library performed the migration without setting these values.
|
||||
// The transaction will succeed even if the individual statements fail with errors (eg. "duplicate column name").
|
||||
sqlite3_stmt *updateLocalyticsInfo;
|
||||
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info set install_id = ?, schema_version = 2 ", -1, &updateLocalyticsInfo, NULL);
|
||||
sqlite3_bind_text (updateLocalyticsInfo, 1, [[self randomUUID] UTF8String], -1, SQLITE_TRANSIENT);
|
||||
code = sqlite3_step(updateLocalyticsInfo);
|
||||
sqlite3_finalize(updateLocalyticsInfo);
|
||||
|
||||
// Commit transaction.
|
||||
if (code == SQLITE_OK || code == SQLITE_DONE) {
|
||||
sqlite3_exec(_databaseConnection, "COMMIT", NULL, NULL, NULL);
|
||||
} else {
|
||||
sqlite3_exec(_databaseConnection, "ROLLBACK", NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// V3 adds a field for the last app key and patches a V2 migration issue.
|
||||
- (void)upgradeToSchemaV3 {
|
||||
sqlite3_exec(_databaseConnection,
|
||||
"ALTER TABLE localytics_info ADD app_key CHAR(64)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
- (NSUInteger)databaseSize {
|
||||
NSUInteger size = 0;
|
||||
NSDictionary *fileAttributes = [[NSFileManager defaultManager]
|
||||
attributesOfItemAtPath:[LocalyticsDatabase localyticsDatabasePath]
|
||||
error:nil];
|
||||
size = [fileAttributes fileSize];
|
||||
return size;
|
||||
}
|
||||
|
||||
- (int) eventCount {
|
||||
int count = 0;
|
||||
const char *sql = "SELECT count(*) FROM events";
|
||||
sqlite3_stmt *selectEventCount;
|
||||
|
||||
if(sqlite3_prepare_v2(_databaseConnection, sql, -1, &selectEventCount, NULL) == SQLITE_OK)
|
||||
{
|
||||
if(sqlite3_step(selectEventCount) == SQLITE_ROW) {
|
||||
count = sqlite3_column_int(selectEventCount, 0);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(selectEventCount);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)createdTimestamp {
|
||||
NSTimeInterval timestamp = 0;
|
||||
NSDictionary *fileAttributes = [[NSFileManager defaultManager]
|
||||
attributesOfItemAtPath:[LocalyticsDatabase localyticsDatabasePath]
|
||||
error:nil];
|
||||
timestamp = [[fileAttributes fileCreationDate] timeIntervalSince1970];
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)lastSessionStartTimestamp {
|
||||
|
||||
NSTimeInterval lastSessionStart = 0;
|
||||
|
||||
sqlite3_stmt *selectLastSessionStart;
|
||||
sqlite3_prepare_v2(_databaseConnection, "SELECT last_session_start FROM localytics_info", -1, &selectLastSessionStart, NULL);
|
||||
int code = sqlite3_step(selectLastSessionStart);
|
||||
if (code == SQLITE_ROW) {
|
||||
lastSessionStart = sqlite3_column_double(selectLastSessionStart, 0) == 1;
|
||||
}
|
||||
sqlite3_finalize(selectLastSessionStart);
|
||||
|
||||
return lastSessionStart;
|
||||
}
|
||||
|
||||
- (BOOL)setLastsessionStartTimestamp:(NSTimeInterval)timestamp {
|
||||
sqlite3_stmt *updateLastSessionStart;
|
||||
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET last_session_start = ?", -1, &updateLastSessionStart, NULL);
|
||||
sqlite3_bind_double(updateLastSessionStart, 1, timestamp);
|
||||
int code = sqlite3_step(updateLastSessionStart);
|
||||
sqlite3_finalize(updateLastSessionStart);
|
||||
|
||||
return code == SQLITE_DONE;
|
||||
}
|
||||
|
||||
- (BOOL)isOptedOut {
|
||||
BOOL optedOut = NO;
|
||||
|
||||
sqlite3_stmt *selectOptOut;
|
||||
sqlite3_prepare_v2(_databaseConnection, "SELECT opt_out FROM localytics_info", -1, &selectOptOut, NULL);
|
||||
int code = sqlite3_step(selectOptOut);
|
||||
if (code == SQLITE_ROW) {
|
||||
optedOut = sqlite3_column_int(selectOptOut, 0) == 1;
|
||||
}
|
||||
sqlite3_finalize(selectOptOut);
|
||||
|
||||
return optedOut;
|
||||
}
|
||||
|
||||
- (BOOL)setOptedOut:(BOOL)optOut {
|
||||
sqlite3_stmt *updateOptedOut;
|
||||
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET opt_out = ?", -1, &updateOptedOut, NULL);
|
||||
sqlite3_bind_int(updateOptedOut, 1, optOut);
|
||||
int code = sqlite3_step(updateOptedOut);
|
||||
sqlite3_finalize(updateOptedOut);
|
||||
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
- (NSString *)customDimension:(int)dimension {
|
||||
if(dimension < 0 || dimension > 3) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *value = nil;
|
||||
NSString *query = [NSString stringWithFormat:@"select custom_d%i from localytics_info", dimension];
|
||||
|
||||
sqlite3_stmt *selectCustomDim;
|
||||
sqlite3_prepare_v2(_databaseConnection, [query UTF8String], -1, &selectCustomDim, NULL);
|
||||
int code = sqlite3_step(selectCustomDim);
|
||||
if (code == SQLITE_ROW && sqlite3_column_text(selectCustomDim, 0)) {
|
||||
value = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectCustomDim, 0)];
|
||||
}
|
||||
sqlite3_finalize(selectCustomDim);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (BOOL)setCustomDimension:(int)dimension value:(NSString *)value {
|
||||
if(dimension < 0 || dimension > 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NSString *query = [NSString stringWithFormat:@"update localytics_info SET custom_d%i = %@",
|
||||
dimension,
|
||||
(value == nil) ? @"null" : [NSString stringWithFormat:@"\"%@\"", value]];
|
||||
|
||||
int code = sqlite3_exec(_databaseConnection, [query UTF8String], NULL, NULL, NULL);
|
||||
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
- (BOOL)incrementLastUploadNumber:(int *)uploadNumber {
|
||||
NSString *t = @"increment_upload_number";
|
||||
int code = SQLITE_OK;
|
||||
|
||||
code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
|
||||
|
||||
if(code == SQLITE_OK) {
|
||||
// Increment value
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"UPDATE localytics_info "
|
||||
"SET last_upload_number = (last_upload_number + 1)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if(code == SQLITE_OK) {
|
||||
// Retrieve new value
|
||||
sqlite3_stmt *selectUploadNumber;
|
||||
sqlite3_prepare_v2(_databaseConnection,
|
||||
"SELECT last_upload_number FROM localytics_info",
|
||||
-1, &selectUploadNumber, NULL);
|
||||
code = sqlite3_step(selectUploadNumber);
|
||||
if (code == SQLITE_ROW) {
|
||||
*uploadNumber = sqlite3_column_int(selectUploadNumber, 0);
|
||||
}
|
||||
sqlite3_finalize(selectUploadNumber);
|
||||
}
|
||||
|
||||
if(code == SQLITE_ROW) {
|
||||
[self releaseTransaction:t];
|
||||
} else {
|
||||
[self rollbackTransaction:t];
|
||||
}
|
||||
|
||||
return code == SQLITE_ROW;
|
||||
}
|
||||
|
||||
- (BOOL)incrementLastSessionNumber:(int *)sessionNumber {
|
||||
NSString *t = @"increment_session_number";
|
||||
int code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
|
||||
|
||||
if(code == SQLITE_OK) {
|
||||
// Increment value
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"UPDATE localytics_info "
|
||||
"SET last_session_number = (last_session_number + 1)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if(code == SQLITE_OK) {
|
||||
// Retrieve new value
|
||||
sqlite3_stmt *selectSessionNumber;
|
||||
sqlite3_prepare_v2(_databaseConnection,
|
||||
"SELECT last_session_number FROM localytics_info",
|
||||
-1, &selectSessionNumber, NULL);
|
||||
code = sqlite3_step(selectSessionNumber);
|
||||
if (code == SQLITE_ROW && sessionNumber != NULL) {
|
||||
*sessionNumber = sqlite3_column_int(selectSessionNumber, 0);
|
||||
}
|
||||
sqlite3_finalize(selectSessionNumber);
|
||||
}
|
||||
|
||||
if(code == SQLITE_ROW) {
|
||||
[self releaseTransaction:t];
|
||||
} else {
|
||||
[self rollbackTransaction:t];
|
||||
}
|
||||
|
||||
return code == SQLITE_ROW;
|
||||
}
|
||||
|
||||
- (BOOL)addEventWithBlobString:(NSString *)blob {
|
||||
|
||||
int code = SQLITE_OK;
|
||||
sqlite3_stmt *insertEvent;
|
||||
sqlite3_prepare_v2(_databaseConnection, "INSERT INTO events (blob_string) VALUES (?)", -1, &insertEvent, NULL);
|
||||
sqlite3_bind_text(insertEvent, 1, [blob UTF8String], -1, SQLITE_TRANSIENT);
|
||||
code = sqlite3_step(insertEvent);
|
||||
sqlite3_finalize(insertEvent);
|
||||
|
||||
return code == SQLITE_DONE;
|
||||
}
|
||||
|
||||
- (BOOL)addCloseEventWithBlobString:(NSString *)blob {
|
||||
NSString *t = @"add_close_event";
|
||||
BOOL success = [self beginTransaction:t];
|
||||
|
||||
// Add close event.
|
||||
if (success) {
|
||||
success = [self addEventWithBlobString:blob];
|
||||
}
|
||||
|
||||
// Record row id to localytics_info so that it can be removed if the session resumes.
|
||||
if (success) {
|
||||
sqlite3_stmt *updateCloseEvent;
|
||||
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET last_close_event = (SELECT event_id FROM events WHERE rowid = ?)", -1, &updateCloseEvent, NULL);
|
||||
sqlite3_int64 lastRow = sqlite3_last_insert_rowid(_databaseConnection);
|
||||
sqlite3_bind_int64(updateCloseEvent, 1, lastRow);
|
||||
int code = sqlite3_step(updateCloseEvent);
|
||||
sqlite3_finalize(updateCloseEvent);
|
||||
success = code == SQLITE_DONE;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
[self releaseTransaction:t];
|
||||
} else {
|
||||
[self rollbackTransaction:t];
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)addFlowEventWithBlobString:(NSString *)blob {
|
||||
NSString *t = @"add_flow_event";
|
||||
BOOL success = [self beginTransaction:t];
|
||||
|
||||
// Add flow event.
|
||||
if (success) {
|
||||
success = [self addEventWithBlobString:blob];
|
||||
}
|
||||
|
||||
// Record row id to localytics_info so that it can be removed if the session resumes.
|
||||
if (success) {
|
||||
sqlite3_stmt *updateFlowEvent;
|
||||
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET last_flow_event = (SELECT event_id FROM events WHERE rowid = ?)", -1, &updateFlowEvent, NULL);
|
||||
sqlite3_int64 lastRow = sqlite3_last_insert_rowid(_databaseConnection);
|
||||
sqlite3_bind_int64(updateFlowEvent, 1, lastRow);
|
||||
int code = sqlite3_step(updateFlowEvent);
|
||||
sqlite3_finalize(updateFlowEvent);
|
||||
success = code == SQLITE_DONE;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
[self releaseTransaction:t];
|
||||
} else {
|
||||
[self rollbackTransaction:t];
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)removeLastCloseAndFlowEvents {
|
||||
// Attempt to remove the last recorded close event.
|
||||
// Fail quietly if none was saved or it was previously removed.
|
||||
int code = sqlite3_exec(_databaseConnection, "DELETE FROM events WHERE event_id = (SELECT last_close_event FROM localytics_info) OR event_id = (SELECT last_flow_event FROM localytics_info)", NULL, NULL, NULL);
|
||||
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
- (BOOL)addHeaderWithSequenceNumber:(int)number blobString:(NSString *)blob rowId:(sqlite3_int64 *)insertedRowId {
|
||||
sqlite3_stmt *insertHeader;
|
||||
sqlite3_prepare_v2(_databaseConnection, "INSERT INTO upload_headers (sequence_number, blob_string) VALUES (?, ?)", -1, &insertHeader, NULL);
|
||||
sqlite3_bind_int(insertHeader, 1, number);
|
||||
sqlite3_bind_text(insertHeader, 2, [blob UTF8String], -1, SQLITE_TRANSIENT);
|
||||
int code = sqlite3_step(insertHeader);
|
||||
sqlite3_finalize(insertHeader);
|
||||
|
||||
if (code == SQLITE_DONE && insertedRowId != NULL) {
|
||||
*insertedRowId = sqlite3_last_insert_rowid(_databaseConnection);
|
||||
}
|
||||
|
||||
return code == SQLITE_DONE;
|
||||
}
|
||||
|
||||
- (int)unstagedEventCount {
|
||||
int rowCount = 0;
|
||||
sqlite3_stmt *selectEventCount;
|
||||
sqlite3_prepare_v2(_databaseConnection, "SELECT COUNT(*) FROM events WHERE UPLOAD_HEADER IS NULL", -1, &selectEventCount, NULL);
|
||||
int code = sqlite3_step(selectEventCount);
|
||||
if (code == SQLITE_ROW) {
|
||||
rowCount = sqlite3_column_int(selectEventCount, 0);
|
||||
}
|
||||
sqlite3_finalize(selectEventCount);
|
||||
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
- (BOOL)stageEventsForUpload:(sqlite3_int64)headerId {
|
||||
|
||||
// Associate all outstanding events with the given upload header ID.
|
||||
NSString *stageEvents = [NSString stringWithFormat:@"UPDATE events SET upload_header = ? WHERE upload_header IS NULL"];
|
||||
sqlite3_stmt *updateEvents;
|
||||
sqlite3_prepare_v2(_databaseConnection, [stageEvents UTF8String], -1, &updateEvents, NULL);
|
||||
sqlite3_bind_int(updateEvents, 1, headerId);
|
||||
int code = sqlite3_step(updateEvents);
|
||||
sqlite3_finalize(updateEvents);
|
||||
BOOL success = (code == SQLITE_DONE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)updateAppKey:(NSString *)appKey {
|
||||
sqlite3_stmt *updateAppKey;
|
||||
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info set app_key = ?", -1, &updateAppKey, NULL);
|
||||
sqlite3_bind_text (updateAppKey, 1, [appKey UTF8String], -1, SQLITE_TRANSIENT);
|
||||
int code = sqlite3_step(updateAppKey);
|
||||
sqlite3_finalize(updateAppKey);
|
||||
BOOL success = (code == SQLITE_DONE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
- (NSString *)uploadBlobString {
|
||||
|
||||
// Retrieve the blob strings of each upload header and its child events, in order.
|
||||
const char *sql = "SELECT * FROM ( "
|
||||
" SELECT h.blob_string AS 'blob', h.sequence_number as 'seq', 0 FROM upload_headers h"
|
||||
" UNION ALL "
|
||||
" SELECT e.blob_string AS 'blob', e.upload_header as 'seq', 1 FROM events e"
|
||||
") "
|
||||
"ORDER BY 2, 3";
|
||||
sqlite3_stmt *selectBlobs;
|
||||
sqlite3_prepare_v2(_databaseConnection, sql, -1, &selectBlobs, NULL);
|
||||
NSMutableString *uploadBlobString = [NSMutableString string];
|
||||
while (sqlite3_step(selectBlobs) == SQLITE_ROW) {
|
||||
const char *blob = (const char *)sqlite3_column_text(selectBlobs, 0);
|
||||
if (blob != NULL) {
|
||||
NSString *blobString = [[NSString alloc] initWithCString:blob encoding:NSUTF8StringEncoding];
|
||||
[uploadBlobString appendString:blobString];
|
||||
[blobString release];
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(selectBlobs);
|
||||
|
||||
return [[uploadBlobString copy] autorelease];
|
||||
}
|
||||
|
||||
- (BOOL)deleteUploadedData {
|
||||
// Delete all headers and staged events.
|
||||
NSString *t = @"delete_upload_data";
|
||||
int code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection, "DELETE FROM events WHERE upload_header IS NOT NULL", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection, "DELETE FROM upload_headers", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
[self releaseTransaction:t];
|
||||
} else {
|
||||
[self rollbackTransaction:t];
|
||||
}
|
||||
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
- (BOOL)resetAnalyticsData {
|
||||
// Delete or zero all analytics data.
|
||||
// Reset: headers, events, session number, upload number, last session start, last close event, and last flow event.
|
||||
// Unaffected: schema version, opt out status, install ID (deprecated), and app key.
|
||||
|
||||
NSString *t = @"reset_analytics_data";
|
||||
int code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection, "DELETE FROM events", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection, "DELETE FROM upload_headers", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,"UPDATE localytics_info SET last_session_number = 0, last_upload_number = 0,"
|
||||
"last_close_event = null, last_flow_event = null, last_session_start = null, "
|
||||
"custom_d0 = null, custom_d1 = null, custom_d2 = null, custom_d3 = null",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
[self releaseTransaction:t];
|
||||
} else {
|
||||
[self rollbackTransaction:t];
|
||||
}
|
||||
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
- (BOOL)vacuumIfRequired {
|
||||
int code = SQLITE_OK;
|
||||
if ([self databaseSize] > MAX_DATABASE_SIZE * VACUUM_THRESHOLD) {
|
||||
code = sqlite3_exec(_databaseConnection, "VACUUM", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
- (NSString *)randomUUID {
|
||||
CFUUIDRef theUUID = CFUUIDCreate(NULL);
|
||||
CFStringRef stringUUID = CFUUIDCreateString(NULL, theUUID);
|
||||
CFRelease(theUUID);
|
||||
return [(NSString *)stringUUID autorelease];
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
+ (id)allocWithZone:(NSZone *)zone {
|
||||
@synchronized(self) {
|
||||
if (_sharedLocalyticsDatabase == nil) {
|
||||
_sharedLocalyticsDatabase = [super allocWithZone:zone];
|
||||
return _sharedLocalyticsDatabase;
|
||||
}
|
||||
}
|
||||
// returns nil on subsequent allocations
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)retain {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (unsigned)retainCount {
|
||||
// maximum value of an unsigned int - prevents additional retains for the class
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
||||
- (oneway void)release {
|
||||
// ignore release commands
|
||||
}
|
||||
|
||||
- (id)autorelease {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
sqlite3_close(_databaseConnection);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
216
Localytics/LocalyticsSession.h
Normal file
216
Localytics/LocalyticsSession.h
Normal file
@@ -0,0 +1,216 @@
|
||||
// LocalyticsSession.h
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// This code is provided under the Localytics Modified BSD License.
|
||||
// A copy of this license has been distributed in a file called LICENSE
|
||||
// with this source code.
|
||||
//
|
||||
// Please visit www.localytics.com for more information.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
// Set this to true to enable localytics traces (useful for debugging)
|
||||
#define DO_LOCALYTICS_LOGGING false
|
||||
|
||||
/*!
|
||||
@class LocalyticsSession
|
||||
@discussion The class which manages creating, collecting, & uploading a Localytics session.
|
||||
Please see the following guides for information on how to best use this
|
||||
library, sample code, and other useful information:
|
||||
<ul>
|
||||
<li><a href="http://wiki.localytics.com/index.php?title=Developer's_Integration_Guide">Main Developer's Integration Guide</a></li>
|
||||
</ul>
|
||||
|
||||
<strong>Best Practices</strong>
|
||||
<ul>
|
||||
<li>Instantiate the LocalyticsSession object in applicationDidFinishLaunching.</li>
|
||||
<li>Open your session and begin your uploads in applicationDidFinishLaunching. This way the
|
||||
upload has time to complete and it all happens before your users have a
|
||||
chance to begin any data intensive actions of their own.</li>
|
||||
<li>Close the session in applicationWillTerminate, and in applicationDidEnterBackground.</li>
|
||||
<li>Resume the session in applicationWillEnterForeground.</li>
|
||||
<li>Do not call any Localytics functions inside a loop. Instead, calls
|
||||
such as <code>tagEvent</code> should follow user actions. This limits the
|
||||
amount of data which is stored and uploaded.</li>
|
||||
<li>Do not use multiple LocalticsSession objects to upload data with
|
||||
multiple application keys. This can cause invalid state.</li>
|
||||
</ul>
|
||||
|
||||
@author Localytics
|
||||
*/
|
||||
@interface LocalyticsSession : NSObject {
|
||||
|
||||
BOOL _hasInitialized; // Whether or not the session object has been initialized.
|
||||
BOOL _isSessionOpen; // Whether or not this session has been opened.
|
||||
float _backgroundSessionTimeout; // If an App stays in the background for more
|
||||
// than this many seconds, start a new session
|
||||
// when it returns to foreground.
|
||||
@private
|
||||
#pragma mark Member Variables
|
||||
dispatch_queue_t _queue; // Queue of Localytics block objects.
|
||||
dispatch_group_t _criticalGroup; // Group of blocks the must complete before backgrounding.
|
||||
NSString *_sessionUUID; // Unique identifier for this session.
|
||||
NSString *_applicationKey; // Unique identifier for the instrumented application
|
||||
NSTimeInterval _lastSessionStartTimestamp; // The start time of the most recent session.
|
||||
NSDate *_sessionResumeTime; // Time session was started or resumed.
|
||||
NSDate *_sessionCloseTime; // Time session was closed.
|
||||
NSMutableString *_unstagedFlowEvents; // Comma-delimited list of app screens and events tagged during this
|
||||
// session that have NOT been staged for upload.
|
||||
NSMutableString *_stagedFlowEvents; // App screens and events tagged during this session that HAVE been staged
|
||||
// for upload.
|
||||
NSMutableString *_screens; // Comma-delimited list of screens tagged during this session.
|
||||
NSTimeInterval _sessionActiveDuration; // Duration that session open.
|
||||
BOOL _sessionHasBeenOpen; // Whether or not this session has ever been open.
|
||||
}
|
||||
|
||||
@property dispatch_queue_t queue;
|
||||
@property dispatch_group_t criticalGroup;
|
||||
@property BOOL isSessionOpen;
|
||||
@property BOOL hasInitialized;
|
||||
@property float backgroundSessionTimeout;
|
||||
|
||||
#pragma mark Public Methods
|
||||
/*!
|
||||
@method sharedLocalyticsSession
|
||||
@abstract Accesses the Session object. This is a Singleton class which maintains
|
||||
a single session throughout your application. It is possible to manage your own
|
||||
session, but this is the easiest way to access the Localytics object throughout your code.
|
||||
The class is accessed within the code using the following syntax:
|
||||
[[LocalyticsSession sharedLocalyticsSession] functionHere]
|
||||
So, to tag an event, all that is necessary, anywhere in the code is:
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"MY_EVENT"];
|
||||
*/
|
||||
+ (LocalyticsSession *)sharedLocalyticsSession;
|
||||
|
||||
/*!
|
||||
@method LocalyticsSession
|
||||
@abstract Initializes the Localytics Object. Not necessary if you choose to use startSession.
|
||||
@param applicationKey The key unique for each application generated at www.localytics.com
|
||||
*/
|
||||
- (void)LocalyticsSession:(NSString *)appKey;
|
||||
|
||||
/*!
|
||||
@method startSession
|
||||
@abstract An optional convenience initialize method that also calls the LocalyticsSession, open &
|
||||
upload methods. Best Practice is to call open & upload immediately after Localytics Session when loading an app,
|
||||
this method fascilitates that behavior.
|
||||
It is recommended that this call be placed in <code>applicationDidFinishLaunching</code>.
|
||||
@param applicationKey The key unique for each application generated
|
||||
at www.localytics.com
|
||||
*/
|
||||
- (void)startSession:(NSString *)appKey;
|
||||
|
||||
/*!
|
||||
@method setOptIn
|
||||
@abstract (OPTIONAL) Allows the application to control whether or not it will collect user data.
|
||||
Even if this call is used, it is necessary to continue calling upload(). No new data will be
|
||||
collected, so nothing new will be uploaded but it is necessary to upload an event telling the
|
||||
server this user has opted out.
|
||||
@param optedIn True if the user is opted in, false otherwise.
|
||||
*/
|
||||
- (void)setOptIn:(BOOL)optedIn;
|
||||
|
||||
/*!
|
||||
@method isOptedIn
|
||||
@abstract (OPTIONAL) Whether or not this user has is opted in or out. The only way they can be
|
||||
opted out is if setOptIn(false) has been called before this. This function should only be
|
||||
used to pre-populate a checkbox in an options menu. It is not recommended that an application
|
||||
branch based on Localytics instrumentation because this creates an additional test case. If
|
||||
the app is opted out, all subsequent Localytics calls will return immediately.
|
||||
@result true if the user is opted in, false otherwise.
|
||||
*/
|
||||
- (BOOL)isOptedIn;
|
||||
|
||||
/*!
|
||||
@method open
|
||||
@abstract Opens the Localytics session. Not necessary if you choose to use startSession.
|
||||
The session time as presented on the website is the time between <code>open</code> and the
|
||||
final <code>close</code> so it is recommended to open the session as early as possible, and close
|
||||
it at the last moment. The session must be opened before any tags can
|
||||
be written. It is recommended that this call be placed in <code>applicationDidFinishLaunching</code>.
|
||||
<br>
|
||||
If for any reason this is called more than once every subsequent open call
|
||||
will be ignored.
|
||||
*/
|
||||
- (void)open;
|
||||
|
||||
/*!
|
||||
@method resume
|
||||
@abstract Resumes the Localytics session. When the App enters the background, the session is
|
||||
closed and the time of closing is recorded. When the app returns to the foreground, the session
|
||||
is resumed. If the time since closing is greater than BACKGROUND_SESSION_TIMEOUT, (15 seconds
|
||||
by default) a new session is created, and uploading is triggered. Otherwise, the previous session
|
||||
is reopened.
|
||||
*/
|
||||
- (void)resume;
|
||||
|
||||
/*!
|
||||
@method close
|
||||
@abstract Closes the Localytics session. This should be called in
|
||||
<code>applicationWillTerminate</code>.
|
||||
<br>
|
||||
If close is not called, the session will still be uploaded but no
|
||||
events will be processed and the session time will not appear. This is
|
||||
because the session is not yet closed so it should not be used in
|
||||
comparison with sessions which are closed.
|
||||
*/
|
||||
- (void)close;
|
||||
|
||||
/*!
|
||||
@method tagEvent
|
||||
@abstract Allows a session to tag a particular event as having occurred. For
|
||||
example, if a view has three buttons, it might make sense to tag
|
||||
each button click with the name of the button which was clicked.
|
||||
For another example, in a game with many levels it might be valuable
|
||||
to create a new tag every time the user gets to a new level in order
|
||||
to determine how far the average user is progressing in the game.
|
||||
<br>
|
||||
<strong>Tagging Best Practices</strong>
|
||||
<ul>
|
||||
<li>DO NOT use tags to record personally identifiable information.</li>
|
||||
<li>The best way to use tags is to create all the tag strings as predefined
|
||||
constants and only use those. This is more efficient and removes the risk of
|
||||
collecting personal information.</li>
|
||||
<li>Do not set tags inside loops or any other place which gets called
|
||||
frequently. This can cause a lot of data to be stored and uploaded.</li>
|
||||
</ul>
|
||||
<br>
|
||||
See the tagging guide at: http://wiki.localytics.com/
|
||||
@param event The name of the event which occurred.
|
||||
*/
|
||||
- (void)tagEvent:(NSString *)event;
|
||||
|
||||
- (void)tagEvent:(NSString *)event attributes:(NSDictionary *)attributes;
|
||||
|
||||
- (void)tagEvent:(NSString *)event attributes:(NSDictionary *)attributes reportAttributes:(NSDictionary *)reportAttributes;
|
||||
|
||||
/*!
|
||||
@method tagScreen
|
||||
@abstract Allows tagging the flow of screens encountered during the session.
|
||||
@param screen The name of the screen
|
||||
*/
|
||||
- (void)tagScreen:(NSString *)screen;
|
||||
|
||||
/*!
|
||||
@method upload
|
||||
@abstract Creates a low priority thread which uploads any Localytics data already stored
|
||||
on the device. This should be done early in the process life in order to
|
||||
guarantee as much time as possible for slow connections to complete. It is also reasonable
|
||||
to upload again when the application is exiting because if the upload is cancelled the data
|
||||
will just get uploaded the next time the app comes up.
|
||||
*/
|
||||
- (void)upload;
|
||||
|
||||
/*!
|
||||
@method setCustomDimension
|
||||
@abstract (ENTERPRISE ONLY) Sets the value of a custom dimension. Custom dimensions are dimensions
|
||||
which contain user defined data unlike the predefined dimensions such as carrier, model, and country.
|
||||
Once a value for a custom dimension is set, the device it was set on will continue to upload that value
|
||||
until the value is changed. To clear a value pass nil as the value.
|
||||
The proper use of custom dimensions involves defining a dimension with less than ten distinct possible
|
||||
values and assigning it to one of the four available custom dimensions. Once assigned this definition should
|
||||
never be changed without changing the App Key otherwise old installs of the application will pollute new data.
|
||||
*/
|
||||
- (void)setCustomDimension:(int)dimension value:(NSString *)value;
|
||||
|
||||
@end
|
||||
1148
Localytics/LocalyticsSession.m
Normal file
1148
Localytics/LocalyticsSession.m
Normal file
File diff suppressed because it is too large
Load Diff
42
Localytics/LocalyticsUploader.h
Normal file
42
Localytics/LocalyticsUploader.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// LocalyticsUploader.h
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// This code is provided under the Localytics Modified BSD License.
|
||||
// A copy of this license has been distributed in a file called LICENSE
|
||||
// with this source code.
|
||||
//
|
||||
// Please visit www.localytics.com for more information.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/*!
|
||||
@class LocalyticsUploader
|
||||
@discussion Singleton class to handle data uploads
|
||||
*/
|
||||
|
||||
@interface LocalyticsUploader : NSObject {
|
||||
}
|
||||
|
||||
@property (readonly) BOOL isUploading;
|
||||
|
||||
/*!
|
||||
@method sharedLocalyticsUploader
|
||||
@abstract Establishes this as a Singleton Class allowing for data persistence.
|
||||
The class is accessed within the code using the following syntax:
|
||||
[[LocalyticsUploader sharedLocalyticsUploader] functionHere]
|
||||
*/
|
||||
+ (LocalyticsUploader *)sharedLocalyticsUploader;
|
||||
|
||||
/*!
|
||||
@method LocalyticsUploader
|
||||
@abstract Creates a thread which uploads all queued header and event data.
|
||||
All files starting with sessionFilePrefix are renamed,
|
||||
uploaded and deleted on upload. This way the sessions can continue
|
||||
writing data regardless of whether or not the upload succeeds. Files
|
||||
which have been renamed still count towards the total number of Localytics
|
||||
files which can be stored on the disk.
|
||||
@param localyticsApplicationKey the Localytics application ID
|
||||
*/
|
||||
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey;
|
||||
|
||||
@end
|
||||
236
Localytics/LocalyticsUploader.m
Normal file
236
Localytics/LocalyticsUploader.m
Normal file
@@ -0,0 +1,236 @@
|
||||
// LocalyticsUploader.m
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// This code is provided under the Localytics Modified BSD License.
|
||||
// A copy of this license has been distributed in a file called LICENSE
|
||||
// with this source code.
|
||||
//
|
||||
// Please visit www.localytics.com for more information.
|
||||
|
||||
#import "LocalyticsUploader.h"
|
||||
#import "LocalyticsSession.h"
|
||||
#import "LocalyticsDatabase.h"
|
||||
#import <zlib.h>
|
||||
|
||||
#define LOCALYTICS_URL @"http://analytics.localytics.com/api/v2/applications/%@/uploads"
|
||||
|
||||
static LocalyticsUploader *_sharedUploader = nil;
|
||||
|
||||
@interface LocalyticsUploader ()
|
||||
- (void)finishUpload;
|
||||
- (NSData *)gzipDeflatedDataWithData:(NSData *)data;
|
||||
- (void)logMessage:(NSString *)message;
|
||||
|
||||
@property (readwrite) BOOL isUploading;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LocalyticsUploader
|
||||
@synthesize isUploading = _isUploading;
|
||||
|
||||
#pragma mark - Singleton Class
|
||||
+ (LocalyticsUploader *)sharedLocalyticsUploader {
|
||||
@synchronized(self) {
|
||||
if (_sharedUploader == nil) {
|
||||
_sharedUploader = [[self alloc] init];
|
||||
}
|
||||
}
|
||||
return _sharedUploader;
|
||||
}
|
||||
|
||||
#pragma mark - Class Methods
|
||||
|
||||
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey {
|
||||
|
||||
// Do nothing if already uploading.
|
||||
if (self.isUploading == true)
|
||||
{
|
||||
[self logMessage:@"Upload already in progress. Aborting."];
|
||||
return;
|
||||
}
|
||||
|
||||
[self logMessage:@"Beginning upload process"];
|
||||
self.isUploading = true;
|
||||
|
||||
// Prepare the data for upload. The upload could take a long time, so some effort has to be made to be sure that events
|
||||
// which get written while the upload is taking place don't get lost or duplicated. To achieve this, the logic is:
|
||||
// 1) Append every header row blob string and and those of its associated events to the upload string.
|
||||
// 2) Deflate and upload the data.
|
||||
// 3) On success, delete all blob headers and staged events. Events added while an upload is in process are not
|
||||
// deleted because they are not associated a header (and cannot be until the upload completes).
|
||||
|
||||
// Step 1
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
LocalyticsDatabase *db = [LocalyticsDatabase sharedLocalyticsDatabase];
|
||||
NSString *blobString = [db uploadBlobString];
|
||||
|
||||
if ([blobString length] == 0) {
|
||||
// There is nothing outstanding to upload.
|
||||
[self logMessage:@"Abandoning upload. There are no new events."];
|
||||
[pool drain];
|
||||
[self finishUpload];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *requestData = [blobString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *myString = [[[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding] autorelease];
|
||||
[self logMessage:[NSString stringWithFormat:@"Uploading data (length: %u)", [myString length]]];
|
||||
|
||||
// Step 2
|
||||
NSData *deflatedRequestData = [[self gzipDeflatedDataWithData:requestData] retain];
|
||||
|
||||
[pool drain];
|
||||
|
||||
NSString *apiUrlString = [NSString stringWithFormat:LOCALYTICS_URL, [localyticsApplicationKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSMutableURLRequest *submitRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:apiUrlString]
|
||||
cachePolicy:NSURLRequestReloadIgnoringCacheData
|
||||
timeoutInterval:60.0];
|
||||
[submitRequest setHTTPMethod:@"POST"];
|
||||
[submitRequest setValue:@"application/x-gzip" forHTTPHeaderField:@"Content-Type"];
|
||||
[submitRequest setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
|
||||
[submitRequest setValue:[NSString stringWithFormat:@"%d", [deflatedRequestData length]] forHTTPHeaderField:@"Content-Length"];
|
||||
[submitRequest setHTTPBody:deflatedRequestData];
|
||||
[deflatedRequestData release];
|
||||
|
||||
// Perform synchronous upload in an async dispatch. This is necessary because the calling block will not persist to
|
||||
// receive the response data.
|
||||
dispatch_group_async([[LocalyticsSession sharedLocalyticsSession] criticalGroup], [[LocalyticsSession sharedLocalyticsSession] queue], ^{
|
||||
@try {
|
||||
NSURLResponse *response = nil;
|
||||
NSError *responseError = nil;
|
||||
[NSURLConnection sendSynchronousRequest:submitRequest returningResponse:&response error:&responseError];
|
||||
NSInteger responseStatusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
|
||||
if (responseError) {
|
||||
// On error, simply print the error and close the uploader. We have to assume the data was not transmited
|
||||
// so it is not deleted. In the event that we accidently store data which was succesfully uploaded, the
|
||||
// duplicate data will be ignored by the server when it is next uploaded.
|
||||
[self logMessage:[NSString stringWithFormat:
|
||||
@"Error Uploading. Code: %d, Description: %@",
|
||||
[responseError code],
|
||||
[responseError localizedDescription]]];
|
||||
} else {
|
||||
// Step 3
|
||||
// While response status codes in the 5xx range leave upload rows intact, the default case is to delete.
|
||||
if (responseStatusCode >= 500 && responseStatusCode < 600) {
|
||||
[self logMessage:[NSString stringWithFormat:@"Upload failed with response status code %d", responseStatusCode]];
|
||||
} else {
|
||||
// Because only one instance of the uploader can be running at a time it should not be possible for
|
||||
// new upload rows to appear so there is no fear of deleting data which has not yet been uploaded.
|
||||
[self logMessage:[NSString stringWithFormat:@"Upload completed successfully. Response code %d", responseStatusCode]];
|
||||
[[LocalyticsDatabase sharedLocalyticsDatabase] deleteUploadedData];
|
||||
}
|
||||
}
|
||||
}
|
||||
@catch (NSException * e) {}
|
||||
|
||||
[self finishUpload];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)finishUpload
|
||||
{
|
||||
self.isUploading = false;
|
||||
|
||||
// Upload data has been deleted. Recover the disk space if necessary.
|
||||
[[LocalyticsDatabase sharedLocalyticsDatabase] vacuumIfRequired];
|
||||
}
|
||||
|
||||
/*!
|
||||
@method gzipDeflatedDataWithData
|
||||
@abstract Deflates the provided data using gzip at the default compression level (6). Complete NSData gzip category available on CocoaDev. http://www.cocoadev.com/index.pl?NSDataCategory.
|
||||
@return the deflated data
|
||||
*/
|
||||
- (NSData *)gzipDeflatedDataWithData:(NSData *)data
|
||||
{
|
||||
if ([data length] == 0) return data;
|
||||
|
||||
z_stream strm;
|
||||
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.total_out = 0;
|
||||
strm.next_in=(Bytef *)[data bytes];
|
||||
strm.avail_in = [data length];
|
||||
|
||||
// Compresssion Levels:
|
||||
// Z_NO_COMPRESSION
|
||||
// Z_BEST_SPEED
|
||||
// Z_BEST_COMPRESSION
|
||||
// Z_DEFAULT_COMPRESSION
|
||||
|
||||
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
|
||||
|
||||
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
|
||||
|
||||
do {
|
||||
|
||||
if (strm.total_out >= [compressed length])
|
||||
[compressed increaseLengthBy: 16384];
|
||||
|
||||
strm.next_out = [compressed mutableBytes] + strm.total_out;
|
||||
strm.avail_out = [compressed length] - strm.total_out;
|
||||
|
||||
deflate(&strm, Z_FINISH);
|
||||
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
deflateEnd(&strm);
|
||||
|
||||
[compressed setLength: strm.total_out];
|
||||
return [NSData dataWithData:compressed];
|
||||
}
|
||||
|
||||
/*!
|
||||
@method logMessage
|
||||
@abstract Logs a message with (localytics uploader) prepended to it
|
||||
@param message The message to log
|
||||
*/
|
||||
- (void) logMessage:(NSString *)message {
|
||||
if(DO_LOCALYTICS_LOGGING) {
|
||||
NSLog(@"(localytics uploader) %s\n", [message UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - System Functions
|
||||
+ (id)allocWithZone:(NSZone *)zone {
|
||||
@synchronized(self) {
|
||||
if (_sharedUploader == nil) {
|
||||
_sharedUploader = [super allocWithZone:zone];
|
||||
return _sharedUploader;
|
||||
}
|
||||
}
|
||||
// returns nil on subsequent allocations
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)retain {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (unsigned)retainCount {
|
||||
// maximum value of an unsigned int - prevents additional retains for the class
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
||||
- (oneway void)release {
|
||||
// ignore release commands
|
||||
}
|
||||
|
||||
- (id)autorelease {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_sharedUploader release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
48
Localytics/UploaderThread.h
Normal file
48
Localytics/UploaderThread.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// UploaderThread.h
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// This code is provided under the Localytics Modified BSD License.
|
||||
// A copy of this license has been distributed in a file called LICENSE
|
||||
// with this source code.
|
||||
//
|
||||
// Please visit www.localytics.com for more information.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/*!
|
||||
@class UploaderThread
|
||||
@discussion Singleton class to handle data uploads
|
||||
*/
|
||||
|
||||
@interface UploaderThread : NSObject {
|
||||
NSURLConnection *_uploadConnection; // The connection which uploads the bits
|
||||
NSInteger _responseStatusCode; // The HTTP response status code for the current connection
|
||||
|
||||
BOOL _isUploading; // A flag to gaurantee only one uploader instance can happen at once
|
||||
}
|
||||
|
||||
@property (nonatomic, retain) NSURLConnection *uploadConnection;
|
||||
|
||||
@property BOOL isUploading;
|
||||
|
||||
/*!
|
||||
@method sharedUploaderThread
|
||||
@abstract Establishes this as a Singleton Class allowing for data persistence.
|
||||
The class is accessed within the code using the following syntax:
|
||||
[[UploaderThread sharedUploaderThread] functionHere]
|
||||
*/
|
||||
+ (UploaderThread *)sharedUploaderThread;
|
||||
|
||||
/*!
|
||||
@method UploaderThread
|
||||
@abstract Creates a thread which uploads all queued header and event data.
|
||||
All files starting with sessionFilePrefix are renamed,
|
||||
uploaded and deleted on upload. This way the sessions can continue
|
||||
writing data regardless of whether or not the upload succeeds. Files
|
||||
which have been renamed still count towards the total number of Localytics
|
||||
files which can be stored on the disk.
|
||||
@param localyticsApplicationKey the Localytics application ID
|
||||
*/
|
||||
- (void)uploaderThreadwithApplicationKey:(NSString *)localyticsApplicationKey;
|
||||
|
||||
@end
|
||||
260
Localytics/UploaderThread.m
Normal file
260
Localytics/UploaderThread.m
Normal file
@@ -0,0 +1,260 @@
|
||||
// UploaderThread.m
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// This code is provided under the Localytics Modified BSD License.
|
||||
// A copy of this license has been distributed in a file called LICENSE
|
||||
// with this source code.
|
||||
//
|
||||
// Please visit www.localytics.com for more information.
|
||||
|
||||
#import "UploaderThread.h"
|
||||
#import "LocalyticsSession.h"
|
||||
#import "LocalyticsDatabase.h"
|
||||
#import <zlib.h>
|
||||
|
||||
#define LOCALYTICS_URL @"http://analytics.localytics.com/api/v2/applications/%@/uploads" // url to send the
|
||||
|
||||
static UploaderThread *_sharedUploaderThread = nil;
|
||||
|
||||
@interface UploaderThread ()
|
||||
- (void)complete;
|
||||
- (NSData *)gzipDeflatedDataWithData:(NSData *)data;
|
||||
- (void)logMessage:(NSString *)message;
|
||||
@end
|
||||
|
||||
@implementation UploaderThread
|
||||
|
||||
@synthesize uploadConnection = _uploadConnection;
|
||||
@synthesize isUploading = _isUploading;
|
||||
|
||||
#pragma mark Singleton Class
|
||||
+ (UploaderThread *)sharedUploaderThread {
|
||||
@synchronized(self) {
|
||||
if (_sharedUploaderThread == nil)
|
||||
{
|
||||
_sharedUploaderThread = [[self alloc] init];
|
||||
}
|
||||
}
|
||||
return _sharedUploaderThread;
|
||||
}
|
||||
|
||||
#pragma mark Class Methods
|
||||
- (void)uploaderThreadwithApplicationKey:(NSString *)localyticsApplicationKey {
|
||||
|
||||
// Do nothing if already uploading.
|
||||
if (self.uploadConnection != nil || self.isUploading == true)
|
||||
{
|
||||
[self logMessage:@"Upload already in progress. Aborting."];
|
||||
return;
|
||||
}
|
||||
|
||||
[self logMessage:@"Beginning upload process"];
|
||||
self.isUploading = true;
|
||||
|
||||
// Prepare the data for upload. The upload could take a long time, so some effort has to be made to be sure that events
|
||||
// which get written while the upload is taking place don't get lost or duplicated. To achieve this, the logic is:
|
||||
// 1) Append every header row blob string and and those of its associated events to the upload string.
|
||||
// 2) Deflate and upload the data.
|
||||
// 3) On success, delete all blob headers and staged events. Events added while an upload is in process are not
|
||||
// deleted because they are not associated a header (and cannot be until the upload completes).
|
||||
|
||||
// Step 1
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
LocalyticsDatabase *db = [LocalyticsDatabase sharedLocalyticsDatabase];
|
||||
NSString *blobString = [db uploadBlobString];
|
||||
|
||||
if ([blobString length] == 0) {
|
||||
// There is nothing outstanding to upload.
|
||||
[self logMessage:@"Abandoning upload. There are no new events."];
|
||||
|
||||
[pool drain];
|
||||
[self complete];
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *requestData = [blobString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *myString = [[[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding] autorelease];
|
||||
[self logMessage:@"Upload data:"];
|
||||
[self logMessage:myString];
|
||||
|
||||
// Step 2
|
||||
NSData *deflatedRequestData = [[self gzipDeflatedDataWithData:requestData] retain];
|
||||
|
||||
[pool drain];
|
||||
|
||||
NSString *apiUrlString = [NSString stringWithFormat:LOCALYTICS_URL, [localyticsApplicationKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSMutableURLRequest *submitRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:apiUrlString]
|
||||
cachePolicy:NSURLRequestReloadIgnoringCacheData
|
||||
timeoutInterval:60.0];
|
||||
[submitRequest setHTTPMethod:@"POST"];
|
||||
[submitRequest setValue:@"application/x-gzip" forHTTPHeaderField:@"Content-Type"];
|
||||
[submitRequest setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
|
||||
[submitRequest setValue:[NSString stringWithFormat:@"%d", [deflatedRequestData length]] forHTTPHeaderField:@"Content-Length"];
|
||||
[submitRequest setHTTPBody:deflatedRequestData];
|
||||
[deflatedRequestData release];
|
||||
|
||||
// The NSURLConnection Object automatically spawns its own thread as a default behavior.
|
||||
@try
|
||||
{
|
||||
[self logMessage:@"Spawning new thread for upload"];
|
||||
self.uploadConnection = [NSURLConnection connectionWithRequest:submitRequest delegate:self];
|
||||
|
||||
// Step 3 is handled by connectionDidFinishLoading.
|
||||
}
|
||||
@catch (NSException * e)
|
||||
{
|
||||
[self complete];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark **** NSURLConnection FUNCTIONS ****
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
|
||||
// Used to gather response data from server - Not utilized in this version
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
|
||||
// Could receive multiple response callbacks, likely due to redirection.
|
||||
// Record status and act only when connection completes load.
|
||||
_responseStatusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
}
|
||||
|
||||
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
|
||||
// If the connection finished loading, the files should be deleted. While response status codes in the 5xx range
|
||||
// leave upload rows intact, the default case is to delete.
|
||||
if (_responseStatusCode >= 500 && _responseStatusCode < 600)
|
||||
{
|
||||
[self logMessage:[NSString stringWithFormat:@"Upload failed with response status code %d", _responseStatusCode]];
|
||||
} else
|
||||
{
|
||||
// The connection finished loading and uploaded data should be deleted. Because only one instance of the
|
||||
// uploader can be running at a time it should not be possible for new upload rows to appear so there is no
|
||||
// fear of deleting data which has not yet been uploaded.
|
||||
[self logMessage:[NSString stringWithFormat:@"Upload completed successfully. Response code %d", _responseStatusCode]];
|
||||
[[LocalyticsDatabase sharedLocalyticsDatabase] deleteUploadData];
|
||||
}
|
||||
|
||||
// Close upload session
|
||||
[self complete];
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
|
||||
// On error, simply print the error and close the uploader. We have to assume the data was not transmited
|
||||
// so it is not deleted. In the event that we accidently store data which was succesfully uploaded, the
|
||||
// duplicate data will be ignored by the server when it is next uploaded.
|
||||
[self logMessage:[NSString stringWithFormat:
|
||||
@"Error Uploading. Code: %d, Description: %s",
|
||||
[error code],
|
||||
[error localizedDescription]]];
|
||||
|
||||
[self complete];
|
||||
}
|
||||
|
||||
/*!
|
||||
@method complete
|
||||
@abstract closes the upload connection and reports back to the session that the upload is complete
|
||||
*/
|
||||
- (void)complete {
|
||||
_responseStatusCode = 0;
|
||||
self.uploadConnection = nil;
|
||||
self.isUploading = false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@method gzipDeflatedDataWithData
|
||||
@abstract Deflates the provided data using gzip at the default compression level (6). Complete NSData gzip category available on CocoaDev. http://www.cocoadev.com/index.pl?NSDataCategory.
|
||||
@return the deflated data
|
||||
*/
|
||||
- (NSData *)gzipDeflatedDataWithData:(NSData *)data
|
||||
{
|
||||
if ([data length] == 0) return data;
|
||||
|
||||
z_stream strm;
|
||||
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.total_out = 0;
|
||||
strm.next_in=(Bytef *)[data bytes];
|
||||
strm.avail_in = [data length];
|
||||
|
||||
// Compresssion Levels:
|
||||
// Z_NO_COMPRESSION
|
||||
// Z_BEST_SPEED
|
||||
// Z_BEST_COMPRESSION
|
||||
// Z_DEFAULT_COMPRESSION
|
||||
|
||||
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
|
||||
|
||||
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
|
||||
|
||||
do {
|
||||
|
||||
if (strm.total_out >= [compressed length])
|
||||
[compressed increaseLengthBy: 16384];
|
||||
|
||||
strm.next_out = [compressed mutableBytes] + strm.total_out;
|
||||
strm.avail_out = [compressed length] - strm.total_out;
|
||||
|
||||
deflate(&strm, Z_FINISH);
|
||||
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
deflateEnd(&strm);
|
||||
|
||||
[compressed setLength: strm.total_out];
|
||||
return [NSData dataWithData:compressed];
|
||||
}
|
||||
|
||||
/*!
|
||||
@method logMessage
|
||||
@abstract Logs a message with (localytics uploader) prepended to it
|
||||
@param message The message to log
|
||||
*/
|
||||
- (void) logMessage:(NSString *)message {
|
||||
if(DO_LOCALYTICS_LOGGING) {
|
||||
NSLog(@"(localytics uploader) %s\n", [message UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark System Functions
|
||||
+ (id)allocWithZone:(NSZone *)zone {
|
||||
@synchronized(self) {
|
||||
if (_sharedUploaderThread == nil) {
|
||||
_sharedUploaderThread = [super allocWithZone:zone];
|
||||
return _sharedUploaderThread;
|
||||
}
|
||||
}
|
||||
// returns nil on subsequent allocations
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)retain {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (unsigned)retainCount {
|
||||
// maximum value of an unsigned int - prevents additional retains for the class
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
||||
- (oneway void)release {
|
||||
// ignore release commands
|
||||
}
|
||||
|
||||
- (id)autorelease {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_uploadConnection release];
|
||||
[_sharedUploaderThread release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
111
Localytics/WebserviceConstants.h
Normal file
111
Localytics/WebserviceConstants.h
Normal file
@@ -0,0 +1,111 @@
|
||||
// WebserviceConstants.h
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// This code is provided under the Localytics Modified BSD License.
|
||||
// A copy of this license has been distributed in a file called LICENSE
|
||||
// with this source code.
|
||||
//
|
||||
// Please visit www.localytics.com for more information.
|
||||
|
||||
// The constants which are used to make up the JSON blob
|
||||
// To save disk space and network bandwidth all the keywords have been
|
||||
// abbreviated and are exploded by the server.
|
||||
|
||||
/*********************
|
||||
* Shared Attributes *
|
||||
*********************/
|
||||
#define PARAM_UUID @"u" // UUID for JSON document
|
||||
#define PARAM_DATA_TYPE @"dt" // Data Type
|
||||
#define PARAM_CLIENT_TIME @"ct" // Client Time, seconds from Unix epoch (int)
|
||||
#define PARAM_LATITUDE @"lat" // Latitude - if available
|
||||
#define PARAM_LONGITUDE @"lon" // Longitude - if available
|
||||
#define PARAM_SESSION_UUID @"su" // UUID for an existing session
|
||||
#define PARAM_NEW_SESSION_UUID @"u" // UUID for a new session
|
||||
#define PARAM_ATTRIBUTES @"attrs" // Attributes (dictionary)
|
||||
|
||||
/***************
|
||||
* Blob Header *
|
||||
***************/
|
||||
|
||||
// PARAM_UUID
|
||||
// PARAM_DATA_TYPE => "h" for Header
|
||||
// PARAM_ATTRIBUTES => dictionary containing Header Common Attributes
|
||||
#define PARAM_PERSISTED_AT @"pa" // Persistent Storage Created At. A timestamp created when the app was
|
||||
// first launched and the persistent storage was created. Stores as
|
||||
// seconds from Unix epoch. (int)
|
||||
#define PARAM_SEQUENCE_NUMBER @"seq" // Sequence number - an increasing count for each blob, stored in the
|
||||
// persistent store Consistent across app starts. (int)
|
||||
|
||||
/****************************
|
||||
* Header Common Attributes *
|
||||
****************************/
|
||||
|
||||
// PARAM_DATA_TYPE
|
||||
#define PARAM_APP_KEY @"au" // Localytics Application ID
|
||||
#define PARAM_DEVICE_UUID @"du" // Device UUID
|
||||
#define PARAM_DEVICE_UUID_HASHED @"udid" // Hashed version of the UUID
|
||||
#define PARAM_DEVICE_MAC @"wmac" // Hashed version of the device Mac
|
||||
#define PARAM_INSTALL_ID @"iu" // Install ID
|
||||
#define PARAM_JAILBROKEN @"j" // Jailbroken (boolean)
|
||||
#define PARAM_LIBRARY_VERSION @"lv" // Client Version
|
||||
#define PARAM_APP_VERSION @"av" // Application Version
|
||||
#define PARAM_DEVICE_PLATFORM @"dp" // Device Platform
|
||||
#define PARAM_LOCALE_LANGUAGE @"dll" // Locale Language
|
||||
#define PARAM_LOCALE_COUNTRY @"dlc" // Locale Country
|
||||
#define PARAM_NETWORK_COUNTRY @"nc" // Network Country (iso code) // ???: Never used on iPhone.
|
||||
#define PARAM_DEVICE_COUNTRY @"dc" // Device Country (iso code)
|
||||
#define PARAM_DEVICE_MANUFACTURER @"dma" // Device Manufacturer // ???: Never used on iPhone. Used to be "Device Make".
|
||||
#define PARAM_DEVICE_MODEL @"dmo" // Device Model
|
||||
#define PARAM_DEVICE_OS_VERSION @"dov" // Device OS Version
|
||||
#define PARAM_NETWORK_CARRIER @"nca" // Network Carrier
|
||||
#define PARAM_DATA_CONNECTION @"dac" // Data Connection Type // ???: Never used on iPhone.
|
||||
#define PARAM_OPT_VALUE @"optin" // Opt In (boolean)
|
||||
#define PARAM_DEVICE_MEMORY @"dmem" // Device Memory
|
||||
|
||||
/*****************
|
||||
* Session Start *
|
||||
*****************/
|
||||
|
||||
// PARAM_UUID
|
||||
// PARAM_DATA_TYPE => "s" for Start
|
||||
// PARAM_CLIENT_TIME
|
||||
#define PARAM_SESSION_NUMBER @"nth" // This is the nth session on the device, 1-indexed (int)
|
||||
|
||||
/****************
|
||||
* Session Stop *
|
||||
****************/
|
||||
|
||||
// PARAM_UUID
|
||||
// PARAM_DATA_TYPE => "c" for Close
|
||||
// PARAM_CLIENT_TIME
|
||||
// PARAM_LATITUDE
|
||||
// PARAM_LONGITUDE
|
||||
// PARAM_SESSION_UUID => UUID of session being closed
|
||||
#define PARAM_SESSION_ACTIVE @"cta" // Active time in seconds (time app was active)
|
||||
#define PARAM_SESSION_TOTAL @"ctl" // Total session length
|
||||
#define PARAM_SESSION_SCREENFLOW @"fl" // Screens encountered during this session, in order
|
||||
|
||||
/*********************
|
||||
* Application Event *
|
||||
*********************/
|
||||
|
||||
// PARAM_UUID
|
||||
// PARAM_DATA_TYPE => "e" for Event
|
||||
// PARAM_CLIENT_TIME
|
||||
// PARAM_LATITUDE
|
||||
// PARAM_LONGITUDE
|
||||
// PARAM_SESSION_UUID => UUID of session event occured in
|
||||
// PARAM_ATTRIBUTES => dictionary containing attributes for this event as key-value string pairs
|
||||
#define PARAM_EVENT_NAME @"n" // Event Name, (eg. 'Button Click')
|
||||
#define PARAM_REPORT_ATTRIBUTES @"rattrs" // Attributes used in custom reports
|
||||
|
||||
/********************
|
||||
* Application flow *
|
||||
********************/
|
||||
|
||||
// PARAM_UUID
|
||||
// PARAM_DATA_TYPE => "f" for Flow
|
||||
// PARAM_CLIENT_TIME
|
||||
#define PARAM_SESSION_START @"ss" // Start time for the current session.
|
||||
#define PARAM_NEW_FLOW_EVENTS @"nw" // Events and screens encountered during this session that have NOT been staged for upload.
|
||||
#define PARAM_OLD_FLOW_EVENTS @"od" // Events and screens encountered during this session that HAVE been staged for upload.
|
||||
6312
MasterPassword-Mac.xcodeproj/project.pbxproj
Normal file
6312
MasterPassword-Mac.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,6 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:MasterPassword.xcodeproj">
|
||||
location = "self:MasterPassword-Mac.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "NO"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DAB8D987150374AD00CED3BC"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DAB8D987150374AD00CED3BC"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DAB8D987150374AD00CED3BC"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "-com.apple.coredata.ubiquity.logLevel 3"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DAB8D987150374AD00CED3BC"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
4866
MasterPassword-iOS.xcodeproj/project.pbxproj
Normal file
4866
MasterPassword-iOS.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load Diff
7
MasterPassword-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
MasterPassword-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:MasterPassword-iOS.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "NO"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "AppStore"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "AppStore"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "AppStore"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "NO"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "AppStore"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "AdHoc"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
Binary file not shown.
Binary file not shown.
BIN
MasterPassword.sketch/QuickLook/Preview.png
Normal file
BIN
MasterPassword.sketch/QuickLook/Preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB |
BIN
MasterPassword.sketch/QuickLook/Thumbnail.png
Normal file
BIN
MasterPassword.sketch/QuickLook/Thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
version = "1.3">
|
||||
<BuildAction>
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForRunning = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Production">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
</Scheme>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
version = "1.3">
|
||||
<BuildAction>
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForRunning = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "MasterPassword.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
</Scheme>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// MPAppDelegate.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface MPAppDelegate : PearlAppDelegate
|
||||
|
||||
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
|
||||
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
|
||||
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||
@property (readonly, strong, nonatomic) NSData *keyPhrase;
|
||||
@property (readonly, strong, nonatomic) NSData *keyPhraseHash;
|
||||
@property (readonly, strong, nonatomic) NSString *keyPhraseHashHex;
|
||||
|
||||
+ (MPAppDelegate *)get;
|
||||
+ (NSManagedObjectModel *)managedObjectModel;
|
||||
+ (NSManagedObjectContext *)managedObjectContext;
|
||||
|
||||
- (void)saveContext;
|
||||
- (NSURL *)applicationDocumentsDirectory;
|
||||
|
||||
- (void)showGuide;
|
||||
- (void)loadKeyPhrase:(BOOL)animated;
|
||||
- (void)signOut;
|
||||
- (void)forgetKeyPhrase;
|
||||
- (NSData *)keyPhraseWithLength:(NSUInteger)keyLength;
|
||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword;
|
||||
|
||||
@end
|
||||
@@ -1,521 +0,0 @@
|
||||
//
|
||||
// MPAppDelegate.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAppDelegate.h"
|
||||
|
||||
#import "MPMainViewController.h"
|
||||
#import "IASKSettingsReader.h"
|
||||
|
||||
@interface MPAppDelegate ()
|
||||
|
||||
@property (strong, nonatomic) NSData *keyPhrase;
|
||||
@property (strong, nonatomic) NSData *keyPhraseHash;
|
||||
@property (strong, nonatomic) NSString *keyPhraseHashHex;
|
||||
|
||||
+ (NSDictionary *)keyPhraseQuery;
|
||||
+ (NSDictionary *)keyPhraseHashQuery;
|
||||
|
||||
- (void)loadStoredKeyPhrase;
|
||||
- (void)askKeyPhrase:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPAppDelegate
|
||||
|
||||
@synthesize managedObjectModel = __managedObjectModel;
|
||||
@synthesize managedObjectContext = __managedObjectContext;
|
||||
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator;
|
||||
|
||||
@synthesize keyPhrase = _keyPhrase;
|
||||
@synthesize keyPhraseHash = _keyPhraseHash;
|
||||
@synthesize keyPhraseHashHex = _keyPhraseHashHex;
|
||||
|
||||
+ (void)initialize {
|
||||
|
||||
[MPConfig get];
|
||||
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelTrace;
|
||||
[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSDictionary *)keyPhraseQuery {
|
||||
|
||||
static NSDictionary *MPKeyPhraseQuery = nil;
|
||||
if (!MPKeyPhraseQuery)
|
||||
MPKeyPhraseQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObject:@"MasterPassword"
|
||||
forKey:(__bridge id)kSecAttrService]
|
||||
matches:nil];
|
||||
|
||||
return MPKeyPhraseQuery;
|
||||
}
|
||||
|
||||
+ (NSDictionary *)keyPhraseHashQuery {
|
||||
|
||||
static NSDictionary *MPKeyPhraseHashQuery = nil;
|
||||
if (!MPKeyPhraseHashQuery)
|
||||
MPKeyPhraseHashQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObject:@"MasterPasswordHash"
|
||||
forKey:(__bridge id)kSecAttrService]
|
||||
matches:nil];
|
||||
|
||||
return MPKeyPhraseHashQuery;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
#ifndef PRODUCTION
|
||||
@try {
|
||||
[TestFlight takeOff:@"bd44885deee7adce0645ce8e5498d80a_NDQ5NDQyMDExLTEyLTAyIDExOjM1OjQ4LjQ2NjM4NA"];
|
||||
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], @"logToConsole",
|
||||
[NSNumber numberWithBool:NO], @"logToSTDERR",
|
||||
nil]];
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelInfo)
|
||||
TFLog(@"%@", message);
|
||||
|
||||
return YES;
|
||||
}];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
err(@"TestFlight: %@", exception);
|
||||
}
|
||||
#endif
|
||||
|
||||
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
|
||||
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault];
|
||||
[[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsLandscapePhone];
|
||||
[[UINavigationBar appearance] setTitleTextAttributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0], UITextAttributeFont,
|
||||
nil]];
|
||||
|
||||
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
|
||||
UIImage *navBarBack = [[UIImage imageNamed:@"ui_navbar_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 5)];
|
||||
[[UIBarButtonItem appearance] setBackgroundImage:navBarButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UIBarButtonItem appearance] setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
|
||||
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:navBarBack forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
|
||||
[[UIBarButtonItem appearance] setTitleTextAttributes:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0], UITextAttributeTextColor,
|
||||
[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5], UITextAttributeTextShadowColor,
|
||||
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
|
||||
[UIFont fontWithName:@"Helvetica-Neue" size:0.0], UITextAttributeFont,
|
||||
nil]
|
||||
forState:UIControlStateNormal];
|
||||
|
||||
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
|
||||
[[UISearchBar appearance] setBackgroundImage:toolBarImage];
|
||||
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
|
||||
|
||||
/*
|
||||
UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
|
||||
UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
|
||||
|
||||
[[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
|
||||
[[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
|
||||
[[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
|
||||
|
||||
UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
|
||||
UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
|
||||
UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
|
||||
UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
|
||||
UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
|
||||
|
||||
[[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
|
||||
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
|
||||
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
if ([NSStringFromSelector(@selector(storeKeyPhrase))
|
||||
isEqualToString:[note.object description]]) {
|
||||
self.keyPhrase = self.keyPhrase;
|
||||
[self loadKeyPhrase:YES];
|
||||
}
|
||||
if ([NSStringFromSelector(@selector(forgetKeyPhrase))
|
||||
isEqualToString:[note.object description]])
|
||||
[self loadKeyPhrase:YES];
|
||||
}];
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
|
||||
@"Thank you for taking the time to test Master Password.\n\n"
|
||||
@"Please provide any feedback, however minor it may seem, via the Feedback action item accessible from the top right.\n\n"
|
||||
@"Contact me directly at:\n"
|
||||
@"lhunath@lyndir.com\n"
|
||||
@"Or report detailed issues at:\n"
|
||||
@"https://youtrack.lyndir.com\n"
|
||||
viewStyle:UIAlertViewStyleDefault tappedButtonBlock:nil
|
||||
cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil];
|
||||
#endif
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO
|
||||
withAnimation:UIStatusBarAnimationSlide];
|
||||
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
|
||||
if ([[MPConfig get].showQuickStart boolValue])
|
||||
[self showGuide];
|
||||
else
|
||||
[self loadKeyPhrase:NO];
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)showGuide {
|
||||
|
||||
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)loadKeyPhrase:(BOOL)animated {
|
||||
|
||||
if (self.keyPhrase)
|
||||
return;
|
||||
|
||||
[self loadStoredKeyPhrase];
|
||||
if (!self.keyPhrase) {
|
||||
// Key phrase is not known. Ask user to set/specify it.
|
||||
dbg(@"Key phrase not known. Will ask user.");
|
||||
[self askKeyPhrase:animated];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)forgetKeyPhrase {
|
||||
|
||||
dbg(@"Forgetting key phrase.");
|
||||
[PearlAlert showAlertWithTitle:@"Changing Master Password"
|
||||
message:
|
||||
@"This will allow you to log in with a different master password.\n\n"
|
||||
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
|
||||
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
|
||||
@"You can always change back to your current master password later.\n"
|
||||
@"Your current sites and passwords will then become available again."
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
if (buttonIndex != [alert cancelButtonIndex]) {
|
||||
// Key phrase reset. Delete it.
|
||||
dbg(@"Deleting master key phrase and hash from key chain.");
|
||||
[PearlKeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]];
|
||||
[PearlKeyChain deleteItemForQuery:[MPAppDelegate keyPhraseHashQuery]];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
|
||||
#endif
|
||||
}
|
||||
|
||||
[self loadKeyPhrase:YES];
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
|
||||
#endif
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||
}
|
||||
|
||||
- (void)signOut {
|
||||
|
||||
self.keyPhrase = nil;
|
||||
[self loadKeyPhrase:YES];
|
||||
}
|
||||
|
||||
- (void)loadStoredKeyPhrase {
|
||||
|
||||
if ([[MPConfig get].storeKeyPhrase boolValue]) {
|
||||
// Key phrase is stored in keychain. Load it.
|
||||
dbg(@"Loading master key phrase from key chain.");
|
||||
self.keyPhrase = [PearlKeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseQuery]];
|
||||
dbg(@" -> Master key phrase %@.", self.keyPhrase? @"found": @"NOT found");
|
||||
} else {
|
||||
// Key phrase should not be stored in keychain. Delete it.
|
||||
dbg(@"Deleting master key phrase from key chain.");
|
||||
[PearlKeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]];
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (void)askKeyPhrase:(BOOL)animated {
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.navigationController presentViewController:
|
||||
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
|
||||
animated:animated completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
|
||||
[self saveContext];
|
||||
|
||||
if (![[MPConfig get].rememberKeyPhrase boolValue])
|
||||
self.keyPhrase = nil;
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
|
||||
[self saveContext];
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (MPAppDelegate *)get {
|
||||
|
||||
return (MPAppDelegate *)[super get];
|
||||
}
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext {
|
||||
|
||||
return [(MPAppDelegate *)[UIApplication sharedApplication].delegate managedObjectContext];
|
||||
}
|
||||
|
||||
+ (NSManagedObjectModel *)managedObjectModel {
|
||||
|
||||
return [(MPAppDelegate *)[UIApplication sharedApplication].delegate managedObjectModel];
|
||||
}
|
||||
|
||||
- (void)saveContext {
|
||||
|
||||
[self.managedObjectContext performBlock:^{
|
||||
NSError *error = nil;
|
||||
if ([self.managedObjectContext hasChanges] && ![self.managedObjectContext save:&error])
|
||||
err(@"Unresolved error %@", error);
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword {
|
||||
|
||||
NSData *keyPhraseHash = [PearlKeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseHashQuery]];
|
||||
dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known");
|
||||
|
||||
if (![tryPassword length])
|
||||
return NO;
|
||||
|
||||
NSData *tryKeyPhrase = keyPhraseForPassword(tryPassword);
|
||||
NSData *tryKeyPhraseHash = keyPhraseHashForKeyPhrase(tryKeyPhrase);
|
||||
if (keyPhraseHash)
|
||||
// A key phrase hash is known -> a key phrase is set.
|
||||
// Make sure the user's entered key phrase matches it.
|
||||
if (![keyPhraseHash isEqual:tryKeyPhraseHash]) {
|
||||
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyPhraseHash, tryKeyPhraseHash);
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
||||
#endif
|
||||
return NO;
|
||||
}
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPAsked];
|
||||
#endif
|
||||
|
||||
self.keyPhrase = tryKeyPhrase;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setKeyPhrase:(NSData *)keyPhrase {
|
||||
|
||||
_keyPhrase = keyPhrase;
|
||||
|
||||
if (keyPhrase)
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
|
||||
else
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
|
||||
|
||||
if (keyPhrase) {
|
||||
self.keyPhraseHash = keyPhraseHashForKeyPhrase(keyPhrase);
|
||||
self.keyPhraseHashHex = [self.keyPhraseHash encodeHex];
|
||||
|
||||
dbg(@"Updating master key phrase hash to: %@.", self.keyPhraseHashHex);
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseHashQuery]
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.keyPhraseHash, (__bridge id)kSecValueData,
|
||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
||||
nil]];
|
||||
if ([[MPConfig get].storeKeyPhrase boolValue]) {
|
||||
dbg(@"Storing master key phrase in key chain.");
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseQuery]
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
keyPhrase, (__bridge id)kSecValueData,
|
||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
||||
nil]];
|
||||
}
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSetKeyphraseLength, _keyPhrase.length]];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *)keyPhraseWithLength:(NSUInteger)keyLength {
|
||||
|
||||
return [self.keyPhrase subdataWithRange:NSMakeRange(0, MIN(keyLength, self.keyPhrase.length))];
|
||||
}
|
||||
|
||||
#pragma mark - Core Data stack
|
||||
|
||||
/**
|
||||
Returns the managed object context for the application.
|
||||
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
|
||||
*/
|
||||
- (NSManagedObjectContext *)managedObjectContext
|
||||
{
|
||||
if (__managedObjectContext)
|
||||
return __managedObjectContext;
|
||||
|
||||
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
|
||||
if (coordinator) {
|
||||
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
||||
__managedObjectContext.persistentStoreCoordinator = coordinator;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSPersistentStoreDidImportUbiquitousContentChangesNotification
|
||||
object:coordinator
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
dbg(@"Ubiquitous content change: %@", note);
|
||||
|
||||
[__managedObjectContext performBlock:^{
|
||||
[__managedObjectContext mergeChangesFromContextDidSaveNotification:note];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotification:
|
||||
[NSNotification notificationWithName:UIScreenModeDidChangeNotification
|
||||
object:self userInfo:[note userInfo]]];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
return __managedObjectContext;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the managed object model for the application.
|
||||
If the model doesn't already exist, it is created from the application's model.
|
||||
*/
|
||||
- (NSManagedObjectModel *)managedObjectModel
|
||||
{
|
||||
if (__managedObjectModel)
|
||||
return __managedObjectModel;
|
||||
|
||||
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MasterPassword" withExtension:@"momd"];
|
||||
return __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the persistent store coordinator for the application.
|
||||
If the coordinator doesn't already exist, it is created and the application's store added to it.
|
||||
*/
|
||||
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
|
||||
{
|
||||
if (__persistentStoreCoordinator)
|
||||
return __persistentStoreCoordinator;
|
||||
|
||||
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"];
|
||||
|
||||
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
|
||||
[__persistentStoreCoordinator lock];
|
||||
NSError *error = nil;
|
||||
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL
|
||||
options:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
|
||||
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
|
||||
@"MasterPassword.store", NSPersistentStoreUbiquitousContentNameKey,
|
||||
[[[NSFileManager defaultManager]
|
||||
URLForUbiquityContainerIdentifier:nil]
|
||||
URLByAppendingPathComponent:@"store"
|
||||
isDirectory:YES], NSPersistentStoreUbiquitousContentURLKey,
|
||||
nil]
|
||||
error:&error])
|
||||
{
|
||||
/*
|
||||
Replace this implementation with code to handle the error appropriately.
|
||||
|
||||
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
|
||||
Typical reasons for an error here include:
|
||||
* The persistent store is not accessible;
|
||||
* The schema for the persistent store is incompatible with current managed object model.
|
||||
Check the error message to determine what the actual problem was.
|
||||
|
||||
|
||||
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
|
||||
|
||||
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
|
||||
* Simply deleting the existing store:
|
||||
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
|
||||
|
||||
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
|
||||
|
||||
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
|
||||
|
||||
*/
|
||||
err(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||
#if DEBUG
|
||||
wrn(@"Deleted datastore: %@", storeURL);
|
||||
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointStoreIncompatible];
|
||||
#endif
|
||||
|
||||
@throw [NSException exceptionWithName:error.domain reason:error.localizedDescription
|
||||
userInfo:[NSDictionary dictionaryWithObject:error forKey:@"cause"]];
|
||||
}
|
||||
|
||||
if (![[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
|
||||
forKey:NSFileProtectionKey]
|
||||
ofItemAtPath:storeURL.path error:&error])
|
||||
err(@"Unresolved error %@, %@", error, [error userInfo]);
|
||||
[__persistentStoreCoordinator unlock];
|
||||
|
||||
return __persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
#pragma mark - Application's Documents directory
|
||||
|
||||
/**
|
||||
Returns the URL to the application's Documents directory.
|
||||
*/
|
||||
- (NSURL *)applicationDocumentsDirectory
|
||||
{
|
||||
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
}
|
||||
|
||||
@end
|
||||
19
MasterPassword/MPAppDelegate_Key.h
Normal file
19
MasterPassword/MPAppDelegate_Key.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// MPAppDelegate_Key.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
@interface MPAppDelegate_Shared (Key)
|
||||
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
|
||||
- (void)signOut;
|
||||
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user;
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
|
||||
|
||||
@end
|
||||
174
MasterPassword/MPAppDelegate_Key.m
Normal file
174
MasterPassword/MPAppDelegate_Key.m
Normal file
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// MPAppDelegate.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
@implementation MPAppDelegate_Shared (Key)
|
||||
|
||||
static NSDictionary *keyQuery(MPUserEntity *user) {
|
||||
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"Saved Master Password", (__bridge id)kSecAttrService,
|
||||
user.name, (__bridge id)kSecAttrAccount,
|
||||
nil]
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
if (key)
|
||||
inf(@"Found key in keychain for: %@", user.userID);
|
||||
|
||||
else {
|
||||
user.saveKey = NO;
|
||||
inf(@"No key found in keychain for: %@", user.userID);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
if (user.saveKey) {
|
||||
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
|
||||
|
||||
if (![existingKey isEqualToData:self.key]) {
|
||||
inf(@"Saving key in keychain for: %@", user.userID);
|
||||
|
||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.key, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
|
||||
|
||||
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)];
|
||||
if (result == noErr || result == errSecItemNotFound) {
|
||||
user.saveKey = NO;
|
||||
|
||||
if (result == noErr) {
|
||||
inf(@"Removed key from keychain for: %@", user.userID);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointForgetSavedKey];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)signOut {
|
||||
|
||||
if (self.key)
|
||||
self.key = nil;
|
||||
|
||||
if (self.activeUser) {
|
||||
self.activeUser = nil;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
|
||||
|
||||
NSData *tryKey = nil;
|
||||
|
||||
// Method 1: When the user has no keyID set, set a new key from the given master password.
|
||||
if (!user.keyID) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name))) {
|
||||
user.keyID = keyIDForKey(tryKey);
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
|
||||
if (!user.saveKey)
|
||||
// Key should not be stored in keychain. Delete it.
|
||||
[self forgetSavedKeyFor:user];
|
||||
|
||||
else
|
||||
if (!tryKey) {
|
||||
// Key should be saved in keychain. Load it.
|
||||
if ((tryKey = [self loadSavedKeyFor:user]))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
|
||||
inf(@"Saved password doesn't match keyID for: %@", user.userID);
|
||||
|
||||
tryKey = nil;
|
||||
[self forgetSavedKeyFor:user];
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Check the given master password string.
|
||||
if (!tryKey) {
|
||||
if ([password length])
|
||||
if ((tryKey = keyForPassword(password, user.name)))
|
||||
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
|
||||
inf(@"Key derived from password doesn't match keyID for: %@", user.userID);
|
||||
|
||||
tryKey = nil;
|
||||
}
|
||||
}
|
||||
|
||||
// No more methods left, fail if key still not known.
|
||||
if (!tryKey) {
|
||||
if (password) {
|
||||
inf(@"Login failed for: %@", user.userID);
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointSignInFailed];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignInFailed attributes:nil];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
inf(@"Logged in: %@", user.userID);
|
||||
|
||||
if (![self.key isEqualToData:tryKey]) {
|
||||
self.key = tryKey;
|
||||
[self storeSavedKeyFor:user];
|
||||
}
|
||||
|
||||
@try {
|
||||
if ([[MPiOSConfig get].sendInfo boolValue]) {
|
||||
[TestFlight addCustomEnvironmentInformation:user.userID forKey:@"username"];
|
||||
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
err(@"While setting username: %@", exception);
|
||||
}
|
||||
|
||||
|
||||
user.lastUsed = [NSDate date];
|
||||
self.activeUser = user;
|
||||
[[MPAppDelegate_Shared get] saveContext];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointSignedIn];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSignedIn
|
||||
attributes:nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
25
MasterPassword/MPAppDelegate_Shared.h
Normal file
25
MasterPassword/MPAppDelegate_Shared.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// MPAppDelegate_Shared.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPEntities.h"
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
@interface MPAppDelegate_Shared : PearlAppDelegate
|
||||
#else
|
||||
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
|
||||
#endif
|
||||
|
||||
@property (strong, nonatomic) MPUserEntity *activeUser;
|
||||
@property (strong, nonatomic) NSData *key;
|
||||
|
||||
+ (MPAppDelegate_Shared *)get;
|
||||
|
||||
- (NSURL *)applicationFilesDirectory;
|
||||
|
||||
@end
|
||||
44
MasterPassword/MPAppDelegate_Shared.m
Normal file
44
MasterPassword/MPAppDelegate_Shared.m
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// MPAppDelegate.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
@implementation MPAppDelegate_Shared
|
||||
|
||||
@synthesize key;
|
||||
@synthesize activeUser;
|
||||
|
||||
+ (MPAppDelegate_Shared *)get {
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
return (MPAppDelegate_Shared *)[UIApplication sharedApplication].delegate;
|
||||
#elif defined (__MAC_OS_X_VERSION_MIN_REQUIRED)
|
||||
return (MPAppDelegate_Shared *)[NSApplication sharedApplication].delegate;
|
||||
#else
|
||||
#error Unsupported OS.
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSURL *)applicationFilesDirectory {
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
#else
|
||||
NSURL *appSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
NSURL *applicationFilesDirectory = [appSupportURL URLByAppendingPathComponent:@"com.lyndir.lhunath.MasterPassword"];
|
||||
|
||||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] createDirectoryAtURL:applicationFilesDirectory withIntermediateDirectories:YES attributes:nil error:&error];
|
||||
if (error)
|
||||
err(@"Couldn't create application directory: %@, error occurred: %@", applicationFilesDirectory, error);
|
||||
|
||||
return applicationFilesDirectory;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
35
MasterPassword/MPAppDelegate_Store.h
Normal file
35
MasterPassword/MPAppDelegate_Store.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// MPAppDelegate_Key.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
#import "UbiquityStoreManager.h"
|
||||
|
||||
typedef enum {
|
||||
MPImportResultSuccess,
|
||||
MPImportResultCancelled,
|
||||
MPImportResultInvalidPassword,
|
||||
MPImportResultMalformedInput,
|
||||
MPImportResultInternalError,
|
||||
} MPImportResult;
|
||||
|
||||
@interface MPAppDelegate_Shared (Store)<UbiquityStoreManagerDelegate>
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext;
|
||||
+ (NSManagedObjectModel *)managedObjectModel;
|
||||
- (NSManagedObjectContext *)managedObjectContext;
|
||||
- (NSManagedObjectModel *)managedObjectModel;
|
||||
|
||||
- (UbiquityStoreManager *)storeManager;
|
||||
- (void)saveContext;
|
||||
|
||||
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
|
||||
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation;
|
||||
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords;
|
||||
|
||||
@end
|
||||
419
MasterPassword/MPAppDelegate_Store.m
Normal file
419
MasterPassword/MPAppDelegate_Store.m
Normal file
@@ -0,0 +1,419 @@
|
||||
//
|
||||
// MPAppDelegate.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
@implementation MPAppDelegate_Shared (Store)
|
||||
|
||||
#pragma mark - Core Data setup
|
||||
|
||||
+ (NSManagedObjectContext *)managedObjectContext {
|
||||
|
||||
return [[self get] managedObjectContext];
|
||||
}
|
||||
|
||||
+ (NSManagedObjectModel *)managedObjectModel {
|
||||
|
||||
return [[self get] managedObjectModel];
|
||||
}
|
||||
|
||||
- (NSManagedObjectModel *)managedObjectModel {
|
||||
|
||||
static NSManagedObjectModel *managedObjectModel = nil;
|
||||
if (managedObjectModel)
|
||||
return managedObjectModel;
|
||||
|
||||
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MasterPassword" withExtension:@"momd"];
|
||||
return managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
|
||||
}
|
||||
|
||||
- (NSManagedObjectContext *)managedObjectContext {
|
||||
|
||||
static NSManagedObjectContext *managedObjectContext = nil;
|
||||
if (managedObjectContext)
|
||||
return managedObjectContext;
|
||||
|
||||
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||
[managedObjectContext performBlockAndWait:^{
|
||||
managedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
|
||||
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
|
||||
}];
|
||||
|
||||
return managedObjectContext;
|
||||
}
|
||||
|
||||
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
|
||||
|
||||
// Start loading the store.
|
||||
[self storeManager];
|
||||
|
||||
// Wait until the storeManager is ready.
|
||||
while (![self storeManager].isReady)
|
||||
[NSThread sleepForTimeInterval:0.1];
|
||||
|
||||
return [self storeManager].persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
- (UbiquityStoreManager *)storeManager {
|
||||
|
||||
static UbiquityStoreManager *storeManager = nil;
|
||||
if (storeManager)
|
||||
return storeManager;
|
||||
|
||||
storeManager = [[UbiquityStoreManager alloc] initWithManagedObjectModel:[self managedObjectModel]
|
||||
localStoreURL:[[self applicationFilesDirectory] URLByAppendingPathComponent:@"MasterPassword.sqlite"]
|
||||
containerIdentifier:@"HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.shared"
|
||||
#if TARGET_OS_IPHONE
|
||||
additionalStoreOptions:[NSDictionary dictionaryWithObject:NSFileProtectionComplete
|
||||
forKey:NSPersistentStoreFileProtectionKey]
|
||||
#else
|
||||
additionalStoreOptions:nil
|
||||
#endif
|
||||
];
|
||||
storeManager.delegate = self;
|
||||
#ifdef DEBUG
|
||||
storeManager.hardResetEnabled = YES;
|
||||
#endif
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification
|
||||
object:[UIApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[storeManager checkiCloudStatus];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification
|
||||
object:[NSApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[storeManager checkiCloudStatus];
|
||||
}];
|
||||
#endif
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification
|
||||
object:[UIApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self saveContext];
|
||||
}];
|
||||
#else
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification
|
||||
object:[NSApplication sharedApplication] queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self saveContext];
|
||||
}];
|
||||
#endif
|
||||
|
||||
return storeManager;
|
||||
}
|
||||
|
||||
- (void)saveContext {
|
||||
|
||||
[self.managedObjectContext performBlock:^{
|
||||
NSError *error = nil;
|
||||
if ([self.managedObjectContext hasChanges])
|
||||
if (![self.managedObjectContext save:&error])
|
||||
err(@"While saving context: %@", error);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - UbiquityStoreManagerDelegate
|
||||
|
||||
- (NSManagedObjectContext *)managedObjectContextForUbiquityStoreManager:(UbiquityStoreManager *)usm {
|
||||
|
||||
return self.managedObjectContext;
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager log:(NSString *)message {
|
||||
|
||||
dbg(@"[StoreManager] %@", message);
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
|
||||
|
||||
// manager.iCloudEnabled is more reliable (eg. iOS' MPAppDelegate tampers with didSwitch a bit)
|
||||
iCloudEnabled = manager.iCloudEnabled;
|
||||
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:iCloudEnabled? MPCheckpointCloudEnabled: MPCheckpointCloudDisabled];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloud
|
||||
attributes:[NSDictionary dictionaryWithObject:iCloudEnabled? @"YES": @"NO" forKey:@"enabled"]];
|
||||
|
||||
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
|
||||
}
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause
|
||||
context:(id)context {
|
||||
|
||||
err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error);
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:PearlString(@"MPCheckpointMPErrorUbiquity_%d", cause)];
|
||||
#endif
|
||||
|
||||
switch (cause) {
|
||||
case UbiquityStoreManagerErrorCauseDeleteStore:
|
||||
case UbiquityStoreManagerErrorCauseDeleteLogs:
|
||||
case UbiquityStoreManagerErrorCauseCreateStorePath:
|
||||
case UbiquityStoreManagerErrorCauseClearStore:
|
||||
break;
|
||||
case UbiquityStoreManagerErrorCauseOpenLocalStore: {
|
||||
wrn(@"Local store could not be opened, resetting it.");
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointLocalStoreIncompatible];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointLocalStoreIncompatible
|
||||
attributes:nil];
|
||||
manager.hardResetEnabled = YES;
|
||||
[manager hardResetLocalStorage];
|
||||
|
||||
[NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."];
|
||||
return;
|
||||
}
|
||||
case UbiquityStoreManagerErrorCauseOpenCloudStore: {
|
||||
wrn(@"iCloud store could not be opened, resetting it.");
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointCloudStoreIncompatible];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCloudStoreIncompatible
|
||||
attributes:nil];
|
||||
manager.hardResetEnabled = YES;
|
||||
[manager hardResetCloudStorage];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Import / Export
|
||||
|
||||
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
|
||||
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation {
|
||||
|
||||
inf(@"Importing sites.");
|
||||
|
||||
static NSRegularExpression *headerPattern, *sitePattern;
|
||||
__autoreleasing NSError *error;
|
||||
if (!headerPattern) {
|
||||
headerPattern = [[NSRegularExpression alloc]
|
||||
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
|
||||
options:0 error:&error];
|
||||
if (error)
|
||||
err(@"Error loading the header pattern: %@", error);
|
||||
}
|
||||
if (!sitePattern) {
|
||||
sitePattern = [[NSRegularExpression alloc]
|
||||
initWithPattern:@"^([^[:space:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([[:digit:]]+)[[:space:]]+([^\t]+)\t(.*)"
|
||||
options:0 error:&error];
|
||||
if (error)
|
||||
err(@"Error loading the site pattern: %@", error);
|
||||
}
|
||||
if (!headerPattern || !sitePattern)
|
||||
return MPImportResultInternalError;
|
||||
|
||||
NSData *key = nil;
|
||||
NSString *keyIDHex = nil, *userName = nil;
|
||||
MPUserEntity *user = nil;
|
||||
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
|
||||
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||
NSMutableSet *elementsToDelete = [NSMutableSet set];
|
||||
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
for (NSString *importedSiteLine in importedSiteLines) {
|
||||
if ([importedSiteLine hasPrefix:@"#"]) {
|
||||
// Comment or header
|
||||
if (!headerStarted) {
|
||||
if ([importedSiteLine isEqualToString:@"##"])
|
||||
headerStarted = YES;
|
||||
continue;
|
||||
}
|
||||
if (headerEnded)
|
||||
continue;
|
||||
if ([importedSiteLine isEqualToString:@"##"]) {
|
||||
headerEnded = YES;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Header
|
||||
if ([headerPattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
|
||||
err(@"Invalid header format in line: %@", importedSiteLine);
|
||||
return MPImportResultMalformedInput;
|
||||
}
|
||||
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0
|
||||
range:NSMakeRange(0, [importedSiteLine length])] lastObject];
|
||||
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
||||
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
||||
if ([headerName isEqualToString:@"User Name"]) {
|
||||
userName = headerValue;
|
||||
key = keyForPassword(password, userName);
|
||||
|
||||
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
|
||||
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
|
||||
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
|
||||
}
|
||||
if ([headerName isEqualToString:@"Key ID"]) {
|
||||
if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]])
|
||||
return MPImportResultInvalidPassword;
|
||||
}
|
||||
if ([headerName isEqualToString:@"Passwords"]) {
|
||||
if ([headerValue isEqualToString:@"VISIBLE"])
|
||||
clearText = YES;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
if (!headerEnded)
|
||||
continue;
|
||||
if (!keyIDHex || ![userName length])
|
||||
return MPImportResultMalformedInput;
|
||||
if (![importedSiteLine length])
|
||||
continue;
|
||||
|
||||
// Site
|
||||
if ([sitePattern numberOfMatchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] != 1) {
|
||||
err(@"Invalid site format in line: %@", importedSiteLine);
|
||||
return MPImportResultMalformedInput;
|
||||
}
|
||||
NSTextCheckingResult *siteElements = [[sitePattern matchesInString:importedSiteLine options: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 *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
|
||||
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
|
||||
|
||||
// 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);
|
||||
if (!existingSites) {
|
||||
err(@"Lookup of existing sites failed for site: %@, user: %@", name, user.userID);
|
||||
return MPImportResultInternalError;
|
||||
}
|
||||
|
||||
[elementsToDelete addObjectsFromArray:existingSites];
|
||||
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
|
||||
}
|
||||
}
|
||||
|
||||
inf(@"Importing %u sites, deleting %u sites, for user: %@",
|
||||
[importedSiteElements count], [elementsToDelete count], [MPUserEntity idFor:userName]);
|
||||
|
||||
// Ask for confirmation to import these sites.
|
||||
if (!confirmation([importedSiteElements count], [elementsToDelete count])) {
|
||||
inf(@"Import cancelled.");
|
||||
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];
|
||||
|
||||
// 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];
|
||||
NSString *name = [siteElements objectAtIndex:3];
|
||||
NSString *exportContent = [siteElements objectAtIndex:4];
|
||||
|
||||
// Create new site.
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
element.name = name;
|
||||
element.user = user;
|
||||
element.type = type;
|
||||
element.uses = uses;
|
||||
element.lastUsed = lastUsed;
|
||||
if ([exportContent length])
|
||||
if (clearText)
|
||||
[element importClearTextContent:exportContent usingKey:key];
|
||||
else
|
||||
[element importProtectedContent:exportContent];
|
||||
}
|
||||
[self saveContext];
|
||||
|
||||
inf(@"Import completed successfully.");
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointSitesImported];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesImported
|
||||
attributes:nil];
|
||||
|
||||
return MPImportResultSuccess;
|
||||
}
|
||||
|
||||
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
|
||||
|
||||
inf(@"Exporting sites, %@, for: %@", showPasswords? @"showing passwords": @"omitting passwords", self.activeUser.userID);
|
||||
|
||||
// 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];
|
||||
[export appendFormat:@"# User Name: %@\n", self.activeUser.name];
|
||||
[export appendFormat:@"# Key ID: %@\n", [self.activeUser.keyID encodeHex]];
|
||||
[export appendFormat:@"# Date: %@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:[NSDate date]]];
|
||||
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"];
|
||||
|
||||
// Sites.
|
||||
for (MPElementEntity *element in self.activeUser.elements) {
|
||||
NSDate *lastUsed = element.lastUsed;
|
||||
NSUInteger uses = element.uses;
|
||||
MPElementType type = element.type;
|
||||
NSString *name = element.name;
|
||||
NSString *content = nil;
|
||||
|
||||
// Determine the content to export.
|
||||
if (!(type & MPElementFeatureDevicePrivate)) {
|
||||
if (showPasswords)
|
||||
content = element.content;
|
||||
else
|
||||
if (type & MPElementFeatureExportContent)
|
||||
content = element.exportContent;
|
||||
}
|
||||
|
||||
[export appendFormat:@"%@ %8d %8d %20s\t%@\n",
|
||||
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content
|
||||
? content: @""];
|
||||
}
|
||||
|
||||
#ifdef TESTFLIGHT_SDK_VERSION
|
||||
[TestFlight passCheckpoint:MPCheckpointSitesExported];
|
||||
#endif
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointSitesExported attributes:nil];
|
||||
|
||||
return export;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -6,13 +6,14 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Pearl.h"
|
||||
|
||||
@interface MPConfig : PearlConfig
|
||||
|
||||
@property (nonatomic, retain) NSNumber *dataStoreError;
|
||||
@property (nonatomic, retain) NSNumber *storeKeyPhrase;
|
||||
@property (nonatomic, retain) NSNumber *rememberKeyPhrase;
|
||||
@property (nonatomic, retain) NSNumber *helpHidden;
|
||||
@property (nonatomic, retain) NSNumber *showQuickStart;
|
||||
@property (nonatomic, retain) NSNumber *rememberLogin;
|
||||
|
||||
@property (nonatomic, retain) NSNumber *iCloud;
|
||||
@property (nonatomic, retain) NSNumber *iCloudDecided;
|
||||
|
||||
+ (MPConfig *)get;
|
||||
|
||||
|
||||
@@ -6,24 +6,26 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPConfig.h"
|
||||
#import "MPAppDelegate.h"
|
||||
|
||||
@implementation MPConfig
|
||||
@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, helpHidden, showQuickStart;
|
||||
@dynamic rememberLogin, iCloud, iCloudDecided;
|
||||
|
||||
- (id)init {
|
||||
|
||||
if(!(self = [super init]))
|
||||
return self;
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(rememberKeyPhrase)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
|
||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)),
|
||||
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberLogin)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)),
|
||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)),
|
||||
nil]];
|
||||
|
||||
self.delegate = [MPAppDelegate get];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
//
|
||||
// MPElementEntity.h
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
@class MPUserEntity;
|
||||
|
||||
@interface MPElementEntity : NSManagedObject
|
||||
|
||||
@property (nonatomic, retain) NSString *name;
|
||||
@property (nonatomic, retain) NSString *mpHashHex;
|
||||
@property (nonatomic, assign) int16_t type;
|
||||
@property (nonatomic, assign) int16_t uses;
|
||||
@property (nonatomic, assign) NSTimeInterval lastUsed;
|
||||
@property (nonatomic, retain, readonly) id content;
|
||||
|
||||
- (void)use;
|
||||
@property (nonatomic, retain) id content;
|
||||
@property (nonatomic, retain) NSDate * lastUsed;
|
||||
@property (nonatomic, retain) NSString * name;
|
||||
@property (nonatomic, retain) NSNumber * type_;
|
||||
@property (nonatomic, retain) NSNumber * uses_;
|
||||
@property (nonatomic, retain) MPUserEntity *user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,42 +1,22 @@
|
||||
//
|
||||
// MPElementEntity.m
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPUserEntity.h"
|
||||
|
||||
|
||||
@implementation MPElementEntity
|
||||
|
||||
@dynamic name;
|
||||
@dynamic mpHashHex;
|
||||
@dynamic type;
|
||||
@dynamic uses;
|
||||
@dynamic content;
|
||||
@dynamic lastUsed;
|
||||
|
||||
- (void)use {
|
||||
|
||||
++self.uses;
|
||||
self.lastUsed = [[NSDate date] timeIntervalSinceReferenceDate];
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
|
||||
return [[self content] description];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
|
||||
return [NSString stringWithFormat:@"{%@: name=%@, mpHashHex=%@, type=%d, uses=%d, lastUsed=%@}",
|
||||
NSStringFromClass([self class]), self.name, self.mpHashHex, self.type, self.uses, self.lastUsed];
|
||||
}
|
||||
@dynamic name;
|
||||
@dynamic type_;
|
||||
@dynamic uses_;
|
||||
@dynamic user;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//
|
||||
// MPElementGeneratedEntity.h
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 16/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
|
||||
@interface MPElementGeneratedEntity : MPElementEntity
|
||||
|
||||
@property (nonatomic, assign) uint16_t counter;
|
||||
@property (nonatomic, retain) NSNumber * counter_;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,31 +1,16 @@
|
||||
//
|
||||
// MPElementGeneratedEntity.m
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 16/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPAppDelegate.h"
|
||||
|
||||
|
||||
@implementation MPElementGeneratedEntity
|
||||
|
||||
@dynamic counter;
|
||||
|
||||
- (id)content {
|
||||
|
||||
assert(self.type & MPElementTypeClassCalculated);
|
||||
|
||||
if (![self.name length])
|
||||
return nil;
|
||||
|
||||
if (self.type & MPElementTypeClassCalculated)
|
||||
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].keyPhrase, self.counter);
|
||||
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:[NSString stringWithFormat:@"Unsupported type: %d", self.type] userInfo:nil];
|
||||
}
|
||||
@dynamic counter_;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//
|
||||
// MPElementStoredEntity.h
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
|
||||
@interface MPElementStoredEntity : MPElementEntity
|
||||
|
||||
@property (nonatomic, retain, readwrite) id content;
|
||||
@property (nonatomic, retain) id contentObject;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,63 +1,16 @@
|
||||
//
|
||||
// MPElementStoredEntity.m
|
||||
// MasterPassword
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 02/01/12.
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "MPAppDelegate.h"
|
||||
|
||||
@interface MPElementStoredEntity ()
|
||||
|
||||
@property (nonatomic, retain, readwrite) id contentObject;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPElementStoredEntity
|
||||
|
||||
@dynamic contentObject;
|
||||
|
||||
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
||||
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"DevicePrivate", (__bridge id)kSecAttrService,
|
||||
name, (__bridge id)kSecAttrAccount,
|
||||
nil]
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
|
||||
NSData *encryptedContent;
|
||||
if (self.type == MPElementTypeStoredDevicePrivate)
|
||||
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
|
||||
else
|
||||
encryptedContent = self.contentObject;
|
||||
|
||||
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyPhraseWithLength:PearlCryptKeySize]
|
||||
usePadding:YES];
|
||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content {
|
||||
|
||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyPhraseWithLength:PearlCryptKeySize]
|
||||
usePadding:YES];
|
||||
|
||||
if (self.type == MPElementTypeStoredDevicePrivate) {
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
encryptedContent, (__bridge id)kSecValueData,
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
nil]];
|
||||
self.contentObject = nil;
|
||||
} else
|
||||
self.contentObject = encryptedContent;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
44
MasterPassword/MPEntities.h
Normal file
44
MasterPassword/MPEntities.h
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// MPElementEntities.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 31/05/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPUserEntity.h"
|
||||
|
||||
#define MPAvatarCount 19
|
||||
|
||||
@interface MPElementEntity (MP)
|
||||
|
||||
@property (assign) MPElementType type;
|
||||
@property (assign) NSUInteger uses;
|
||||
|
||||
- (NSUInteger)use;
|
||||
- (NSString *)exportContent;
|
||||
- (void)importProtectedContent:(NSString *)protectedContent;
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPElementGeneratedEntity (MP)
|
||||
|
||||
@property (assign) NSUInteger counter;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPUserEntity (MP)
|
||||
|
||||
@property (assign) NSUInteger avatar;
|
||||
@property (assign) BOOL saveKey;
|
||||
@property (assign) MPElementType defaultType;
|
||||
@property (readonly) NSString *userID;
|
||||
|
||||
+ (NSString *)idFor:(NSString *)userName;
|
||||
|
||||
@end
|
||||
218
MasterPassword/MPEntities.m
Normal file
218
MasterPassword/MPEntities.m
Normal file
@@ -0,0 +1,218 @@
|
||||
//
|
||||
// MPElementEntities.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 31/05/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPEntities.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPUserEntity.h"
|
||||
|
||||
@implementation MPElementEntity (MP)
|
||||
|
||||
- (MPElementType)type {
|
||||
|
||||
return (MPElementType)[self.type_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (void)setType:(MPElementType)aType {
|
||||
|
||||
self.type_ = PearlUnsignedInteger(aType);
|
||||
}
|
||||
|
||||
- (NSUInteger)uses {
|
||||
|
||||
return [self.uses_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (void)setUses:(NSUInteger)anUses {
|
||||
|
||||
self.uses_ = PearlUnsignedInteger(anUses);
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)use {
|
||||
|
||||
self.lastUsed = [NSDate date];
|
||||
return ++self.uses;
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
|
||||
}
|
||||
|
||||
- (NSString *)exportContent {
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)importProtectedContent:(NSString *)content {
|
||||
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key {
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
|
||||
return PearlString(@"%@:%@", [self class], [self name]);
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
|
||||
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@}",
|
||||
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPElementGeneratedEntity (MP)
|
||||
|
||||
- (NSUInteger)counter {
|
||||
|
||||
return [self.counter_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (void)setCounter:(NSUInteger)aCounter {
|
||||
|
||||
self.counter_ = PearlUnsignedInteger(aCounter);
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
if (!(self.type & MPElementTypeClassGenerated)) {
|
||||
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![self.name length])
|
||||
return nil;
|
||||
|
||||
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPElementStoredEntity (MP)
|
||||
|
||||
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
||||
|
||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"DevicePrivate", (__bridge id)kSecAttrService,
|
||||
name, (__bridge id)kSecAttrAccount,
|
||||
nil]
|
||||
matches:nil];
|
||||
}
|
||||
|
||||
- (id)content {
|
||||
|
||||
return [self contentUsingKey:[MPAppDelegate get].key];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content {
|
||||
|
||||
[self setContent:content usingKey:[MPAppDelegate get].key];
|
||||
}
|
||||
|
||||
- (id)contentUsingKey:(NSData *)key {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
|
||||
|
||||
NSData *encryptedContent;
|
||||
if (self.type & MPElementFeatureDevicePrivate)
|
||||
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
|
||||
else
|
||||
encryptedContent = self.contentObject;
|
||||
|
||||
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
|
||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (void)setContent:(id)content usingKey:(NSData *)key {
|
||||
|
||||
assert(self.type & MPElementTypeClassStored);
|
||||
assert([keyIDForKey(key) isEqualToData:self.user.keyID]);
|
||||
|
||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES];
|
||||
|
||||
if (self.type & MPElementFeatureDevicePrivate) {
|
||||
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
encryptedContent, (__bridge id)kSecValueData,
|
||||
#if TARGET_OS_IPHONE
|
||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||
#endif
|
||||
nil]];
|
||||
self.contentObject = nil;
|
||||
} else
|
||||
self.contentObject = encryptedContent;
|
||||
}
|
||||
|
||||
- (NSString *)exportContent {
|
||||
|
||||
return [self.contentObject encodeBase64];
|
||||
}
|
||||
|
||||
- (void)importProtectedContent:(NSString *)protectedContent {
|
||||
|
||||
self.contentObject = [protectedContent decodeBase64];
|
||||
}
|
||||
|
||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key {
|
||||
|
||||
[self setContent:clearContent usingKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPUserEntity (MP)
|
||||
|
||||
- (NSUInteger)avatar {
|
||||
|
||||
return [self.avatar_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (void)setAvatar:(NSUInteger)anAvatar {
|
||||
|
||||
self.avatar_ = PearlUnsignedInteger(anAvatar);
|
||||
}
|
||||
|
||||
- (BOOL)saveKey {
|
||||
|
||||
return [self.saveKey_ boolValue];
|
||||
}
|
||||
|
||||
- (void)setSaveKey:(BOOL)aSaveKey {
|
||||
|
||||
self.saveKey_ = [NSNumber numberWithBool:aSaveKey];
|
||||
}
|
||||
|
||||
- (MPElementType)defaultType {
|
||||
|
||||
return (MPElementType)[self.defaultType_ unsignedIntegerValue];
|
||||
}
|
||||
|
||||
- (NSString *)userID {
|
||||
|
||||
return [MPUserEntity idFor:self.name];
|
||||
}
|
||||
|
||||
|
||||
- (void)setDefaultType:(MPElementType)aDefaultType {
|
||||
|
||||
self.defaultType_ = PearlUnsignedInteger(aDefaultType);
|
||||
}
|
||||
|
||||
+ (NSString *)idFor:(NSString *)userName {
|
||||
|
||||
return [[userName hashWith:PearlHashSHA1] encodeHex];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,53 +0,0 @@
|
||||
//
|
||||
// MPGuideViewController.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 30/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPGuideViewController.h"
|
||||
#import "MPAppDelegate.h"
|
||||
|
||||
@implementation MPGuideViewController
|
||||
@synthesize scrollView;
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
||||
|
||||
return (interfaceOrientation == UIInterfaceOrientationPortrait);
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
[super viewDidLoad];
|
||||
|
||||
[PearlUIUtils autoSizeContent:self.scrollView];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[MPConfig get].showQuickStart = [NSNumber numberWithBool:NO];
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
|
||||
[super viewDidDisappear:animated];
|
||||
|
||||
[[MPAppDelegate get] loadKeyPhrase:animated];
|
||||
}
|
||||
|
||||
|
||||
- (void)viewDidUnload {
|
||||
|
||||
[self setScrollView:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
- (IBAction)close {
|
||||
|
||||
[self.presentingViewController dismissModalViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// MPMainViewController.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTypeViewController.h"
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPSearchDelegate.h"
|
||||
#import "IASKAppSettingsViewController.h"
|
||||
|
||||
@interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, IASKSettingsDelegate>
|
||||
|
||||
@property (strong, nonatomic) MPElementEntity *activeElement;
|
||||
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *contentField;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *typeButton;
|
||||
@property (weak, nonatomic) IBOutlet UIWebView *helpView;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *siteName;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *passwordCounter;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *passwordEdit;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
|
||||
@property (weak, nonatomic) IBOutlet UIView *alertContainer;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *alertTitle;
|
||||
@property (weak, nonatomic) IBOutlet UITextView *alertBody;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
|
||||
- (IBAction)copyContent;
|
||||
- (IBAction)incrementPasswordCounter;
|
||||
- (IBAction)editPassword;
|
||||
- (IBAction)closeAlert;
|
||||
- (IBAction)action:(UIBarButtonItem *)sender;
|
||||
|
||||
- (BOOL)isHelpVisible;
|
||||
- (void)toggleHelpAnimated:(BOOL)animated;
|
||||
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated;
|
||||
- (void)setHelpChapter:(NSString *)chapter;
|
||||
|
||||
@end
|
||||
@@ -1,482 +0,0 @@
|
||||
//
|
||||
// MPMainViewController.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 24/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPMainViewController.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "IASKAppSettingsViewController.h"
|
||||
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
|
||||
@interface MPMainViewController (Private)
|
||||
|
||||
- (void)updateAnimated:(BOOL)animated;
|
||||
- (void)updateWasAnimated:(BOOL)animated;
|
||||
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon;
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
|
||||
- (void)updateElement:(void (^)(void))updateElement;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPMainViewController
|
||||
@synthesize activeElement = _activeElement;
|
||||
@synthesize searchResultsController = _searchResultsController;
|
||||
@synthesize typeButton = _typeButton;
|
||||
@synthesize helpView = _helpView;
|
||||
@synthesize siteName = _siteName;
|
||||
@synthesize passwordCounter = _passwordCounter;
|
||||
@synthesize passwordIncrementer = _passwordIncrementer;
|
||||
@synthesize passwordEdit = _passwordEdit;
|
||||
@synthesize contentContainer = _contentContainer;
|
||||
@synthesize helpContainer = _helpContainer;
|
||||
@synthesize contentTipContainer = _copiedContainer;
|
||||
@synthesize alertContainer = _alertContainer;
|
||||
@synthesize alertTitle = _alertTitle;
|
||||
@synthesize alertBody = _alertBody;
|
||||
@synthesize contentTipBody = _contentTipBody;
|
||||
@synthesize contentTipEditIcon = _contentTipEditIcon;
|
||||
@synthesize searchTipContainer = _searchTip;
|
||||
@synthesize contentField = _contentField;
|
||||
|
||||
#pragma mark - View lifecycle
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
||||
|
||||
return [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad || interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
|
||||
}
|
||||
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
|
||||
if ([[segue identifier] isEqualToString:@"MP_Main_ChooseType"])
|
||||
[[segue destinationViewController] setDelegate:self];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
self.searchTipContainer.hidden = NO;
|
||||
|
||||
if (!self.activeElement.name)
|
||||
[UIView animateWithDuration:animated? 0.2f: 0 animations:^{
|
||||
self.searchTipContainer.alpha = 1;
|
||||
}];
|
||||
|
||||
[self setHelpHidden:[[MPConfig get].helpHidden boolValue] animated:animated];
|
||||
[self updateAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
self.searchTipContainer.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
|
||||
|
||||
// Put the search tip on the window so it's above the nav bar.
|
||||
if (![self.searchTipContainer.superview isEqual:self.navigationController.navigationBar.superview]) {
|
||||
CGRect frameInWindow = [self.searchTipContainer.window convertRect:self.searchTipContainer.frame
|
||||
fromView:self.searchTipContainer.superview];
|
||||
[self.searchTipContainer removeFromSuperview];
|
||||
[self.navigationController.navigationBar.superview addSubview:self.searchTipContainer];
|
||||
self.searchTipContainer.frame = [self.searchTipContainer.window convertRect:frameInWindow
|
||||
toView:self.searchTipContainer.superview];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue]
|
||||
usingBlock:^(NSNotification *note) {
|
||||
if (![MPAppDelegate get].keyPhrase) {
|
||||
self.activeElement = nil;
|
||||
[self updateAnimated:NO];
|
||||
}
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue]
|
||||
usingBlock:^(NSNotification *note) {
|
||||
if (![MPAppDelegate get].keyPhrase) {
|
||||
self.activeElement = nil;
|
||||
[self updateAnimated:NO];
|
||||
}
|
||||
}];
|
||||
|
||||
self.alertBody.text = nil;
|
||||
self.contentTipEditIcon.alpha = 0;
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
- (void)viewDidUnload {
|
||||
|
||||
[self setContentField:nil];
|
||||
[self setTypeButton:nil];
|
||||
[self setSearchResultsController:nil];
|
||||
[self setHelpView:nil];
|
||||
[self setSiteName:nil];
|
||||
[self setPasswordCounter:nil];
|
||||
[self setPasswordIncrementer:nil];
|
||||
[self setPasswordEdit:nil];
|
||||
[self setContentContainer:nil];
|
||||
[self setHelpContainer:nil];
|
||||
[self setContentTipContainer:nil];
|
||||
[self setAlertContainer:nil];
|
||||
[self setAlertTitle:nil];
|
||||
[self setAlertBody:nil];
|
||||
[self setContentTipBody:nil];
|
||||
[self setContentTipEditIcon:nil];
|
||||
[self setSearchTipContainer:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
- (void)updateAnimated:(BOOL)animated {
|
||||
|
||||
[[MPAppDelegate get] saveContext];
|
||||
|
||||
if (animated)
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
[self updateWasAnimated:animated];
|
||||
}];
|
||||
else
|
||||
[self updateWasAnimated:animated];
|
||||
}
|
||||
|
||||
- (void)updateWasAnimated:(BOOL)animated {
|
||||
|
||||
[self setHelpChapter:self.activeElement? @"2": @"1"];
|
||||
self.siteName.text = self.activeElement.name;
|
||||
|
||||
self.passwordCounter.alpha = self.activeElement.type & MPElementTypeClassCalculated? 0.5f: 0;
|
||||
self.passwordIncrementer.alpha = self.activeElement.type & MPElementTypeClassCalculated? 0.5f: 0;
|
||||
self.passwordEdit.alpha = self.activeElement.type & MPElementTypeClassStored? 0.5f: 0;
|
||||
|
||||
[self.typeButton setTitle:NSStringFromMPElementType(self.activeElement.type)
|
||||
forState:UIControlStateNormal];
|
||||
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
|
||||
|
||||
self.contentField.enabled = NO;
|
||||
|
||||
if ([self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
self.passwordCounter.text = [NSString stringWithFormat:@"%u", ((MPElementGeneratedEntity *) self.activeElement).counter];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
NSString *description = self.activeElement.description;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.contentField.text = description;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)isHelpVisible {
|
||||
|
||||
return self.helpContainer.frame.origin.y < 400;
|
||||
}
|
||||
|
||||
- (void)toggleHelpAnimated:(BOOL)animated {
|
||||
|
||||
[self setHelpHidden:[self isHelpVisible] animated:animated];
|
||||
}
|
||||
|
||||
- (void)setHelpHidden:(BOOL)hidden animated:(BOOL)animated {
|
||||
|
||||
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{
|
||||
|
||||
if (hidden) {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 373);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 416);
|
||||
[MPConfig get].helpHidden = [NSNumber numberWithBool:YES];
|
||||
} else {
|
||||
self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 175);
|
||||
self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 216);
|
||||
[MPConfig get].helpHidden = [NSNumber numberWithBool:NO];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setHelpChapter:(NSString *)chapter {
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]];
|
||||
#endif
|
||||
|
||||
[self.helpView loadRequest:
|
||||
[NSURLRequest requestWithURL:
|
||||
[NSURL URLWithString:[NSString stringWithFormat:@"#%@", chapter] relativeToURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]];
|
||||
|
||||
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
|
||||
ClassNameFromMPElementType(self.activeElement.type)]];
|
||||
if (error.length)
|
||||
err(@"setClass: %@", error);
|
||||
}
|
||||
|
||||
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon {
|
||||
|
||||
self.contentTipBody.text = message;
|
||||
|
||||
icon.hidden = NO;
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.contentTipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5.0f * NSEC_PER_SEC);
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.contentTipContainer.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
icon.hidden = YES;
|
||||
}];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
|
||||
|
||||
self.alertTitle.text = title;
|
||||
NSRange scrollRange = NSMakeRange(self.alertBody.text.length, message.length);
|
||||
if ([self.alertBody.text length])
|
||||
self.alertBody.text = [NSString stringWithFormat:@"%@\n\n---\n\n%@", self.alertBody.text, message];
|
||||
else
|
||||
self.alertBody.text = message;
|
||||
[self.alertBody scrollRangeToVisible:scrollRange];
|
||||
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.alertContainer.alpha = 1;
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Protocols
|
||||
|
||||
- (IBAction)copyContent {
|
||||
|
||||
if (!self.activeElement)
|
||||
return;
|
||||
|
||||
[[UIPasteboard generalPasteboard] setValue:self.activeElement.content
|
||||
forPasteboardType:(id)kUTTypeUTF8PlainText];
|
||||
|
||||
[self showContentTip:@"Copied!" withIcon:nil];
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCopyToPasteboard];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (IBAction)incrementPasswordCounter {
|
||||
|
||||
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
|
||||
// Not of a type that supports a password counter;
|
||||
return;
|
||||
|
||||
[self updateElement:^{
|
||||
++((MPElementGeneratedEntity *) self.activeElement).counter;
|
||||
}];
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)updateElement:(void (^)(void))updateElement {
|
||||
|
||||
// Update password counter.
|
||||
NSString *oldPassword = self.activeElement.description;
|
||||
updateElement();
|
||||
NSString *newPassword = self.activeElement.description;
|
||||
[self updateAnimated:YES];
|
||||
|
||||
// Show new and old password.
|
||||
if ([oldPassword length] && ![oldPassword isEqualToString:newPassword])
|
||||
[self showAlertWithTitle:@"Password Changed!" message:l(@"The password for %@ has changed.\n\n"
|
||||
@"Don't forget to update the site with your new password! "
|
||||
@"Your old password was:\n"
|
||||
@"%@", self.activeElement.name, oldPassword)];
|
||||
}
|
||||
|
||||
- (IBAction)editPassword {
|
||||
|
||||
if (self.activeElement.type & MPElementTypeClassStored) {
|
||||
self.contentField.enabled = YES;
|
||||
[self.contentField becomeFirstResponder];
|
||||
}
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (IBAction)closeAlert {
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.alertContainer.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
self.alertBody.text = nil;
|
||||
}];
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (IBAction)action:(id)sender {
|
||||
|
||||
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
|
||||
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
||||
switch (buttonIndex - [sheet firstOtherButtonIndex]) {
|
||||
case 0:
|
||||
[self toggleHelpAnimated:YES];
|
||||
break;
|
||||
case 1:
|
||||
[self setHelpChapter:@"faq"];
|
||||
[self setHelpHidden:NO animated:YES];
|
||||
break;
|
||||
case 2:
|
||||
[[MPAppDelegate get] showGuide];
|
||||
break;
|
||||
case 3: {
|
||||
IASKAppSettingsViewController *settingsVC = [IASKAppSettingsViewController new];
|
||||
settingsVC.delegate = self;
|
||||
[self.navigationController pushViewController:settingsVC animated:YES];
|
||||
break;
|
||||
}
|
||||
#ifndef PRODUCTION
|
||||
case 4:
|
||||
[TestFlight openFeedbackView];
|
||||
break;
|
||||
case 5: {
|
||||
#else
|
||||
case 4: {
|
||||
#endif
|
||||
[[MPAppDelegate get] signOut];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointAction];
|
||||
#endif
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil
|
||||
otherTitles:
|
||||
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings",
|
||||
#ifndef PRODUCTION
|
||||
@"Feedback",
|
||||
#endif
|
||||
@"Sign Out",
|
||||
nil];
|
||||
}
|
||||
|
||||
- (MPElementType)selectedType {
|
||||
|
||||
return self.activeElement.type;
|
||||
}
|
||||
|
||||
- (void)didSelectType:(MPElementType)type {
|
||||
|
||||
[self updateElement:^{
|
||||
// Update password type.
|
||||
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
|
||||
// Type requires a different class of element. Recreate the element.
|
||||
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
|
||||
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
||||
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
|
||||
newElement.name = self.activeElement.name;
|
||||
newElement.mpHashHex = self.activeElement.mpHashHex;
|
||||
newElement.uses = self.activeElement.uses;
|
||||
newElement.lastUsed = self.activeElement.lastUsed;
|
||||
|
||||
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
|
||||
self.activeElement = newElement;
|
||||
}];
|
||||
|
||||
self.activeElement.type = type;
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
|
||||
#endif
|
||||
|
||||
if (type & MPElementTypeClassStored && ![self.activeElement.description length])
|
||||
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didSelectElement:(MPElementEntity *)element {
|
||||
|
||||
if (element) {
|
||||
self.activeElement = element;
|
||||
[self.activeElement use];
|
||||
|
||||
[self.searchDisplayController setActive:NO animated:YES];
|
||||
self.searchDisplayController.searchBar.text = self.activeElement.name;
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement];
|
||||
#endif
|
||||
}
|
||||
|
||||
[self updateAnimated:YES];
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
if (textField == self.contentField)
|
||||
[self.contentField resignFirstResponder];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
|
||||
if (textField == self.contentField) {
|
||||
self.contentField.enabled = NO;
|
||||
if (![self.activeElement isKindOfClass:[MPElementStoredEntity class]])
|
||||
// Not of a type whose content can be edited.
|
||||
return;
|
||||
|
||||
if ([((MPElementStoredEntity *) self.activeElement).content isEqual:self.contentField.text])
|
||||
// Content hasn't changed.
|
||||
return;
|
||||
|
||||
[self updateElement:^{
|
||||
((MPElementStoredEntity *) self.activeElement).content = self.contentField.text;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
|
||||
navigationType:(UIWebViewNavigationType)navigationType {
|
||||
|
||||
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink];
|
||||
#endif
|
||||
|
||||
[[UIApplication sharedApplication] openURL:[request URL]];
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)settingsViewControllerDidEnd:(IASKAppSettingsViewController *)sender {
|
||||
|
||||
while ([self.navigationController.viewControllers containsObject:sender])
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// MPSearchDelegate.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 04/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPElementEntity.h"
|
||||
|
||||
@protocol MPSearchResultsDelegate <NSObject>
|
||||
|
||||
- (void)didSelectElement:(MPElementEntity *)element;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPSearchDelegate : NSObject <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
|
||||
|
||||
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
|
||||
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
|
||||
@property (strong, nonatomic) NSString *query;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
|
||||
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
|
||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||
|
||||
@end
|
||||
@@ -1,313 +0,0 @@
|
||||
//
|
||||
// MPSearchDelegate.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 04/01/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPSearchDelegate.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
|
||||
@interface MPSearchDelegate (Private)
|
||||
|
||||
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)update;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPSearchDelegate
|
||||
@synthesize query;
|
||||
@synthesize dateFormatter;
|
||||
@synthesize fetchedResultsController;
|
||||
@synthesize delegate;
|
||||
@synthesize searchDisplayController;
|
||||
@synthesize searchTipContainer;
|
||||
|
||||
- (id)init {
|
||||
|
||||
if (!([super init]))
|
||||
return nil;
|
||||
|
||||
self.dateFormatter = [NSDateFormatter new];
|
||||
self.dateFormatter.timeStyle = NSDateFormatterNoStyle;
|
||||
self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
|
||||
self.query = @"";
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
||||
|
||||
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
|
||||
for (NSUInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
|
||||
NSUInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
|
||||
if (!rowCount)
|
||||
continue;
|
||||
|
||||
if (rowCount == 1)
|
||||
[self tableView:tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
|
||||
#endif
|
||||
|
||||
[self.delegate didSelectElement:nil];
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||
|
||||
if (searchBar.searchResultsButtonSelected && !searchText.length)
|
||||
searchBar.text = @" ";
|
||||
|
||||
self.query = [searchBar.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
if (!self.query)
|
||||
self.query = @"";
|
||||
}
|
||||
|
||||
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
|
||||
|
||||
controller.searchBar.prompt = @"Enter the site's name (eg. apple.com):";
|
||||
|
||||
[UIView animateWithDuration:0.2f animations:^{
|
||||
self.searchTipContainer.alpha = 0;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
|
||||
|
||||
controller.searchBar.text = controller.searchBar.searchResultsButtonSelected? @" ": @"";
|
||||
self.query = @"";
|
||||
}
|
||||
|
||||
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
|
||||
|
||||
controller.searchBar.prompt = nil;
|
||||
controller.searchBar.searchResultsButtonSelected = NO;
|
||||
}
|
||||
|
||||
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
|
||||
|
||||
tableView.backgroundColor = [UIColor blackColor];
|
||||
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
tableView.rowHeight = 48.0f;
|
||||
}
|
||||
|
||||
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
|
||||
|
||||
[self update];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
|
||||
assert(self.query);
|
||||
assert([MPAppDelegate get].keyPhraseHashHex);
|
||||
NSFetchRequest *fetchRequest = [[MPAppDelegate get].managedObjectModel
|
||||
fetchRequestFromTemplateWithName:@"MPElements"
|
||||
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
self.query, @"query",
|
||||
[MPAppDelegate get].keyPhraseHashHex, @"mpHashHex",
|
||||
nil]];
|
||||
[fetchRequest setSortDescriptors:
|
||||
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
|
||||
|
||||
self.fetchedResultsController.delegate = nil;
|
||||
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
|
||||
managedObjectContext:[MPAppDelegate managedObjectContext]
|
||||
sectionNameKeyPath:nil cacheName:nil];
|
||||
self.fetchedResultsController.delegate = self;
|
||||
|
||||
NSError *error;
|
||||
if (![self.fetchedResultsController performFetch:&error])
|
||||
err(@"Couldn't fetch elements: %@", error);
|
||||
}
|
||||
|
||||
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
|
||||
|
||||
[self.searchDisplayController.searchResultsTableView beginUpdates];
|
||||
}
|
||||
|
||||
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
|
||||
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
|
||||
|
||||
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
|
||||
switch(type) {
|
||||
|
||||
case NSFetchedResultsChangeInsert:
|
||||
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
|
||||
break;
|
||||
|
||||
case NSFetchedResultsChangeDelete:
|
||||
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
|
||||
break;
|
||||
|
||||
case NSFetchedResultsChangeUpdate:
|
||||
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
|
||||
inTableView:tableView atIndexPath:indexPath];
|
||||
break;
|
||||
|
||||
case NSFetchedResultsChangeMove:
|
||||
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
|
||||
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
|
||||
|
||||
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
|
||||
switch(type) {
|
||||
|
||||
case NSFetchedResultsChangeInsert:
|
||||
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
break;
|
||||
|
||||
case NSFetchedResultsChangeDelete:
|
||||
[tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
|
||||
|
||||
[self.searchDisplayController.searchResultsTableView endUpdates];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
|
||||
return [[self.fetchedResultsController sections] count] + ([self.query length]? 1: 0);
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
|
||||
if (section < [[self.fetchedResultsController sections] count])
|
||||
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"];
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MPElementSearch"];
|
||||
|
||||
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ui_list_middle"]];
|
||||
backgroundImageView.frame = CGRectMake(-5, 0, 330, 34);
|
||||
backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
backgroundImageView.contentStretch = CGRectMake(0.2f, 0.2f, 0.6f, 0.6f);
|
||||
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 34)];
|
||||
[backgroundView addSubview:backgroundImageView];
|
||||
backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
cell.backgroundView = backgroundView;
|
||||
cell.textLabel.backgroundColor = [UIColor clearColor];
|
||||
cell.textLabel.textColor = [UIColor whiteColor];
|
||||
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
|
||||
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
|
||||
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
cell.clipsToBounds = YES;
|
||||
}
|
||||
|
||||
[self configureCell:cell inTableView:tableView atIndexPath:indexPath];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < [[self.fetchedResultsController sections] count]) {
|
||||
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
|
||||
|
||||
cell.textLabel.text = element.name;
|
||||
cell.detailTextLabel.text = [NSString stringWithFormat:@"Used %d times, last on %@",
|
||||
element.uses, [self.dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:element.lastUsed]]];
|
||||
} else {
|
||||
// "New" section
|
||||
cell.textLabel.text = self.query;
|
||||
cell.detailTextLabel.text = @"Create a new site.";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < [[self.fetchedResultsController sections] count])
|
||||
[self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]];
|
||||
|
||||
else {
|
||||
// "New" section.
|
||||
NSString *siteName = self.query;
|
||||
[PearlAlert showAlertWithTitle:@"New Site"
|
||||
message:l(@"Do you want to create a new site named:\n%@", siteName)
|
||||
viewStyle:UIAlertViewStyleDefault
|
||||
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
|
||||
if (buttonIndex == [alert cancelButtonIndex])
|
||||
return;
|
||||
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
|
||||
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
|
||||
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
|
||||
|
||||
element.name = siteName;
|
||||
element.mpHashHex = [MPAppDelegate get].keyPhraseHashHex;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.delegate didSelectElement:element];
|
||||
});
|
||||
}];
|
||||
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonYes, nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
|
||||
|
||||
if (section < [[self.fetchedResultsController sections] count])
|
||||
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
|
||||
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
|
||||
|
||||
return [self.fetchedResultsController sectionIndexTitles];
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
|
||||
|
||||
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
if (indexPath.section < [[self.fetchedResultsController sections] count]) {
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete)
|
||||
[self.fetchedResultsController.managedObjectContext performBlock:^{
|
||||
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
|
||||
[self.fetchedResultsController.managedObjectContext deleteObject:element];
|
||||
|
||||
#ifndef PRODUCTION
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement];
|
||||
#endif
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,111 +0,0 @@
|
||||
//
|
||||
// MPTypeViewController.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 27/11/11.
|
||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTypeViewController.h"
|
||||
|
||||
|
||||
@interface MPTypeViewController ()
|
||||
|
||||
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPTypeViewController
|
||||
@synthesize delegate;
|
||||
|
||||
#pragma mark - View lifecycle
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(selectedType)])
|
||||
if ([delegate selectedType] == [self typeAtIndexPath:indexPath])
|
||||
[cell iterateSubviewsContinueAfter:^BOOL(UIView *subview) {
|
||||
if ([subview isKindOfClass:[UIImageView class]]) {
|
||||
UIImageView *imageView = ((UIImageView *)subview);
|
||||
if (!imageView.highlightedImage)
|
||||
imageView.highlightedImage = [imageView.image highlightedImage];
|
||||
imageView.highlighted = YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
assert(self.navigationController.topViewController == self);
|
||||
|
||||
[delegate didSelectType:[self typeAtIndexPath:indexPath]];
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
|
||||
|
||||
switch (indexPath.section) {
|
||||
case 0: {
|
||||
// Calculated
|
||||
switch (indexPath.row) {
|
||||
case 0:
|
||||
return MPElementTypeCalculatedLong;
|
||||
case 1:
|
||||
return MPElementTypeCalculatedMedium;
|
||||
case 2:
|
||||
return MPElementTypeCalculatedShort;
|
||||
case 3:
|
||||
return MPElementTypeCalculatedBasic;
|
||||
case 4:
|
||||
return MPElementTypeCalculatedPIN;
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"Unsupported row: %d, when selecting calculated element type.", indexPath.row];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
// Stored
|
||||
switch (indexPath.row) {
|
||||
case 0:
|
||||
return MPElementTypeStoredPersonal;
|
||||
case 1:
|
||||
return MPElementTypeStoredDevicePrivate;
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"Unsupported row: %d, when selecting stored element type.", indexPath.row];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"Unsupported section: %d, when selecting element type.", indexPath.section];
|
||||
}
|
||||
|
||||
@throw nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -17,55 +17,72 @@ typedef enum {
|
||||
} MPElementContentType;
|
||||
|
||||
typedef enum {
|
||||
MPElementTypeClassCalculated = 2 << 7,
|
||||
MPElementTypeClassStored = 2 << 8,
|
||||
/** Generate the password. */
|
||||
MPElementTypeClassGenerated = 1 << 4,
|
||||
/** Store the password. */
|
||||
MPElementTypeClassStored = 1 << 5,
|
||||
} MPElementTypeClass;
|
||||
|
||||
typedef enum {
|
||||
MPElementTypeCalculatedLong = MPElementTypeClassCalculated | 0x01,
|
||||
MPElementTypeCalculatedMedium = MPElementTypeClassCalculated | 0x02,
|
||||
MPElementTypeCalculatedShort = MPElementTypeClassCalculated | 0x03,
|
||||
MPElementTypeCalculatedBasic = MPElementTypeClassCalculated | 0x04,
|
||||
MPElementTypeCalculatedPIN = MPElementTypeClassCalculated | 0x05,
|
||||
/** Export the key-protected content data. */
|
||||
MPElementFeatureExportContent = 1 << 10,
|
||||
/** Never export content. */
|
||||
MPElementFeatureDevicePrivate = 1 << 11,
|
||||
} MPElementFeature;
|
||||
|
||||
MPElementTypeStoredPersonal = MPElementTypeClassStored | 0x01,
|
||||
MPElementTypeStoredDevicePrivate = MPElementTypeClassStored | 0x02,
|
||||
typedef enum {
|
||||
MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
|
||||
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
|
||||
|
||||
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
||||
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
||||
} MPElementType;
|
||||
|
||||
#ifndef PRODUCTION
|
||||
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
|
||||
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@"
|
||||
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard"
|
||||
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter"
|
||||
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword"
|
||||
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert"
|
||||
#define MPTestFlightCheckpointSelectType @"MPTestFlightCheckpointSelectType_%@"
|
||||
#define MPTestFlightCheckpointSelectElement @"MPTestFlightCheckpointSelectElement"
|
||||
#define MPTestFlightCheckpointDeleteElement @"MPTestFlightCheckpointDeleteElement"
|
||||
#define MPTestFlightCheckpointCancelSearch @"MPTestFlightCheckpointCancelSearch"
|
||||
#define MPTestFlightCheckpointExternalLink @"MPTestFlightCheckpointExternalLink"
|
||||
#define MPTestFlightCheckpointLaunched @"MPTestFlightCheckpointLaunched"
|
||||
#define MPTestFlightCheckpointActivated @"MPTestFlightCheckpointActivated"
|
||||
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
|
||||
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
|
||||
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
|
||||
#define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten"
|
||||
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
|
||||
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
|
||||
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
|
||||
#define MPTestFlightCheckpointMPAsked @"MPTestFlightCheckpointMPAsked"
|
||||
#define MPTestFlightCheckpointStoreIncompatible @"MPTestFlightCheckpointStoreIncompatible"
|
||||
#define MPTestFlightCheckpointSetKeyphraseLength @"MPTestFlightCheckpointSetKeyphraseLength_%d"
|
||||
#endif
|
||||
#define MPCheckpointAction @"MPCheckpointAction"
|
||||
#define MPCheckpointHelpChapter @"MPCheckpointHelpChapter"
|
||||
#define MPCheckpointCopyToPasteboard @"MPCheckpointCopyToPasteboard"
|
||||
#define MPCheckpointResetPasswordCounter @"MPCheckpointResetPasswordCounter"
|
||||
#define MPCheckpointIncrementPasswordCounter @"MPCheckpointIncrementPasswordCounter"
|
||||
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
|
||||
#define MPCheckpointCloseAlert @"MPCheckpointCloseAlert"
|
||||
#define MPCheckpointUseType @"MPCheckpointUseType"
|
||||
#define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
|
||||
#define MPCheckpointCancelSearch @"MPCheckpointCancelSearch"
|
||||
#define MPCheckpointExternalLink @"MPCheckpointExternalLink"
|
||||
#define MPCheckpointLaunched @"MPCheckpointLaunched"
|
||||
#define MPCheckpointActivated @"MPCheckpointActivated"
|
||||
#define MPCheckpointDeactivated @"MPCheckpointDeactivated"
|
||||
#define MPCheckpointTerminated @"MPCheckpointTerminated"
|
||||
#define MPCheckpointShowGuide @"MPCheckpointShowGuide"
|
||||
#define MPCheckpointForgetSavedKey @"MPCheckpointForgetSavedKey"
|
||||
#define MPCheckpointChangeMP @"MPCheckpointChangeMP"
|
||||
#define MPCheckpointLocalStoreIncompatible @"MPCheckpointLocalStoreIncompatible"
|
||||
#define MPCheckpointCloudStoreIncompatible @"MPCheckpointCloudStoreIncompatible"
|
||||
#define MPCheckpointSignInFailed @"MPCheckpointSignInFailed"
|
||||
#define MPCheckpointSignedIn @"MPCheckpointSignedIn"
|
||||
#define MPCheckpointConfig @"MPCheckpointConfig"
|
||||
#define MPCheckpointCloud @"MPCheckpointCloud"
|
||||
#define MPCheckpointCloudEnabled @"MPCheckpointCloudEnabled"
|
||||
#define MPCheckpointCloudDisabled @"MPCheckpointCloudDisabled"
|
||||
#define MPCheckpointSitesImported @"MPCheckpointSitesImported"
|
||||
#define MPCheckpointSitesExported @"MPCheckpointSitesExported"
|
||||
|
||||
#define MPNotificationKeySet @"MPNotificationKeySet"
|
||||
#define MPNotificationKeyUnset @"MPNotificationKeyUnset"
|
||||
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
||||
#define MPNotificationSignedIn @"MPNotificationKeySet"
|
||||
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
|
||||
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
||||
#define MPNotificationElementUsed @"MPNotificationElementUsed"
|
||||
|
||||
NSData *keyPhraseForPassword(NSString *password);
|
||||
NSData *keyPhraseHashForPassword(NSString *password);
|
||||
NSData *keyPhraseHashForKeyPhrase(NSData *keyPhrase);
|
||||
NSData *keyForPassword(NSString *password, NSString *username);
|
||||
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength);
|
||||
NSData *keyIDForPassword(NSString *password, NSString *username);
|
||||
NSData *keyIDForKey(NSData *key);
|
||||
NSString *NSStringFromMPElementType(MPElementType type);
|
||||
NSString *NSStringShortFromMPElementType(MPElementType type);
|
||||
NSString *ClassNameFromMPElementType(MPElementType type);
|
||||
Class ClassFromMPElementType(MPElementType type);
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *keyPhrase, uint16_t counter);
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter);
|
||||
|
||||
@@ -6,49 +6,70 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTypes.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
#import "MPElementStoredEntity.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
#define MP_salt nil
|
||||
#define MP_N 16384
|
||||
|
||||
#define MP_N 131072
|
||||
#define MP_r 8
|
||||
#define MP_p 1
|
||||
#define MP_dkLen 64
|
||||
#define MP_hash PearlDigestSHA256
|
||||
#define MP_hash PearlHashSHA256
|
||||
|
||||
NSData *keyPhraseForPassword(NSString *password) {
|
||||
NSData *keyForPassword(NSString *password, NSString *username) {
|
||||
|
||||
return [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p];
|
||||
uint32_t nusernameLength = htonl(username.length);
|
||||
NSDate *start = [NSDate date];
|
||||
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
usingSalt:[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nusernameLength
|
||||
length:sizeof(nusernameLength)],
|
||||
[username dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil] N:MP_N r:MP_r p:MP_p];
|
||||
|
||||
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2f)", username, password, [keyIDForKey(key) encodeHex], -[start timeIntervalSinceNow]);
|
||||
return key;
|
||||
}
|
||||
NSData *keyPhraseHashForPassword(NSString *password) {
|
||||
|
||||
return keyPhraseHashForKeyPhrase(keyPhraseForPassword(password));
|
||||
}
|
||||
NSData *keyPhraseHashForKeyPhrase(NSData *keyPhrase) {
|
||||
|
||||
return [keyPhrase hashWith:MP_hash];
|
||||
NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) {
|
||||
|
||||
return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))];
|
||||
}
|
||||
|
||||
|
||||
NSData *keyIDForPassword(NSString *password, NSString *username) {
|
||||
|
||||
return keyIDForKey(keyForPassword(password, username));
|
||||
}
|
||||
|
||||
NSData *keyIDForKey(NSData *key) {
|
||||
|
||||
return [key hashWith:MP_hash];
|
||||
}
|
||||
|
||||
NSString *NSStringFromMPElementType(MPElementType type) {
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeCalculatedLong:
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
return @"Maximum Security Password";
|
||||
|
||||
case MPElementTypeGeneratedLong:
|
||||
return @"Long Password";
|
||||
|
||||
case MPElementTypeCalculatedMedium:
|
||||
case MPElementTypeGeneratedMedium:
|
||||
return @"Medium Password";
|
||||
|
||||
case MPElementTypeCalculatedShort:
|
||||
case MPElementTypeGeneratedShort:
|
||||
return @"Short Password";
|
||||
|
||||
case MPElementTypeCalculatedBasic:
|
||||
case MPElementTypeGeneratedBasic:
|
||||
return @"Basic Password";
|
||||
|
||||
case MPElementTypeCalculatedPIN:
|
||||
case MPElementTypeGeneratedPIN:
|
||||
return @"PIN";
|
||||
|
||||
case MPElementTypeStoredPersonal:
|
||||
@@ -58,7 +79,42 @@ NSString *NSStringFromMPElementType(MPElementType type) {
|
||||
return @"Device Private Password";
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type];
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
}
|
||||
|
||||
NSString *NSStringShortFromMPElementType(MPElementType type) {
|
||||
|
||||
if (!type)
|
||||
return nil;
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
return @"Maximum";
|
||||
|
||||
case MPElementTypeGeneratedLong:
|
||||
return @"Long";
|
||||
|
||||
case MPElementTypeGeneratedMedium:
|
||||
return @"Medium";
|
||||
|
||||
case MPElementTypeGeneratedShort:
|
||||
return @"Short";
|
||||
|
||||
case MPElementTypeGeneratedBasic:
|
||||
return @"Basic";
|
||||
|
||||
case MPElementTypeGeneratedPIN:
|
||||
return @"PIN";
|
||||
|
||||
case MPElementTypeStoredPersonal:
|
||||
return @"Personal";
|
||||
|
||||
case MPElementTypeStoredDevicePrivate:
|
||||
return @"Device";
|
||||
|
||||
default:
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,19 +124,22 @@ Class ClassFromMPElementType(MPElementType type) {
|
||||
return nil;
|
||||
|
||||
switch (type) {
|
||||
case MPElementTypeCalculatedLong:
|
||||
case MPElementTypeGeneratedMaximum:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
case MPElementTypeCalculatedMedium:
|
||||
case MPElementTypeGeneratedLong:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
case MPElementTypeCalculatedShort:
|
||||
case MPElementTypeGeneratedMedium:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
case MPElementTypeCalculatedBasic:
|
||||
case MPElementTypeGeneratedShort:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
case MPElementTypeCalculatedPIN:
|
||||
case MPElementTypeGeneratedBasic:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
case MPElementTypeGeneratedPIN:
|
||||
return [MPElementGeneratedEntity class];
|
||||
|
||||
case MPElementTypeStoredPersonal:
|
||||
@@ -90,7 +149,7 @@ Class ClassFromMPElementType(MPElementType type) {
|
||||
return [MPElementStoredEntity class];
|
||||
|
||||
default:
|
||||
[NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type];
|
||||
Throw(@"Type not supported: %d", type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,39 +159,62 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
|
||||
}
|
||||
|
||||
static NSDictionary *MPTypes_ciphers = nil;
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *keyPhrase, uint16_t counter) {
|
||||
|
||||
assert(type & MPElementTypeClassCalculated);
|
||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint32_t counter) {
|
||||
|
||||
if (!(type & MPElementTypeClassGenerated)) {
|
||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
|
||||
return nil;
|
||||
}
|
||||
if (!name.length) {
|
||||
err(@"Missing name.");
|
||||
return nil;
|
||||
}
|
||||
if (!key.length) {
|
||||
err(@"Missing key.");
|
||||
return nil;
|
||||
}
|
||||
if (!counter)
|
||||
// Counter unset, go into OTP mode.
|
||||
// Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in.
|
||||
counter = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
|
||||
|
||||
if (MPTypes_ciphers == nil)
|
||||
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
|
||||
withExtension:@"plist"]];
|
||||
|
||||
// Determine the hash whose bytes will be used for calculating a password: md4(name-keyPhrase)
|
||||
assert(name && keyPhrase);
|
||||
uint16_t ncounter = htons(counter);
|
||||
NSData *keyHash = [[NSData dataByConcatenatingWithDelimitor:'-' datas:
|
||||
// Determine the seed whose bytes will be used for calculating a password
|
||||
trc(@"seed from: hmac-sha256(%@, 'com.lyndir.masterpassword' | %u | %@ | %u)", key, name.length, name, counter);
|
||||
uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
|
||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)],
|
||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
keyPhrase,
|
||||
[NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
|
||||
nil] hashWith:PearlDigestSHA1];
|
||||
const char *keyBytes = keyHash.bytes;
|
||||
nil]
|
||||
hmacWith:PearlHashSHA256 key:key];
|
||||
trc(@"seed is: %@", seed);
|
||||
const char *seedBytes = seed.bytes;
|
||||
|
||||
// Determine the cipher from the first hash byte.
|
||||
assert([keyHash length]);
|
||||
// Determine the cipher from the first seed byte.
|
||||
assert([seed length]);
|
||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
|
||||
valueForKey:NSStringFromMPElementType(type)];
|
||||
NSString *cipher = [typeCiphers objectAtIndex:keyBytes[0] % [typeCiphers count]];
|
||||
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
||||
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
|
||||
|
||||
// Encode the content, character by character, using subsequent hash bytes and the cipher.
|
||||
assert([keyHash length] >= [cipher length] + 1);
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
assert([seed length] >= [cipher length] + 1);
|
||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||
const char keyByte = keyBytes[c + 1];
|
||||
uint16_t keyByte = htons(seedBytes[c + 1]);
|
||||
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
|
||||
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
|
||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length],
|
||||
1)];
|
||||
|
||||
[content appendString:[cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)]];
|
||||
trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
|
||||
[content appendString:character];
|
||||
}
|
||||
|
||||
return content;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
//
|
||||
// MBUnlockViewController.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 22/02/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface MPUnlockViewController : UIViewController <UITextFieldDelegate>
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *lock;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *spinner;
|
||||
@property (weak, nonatomic) IBOutlet UITextField *field;
|
||||
@property (weak, nonatomic) IBOutlet UILabel *messageLabel;
|
||||
@property (weak, nonatomic) IBOutlet UIView *changeMPView;
|
||||
|
||||
- (IBAction)changeMP;
|
||||
|
||||
@end
|
||||
@@ -1,205 +0,0 @@
|
||||
//
|
||||
// MBUnlockViewController.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 22/02/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import "MPUnlockViewController.h"
|
||||
#import "MPAppDelegate.h"
|
||||
|
||||
typedef enum {
|
||||
MPLockscreenIdle,
|
||||
MPLockscreenError,
|
||||
MPLockscreenSuccess,
|
||||
MPLockscreenProgress,
|
||||
} MPLockscreen;
|
||||
|
||||
@interface MPUnlockViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPUnlockViewController
|
||||
@synthesize lock;
|
||||
@synthesize spinner;
|
||||
@synthesize field;
|
||||
@synthesize messageLabel;
|
||||
@synthesize changeMPView;
|
||||
|
||||
- (void)showMessage:(NSString *)message state:(MPLockscreen)state {
|
||||
|
||||
__block void(^showMessageAnimation)(void) = ^{
|
||||
self.lock.alpha = 0.0f;
|
||||
switch (state) {
|
||||
case MPLockscreenIdle:
|
||||
[self.lock setImage:[UIImage imageNamed:@"lock_idle"]];
|
||||
break;
|
||||
case MPLockscreenError:
|
||||
[self.lock setImage:[UIImage imageNamed:@"lock_red"]];
|
||||
break;
|
||||
case MPLockscreenSuccess:
|
||||
[self.lock setImage:[UIImage imageNamed:@"lock_green"]];
|
||||
break;
|
||||
case MPLockscreenProgress:
|
||||
[self.lock setImage:[UIImage imageNamed:@"lock_blue"]];
|
||||
break;
|
||||
}
|
||||
|
||||
self.lock.alpha = 0.0f;
|
||||
[UIView animateWithDuration:1.0f animations:^{
|
||||
self.lock.alpha = 1.0f;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
[UIView animateWithDuration:1.0f delay:0 options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{
|
||||
self.lock.alpha = 0.5f;
|
||||
} completion:nil];
|
||||
}];
|
||||
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.messageLabel.alpha = 1.0f;
|
||||
self.messageLabel.text = message;
|
||||
}];
|
||||
};
|
||||
|
||||
if (self.messageLabel.alpha)
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.messageLabel.alpha = 0.0f;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
showMessageAnimation();
|
||||
}];
|
||||
else
|
||||
showMessageAnimation();
|
||||
}
|
||||
|
||||
- (void)hideMessage {
|
||||
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.messageLabel.alpha = 0.0f;
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
|
||||
|
||||
return (interfaceOrientation == UIInterfaceOrientationPortrait);
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
||||
self.messageLabel.text = nil;
|
||||
self.messageLabel.alpha = 0;
|
||||
self.changeMPView.alpha = 0;
|
||||
self.spinner.alpha = 0;
|
||||
self.field.text = nil;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationKeyForgotten
|
||||
object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
[self.field becomeFirstResponder];
|
||||
}];
|
||||
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
- (void)viewDidUnload {
|
||||
|
||||
[self setSpinner:nil];
|
||||
[self setField:nil];
|
||||
|
||||
[self setMessageLabel:nil];
|
||||
[self setLock:nil];
|
||||
[self setChangeMPView:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:YES
|
||||
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO
|
||||
withAnimation:animated? UIStatusBarAnimationSlide: UIStatusBarAnimationNone];
|
||||
|
||||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[self.field becomeFirstResponder];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
|
||||
if ([textField.text length]) {
|
||||
[textField resignFirstResponder];
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)textFieldDidEndEditing:(UITextField *)textField {
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
@try {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
|
||||
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
rotate.fromValue = [NSNumber numberWithFloat:0];
|
||||
rotate.toValue = [NSNumber numberWithFloat:2 * M_PI];
|
||||
rotate.repeatCount = MAXFLOAT;
|
||||
rotate.duration = 3.0;
|
||||
|
||||
[self.spinner.layer removeAllAnimations];
|
||||
[self.spinner.layer addAnimation:rotate forKey:@"transform"];
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.spinner.alpha = 1.0f;
|
||||
}];
|
||||
|
||||
[self showMessage:@"Checking password..." state:MPLockscreenProgress];
|
||||
});
|
||||
|
||||
if ([[MPAppDelegate get] tryMasterPassword:textField.text])
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self showMessage:@"Success!" state:MPLockscreenSuccess];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 1.5f), dispatch_get_main_queue(), ^{
|
||||
[self dismissModalViewControllerAnimated:YES];
|
||||
});
|
||||
});
|
||||
else
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self showMessage:@"Not valid." state:MPLockscreenError];
|
||||
[UIView animateWithDuration:0.5f animations:^{
|
||||
self.changeMPView.alpha = 1.0f;
|
||||
}];
|
||||
});
|
||||
}
|
||||
@finally {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.spinner.alpha = 0.0f;
|
||||
} completion:^(BOOL finished) {
|
||||
[self.spinner.layer removeAllAnimations];
|
||||
}];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (IBAction)changeMP {
|
||||
|
||||
[[MPAppDelegate get] forgetKeyPhrase];
|
||||
}
|
||||
|
||||
@end
|
||||
32
MasterPassword/MPUserEntity.h
Normal file
32
MasterPassword/MPUserEntity.h
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// MPUserEntity.h
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
@class MPElementEntity;
|
||||
|
||||
@interface MPUserEntity : NSManagedObject
|
||||
|
||||
@property (nonatomic, retain) NSNumber * avatar_;
|
||||
@property (nonatomic, retain) NSData * keyID;
|
||||
@property (nonatomic, retain) NSDate * lastUsed;
|
||||
@property (nonatomic, retain) NSString * name;
|
||||
@property (nonatomic, retain) NSNumber * saveKey_;
|
||||
@property (nonatomic, retain) NSNumber * defaultType_;
|
||||
@property (nonatomic, retain) NSSet *elements;
|
||||
@end
|
||||
|
||||
@interface MPUserEntity (CoreDataGeneratedAccessors)
|
||||
|
||||
- (void)addElementsObject:(MPElementEntity *)value;
|
||||
- (void)removeElementsObject:(MPElementEntity *)value;
|
||||
- (void)addElements:(NSSet *)values;
|
||||
- (void)removeElements:(NSSet *)values;
|
||||
|
||||
@end
|
||||
23
MasterPassword/MPUserEntity.m
Normal file
23
MasterPassword/MPUserEntity.m
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// MPUserEntity.m
|
||||
// MasterPassword-iOS
|
||||
//
|
||||
// Created by Maarten Billemont on 11/06/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPUserEntity.h"
|
||||
#import "MPElementEntity.h"
|
||||
|
||||
|
||||
@implementation MPUserEntity
|
||||
|
||||
@dynamic avatar_;
|
||||
@dynamic keyID;
|
||||
@dynamic lastUsed;
|
||||
@dynamic name;
|
||||
@dynamic saveKey_;
|
||||
@dynamic defaultType_;
|
||||
@dynamic elements;
|
||||
|
||||
@end
|
||||
29
MasterPassword/Mac/MPAppDelegate.h
Normal file
29
MasterPassword/Mac/MPAppDelegate.h
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// MPAppDelegate.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 04/03/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import "MPPasswordWindowController.h"
|
||||
|
||||
@interface MPAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate>
|
||||
|
||||
@property (strong) NSStatusItem *statusItem;
|
||||
@property (strong) MPPasswordWindowController *passwordWindow;
|
||||
@property (weak) IBOutlet NSMenuItem *lockItem;
|
||||
@property (weak) IBOutlet NSMenuItem *showItem;
|
||||
@property (strong) IBOutlet NSMenu *statusMenu;
|
||||
@property (weak) IBOutlet NSMenuItem *useICloudItem;
|
||||
@property (weak) IBOutlet NSMenuItem *rememberPasswordItem;
|
||||
@property (weak) IBOutlet NSMenuItem *savePasswordItem;
|
||||
|
||||
+ (MPAppDelegate *)get;
|
||||
|
||||
- (IBAction)activate:(id)sender;
|
||||
- (IBAction)togglePreference:(NSMenuItem *)sender;
|
||||
|
||||
@end
|
||||
223
MasterPassword/Mac/MPAppDelegate.m
Normal file
223
MasterPassword/Mac/MPAppDelegate.m
Normal file
@@ -0,0 +1,223 @@
|
||||
//
|
||||
// MPAppDelegate.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 04/03/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPConfig.h"
|
||||
#import "MPElementEntity.h"
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
|
||||
@implementation MPAppDelegate
|
||||
@synthesize statusItem;
|
||||
@synthesize lockItem;
|
||||
@synthesize showItem;
|
||||
@synthesize statusMenu;
|
||||
@synthesize useICloudItem;
|
||||
@synthesize rememberPasswordItem;
|
||||
@synthesize savePasswordItem;
|
||||
@synthesize passwordWindow;
|
||||
|
||||
@synthesize key;
|
||||
@synthesize keyID;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wfour-char-constants"
|
||||
static EventHotKeyID MPShowHotKey = {.signature = 'show', .id = 1};
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
+ (void)initialize {
|
||||
|
||||
[MPConfig get];
|
||||
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].autoprintLevel = PearlLogLevelTrace;
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (MPAppDelegate *)get {
|
||||
|
||||
return (MPAppDelegate *)[super get];
|
||||
}
|
||||
|
||||
static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) {
|
||||
|
||||
// Extract the hotkey ID.
|
||||
EventHotKeyID hotKeyID;
|
||||
GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID,
|
||||
NULL, sizeof(hotKeyID), NULL, &hotKeyID);
|
||||
|
||||
// Check which hotkey this was.
|
||||
if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) {
|
||||
[((__bridge MPAppDelegate *)userData) activate:nil];
|
||||
return noErr;
|
||||
}
|
||||
|
||||
return eventNotHandledErr;
|
||||
}
|
||||
|
||||
- (void)showMenu {
|
||||
|
||||
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
|
||||
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
|
||||
self.showItem.enabled = ![self.passwordWindow.window isVisible];
|
||||
|
||||
[self.statusItem popUpStatusItemMenu:self.statusMenu];
|
||||
}
|
||||
|
||||
- (IBAction)activate:(id)sender {
|
||||
|
||||
if ([[NSApplication sharedApplication] isActive])
|
||||
[self applicationDidBecomeActive:nil];
|
||||
else
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||
}
|
||||
|
||||
- (IBAction)togglePreference:(NSMenuItem *)sender {
|
||||
|
||||
if (sender == useICloudItem)
|
||||
[self.storeManager useiCloudStore:sender.state == NSOffState alertUser:YES];
|
||||
if (sender == rememberPasswordItem)
|
||||
[MPConfig get].rememberKey = [NSNumber numberWithBool:![[MPConfig get].rememberKey boolValue]];
|
||||
if (sender == savePasswordItem)
|
||||
[MPConfig get].saveKey = [NSNumber numberWithBool:![[MPConfig get].saveKey boolValue]];
|
||||
}
|
||||
|
||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
|
||||
|
||||
if (configKey == @selector(rememberKey))
|
||||
self.rememberPasswordItem.state = [[MPConfig get].rememberKey boolValue]? NSOnState: NSOffState;
|
||||
if (configKey == @selector(saveKey))
|
||||
self.savePasswordItem.state = [[MPConfig get].saveKey boolValue]? NSOnState: NSOffState;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
|
||||
if ([keyPath isEqualToString:@"key"]) {
|
||||
if (self.key)
|
||||
[self.lockItem setEnabled:YES];
|
||||
else {
|
||||
[self.lockItem setEnabled:NO];
|
||||
[self.passwordWindow close];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
|
||||
|
||||
return [[self managedObjectContext] undoManager];
|
||||
}
|
||||
|
||||
#pragma mark - NSApplicationDelegate
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
|
||||
// Setup delegates and listeners.
|
||||
[MPConfig get].delegate = self;
|
||||
[self addObserver:self forKeyPath:@"key" options:0 context:nil];
|
||||
|
||||
// Initially, use iCloud.
|
||||
if ([[MPConfig get].firstRun boolValue])
|
||||
[[self storeManager] useiCloudStore:YES alertUser:YES];
|
||||
|
||||
// Status item.
|
||||
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
|
||||
self.statusItem.title = @"•••";
|
||||
self.statusItem.highlightMode = YES;
|
||||
self.statusItem.target = self;
|
||||
self.statusItem.action = @selector(showMenu);
|
||||
|
||||
// Global hotkey.
|
||||
EventHotKeyRef hotKeyRef;
|
||||
EventTypeSpec hotKeyEvents[1] = {{.eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed}};
|
||||
OSStatus status = InstallApplicationEventHandler(NewEventHandlerUPP(MPHotKeyHander), GetEventTypeCount(hotKeyEvents),
|
||||
hotKeyEvents,
|
||||
(__bridge void *)self, NULL);
|
||||
if (status != noErr)
|
||||
err(@"Error installing application event handler: %d", status);
|
||||
status = RegisterEventHotKey(35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef);
|
||||
if (status != noErr)
|
||||
err(@"Error registering hotkey: %d", status);
|
||||
}
|
||||
|
||||
- (void)applicationWillBecomeActive:(NSNotification *)notification {
|
||||
|
||||
if (!self.passwordWindow)
|
||||
self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
|
||||
static BOOL firstTime = YES;
|
||||
if (firstTime)
|
||||
firstTime = NO;
|
||||
else
|
||||
[self.passwordWindow showWindow:self];
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
|
||||
if (![[MPConfig get].rememberKey boolValue])
|
||||
self.key = nil;
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||
// Save changes in the application's managed object context before the application terminates.
|
||||
|
||||
if (![self managedObjectContext]) {
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
if (![[self managedObjectContext] commitEditing]) {
|
||||
NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd));
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
if (![[self managedObjectContext] hasChanges]) {
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
if (![[self managedObjectContext] save:&error]) {
|
||||
|
||||
// Customize this code block to include application-specific recovery steps.
|
||||
BOOL result = [sender presentError:error];
|
||||
if (result) {
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message");
|
||||
NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info");
|
||||
NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
|
||||
NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title");
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:question];
|
||||
[alert setInformativeText:info];
|
||||
[alert addButtonWithTitle:quitButton];
|
||||
[alert addButtonWithTitle:cancelButton];
|
||||
|
||||
NSInteger answer = [alert runModal];
|
||||
|
||||
if (answer == NSAlertAlternateReturn) {
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
}
|
||||
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
#pragma mark - UbiquityStoreManagerDelegate
|
||||
|
||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didSwitchToiCloud:(BOOL)iCloudEnabled {
|
||||
|
||||
self.useICloudItem.state = iCloudEnabled? NSOnState: NSOffState;
|
||||
self.useICloudItem.enabled = !iCloudEnabled;
|
||||
}
|
||||
|
||||
@end
|
||||
24
MasterPassword/Mac/MPPasswordWindowController.h
Normal file
24
MasterPassword/Mac/MPPasswordWindowController.h
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// MPPasswordWindowController.h
|
||||
// MasterPassword-Mac
|
||||
//
|
||||
// Created by Maarten Billemont on 04/03/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface MPPasswordWindowController : NSWindowController<NSTextFieldDelegate> {
|
||||
|
||||
NSString *_content;
|
||||
}
|
||||
|
||||
@property (strong) NSString *content;
|
||||
|
||||
@property (weak) IBOutlet NSTextField *siteField;
|
||||
@property (weak) IBOutlet NSTextField *contentField;
|
||||
@property (weak) IBOutlet NSTextField *tipField;
|
||||
|
||||
- (void)unlock;
|
||||
|
||||
@end
|
||||
249
MasterPassword/Mac/MPPasswordWindowController.m
Normal file
249
MasterPassword/Mac/MPPasswordWindowController.m
Normal file
@@ -0,0 +1,249 @@
|
||||
//
|
||||
// MPPasswordWindowController.m
|
||||
// MasterPassword-Mac
|
||||
//
|
||||
// Created by Maarten Billemont on 04/03/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPasswordWindowController.h"
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPElementEntity.h"
|
||||
#import "MPElementGeneratedEntity.h"
|
||||
|
||||
@interface MPPasswordWindowController ()
|
||||
|
||||
@property (nonatomic, strong) NSString *oldSiteName;
|
||||
@property (nonatomic, strong) NSArray /* MPElementEntity */ *siteResults;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPPasswordWindowController
|
||||
@synthesize oldSiteName, siteResults;
|
||||
@synthesize siteField;
|
||||
@synthesize contentField;
|
||||
@synthesize tipField;
|
||||
|
||||
- (void)windowDidLoad {
|
||||
|
||||
[self setContent:@""];
|
||||
[self.tipField setStringValue:@""];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self unlock];
|
||||
[self.siteField selectText:self];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[[NSApplication sharedApplication] hide:self];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSControlTextDidChangeNotification object:self.siteField queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
NSString *newSiteName = [self.siteField stringValue];
|
||||
BOOL shouldComplete = [self.oldSiteName length] < [newSiteName length];
|
||||
self.oldSiteName = newSiteName;
|
||||
|
||||
if ([self trySite])
|
||||
shouldComplete = NO;
|
||||
|
||||
if (shouldComplete)
|
||||
[[[note userInfo] objectForKey:@"NSFieldEditor"] complete:nil];
|
||||
}];
|
||||
|
||||
[super windowDidLoad];
|
||||
}
|
||||
|
||||
- (void)unlock {
|
||||
|
||||
if (![MPAppDelegate get].key)
|
||||
// Try and load the key from the keychain.
|
||||
[[MPAppDelegate get] loadStoredKey];
|
||||
|
||||
if (![MPAppDelegate get].key)
|
||||
// Ask the user to set the key through his master password.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([MPAppDelegate get].key)
|
||||
return;
|
||||
|
||||
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
|
||||
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Quit"
|
||||
informativeTextWithFormat:@"Your master password is required to unlock the application."];
|
||||
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 22)];
|
||||
[alert setAccessoryView:passwordField];
|
||||
[alert layout];
|
||||
[passwordField becomeFirstResponder];
|
||||
[alert beginSheetModalForWindow:self.window modalDelegate:self
|
||||
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:NULL];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
||||
|
||||
switch (returnCode) {
|
||||
case NSAlertAlternateReturn:
|
||||
// "Change" button.
|
||||
if ([[NSAlert alertWithMessageText:@"Changing Master Password"
|
||||
defaultButton:nil alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil
|
||||
informativeTextWithFormat:
|
||||
@"This will allow you to log in with a different master password.\n\n"
|
||||
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
|
||||
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
|
||||
@"You can always change back to your current master password later.\n"
|
||||
@"Your current sites and passwords will then become available again."] runModal]
|
||||
== 1)
|
||||
[[MPAppDelegate get] forgetKey];
|
||||
break;
|
||||
|
||||
case NSAlertOtherReturn:
|
||||
// "Quit" button.
|
||||
[[NSApplication sharedApplication] terminate:self];
|
||||
return;
|
||||
|
||||
case NSAlertDefaultReturn:
|
||||
// "Unlock" button.
|
||||
[[MPAppDelegate get] tryMasterPassword:[(NSSecureTextField *)alert.accessoryView stringValue]];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words
|
||||
forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
|
||||
|
||||
NSString *query = [[control stringValue] substringWithRange:charRange];
|
||||
if (![query length] || ![MPAppDelegate get].keyID)
|
||||
return nil;
|
||||
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses_" ascending:NO]];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
|
||||
query, query, [MPAppDelegate get].activeUser];
|
||||
|
||||
NSError *error = nil;
|
||||
self.siteResults = [[MPAppDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error];
|
||||
if (error)
|
||||
err(@"Couldn't fetch elements: %@", error);
|
||||
|
||||
NSMutableArray *mutableResults = [NSMutableArray arrayWithCapacity:[self.siteResults count] + 1];
|
||||
if (self.siteResults)
|
||||
for (MPElementEntity *element in self.siteResults)
|
||||
[mutableResults addObject:element.name];
|
||||
// [mutableResults addObject:query]; // For when the app should be able to create new sites.
|
||||
return mutableResults;
|
||||
}
|
||||
|
||||
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
|
||||
|
||||
if (commandSelector == @selector(cancel:)) {
|
||||
[self.window close];
|
||||
return YES;
|
||||
}
|
||||
if (commandSelector == @selector(insertNewline:) && [self.content length]) {
|
||||
[[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
|
||||
if ([[NSPasteboard generalPasteboard] setString:self.content forType:NSPasteboardTypeString]) {
|
||||
self.tipField.alphaValue = 1;
|
||||
[self.tipField setStringValue:@"Copied!"];
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC));
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^{
|
||||
[NSAnimationContext beginGrouping];
|
||||
[[NSAnimationContext currentContext] setDuration:0.2f];
|
||||
[self.tipField.animator setAlphaValue:0];
|
||||
[NSAnimationContext endGrouping];
|
||||
});
|
||||
|
||||
[[self findElement] use];
|
||||
return YES;
|
||||
} else
|
||||
wrn(@"Couldn't copy password to pasteboard.");
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)controlTextDidEndEditing:(NSNotification *)obj {
|
||||
|
||||
if (obj.object == self.siteField)
|
||||
[self trySite];
|
||||
}
|
||||
|
||||
- (NSString *)content {
|
||||
|
||||
return _content;
|
||||
}
|
||||
|
||||
- (void)setContent:(NSString *)content {
|
||||
|
||||
_content = content;
|
||||
|
||||
NSShadow *shadow = [NSShadow new];
|
||||
shadow.shadowColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.6f];
|
||||
shadow.shadowOffset = NSMakeSize(1.0f, -1.0f);
|
||||
shadow.shadowBlurRadius = 1.2f;
|
||||
|
||||
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
|
||||
paragraph.alignment = NSCenterTextAlignment;
|
||||
|
||||
[self.contentField setAttributedStringValue:
|
||||
[[NSAttributedString alloc] initWithString:_content
|
||||
attributes:[[NSMutableDictionary alloc] initWithObjectsAndKeys:
|
||||
shadow, NSShadowAttributeName,
|
||||
paragraph, NSParagraphStyleAttributeName,
|
||||
nil]]];
|
||||
}
|
||||
|
||||
- (BOOL)trySite {
|
||||
|
||||
MPElementEntity *result = [self findElement];
|
||||
if (!result) {
|
||||
[self setContent:@""];
|
||||
[self.tipField setStringValue:@""];
|
||||
return NO;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
NSString *description = [result.content description];
|
||||
if (!description)
|
||||
description = @"";
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self setContent:description];
|
||||
[self.tipField setStringValue:@"Hit enter to copy the password."];
|
||||
self.tipField.alphaValue = 1;
|
||||
});
|
||||
});
|
||||
|
||||
// For when the app should be able to create new sites.
|
||||
/*
|
||||
else
|
||||
[[MPAppDelegate get].managedObjectContext performBlock:^{
|
||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
|
||||
inManagedObjectContext:[MPAppDelegate get].managedObjectContext];
|
||||
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
|
||||
assert([MPAppDelegate get].keyID);
|
||||
|
||||
element.name = siteName;
|
||||
element.keyID = [MPAppDelegate get].keyID;
|
||||
|
||||
NSString *description = [element.content description];
|
||||
[element use];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self setContent:description];
|
||||
});
|
||||
}];
|
||||
*/
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (MPElementEntity *)findElement {
|
||||
|
||||
for (MPElementEntity *element in self.siteResults)
|
||||
if ([element.name isEqualToString:[self.siteField stringValue]])
|
||||
return element;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
691
MasterPassword/Mac/MPPasswordWindowController.xib
Normal file
691
MasterPassword/Mac/MPPasswordWindowController.xib
Normal file
@@ -0,0 +1,691 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
|
||||
<data>
|
||||
<int key="IBDocument.SystemTarget">1070</int>
|
||||
<string key="IBDocument.SystemVersion">11D50</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">2182</string>
|
||||
<string key="IBDocument.AppKitVersion">1138.32</string>
|
||||
<string key="IBDocument.HIToolboxVersion">568.00</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="NS.object.0">2182</string>
|
||||
</object>
|
||||
<array key="IBDocument.IntegratedClassDependencies">
|
||||
<string>NSTextField</string>
|
||||
<string>NSTextFieldCell</string>
|
||||
<string>NSWindowTemplate</string>
|
||||
<string>NSView</string>
|
||||
<string>IBNSLayoutConstraint</string>
|
||||
<string>NSCustomObject</string>
|
||||
</array>
|
||||
<array key="IBDocument.PluginDependencies">
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
</array>
|
||||
<object class="NSMutableDictionary" key="IBDocument.Metadata">
|
||||
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
|
||||
<integer value="1" key="NS.object.0"/>
|
||||
</object>
|
||||
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
|
||||
<object class="NSCustomObject" id="1001">
|
||||
<string key="NSClassName">MPPasswordWindowController</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="1003">
|
||||
<string key="NSClassName">FirstResponder</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="1004">
|
||||
<string key="NSClassName">NSApplication</string>
|
||||
</object>
|
||||
<object class="NSWindowTemplate" id="45434518">
|
||||
<int key="NSWindowStyleMask">277</int>
|
||||
<int key="NSWindowBacking">2</int>
|
||||
<string key="NSWindowRect">{{600, 530}, {480, 159}}</string>
|
||||
<int key="NSWTFlags">611845120</int>
|
||||
<string key="NSWindowTitle">Master Password</string>
|
||||
<string key="NSWindowClass">NSPanel</string>
|
||||
<nil key="NSViewClass"/>
|
||||
<nil key="NSUserInterfaceItemIdentifier"/>
|
||||
<string key="NSWindowContentMaxSize">{480, 320}</string>
|
||||
<string key="NSWindowContentMinSize">{480, 134}</string>
|
||||
<object class="NSView" key="NSWindowView" id="258451033">
|
||||
<reference key="NSNextResponder"/>
|
||||
<int key="NSvFlags">256</int>
|
||||
<array class="NSMutableArray" key="NSSubviews">
|
||||
<object class="NSTextField" id="291791585">
|
||||
<reference key="NSNextResponder" ref="258451033"/>
|
||||
<int key="NSvFlags">268</int>
|
||||
<string key="NSFrame">{{140, 117}, {200, 22}}</string>
|
||||
<reference key="NSSuperview" ref="258451033"/>
|
||||
<reference key="NSWindow"/>
|
||||
<reference key="NSNextKeyView" ref="226215720"/>
|
||||
<int key="NSViewLayerContentsRedrawPolicy">2</int>
|
||||
<string key="NSReuseIdentifierKey">_NS:9</string>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="213149334">
|
||||
<int key="NSCellFlags">-1804468671</int>
|
||||
<int key="NSCellFlags2">138413120</int>
|
||||
<string key="NSContents"/>
|
||||
<object class="NSFont" key="NSSupport" id="590895625">
|
||||
<string key="NSName">LucidaGrande</string>
|
||||
<double key="NSSize">13</double>
|
||||
<int key="NSfFlags">1044</int>
|
||||
</object>
|
||||
<string key="NSPlaceholderString">Site name</string>
|
||||
<string key="NSCellIdentifier">_NS:9</string>
|
||||
<reference key="NSControlView" ref="291791585"/>
|
||||
<int key="NSTextBezelStyle">1</int>
|
||||
<object class="NSColor" key="NSBackgroundColor">
|
||||
<int key="NSColorSpace">6</int>
|
||||
<string key="NSCatalogName">System</string>
|
||||
<string key="NSColorName">textBackgroundColor</string>
|
||||
<object class="NSColor" key="NSColor" id="370404594">
|
||||
<int key="NSColorSpace">3</int>
|
||||
<bytes key="NSWhite">MQA</bytes>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSColor" key="NSTextColor">
|
||||
<int key="NSColorSpace">6</int>
|
||||
<string key="NSCatalogName">System</string>
|
||||
<string key="NSColorName">textColor</string>
|
||||
<object class="NSColor" key="NSColor">
|
||||
<int key="NSColorSpace">3</int>
|
||||
<bytes key="NSWhite">MAA</bytes>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSTextField" id="226215720">
|
||||
<reference key="NSNextResponder" ref="258451033"/>
|
||||
<int key="NSvFlags">268</int>
|
||||
<string key="NSFrame">{{17, 45}, {446, 64}}</string>
|
||||
<reference key="NSSuperview" ref="258451033"/>
|
||||
<reference key="NSWindow"/>
|
||||
<reference key="NSNextKeyView" ref="1020047619"/>
|
||||
<int key="NSViewLayerContentsRedrawPolicy">2</int>
|
||||
<string key="NSReuseIdentifierKey">_NS:9</string>
|
||||
<string key="NSAntiCompressionPriority">{250, 750}</string>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="254722226">
|
||||
<int key="NSCellFlags">67239424</int>
|
||||
<int key="NSCellFlags2">138412032</int>
|
||||
<string key="NSContents">S3cretP4s$w0rD</string>
|
||||
<object class="NSFont" key="NSSupport">
|
||||
<string key="NSName">Exo-Black</string>
|
||||
<double key="NSSize">48</double>
|
||||
<int key="NSfFlags">16</int>
|
||||
</object>
|
||||
<string key="NSCellIdentifier">_NS:9</string>
|
||||
<reference key="NSControlView" ref="226215720"/>
|
||||
<bool key="NSDrawsBackground">YES</bool>
|
||||
<object class="NSColor" key="NSBackgroundColor" id="245864165">
|
||||
<int key="NSColorSpace">6</int>
|
||||
<string key="NSCatalogName">System</string>
|
||||
<string key="NSColorName">controlColor</string>
|
||||
<object class="NSColor" key="NSColor">
|
||||
<int key="NSColorSpace">3</int>
|
||||
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSColor" key="NSTextColor">
|
||||
<int key="NSColorSpace">1</int>
|
||||
<bytes key="NSRGB">MC40NzQ1MDk4MDM5IDAuODY2NjY2NjY2NyAwLjk4NDMxMzcyNTUAA</bytes>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSTextField" id="1020047619">
|
||||
<reference key="NSNextResponder" ref="258451033"/>
|
||||
<int key="NSvFlags">268</int>
|
||||
<string key="NSFrame">{{17, 20}, {446, 17}}</string>
|
||||
<reference key="NSSuperview" ref="258451033"/>
|
||||
<reference key="NSWindow"/>
|
||||
<int key="NSViewLayerContentsRedrawPolicy">2</int>
|
||||
<string key="NSReuseIdentifierKey">_NS:1505</string>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="691402180">
|
||||
<int key="NSCellFlags">68288064</int>
|
||||
<int key="NSCellFlags2">138413056</int>
|
||||
<string key="NSContents">Hit enter to copy the password.</string>
|
||||
<reference key="NSSupport" ref="590895625"/>
|
||||
<string key="NSCellIdentifier">_NS:1505</string>
|
||||
<reference key="NSControlView" ref="1020047619"/>
|
||||
<reference key="NSBackgroundColor" ref="245864165"/>
|
||||
<object class="NSColor" key="NSTextColor">
|
||||
<int key="NSColorSpace">6</int>
|
||||
<string key="NSCatalogName">System</string>
|
||||
<string key="NSColorName">controlLightHighlightColor</string>
|
||||
<reference key="NSColor" ref="370404594"/>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</array>
|
||||
<string key="NSFrameSize">{480, 159}</string>
|
||||
<reference key="NSSuperview"/>
|
||||
<reference key="NSWindow"/>
|
||||
<reference key="NSNextKeyView" ref="291791585"/>
|
||||
<bool key="NSViewIsLayerTreeHost">YES</bool>
|
||||
<int key="NSViewLayerContentsRedrawPolicy">2</int>
|
||||
<string key="NSReuseIdentifierKey">_NS:21</string>
|
||||
</object>
|
||||
<string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string>
|
||||
<string key="NSMinSize">{480, 150}</string>
|
||||
<string key="NSMaxSize">{480, 336}</string>
|
||||
<bool key="NSWindowIsRestorable">YES</bool>
|
||||
</object>
|
||||
</array>
|
||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||
<array class="NSMutableArray" key="connectionRecords">
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">window</string>
|
||||
<reference key="source" ref="1001"/>
|
||||
<reference key="destination" ref="45434518"/>
|
||||
</object>
|
||||
<int key="connectionID">40</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">siteField</string>
|
||||
<reference key="source" ref="1001"/>
|
||||
<reference key="destination" ref="291791585"/>
|
||||
</object>
|
||||
<int key="connectionID">41</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">contentField</string>
|
||||
<reference key="source" ref="1001"/>
|
||||
<reference key="destination" ref="226215720"/>
|
||||
</object>
|
||||
<int key="connectionID">42</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">tipField</string>
|
||||
<reference key="source" ref="1001"/>
|
||||
<reference key="destination" ref="1020047619"/>
|
||||
</object>
|
||||
<int key="connectionID">95</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">delegate</string>
|
||||
<reference key="source" ref="45434518"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
</object>
|
||||
<int key="connectionID">39</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">initialFirstResponder</string>
|
||||
<reference key="source" ref="45434518"/>
|
||||
<reference key="destination" ref="291791585"/>
|
||||
</object>
|
||||
<int key="connectionID">49</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">delegate</string>
|
||||
<reference key="source" ref="291791585"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
</object>
|
||||
<int key="connectionID">43</int>
|
||||
</object>
|
||||
</array>
|
||||
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||
<array key="orderedObjects">
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">0</int>
|
||||
<array key="object" id="0"/>
|
||||
<reference key="children" ref="1000"/>
|
||||
<nil key="parent"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-2</int>
|
||||
<reference key="object" ref="1001"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">File's Owner</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-1</int>
|
||||
<reference key="object" ref="1003"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">First Responder</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-3</int>
|
||||
<reference key="object" ref="1004"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">Application</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">22</int>
|
||||
<reference key="object" ref="45434518"/>
|
||||
<array class="NSMutableArray" key="children">
|
||||
<reference ref="258451033"/>
|
||||
</array>
|
||||
<reference key="parent" ref="0"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">23</int>
|
||||
<reference key="object" ref="258451033"/>
|
||||
<array class="NSMutableArray" key="children">
|
||||
<object class="IBNSLayoutConstraint" id="490796257">
|
||||
<reference key="firstItem" ref="226215720"/>
|
||||
<int key="firstAttribute">3</int>
|
||||
<int key="relation">0</int>
|
||||
<reference key="secondItem" ref="258451033"/>
|
||||
<int key="secondAttribute">3</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBLayoutConstant" key="constant">
|
||||
<double key="value">50</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">9</int>
|
||||
<float key="scoringTypeFloat">40</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBNSLayoutConstraint" id="751044970">
|
||||
<reference key="firstItem" ref="291791585"/>
|
||||
<int key="firstAttribute">3</int>
|
||||
<int key="relation">1</int>
|
||||
<reference key="secondItem" ref="258451033"/>
|
||||
<int key="secondAttribute">3</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBLayoutConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">9</int>
|
||||
<float key="scoringTypeFloat">40</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBNSLayoutConstraint" id="1014829211">
|
||||
<reference key="firstItem" ref="258451033"/>
|
||||
<int key="firstAttribute">4</int>
|
||||
<int key="relation">1</int>
|
||||
<reference key="secondItem" ref="226215720"/>
|
||||
<int key="secondAttribute">4</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBLayoutConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">9</int>
|
||||
<float key="scoringTypeFloat">40</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBNSLayoutConstraint" id="582018795">
|
||||
<reference key="firstItem" ref="258451033"/>
|
||||
<int key="firstAttribute">6</int>
|
||||
<int key="relation">1</int>
|
||||
<reference key="secondItem" ref="226215720"/>
|
||||
<int key="secondAttribute">6</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBLayoutConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">9</int>
|
||||
<float key="scoringTypeFloat">40</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBNSLayoutConstraint" id="137127436">
|
||||
<reference key="firstItem" ref="226215720"/>
|
||||
<int key="firstAttribute">5</int>
|
||||
<int key="relation">1</int>
|
||||
<reference key="secondItem" ref="258451033"/>
|
||||
<int key="secondAttribute">5</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBLayoutConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">9</int>
|
||||
<float key="scoringTypeFloat">40</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<reference ref="226215720"/>
|
||||
<reference ref="291791585"/>
|
||||
<object class="IBNSLayoutConstraint" id="456428551">
|
||||
<reference key="firstItem" ref="226215720"/>
|
||||
<int key="firstAttribute">5</int>
|
||||
<int key="relation">0</int>
|
||||
<reference key="secondItem" ref="258451033"/>
|
||||
<int key="secondAttribute">5</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBNSLayoutSymbolicConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">8</int>
|
||||
<float key="scoringTypeFloat">29</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<reference ref="1020047619"/>
|
||||
<object class="IBNSLayoutConstraint" id="51508433">
|
||||
<reference key="firstItem" ref="291791585"/>
|
||||
<int key="firstAttribute">9</int>
|
||||
<int key="relation">0</int>
|
||||
<reference key="secondItem" ref="226215720"/>
|
||||
<int key="secondAttribute">9</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBLayoutConstant" key="constant">
|
||||
<double key="value">0.0</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">6</int>
|
||||
<float key="scoringTypeFloat">24</float>
|
||||
<int key="contentType">2</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBNSLayoutConstraint" id="99093936">
|
||||
<reference key="firstItem" ref="258451033"/>
|
||||
<int key="firstAttribute">6</int>
|
||||
<int key="relation">0</int>
|
||||
<reference key="secondItem" ref="1020047619"/>
|
||||
<int key="secondAttribute">6</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBNSLayoutSymbolicConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">8</int>
|
||||
<float key="scoringTypeFloat">29</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBNSLayoutConstraint" id="536265876">
|
||||
<reference key="firstItem" ref="258451033"/>
|
||||
<int key="firstAttribute">4</int>
|
||||
<int key="relation">0</int>
|
||||
<reference key="secondItem" ref="1020047619"/>
|
||||
<int key="secondAttribute">4</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBNSLayoutSymbolicConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">8</int>
|
||||
<float key="scoringTypeFloat">29</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBNSLayoutConstraint" id="1695776">
|
||||
<reference key="firstItem" ref="258451033"/>
|
||||
<int key="firstAttribute">6</int>
|
||||
<int key="relation">0</int>
|
||||
<reference key="secondItem" ref="226215720"/>
|
||||
<int key="secondAttribute">6</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBNSLayoutSymbolicConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">8</int>
|
||||
<float key="scoringTypeFloat">29</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBNSLayoutConstraint" id="400319127">
|
||||
<reference key="firstItem" ref="291791585"/>
|
||||
<int key="firstAttribute">3</int>
|
||||
<int key="relation">0</int>
|
||||
<reference key="secondItem" ref="258451033"/>
|
||||
<int key="secondAttribute">3</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBNSLayoutSymbolicConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">8</int>
|
||||
<float key="scoringTypeFloat">29</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBNSLayoutConstraint" id="105480775">
|
||||
<reference key="firstItem" ref="1020047619"/>
|
||||
<int key="firstAttribute">5</int>
|
||||
<int key="relation">0</int>
|
||||
<reference key="secondItem" ref="258451033"/>
|
||||
<int key="secondAttribute">5</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBNSLayoutSymbolicConstant" key="constant">
|
||||
<double key="value">20</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">8</int>
|
||||
<float key="scoringTypeFloat">29</float>
|
||||
<int key="contentType">3</int>
|
||||
<reference key="containingView" ref="258451033"/>
|
||||
</object>
|
||||
</array>
|
||||
<reference key="parent" ref="45434518"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">26</int>
|
||||
<reference key="object" ref="490796257"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">27</int>
|
||||
<reference key="object" ref="751044970"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">28</int>
|
||||
<reference key="object" ref="1014829211"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">32</int>
|
||||
<reference key="object" ref="582018795"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">33</int>
|
||||
<reference key="object" ref="137127436"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">34</int>
|
||||
<reference key="object" ref="226215720"/>
|
||||
<array class="NSMutableArray" key="children">
|
||||
<reference ref="254722226"/>
|
||||
</array>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">35</int>
|
||||
<reference key="object" ref="291791585"/>
|
||||
<array class="NSMutableArray" key="children">
|
||||
<reference ref="213149334"/>
|
||||
<object class="IBNSLayoutConstraint" id="302161926">
|
||||
<reference key="firstItem" ref="291791585"/>
|
||||
<int key="firstAttribute">7</int>
|
||||
<int key="relation">0</int>
|
||||
<nil key="secondItem"/>
|
||||
<int key="secondAttribute">0</int>
|
||||
<float key="multiplier">1</float>
|
||||
<object class="IBLayoutConstant" key="constant">
|
||||
<double key="value">200</double>
|
||||
</object>
|
||||
<float key="priority">1000</float>
|
||||
<int key="scoringType">3</int>
|
||||
<float key="scoringTypeFloat">9</float>
|
||||
<int key="contentType">1</int>
|
||||
<reference key="containingView" ref="291791585"/>
|
||||
</object>
|
||||
</array>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">37</int>
|
||||
<reference key="object" ref="213149334"/>
|
||||
<reference key="parent" ref="291791585"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">38</int>
|
||||
<reference key="object" ref="254722226"/>
|
||||
<reference key="parent" ref="226215720"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">50</int>
|
||||
<reference key="object" ref="1020047619"/>
|
||||
<array class="NSMutableArray" key="children">
|
||||
<reference ref="691402180"/>
|
||||
</array>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">51</int>
|
||||
<reference key="object" ref="691402180"/>
|
||||
<reference key="parent" ref="1020047619"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">74</int>
|
||||
<reference key="object" ref="456428551"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">85</int>
|
||||
<reference key="object" ref="51508433"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">87</int>
|
||||
<reference key="object" ref="99093936"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">90</int>
|
||||
<reference key="object" ref="302161926"/>
|
||||
<reference key="parent" ref="291791585"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">91</int>
|
||||
<reference key="object" ref="536265876"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">92</int>
|
||||
<reference key="object" ref="1695776"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">93</int>
|
||||
<reference key="object" ref="400319127"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">94</int>
|
||||
<reference key="object" ref="105480775"/>
|
||||
<reference key="parent" ref="258451033"/>
|
||||
</object>
|
||||
</array>
|
||||
</object>
|
||||
<dictionary class="NSMutableDictionary" key="flattenedProperties">
|
||||
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="YES" key="22.IBNSWindowAutoPositionCentersHorizontal"/>
|
||||
<boolean value="YES" key="22.IBNSWindowAutoPositionCentersVertical"/>
|
||||
<string key="22.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="NO" key="22.NSWindowTemplate.visibleAtLaunch"/>
|
||||
<array class="NSMutableArray" key="23.IBNSViewMetadataConstraints">
|
||||
<reference ref="137127436"/>
|
||||
<reference ref="582018795"/>
|
||||
<reference ref="1014829211"/>
|
||||
<reference ref="751044970"/>
|
||||
<reference ref="490796257"/>
|
||||
<reference ref="456428551"/>
|
||||
<reference ref="51508433"/>
|
||||
<reference ref="99093936"/>
|
||||
<reference ref="536265876"/>
|
||||
<reference ref="1695776"/>
|
||||
<reference ref="400319127"/>
|
||||
<reference ref="105480775"/>
|
||||
</array>
|
||||
<string key="23.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="26.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="27.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="28.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="32.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="33.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="NO" key="34.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
|
||||
<string key="34.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<array class="NSMutableArray" key="35.IBNSViewMetadataConstraints">
|
||||
<reference ref="302161926"/>
|
||||
</array>
|
||||
<boolean value="NO" key="35.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
|
||||
<string key="35.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="37.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="38.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<boolean value="NO" key="50.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
|
||||
<string key="50.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="51.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="74.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="85.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="87.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="90.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="91.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="92.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="93.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="94.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
</dictionary>
|
||||
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
|
||||
<nil key="activeLocalization"/>
|
||||
<dictionary class="NSMutableDictionary" key="localizations"/>
|
||||
<nil key="sourceID"/>
|
||||
<int key="maxID">95</int>
|
||||
</object>
|
||||
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">MPPasswordWindowController</string>
|
||||
<string key="superclassName">NSWindowController</string>
|
||||
<dictionary class="NSMutableDictionary" key="outlets">
|
||||
<string key="contentField">NSTextField</string>
|
||||
<string key="siteField">NSTextField</string>
|
||||
<string key="tipField">NSTextField</string>
|
||||
</dictionary>
|
||||
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<object class="IBToOneOutletInfo" key="contentField">
|
||||
<string key="name">contentField</string>
|
||||
<string key="candidateClassName">NSTextField</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo" key="siteField">
|
||||
<string key="name">siteField</string>
|
||||
<string key="candidateClassName">NSTextField</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo" key="tipField">
|
||||
<string key="name">tipField</string>
|
||||
<string key="candidateClassName">NSTextField</string>
|
||||
</object>
|
||||
</dictionary>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">./Classes/MPPasswordWindowController.h</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">NSLayoutConstraint</string>
|
||||
<string key="superclassName">NSObject</string>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">./Classes/NSLayoutConstraint.h</string>
|
||||
</object>
|
||||
</object>
|
||||
</array>
|
||||
</object>
|
||||
<int key="IBDocument.localizationMode">0</int>
|
||||
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||
<bool key="IBDocument.UseAutolayout">YES</bool>
|
||||
</data>
|
||||
</archive>
|
||||
38
MasterPassword/Mac/MasterPassword-Info.plist
Normal file
38
MasterPassword/Mac/MasterPassword-Info.plist
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>Icon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.lyndir.lhunath.${PRODUCT_NAME:rfc1034identifier}.Mac</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>[auto]</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>[auto]</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.productivity</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2012 Lyndir. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
13
MasterPassword/Mac/MasterPassword-Prefix.pch
Normal file
13
MasterPassword/Mac/MasterPassword-Prefix.pch
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Prefix header for all source files of the 'MasterPassword' target in the 'MasterPassword' project
|
||||
//
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#endif
|
||||
|
||||
#import "Pearl-Prefix.pch"
|
||||
|
||||
#import "MPTypes.h"
|
||||
15
MasterPassword/Mac/MasterPassword.entitlements
Normal file
15
MasterPassword/Mac/MasterPassword.entitlements
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
||||
<array>
|
||||
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.Mac</string>
|
||||
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string>
|
||||
</array>
|
||||
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
|
||||
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
29
MasterPassword/Mac/en.lproj/Credits.rtf
Normal file
29
MasterPassword/Mac/en.lproj/Credits.rtf
Normal file
@@ -0,0 +1,29 @@
|
||||
{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\paperw9840\paperh8400
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
|
||||
|
||||
\f0\b\fs24 \cf0 Engineering:
|
||||
\b0 \
|
||||
Some people\
|
||||
\
|
||||
|
||||
\b Human Interface Design:
|
||||
\b0 \
|
||||
Some other people\
|
||||
\
|
||||
|
||||
\b Testing:
|
||||
\b0 \
|
||||
Hopefully not nobody\
|
||||
\
|
||||
|
||||
\b Documentation:
|
||||
\b0 \
|
||||
Whoever\
|
||||
\
|
||||
|
||||
\b With special thanks to:
|
||||
\b0 \
|
||||
Mom\
|
||||
}
|
||||
523
MasterPassword/Mac/en.lproj/MainMenu.xib
Normal file
523
MasterPassword/Mac/en.lproj/MainMenu.xib
Normal file
@@ -0,0 +1,523 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
|
||||
<data>
|
||||
<int key="IBDocument.SystemTarget">1070</int>
|
||||
<string key="IBDocument.SystemVersion">11D50</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">2182</string>
|
||||
<string key="IBDocument.AppKitVersion">1138.32</string>
|
||||
<string key="IBDocument.HIToolboxVersion">568.00</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="NS.object.0">2182</string>
|
||||
</object>
|
||||
<array key="IBDocument.IntegratedClassDependencies">
|
||||
<string>NSMenuItem</string>
|
||||
<string>NSMenu</string>
|
||||
<string>NSCustomObject</string>
|
||||
<string>NSUserDefaultsController</string>
|
||||
</array>
|
||||
<array key="IBDocument.PluginDependencies">
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
</array>
|
||||
<object class="NSMutableDictionary" key="IBDocument.Metadata">
|
||||
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
|
||||
<integer value="1" key="NS.object.0"/>
|
||||
</object>
|
||||
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
|
||||
<object class="NSCustomObject" id="1021">
|
||||
<string key="NSClassName">NSApplication</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="1014">
|
||||
<string key="NSClassName">FirstResponder</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="1050">
|
||||
<string key="NSClassName">NSApplication</string>
|
||||
</object>
|
||||
<object class="NSMenu" id="649796088">
|
||||
<string key="NSTitle">AMainMenu</string>
|
||||
<array class="NSMutableArray" key="NSMenuItems"/>
|
||||
<string key="NSName">_NSMainMenu</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="976324537">
|
||||
<string key="NSClassName">MPAppDelegate</string>
|
||||
</object>
|
||||
<object class="NSUserDefaultsController" id="705910970">
|
||||
<bool key="NSSharedInstance">YES</bool>
|
||||
</object>
|
||||
<object class="NSMenu" id="764588027">
|
||||
<string key="NSTitle"/>
|
||||
<array class="NSMutableArray" key="NSMenuItems">
|
||||
<object class="NSMenuItem" id="846612332">
|
||||
<reference key="NSMenu" ref="764588027"/>
|
||||
<string key="NSTitle">Show</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<object class="NSCustomResource" key="NSOnImage" id="552246001">
|
||||
<string key="NSClassName">NSImage</string>
|
||||
<string key="NSResourceName">NSMenuCheckmark</string>
|
||||
</object>
|
||||
<object class="NSCustomResource" key="NSMixedImage" id="752047669">
|
||||
<string key="NSClassName">NSImage</string>
|
||||
<string key="NSResourceName">NSMenuMixedState</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="851296005">
|
||||
<reference key="NSMenu" ref="764588027"/>
|
||||
<string key="NSTitle">Preferences</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
<string key="NSAction">submenuAction:</string>
|
||||
<object class="NSMenu" key="NSSubmenu" id="800575174">
|
||||
<string key="NSTitle">Preferences</string>
|
||||
<array class="NSMutableArray" key="NSMenuItems">
|
||||
<object class="NSMenuItem" id="14397049">
|
||||
<reference key="NSMenu" ref="800575174"/>
|
||||
<string key="NSTitle">Use iCloud</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="461686112">
|
||||
<reference key="NSMenu" ref="800575174"/>
|
||||
<bool key="NSIsDisabled">YES</bool>
|
||||
<string key="NSTitle">Synchronize available sites from your iCloud account.</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
<object class="NSAttributedString" key="NSAttributedTitle">
|
||||
<string key="NSString">Synchronize available sites from your iCloud account.</string>
|
||||
<dictionary key="NSAttributes" id="583461090">
|
||||
<object class="NSFont" key="NSFont">
|
||||
<string key="NSName">Helvetica</string>
|
||||
<double key="NSSize">12</double>
|
||||
<int key="NSfFlags">16</int>
|
||||
</object>
|
||||
<object class="NSParagraphStyle" key="NSParagraphStyle">
|
||||
<int key="NSAlignment">4</int>
|
||||
<nil key="NSTabStops"/>
|
||||
</object>
|
||||
</dictionary>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="290760748">
|
||||
<reference key="NSMenu" ref="800575174"/>
|
||||
<string key="NSTitle">Remember Password</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="907921953">
|
||||
<reference key="NSMenu" ref="800575174"/>
|
||||
<bool key="NSIsDisabled">YES</bool>
|
||||
<string key="NSTitle">Remember the password while the application is running.</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
<object class="NSAttributedString" key="NSAttributedTitle">
|
||||
<string key="NSString">Remember the password while the application is running.</string>
|
||||
<reference key="NSAttributes" ref="583461090"/>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="110488020">
|
||||
<reference key="NSMenu" ref="800575174"/>
|
||||
<string key="NSTitle">Save Password</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="123831322">
|
||||
<reference key="NSMenu" ref="800575174"/>
|
||||
<bool key="NSIsDisabled">YES</bool>
|
||||
<string key="NSTitle">Save the password in your keychain so you don't need to enter it again.</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
<object class="NSAttributedString" key="NSAttributedTitle">
|
||||
<string key="NSString">Save the password in your keychain so you don't need to enter it again.</string>
|
||||
<reference key="NSAttributes" ref="583461090"/>
|
||||
</object>
|
||||
</object>
|
||||
</array>
|
||||
<bool key="NSNoAutoenable">YES</bool>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="466252869">
|
||||
<reference key="NSMenu" ref="764588027"/>
|
||||
<bool key="NSIsDisabled">YES</bool>
|
||||
<bool key="NSIsSeparator">YES</bool>
|
||||
<string key="NSTitle"/>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="229948989">
|
||||
<reference key="NSMenu" ref="764588027"/>
|
||||
<bool key="NSIsDisabled">YES</bool>
|
||||
<string key="NSTitle">Lock</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
</object>
|
||||
<object class="NSMenuItem" id="291035877">
|
||||
<reference key="NSMenu" ref="764588027"/>
|
||||
<string key="NSTitle">Quit</string>
|
||||
<string key="NSKeyEquiv"/>
|
||||
<int key="NSMnemonicLoc">2147483647</int>
|
||||
<reference key="NSOnImage" ref="552246001"/>
|
||||
<reference key="NSMixedImage" ref="752047669"/>
|
||||
</object>
|
||||
</array>
|
||||
<bool key="NSNoAutoenable">YES</bool>
|
||||
</object>
|
||||
</array>
|
||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||
<array class="NSMutableArray" key="connectionRecords">
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">terminate:</string>
|
||||
<reference key="source" ref="1050"/>
|
||||
<reference key="destination" ref="291035877"/>
|
||||
</object>
|
||||
<int key="connectionID">734</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">delegate</string>
|
||||
<reference key="source" ref="1021"/>
|
||||
<reference key="destination" ref="976324537"/>
|
||||
</object>
|
||||
<int key="connectionID">495</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">lockItem</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="229948989"/>
|
||||
</object>
|
||||
<int key="connectionID">726</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">signOut:</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="229948989"/>
|
||||
</object>
|
||||
<int key="connectionID">727</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">showItem</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="846612332"/>
|
||||
</object>
|
||||
<int key="connectionID">730</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">statusMenu</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="764588027"/>
|
||||
</object>
|
||||
<int key="connectionID">731</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">activate:</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="846612332"/>
|
||||
</object>
|
||||
<int key="connectionID">736</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">useICloudItem</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="14397049"/>
|
||||
</object>
|
||||
<int key="connectionID">749</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">rememberPasswordItem</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="290760748"/>
|
||||
</object>
|
||||
<int key="connectionID">750</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">savePasswordItem</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="110488020"/>
|
||||
</object>
|
||||
<int key="connectionID">751</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">togglePreference:</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="14397049"/>
|
||||
</object>
|
||||
<int key="connectionID">752</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">togglePreference:</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="290760748"/>
|
||||
</object>
|
||||
<int key="connectionID">753</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">togglePreference:</string>
|
||||
<reference key="source" ref="976324537"/>
|
||||
<reference key="destination" ref="110488020"/>
|
||||
</object>
|
||||
<int key="connectionID">754</int>
|
||||
</object>
|
||||
</array>
|
||||
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||
<array key="orderedObjects">
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">0</int>
|
||||
<array key="object" id="0"/>
|
||||
<reference key="children" ref="1048"/>
|
||||
<nil key="parent"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-2</int>
|
||||
<reference key="object" ref="1021"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">File's Owner</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-1</int>
|
||||
<reference key="object" ref="1014"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">First Responder</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-3</int>
|
||||
<reference key="object" ref="1050"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
<string key="objectName">Application</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">29</int>
|
||||
<reference key="object" ref="649796088"/>
|
||||
<array class="NSMutableArray" key="children"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">494</int>
|
||||
<reference key="object" ref="976324537"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">548</int>
|
||||
<reference key="object" ref="705910970"/>
|
||||
<reference key="parent" ref="0"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">716</int>
|
||||
<reference key="object" ref="764588027"/>
|
||||
<array class="NSMutableArray" key="children">
|
||||
<reference ref="291035877"/>
|
||||
<reference ref="466252869"/>
|
||||
<reference ref="846612332"/>
|
||||
<reference ref="229948989"/>
|
||||
<reference ref="851296005"/>
|
||||
</array>
|
||||
<reference key="parent" ref="0"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">717</int>
|
||||
<reference key="object" ref="291035877"/>
|
||||
<reference key="parent" ref="764588027"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">718</int>
|
||||
<reference key="object" ref="466252869"/>
|
||||
<reference key="parent" ref="764588027"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">719</int>
|
||||
<reference key="object" ref="846612332"/>
|
||||
<reference key="parent" ref="764588027"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">720</int>
|
||||
<reference key="object" ref="229948989"/>
|
||||
<reference key="parent" ref="764588027"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">739</int>
|
||||
<reference key="object" ref="851296005"/>
|
||||
<array class="NSMutableArray" key="children">
|
||||
<reference ref="800575174"/>
|
||||
</array>
|
||||
<reference key="parent" ref="764588027"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">742</int>
|
||||
<reference key="object" ref="800575174"/>
|
||||
<array class="NSMutableArray" key="children">
|
||||
<reference ref="14397049"/>
|
||||
<reference ref="290760748"/>
|
||||
<reference ref="907921953"/>
|
||||
<reference ref="461686112"/>
|
||||
<reference ref="110488020"/>
|
||||
<reference ref="123831322"/>
|
||||
</array>
|
||||
<reference key="parent" ref="851296005"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">743</int>
|
||||
<reference key="object" ref="14397049"/>
|
||||
<reference key="parent" ref="800575174"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">745</int>
|
||||
<reference key="object" ref="907921953"/>
|
||||
<reference key="parent" ref="800575174"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">744</int>
|
||||
<reference key="object" ref="290760748"/>
|
||||
<reference key="parent" ref="800575174"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">746</int>
|
||||
<reference key="object" ref="461686112"/>
|
||||
<reference key="parent" ref="800575174"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">747</int>
|
||||
<reference key="object" ref="110488020"/>
|
||||
<reference key="parent" ref="800575174"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">748</int>
|
||||
<reference key="object" ref="123831322"/>
|
||||
<reference key="parent" ref="800575174"/>
|
||||
</object>
|
||||
</array>
|
||||
</object>
|
||||
<dictionary class="NSMutableDictionary" key="flattenedProperties">
|
||||
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="29.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="494.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="548.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="716.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="717.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="718.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="719.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="720.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="739.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="742.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="743.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="744.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="745.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="746.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="747.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string key="748.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
</dictionary>
|
||||
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
|
||||
<nil key="activeLocalization"/>
|
||||
<dictionary class="NSMutableDictionary" key="localizations"/>
|
||||
<nil key="sourceID"/>
|
||||
<int key="maxID">754</int>
|
||||
</object>
|
||||
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">MPAppDelegate</string>
|
||||
<string key="superclassName">NSObject</string>
|
||||
<dictionary class="NSMutableDictionary" key="actions">
|
||||
<string key="activate:">id</string>
|
||||
<string key="signOut:">id</string>
|
||||
<string key="togglePreference:">NSMenuItem</string>
|
||||
</dictionary>
|
||||
<dictionary class="NSMutableDictionary" key="actionInfosByName">
|
||||
<object class="IBActionInfo" key="activate:">
|
||||
<string key="name">activate:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo" key="signOut:">
|
||||
<string key="name">signOut:</string>
|
||||
<string key="candidateClassName">id</string>
|
||||
</object>
|
||||
<object class="IBActionInfo" key="togglePreference:">
|
||||
<string key="name">togglePreference:</string>
|
||||
<string key="candidateClassName">NSMenuItem</string>
|
||||
</object>
|
||||
</dictionary>
|
||||
<dictionary class="NSMutableDictionary" key="outlets">
|
||||
<string key="lockItem">NSMenuItem</string>
|
||||
<string key="rememberPasswordItem">NSMenuItem</string>
|
||||
<string key="savePasswordItem">NSMenuItem</string>
|
||||
<string key="showItem">NSMenuItem</string>
|
||||
<string key="statusMenu">NSMenu</string>
|
||||
<string key="useICloudItem">NSMenuItem</string>
|
||||
</dictionary>
|
||||
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||
<object class="IBToOneOutletInfo" key="lockItem">
|
||||
<string key="name">lockItem</string>
|
||||
<string key="candidateClassName">NSMenuItem</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo" key="rememberPasswordItem">
|
||||
<string key="name">rememberPasswordItem</string>
|
||||
<string key="candidateClassName">NSMenuItem</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo" key="savePasswordItem">
|
||||
<string key="name">savePasswordItem</string>
|
||||
<string key="candidateClassName">NSMenuItem</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo" key="showItem">
|
||||
<string key="name">showItem</string>
|
||||
<string key="candidateClassName">NSMenuItem</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo" key="statusMenu">
|
||||
<string key="name">statusMenu</string>
|
||||
<string key="candidateClassName">NSMenu</string>
|
||||
</object>
|
||||
<object class="IBToOneOutletInfo" key="useICloudItem">
|
||||
<string key="name">useICloudItem</string>
|
||||
<string key="candidateClassName">NSMenuItem</string>
|
||||
</object>
|
||||
</dictionary>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">./Classes/MPAppDelegate.h</string>
|
||||
</object>
|
||||
</object>
|
||||
</array>
|
||||
</object>
|
||||
<int key="IBDocument.localizationMode">0</int>
|
||||
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||
<integer value="1070" key="NS.object.0"/>
|
||||
</object>
|
||||
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||
<dictionary class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
|
||||
<string key="NSMenuCheckmark">{11, 11}</string>
|
||||
<string key="NSMenuMixedState">{10, 3}</string>
|
||||
</dictionary>
|
||||
<bool key="IBDocument.UseAutolayout">YES</bool>
|
||||
</data>
|
||||
</archive>
|
||||
14
MasterPassword/Mac/main.m
Normal file
14
MasterPassword/Mac/main.m
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// main.m
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 04/03/12.
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
return NSApplicationMain(argc, (const char **)argv);
|
||||
}
|
||||
@@ -1,877 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2177" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||
<dependencies>
|
||||
<development defaultVersion="4200" identifier="xcode"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1173"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Type View Controller - Type-->
|
||||
<scene sceneID="WkW-SR-cr2">
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="jZj-N1-rhF" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<tableViewController id="NKe-nv-566" customClass="MPTypeViewController" sceneMemberID="viewController">
|
||||
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="none" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="btl-G4-V0S">
|
||||
<rect key="frame" x="0.0" y="64" width="320" height="416"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.12549020350000001" green="0.1411764771" blue="0.14901961389999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<view key="tableFooterView" contentMode="scaleToFill" id="aNa-wb-cYK">
|
||||
<rect key="frame" x="0.0" y="588" width="320" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<sections>
|
||||
<tableViewSection id="gUv-FU-O36">
|
||||
<cells>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="204" id="NCB-rX-weq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="204"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="203"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Generated" lineBreakMode="tailTruncation" minimumFontSize="10" id="azh-GA-KA1">
|
||||
<rect key="frame" x="20" y="20" width="280" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans-Bold" family="Gill Sans" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_first.png" id="WXs-ZQ-JuJ">
|
||||
<rect key="frame" x="10" y="160" width="300" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Long Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="QIm-RX-xY7">
|
||||
<rect key="frame" x="20" y="160" width="280" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 14 characters, contains symbols." lineBreakMode="tailTruncation" minimumFontSize="10" id="6iu-aM-lJA">
|
||||
<rect key="frame" x="20" y="183" width="280" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" id="E4a-XS-Qlz">
|
||||
<rect key="frame" x="23" y="49" width="280" height="112"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<string key="text">These types create a safe password for your site.
|
||||
The passwords aren't saved anywhere. This is a major advantage: if you loose your device, your passwords cannot be stolen. You can use any other device to get all your passwords back, no need for restoring backups or any other pains.</string>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="re0-ZT-bnP">
|
||||
<rect key="frame" x="0.0" y="204" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_middle.png" id="EWy-Eb-L5p">
|
||||
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Medium Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="5NA-XX-th4">
|
||||
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 8 characters, contains symbols." lineBreakMode="tailTruncation" minimumFontSize="10" id="MI2-JM-j4b">
|
||||
<rect key="frame" x="20" y="23" width="280" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="4pb-c9-i2D">
|
||||
<rect key="frame" x="0.0" y="248" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_middle.png" id="Jqw-h1-g2X">
|
||||
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Short Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="iqW-mf-H1R">
|
||||
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 4 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="Zi3-26-3iq">
|
||||
<rect key="frame" x="20" y="23" width="280" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="ywW-4w-Wej">
|
||||
<rect key="frame" x="0.0" y="292" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_middle.png" id="Son-EL-Drx">
|
||||
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Basic Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="Pim-A8-aib">
|
||||
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="8 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="CYQ-D8-vNS">
|
||||
<rect key="frame" x="20" y="23" width="280" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="5p5-OD-zpn">
|
||||
<rect key="frame" x="0.0" y="336" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_last.png" id="5nI-rL-CoO">
|
||||
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="PIN" lineBreakMode="tailTruncation" minimumFontSize="10" id="Yu2-5k-AwW">
|
||||
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="4 numbers." lineBreakMode="tailTruncation" minimumFontSize="10" id="5Zm-AH-bAe">
|
||||
<rect key="frame" x="20" y="23" width="280" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection id="Jqk-Jl-vVm">
|
||||
<cells>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="164" id="OK2-fa-fDw">
|
||||
<rect key="frame" x="0.0" y="380" width="320" height="164"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="163"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="User-specified" lineBreakMode="tailTruncation" minimumFontSize="10" id="146-3h-mdD">
|
||||
<rect key="frame" x="20" y="20" width="280" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans-Bold" family="Gill Sans" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" id="Bbk-lL-BeB">
|
||||
<rect key="frame" x="20" y="49" width="280" height="63"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<string key="text">If you need to specify a custom password for your site, use one of these types. The site's password will be encrypted using your master password.</string>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_first.png" id="fxL-ee-UVP">
|
||||
<rect key="frame" x="10" y="120" width="300" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Personal Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="kzc-rQ-74F">
|
||||
<rect key="frame" x="20" y="120" width="280" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="AES-encrypted, iTunes backup, iCloud sync." lineBreakMode="tailTruncation" minimumFontSize="10" id="vNa-Yq-XIJ">
|
||||
<rect key="frame" x="20" y="143" width="280" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="ePk-a9-aNi">
|
||||
<rect key="frame" x="0.0" y="544" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_list_last.png" id="PUy-Qk-CeF">
|
||||
<rect key="frame" x="10" y="0.0" width="300" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<rect key="contentStretch" x="0.10000000000000001" y="0.20000000000000001" width="0.79999999999999982" height="0.59999999999999964"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Device Private Password" lineBreakMode="tailTruncation" minimumFontSize="10" id="2kB-0P-FeR">
|
||||
<rect key="frame" x="20" y="0.0" width="280" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans" family="Gill Sans" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="AES-encrypted, stays on this device only." lineBreakMode="tailTruncation" minimumFontSize="10" id="6S8-9Y-pzj">
|
||||
<rect key="frame" x="20" y="23" width="280" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="NKe-nv-566" id="5yH-j4-f9c"/>
|
||||
<outlet property="delegate" destination="NKe-nv-566" id="O5k-hI-7Tf"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Type" id="rak-Td-wu1"/>
|
||||
</tableViewController>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="996" y="182"/>
|
||||
</scene>
|
||||
<!--Main View Controller - Master Password-->
|
||||
<scene sceneID="U26-Zf-euQ">
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="mK2-p1-3zC" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<customObject id="0QO-2P-OhD" customClass="MPSearchDelegate">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="PQa-Xl-A3x" id="O2f-mW-ab8"/>
|
||||
<outlet property="searchDisplayController" destination="P8c-gf-nN3" id="exk-dS-Ui3"/>
|
||||
<outlet property="searchTipContainer" destination="zOR-Du-qRL" id="lk6-2X-lIb"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<searchDisplayController id="P8c-gf-nN3">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="0QO-2P-OhD" id="gst-Hm-5ja"/>
|
||||
<outlet property="searchBar" destination="qeo-n2-WVh" id="bFO-FC-Xdj"/>
|
||||
<outlet property="searchContentsController" destination="PQa-Xl-A3x" id="iEu-t3-hJY"/>
|
||||
<outlet property="searchResultsDataSource" destination="0QO-2P-OhD" id="XJh-rC-kId"/>
|
||||
<outlet property="searchResultsDelegate" destination="0QO-2P-OhD" id="Bm8-Q3-lLA"/>
|
||||
</connections>
|
||||
</searchDisplayController>
|
||||
<viewController id="PQa-Xl-A3x" customClass="MPMainViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ep0-Hn-5TR">
|
||||
<rect key="frame" x="0.0" y="64" width="320" height="416"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="background.png" id="0hY-LL-ITu">
|
||||
<rect key="frame" x="0.0" y="44" width="320" height="372"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<view contentMode="scaleToFill" id="g9q-4d-ZgJ">
|
||||
<rect key="frame" x="0.0" y="43" width="320" height="175"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleToFill" image="ui_panel_container.png" id="0Yh-Py-lB6">
|
||||
<rect key="frame" x="11" y="20" width="298" height="87"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<rect key="contentStretch" x="0.050000000000000003" y="0.10000000000000001" width="0.89999999999999991" height="0.79999999999999982"/>
|
||||
</imageView>
|
||||
<imageView userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleToFill" image="ui_panel_display.png" id="cw7-HD-Wht">
|
||||
<rect key="frame" x="11" y="20" width="298" height="86"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<rect key="contentStretch" x="0.050000000000000003" y="0.10000000000000001" width="0.89999999999999991" height="0.79999999999999982"/>
|
||||
</imageView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" id="Zji-Vg-VwX">
|
||||
<rect key="frame" x="11" y="20" width="298" height="86"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" size="button"/>
|
||||
<state key="normal">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="copyContent" destination="PQa-Xl-A3x" eventType="touchUpInside" id="9tS-J5-wlx"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="apple.com" lineBreakMode="tailTruncation" minimumFontSize="10" id="gSK-aB-wNI">
|
||||
<rect key="frame" x="25" y="20" width="270" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="12"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="S3cre7^Pa$swcrD" textAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" id="fiX-10-fif">
|
||||
<rect key="frame" x="20" y="46" width="280" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="textColor" red="0.47450980390000003" green="0.86666666670000003" blue="0.98431372549999996" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="26"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="PQa-Xl-A3x" id="qOM-gq-c6g"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" minimumFontSize="10" id="Iuf-np-e9C">
|
||||
<rect key="frame" x="240" y="27" width="40" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<size key="shadowOffset" width="0.0" height="1"/>
|
||||
</label>
|
||||
<button opaque="NO" alpha="0.50000000000000011" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="jec-mu-nPt">
|
||||
<rect key="frame" x="272.5" y="18.5" width="36.5" height="36"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
|
||||
<state key="normal" image="icon_plus.png">
|
||||
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="incrementPasswordCounter" destination="PQa-Xl-A3x" eventType="touchUpInside" id="hMc-kb-yFA"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9FS-fS-xH6">
|
||||
<rect key="frame" x="273" y="18.5" width="36" height="36"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
|
||||
<state key="normal" image="icon_edit.png">
|
||||
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="editPassword" destination="PQa-Xl-A3x" eventType="touchUpInside" id="jZC-Jm-ZZ0"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="Cei-5z-uWE">
|
||||
<rect key="frame" x="10" y="115" width="300" height="46"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="15"/>
|
||||
<size key="titleShadowOffset" width="0.0" height="1"/>
|
||||
<state key="normal" title="Long Password" backgroundImage="ui_button_standard_large.png">
|
||||
<color key="titleColor" cocoaTouchSystemColor="lightTextColor"/>
|
||||
<color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<segue destination="NKe-nv-566" kind="push" identifier="MP_Main_ChooseType" id="vw4-Vd-O6q"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" id="v2m-Gf-pEV">
|
||||
<rect key="frame" x="55" y="5" width="210" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="fdm-NG-GFC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="210" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Tap to set a password." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="ieN-QQ-PyR">
|
||||
<rect key="frame" x="20" y="0.0" width="170" height="40"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="icon_edit.png" id="KEn-n3-qhX">
|
||||
<rect key="frame" x="48" y="6" width="24" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<view clipsSubviews="YES" contentMode="scaleToFill" id="61G-By-qLB">
|
||||
<rect key="frame" x="0.0" y="216" width="320" height="200"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="bottom" image="Square-bottom.png" id="lbm-A0-3PK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="58"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
<webView opaque="NO" contentMode="scaleToFill" id="8FQ-x4-lR9">
|
||||
<rect key="frame" x="0.0" y="1" width="320" height="199"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="PQa-Xl-A3x" id="H1z-xP-1Pm"/>
|
||||
</connections>
|
||||
</webView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/>
|
||||
</view>
|
||||
<view alpha="0.0" contentMode="scaleToFill" id="yRY-qt-gz8">
|
||||
<rect key="frame" x="10" y="230" width="300" height="180"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_alert_black.png" id="TUv-Tl-xTc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="180"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<rect key="contentStretch" x="0.25" y="0.6499999999999998" width="0.64999999999999969" height="0.10000000000000002"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Password Changed!" lineBreakMode="tailTruncation" minimumFontSize="10" id="ZdH-we-KcW">
|
||||
<rect key="frame" x="70" y="20" width="210" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" id="jCg-t7-RuK">
|
||||
<rect key="frame" x="20" y="60" width="260" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<string key="text">The password for apple.com has changed.
|
||||
|
||||
Don't forget to update the site with your new password! Your old password was:
|
||||
L4m3P4sSw0rD</string>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="cX2-vD-vjc">
|
||||
<rect key="frame" x="259" y="5" width="32" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<state key="normal" image="icon_cancel.png">
|
||||
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="closeAlert" destination="PQa-Xl-A3x" eventType="touchUpInside" id="oJt-DU-l38"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<searchBar contentMode="redraw" barStyle="blackOpaque" placeholder="Enter site name" showsSearchResultsButton="YES" id="qeo-n2-WVh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<gestureRecognizers/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="0QO-2P-OhD" id="Maj-Iz-6Hb"/>
|
||||
</connections>
|
||||
</searchBar>
|
||||
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL">
|
||||
<rect key="frame" x="10" y="-25" width="300" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="ORD-Xv-bOQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<rect key="contentStretch" x="0.15000000000000002" y="0.15000000000000002" width="0.69999999999999973" height="0.69999999999999973"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Begin by entering the name of your site." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="21b-bH-lR9">
|
||||
<rect key="frame" x="-20" y="9" width="340" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.14901960784313725" green="0.16470588235294117" blue="0.1803921568627451" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Master Password" id="hB9-S6-KHC">
|
||||
<barButtonItem key="rightBarButtonItem" systemItem="action" id="8Y7-Bf-FHa">
|
||||
<connections>
|
||||
<action selector="action:" destination="PQa-Xl-A3x" id="mhz-Ey-IOC"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="alertBody" destination="jCg-t7-RuK" id="Aru-Nr-DY0"/>
|
||||
<outlet property="alertContainer" destination="yRY-qt-gz8" id="gZx-90-Zgr"/>
|
||||
<outlet property="alertTitle" destination="ZdH-we-KcW" id="VWi-aS-HRw"/>
|
||||
<outlet property="contentContainer" destination="g9q-4d-ZgJ" id="Hgw-Hp-FHB"/>
|
||||
<outlet property="contentField" destination="fiX-10-fif" id="b2c-gv-qMp"/>
|
||||
<outlet property="contentTipBody" destination="ieN-QQ-PyR" id="flJ-gF-wbI"/>
|
||||
<outlet property="contentTipContainer" destination="v2m-Gf-pEV" id="9zL-GR-kyH"/>
|
||||
<outlet property="contentTipEditIcon" destination="KEn-n3-qhX" id="dDG-Nt-OLM"/>
|
||||
<outlet property="helpContainer" destination="61G-By-qLB" id="Jwt-0d-ZAV"/>
|
||||
<outlet property="helpView" destination="8FQ-x4-lR9" id="MOg-5s-kvK"/>
|
||||
<outlet property="passwordCounter" destination="Iuf-np-e9C" id="CIm-Mk-nJh"/>
|
||||
<outlet property="passwordEdit" destination="9FS-fS-xH6" id="YeB-HF-ZPk"/>
|
||||
<outlet property="passwordIncrementer" destination="jec-mu-nPt" id="i9B-lX-zzX"/>
|
||||
<outlet property="searchDisplayController" destination="P8c-gf-nN3" id="CLs-YI-7NC"/>
|
||||
<outlet property="searchResultsController" destination="0QO-2P-OhD" id="xEC-gV-lHp"/>
|
||||
<outlet property="searchTipContainer" destination="zOR-Du-qRL" id="X7h-Vh-iCE"/>
|
||||
<outlet property="siteName" destination="gSK-aB-wNI" id="IIe-z8-zy8"/>
|
||||
<outlet property="typeButton" destination="Cei-5z-uWE" id="4M1-d7-5Bh"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="455" y="182"/>
|
||||
</scene>
|
||||
<!--Guide View Controller-->
|
||||
<scene sceneID="Rt1-b4-sUB">
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="7yf-G7-kVy" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<viewController id="qz3-eG-aEi" customClass="MPGuideViewController" sceneMemberID="viewController">
|
||||
<scrollView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" directionalLockEnabled="YES" pagingEnabled="YES" showsVerticalScrollIndicator="NO" id="Nr4-hi-8b2">
|
||||
<rect key="frame" x="0.0" y="20" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_0.png" id="eJP-cS-VRU">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_1.png" id="1J9-z9-h96">
|
||||
<rect key="frame" x="320" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_2.png" id="8kf-Wm-F3L">
|
||||
<rect key="frame" x="640" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_3.png" id="H5W-P4-o7O">
|
||||
<rect key="frame" x="960" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_4.png" id="IXz-T9-Umi">
|
||||
<rect key="frame" x="1280" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_5.png" id="Hd0-Or-naq">
|
||||
<rect key="frame" x="1280" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_6.png" id="1jq-Ic-GVZ">
|
||||
<rect key="frame" x="1600" y="0.0" width="320" height="460"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="q93-J1-auj">
|
||||
<rect key="frame" x="1610" y="394" width="300" height="46"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<size key="titleShadowOffset" width="0.0" height="1"/>
|
||||
<state key="normal" title="Get started" backgroundImage="ui_button_green_large.png">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="titleShadowColor" red="0.194560533886145" green="0.30086678832116787" blue="0.1263640535337863" alpha="1" colorSpace="deviceRGB"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="close" destination="qz3-eG-aEi" eventType="touchUpInside" id="uYR-EJ-U6H"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</scrollView>
|
||||
<connections>
|
||||
<outlet property="scrollView" destination="Nr4-hi-8b2" id="8QD-sv-vu6"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="455" y="785"/>
|
||||
</scene>
|
||||
<!--Unlock View Controller-->
|
||||
<scene sceneID="HkH-JR-Fhy">
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="OGA-5j-IcQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<viewController storyboardIdentifier="MPUnlockViewController" modalTransitionStyle="flipHorizontal" id="Nbn-Rv-sP1" customClass="MPUnlockViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="PHH-XC-9QQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="top" image="background.png" id="QWe-Gw-rD3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</imageView>
|
||||
<view contentMode="scaleToFill" id="OP6-72-eij">
|
||||
<rect key="frame" x="0.0" y="157" width="320" height="130"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="center" image="lock_idle.png" id="tyv-qJ-bKR">
|
||||
<rect key="frame" x="110" y="15" width="100" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
<imageView userInteractionEnabled="NO" alpha="0.0" contentMode="center" image="lock_idle.png" id="Lpf-KA-3CV">
|
||||
<rect key="frame" x="110" y="15" width="100" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_spinner.png" id="27q-lX-0vy">
|
||||
<rect key="frame" x="122" y="28" width="75" height="75"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Checking password..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="oU9-lf-nnJ">
|
||||
<rect key="frame" x="20" y="115" width="280" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="13"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="ui_textfield.png" id="ivR-Xl-NrT">
|
||||
<rect key="frame" x="20" y="89" width="280" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<rect key="contentStretch" x="0.25" y="0.25" width="0.49999999999999961" height="0.49999999999999961"/>
|
||||
</imageView>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="center" clearsOnBeginEditing="YES" minimumFontSize="17" id="rTR-7Q-X8o">
|
||||
<rect key="frame" x="20" y="89" width="280" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="36"/>
|
||||
<textInputTraits key="textInputTraits" secureTextEntry="YES"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Nbn-Rv-sP1" id="Y0T-cI-gF1"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter your master password:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="RhX-bA-EhC">
|
||||
<rect key="frame" x="32" y="61" width="256" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" id="Wu7-Mg-9SD">
|
||||
<rect key="frame" x="0.0" y="391" width="320" height="89"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Trying to log in with a different master password?" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="vnS-n6-NZI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="11"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="wad-V1-K3f">
|
||||
<rect key="frame" x="10" y="23" width="300" height="46"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<size key="titleShadowOffset" width="0.0" height="1"/>
|
||||
<state key="normal" title="Change master password" backgroundImage="ui_button_standard_large.png">
|
||||
<color key="titleColor" cocoaTouchSystemColor="lightTextColor"/>
|
||||
<color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="changeMP" destination="Nbn-Rv-sP1" eventType="touchUpInside" id="7RI-hu-SiS"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<connections>
|
||||
<outlet property="changeMPView" destination="Wu7-Mg-9SD" id="84H-HT-5ml"/>
|
||||
<outlet property="field" destination="rTR-7Q-X8o" id="DHg-gg-MVD"/>
|
||||
<outlet property="lock" destination="Lpf-KA-3CV" id="6cE-2g-4XQ"/>
|
||||
<outlet property="messageLabel" destination="oU9-lf-nnJ" id="Ahc-hl-KnJ"/>
|
||||
<outlet property="spinner" destination="27q-lX-0vy" id="jUx-GK-Lgf"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="455" y="1425"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="8r0-wA-Zre">
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Lcz-JH-B5B" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<navigationController id="KZF-fe-y9n" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackTranslucent"/>
|
||||
<navigationBar key="navigationBar" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" barStyle="blackOpaque" id="sif-x3-Fol">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="PQa-Xl-A3x" kind="relationship" relationship="rootViewController" id="LUg-eF-JQd"/>
|
||||
<segue destination="qz3-eG-aEi" kind="modal" identifier="MP_Guide" id="vyG-wN-8hU"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-85" y="182"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="Square-bottom.png" width="551" height="58"/>
|
||||
<image name="background.png" width="480" height="480"/>
|
||||
<image name="guide_page_0.png" width="320" height="480"/>
|
||||
<image name="guide_page_1.png" width="320" height="480"/>
|
||||
<image name="guide_page_2.png" width="320" height="480"/>
|
||||
<image name="guide_page_3.png" width="320" height="480"/>
|
||||
<image name="guide_page_4.png" width="320" height="480"/>
|
||||
<image name="guide_page_5.png" width="320" height="480"/>
|
||||
<image name="guide_page_6.png" width="320" height="480"/>
|
||||
<image name="icon_cancel.png" width="32" height="32"/>
|
||||
<image name="icon_edit.png" width="32" height="32"/>
|
||||
<image name="icon_plus.png" width="32" height="32"/>
|
||||
<image name="lock_idle.png" width="100" height="100"/>
|
||||
<image name="tip_alert_black.png" width="235" height="81"/>
|
||||
<image name="tip_basic_black.png" width="210" height="60"/>
|
||||
<image name="ui_button_green_large.png" width="300" height="46"/>
|
||||
<image name="ui_button_standard_large.png" width="300" height="46"/>
|
||||
<image name="ui_list_first.png" width="300" height="35"/>
|
||||
<image name="ui_list_last.png" width="300" height="35"/>
|
||||
<image name="ui_list_middle.png" width="300" height="34"/>
|
||||
<image name="ui_panel_container.png" width="300" height="87"/>
|
||||
<image name="ui_panel_display.png" width="300" height="86"/>
|
||||
<image name="ui_spinner.png" width="75" height="75"/>
|
||||
<image name="ui_textfield.png" width="158" height="34"/>
|
||||
</resources>
|
||||
<classes>
|
||||
<class className="MPGuideViewController" superclassName="UIViewController">
|
||||
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPGuideViewController.h"/>
|
||||
<relationships>
|
||||
<relationship kind="action" name="close"/>
|
||||
<relationship kind="outlet" name="scrollView" candidateClass="UIScrollView"/>
|
||||
</relationships>
|
||||
</class>
|
||||
<class className="MPMainViewController" superclassName="UIViewController">
|
||||
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPMainViewController.h"/>
|
||||
<relationships>
|
||||
<relationship kind="action" name="action:" candidateClass="UIBarButtonItem"/>
|
||||
<relationship kind="action" name="closeAlert"/>
|
||||
<relationship kind="action" name="copyContent"/>
|
||||
<relationship kind="action" name="editPassword"/>
|
||||
<relationship kind="action" name="incrementPasswordCounter"/>
|
||||
<relationship kind="outlet" name="alertBody" candidateClass="UITextView"/>
|
||||
<relationship kind="outlet" name="alertContainer" candidateClass="UIView"/>
|
||||
<relationship kind="outlet" name="alertTitle" candidateClass="UILabel"/>
|
||||
<relationship kind="outlet" name="contentContainer" candidateClass="UIView"/>
|
||||
<relationship kind="outlet" name="contentField" candidateClass="UITextField"/>
|
||||
<relationship kind="outlet" name="contentTipBody" candidateClass="UILabel"/>
|
||||
<relationship kind="outlet" name="contentTipContainer" candidateClass="UIView"/>
|
||||
<relationship kind="outlet" name="contentTipEditIcon" candidateClass="UIImageView"/>
|
||||
<relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
|
||||
<relationship kind="outlet" name="helpView" candidateClass="UIWebView"/>
|
||||
<relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
|
||||
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
|
||||
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>
|
||||
<relationship kind="outlet" name="searchResultsController" candidateClass="MPSearchDelegate"/>
|
||||
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
|
||||
<relationship kind="outlet" name="siteName" candidateClass="UILabel"/>
|
||||
<relationship kind="outlet" name="typeButton" candidateClass="UIButton"/>
|
||||
</relationships>
|
||||
</class>
|
||||
<class className="MPSearchDelegate" superclassName="NSObject">
|
||||
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPSearchDelegate.h"/>
|
||||
<relationships>
|
||||
<relationship kind="outlet" name="delegate"/>
|
||||
<relationship kind="outlet" name="searchDisplayController" candidateClass="UISearchDisplayController"/>
|
||||
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
|
||||
</relationships>
|
||||
</class>
|
||||
<class className="MPTypeViewController" superclassName="UITableViewController">
|
||||
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPTypeViewController.h"/>
|
||||
</class>
|
||||
<class className="MPUnlockViewController" superclassName="UIViewController">
|
||||
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPUnlockViewController.h"/>
|
||||
<relationships>
|
||||
<relationship kind="action" name="changeMP"/>
|
||||
<relationship kind="outlet" name="changeMPView" candidateClass="UIView"/>
|
||||
<relationship kind="outlet" name="field" candidateClass="UITextField"/>
|
||||
<relationship kind="outlet" name="lock" candidateClass="UIImageView"/>
|
||||
<relationship kind="outlet" name="messageLabel" candidateClass="UILabel"/>
|
||||
<relationship kind="outlet" name="spinner" candidateClass="UIImageView"/>
|
||||
</relationships>
|
||||
</class>
|
||||
</classes>
|
||||
<simulatedMetricsContainer key="defaultSimulatedMetrics">
|
||||
<nil key="statusBar"/>
|
||||
<simulatedOrientationMetrics key="orientation"/>
|
||||
<simulatedScreenMetrics key="destination"/>
|
||||
</simulatedMetricsContainer>
|
||||
</document>
|
||||
@@ -1,24 +0,0 @@
|
||||
//
|
||||
// Prefix header for all source files of the 'MasterPassword' target in the 'MasterPassword' project
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
|
||||
#ifndef __IPHONE_5_0
|
||||
#warning "This project uses features only available in iOS SDK 5.0 and later."
|
||||
#endif
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
#ifndef PRODUCTION
|
||||
#import "TestFlight.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#import "Pearl-Prefix.pch"
|
||||
|
||||
#import "MPTypes.h"
|
||||
#import "MPConfig.h"
|
||||
@@ -1,22 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1170" systemVersion="11D50" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" 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="mpHashHex" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" indexed="YES" syncable="YES"/>
|
||||
<attribute name="type" attributeType="Integer 16" defaultValueString="257" syncable="YES"/>
|
||||
<attribute name="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
|
||||
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="counter" optional="YES" attributeType="Integer 16" defaultValueString="1" syncable="YES"/>
|
||||
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||
</entity>
|
||||
<fetchRequest name="MPElements" entity="MPElementEntity" predicateString="($query == '' OR name BEGINSWITH[cd] $query) AND mpHashHex == $mpHashHex"/>
|
||||
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
|
||||
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
|
||||
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO"/>
|
||||
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
|
||||
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="150"/>
|
||||
</elements>
|
||||
</model>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1023 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user