Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54db3d3199 |
5
.gitmodules
vendored
@@ -1,9 +1,6 @@
|
|||||||
[submodule "External/Pearl"]
|
[submodule "External/Pearl"]
|
||||||
path = External/Pearl
|
path = External/Pearl
|
||||||
url = git://github.com/Lyndir/Pearl.git
|
url = git@github.com:Lyndir/Pearl.git
|
||||||
[submodule "External/InAppSettingsKit"]
|
[submodule "External/InAppSettingsKit"]
|
||||||
path = External/InAppSettingsKit
|
path = External/InAppSettingsKit
|
||||||
url = git://github.com/futuretap/InAppSettingsKit.git
|
url = git://github.com/futuretap/InAppSettingsKit.git
|
||||||
[submodule "External/iCloudStoreManager"]
|
|
||||||
path = External/iCloudStoreManager
|
|
||||||
url = git://github.com/alekseyn/iCloudStoreManager.git
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
Versions/Current/Crashlytics
|
|
||||||
1
Crashlytics/Crashlytics.framework/Headers
vendored
@@ -1 +0,0 @@
|
|||||||
Versions/Current/Headers
|
|
||||||
1
Crashlytics/Crashlytics.framework/Resources
vendored
@@ -1 +0,0 @@
|
|||||||
Versions/Current/Resources
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
//
|
|
||||||
// Crashlytics.h
|
|
||||||
// Crashlytics
|
|
||||||
//
|
|
||||||
// Copyright 2012 Crashlytics, Inc. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
@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;
|
|
||||||
|
|
||||||
@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 specififed a delay in one of the
|
|
||||||
* startWithAPIKey:... calls, this will take at least that long to be invoked.
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<?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.0.1.3</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>????</string>
|
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
|
||||||
<array>
|
|
||||||
<string>iPhoneOS</string>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>0100.01.03</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 +0,0 @@
|
|||||||
../../../run
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/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 +0,0 @@
|
|||||||
A
|
|
||||||
BIN
Crashlytics/Crashlytics.framework/run
vendored
@@ -1,8 +0,0 @@
|
|||||||
<?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
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
Default@2x.png
Normal file
|
After Width: | Height: | Size: 502 KiB |
2
External/InAppSettingsKit
vendored
2
External/Pearl
vendored
@@ -1,19 +1,17 @@
|
|||||||
Thanks for downloading the TestFlight SDK 1.0!
|
Thanks for downloading the TestFlight SDK 0.8.2!
|
||||||
|
|
||||||
This document is also available on the web at https://www.testflightapp.com/sdk/doc
|
This document is also available on the web at https://www.testflightapp.com/sdk/doc
|
||||||
|
|
||||||
1. Why use the TestFlight SDK?
|
1. Why use the TestFlight SDK?
|
||||||
2. Considerations
|
2. Considerations
|
||||||
3. How do I integrate the SDK into my project?
|
3. How do I integrate the SDK into my project?
|
||||||
4. Beta Testing and Release Differentiation
|
4. Using the Checkpoint API
|
||||||
5. Using the Checkpoint API
|
5. Using the Feedback API
|
||||||
6. Using the Feedback API
|
6. Upload your build
|
||||||
7. Upload your build
|
7. Questions API
|
||||||
8. Questions API
|
8. View your results
|
||||||
9. View your results
|
9. Advanced Exception Handling
|
||||||
10. Advanced Exception Handling
|
10. Remote Logging
|
||||||
11. Remote Logging
|
|
||||||
12. iOS 3
|
|
||||||
|
|
||||||
START
|
START
|
||||||
|
|
||||||
@@ -47,7 +45,7 @@ This SDK can be run from both the iPhone Simulator and Device and has been teste
|
|||||||
3. Set Folders to "Create groups for any added folders"
|
3. Set Folders to "Create groups for any added folders"
|
||||||
4. Select all targets that you want to add the SDK to
|
4. Select all targets that you want to add the SDK to
|
||||||
|
|
||||||
2. Verify that libTestFlight.a and has been added to the Link Binary With Libraries Build Phase for the targets you want to use the SDK with
|
2. Verify that libTestFlight.a has been added to the Link Binary With Libraries Build Phase for the targets you want to use the SDK with
|
||||||
|
|
||||||
1. Select your Project in the Project Navigator
|
1. Select your Project in the Project Navigator
|
||||||
2. Select the target you want to enable the SDK for
|
2. Select the target you want to enable the SDK for
|
||||||
@@ -56,26 +54,15 @@ This SDK can be run from both the iPhone Simulator and Device and has been teste
|
|||||||
5. If libTestFlight.a is not listed, drag and drop the library from your Project Navigator to the Link Binary With Libraries area
|
5. If libTestFlight.a is not listed, drag and drop the library from your Project Navigator to the Link Binary With Libraries area
|
||||||
6. Repeat Steps 2 - 5 until all targets you want to use the SDK with have the SDK linked
|
6. Repeat Steps 2 - 5 until all targets you want to use the SDK with have the SDK linked
|
||||||
|
|
||||||
3. Add libz to your Link Binary With Libraries Build Phase
|
3. In your Application Delegate:
|
||||||
|
|
||||||
1. Select your Project in the Project Navigator
|
|
||||||
2. Select the target you want to enable the SDK for
|
|
||||||
3. Select the Build Phases tab
|
|
||||||
4. Open the Link Binary With Libraries Phase
|
|
||||||
5. Click the + to add a new library
|
|
||||||
6. Find libz.dylib in the list and add it
|
|
||||||
7. Repeat Steps 2 - 6 until all targets you want to use the SDK with have libz.dylib
|
|
||||||
|
|
||||||
4. In your Application Delegate:
|
|
||||||
|
|
||||||
1. Import TestFlight: `#import "TestFlight.h"`
|
1. Import TestFlight: `#import "TestFlight.h"`
|
||||||
NOTE: If you do not want to import TestFlight.h in every file you may add the above line into you pre-compiled header (`<projectname>_Prefix.pch`) file inside of the
|
NOTE: If you do not want to import TestFlight.h in every file you may add the above line into you pre-compiled header (`<projectname>_Prefix.pch`) file inside of the
|
||||||
|
|
||||||
#ifdef __OBJC__
|
#ifdef __OBJC__ section.
|
||||||
section.
|
|
||||||
This will give you access to the SDK across all files.
|
This will give you access to the SDK across all files.
|
||||||
|
|
||||||
2. Get your Team Token which you can find at [http://testflightapp.com/dashboard/team/](http://testflightapp.com/dashboard/team/) select the team you are using from the team selection drop down list on the top of the page and then select Team Info.
|
2. Get your Team Token which you can find at [http://testflightapp.com/dashboard/team/](http://testflightapp.com/dashboard/team/) select the team you are using from the team selection drop down list on the top of the page and then select edit.
|
||||||
|
|
||||||
|
|
||||||
3. Launch TestFlight with your Team Token
|
3. Launch TestFlight with your Team Token
|
||||||
@@ -91,32 +78,20 @@ This SDK can be run from both the iPhone Simulator and Device and has been teste
|
|||||||
|
|
||||||
4. To report crashes to you we install our own uncaught exception handler. If you are not currently using an exception handler of your own then all you need to do is go to the next step. If you currently use an Exception Handler, or you use another framework that does please go to the section on advanced exception handling.
|
4. To report crashes to you we install our own uncaught exception handler. If you are not currently using an exception handler of your own then all you need to do is go to the next step. If you currently use an Exception Handler, or you use another framework that does please go to the section on advanced exception handling.
|
||||||
|
|
||||||
5. To enable the best crash reporting possible we recommend setting the following project build settings in Xcode to NO for all targets that you want to have live crash reporting for. You can find build settings by opening the Project Navigator (default command+1 or command+shift+j) then clicking on the project you are configuring (usually the first selection in the list). From there you can choose to either change the global project settings or settings on an individual project basis. All settings below are in the Deployment Section.
|
4. To enable the best crash reporting possible we recommend setting the following project build settings in Xcode to NO for all targets that you want to have live crash reporting for. You can find build settings by opening the Project Navigator (default command+1 or command+shift+j) then clicking on the project you are configuring (usually the first selection in the list). From there you can choose to either change the global project settings or settings on an individual project basis. All settings below are in the Deployment Section.
|
||||||
|
|
||||||
1. Deployment Postrocessing
|
1. Deployment Post Processing
|
||||||
2. Strip Debug Symbols During Copy
|
2. Strip Debug Symbols During Copy
|
||||||
3. Strip Linked Product
|
3. Strip Linked Product
|
||||||
|
|
||||||
|
4. Use the Checkpoint API to create important checkpoints throughout your application.
|
||||||
4. Beta Testing and Release Differentiation
|
|
||||||
|
|
||||||
In order to provide more information about your testers while beta testing you will need to provide the device's unique identifier. This identifier is not something that the SDK will collect from the device and we do not recommend using this in production. Here is the recommended code for providing the device unique identifier.
|
|
||||||
|
|
||||||
#define TESTING 1
|
|
||||||
#ifdef TESTING
|
|
||||||
[TestFlight setDeviceIdentifier:[[UIDevice currentDevice] uniqueIdentifier]];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
This will allow you to have the best possible information during testing, but disable getting and sending of the device unique identifier when you release your application. When it is time to release simply comment out #define TESTING 1. If you decide to not include the device's unique identifier during your testing phase TestFlight will still collect all of the information that you send but it may be anonymized.
|
|
||||||
|
|
||||||
5. Use the Checkpoint API to create important checkpoints throughout your application.
|
|
||||||
|
|
||||||
When a tester does something you care about in your app you can pass a checkpoint. For example completing a level, adding a todo item, etc. The checkpoint progress is used to provide insight into how your testers are testing your apps. The passed checkpoints are also attached to crashes, which can help when creating steps to replicate.
|
When a tester does something you care about in your app you can pass a checkpoint. For example completing a level, adding a todo item, etc. The checkpoint progress is used to provide insight into how your testers are testing your apps. The passed checkpoints are also attached to crashes, which can help when creating steps to replicate.
|
||||||
|
|
||||||
`[TestFlight passCheckpoint:@"CHECKPOINT_NAME"];`
|
`[TestFlight passCheckpoint:@"CHECKPOINT_NAME"];`
|
||||||
Use `+passCheckpoint:` to track when a user performs certain tasks in your application. This can be useful for making sure testers are hitting all parts of your application, as well as tracking which testers are being thorough.
|
Use `passCheckpoint:` to track when a user performs certain tasks in your application. This can be useful for making sure testers are hitting all parts of your application, as well as tracking which testers are being thorough.
|
||||||
|
|
||||||
6. Using the Feedback API
|
5. Using the Feedback API
|
||||||
|
|
||||||
To launch unguided feedback call the `openFeedbackView` method. We recommend that you call this from a GUI element.
|
To launch unguided feedback call the `openFeedbackView` method. We recommend that you call this from a GUI element.
|
||||||
|
|
||||||
@@ -124,22 +99,13 @@ To launch unguided feedback call the `openFeedbackView` method. We recommend tha
|
|||||||
[TestFlight openFeedbackView];
|
[TestFlight openFeedbackView];
|
||||||
}
|
}
|
||||||
|
|
||||||
If you want to create your own feedback form you can use the `submitCustomFeedback` method to submit the feedback that the user has entered.
|
|
||||||
|
|
||||||
-(IBAction)submitFeedbackPressed:(id)sender {
|
|
||||||
NSString *feedback = [self getUserFeedback];
|
|
||||||
[TestFlight submitCustomFeedback:feedback];
|
|
||||||
}
|
|
||||||
|
|
||||||
The above sample assumes that [self getUserFeedback] is implemented such that it obtains the users feedback from the GUI element you have created and that submitFeedbackPressed is the action for your submit button.
|
|
||||||
|
|
||||||
Once users have submitted feedback from inside of the application you can view it in the feedback area of your build page.
|
Once users have submitted feedback from inside of the application you can view it in the feedback area of your build page.
|
||||||
|
|
||||||
7. Upload your build.
|
6. Upload your build.
|
||||||
|
|
||||||
After you have integrated the SDK into your application you need to upload your build to TestFlight. You can upload from your dashboard or or using the Upload API, full documentation at <https://testflightapp.com/api/doc/>
|
After you have integrated the SDK into your application you need to upload your build to TestFlight. You can upload from your dashboard or or using the Upload API, full documentation at <https://testflightapp.com/api/doc/>
|
||||||
|
|
||||||
8. Add Questions to Checkpoints
|
7. Add Questions to Checkpoints
|
||||||
|
|
||||||
In order to ask a question, you'll need to associate it with a checkpoint. Make sure your checkpoints are initialized by running your app and hitting them all yourself before you start adding questions.
|
In order to ask a question, you'll need to associate it with a checkpoint. Make sure your checkpoints are initialized by running your app and hitting them all yourself before you start adding questions.
|
||||||
|
|
||||||
@@ -151,11 +117,11 @@ After restarting your application on an approved device, when you pass the check
|
|||||||
|
|
||||||
After you upload a new build to TestFlight you will need to associate questions once again. However if your checkpoints and questions have remained the same you can choose "copy questions from an older build" and choose which build to copy the questions from.
|
After you upload a new build to TestFlight you will need to associate questions once again. However if your checkpoints and questions have remained the same you can choose "copy questions from an older build" and choose which build to copy the questions from.
|
||||||
|
|
||||||
9. View your results.
|
8. View your results.
|
||||||
|
|
||||||
As testers install your build and start to test it you will see their session data on the web on the build report page for the build you've uploaded.
|
As testers install your build and start to test it you will see their session data on the web on the build report page for the build you've uploaded.
|
||||||
|
|
||||||
10. Advanced Exception Handling
|
9. Advanced Exception Handling
|
||||||
|
|
||||||
An uncaught exception means that your application is in an unknown state and there is not much that you can do but try and exit gracefully. Our SDK does its best to get the data we collect in this situation to you while it is crashing, but it is designed in such a way that the important act of saving the data occurs in as safe way a way as possible before trying to send anything. If you do use uncaught exception or signal handlers install your handlers before calling `takeOff`. Our SDK will then call your handler while ours is running. For example:
|
An uncaught exception means that your application is in an unknown state and there is not much that you can do but try and exit gracefully. Our SDK does its best to get the data we collect in this situation to you while it is crashing, but it is designed in such a way that the important act of saving the data occurs in as safe way a way as possible before trying to send anything. If you do use uncaught exception or signal handlers install your handlers before calling `takeOff`. Our SDK will then call your handler while ours is running. For example:
|
||||||
|
|
||||||
@@ -195,7 +161,7 @@ An uncaught exception means that your application is in an unknown state and the
|
|||||||
|
|
||||||
You do not need to add the above code if your application does not use exception handling already.
|
You do not need to add the above code if your application does not use exception handling already.
|
||||||
|
|
||||||
11. Remote Logging
|
10. Remote Logging
|
||||||
|
|
||||||
To perform remote logging you can use the TFLog method which logs in a few different methods described below. In order to make the transition from NSLog to TFLog easy we have used the same method signature for TFLog as NSLog. You can easily switch over to TFLog by adding the following macro to your header
|
To perform remote logging you can use the TFLog method which logs in a few different methods described below. In order to make the transition from NSLog to TFLog easy we have used the same method signature for TFLog as NSLog. You can easily switch over to TFLog by adding the following macro to your header
|
||||||
|
|
||||||
@@ -217,26 +183,11 @@ We have implemented three different loggers.
|
|||||||
|
|
||||||
Each of the loggers log asynchronously and all TFLog calls are non blocking. The TestFlight logger writes its data to a file which is then sent to our servers on Session End events. The Apple System Logger sends its messages to the Apple System Log and are viewable using the Organizer in Xcode when the device is attached to your computer. The ASL logger can be disabled by turning it off in your TestFlight options
|
Each of the loggers log asynchronously and all TFLog calls are non blocking. The TestFlight logger writes its data to a file which is then sent to our servers on Session End events. The Apple System Logger sends its messages to the Apple System Log and are viewable using the Organizer in Xcode when the device is attached to your computer. The ASL logger can be disabled by turning it off in your TestFlight options
|
||||||
|
|
||||||
[TestFlight setOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:@"logToConsole"]];
|
[TestFlight setOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:@"logsToConsole"]];
|
||||||
|
|
||||||
The default option is YES.
|
The default option is YES.
|
||||||
|
|
||||||
The STDERR logger sends log messages to STDERR so that you can see your log statements while debugging. The STDERR logger is only active when a debugger is attached to your application. If you do not wish to use the STDERR logger you can disable it by turning it off in your TestFlight options
|
The STDERR logger sends log messages to STDERR so that you can see your log statements while debugging. The STDERR logger is only active when a debugger is attached to your application.
|
||||||
|
|
||||||
[TestFlight setOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:@"logToSTDERR"]];
|
|
||||||
|
|
||||||
The default option is YES.
|
|
||||||
|
|
||||||
12. iOS3
|
|
||||||
|
|
||||||
We now require that anyone who is writing an application that supports iOS3 add the System.framework as an optional link. In order to provide a better shutdown experience we send any large log files to our servers in the background. To add System.framework as an optional link:
|
|
||||||
1. Select your Project in the Project Navigator
|
|
||||||
2. Select the target you want to enable the SDK for
|
|
||||||
3. Select the Build Phases tab
|
|
||||||
4. Open the Link Binary With Libraries Phase
|
|
||||||
5. Click the + to add a new library
|
|
||||||
6. Find libSystem.dylib in the list and add it
|
|
||||||
7. To the right of libSystem.dylib in the Link Binary With Libraries pane change "Required" to "Optional"
|
|
||||||
|
|
||||||
END
|
END
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
// Copyright 2011 TestFlight. All rights reserved.
|
// Copyright 2011 TestFlight. All rights reserved.
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#define TESTFLIGHT_SDK_VERSION @"1.0"
|
#define TESTFLIGHT_SDK_VERSION @"0.8.2"
|
||||||
#undef TFLog
|
#undef TFLog
|
||||||
|
|
||||||
#if __cplusplus
|
#if __cplusplus
|
||||||
@@ -51,8 +51,6 @@ extern "C" {
|
|||||||
* library installs crash handlers overtop of the TestFlight Crash Handlers
|
* library installs crash handlers overtop of the TestFlight Crash Handlers
|
||||||
* logToConsole [ NSNumber numberWithBool:YES ] YES - default, sends log statements to Apple System Log and TestFlight log
|
* logToConsole [ NSNumber numberWithBool:YES ] YES - default, sends log statements to Apple System Log and TestFlight log
|
||||||
* NO - sends log statements to TestFlight log only
|
* NO - sends log statements to TestFlight log only
|
||||||
* logToSTDERR [ NSNumber numberWithBool:YES ] YES - default, sends log statements to STDERR when debugger is attached
|
|
||||||
* NO - sends log statements to TestFlight log only
|
|
||||||
* sendLogOnlyOnCrash [ NSNumber numberWithBool:YES ] NO - default, sends logs to TestFlight at the end of every session
|
* sendLogOnlyOnCrash [ NSNumber numberWithBool:YES ] NO - default, sends logs to TestFlight at the end of every session
|
||||||
* YES - sends logs statements to TestFlight only if there was a crash
|
* YES - sends logs statements to TestFlight only if there was a crash
|
||||||
*/
|
*/
|
||||||
@@ -70,27 +68,4 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
+ (void)openFeedbackView;
|
+ (void)openFeedbackView;
|
||||||
|
|
||||||
/**
|
|
||||||
* Submits custom feedback to the site. Sends the data in feedback to the site. This is to be used as the method to submit
|
|
||||||
* feedback from custom feedback forms.
|
|
||||||
*
|
|
||||||
* @param feedback Your users feedback, method does nothing if feedback is nil
|
|
||||||
*/
|
|
||||||
+ (void)submitFeedback:(NSString*)feedback;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Device Identifier.
|
|
||||||
* The SDK no longer obtains the device unique identifier. This method should only be used during testing so that you can
|
|
||||||
* identify a testers test data with them. If you do not provide the identifier you will still see all session data, with checkpoints
|
|
||||||
* and logs, but the data will be anonymized.
|
|
||||||
* It is recommended that you only use this method during testing. We also recommended that you wrap this method with a pre-processor
|
|
||||||
* directive that is only active for non-app store builds.
|
|
||||||
* #ifndef RELEASE
|
|
||||||
* [TestFlight setDeviceIdentifier:[[UIDevice currentDevice] uniqueIdentifier]];
|
|
||||||
* #endif
|
|
||||||
*
|
|
||||||
* @param deviceIdentifier The current devices device identifier
|
|
||||||
*/
|
|
||||||
+ (void)setDeviceIdentifier:(NSString*)deviceIdentifer;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -1,48 +1,3 @@
|
|||||||
1.0 - March 29, 2012
|
|
||||||
Resolved occurrences of exceptions with the message "No background task exists with identifier 0"
|
|
||||||
|
|
||||||
1.0 BETA 1 - March 23, 2012
|
|
||||||
Privacy Updates
|
|
||||||
UDID is no longer collected by the SDK. During testing please use [TestFlight setDeviceIdentifier:[[UIDevice currentDevice] uniqueIdentifier]]; to send the UDID so you can identify your testers. For release do not set +setDeviceIdentifier. See Beta Testing and Release Differentiation in the README or online at https://testflightapp.com/sdk/doc/1.0beta1/
|
|
||||||
|
|
||||||
0.8.3 - February 14, 2012
|
|
||||||
Rolled previous beta code into release builds
|
|
||||||
No longer allow in application updates to occur in applications that were obtained from the app store.
|
|
||||||
|
|
||||||
Tested compiled library with:
|
|
||||||
Xcode 4.3
|
|
||||||
Xcode 4.2
|
|
||||||
Xcode 4.1
|
|
||||||
Xcode 3.2.6
|
|
||||||
|
|
||||||
0.8.3 BETA 5 - February 10, 2012
|
|
||||||
Changed logging from asynchronous to synchronous.
|
|
||||||
Resolved crash when looking for a log path failed.
|
|
||||||
Added submitFeedback to the TestFlight class to allow for custom feedback forms.
|
|
||||||
|
|
||||||
0.8.3 BETA 4 - January 20, 2012
|
|
||||||
Resolved an issue that occured when an application was upgraded from 0.8.3 BETA 1 to 0.8.3 BETA 3+ with unsent data from 0.8.3 BETA 1
|
|
||||||
|
|
||||||
0.8.3 BETA 3 - January 19, 2012
|
|
||||||
On crash log files over 64k will not be sent until next launch.
|
|
||||||
|
|
||||||
Known Issues:
|
|
||||||
Logging massive amounts of data at the end of a session may prevent the application from launching in time on next launch
|
|
||||||
|
|
||||||
0.8.3 BETA 2 - January 13, 2012
|
|
||||||
libz.dylib is now required to be added to your "Link Binary with Libraries" build phase
|
|
||||||
Log file compression, The compression is done on an as needed basis rather than before sending
|
|
||||||
Changed all outgoing data from JSON to MessagePack
|
|
||||||
Added option logToSTDERR to disable the STDERR logger
|
|
||||||
|
|
||||||
0.8.3 BETA 1 - December 29, 2011
|
|
||||||
In rare occurrences old session data that had not been sent to our server may have been discarded or attached to the wrong build. It is now no longer discarded
|
|
||||||
Made sending of Session End events more robust
|
|
||||||
Network queuing system does better bursting of unsent data
|
|
||||||
Log files that are larger than 64K are now sent sometime after the next launch
|
|
||||||
Log files that are larger than 16MB are no longer supported and will be replaced with a message indicating the log file was too large
|
|
||||||
Fixed crashes while resuming from background
|
|
||||||
|
|
||||||
0.8.2 - December 20, 2011
|
0.8.2 - December 20, 2011
|
||||||
Promoted 0.8.2 BETA 4 to stable
|
Promoted 0.8.2 BETA 4 to stable
|
||||||
|
|
||||||
@@ -50,7 +5,7 @@ Known Issues:
|
|||||||
Under some circumstances Session End events may not be sent until the next launch.
|
Under some circumstances Session End events may not be sent until the next launch.
|
||||||
With large log files Session End events may take a long time to show up.
|
With large log files Session End events may take a long time to show up.
|
||||||
|
|
||||||
Tested compiled library with:
|
Tested compiled library with
|
||||||
Xcode 4.3
|
Xcode 4.3
|
||||||
Xcode 4.2
|
Xcode 4.2
|
||||||
Xcode 4.1
|
Xcode 4.1
|
||||||
@@ -58,10 +13,11 @@ Xcode 3.2.6
|
|||||||
|
|
||||||
0.8.2 BETA 4 - December 12, 2011
|
0.8.2 BETA 4 - December 12, 2011
|
||||||
Prevented "The string argument is NULL" from occuring during finishedHandshake in rare cases
|
Prevented "The string argument is NULL" from occuring during finishedHandshake in rare cases
|
||||||
Resolved issue where data recorded while offline may not be sent
|
Resolved issue where background data may not be sent
|
||||||
|
|
||||||
0.8.2 BETA 3 - December 8, 2011
|
0.8.2 BETA 3 - December 8, 2011
|
||||||
Added auto-release pools to background setup and tear down
|
Added auto-release pools to background setup and tear down
|
||||||
|
Added C++ specific code to TFLog declaration
|
||||||
|
|
||||||
0.8.2 BETA 2 - December 5, 2011
|
0.8.2 BETA 2 - December 5, 2011
|
||||||
Fixed the "pointer being freed was not allocated" bug
|
Fixed the "pointer being freed was not allocated" bug
|
||||||
@@ -72,7 +28,7 @@ Fixed an issue where Session End events may not be sent until next launch
|
|||||||
Fixed an issue where duplicate events could be sent
|
Fixed an issue where duplicate events could be sent
|
||||||
Fixed an issue with Session End events not being sent from some iPod touch models
|
Fixed an issue with Session End events not being sent from some iPod touch models
|
||||||
|
|
||||||
Tested compiled library with:
|
Tested compiled library with
|
||||||
Xcode 4.2
|
Xcode 4.2
|
||||||
Xcode 4.1
|
Xcode 4.1
|
||||||
Xcode 3.2.6
|
Xcode 3.2.6
|
||||||
@@ -90,7 +46,7 @@ Fixed compability issues with iOS 3
|
|||||||
Added calling into the rootViewController shouldAutorotateToInterfaceOrientation if a rootViewController is set
|
Added calling into the rootViewController shouldAutorotateToInterfaceOrientation if a rootViewController is set
|
||||||
Made the comments in TestFlight.h compatible with Appledoc
|
Made the comments in TestFlight.h compatible with Appledoc
|
||||||
|
|
||||||
Tested compiled library with:
|
Tested compiled library with
|
||||||
Xcode 4.2
|
Xcode 4.2
|
||||||
Xcode 4.1
|
Xcode 4.1
|
||||||
Xcode 3.2
|
Xcode 3.2
|
||||||
1
External/iCloudStoreManager
vendored
@@ -1,10 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
//
|
|
||||||
// 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
|
|
||||||
@@ -1,743 +0,0 @@
|
|||||||
//
|
|
||||||
// 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
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
// 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.
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "self:MasterPassword-iOS.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?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>
|
|
||||||
3750
MasterPassword.xcodeproj/project.pbxproj
Normal file
@@ -2,6 +2,6 @@
|
|||||||
<Workspace
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "self:MasterPassword-Mac.xcodeproj">
|
location = "self:MasterPassword.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "NO"
|
buildForTesting = "YES"
|
||||||
buildForRunning = "YES"
|
buildForRunning = "YES"
|
||||||
buildForProfiling = "YES"
|
buildForProfiling = "YES"
|
||||||
buildForArchiving = "YES"
|
buildForArchiving = "YES"
|
||||||
@@ -16,16 +16,16 @@
|
|||||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||||
BuildableName = "MasterPassword.app"
|
BuildableName = "MasterPassword.app"
|
||||||
BlueprintName = "MasterPassword"
|
BlueprintName = "MasterPassword"
|
||||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Production">
|
||||||
<Testables>
|
<Testables>
|
||||||
</Testables>
|
</Testables>
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
@@ -34,17 +34,16 @@
|
|||||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||||
BuildableName = "MasterPassword.app"
|
BuildableName = "MasterPassword.app"
|
||||||
BlueprintName = "MasterPassword"
|
BlueprintName = "MasterPassword"
|
||||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
buildConfiguration = "AppStore"
|
buildConfiguration = "Production"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<BuildableProductRunnable>
|
<BuildableProductRunnable>
|
||||||
@@ -53,7 +52,7 @@
|
|||||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||||
BuildableName = "MasterPassword.app"
|
BuildableName = "MasterPassword.app"
|
||||||
BlueprintName = "MasterPassword"
|
BlueprintName = "MasterPassword"
|
||||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
<AdditionalOptions>
|
<AdditionalOptions>
|
||||||
@@ -65,9 +64,18 @@
|
|||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
buildConfiguration = "Production"
|
buildConfiguration = "Production"
|
||||||
debugDocumentVersioning = "YES">
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||||
|
BuildableName = "MasterPassword.app"
|
||||||
|
BlueprintName = "MasterPassword"
|
||||||
|
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Production">
|
||||||
</AnalyzeAction>
|
</AnalyzeAction>
|
||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
buildConfiguration = "Production"
|
buildConfiguration = "Production"
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0430"
|
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "NO"
|
buildForTesting = "YES"
|
||||||
buildForRunning = "YES"
|
buildForRunning = "YES"
|
||||||
buildForProfiling = "YES"
|
buildForProfiling = "YES"
|
||||||
buildForArchiving = "YES"
|
buildForArchiving = "YES"
|
||||||
@@ -17,14 +16,14 @@
|
|||||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||||
BuildableName = "MasterPassword.app"
|
BuildableName = "MasterPassword.app"
|
||||||
BlueprintName = "MasterPassword"
|
BlueprintName = "MasterPassword"
|
||||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Debug">
|
||||||
<Testables>
|
<Testables>
|
||||||
@@ -35,7 +34,7 @@
|
|||||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||||
BuildableName = "MasterPassword.app"
|
BuildableName = "MasterPassword.app"
|
||||||
BlueprintName = "MasterPassword"
|
BlueprintName = "MasterPassword"
|
||||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
@@ -45,7 +44,6 @@
|
|||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<BuildableProductRunnable>
|
<BuildableProductRunnable>
|
||||||
@@ -54,28 +52,17 @@
|
|||||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||||
BuildableName = "MasterPassword.app"
|
BuildableName = "MasterPassword.app"
|
||||||
BlueprintName = "MasterPassword"
|
BlueprintName = "MasterPassword"
|
||||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
<CommandLineArguments>
|
|
||||||
<CommandLineArgument
|
|
||||||
argument = "-com.apple.coredata.ubiquity.logLevel 3"
|
|
||||||
isEnabled = "NO">
|
|
||||||
</CommandLineArgument>
|
|
||||||
</CommandLineArguments>
|
|
||||||
<AdditionalOptions>
|
<AdditionalOptions>
|
||||||
<AdditionalOption
|
|
||||||
key = "NSZombieEnabled"
|
|
||||||
value = "YES"
|
|
||||||
isEnabled = "YES">
|
|
||||||
</AdditionalOption>
|
|
||||||
</AdditionalOptions>
|
</AdditionalOptions>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
buildConfiguration = "AdHoc"
|
buildConfiguration = "Release"
|
||||||
debugDocumentVersioning = "YES">
|
debugDocumentVersioning = "YES">
|
||||||
<BuildableProductRunnable>
|
<BuildableProductRunnable>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
@@ -83,7 +70,7 @@
|
|||||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||||
BuildableName = "MasterPassword.app"
|
BuildableName = "MasterPassword.app"
|
||||||
BlueprintName = "MasterPassword"
|
BlueprintName = "MasterPassword"
|
||||||
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
|
ReferencedContainer = "container:MasterPassword.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
@@ -91,7 +78,7 @@
|
|||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Debug">
|
||||||
</AnalyzeAction>
|
</AnalyzeAction>
|
||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
buildConfiguration = "AdHoc"
|
buildConfiguration = "Release"
|
||||||
revealArchiveInOrganizer = "YES">
|
revealArchiveInOrganizer = "YES">
|
||||||
</ArchiveAction>
|
</ArchiveAction>
|
||||||
</Scheme>
|
</Scheme>
|
||||||
BIN
MasterPassword/.OPMainViewController.h.swk
Normal file
BIN
MasterPassword/.OPMainViewController.h.swl
Normal file
BIN
MasterPassword/.OPMainViewController.h.swm
Normal file
BIN
MasterPassword/.OPMainViewController.h.swn
Normal file
BIN
MasterPassword/.OPMainViewController.h.swo
Normal file
BIN
MasterPassword/.OPTypes.m.swn
Normal file
BIN
MasterPassword/.OPTypes.m.swo
Normal file
30
MasterPassword/MPAppDelegate.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// MPAppDelegate.h
|
||||||
|
// MasterPassword
|
||||||
|
//
|
||||||
|
// Created by Maarten Billemont on 24/11/11.
|
||||||
|
// Copyright (c) 2011 Lyndir. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@interface MPAppDelegate : AbstractAppDelegate
|
||||||
|
|
||||||
|
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
|
||||||
|
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
|
||||||
|
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||||
|
@property (strong, nonatomic) NSString *keyPhrase;
|
||||||
|
@property (strong, nonatomic) NSData *keyPhraseHash;
|
||||||
|
@property (strong, nonatomic) NSString *keyPhraseHashHex;
|
||||||
|
|
||||||
|
+ (MPAppDelegate *)get;
|
||||||
|
+ (NSManagedObjectModel *)managedObjectModel;
|
||||||
|
+ (NSManagedObjectContext *)managedObjectContext;
|
||||||
|
|
||||||
|
- (void)saveContext;
|
||||||
|
- (NSURL *)applicationDocumentsDirectory;
|
||||||
|
|
||||||
|
- (void)showGuide;
|
||||||
|
- (void)loadKeyPhrase;
|
||||||
|
|
||||||
|
@end
|
||||||
492
MasterPassword/MPAppDelegate.m
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
//
|
||||||
|
// 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 ()
|
||||||
|
|
||||||
|
+ (NSDictionary *)keyPhraseQuery;
|
||||||
|
+ (NSDictionary *)keyPhraseHashQuery;
|
||||||
|
|
||||||
|
- (void)forgetKeyPhrase;
|
||||||
|
- (void)loadStoredKeyPhrase;
|
||||||
|
- (void)askKeyPhrase;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation MPAppDelegate
|
||||||
|
|
||||||
|
@synthesize managedObjectModel = __managedObjectModel;
|
||||||
|
@synthesize managedObjectContext = __managedObjectContext;
|
||||||
|
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator;
|
||||||
|
|
||||||
|
@synthesize keyPhrase = _keyPhrase;
|
||||||
|
@synthesize keyPhraseHash = _keyPhraseHash;
|
||||||
|
@synthesize keyPhraseHashHex = _keyPhraseHashHex;
|
||||||
|
|
||||||
|
+ (void)initialize {
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
[Logger get].autoprintLevel = LogLevelTrace;
|
||||||
|
[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSDictionary *)keyPhraseQuery {
|
||||||
|
|
||||||
|
static NSDictionary *MPKeyPhraseQuery = nil;
|
||||||
|
if (!MPKeyPhraseQuery)
|
||||||
|
MPKeyPhraseQuery = [KeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
|
attributes:[NSDictionary dictionaryWithObject:@"MasterPassword"
|
||||||
|
forKey:(__bridge id)kSecAttrService]
|
||||||
|
matches:nil];
|
||||||
|
|
||||||
|
return MPKeyPhraseQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSDictionary *)keyPhraseHashQuery {
|
||||||
|
|
||||||
|
static NSDictionary *MPKeyPhraseHashQuery = nil;
|
||||||
|
if (!MPKeyPhraseHashQuery)
|
||||||
|
MPKeyPhraseHashQuery = [KeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
|
attributes:[NSDictionary dictionaryWithObject:@"MasterPasswordHash"
|
||||||
|
forKey:(__bridge id)kSecAttrService]
|
||||||
|
matches:nil];
|
||||||
|
|
||||||
|
return MPKeyPhraseHashQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
|
[TestFlight takeOff:@"bd44885deee7adce0645ce8e5498d80a_NDQ5NDQyMDExLTEyLTAyIDExOjM1OjQ4LjQ2NjM4NA"];
|
||||||
|
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
[NSNumber numberWithBool:YES], @"logToConsole",
|
||||||
|
nil]];
|
||||||
|
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
|
||||||
|
#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];
|
||||||
|
}
|
||||||
|
if ([NSStringFromSelector(@selector(forgetKeyPhrase))
|
||||||
|
isEqualToString:[note.object description]])
|
||||||
|
[self loadKeyPhrase];
|
||||||
|
}];
|
||||||
|
|
||||||
|
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||||
|
}
|
||||||
|
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||||
|
|
||||||
|
if ([[MPConfig get].showQuickStart boolValue])
|
||||||
|
[self showGuide];
|
||||||
|
else
|
||||||
|
[self loadKeyPhrase];
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
|
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showGuide {
|
||||||
|
|
||||||
|
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
|
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)loadKeyPhrase {
|
||||||
|
|
||||||
|
if ([[MPConfig get].forgetKeyPhrase boolValue]) {
|
||||||
|
[self forgetKeyPhrase];
|
||||||
|
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];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)forgetKeyPhrase {
|
||||||
|
|
||||||
|
dbg(@"Forgetting key phrase.");
|
||||||
|
[AlertViewController showAlertWithTitle:@"Changing Master Password"
|
||||||
|
message:
|
||||||
|
@"You've requested to change your master password.\n\n"
|
||||||
|
@"If you continue, your current sites and passwords will become unavailable.\n\n"
|
||||||
|
@"You can always change back to the old master password later.\n"
|
||||||
|
@"Your old 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.");
|
||||||
|
[KeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]];
|
||||||
|
[KeyChain deleteItemForQuery:[MPAppDelegate keyPhraseHashQuery]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self loadKeyPhrase];
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
|
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
cancelTitle:[PearlStrings get].commonButtonAbort
|
||||||
|
otherTitles:[PearlStrings get].commonButtonContinue, nil];
|
||||||
|
[MPConfig get].forgetKeyPhrase = [NSNumber numberWithBool:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)loadStoredKeyPhrase {
|
||||||
|
|
||||||
|
if ([[MPConfig get].storeKeyPhrase boolValue]) {
|
||||||
|
// Key phrase is stored in keychain. Load it.
|
||||||
|
dbg(@"Loading master key phrase from key chain.");
|
||||||
|
NSData *keyPhraseData = [KeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseQuery]];
|
||||||
|
dbg(@" -> Master key phrase %@.", keyPhraseData? @"found": @"NOT found");
|
||||||
|
|
||||||
|
self.keyPhrase = keyPhraseData? [[NSString alloc] initWithBytes:keyPhraseData.bytes length:keyPhraseData.length
|
||||||
|
encoding:NSUTF8StringEncoding]: nil;
|
||||||
|
} else {
|
||||||
|
// Key phrase should not be stored in keychain. Delete it.
|
||||||
|
dbg(@"Deleting master key phrase from key chain.");
|
||||||
|
[KeyChain deleteItemForQuery:[MPAppDelegate keyPhraseQuery]];
|
||||||
|
#ifndef PRODUCTION
|
||||||
|
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)askKeyPhrase {
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
NSData *keyPhraseHash = [KeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseHashQuery]];
|
||||||
|
dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known");
|
||||||
|
|
||||||
|
[AlertViewController showAlertWithTitle:@"Master Password"
|
||||||
|
message:keyPhraseHash? @"Unlock with your master password:": @"Choose your master password:"
|
||||||
|
viewStyle:UIAlertViewStyleSecureTextInput
|
||||||
|
tappedButtonBlock:
|
||||||
|
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||||
|
if (buttonIndex == [alert cancelButtonIndex])
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
NSString *answer = [alert textFieldAtIndex:0].text;
|
||||||
|
if (![answer length]) {
|
||||||
|
// User didn't enter a key phrase.
|
||||||
|
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
|
||||||
|
message:@"No master password entered."
|
||||||
|
viewStyle:UIAlertViewStyleDefault
|
||||||
|
tappedButtonBlock:
|
||||||
|
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||||
|
exit(0);
|
||||||
|
} cancelTitle:@"Quit" otherTitles:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSData *answerHash = [answer hashWith:PearlDigestSHA512];
|
||||||
|
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:answerHash]) {
|
||||||
|
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyPhraseHash, answerHash);
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
|
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
||||||
|
#endif
|
||||||
|
[AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
|
||||||
|
message:
|
||||||
|
@"Incorrect master password.\n\n"
|
||||||
|
@"If you are trying to use the app with a different master password, "
|
||||||
|
@"flip the 'Change my password' option in Settings."
|
||||||
|
viewStyle:UIAlertViewStyleDefault
|
||||||
|
tappedButtonBlock:
|
||||||
|
^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||||
|
exit(0);
|
||||||
|
} cancelTitle:@"Quit" otherTitles:nil];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
|
[TestFlight passCheckpoint:MPTestFlightCheckpointMPAsked];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
self.keyPhrase = answer;
|
||||||
|
} cancelTitle:@"Quit" otherTitles:@"Unlock", 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);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setKeyPhrase:(NSString *)keyPhrase {
|
||||||
|
|
||||||
|
_keyPhrase = keyPhrase;
|
||||||
|
|
||||||
|
if (keyPhrase) {
|
||||||
|
self.keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512];
|
||||||
|
self.keyPhraseHashHex = [self.keyPhraseHash encodeHex];
|
||||||
|
|
||||||
|
dbg(@"Updating master key phrase hash to: %@.", self.keyPhraseHashHex);
|
||||||
|
[KeyChain 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.");
|
||||||
|
[KeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseQuery]
|
||||||
|
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
[keyPhrase dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecValueData,
|
||||||
|
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
||||||
|
nil]];
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
|
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSetKeyphraseLength, _keyPhrase.length]];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
//
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
- (void)loadStoredKey;
|
|
||||||
- (IBAction)signOut:(id)sender;
|
|
||||||
|
|
||||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword;
|
|
||||||
- (void)updateKey:(NSData *)key;
|
|
||||||
- (void)forgetKey;
|
|
||||||
|
|
||||||
- (NSData *)keyWithLength:(NSUInteger)keyLength;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
//
|
|
||||||
// MPAppDelegate.m
|
|
||||||
// MasterPassword
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 24/11/11.
|
|
||||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPConfig.h"
|
|
||||||
#import "MPAppDelegate_Key.h"
|
|
||||||
#import "MPElementEntity.h"
|
|
||||||
|
|
||||||
@implementation MPAppDelegate_Shared (Key)
|
|
||||||
|
|
||||||
static NSDictionary *keyQuery() {
|
|
||||||
|
|
||||||
static NSDictionary *MPKeyQuery = nil;
|
|
||||||
if (!MPKeyQuery)
|
|
||||||
MPKeyQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
|
||||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
||||||
@"Saved Master Password", (__bridge id)kSecAttrService,
|
|
||||||
@"default", (__bridge id)kSecAttrAccount,
|
|
||||||
nil]
|
|
||||||
matches:nil];
|
|
||||||
|
|
||||||
return MPKeyQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
static NSDictionary *keyIDQuery() {
|
|
||||||
|
|
||||||
static NSDictionary *MPKeyIDQuery = nil;
|
|
||||||
if (!MPKeyIDQuery)
|
|
||||||
MPKeyIDQuery = [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
|
||||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
||||||
@"Master Password Check", (__bridge id)kSecAttrService,
|
|
||||||
@"default", (__bridge id)kSecAttrAccount,
|
|
||||||
nil]
|
|
||||||
matches:nil];
|
|
||||||
|
|
||||||
return MPKeyIDQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)forgetKey {
|
|
||||||
|
|
||||||
inf(@"Deleting key and ID from keychain.");
|
|
||||||
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound)
|
|
||||||
inf(@"Removed key from keychain.");
|
|
||||||
if ([PearlKeyChain deleteItemForQuery:keyIDQuery()] != errSecItemNotFound)
|
|
||||||
inf(@"Removed key ID from keychain.");
|
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)signOut:(id)sender {
|
|
||||||
|
|
||||||
[MPConfig get].saveKey = [NSNumber numberWithBool:NO];
|
|
||||||
[self updateKey:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)loadStoredKey {
|
|
||||||
|
|
||||||
if ([[MPConfig get].saveKey boolValue]) {
|
|
||||||
// Key is stored in keychain. Load it.
|
|
||||||
[self updateKey:[PearlKeyChain dataOfItemForQuery:keyQuery()]];
|
|
||||||
inf(@"Looking for key in keychain: %@.", self.key? @"found": @"missing");
|
|
||||||
} else {
|
|
||||||
// Key should not be stored in keychain. Delete it.
|
|
||||||
if ([PearlKeyChain deleteItemForQuery:keyQuery()] != errSecItemNotFound)
|
|
||||||
inf(@"Removed key from keychain.");
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)tryMasterPassword:(NSString *)tryPassword {
|
|
||||||
|
|
||||||
if (![tryPassword length])
|
|
||||||
return NO;
|
|
||||||
|
|
||||||
NSData *tryKey = keyForPassword(tryPassword);
|
|
||||||
NSData *tryKeyID = keyIDForKey(tryKey);
|
|
||||||
NSData *keyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()];
|
|
||||||
inf(@"Key ID known? %@.", keyID? @"YES": @"NO");
|
|
||||||
if (keyID)
|
|
||||||
// A key ID is known -> a password is set.
|
|
||||||
// Make sure the user's entered password matches it.
|
|
||||||
if (![keyID isEqual:tryKeyID]) {
|
|
||||||
wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [keyID encodeHex], [tryKeyID encodeHex]);
|
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
|
|
||||||
#endif
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[self updateKey:tryKey];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateKey:(NSData *)key {
|
|
||||||
|
|
||||||
if (self.key != key) {
|
|
||||||
self.key = key;
|
|
||||||
|
|
||||||
if (key)
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
|
|
||||||
else
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.key) {
|
|
||||||
self.keyID = keyIDForKey(self.key);
|
|
||||||
|
|
||||||
NSData *existingKeyID = [PearlKeyChain dataOfItemForQuery:keyIDQuery()];
|
|
||||||
if (![existingKeyID isEqualToData:self.keyID]) {
|
|
||||||
inf(@"Updating key ID in keychain.");
|
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:keyIDQuery()
|
|
||||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
||||||
self.keyID, (__bridge id)kSecValueData,
|
|
||||||
#if TARGET_OS_IPHONE
|
|
||||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
|
||||||
#endif
|
|
||||||
nil]];
|
|
||||||
}
|
|
||||||
if ([[MPConfig get].saveKey boolValue]) {
|
|
||||||
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery()];
|
|
||||||
if (![existingKey isEqualToData:self.key]) {
|
|
||||||
inf(@"Updating key in keychain.");
|
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:keyQuery()
|
|
||||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
||||||
self.key, (__bridge id)kSecValueData,
|
|
||||||
#if TARGET_OS_IPHONE
|
|
||||||
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
|
|
||||||
#endif
|
|
||||||
nil]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSData *)keyWithLength:(NSUInteger)keyLength {
|
|
||||||
|
|
||||||
return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
//
|
|
||||||
// MPAppDelegate_Shared.h
|
|
||||||
// MasterPassword
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 24/11/11.
|
|
||||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
|
||||||
@interface MPAppDelegate_Shared : PearlAppDelegate
|
|
||||||
#else
|
|
||||||
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@property (strong, nonatomic) NSData *key;
|
|
||||||
@property (strong, nonatomic) NSData *keyID;
|
|
||||||
|
|
||||||
+ (MPAppDelegate_Shared *)get;
|
|
||||||
|
|
||||||
- (NSURL *)applicationFilesDirectory;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
//
|
|
||||||
// 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 keyID;
|
|
||||||
|
|
||||||
+ (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
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
//
|
|
||||||
// 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;
|
|
||||||
- (void)printStore;
|
|
||||||
|
|
||||||
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
|
|
||||||
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation;
|
|
||||||
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,439 +0,0 @@
|
|||||||
//
|
|
||||||
// MPAppDelegate.m
|
|
||||||
// MasterPassword
|
|
||||||
//
|
|
||||||
// Created by Maarten Billemont on 24/11/11.
|
|
||||||
// Copyright (c) 2011 Lyndir. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "MPAppDelegate_Store.h"
|
|
||||||
#import "MPElementEntity.h"
|
|
||||||
#import "MPConfig.h"
|
|
||||||
|
|
||||||
@implementation MPAppDelegate_Shared (Store)
|
|
||||||
|
|
||||||
static NSDateFormatter *rfc3339DateFormatter = nil;
|
|
||||||
|
|
||||||
#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;
|
|
||||||
|
|
||||||
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
|
|
||||||
assert(coordinator);
|
|
||||||
|
|
||||||
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
|
||||||
[managedObjectContext performBlockAndWait:^{
|
|
||||||
managedObjectContext.persistentStoreCoordinator = coordinator;
|
|
||||||
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
|
|
||||||
}];
|
|
||||||
|
|
||||||
return managedObjectContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
|
|
||||||
|
|
||||||
// Wait until the storeManager is ready.
|
|
||||||
for(__block BOOL isReady = [self storeManager].isReady; !isReady;)
|
|
||||||
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
||||||
isReady = [self storeManager].isReady;
|
|
||||||
});
|
|
||||||
|
|
||||||
assert([self storeManager].isReady);
|
|
||||||
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);
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)printStore {
|
|
||||||
|
|
||||||
if (![self managedObjectModel] || ![self managedObjectContext]) {
|
|
||||||
trc(@"Not printing store: store not initialized.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
[self.managedObjectContext performBlock:^{
|
|
||||||
trc(@"=== All entities ===");
|
|
||||||
for(NSEntityDescription *entity in [[self managedObjectModel] entities]) {
|
|
||||||
NSFetchRequest *request = [NSFetchRequest new];
|
|
||||||
[request setEntity:entity];
|
|
||||||
NSError *error;
|
|
||||||
NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:&error];
|
|
||||||
for(NSManagedObject *o in results) {
|
|
||||||
if ([o isKindOfClass:[MPElementEntity class]]) {
|
|
||||||
MPElementEntity *e = (MPElementEntity *)o;
|
|
||||||
trc(@"For descriptor: %@, found: %@: %@ (%@)", entity.name, [o class], e.name, e.keyID);
|
|
||||||
} else {
|
|
||||||
trc(@"For descriptor: %@, found: %@", entity.name, [o class]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trc(@"---");
|
|
||||||
if ([MPAppDelegate_Shared get].keyID) {
|
|
||||||
trc(@"=== Known sites ===");
|
|
||||||
NSFetchRequest *fetchRequest = [[self managedObjectModel]
|
|
||||||
fetchRequestFromTemplateWithName:@"MPElements"
|
|
||||||
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
||||||
@"", @"query",
|
|
||||||
[MPAppDelegate_Shared get].keyID, @"keyID",
|
|
||||||
nil]];
|
|
||||||
[fetchRequest setSortDescriptors:
|
|
||||||
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
|
|
||||||
|
|
||||||
NSError *error = nil;
|
|
||||||
for (MPElementEntity *e in [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error]) {
|
|
||||||
trc(@"Found site: %@ (%@): %@", e.name, e.keyID, e);
|
|
||||||
}
|
|
||||||
trc(@"---");
|
|
||||||
} else
|
|
||||||
trc(@"Not printing sites: master password not set.");
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
#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 tampers with didSwitch a bit)
|
|
||||||
iCloudEnabled = manager.iCloudEnabled;
|
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:iCloudEnabled? MPTestFlightCheckpointCloudEnabled: MPTestFlightCheckpointCloudDisabled];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
inf(@"Using iCloud? %@", iCloudEnabled? @"YES": @"NO");
|
|
||||||
[MPConfig get].iCloud = [NSNumber numberWithBool:iCloudEnabled];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreManagerErrorCause)cause context:(id)context {
|
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:PearlString(@"MPTestFlightCheckpointMPErrorUbiquity_%d", cause)];
|
|
||||||
#endif
|
|
||||||
err(@"StoreManager: cause=%d, context=%@, error=%@", cause, context, error);
|
|
||||||
|
|
||||||
switch (cause) {
|
|
||||||
case UbiquityStoreManagerErrorCauseDeleteStore:
|
|
||||||
case UbiquityStoreManagerErrorCauseDeleteLogs:
|
|
||||||
case UbiquityStoreManagerErrorCauseCreateStorePath:
|
|
||||||
case UbiquityStoreManagerErrorCauseClearStore:
|
|
||||||
break;
|
|
||||||
case UbiquityStoreManagerErrorCauseOpenLocalStore: {
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointLocalStoreIncompatible];
|
|
||||||
#endif
|
|
||||||
wrn(@"Local store could not be opened, resetting it.");
|
|
||||||
manager.hardResetEnabled = YES;
|
|
||||||
[manager hardResetLocalStorage];
|
|
||||||
|
|
||||||
[NSException raise:NSGenericException format:@"Local store was reset, application must be restarted to use it."];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case UbiquityStoreManagerErrorCauseOpenCloudStore: {
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointCloudStoreIncompatible];
|
|
||||||
#endif
|
|
||||||
wrn(@"iCloud store could not be opened, resetting it.");
|
|
||||||
manager.hardResetEnabled = YES;
|
|
||||||
[manager hardResetCloudStorage];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Import / Export
|
|
||||||
|
|
||||||
- (void)loadRFC3339DateFormatter {
|
|
||||||
|
|
||||||
if (rfc3339DateFormatter)
|
|
||||||
return;
|
|
||||||
|
|
||||||
rfc3339DateFormatter = [NSDateFormatter new];
|
|
||||||
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
|
||||||
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
|
|
||||||
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
|
|
||||||
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
|
|
||||||
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation {
|
|
||||||
|
|
||||||
[self loadRFC3339DateFormatter];
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
NSString *keyIDHex = nil;
|
|
||||||
BOOL headerStarted = NO, headerEnded = 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 *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
|
|
||||||
NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
|
|
||||||
if ([key isEqualToString:@"Key ID"]) {
|
|
||||||
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password) encodeHex]])
|
|
||||||
return MPImportResultInvalidPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!headerEnded)
|
|
||||||
continue;
|
|
||||||
if (!keyIDHex)
|
|
||||||
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.
|
|
||||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND keyID == %@", name, keyIDHex];
|
|
||||||
NSArray *existingSites = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
|
||||||
if (error)
|
|
||||||
err(@"Couldn't search existing sites: %@", error);
|
|
||||||
if (!existingSites)
|
|
||||||
return MPImportResultInternalError;
|
|
||||||
|
|
||||||
[elementsToDelete addObjectsFromArray:existingSites];
|
|
||||||
[importedSiteElements addObject:[NSArray arrayWithObjects:lastUsed, uses, type, name, exportContent, nil]];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask for confirmation to import these sites.
|
|
||||||
if (!confirmation([importedSiteElements count], [elementsToDelete count]))
|
|
||||||
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.
|
|
||||||
for (NSArray *siteElements in importedSiteElements) {
|
|
||||||
NSDate *lastUsed = [rfc3339DateFormatter dateFromString:[siteElements objectAtIndex:0]];
|
|
||||||
NSInteger uses = [[siteElements objectAtIndex:1] integerValue];
|
|
||||||
MPElementType type = (unsigned)[[siteElements objectAtIndex:2] integerValue];
|
|
||||||
NSString *name = [siteElements objectAtIndex:3];
|
|
||||||
NSString *exportContent = [siteElements objectAtIndex:4];
|
|
||||||
|
|
||||||
// Create new site.
|
|
||||||
inf(@"Importing site: name=%@, lastUsed=%@, uses=%d, type=%u, keyID=%@", name, lastUsed, uses, type, keyIDHex);
|
|
||||||
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
|
|
||||||
inManagedObjectContext:self.managedObjectContext];
|
|
||||||
element.name = name;
|
|
||||||
element.keyID = [keyIDHex decodeHex];
|
|
||||||
element.type = type;
|
|
||||||
element.uses = uses;
|
|
||||||
element.lastUsed = [lastUsed timeIntervalSinceReferenceDate];
|
|
||||||
if ([exportContent length])
|
|
||||||
[element importContent:exportContent];
|
|
||||||
}
|
|
||||||
[self saveContext];
|
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesImported];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return MPImportResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords {
|
|
||||||
|
|
||||||
[self loadRFC3339DateFormatter];
|
|
||||||
|
|
||||||
// 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:@"# Key ID: %@\n", [self.keyID encodeHex]];
|
|
||||||
[export appendFormat:@"# Date: %@\n", [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.
|
|
||||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
|
|
||||||
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
|
|
||||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"keyID == %@", self.keyID];
|
|
||||||
__autoreleasing NSError *error = nil;
|
|
||||||
NSArray *elements = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
|
||||||
if (error)
|
|
||||||
err(@"Error fetching sites for export: %@", error);
|
|
||||||
|
|
||||||
for (MPElementEntity *element in elements) {
|
|
||||||
NSTimeInterval lastUsed = element.lastUsed;
|
|
||||||
int16_t uses = element.uses;
|
|
||||||
MPElementType type = (unsigned)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",
|
|
||||||
[rfc3339DateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:lastUsed]], uses, type, [name cStringUsingEncoding:NSUTF8StringEncoding], content? content: @""];
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef TESTFLIGHT_SDK_VERSION
|
|
||||||
[TestFlight passCheckpoint:MPTestFlightCheckpointSitesExported];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return export;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -6,13 +6,14 @@
|
|||||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
@interface MPConfig : PearlConfig
|
@interface MPConfig : Config
|
||||||
|
|
||||||
@property (nonatomic, retain) NSNumber *saveKey;
|
@property (nonatomic, retain) NSNumber *dataStoreError;
|
||||||
@property (nonatomic, retain) NSNumber *rememberKey;
|
@property (nonatomic, retain) NSNumber *storeKeyPhrase;
|
||||||
|
@property (nonatomic, retain) NSNumber *rememberKeyPhrase;
|
||||||
@property (nonatomic, retain) NSNumber *iCloud;
|
@property (nonatomic, retain) NSNumber *forgetKeyPhrase;
|
||||||
@property (nonatomic, retain) NSNumber *iCloudDecided;
|
@property (nonatomic, retain) NSNumber *helpHidden;
|
||||||
|
@property (nonatomic, retain) NSNumber *showQuickStart;
|
||||||
|
|
||||||
+ (MPConfig *)get;
|
+ (MPConfig *)get;
|
||||||
|
|
||||||
|
|||||||
@@ -7,27 +7,26 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MPConfig.h"
|
#import "MPConfig.h"
|
||||||
#import "MPAppDelegate.h"
|
|
||||||
|
|
||||||
@implementation MPConfig
|
@implementation MPConfig
|
||||||
@dynamic saveKey, rememberKey, iCloud, iCloudDecided;
|
|
||||||
|
@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, forgetKeyPhrase, helpHidden, showQuickStart;
|
||||||
|
|
||||||
|
|
||||||
- (id)init {
|
- (id)init {
|
||||||
|
|
||||||
if(!(self = [super init]))
|
if(!(self = [super init]))
|
||||||
return nil;
|
return self;
|
||||||
|
|
||||||
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
|
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(askForReviews)),
|
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)),
|
||||||
|
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)),
|
||||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(saveKey)),
|
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(rememberKeyPhrase)),
|
||||||
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(rememberKey)),
|
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(forgetKeyPhrase)),
|
||||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloud)),
|
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
|
||||||
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(iCloudDecided)),
|
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
|
||||||
nil]];
|
nil]];
|
||||||
|
|
||||||
self.delegate = [MPAppDelegate get];
|
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,12 @@
|
|||||||
@interface MPElementEntity : NSManagedObject
|
@interface MPElementEntity : NSManagedObject
|
||||||
|
|
||||||
@property (nonatomic, retain) NSString *name;
|
@property (nonatomic, retain) NSString *name;
|
||||||
@property (nonatomic, retain) NSData *keyID;
|
@property (nonatomic, retain) NSString *mpHashHex;
|
||||||
@property (nonatomic, assign) int16_t type;
|
@property (nonatomic, assign) int16_t type;
|
||||||
@property (nonatomic, assign) int16_t uses;
|
@property (nonatomic, assign) int16_t uses;
|
||||||
@property (nonatomic, assign) NSTimeInterval lastUsed;
|
@property (nonatomic, assign) NSTimeInterval lastUsed;
|
||||||
|
|
||||||
@property (nonatomic, retain, readonly) id content;
|
@property (nonatomic, retain, readonly) id content;
|
||||||
|
|
||||||
- (int16_t)use;
|
- (void)use;
|
||||||
- (NSString *)exportContent;
|
|
||||||
- (void)importContent:(NSString *)content;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -12,15 +12,15 @@
|
|||||||
@implementation MPElementEntity
|
@implementation MPElementEntity
|
||||||
|
|
||||||
@dynamic name;
|
@dynamic name;
|
||||||
@dynamic keyID;
|
@dynamic mpHashHex;
|
||||||
@dynamic type;
|
@dynamic type;
|
||||||
@dynamic uses;
|
@dynamic uses;
|
||||||
@dynamic lastUsed;
|
@dynamic lastUsed;
|
||||||
|
|
||||||
- (int16_t)use {
|
- (void)use {
|
||||||
|
|
||||||
|
++self.uses;
|
||||||
self.lastUsed = [[NSDate date] timeIntervalSinceReferenceDate];
|
self.lastUsed = [[NSDate date] timeIntervalSinceReferenceDate];
|
||||||
return ++self.uses;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)content {
|
- (id)content {
|
||||||
@@ -28,24 +28,15 @@
|
|||||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
|
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)exportContent {
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)importContent:(NSString *)content {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)description {
|
- (NSString *)description {
|
||||||
|
|
||||||
return PearlString(@"%@:%@", [self class], [self name]);
|
return [[self content] description];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)debugDescription {
|
- (NSString *)debugDescription {
|
||||||
|
|
||||||
return PearlString(@"{%@: name=%@, keyID=%@, type=%d, uses=%d, lastUsed=%@}",
|
return [NSString stringWithFormat:@"{%@: name=%@, mpHashHex=%@, type=%d, uses=%d, lastUsed=%@}",
|
||||||
NSStringFromClass([self class]), self.name, self.keyID, self.type, self.uses, self.lastUsed);
|
NSStringFromClass([self class]), self.name, self.mpHashHex, self.type, self.uses, self.lastUsed];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -13,6 +13,6 @@
|
|||||||
|
|
||||||
@interface MPElementGeneratedEntity : MPElementEntity
|
@interface MPElementGeneratedEntity : MPElementEntity
|
||||||
|
|
||||||
@property (nonatomic, assign) int32_t counter;
|
@property (nonatomic, assign) int16_t counter;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#import "MPElementGeneratedEntity.h"
|
#import "MPElementGeneratedEntity.h"
|
||||||
#import "MPAppDelegate.h"
|
#import "MPAppDelegate.h"
|
||||||
#import "MPAppDelegate_Key.h"
|
|
||||||
|
|
||||||
|
|
||||||
@implementation MPElementGeneratedEntity
|
@implementation MPElementGeneratedEntity
|
||||||
@@ -17,15 +16,16 @@
|
|||||||
|
|
||||||
- (id)content {
|
- (id)content {
|
||||||
|
|
||||||
if (!(self.type & MPElementTypeClassGenerated)) {
|
assert(self.type & MPElementTypeClassCalculated);
|
||||||
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![self.name length])
|
if (![self.name length])
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
return MPCalculateContent((unsigned)self.type, self.name, [MPAppDelegate get].key, self.counter);
|
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#import "MPElementStoredEntity.h"
|
#import "MPElementStoredEntity.h"
|
||||||
#import "MPAppDelegate.h"
|
#import "MPAppDelegate.h"
|
||||||
#import "MPAppDelegate_Key.h"
|
|
||||||
|
|
||||||
@interface MPElementStoredEntity ()
|
@interface MPElementStoredEntity ()
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@
|
|||||||
|
|
||||||
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
||||||
|
|
||||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
return [KeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
@"DevicePrivate", (__bridge id)kSecAttrService,
|
@"DevicePrivate", (__bridge id)kSecAttrService,
|
||||||
name, (__bridge id)kSecAttrAccount,
|
name, (__bridge id)kSecAttrAccount,
|
||||||
@@ -35,42 +34,32 @@
|
|||||||
assert(self.type & MPElementTypeClassStored);
|
assert(self.type & MPElementTypeClassStored);
|
||||||
|
|
||||||
NSData *encryptedContent;
|
NSData *encryptedContent;
|
||||||
if (self.type & MPElementFeatureDevicePrivate)
|
if (self.type == MPElementTypeStoredDevicePrivate)
|
||||||
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
|
encryptedContent = [KeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
|
||||||
else
|
else
|
||||||
encryptedContent = self.contentObject;
|
encryptedContent = self.contentObject;
|
||||||
|
|
||||||
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
|
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get].keyPhrase
|
||||||
padding:YES];
|
dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
|
usePadding:YES];
|
||||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setContent:(id)content {
|
- (void)setContent:(id)content {
|
||||||
|
|
||||||
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize]
|
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get].keyPhrase
|
||||||
padding:YES];
|
dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
|
usePadding:YES];
|
||||||
|
|
||||||
if (self.type & MPElementFeatureDevicePrivate) {
|
if (self.type == MPElementTypeStoredDevicePrivate) {
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
[KeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
||||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
encryptedContent, (__bridge id)kSecValueData,
|
encryptedContent, (__bridge id)kSecValueData,
|
||||||
#if TARGET_OS_IPHONE
|
|
||||||
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
|
||||||
#endif
|
|
||||||
nil]];
|
nil]];
|
||||||
self.contentObject = nil;
|
self.contentObject = nil;
|
||||||
} else
|
} else
|
||||||
self.contentObject = encryptedContent;
|
self.contentObject = encryptedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)exportContent {
|
|
||||||
|
|
||||||
return [self.contentObject encodeBase64];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)importContent:(NSString *)content {
|
|
||||||
|
|
||||||
self.contentObject = [content decodeBase64];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#import "MPGuideViewController.h"
|
#import "MPGuideViewController.h"
|
||||||
#import "MPAppDelegate.h"
|
#import "MPAppDelegate.h"
|
||||||
|
|
||||||
|
|
||||||
@implementation MPGuideViewController
|
@implementation MPGuideViewController
|
||||||
@synthesize scrollView;
|
@synthesize scrollView;
|
||||||
|
|
||||||
@@ -22,24 +21,17 @@
|
|||||||
|
|
||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
|
||||||
[PearlUIUtils autoSizeContent:self.scrollView];
|
[UIUtils autoSizeContent:self.scrollView];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillDisappear:(BOOL)animated {
|
- (void)viewWillDisappear:(BOOL)animated {
|
||||||
|
|
||||||
[super viewWillDisappear:animated];
|
[super viewWillDisappear:animated];
|
||||||
|
|
||||||
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];
|
[MPConfig get].showQuickStart = [NSNumber numberWithBool:NO];
|
||||||
|
[[MPAppDelegate get] loadKeyPhrase];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidDisappear:(BOOL)animated {
|
|
||||||
|
|
||||||
[super viewDidDisappear:animated];
|
|
||||||
|
|
||||||
[[MPAppDelegate get] loadKey:animated];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (void)viewDidUnload {
|
- (void)viewDidUnload {
|
||||||
|
|
||||||
[self setScrollView:nil];
|
[self setScrollView:nil];
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
#import "MPSearchDelegate.h"
|
#import "MPSearchDelegate.h"
|
||||||
#import "IASKAppSettingsViewController.h"
|
#import "IASKAppSettingsViewController.h"
|
||||||
|
|
||||||
@interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, IASKSettingsDelegate>
|
@interface MPMainViewController : UIViewController <MPTypeDelegate, UITextFieldDelegate, UISearchBarDelegate, MPSearchResultsDelegate, UIWebViewDelegate, IASKSettingsDelegate>
|
||||||
|
|
||||||
@property (strong, nonatomic) MPElementEntity *activeElement;
|
@property (strong, nonatomic) MPElementEntity *activeElement;
|
||||||
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
|
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchResultsController;
|
||||||
@@ -32,11 +32,8 @@
|
|||||||
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
|
@property (weak, nonatomic) IBOutlet UIImageView *contentTipEditIcon;
|
||||||
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
|
||||||
|
|
||||||
@property (copy) void (^contentTipCleanup)(BOOL finished);
|
|
||||||
|
|
||||||
- (IBAction)copyContent;
|
- (IBAction)copyContent;
|
||||||
- (IBAction)incrementPasswordCounter;
|
- (IBAction)incrementPasswordCounter;
|
||||||
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender;
|
|
||||||
- (IBAction)editPassword;
|
- (IBAction)editPassword;
|
||||||
- (IBAction)closeAlert;
|
- (IBAction)closeAlert;
|
||||||
- (IBAction)action:(UIBarButtonItem *)sender;
|
- (IBAction)action:(UIBarButtonItem *)sender;
|
||||||
478
MasterPassword/MPMainViewController.m
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
//
|
||||||
|
// 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 closeAlert];
|
||||||
|
|
||||||
|
[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.2 animations:^{
|
||||||
|
[self updateWasAnimated:YES];
|
||||||
|
}];
|
||||||
|
else
|
||||||
|
[self updateWasAnimated:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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:@"%d", ((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, 415);
|
||||||
|
[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"]]]];
|
||||||
|
[self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
|
||||||
|
ClassNameFromMPElementType(self.activeElement.type)]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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) {
|
||||||
|
if (!finished) {
|
||||||
|
icon.hidden = YES;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 && ![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 {
|
||||||
|
|
||||||
|
[SheetViewController 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;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
|
nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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 {
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
|
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
|
||||||
|
#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
|
||||||
@@ -15,12 +15,9 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MPSearchDelegate : NSObject <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
|
@interface MPSearchDelegate : NSObject <UITableViewDelegate, UITableViewDataSource, UISearchDisplayDelegate, NSFetchedResultsControllerDelegate>
|
||||||
|
|
||||||
@property (strong, nonatomic) NSDateFormatter *dateFormatter;
|
|
||||||
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
|
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
|
||||||
@property (strong, nonatomic) NSString *query;
|
|
||||||
@property (strong, nonatomic) UILabel *tipView;
|
|
||||||
|
|
||||||
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
|
@property (weak, nonatomic) IBOutlet id<MPSearchResultsDelegate> delegate;
|
||||||
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
|
@property (weak, nonatomic) IBOutlet UISearchDisplayController *searchDisplayController;
|
||||||
263
MasterPassword/MPSearchDelegate.m
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
//
|
||||||
|
// 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 fetchedResultsController;
|
||||||
|
@synthesize delegate;
|
||||||
|
@synthesize searchDisplayController;
|
||||||
|
@synthesize searchTipContainer;
|
||||||
|
|
||||||
|
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
|
||||||
|
|
||||||
|
self.searchDisplayController.searchBar.text = @"";
|
||||||
|
self.searchDisplayController.searchBar.prompt = @"Enter the site's domain name (eg. apple.com):";
|
||||||
|
|
||||||
|
[UIView animateWithDuration:0.2f animations:^{
|
||||||
|
self.searchTipContainer.alpha = 0;
|
||||||
|
}];
|
||||||
|
|
||||||
|
[self update];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
|
||||||
|
|
||||||
|
self.searchDisplayController.searchBar.prompt = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView {
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserverForName:UIScreenModeDidChangeNotification
|
||||||
|
object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||||
|
NSError *error;
|
||||||
|
if (![self.fetchedResultsController performFetch:&error])
|
||||||
|
err(@"Couldn't fetch elements: %@", error);
|
||||||
|
}];
|
||||||
|
|
||||||
|
tableView.backgroundColor = [UIColor blackColor];
|
||||||
|
tableView.rowHeight = 34.0f;
|
||||||
|
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
|
||||||
|
|
||||||
|
[self update];
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)update {
|
||||||
|
|
||||||
|
NSString *query = self.searchDisplayController.searchBar.text;
|
||||||
|
if (!query)
|
||||||
|
query = @"";
|
||||||
|
|
||||||
|
NSFetchRequest *fetchRequest = [[MPAppDelegate get].managedObjectModel
|
||||||
|
fetchRequestFromTemplateWithName:@"MPSearchElement"
|
||||||
|
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
query, @"query",
|
||||||
|
[MPAppDelegate get].keyPhraseHashHex, @"mpHashHex",
|
||||||
|
nil]];
|
||||||
|
[fetchRequest setSortDescriptors:
|
||||||
|
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
|
||||||
|
|
||||||
|
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] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||||
|
|
||||||
|
if (section == [self numberOfSectionsInTableView:tableView] - 1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
|
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MPElementSearch"];
|
||||||
|
if (!cell) {
|
||||||
|
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"MPElementSearch"];
|
||||||
|
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"ui_list_middle"] resizableImageWithCapInsets:UIEdgeInsetsMake(5, 5, 5, 5)]];
|
||||||
|
backgroundImageView.frame = CGRectMake(-5, 0, 330, 34);
|
||||||
|
UIView *backgroundView = [[UIView alloc] initWithFrame:cell.frame];
|
||||||
|
[backgroundView addSubview:backgroundImageView];
|
||||||
|
|
||||||
|
cell.backgroundView = backgroundView;
|
||||||
|
cell.textLabel.backgroundColor = [UIColor clearColor];
|
||||||
|
cell.textLabel.textColor = [UIColor whiteColor];
|
||||||
|
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
|
||||||
|
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
[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:@"%d", element.uses];
|
||||||
|
} else {
|
||||||
|
// "New" section
|
||||||
|
cell.textLabel.text = self.searchDisplayController.searchBar.text;
|
||||||
|
cell.detailTextLabel.text = @"New";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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.searchDisplayController.searchBar.text;
|
||||||
|
[AlertViewController showAlertWithTitle:@"New Site"
|
||||||
|
message:l(@"Do you want to create a new site named:\n%@", siteName)
|
||||||
|
viewStyle:UIAlertViewStyleDefault
|
||||||
|
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
|
||||||
|
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 == -1)
|
||||||
|
return @"";
|
||||||
|
|
||||||
|
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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])
|
||||||
|
// "New" section.
|
||||||
|
return;
|
||||||
|
|
||||||
|
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
|
||||||
@@ -12,9 +12,6 @@
|
|||||||
|
|
||||||
- (void)didSelectType:(MPElementType)type;
|
- (void)didSelectType:(MPElementType)type;
|
||||||
|
|
||||||
@optional
|
|
||||||
- (MPElementType)selectedType;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MPTypeViewController : UITableViewController
|
@interface MPTypeViewController : UITableViewController
|
||||||
@@ -8,13 +8,6 @@
|
|||||||
|
|
||||||
#import "MPTypeViewController.h"
|
#import "MPTypeViewController.h"
|
||||||
|
|
||||||
|
|
||||||
@interface MPTypeViewController ()
|
|
||||||
|
|
||||||
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation MPTypeViewController
|
@implementation MPTypeViewController
|
||||||
@synthesize delegate;
|
@synthesize delegate;
|
||||||
|
|
||||||
@@ -32,55 +25,34 @@
|
|||||||
return YES;
|
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 {
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
assert(self.navigationController.topViewController == self);
|
assert(self.navigationController.topViewController == self);
|
||||||
|
|
||||||
[delegate didSelectType:[self typeAtIndexPath:indexPath]];
|
MPElementType type;
|
||||||
[self.navigationController popViewControllerAnimated:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
|
|
||||||
|
|
||||||
switch (indexPath.section) {
|
switch (indexPath.section) {
|
||||||
case 0: {
|
case 0: {
|
||||||
// Generated
|
// Calculated
|
||||||
switch (indexPath.row) {
|
switch (indexPath.row) {
|
||||||
case 0:
|
case 0:
|
||||||
return MPElementTypeGeneratedLong;
|
type = MPElementTypeCalculatedLong;
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
return MPElementTypeGeneratedMedium;
|
type = MPElementTypeCalculatedMedium;
|
||||||
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
return MPElementTypeGeneratedShort;
|
type = MPElementTypeCalculatedShort;
|
||||||
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
return MPElementTypeGeneratedBasic;
|
type = MPElementTypeCalculatedBasic;
|
||||||
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
return MPElementTypeGeneratedPIN;
|
type = MPElementTypeCalculatedPIN;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
[NSException raise:NSInternalInconsistencyException
|
[NSException raise:NSInternalInconsistencyException
|
||||||
format:@"Unsupported row: %d, when selecting generated element type.", indexPath.row];
|
format:@"Unsupported row: %d, when selecting calculated element type.", indexPath.row];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -89,9 +61,11 @@
|
|||||||
// Stored
|
// Stored
|
||||||
switch (indexPath.row) {
|
switch (indexPath.row) {
|
||||||
case 0:
|
case 0:
|
||||||
return MPElementTypeStoredPersonal;
|
type = MPElementTypeStoredPersonal;
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
return MPElementTypeStoredDevicePrivate;
|
type = MPElementTypeStoredDevicePrivate;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
[NSException raise:NSInternalInconsistencyException
|
[NSException raise:NSInternalInconsistencyException
|
||||||
@@ -105,7 +79,8 @@
|
|||||||
format:@"Unsupported section: %d, when selecting element type.", indexPath.section];
|
format:@"Unsupported section: %d, when selecting element type.", indexPath.section];
|
||||||
}
|
}
|
||||||
|
|
||||||
@throw nil;
|
[delegate didSelectType:type];
|
||||||
|
[self.navigationController popViewControllerAnimated:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -17,34 +17,25 @@ typedef enum {
|
|||||||
} MPElementContentType;
|
} MPElementContentType;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
/** Generate the password. */
|
MPElementTypeClassCalculated = 2 << 7,
|
||||||
MPElementTypeClassGenerated = 1 << 4,
|
MPElementTypeClassStored = 2 << 8,
|
||||||
/** Store the password. */
|
|
||||||
MPElementTypeClassStored = 1 << 5,
|
|
||||||
} MPElementTypeClass;
|
} MPElementTypeClass;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
/** Export the key-protected content data. */
|
MPElementTypeCalculatedLong = MPElementTypeClassCalculated | 0x01,
|
||||||
MPElementFeatureExportContent = 1 << 10,
|
MPElementTypeCalculatedMedium = MPElementTypeClassCalculated | 0x02,
|
||||||
/** Never export content. */
|
MPElementTypeCalculatedShort = MPElementTypeClassCalculated | 0x03,
|
||||||
MPElementFeatureDevicePrivate = 1 << 11,
|
MPElementTypeCalculatedBasic = MPElementTypeClassCalculated | 0x04,
|
||||||
} MPElementFeature;
|
MPElementTypeCalculatedPIN = MPElementTypeClassCalculated | 0x05,
|
||||||
|
|
||||||
typedef enum {
|
MPElementTypeStoredPersonal = MPElementTypeClassStored | 0x01,
|
||||||
MPElementTypeGeneratedLong = 0x0 | MPElementTypeClassGenerated | 0x0,
|
MPElementTypeStoredDevicePrivate = MPElementTypeClassStored | 0x02,
|
||||||
MPElementTypeGeneratedMedium = 0x1 | MPElementTypeClassGenerated | 0x0,
|
|
||||||
MPElementTypeGeneratedShort = 0x2 | MPElementTypeClassGenerated | 0x0,
|
|
||||||
MPElementTypeGeneratedBasic = 0x3 | MPElementTypeClassGenerated | 0x0,
|
|
||||||
MPElementTypeGeneratedPIN = 0x4 | MPElementTypeClassGenerated | 0x0,
|
|
||||||
|
|
||||||
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
|
|
||||||
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
|
|
||||||
} MPElementType;
|
} MPElementType;
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
|
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
|
||||||
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@"
|
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@"
|
||||||
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard"
|
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard"
|
||||||
#define MPTestFlightCheckpointResetPasswordCounter @"MPTestFlightCheckpointResetPasswordCounter"
|
|
||||||
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter"
|
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter"
|
||||||
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword"
|
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword"
|
||||||
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert"
|
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert"
|
||||||
@@ -58,28 +49,15 @@ typedef enum {
|
|||||||
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
|
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
|
||||||
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
|
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
|
||||||
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
|
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
|
||||||
#define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten"
|
|
||||||
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
|
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
|
||||||
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
|
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
|
||||||
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
|
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
|
||||||
#define MPTestFlightCheckpointMPEntered @"MPTestFlightCheckpointMPEntered"
|
#define MPTestFlightCheckpointMPAsked @"MPTestFlightCheckpointMPAsked"
|
||||||
#define MPTestFlightCheckpointLocalStoreIncompatible @"MPTestFlightCheckpointLocalStoreIncompatible"
|
#define MPTestFlightCheckpointStoreIncompatible @"MPTestFlightCheckpointStoreIncompatible"
|
||||||
#define MPTestFlightCheckpointCloudStoreIncompatible @"MPTestFlightCheckpointCloudStoreIncompatible"
|
#define MPTestFlightCheckpointSetKeyphraseLength @"MPTestFlightCheckpointSetKeyphraseLength_%d"
|
||||||
#define MPTestFlightCheckpointSetKey @"MPTestFlightCheckpointSetKey"
|
#endif
|
||||||
#define MPTestFlightCheckpointCloudEnabled @"MPTestFlightCheckpointCloudEnabled"
|
|
||||||
#define MPTestFlightCheckpointCloudDisabled @"MPTestFlightCheckpointCloudDisabled"
|
|
||||||
#define MPTestFlightCheckpointSitesImported @"MPTestFlightCheckpointSitesImported"
|
|
||||||
#define MPTestFlightCheckpointSitesExported @"MPTestFlightCheckpointSitesExported"
|
|
||||||
|
|
||||||
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
|
|
||||||
#define MPNotificationKeySet @"MPNotificationKeySet"
|
|
||||||
#define MPNotificationKeyUnset @"MPNotificationKeyUnset"
|
|
||||||
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
|
|
||||||
|
|
||||||
NSData *keyForPassword(NSString *password);
|
|
||||||
NSData *keyIDForPassword(NSString *password);
|
|
||||||
NSData *keyIDForKey(NSData *key);
|
|
||||||
NSString *NSStringFromMPElementType(MPElementType type);
|
NSString *NSStringFromMPElementType(MPElementType type);
|
||||||
NSString *ClassNameFromMPElementType(MPElementType type);
|
NSString *ClassNameFromMPElementType(MPElementType type);
|
||||||
Class ClassFromMPElementType(MPElementType type);
|
Class ClassFromMPElementType(MPElementType type);
|
||||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter);
|
NSString *MPCalculateContent(MPElementType type, NSString *name, NSString *keyPhrase, int counter);
|
||||||
|
|||||||
@@ -11,48 +11,25 @@
|
|||||||
#import "MPElementStoredEntity.h"
|
#import "MPElementStoredEntity.h"
|
||||||
|
|
||||||
|
|
||||||
#define MP_salt nil
|
|
||||||
#define MP_N 16384
|
|
||||||
#define MP_r 8
|
|
||||||
#define MP_p 1
|
|
||||||
#define MP_dkLen 64
|
|
||||||
#define MP_hash PearlDigestSHA256
|
|
||||||
|
|
||||||
NSData *keyForPassword(NSString *password) {
|
|
||||||
|
|
||||||
NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
|
||||||
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p];
|
|
||||||
|
|
||||||
trc(@"Password: %@ derives to key ID: %@", password, [keyIDForKey(key) encodeHex]);
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
NSData *keyIDForPassword(NSString *password) {
|
|
||||||
|
|
||||||
return keyIDForKey(keyForPassword(password));
|
|
||||||
}
|
|
||||||
NSData *keyIDForKey(NSData *key) {
|
|
||||||
|
|
||||||
return [key hashWith:MP_hash];
|
|
||||||
}
|
|
||||||
NSString *NSStringFromMPElementType(MPElementType type) {
|
NSString *NSStringFromMPElementType(MPElementType type) {
|
||||||
|
|
||||||
if (!type)
|
if (!type)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedLong:
|
case MPElementTypeCalculatedLong:
|
||||||
return @"Long Password";
|
return @"Long Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPElementTypeCalculatedMedium:
|
||||||
return @"Medium Password";
|
return @"Medium Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedShort:
|
case MPElementTypeCalculatedShort:
|
||||||
return @"Short Password";
|
return @"Short Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPElementTypeCalculatedBasic:
|
||||||
return @"Basic Password";
|
return @"Basic Password";
|
||||||
|
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPElementTypeCalculatedPIN:
|
||||||
return @"PIN";
|
return @"PIN";
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal:
|
case MPElementTypeStoredPersonal:
|
||||||
@@ -72,19 +49,19 @@ Class ClassFromMPElementType(MPElementType type) {
|
|||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MPElementTypeGeneratedLong:
|
case MPElementTypeCalculatedLong:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPElementGeneratedEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedMedium:
|
case MPElementTypeCalculatedMedium:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPElementGeneratedEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedShort:
|
case MPElementTypeCalculatedShort:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPElementGeneratedEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedBasic:
|
case MPElementTypeCalculatedBasic:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPElementGeneratedEntity class];
|
||||||
|
|
||||||
case MPElementTypeGeneratedPIN:
|
case MPElementTypeCalculatedPIN:
|
||||||
return [MPElementGeneratedEntity class];
|
return [MPElementGeneratedEntity class];
|
||||||
|
|
||||||
case MPElementTypeStoredPersonal:
|
case MPElementTypeStoredPersonal:
|
||||||
@@ -104,59 +81,34 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static NSDictionary *MPTypes_ciphers = nil;
|
static NSDictionary *MPTypes_ciphers = nil;
|
||||||
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int32_t counter) {
|
NSString *MPCalculateContent(MPElementType type, NSString *name, NSString *keyPhrase, int counter) {
|
||||||
|
|
||||||
if (!(type & MPElementTypeClassGenerated)) {
|
assert(type & MPElementTypeClassCalculated);
|
||||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
if (!name) {
|
|
||||||
err(@"Missing name.");
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
if (!key) {
|
|
||||||
err(@"Key not set.");
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
uint32_t salt = (unsigned)counter;
|
|
||||||
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.
|
|
||||||
salt = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300;
|
|
||||||
|
|
||||||
if (MPTypes_ciphers == nil)
|
if (MPTypes_ciphers == nil)
|
||||||
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
|
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
|
||||||
withExtension:@"plist"]];
|
withExtension:@"plist"]];
|
||||||
|
|
||||||
// Determine the seed whose bytes will be used for calculating a password: sha1(name . '\0' . key . '\0' . salt)
|
// Determine the hash whose bytes will be used for calculating a password: md4(name-keyPhrase)
|
||||||
uint32_t nsalt = htonl(salt);
|
assert(name && keyPhrase);
|
||||||
trc(@"seed from: sha1(%@, %@, %u)", name, key, nsalt);
|
NSData *keyHash = [[NSString stringWithFormat:@"%@-%@-%d", name, keyPhrase, counter] hashWith:PearlDigestSHA1];
|
||||||
NSData *seed = [[NSData dataByConcatenatingWithDelimitor:'\0' datas:
|
const char *keyBytes = keyHash.bytes;
|
||||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
|
||||||
key,
|
|
||||||
[NSData dataWithBytes:&nsalt length:sizeof(nsalt)],
|
|
||||||
nil] hashWith:PearlDigestSHA1];
|
|
||||||
trc(@"seed is: %@", seed);
|
|
||||||
const char *seedBytes = seed.bytes;
|
|
||||||
|
|
||||||
// Determine the cipher from the first seed byte.
|
// Determine the cipher from the first hash byte.
|
||||||
assert([seed length]);
|
assert([keyHash length]);
|
||||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
|
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
|
||||||
valueForKey:NSStringFromMPElementType(type)];
|
valueForKey:NSStringFromMPElementType(type)];
|
||||||
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
NSString *cipher = [typeCiphers objectAtIndex:keyBytes[0] % [typeCiphers count]];
|
||||||
trc(@"type %d, ciphers: %@, selected: %@", type, typeCiphers, cipher);
|
|
||||||
|
|
||||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
// Encode the content, character by character, using subsequent hash bytes and the cipher.
|
||||||
assert([seed length] >= [cipher length] + 1);
|
assert([keyHash length] >= [cipher length] + 1);
|
||||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||||
uint16_t keyByte = htons(seedBytes[c + 1]);
|
const char keyByte = keyBytes[c + 1];
|
||||||
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
|
NSString *cipherClass = [cipher substringWithRange:NSMakeRange(c, 1)];
|
||||||
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
|
NSString *cipherClassCharacters = [[MPTypes_ciphers valueForKey:@"MPCharacterClasses"] valueForKey:cipherClass];
|
||||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)];
|
|
||||||
|
|
||||||
trc(@"class %@ has characters: %@, selected: %@", cipherClass, cipherClassCharacters, character);
|
[content appendString:[cipherClassCharacters substringWithRange:NSMakeRange(keyByte % [cipherClassCharacters length], 1)]];
|
||||||
[content appendString:character];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
//
|
|
||||||
// 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
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
//
|
|
||||||
// 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 GCC diagnostic ignored "-Wfour-char-constants"
|
|
||||||
static EventHotKeyID MPShowHotKey = { .signature = 'show', .id = 1 };
|
|
||||||
|
|
||||||
+ (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
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
//
|
|
||||||
// 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
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
//
|
|
||||||
// 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 keyID == %@",
|
|
||||||
query, query, [MPAppDelegate get].keyID];
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,691 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//
|
|
||||||
// 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"
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{\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\
|
|
||||||
}
|
|
||||||
@@ -1,523 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//
|
|
||||||
// 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,12 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2182" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.0" toolsVersion="1938" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment defaultVersion="1296" identifier="iOS"/>
|
|
||||||
<development defaultVersion="4200" identifier="xcode"/>
|
<development defaultVersion="4200" identifier="xcode"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1181"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="933"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Type View Controller - Type-->
|
|
||||||
<scene sceneID="WkW-SR-cr2">
|
<scene sceneID="WkW-SR-cr2">
|
||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="jZj-N1-rhF" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="jZj-N1-rhF" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
@@ -54,9 +52,9 @@
|
|||||||
<size key="shadowOffset" width="0.0" height="1"/>
|
<size key="shadowOffset" width="0.0" height="1"/>
|
||||||
</label>
|
</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">
|
<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"/>
|
<rect key="frame" x="20" y="184" width="280" height="20"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
@@ -64,7 +62,7 @@
|
|||||||
<rect key="frame" x="23" y="49" width="280" height="112"/>
|
<rect key="frame" x="23" y="49" width="280" height="112"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<string key="text">These types create a safe password for your site.
|
<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 lose 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>
|
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"/>
|
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -98,9 +96,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
<size key="shadowOffset" width="0.0" height="1"/>
|
<size key="shadowOffset" width="0.0" height="1"/>
|
||||||
</label>
|
</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">
|
<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"/>
|
<rect key="frame" x="20" y="24" width="280" height="20"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
@@ -131,9 +129,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
<size key="shadowOffset" width="0.0" height="1"/>
|
<size key="shadowOffset" width="0.0" height="1"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Word-like, 4 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="Zi3-26-3iq">
|
<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"/>
|
<rect key="frame" x="20" y="24" width="280" height="20"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
@@ -164,9 +162,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
<size key="shadowOffset" width="0.0" height="1"/>
|
<size key="shadowOffset" width="0.0" height="1"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="8 characters." lineBreakMode="tailTruncation" minimumFontSize="10" id="CYQ-D8-vNS">
|
<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"/>
|
<rect key="frame" x="20" y="24" width="280" height="20"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
@@ -197,9 +195,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
<size key="shadowOffset" width="0.0" height="1"/>
|
<size key="shadowOffset" width="0.0" height="1"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="4 numbers." lineBreakMode="tailTruncation" minimumFontSize="10" id="5Zm-AH-bAe">
|
<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"/>
|
<rect key="frame" x="20" y="24" width="280" height="20"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
@@ -253,9 +251,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
<size key="shadowOffset" width="0.0" height="1"/>
|
<size key="shadowOffset" width="0.0" height="1"/>
|
||||||
</label>
|
</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">
|
<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"/>
|
<rect key="frame" x="20" y="144" width="280" height="20"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
@@ -286,9 +284,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
<size key="shadowOffset" width="0.0" height="1"/>
|
<size key="shadowOffset" width="0.0" height="1"/>
|
||||||
</label>
|
</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">
|
<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"/>
|
<rect key="frame" x="20" y="24" width="280" height="20"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="GillSans-LightItalic" family="Gill Sans" pointSize="14"/>
|
<fontDescription key="fontDescription" name="GillSans-Light" family="Gill Sans" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
@@ -310,7 +308,6 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="996" y="182"/>
|
<point key="canvasLocation" x="996" y="182"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Main View Controller - Master Password-->
|
|
||||||
<scene sceneID="U26-Zf-euQ">
|
<scene sceneID="U26-Zf-euQ">
|
||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="mK2-p1-3zC" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="mK2-p1-3zC" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
@@ -335,7 +332,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
<rect key="frame" x="0.0" y="64" width="320" height="416"/>
|
<rect key="frame" x="0.0" y="64" width="320" height="416"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="background.png" id="0hY-LL-ITu">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="top" image="Content-Backdrop.png" id="0hY-LL-ITu">
|
||||||
<rect key="frame" x="0.0" y="44" width="320" height="372"/>
|
<rect key="frame" x="0.0" y="44" width="320" height="372"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
@@ -343,6 +340,10 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
<rect key="frame" x="0.0" y="43" width="320" height="175"/>
|
<rect key="frame" x="0.0" y="43" width="320" height="175"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="top" image="Content-Backdrop.png" id="enb-OH-DVZ">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="320" height="175"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
</imageView>
|
||||||
<imageView userInteractionEnabled="NO" alpha="0.80000000000000004" contentMode="scaleToFill" image="ui_panel_container.png" id="0Yh-Py-lB6">
|
<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"/>
|
<rect key="frame" x="11" y="20" width="298" height="87"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
@@ -371,8 +372,8 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
</button>
|
</button>
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.50000000000000011" contentMode="left" text="apple.com" lineBreakMode="tailTruncation" minimumFontSize="10" id="gSK-aB-wNI">
|
<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"/>
|
<rect key="frame" x="25" y="20" width="270" height="26"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="12"/>
|
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
@@ -380,9 +381,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
</label>
|
</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">
|
<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"/>
|
<rect key="frame" x="20" y="46" width="280" height="60"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<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"/>
|
<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"/>
|
<fontDescription key="fontDescription" name="Optima-ExtraBlack" family="Optima" pointSize="26"/>
|
||||||
<textInputTraits key="textInputTraits" autocorrectionType="no"/>
|
<textInputTraits key="textInputTraits" autocorrectionType="no"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="delegate" destination="PQa-Xl-A3x" id="qOM-gq-c6g"/>
|
<outlet property="delegate" destination="PQa-Xl-A3x" id="qOM-gq-c6g"/>
|
||||||
@@ -390,17 +391,16 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
</textField>
|
</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">
|
<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"/>
|
<rect key="frame" x="240" y="27" width="40" height="20"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="Copperplate-Bold" family="Copperplate" pointSize="17"/>
|
<fontDescription key="fontDescription" name="Optima-ExtraBlack" family="Optima" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="shadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<size key="shadowOffset" width="0.0" height="1"/>
|
<size key="shadowOffset" width="0.0" height="1"/>
|
||||||
</label>
|
</label>
|
||||||
<button opaque="NO" alpha="0.50000000000000011" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="jec-mu-nPt">
|
<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"/>
|
<rect key="frame" x="274.5" y="18.5" width="37" height="37"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<gestureRecognizers/>
|
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||||
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
|
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
|
||||||
<state key="normal" image="icon_plus.png">
|
<state key="normal" image="icon_plus.png">
|
||||||
@@ -412,11 +412,10 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
</state>
|
</state>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="incrementPasswordCounter" destination="PQa-Xl-A3x" eventType="touchUpInside" id="hMc-kb-yFA"/>
|
<action selector="incrementPasswordCounter" destination="PQa-Xl-A3x" eventType="touchUpInside" id="hMc-kb-yFA"/>
|
||||||
<outletCollection property="gestureRecognizers" destination="cZr-Fj-eBw" appends="YES" id="azb-m1-tta"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9FS-fS-xH6">
|
<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"/>
|
<rect key="frame" x="275" y="18.5" width="37" height="37"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||||
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
|
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
|
||||||
@@ -434,9 +433,9 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="Cei-5z-uWE">
|
<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"/>
|
<rect key="frame" x="10" y="115" width="300" height="46"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="15"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||||
<size key="titleShadowOffset" width="0.0" height="1"/>
|
<size key="titleShadowOffset" width="0.0" height="1"/>
|
||||||
<state key="normal" title="Long Password" backgroundImage="ui_button_standard_large.png">
|
<state key="normal" title="Long" backgroundImage="ui_button_standard_large.png">
|
||||||
<color key="titleColor" cocoaTouchSystemColor="lightTextColor"/>
|
<color key="titleColor" cocoaTouchSystemColor="lightTextColor"/>
|
||||||
<color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="titleShadowColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
</state>
|
</state>
|
||||||
@@ -448,23 +447,23 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" id="v2m-Gf-pEV">
|
<view userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" id="v2m-Gf-pEV">
|
||||||
<rect key="frame" x="55" y="5" width="210" height="60"/>
|
<rect key="frame" x="55" y="8" width="210" height="57"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="fdm-NG-GFC">
|
<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"/>
|
<rect key="frame" x="0.0" y="-1" width="210" height="60"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
</imageView>
|
</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">
|
<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="171" height="40"/>
|
<rect key="frame" x="20" y="0.0" width="170" height="37"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="icon_edit.png" id="KEn-n3-qhX">
|
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="icon_edit.png" id="KEn-n3-qhX">
|
||||||
<rect key="frame" x="48" y="8" width="24" height="24"/>
|
<rect key="frame" x="48.5" y="6.5" width="24" height="24"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||||
@@ -472,7 +471,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
|
|||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||||
</view>
|
</view>
|
||||||
<view clipsSubviews="YES" contentMode="scaleToFill" id="61G-By-qLB">
|
<view clipsSubviews="YES" alpha="0.60000000000000009" contentMode="scaleToFill" id="61G-By-qLB">
|
||||||
<rect key="frame" x="0.0" y="216" width="320" height="200"/>
|
<rect key="frame" x="0.0" y="216" width="320" height="200"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
@@ -537,31 +536,34 @@ L4m3P4sSw0rD</string>
|
|||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||||
</view>
|
</view>
|
||||||
<searchBar contentMode="redraw" barStyle="blackOpaque" placeholder="Enter site name" showsSearchResultsButton="YES" id="qeo-n2-WVh">
|
<searchBar contentMode="redraw" barStyle="blackOpaque" placeholder="Site" id="qeo-n2-WVh">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
<gestureRecognizers/>
|
<gestureRecognizers/>
|
||||||
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/>
|
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="delegate" destination="0QO-2P-OhD" id="Maj-Iz-6Hb"/>
|
<outlet property="delegate" destination="PQa-Xl-A3x" id="oXp-0r-Gkl"/>
|
||||||
</connections>
|
</connections>
|
||||||
</searchBar>
|
</searchBar>
|
||||||
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL">
|
<view userInteractionEnabled="NO" contentMode="scaleToFill" id="zOR-Du-qRL">
|
||||||
<rect key="frame" x="10" y="-25" width="300" height="60"/>
|
<rect key="frame" x="55" y="-27" width="210" height="57"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" id="ORD-Xv-bOQ">
|
<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"/>
|
<rect key="frame" x="0.0" y="-1" width="210" height="60"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<rect key="contentStretch" x="0.15000000000000002" y="0.15000000000000002" width="0.69999999999999973" height="0.69999999999999973"/>
|
|
||||||
</imageView>
|
</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">
|
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Enter the site's name." textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" id="21b-bH-lR9">
|
||||||
<rect key="frame" x="-20" y="9" width="340" height="21"/>
|
<rect key="frame" x="20" y="0.0" width="170" height="37"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
|
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="icon_edit.png" id="4Xf-mC-295">
|
||||||
|
<rect key="frame" x="49" y="6" width="24" height="24"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||||
</view>
|
</view>
|
||||||
@@ -596,54 +598,52 @@ L4m3P4sSw0rD</string>
|
|||||||
<outlet property="typeButton" destination="Cei-5z-uWE" id="4M1-d7-5Bh"/>
|
<outlet property="typeButton" destination="Cei-5z-uWE" id="4M1-d7-5Bh"/>
|
||||||
</connections>
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<pongPressGestureRecognizer allowableMovement="10" minimumPressDuration="0.5" id="cZr-Fj-eBw">
|
|
||||||
<connections>
|
|
||||||
<action selector="resetPasswordCounter:" destination="PQa-Xl-A3x" id="JL9-Ob-AjQ"/>
|
|
||||||
</connections>
|
|
||||||
</pongPressGestureRecognizer>
|
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="455" y="182"/>
|
<point key="canvasLocation" x="455" y="182"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Guide View Controller-->
|
|
||||||
<scene sceneID="Rt1-b4-sUB">
|
<scene sceneID="Rt1-b4-sUB">
|
||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="7yf-G7-kVy" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="7yf-G7-kVy" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
<viewController id="qz3-eG-aEi" customClass="MPGuideViewController" sceneMemberID="viewController">
|
<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">
|
<view key="view" contentMode="scaleToFill" id="cwa-ct-2DY">
|
||||||
<rect key="frame" x="0.0" y="20" width="320" height="460"/>
|
<rect key="frame" x="0.0" y="20" width="320" height="460"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" pagingEnabled="YES" showsVerticalScrollIndicator="NO" id="Nr4-hi-8b2">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_0.png" id="eJP-cS-VRU">
|
<imageView 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"/>
|
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_1.png" id="1J9-z9-h96">
|
<imageView 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"/>
|
<rect key="frame" x="320" y="0.0" width="320" height="460"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_2.png" id="8kf-Wm-F3L">
|
<imageView 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"/>
|
<rect key="frame" x="640" y="0.0" width="320" height="460"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_3.png" id="H5W-P4-o7O">
|
<imageView 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"/>
|
<rect key="frame" x="960" y="0.0" width="320" height="460"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_4.png" id="IXz-T9-Umi">
|
<imageView 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"/>
|
<rect key="frame" x="1280" y="0.0" width="320" height="460"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_5.png" id="Hd0-Or-naq">
|
<imageView 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"/>
|
<rect key="frame" x="1280" y="0.0" width="320" height="460"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="bottom" image="guide_page_6.png" id="1jq-Ic-GVZ">
|
<imageView 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"/>
|
<rect key="frame" x="1600" y="0.0" width="320" height="460"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" lineBreakMode="middleTruncation" id="q93-J1-auj">
|
<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"/>
|
<rect key="frame" x="1610" y="394" width="300" height="46"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||||
<size key="titleShadowOffset" width="0.0" height="1"/>
|
<size key="titleShadowOffset" width="0.0" height="1"/>
|
||||||
<state key="normal" title="Get started" backgroundImage="ui_button_green_large.png">
|
<state key="normal" title="Get started" backgroundImage="ui_button_green_large.png">
|
||||||
@@ -659,6 +659,9 @@ L4m3P4sSw0rD</string>
|
|||||||
</button>
|
</button>
|
||||||
</subviews>
|
</subviews>
|
||||||
</scrollView>
|
</scrollView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="scrollView" destination="Nr4-hi-8b2" id="8QD-sv-vu6"/>
|
<outlet property="scrollView" destination="Nr4-hi-8b2" id="8QD-sv-vu6"/>
|
||||||
</connections>
|
</connections>
|
||||||
@@ -666,113 +669,6 @@ L4m3P4sSw0rD</string>
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="455" y="785"/>
|
<point key="canvasLocation" x="455" y="785"/>
|
||||||
</scene>
|
</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">
|
<scene sceneID="8r0-wA-Zre">
|
||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Lcz-JH-B5B" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="Lcz-JH-B5B" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
@@ -794,8 +690,8 @@ L4m3P4sSw0rD</string>
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
|
<image name="Content-Backdrop.png" width="480" height="480"/>
|
||||||
<image name="Square-bottom.png" width="551" height="58"/>
|
<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_0.png" width="320" height="480"/>
|
||||||
<image name="guide_page_1.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_2.png" width="320" height="480"/>
|
||||||
@@ -806,7 +702,6 @@ L4m3P4sSw0rD</string>
|
|||||||
<image name="icon_cancel.png" width="32" height="32"/>
|
<image name="icon_cancel.png" width="32" height="32"/>
|
||||||
<image name="icon_edit.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="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_alert_black.png" width="235" height="81"/>
|
||||||
<image name="tip_basic_black.png" width="210" height="60"/>
|
<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_green_large.png" width="300" height="46"/>
|
||||||
@@ -816,11 +711,9 @@ L4m3P4sSw0rD</string>
|
|||||||
<image name="ui_list_middle.png" width="300" height="34"/>
|
<image name="ui_list_middle.png" width="300" height="34"/>
|
||||||
<image name="ui_panel_container.png" width="300" height="87"/>
|
<image name="ui_panel_container.png" width="300" height="87"/>
|
||||||
<image name="ui_panel_display.png" width="300" height="86"/>
|
<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>
|
</resources>
|
||||||
<simulatedMetricsContainer key="defaultSimulatedMetrics">
|
<simulatedMetricsContainer key="defaultSimulatedMetrics">
|
||||||
<nil key="statusBar"/>
|
<simulatedStatusBarMetrics key="statusBar" statusBarStyle="blackTranslucent"/>
|
||||||
<simulatedOrientationMetrics key="orientation"/>
|
<simulatedOrientationMetrics key="orientation"/>
|
||||||
<simulatedScreenMetrics key="destination"/>
|
<simulatedScreenMetrics key="destination"/>
|
||||||
</simulatedMetricsContainer>
|
</simulatedMetricsContainer>
|
||||||
@@ -5,26 +5,7 @@
|
|||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>M. Password</string>
|
<string>Passwords</string>
|
||||||
<key>CFBundleDocumentTypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleTypeExtensions</key>
|
|
||||||
<array>
|
|
||||||
<string>mpsites</string>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleTypeIconFiles</key>
|
|
||||||
<array/>
|
|
||||||
<key>CFBundleTypeName</key>
|
|
||||||
<string>Master Password sites</string>
|
|
||||||
<key>LSHandlerRank</key>
|
|
||||||
<string>Owner</string>
|
|
||||||
<key>LSItemContentTypes</key>
|
|
||||||
<array>
|
|
||||||
<string>com.lyndir.lhunath.MasterPassword.sites</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>${EXECUTABLE_NAME}</string>
|
<string>${EXECUTABLE_NAME}</string>
|
||||||
<key>CFBundleIconFiles</key>
|
<key>CFBundleIconFiles</key>
|
||||||
@@ -32,7 +13,6 @@
|
|||||||
<string>Icon.png</string>
|
<string>Icon.png</string>
|
||||||
<string>Icon@2x.png</string>
|
<string>Icon@2x.png</string>
|
||||||
<string>Icon-72.png</string>
|
<string>Icon-72.png</string>
|
||||||
<string>Icon-72@2x.png</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleIcons</key>
|
<key>CFBundleIcons</key>
|
||||||
<dict>
|
<dict>
|
||||||
@@ -43,7 +23,6 @@
|
|||||||
<string>Icon.png</string>
|
<string>Icon.png</string>
|
||||||
<string>Icon@2x.png</string>
|
<string>Icon@2x.png</string>
|
||||||
<string>Icon-72.png</string>
|
<string>Icon-72.png</string>
|
||||||
<string>Icon-72@2x.png</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>UIPrerenderedIcon</key>
|
<key>UIPrerenderedIcon</key>
|
||||||
<true/>
|
<true/>
|
||||||
@@ -67,28 +46,16 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>© 2011-2012, Lyndir</string>
|
<string>© 2011-2012, Lyndir</string>
|
||||||
<key>UIAppFonts</key>
|
|
||||||
<array>
|
|
||||||
<string>Exo-Black.otf</string>
|
|
||||||
<string>Exo-ExtraBold.otf</string>
|
|
||||||
<string>Exo-Bold.otf</string>
|
|
||||||
<string>Exo-DemiBold.otf</string>
|
|
||||||
<string>Exo-Medium.otf</string>
|
|
||||||
<string>Exo-Regular.otf</string>
|
|
||||||
<string>Exo-Light.otf</string>
|
|
||||||
<string>Exo-ExtraLight.otf</string>
|
|
||||||
<string>Exo-Thin.otf</string>
|
|
||||||
</array>
|
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>MainStoryboard_iPhone</string>
|
<string>MainStoryboard_iPhone</string>
|
||||||
|
<key>UIMainStoryboardFile~ipad</key>
|
||||||
|
<string>MainStoryboard_iPad</string>
|
||||||
<key>UIPrerenderedIcon</key>
|
<key>UIPrerenderedIcon</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
<array>
|
<array>
|
||||||
<string>armv7</string>
|
<string>armv7</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIStatusBarHidden</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIStatusBarStyle</key>
|
<key>UIStatusBarStyle</key>
|
||||||
<string>UIStatusBarStyleBlackOpaque</string>
|
<string>UIStatusBarStyleBlackOpaque</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
@@ -104,25 +71,5 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UTExportedTypeDeclarations</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>UTTypeDescription</key>
|
|
||||||
<string>Master Password sites</string>
|
|
||||||
<key>UTTypeIdentifier</key>
|
|
||||||
<string>com.lyndir.lhunath.MasterPassword.sites</string>
|
|
||||||
<key>UTTypeSize320IconFile</key>
|
|
||||||
<string></string>
|
|
||||||
<key>UTTypeSize64IconFile</key>
|
|
||||||
<string></string>
|
|
||||||
<key>UTTypeTagSpecification</key>
|
|
||||||
<dict>
|
|
||||||
<key>public.filename-extension</key>
|
|
||||||
<array>
|
|
||||||
<string>mpsites</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -5,18 +5,26 @@
|
|||||||
#import <Availability.h>
|
#import <Availability.h>
|
||||||
|
|
||||||
#ifndef __IPHONE_5_0
|
#ifndef __IPHONE_5_0
|
||||||
#warning "This project uses features only available in iOS SDK 5.0 and later."
|
#warning "This project uses features only available in iOS SDK 5.0 and later."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#import "Pearl-Prefix.pch"
|
|
||||||
|
|
||||||
#ifdef __OBJC__
|
#ifdef __OBJC__
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <CoreData/CoreData.h>
|
#import <CoreData/CoreData.h>
|
||||||
|
|
||||||
|
#ifndef PRODUCTION
|
||||||
#import "TestFlight.h"
|
#import "TestFlight.h"
|
||||||
|
#endif
|
||||||
#import "MPTypes.h"
|
|
||||||
#import "MPiOSConfig.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define PEARL
|
||||||
|
#define PEARL_CRYPTO
|
||||||
|
#define PEARL_UIKIT
|
||||||
|
|
||||||
|
#import "Pearl.h"
|
||||||
|
#import "Pearl-Crypto.h"
|
||||||
|
#import "Pearl-UIKit.h"
|
||||||
|
|
||||||
|
#import "MPTypes.h"
|
||||||
|
#import "MPConfig.h"
|
||||||
@@ -4,12 +4,13 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.Mac</string>
|
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword</string>
|
||||||
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
|
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
|
||||||
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string>
|
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword</string>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>keychain-access-groups</key>
|
||||||
<true/>
|
<array>
|
||||||
|
<string>$(AppIdentifierPrefix)com.lyndir.lhunath.MasterPassword</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="872" systemVersion="11C74" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||||
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
|
||||||
<attribute name="keyID" attributeType="Binary" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
|
|
||||||
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
|
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
|
||||||
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="YES"/>
|
<attribute name="mpHashHex" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="type" attributeType="Integer 16" defaultValueString="16" 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="uses" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||||
<attribute name="counter" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
<attribute name="counter" optional="YES" attributeType="Integer 16" defaultValueString="1" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
|
||||||
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
|
<fetchRequest name="MPSearchElement" entity="MPElementEntity" predicateString="name BEGINSWITH[cd] $query AND mpHashHex == $mpHashHex"/>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="120"/>
|
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="135"/>
|
||||||
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
|
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||||
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
|
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
|
||||||
</elements>
|
</elements>
|
||||||
|
|||||||
BIN
MasterPassword/Resources/Automaton/ui_background.png
Normal file
|
After Width: | Height: | Size: 1023 B |
BIN
MasterPassword/Resources/Automaton/ui_background@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
MasterPassword/Resources/Automaton/ui_box_checked.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
MasterPassword/Resources/Automaton/ui_box_checked@2x.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
MasterPassword/Resources/Automaton/ui_box_empty.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
MasterPassword/Resources/Automaton/ui_box_empty@2x.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
MasterPassword/Resources/Automaton/ui_box_filled.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
MasterPassword/Resources/Automaton/ui_box_filled@2x.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
MasterPassword/Resources/Automaton/ui_button_green_large.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
MasterPassword/Resources/Automaton/ui_button_green_large@2x.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
MasterPassword/Resources/Automaton/ui_button_green_small.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |