2
0

Crashlytics, Localytics, TestFlight + password change warnings.

[ADDED]     Crashlytics, Localytics.
[IMPROVED]  Async TestFlight takeOff.
[REMOVED]   TestFlight token hidden.
[FIXED]     Warnings, mostly to do with sign conversions.
[ADDED]     Warning messages whenever site's password changes, allowing
            the user to cancel the operation.
[ADDED]     Make password counter resettable by holding down on the
            counter increment button.
This commit is contained in:
Maarten Billemont 2012-05-03 16:49:15 +02:00
parent 657fef6249
commit 950ce888e2
43 changed files with 3751 additions and 346 deletions

View File

@ -0,0 +1 @@
Versions/Current/Crashlytics

View File

@ -0,0 +1 @@
Versions/Current/Headers

View File

@ -0,0 +1 @@
Versions/Current/Resources

Binary file not shown.

View File

@ -0,0 +1,89 @@
//
// 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

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>11C74</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Crashlytics</string>
<key>CFBundleIdentifier</key>
<string>com.crashlytics.ios</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Crashlytics</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.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>

View File

@ -0,0 +1 @@
../../../run

View File

@ -0,0 +1,15 @@
#!/usr/bin/ruby
#
# WARNING: DO NOT MODIFY THIS FILE.
#
# Crashlytics
# Crashlytics Version: 1.0.0.1
#
# Copyright Crashlytics, Inc. 2012. All rights reserved.
#
require 'pathname'
path = Pathname.new(__FILE__).parent
`#{path}/../../../run`

View File

@ -0,0 +1 @@
A

BIN
Crashlytics/Crashlytics.framework/run vendored Executable file

Binary file not shown.

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API Key</key>
<string></string>
</dict>
</plist>

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit 9b1a79dc981997c2c0c0276bca4aad2eeb9de9cd
Subproject commit 32660d8ebc2701bf8f702c64c1bdc1b9382b1612

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Key.development</key>
<string>e6238ceba8ec92832e77b1b-9ccd60bc-c39b-11e0-06e4-007f58cb3154</string>
<key>Key.distribution</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,57 @@
//
// LocalyticsDatabase.h
// LocalyticsDemo
//
// Created by jkaufman on 5/26/11.
// Copyright 2011 Localytics. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <sqlite3.h>
#define MAX_DATABASE_SIZE 500000 // The maximum allowed disk size of the primary database file at open, in bytes
#define VACUUM_THRESHOLD 0.8 // The database is vacuumed after its size exceeds this proportion of the maximum.
@interface LocalyticsDatabase : NSObject {
sqlite3 *_databaseConnection;
}
+ (LocalyticsDatabase *)sharedLocalyticsDatabase;
- (NSUInteger)databaseSize;
- (int)eventCount;
- (NSTimeInterval)createdTimestamp;
- (BOOL)beginTransaction:(NSString *)name;
- (BOOL)releaseTransaction:(NSString *)name;
- (BOOL)rollbackTransaction:(NSString *)name;
- (BOOL)incrementLastUploadNumber:(int *)uploadNumber;
- (BOOL)incrementLastSessionNumber:(int *)sessionNumber;
- (BOOL)addEventWithBlobString:(NSString *)blob;
- (BOOL)addCloseEventWithBlobString:(NSString *)blob;
- (BOOL)addFlowEventWithBlobString:(NSString *)blob;
- (BOOL)removeLastCloseAndFlowEvents;
- (BOOL)addHeaderWithSequenceNumber:(int)number blobString:(NSString *)blob rowId:(sqlite3_int64 *)insertedRowId;
- (int)unstagedEventCount;
- (BOOL)stageEventsForUpload:(sqlite3_int64)headerId;
- (BOOL)updateAppKey:(NSString *)appKey;
- (NSString *)uploadBlobString;
- (BOOL)deleteUploadedData;
- (BOOL)resetAnalyticsData;
- (BOOL)vacuumIfRequired;
- (NSTimeInterval)lastSessionStartTimestamp;
- (BOOL)setLastsessionStartTimestamp:(NSTimeInterval)timestamp;
- (BOOL)isOptedOut;
- (BOOL)setOptedOut:(BOOL)optOut;
- (NSString *)installId;
- (NSString *)appKey; // Most recent app key-- may not be that used to open the session.
- (NSString *)customDimension:(int)dimension;
- (BOOL)setCustomDimension:(int)dimension value:(NSString *)value;
@end

View File

@ -0,0 +1,743 @@
//
// LocalyticsDatabase.m
// LocalyticsDemo
//
// Created by jkaufman on 5/26/11.
// Copyright 2011 Localytics. All rights reserved.
//
#import "LocalyticsDatabase.h"
#define LOCALYTICS_DIR @".localytics" // Name for the directory in which Localytics database is stored
#define LOCALYTICS_DB @"localytics" // File name for the database (without extension)
#define BUSY_TIMEOUT 30 // Maximum time SQlite will busy-wait for the database to unlock before returning SQLITE_BUSY
@interface LocalyticsDatabase ()
- (int)schemaVersion;
- (void)createSchema;
- (void)upgradeToSchemaV2;
- (void)upgradeToSchemaV3;
- (void)moveDbToCaches;
- (NSString *)randomUUID;
@end
@implementation LocalyticsDatabase
// The singleton database object.
static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
+ (NSString *)localyticsDirectoryPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [[paths objectAtIndex:0] stringByAppendingPathComponent:LOCALYTICS_DIR];
}
+ (NSString *)localyticsDatabasePath {
NSString *path = [[LocalyticsDatabase localyticsDirectoryPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.sqlite", LOCALYTICS_DB]];
return path;
}
#pragma mark Singleton Class
+ (LocalyticsDatabase *)sharedLocalyticsDatabase {
@synchronized(self) {
if (_sharedLocalyticsDatabase == nil) {
_sharedLocalyticsDatabase = [[self alloc] init];
}
}
return _sharedLocalyticsDatabase;
}
- (LocalyticsDatabase *)init {
if((self = [super init])) {
// Mover any data that a previous library may have left in the documents directory
[self moveDbToCaches];
// Create directory structure for Localytics.
NSString *directoryPath = [LocalyticsDatabase localyticsDirectoryPath];
if (![[NSFileManager defaultManager] fileExistsAtPath:directoryPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];
}
// Attempt to open database. It will be created if it does not exist, already.
NSString *dbPath = [LocalyticsDatabase localyticsDatabasePath];
int code = sqlite3_open([dbPath UTF8String], &_databaseConnection);
// If we were unable to open the database, it is likely corrupted. Clobber it and move on.
if (code != SQLITE_OK) {
[[NSFileManager defaultManager] removeItemAtPath:dbPath error:nil];
code = sqlite3_open([dbPath UTF8String], &_databaseConnection);
}
// Check db connection, creating schema if necessary.
if (code == SQLITE_OK) {
sqlite3_busy_timeout(_databaseConnection, BUSY_TIMEOUT); // Defaults to 0, otherwise.
if ([self schemaVersion] == 0) {
[self createSchema];
}
}
// Perform any Migrations if necessary
if ([self schemaVersion] < 2) {
[self upgradeToSchemaV2];
}
if ([self schemaVersion] < 3) {
[self upgradeToSchemaV3];
}
}
return self;
}
#pragma mark - Database
- (BOOL)beginTransaction:(NSString *)name {
const char *sql = [[NSString stringWithFormat:@"SAVEPOINT %@", name] cStringUsingEncoding:NSUTF8StringEncoding];
int code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (BOOL)releaseTransaction:(NSString *)name {
const char *sql = [[NSString stringWithFormat:@"RELEASE SAVEPOINT %@", name] cStringUsingEncoding:NSUTF8StringEncoding];
int code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (BOOL)rollbackTransaction:(NSString *)name {
const char *sql = [[NSString stringWithFormat:@"ROLLBACK SAVEPOINT %@", name] cStringUsingEncoding:NSUTF8StringEncoding];
int code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (int)schemaVersion {
int version = 0;
const char *sql = "SELECT MAX(schema_version) FROM localytics_info";
sqlite3_stmt *selectSchemaVersion;
if(sqlite3_prepare_v2(_databaseConnection, sql, -1, &selectSchemaVersion, NULL) == SQLITE_OK) {
if(sqlite3_step(selectSchemaVersion) == SQLITE_ROW) {
version = sqlite3_column_int(selectSchemaVersion, 0);
}
}
sqlite3_finalize(selectSchemaVersion);
return version;
}
- (NSString *)installId {
NSString *installId = nil;
sqlite3_stmt *selectInstallId;
sqlite3_prepare_v2(_databaseConnection, "SELECT install_id FROM localytics_info", -1, &selectInstallId, NULL);
int code = sqlite3_step(selectInstallId);
if (code == SQLITE_ROW && sqlite3_column_text(selectInstallId, 0)) {
installId = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectInstallId, 0)];
}
sqlite3_finalize(selectInstallId);
return installId;
}
- (NSString *)appKey {
NSString *appKey = nil;
sqlite3_stmt *selectAppKey;
sqlite3_prepare_v2(_databaseConnection, "SELECT app_key FROM localytics_info", -1, &selectAppKey, NULL);
int code = sqlite3_step(selectAppKey);
if (code == SQLITE_ROW && sqlite3_column_text(selectAppKey, 0)) {
appKey = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectAppKey, 0)];
}
sqlite3_finalize(selectAppKey);
return appKey;
}
// Due to the new iOS storage guidelines it is necessary to move the database out of the documents directory
// and into the /library/caches directory
- (void)moveDbToCaches {
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *localyticsDocumentsDirectory = [[documentPaths objectAtIndex:0] stringByAppendingPathComponent:LOCALYTICS_DIR];
NSArray *cachesPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *localyticsCachesDirectory = [[cachesPaths objectAtIndex:0] stringByAppendingPathComponent:LOCALYTICS_DIR];
// If the old directory doesn't exist, there is nothing else to do here
if([[NSFileManager defaultManager] fileExistsAtPath:localyticsDocumentsDirectory] == NO)
{
return;
}
// Try to move the directory
if(NO == [[NSFileManager defaultManager] moveItemAtPath:localyticsDocumentsDirectory
toPath:localyticsCachesDirectory
error:nil])
{
// If the move failed try and, delete the old directory
[ [NSFileManager defaultManager] removeItemAtPath:localyticsDocumentsDirectory error:nil];
}
}
- (void)createSchema {
int code = SQLITE_OK;
// Execute schema creation within a single transaction.
code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"CREATE TABLE upload_headers ("
"sequence_number INTEGER PRIMARY KEY, "
"blob_string TEXT)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"CREATE TABLE events ("
"event_id INTEGER PRIMARY KEY AUTOINCREMENT, " // In case foreign key constraints are reintroduced.
"upload_header INTEGER, "
"blob_string TEXT NOT NULL)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"CREATE TABLE localytics_info ("
"schema_version INTEGER PRIMARY KEY, "
"last_upload_number INTEGER, "
"last_session_number INTEGER, "
"opt_out BOOLEAN, "
"last_close_event INTEGER, "
"last_flow_event INTEGER, "
"last_session_start REAL, "
"app_key CHAR(64), "
"custom_d0 CHAR(64), "
"custom_d1 CHAR(64), "
"custom_d2 CHAR(64), "
"custom_d3 CHAR(64) "
")",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"INSERT INTO localytics_info (schema_version, last_upload_number, last_session_number, opt_out) "
"VALUES (3, 0, 0, 0)", NULL, NULL, NULL);
}
// Commit transaction.
if (code == SQLITE_OK || code == SQLITE_DONE) {
sqlite3_exec(_databaseConnection, "COMMIT", NULL, NULL, NULL);
} else {
sqlite3_exec(_databaseConnection, "ROLLBACK", NULL, NULL, NULL);
}
}
// V2 adds a unique identifier for each installation
// This identifier has been moved to user preferences so the database an live in the caches directory
// Also adds storage for custom dimensions
- (void)upgradeToSchemaV2 {
int code = SQLITE_OK;
code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD install_id CHAR(40)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD custom_d0 CHAR(64)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD custom_d1 CHAR(64)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD custom_d2 CHAR(64)",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD custom_d3 CHAR(64)",
NULL, NULL, NULL);
}
// Attempt to set schema version and install_id regardless of the result code following the ALTER statements above.
// This is necessary because a previous version of the library performed the migration without setting these values.
// The transaction will succeed even if the individual statements fail with errors (eg. "duplicate column name").
sqlite3_stmt *updateLocalyticsInfo;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info set install_id = ?, schema_version = 2 ", -1, &updateLocalyticsInfo, NULL);
sqlite3_bind_text (updateLocalyticsInfo, 1, [[self randomUUID] UTF8String], -1, SQLITE_TRANSIENT);
code = sqlite3_step(updateLocalyticsInfo);
sqlite3_finalize(updateLocalyticsInfo);
// Commit transaction.
if (code == SQLITE_OK || code == SQLITE_DONE) {
sqlite3_exec(_databaseConnection, "COMMIT", NULL, NULL, NULL);
} else {
sqlite3_exec(_databaseConnection, "ROLLBACK", NULL, NULL, NULL);
}
}
// V3 adds a field for the last app key and patches a V2 migration issue.
- (void)upgradeToSchemaV3 {
sqlite3_exec(_databaseConnection,
"ALTER TABLE localytics_info ADD app_key CHAR(64)",
NULL, NULL, NULL);
}
- (NSUInteger)databaseSize {
NSUInteger size = 0;
NSDictionary *fileAttributes = [[NSFileManager defaultManager]
attributesOfItemAtPath:[LocalyticsDatabase localyticsDatabasePath]
error:nil];
size = [fileAttributes fileSize];
return size;
}
- (int) eventCount {
int count = 0;
const char *sql = "SELECT count(*) FROM events";
sqlite3_stmt *selectEventCount;
if(sqlite3_prepare_v2(_databaseConnection, sql, -1, &selectEventCount, NULL) == SQLITE_OK)
{
if(sqlite3_step(selectEventCount) == SQLITE_ROW) {
count = sqlite3_column_int(selectEventCount, 0);
}
}
sqlite3_finalize(selectEventCount);
return count;
}
- (NSTimeInterval)createdTimestamp {
NSTimeInterval timestamp = 0;
NSDictionary *fileAttributes = [[NSFileManager defaultManager]
attributesOfItemAtPath:[LocalyticsDatabase localyticsDatabasePath]
error:nil];
timestamp = [[fileAttributes fileCreationDate] timeIntervalSince1970];
return timestamp;
}
- (NSTimeInterval)lastSessionStartTimestamp {
NSTimeInterval lastSessionStart = 0;
sqlite3_stmt *selectLastSessionStart;
sqlite3_prepare_v2(_databaseConnection, "SELECT last_session_start FROM localytics_info", -1, &selectLastSessionStart, NULL);
int code = sqlite3_step(selectLastSessionStart);
if (code == SQLITE_ROW) {
lastSessionStart = sqlite3_column_double(selectLastSessionStart, 0) == 1;
}
sqlite3_finalize(selectLastSessionStart);
return lastSessionStart;
}
- (BOOL)setLastsessionStartTimestamp:(NSTimeInterval)timestamp {
sqlite3_stmt *updateLastSessionStart;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET last_session_start = ?", -1, &updateLastSessionStart, NULL);
sqlite3_bind_double(updateLastSessionStart, 1, timestamp);
int code = sqlite3_step(updateLastSessionStart);
sqlite3_finalize(updateLastSessionStart);
return code == SQLITE_DONE;
}
- (BOOL)isOptedOut {
BOOL optedOut = NO;
sqlite3_stmt *selectOptOut;
sqlite3_prepare_v2(_databaseConnection, "SELECT opt_out FROM localytics_info", -1, &selectOptOut, NULL);
int code = sqlite3_step(selectOptOut);
if (code == SQLITE_ROW) {
optedOut = sqlite3_column_int(selectOptOut, 0) == 1;
}
sqlite3_finalize(selectOptOut);
return optedOut;
}
- (BOOL)setOptedOut:(BOOL)optOut {
sqlite3_stmt *updateOptedOut;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET opt_out = ?", -1, &updateOptedOut, NULL);
sqlite3_bind_int(updateOptedOut, 1, optOut);
int code = sqlite3_step(updateOptedOut);
sqlite3_finalize(updateOptedOut);
return code == SQLITE_OK;
}
- (NSString *)customDimension:(int)dimension {
if(dimension < 0 || dimension > 3) {
return nil;
}
NSString *value = nil;
NSString *query = [NSString stringWithFormat:@"select custom_d%i from localytics_info", dimension];
sqlite3_stmt *selectCustomDim;
sqlite3_prepare_v2(_databaseConnection, [query UTF8String], -1, &selectCustomDim, NULL);
int code = sqlite3_step(selectCustomDim);
if (code == SQLITE_ROW && sqlite3_column_text(selectCustomDim, 0)) {
value = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectCustomDim, 0)];
}
sqlite3_finalize(selectCustomDim);
return value;
}
- (BOOL)setCustomDimension:(int)dimension value:(NSString *)value {
if(dimension < 0 || dimension > 3) {
return false;
}
NSString *query = [NSString stringWithFormat:@"update localytics_info SET custom_d%i = %@",
dimension,
(value == nil) ? @"null" : [NSString stringWithFormat:@"\"%@\"", value]];
int code = sqlite3_exec(_databaseConnection, [query UTF8String], NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (BOOL)incrementLastUploadNumber:(int *)uploadNumber {
NSString *t = @"increment_upload_number";
int code = SQLITE_OK;
code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
if(code == SQLITE_OK) {
// Increment value
code = sqlite3_exec(_databaseConnection,
"UPDATE localytics_info "
"SET last_upload_number = (last_upload_number + 1)",
NULL, NULL, NULL);
}
if(code == SQLITE_OK) {
// Retrieve new value
sqlite3_stmt *selectUploadNumber;
sqlite3_prepare_v2(_databaseConnection,
"SELECT last_upload_number FROM localytics_info",
-1, &selectUploadNumber, NULL);
code = sqlite3_step(selectUploadNumber);
if (code == SQLITE_ROW) {
*uploadNumber = sqlite3_column_int(selectUploadNumber, 0);
}
sqlite3_finalize(selectUploadNumber);
}
if(code == SQLITE_ROW) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return code == SQLITE_ROW;
}
- (BOOL)incrementLastSessionNumber:(int *)sessionNumber {
NSString *t = @"increment_session_number";
int code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
if(code == SQLITE_OK) {
// Increment value
code = sqlite3_exec(_databaseConnection,
"UPDATE localytics_info "
"SET last_session_number = (last_session_number + 1)",
NULL, NULL, NULL);
}
if(code == SQLITE_OK) {
// Retrieve new value
sqlite3_stmt *selectSessionNumber;
sqlite3_prepare_v2(_databaseConnection,
"SELECT last_session_number FROM localytics_info",
-1, &selectSessionNumber, NULL);
code = sqlite3_step(selectSessionNumber);
if (code == SQLITE_ROW && sessionNumber != NULL) {
*sessionNumber = sqlite3_column_int(selectSessionNumber, 0);
}
sqlite3_finalize(selectSessionNumber);
}
if(code == SQLITE_ROW) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return code == SQLITE_ROW;
}
- (BOOL)addEventWithBlobString:(NSString *)blob {
int code = SQLITE_OK;
sqlite3_stmt *insertEvent;
sqlite3_prepare_v2(_databaseConnection, "INSERT INTO events (blob_string) VALUES (?)", -1, &insertEvent, NULL);
sqlite3_bind_text(insertEvent, 1, [blob UTF8String], -1, SQLITE_TRANSIENT);
code = sqlite3_step(insertEvent);
sqlite3_finalize(insertEvent);
return code == SQLITE_DONE;
}
- (BOOL)addCloseEventWithBlobString:(NSString *)blob {
NSString *t = @"add_close_event";
BOOL success = [self beginTransaction:t];
// Add close event.
if (success) {
success = [self addEventWithBlobString:blob];
}
// Record row id to localytics_info so that it can be removed if the session resumes.
if (success) {
sqlite3_stmt *updateCloseEvent;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET last_close_event = (SELECT event_id FROM events WHERE rowid = ?)", -1, &updateCloseEvent, NULL);
sqlite3_int64 lastRow = sqlite3_last_insert_rowid(_databaseConnection);
sqlite3_bind_int64(updateCloseEvent, 1, lastRow);
int code = sqlite3_step(updateCloseEvent);
sqlite3_finalize(updateCloseEvent);
success = code == SQLITE_DONE;
}
if (success) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return success;
}
- (BOOL)addFlowEventWithBlobString:(NSString *)blob {
NSString *t = @"add_flow_event";
BOOL success = [self beginTransaction:t];
// Add flow event.
if (success) {
success = [self addEventWithBlobString:blob];
}
// Record row id to localytics_info so that it can be removed if the session resumes.
if (success) {
sqlite3_stmt *updateFlowEvent;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET last_flow_event = (SELECT event_id FROM events WHERE rowid = ?)", -1, &updateFlowEvent, NULL);
sqlite3_int64 lastRow = sqlite3_last_insert_rowid(_databaseConnection);
sqlite3_bind_int64(updateFlowEvent, 1, lastRow);
int code = sqlite3_step(updateFlowEvent);
sqlite3_finalize(updateFlowEvent);
success = code == SQLITE_DONE;
}
if (success) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return success;
}
- (BOOL)removeLastCloseAndFlowEvents {
// Attempt to remove the last recorded close event.
// Fail quietly if none was saved or it was previously removed.
int code = sqlite3_exec(_databaseConnection, "DELETE FROM events WHERE event_id = (SELECT last_close_event FROM localytics_info) OR event_id = (SELECT last_flow_event FROM localytics_info)", NULL, NULL, NULL);
return code == SQLITE_OK;
}
- (BOOL)addHeaderWithSequenceNumber:(int)number blobString:(NSString *)blob rowId:(sqlite3_int64 *)insertedRowId {
sqlite3_stmt *insertHeader;
sqlite3_prepare_v2(_databaseConnection, "INSERT INTO upload_headers (sequence_number, blob_string) VALUES (?, ?)", -1, &insertHeader, NULL);
sqlite3_bind_int(insertHeader, 1, number);
sqlite3_bind_text(insertHeader, 2, [blob UTF8String], -1, SQLITE_TRANSIENT);
int code = sqlite3_step(insertHeader);
sqlite3_finalize(insertHeader);
if (code == SQLITE_DONE && insertedRowId != NULL) {
*insertedRowId = sqlite3_last_insert_rowid(_databaseConnection);
}
return code == SQLITE_DONE;
}
- (int)unstagedEventCount {
int rowCount = 0;
sqlite3_stmt *selectEventCount;
sqlite3_prepare_v2(_databaseConnection, "SELECT COUNT(*) FROM events WHERE UPLOAD_HEADER IS NULL", -1, &selectEventCount, NULL);
int code = sqlite3_step(selectEventCount);
if (code == SQLITE_ROW) {
rowCount = sqlite3_column_int(selectEventCount, 0);
}
sqlite3_finalize(selectEventCount);
return rowCount;
}
- (BOOL)stageEventsForUpload:(sqlite3_int64)headerId {
// Associate all outstanding events with the given upload header ID.
NSString *stageEvents = [NSString stringWithFormat:@"UPDATE events SET upload_header = ? WHERE upload_header IS NULL"];
sqlite3_stmt *updateEvents;
sqlite3_prepare_v2(_databaseConnection, [stageEvents UTF8String], -1, &updateEvents, NULL);
sqlite3_bind_int(updateEvents, 1, headerId);
int code = sqlite3_step(updateEvents);
sqlite3_finalize(updateEvents);
BOOL success = (code == SQLITE_DONE);
return success;
}
- (BOOL)updateAppKey:(NSString *)appKey {
sqlite3_stmt *updateAppKey;
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info set app_key = ?", -1, &updateAppKey, NULL);
sqlite3_bind_text (updateAppKey, 1, [appKey UTF8String], -1, SQLITE_TRANSIENT);
int code = sqlite3_step(updateAppKey);
sqlite3_finalize(updateAppKey);
BOOL success = (code == SQLITE_DONE);
return success;
}
- (NSString *)uploadBlobString {
// Retrieve the blob strings of each upload header and its child events, in order.
const char *sql = "SELECT * FROM ( "
" SELECT h.blob_string AS 'blob', h.sequence_number as 'seq', 0 FROM upload_headers h"
" UNION ALL "
" SELECT e.blob_string AS 'blob', e.upload_header as 'seq', 1 FROM events e"
") "
"ORDER BY 2, 3";
sqlite3_stmt *selectBlobs;
sqlite3_prepare_v2(_databaseConnection, sql, -1, &selectBlobs, NULL);
NSMutableString *uploadBlobString = [NSMutableString string];
while (sqlite3_step(selectBlobs) == SQLITE_ROW) {
const char *blob = (const char *)sqlite3_column_text(selectBlobs, 0);
if (blob != NULL) {
NSString *blobString = [[NSString alloc] initWithCString:blob encoding:NSUTF8StringEncoding];
[uploadBlobString appendString:blobString];
[blobString release];
}
}
sqlite3_finalize(selectBlobs);
return [[uploadBlobString copy] autorelease];
}
- (BOOL)deleteUploadedData {
// Delete all headers and staged events.
NSString *t = @"delete_upload_data";
int code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection, "DELETE FROM events WHERE upload_header IS NOT NULL", NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection, "DELETE FROM upload_headers", NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return code == SQLITE_OK;
}
- (BOOL)resetAnalyticsData {
// Delete or zero all analytics data.
// Reset: headers, events, session number, upload number, last session start, last close event, and last flow event.
// Unaffected: schema version, opt out status, install ID (deprecated), and app key.
NSString *t = @"reset_analytics_data";
int code = [self beginTransaction:t] ? SQLITE_OK : SQLITE_ERROR;
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection, "DELETE FROM events", NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection, "DELETE FROM upload_headers", NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
code = sqlite3_exec(_databaseConnection,"UPDATE localytics_info SET last_session_number = 0, last_upload_number = 0,"
"last_close_event = null, last_flow_event = null, last_session_start = null, "
"custom_d0 = null, custom_d1 = null, custom_d2 = null, custom_d3 = null",
NULL, NULL, NULL);
}
if (code == SQLITE_OK) {
[self releaseTransaction:t];
} else {
[self rollbackTransaction:t];
}
return code == SQLITE_OK;
}
- (BOOL)vacuumIfRequired {
int code = SQLITE_OK;
if ([self databaseSize] > MAX_DATABASE_SIZE * VACUUM_THRESHOLD) {
code = sqlite3_exec(_databaseConnection, "VACUUM", NULL, NULL, NULL);
}
return code == SQLITE_OK;
}
- (NSString *)randomUUID {
CFUUIDRef theUUID = CFUUIDCreate(NULL);
CFStringRef stringUUID = CFUUIDCreateString(NULL, theUUID);
CFRelease(theUUID);
return [(NSString *)stringUUID autorelease];
}
#pragma mark - Lifecycle
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (_sharedLocalyticsDatabase == nil) {
_sharedLocalyticsDatabase = [super allocWithZone:zone];
return _sharedLocalyticsDatabase;
}
}
// returns nil on subsequent allocations
return nil;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
// maximum value of an unsigned int - prevents additional retains for the class
return UINT_MAX;
}
- (oneway void)release {
// ignore release commands
}
- (id)autorelease {
return self;
}
- (void)dealloc {
sqlite3_close(_databaseConnection);
[super dealloc];
}
@end

View File

@ -0,0 +1,216 @@
// LocalyticsSession.h
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import <UIKit/UIKit.h>
// Set this to true to enable localytics traces (useful for debugging)
#define DO_LOCALYTICS_LOGGING false
/*!
@class LocalyticsSession
@discussion The class which manages creating, collecting, & uploading a Localytics session.
Please see the following guides for information on how to best use this
library, sample code, and other useful information:
<ul>
<li><a href="http://wiki.localytics.com/index.php?title=Developer's_Integration_Guide">Main Developer's Integration Guide</a></li>
</ul>
<strong>Best Practices</strong>
<ul>
<li>Instantiate the LocalyticsSession object in applicationDidFinishLaunching.</li>
<li>Open your session and begin your uploads in applicationDidFinishLaunching. This way the
upload has time to complete and it all happens before your users have a
chance to begin any data intensive actions of their own.</li>
<li>Close the session in applicationWillTerminate, and in applicationDidEnterBackground.</li>
<li>Resume the session in applicationWillEnterForeground.</li>
<li>Do not call any Localytics functions inside a loop. Instead, calls
such as <code>tagEvent</code> should follow user actions. This limits the
amount of data which is stored and uploaded.</li>
<li>Do not use multiple LocalticsSession objects to upload data with
multiple application keys. This can cause invalid state.</li>
</ul>
@author Localytics
*/
@interface LocalyticsSession : NSObject {
BOOL _hasInitialized; // Whether or not the session object has been initialized.
BOOL _isSessionOpen; // Whether or not this session has been opened.
float _backgroundSessionTimeout; // If an App stays in the background for more
// than this many seconds, start a new session
// when it returns to foreground.
@private
#pragma mark Member Variables
dispatch_queue_t _queue; // Queue of Localytics block objects.
dispatch_group_t _criticalGroup; // Group of blocks the must complete before backgrounding.
NSString *_sessionUUID; // Unique identifier for this session.
NSString *_applicationKey; // Unique identifier for the instrumented application
NSTimeInterval _lastSessionStartTimestamp; // The start time of the most recent session.
NSDate *_sessionResumeTime; // Time session was started or resumed.
NSDate *_sessionCloseTime; // Time session was closed.
NSMutableString *_unstagedFlowEvents; // Comma-delimited list of app screens and events tagged during this
// session that have NOT been staged for upload.
NSMutableString *_stagedFlowEvents; // App screens and events tagged during this session that HAVE been staged
// for upload.
NSMutableString *_screens; // Comma-delimited list of screens tagged during this session.
NSTimeInterval _sessionActiveDuration; // Duration that session open.
BOOL _sessionHasBeenOpen; // Whether or not this session has ever been open.
}
@property dispatch_queue_t queue;
@property dispatch_group_t criticalGroup;
@property BOOL isSessionOpen;
@property BOOL hasInitialized;
@property float backgroundSessionTimeout;
#pragma mark Public Methods
/*!
@method sharedLocalyticsSession
@abstract Accesses the Session object. This is a Singleton class which maintains
a single session throughout your application. It is possible to manage your own
session, but this is the easiest way to access the Localytics object throughout your code.
The class is accessed within the code using the following syntax:
[[LocalyticsSession sharedLocalyticsSession] functionHere]
So, to tag an event, all that is necessary, anywhere in the code is:
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"MY_EVENT"];
*/
+ (LocalyticsSession *)sharedLocalyticsSession;
/*!
@method LocalyticsSession
@abstract Initializes the Localytics Object. Not necessary if you choose to use startSession.
@param applicationKey The key unique for each application generated at www.localytics.com
*/
- (void)LocalyticsSession:(NSString *)appKey;
/*!
@method startSession
@abstract An optional convenience initialize method that also calls the LocalyticsSession, open &
upload methods. Best Practice is to call open & upload immediately after Localytics Session when loading an app,
this method fascilitates that behavior.
It is recommended that this call be placed in <code>applicationDidFinishLaunching</code>.
@param applicationKey The key unique for each application generated
at www.localytics.com
*/
- (void)startSession:(NSString *)appKey;
/*!
@method setOptIn
@abstract (OPTIONAL) Allows the application to control whether or not it will collect user data.
Even if this call is used, it is necessary to continue calling upload(). No new data will be
collected, so nothing new will be uploaded but it is necessary to upload an event telling the
server this user has opted out.
@param optedIn True if the user is opted in, false otherwise.
*/
- (void)setOptIn:(BOOL)optedIn;
/*!
@method isOptedIn
@abstract (OPTIONAL) Whether or not this user has is opted in or out. The only way they can be
opted out is if setOptIn(false) has been called before this. This function should only be
used to pre-populate a checkbox in an options menu. It is not recommended that an application
branch based on Localytics instrumentation because this creates an additional test case. If
the app is opted out, all subsequent Localytics calls will return immediately.
@result true if the user is opted in, false otherwise.
*/
- (BOOL)isOptedIn;
/*!
@method open
@abstract Opens the Localytics session. Not necessary if you choose to use startSession.
The session time as presented on the website is the time between <code>open</code> and the
final <code>close</code> so it is recommended to open the session as early as possible, and close
it at the last moment. The session must be opened before any tags can
be written. It is recommended that this call be placed in <code>applicationDidFinishLaunching</code>.
<br>
If for any reason this is called more than once every subsequent open call
will be ignored.
*/
- (void)open;
/*!
@method resume
@abstract Resumes the Localytics session. When the App enters the background, the session is
closed and the time of closing is recorded. When the app returns to the foreground, the session
is resumed. If the time since closing is greater than BACKGROUND_SESSION_TIMEOUT, (15 seconds
by default) a new session is created, and uploading is triggered. Otherwise, the previous session
is reopened.
*/
- (void)resume;
/*!
@method close
@abstract Closes the Localytics session. This should be called in
<code>applicationWillTerminate</code>.
<br>
If close is not called, the session will still be uploaded but no
events will be processed and the session time will not appear. This is
because the session is not yet closed so it should not be used in
comparison with sessions which are closed.
*/
- (void)close;
/*!
@method tagEvent
@abstract Allows a session to tag a particular event as having occurred. For
example, if a view has three buttons, it might make sense to tag
each button click with the name of the button which was clicked.
For another example, in a game with many levels it might be valuable
to create a new tag every time the user gets to a new level in order
to determine how far the average user is progressing in the game.
<br>
<strong>Tagging Best Practices</strong>
<ul>
<li>DO NOT use tags to record personally identifiable information.</li>
<li>The best way to use tags is to create all the tag strings as predefined
constants and only use those. This is more efficient and removes the risk of
collecting personal information.</li>
<li>Do not set tags inside loops or any other place which gets called
frequently. This can cause a lot of data to be stored and uploaded.</li>
</ul>
<br>
See the tagging guide at: http://wiki.localytics.com/
@param event The name of the event which occurred.
*/
- (void)tagEvent:(NSString *)event;
- (void)tagEvent:(NSString *)event attributes:(NSDictionary *)attributes;
- (void)tagEvent:(NSString *)event attributes:(NSDictionary *)attributes reportAttributes:(NSDictionary *)reportAttributes;
/*!
@method tagScreen
@abstract Allows tagging the flow of screens encountered during the session.
@param screen The name of the screen
*/
- (void)tagScreen:(NSString *)screen;
/*!
@method upload
@abstract Creates a low priority thread which uploads any Localytics data already stored
on the device. This should be done early in the process life in order to
guarantee as much time as possible for slow connections to complete. It is also reasonable
to upload again when the application is exiting because if the upload is cancelled the data
will just get uploaded the next time the app comes up.
*/
- (void)upload;
/*!
@method setCustomDimension
@abstract (ENTERPRISE ONLY) Sets the value of a custom dimension. Custom dimensions are dimensions
which contain user defined data unlike the predefined dimensions such as carrier, model, and country.
Once a value for a custom dimension is set, the device it was set on will continue to upload that value
until the value is changed. To clear a value pass nil as the value.
The proper use of custom dimensions involves defining a dimension with less than ten distinct possible
values and assigning it to one of the four available custom dimensions. Once assigned this definition should
never be changed without changing the App Key otherwise old installs of the application will pollute new data.
*/
- (void)setCustomDimension:(int)dimension value:(NSString *)value;
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
// LocalyticsUploader.h
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import <UIKit/UIKit.h>
/*!
@class LocalyticsUploader
@discussion Singleton class to handle data uploads
*/
@interface LocalyticsUploader : NSObject {
}
@property (readonly) BOOL isUploading;
/*!
@method sharedLocalyticsUploader
@abstract Establishes this as a Singleton Class allowing for data persistence.
The class is accessed within the code using the following syntax:
[[LocalyticsUploader sharedLocalyticsUploader] functionHere]
*/
+ (LocalyticsUploader *)sharedLocalyticsUploader;
/*!
@method LocalyticsUploader
@abstract Creates a thread which uploads all queued header and event data.
All files starting with sessionFilePrefix are renamed,
uploaded and deleted on upload. This way the sessions can continue
writing data regardless of whether or not the upload succeeds. Files
which have been renamed still count towards the total number of Localytics
files which can be stored on the disk.
@param localyticsApplicationKey the Localytics application ID
*/
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey;
@end

View File

@ -0,0 +1,236 @@
// LocalyticsUploader.m
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import "LocalyticsUploader.h"
#import "LocalyticsSession.h"
#import "LocalyticsDatabase.h"
#import <zlib.h>
#define LOCALYTICS_URL @"http://analytics.localytics.com/api/v2/applications/%@/uploads"
static LocalyticsUploader *_sharedUploader = nil;
@interface LocalyticsUploader ()
- (void)finishUpload;
- (NSData *)gzipDeflatedDataWithData:(NSData *)data;
- (void)logMessage:(NSString *)message;
@property (readwrite) BOOL isUploading;
@end
@implementation LocalyticsUploader
@synthesize isUploading = _isUploading;
#pragma mark - Singleton Class
+ (LocalyticsUploader *)sharedLocalyticsUploader {
@synchronized(self) {
if (_sharedUploader == nil) {
_sharedUploader = [[self alloc] init];
}
}
return _sharedUploader;
}
#pragma mark - Class Methods
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey {
// Do nothing if already uploading.
if (self.isUploading == true)
{
[self logMessage:@"Upload already in progress. Aborting."];
return;
}
[self logMessage:@"Beginning upload process"];
self.isUploading = true;
// Prepare the data for upload. The upload could take a long time, so some effort has to be made to be sure that events
// which get written while the upload is taking place don't get lost or duplicated. To achieve this, the logic is:
// 1) Append every header row blob string and and those of its associated events to the upload string.
// 2) Deflate and upload the data.
// 3) On success, delete all blob headers and staged events. Events added while an upload is in process are not
// deleted because they are not associated a header (and cannot be until the upload completes).
// Step 1
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LocalyticsDatabase *db = [LocalyticsDatabase sharedLocalyticsDatabase];
NSString *blobString = [db uploadBlobString];
if ([blobString length] == 0) {
// There is nothing outstanding to upload.
[self logMessage:@"Abandoning upload. There are no new events."];
[pool drain];
[self finishUpload];
return;
}
NSData *requestData = [blobString dataUsingEncoding:NSUTF8StringEncoding];
NSString *myString = [[[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding] autorelease];
[self logMessage:[NSString stringWithFormat:@"Uploading data (length: %u)", [myString length]]];
// Step 2
NSData *deflatedRequestData = [[self gzipDeflatedDataWithData:requestData] retain];
[pool drain];
NSString *apiUrlString = [NSString stringWithFormat:LOCALYTICS_URL, [localyticsApplicationKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *submitRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:apiUrlString]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60.0];
[submitRequest setHTTPMethod:@"POST"];
[submitRequest setValue:@"application/x-gzip" forHTTPHeaderField:@"Content-Type"];
[submitRequest setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
[submitRequest setValue:[NSString stringWithFormat:@"%d", [deflatedRequestData length]] forHTTPHeaderField:@"Content-Length"];
[submitRequest setHTTPBody:deflatedRequestData];
[deflatedRequestData release];
// Perform synchronous upload in an async dispatch. This is necessary because the calling block will not persist to
// receive the response data.
dispatch_group_async([[LocalyticsSession sharedLocalyticsSession] criticalGroup], [[LocalyticsSession sharedLocalyticsSession] queue], ^{
@try {
NSURLResponse *response = nil;
NSError *responseError = nil;
[NSURLConnection sendSynchronousRequest:submitRequest returningResponse:&response error:&responseError];
NSInteger responseStatusCode = [(NSHTTPURLResponse *)response statusCode];
if (responseError) {
// On error, simply print the error and close the uploader. We have to assume the data was not transmited
// so it is not deleted. In the event that we accidently store data which was succesfully uploaded, the
// duplicate data will be ignored by the server when it is next uploaded.
[self logMessage:[NSString stringWithFormat:
@"Error Uploading. Code: %d, Description: %@",
[responseError code],
[responseError localizedDescription]]];
} else {
// Step 3
// While response status codes in the 5xx range leave upload rows intact, the default case is to delete.
if (responseStatusCode >= 500 && responseStatusCode < 600) {
[self logMessage:[NSString stringWithFormat:@"Upload failed with response status code %d", responseStatusCode]];
} else {
// Because only one instance of the uploader can be running at a time it should not be possible for
// new upload rows to appear so there is no fear of deleting data which has not yet been uploaded.
[self logMessage:[NSString stringWithFormat:@"Upload completed successfully. Response code %d", responseStatusCode]];
[[LocalyticsDatabase sharedLocalyticsDatabase] deleteUploadedData];
}
}
}
@catch (NSException * e) {}
[self finishUpload];
});
}
- (void)finishUpload
{
self.isUploading = false;
// Upload data has been deleted. Recover the disk space if necessary.
[[LocalyticsDatabase sharedLocalyticsDatabase] vacuumIfRequired];
}
/*!
@method gzipDeflatedDataWithData
@abstract Deflates the provided data using gzip at the default compression level (6). Complete NSData gzip category available on CocoaDev. http://www.cocoadev.com/index.pl?NSDataCategory.
@return the deflated data
*/
- (NSData *)gzipDeflatedDataWithData:(NSData *)data
{
if ([data length] == 0) return data;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in=(Bytef *)[data bytes];
strm.avail_in = [data length];
// Compresssion Levels:
// Z_NO_COMPRESSION
// Z_BEST_SPEED
// Z_BEST_COMPRESSION
// Z_DEFAULT_COMPRESSION
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
do {
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy: 16384];
strm.next_out = [compressed mutableBytes] + strm.total_out;
strm.avail_out = [compressed length] - strm.total_out;
deflate(&strm, Z_FINISH);
} while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength: strm.total_out];
return [NSData dataWithData:compressed];
}
/*!
@method logMessage
@abstract Logs a message with (localytics uploader) prepended to it
@param message The message to log
*/
- (void) logMessage:(NSString *)message {
if(DO_LOCALYTICS_LOGGING) {
NSLog(@"(localytics uploader) %s\n", [message UTF8String]);
}
}
#pragma mark - System Functions
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (_sharedUploader == nil) {
_sharedUploader = [super allocWithZone:zone];
return _sharedUploader;
}
}
// returns nil on subsequent allocations
return nil;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
// maximum value of an unsigned int - prevents additional retains for the class
return UINT_MAX;
}
- (oneway void)release {
// ignore release commands
}
- (id)autorelease {
return self;
}
- (void)dealloc {
[_sharedUploader release];
[super dealloc];
}
@end

View File

@ -0,0 +1,48 @@
// UploaderThread.h
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import <UIKit/UIKit.h>
/*!
@class UploaderThread
@discussion Singleton class to handle data uploads
*/
@interface UploaderThread : NSObject {
NSURLConnection *_uploadConnection; // The connection which uploads the bits
NSInteger _responseStatusCode; // The HTTP response status code for the current connection
BOOL _isUploading; // A flag to gaurantee only one uploader instance can happen at once
}
@property (nonatomic, retain) NSURLConnection *uploadConnection;
@property BOOL isUploading;
/*!
@method sharedUploaderThread
@abstract Establishes this as a Singleton Class allowing for data persistence.
The class is accessed within the code using the following syntax:
[[UploaderThread sharedUploaderThread] functionHere]
*/
+ (UploaderThread *)sharedUploaderThread;
/*!
@method UploaderThread
@abstract Creates a thread which uploads all queued header and event data.
All files starting with sessionFilePrefix are renamed,
uploaded and deleted on upload. This way the sessions can continue
writing data regardless of whether or not the upload succeeds. Files
which have been renamed still count towards the total number of Localytics
files which can be stored on the disk.
@param localyticsApplicationKey the Localytics application ID
*/
- (void)uploaderThreadwithApplicationKey:(NSString *)localyticsApplicationKey;
@end

260
Localytics/UploaderThread.m Normal file
View File

@ -0,0 +1,260 @@
// UploaderThread.m
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
#import "UploaderThread.h"
#import "LocalyticsSession.h"
#import "LocalyticsDatabase.h"
#import <zlib.h>
#define LOCALYTICS_URL @"http://analytics.localytics.com/api/v2/applications/%@/uploads" // url to send the
static UploaderThread *_sharedUploaderThread = nil;
@interface UploaderThread ()
- (void)complete;
- (NSData *)gzipDeflatedDataWithData:(NSData *)data;
- (void)logMessage:(NSString *)message;
@end
@implementation UploaderThread
@synthesize uploadConnection = _uploadConnection;
@synthesize isUploading = _isUploading;
#pragma mark Singleton Class
+ (UploaderThread *)sharedUploaderThread {
@synchronized(self) {
if (_sharedUploaderThread == nil)
{
_sharedUploaderThread = [[self alloc] init];
}
}
return _sharedUploaderThread;
}
#pragma mark Class Methods
- (void)uploaderThreadwithApplicationKey:(NSString *)localyticsApplicationKey {
// Do nothing if already uploading.
if (self.uploadConnection != nil || self.isUploading == true)
{
[self logMessage:@"Upload already in progress. Aborting."];
return;
}
[self logMessage:@"Beginning upload process"];
self.isUploading = true;
// Prepare the data for upload. The upload could take a long time, so some effort has to be made to be sure that events
// which get written while the upload is taking place don't get lost or duplicated. To achieve this, the logic is:
// 1) Append every header row blob string and and those of its associated events to the upload string.
// 2) Deflate and upload the data.
// 3) On success, delete all blob headers and staged events. Events added while an upload is in process are not
// deleted because they are not associated a header (and cannot be until the upload completes).
// Step 1
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LocalyticsDatabase *db = [LocalyticsDatabase sharedLocalyticsDatabase];
NSString *blobString = [db uploadBlobString];
if ([blobString length] == 0) {
// There is nothing outstanding to upload.
[self logMessage:@"Abandoning upload. There are no new events."];
[pool drain];
[self complete];
return;
}
NSData *requestData = [blobString dataUsingEncoding:NSUTF8StringEncoding];
NSString *myString = [[[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding] autorelease];
[self logMessage:@"Upload data:"];
[self logMessage:myString];
// Step 2
NSData *deflatedRequestData = [[self gzipDeflatedDataWithData:requestData] retain];
[pool drain];
NSString *apiUrlString = [NSString stringWithFormat:LOCALYTICS_URL, [localyticsApplicationKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *submitRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:apiUrlString]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60.0];
[submitRequest setHTTPMethod:@"POST"];
[submitRequest setValue:@"application/x-gzip" forHTTPHeaderField:@"Content-Type"];
[submitRequest setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
[submitRequest setValue:[NSString stringWithFormat:@"%d", [deflatedRequestData length]] forHTTPHeaderField:@"Content-Length"];
[submitRequest setHTTPBody:deflatedRequestData];
[deflatedRequestData release];
// The NSURLConnection Object automatically spawns its own thread as a default behavior.
@try
{
[self logMessage:@"Spawning new thread for upload"];
self.uploadConnection = [NSURLConnection connectionWithRequest:submitRequest delegate:self];
// Step 3 is handled by connectionDidFinishLoading.
}
@catch (NSException * e)
{
[self complete];
}
}
#pragma mark **** NSURLConnection FUNCTIONS ****
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Used to gather response data from server - Not utilized in this version
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// Could receive multiple response callbacks, likely due to redirection.
// Record status and act only when connection completes load.
_responseStatusCode = [(NSHTTPURLResponse *)response statusCode];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// If the connection finished loading, the files should be deleted. While response status codes in the 5xx range
// leave upload rows intact, the default case is to delete.
if (_responseStatusCode >= 500 && _responseStatusCode < 600)
{
[self logMessage:[NSString stringWithFormat:@"Upload failed with response status code %d", _responseStatusCode]];
} else
{
// The connection finished loading and uploaded data should be deleted. Because only one instance of the
// uploader can be running at a time it should not be possible for new upload rows to appear so there is no
// fear of deleting data which has not yet been uploaded.
[self logMessage:[NSString stringWithFormat:@"Upload completed successfully. Response code %d", _responseStatusCode]];
[[LocalyticsDatabase sharedLocalyticsDatabase] deleteUploadData];
}
// Close upload session
[self complete];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// On error, simply print the error and close the uploader. We have to assume the data was not transmited
// so it is not deleted. In the event that we accidently store data which was succesfully uploaded, the
// duplicate data will be ignored by the server when it is next uploaded.
[self logMessage:[NSString stringWithFormat:
@"Error Uploading. Code: %d, Description: %s",
[error code],
[error localizedDescription]]];
[self complete];
}
/*!
@method complete
@abstract closes the upload connection and reports back to the session that the upload is complete
*/
- (void)complete {
_responseStatusCode = 0;
self.uploadConnection = nil;
self.isUploading = false;
}
/*!
@method gzipDeflatedDataWithData
@abstract Deflates the provided data using gzip at the default compression level (6). Complete NSData gzip category available on CocoaDev. http://www.cocoadev.com/index.pl?NSDataCategory.
@return the deflated data
*/
- (NSData *)gzipDeflatedDataWithData:(NSData *)data
{
if ([data length] == 0) return data;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in=(Bytef *)[data bytes];
strm.avail_in = [data length];
// Compresssion Levels:
// Z_NO_COMPRESSION
// Z_BEST_SPEED
// Z_BEST_COMPRESSION
// Z_DEFAULT_COMPRESSION
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
do {
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy: 16384];
strm.next_out = [compressed mutableBytes] + strm.total_out;
strm.avail_out = [compressed length] - strm.total_out;
deflate(&strm, Z_FINISH);
} while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength: strm.total_out];
return [NSData dataWithData:compressed];
}
/*!
@method logMessage
@abstract Logs a message with (localytics uploader) prepended to it
@param message The message to log
*/
- (void) logMessage:(NSString *)message {
if(DO_LOCALYTICS_LOGGING) {
NSLog(@"(localytics uploader) %s\n", [message UTF8String]);
}
}
#pragma mark System Functions
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (_sharedUploaderThread == nil) {
_sharedUploaderThread = [super allocWithZone:zone];
return _sharedUploaderThread;
}
}
// returns nil on subsequent allocations
return nil;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
// maximum value of an unsigned int - prevents additional retains for the class
return UINT_MAX;
}
- (oneway void)release {
// ignore release commands
}
- (id)autorelease {
return self;
}
- (void)dealloc {
[_uploadConnection release];
[_sharedUploaderThread release];
[super dealloc];
}
@end

View File

@ -0,0 +1,111 @@
// WebserviceConstants.h
// Copyright (C) 2009 Char Software Inc., DBA Localytics
//
// This code is provided under the Localytics Modified BSD License.
// A copy of this license has been distributed in a file called LICENSE
// with this source code.
//
// Please visit www.localytics.com for more information.
// The constants which are used to make up the JSON blob
// To save disk space and network bandwidth all the keywords have been
// abbreviated and are exploded by the server.
/*********************
* Shared Attributes *
*********************/
#define PARAM_UUID @"u" // UUID for JSON document
#define PARAM_DATA_TYPE @"dt" // Data Type
#define PARAM_CLIENT_TIME @"ct" // Client Time, seconds from Unix epoch (int)
#define PARAM_LATITUDE @"lat" // Latitude - if available
#define PARAM_LONGITUDE @"lon" // Longitude - if available
#define PARAM_SESSION_UUID @"su" // UUID for an existing session
#define PARAM_NEW_SESSION_UUID @"u" // UUID for a new session
#define PARAM_ATTRIBUTES @"attrs" // Attributes (dictionary)
/***************
* Blob Header *
***************/
// PARAM_UUID
// PARAM_DATA_TYPE => "h" for Header
// PARAM_ATTRIBUTES => dictionary containing Header Common Attributes
#define PARAM_PERSISTED_AT @"pa" // Persistent Storage Created At. A timestamp created when the app was
// first launched and the persistent storage was created. Stores as
// seconds from Unix epoch. (int)
#define PARAM_SEQUENCE_NUMBER @"seq" // Sequence number - an increasing count for each blob, stored in the
// persistent store Consistent across app starts. (int)
/****************************
* Header Common Attributes *
****************************/
// PARAM_DATA_TYPE
#define PARAM_APP_KEY @"au" // Localytics Application ID
#define PARAM_DEVICE_UUID @"du" // Device UUID
#define PARAM_DEVICE_UUID_HASHED @"udid" // Hashed version of the UUID
#define PARAM_DEVICE_MAC @"wmac" // Hashed version of the device Mac
#define PARAM_INSTALL_ID @"iu" // Install ID
#define PARAM_JAILBROKEN @"j" // Jailbroken (boolean)
#define PARAM_LIBRARY_VERSION @"lv" // Client Version
#define PARAM_APP_VERSION @"av" // Application Version
#define PARAM_DEVICE_PLATFORM @"dp" // Device Platform
#define PARAM_LOCALE_LANGUAGE @"dll" // Locale Language
#define PARAM_LOCALE_COUNTRY @"dlc" // Locale Country
#define PARAM_NETWORK_COUNTRY @"nc" // Network Country (iso code) // ???: Never used on iPhone.
#define PARAM_DEVICE_COUNTRY @"dc" // Device Country (iso code)
#define PARAM_DEVICE_MANUFACTURER @"dma" // Device Manufacturer // ???: Never used on iPhone. Used to be "Device Make".
#define PARAM_DEVICE_MODEL @"dmo" // Device Model
#define PARAM_DEVICE_OS_VERSION @"dov" // Device OS Version
#define PARAM_NETWORK_CARRIER @"nca" // Network Carrier
#define PARAM_DATA_CONNECTION @"dac" // Data Connection Type // ???: Never used on iPhone.
#define PARAM_OPT_VALUE @"optin" // Opt In (boolean)
#define PARAM_DEVICE_MEMORY @"dmem" // Device Memory
/*****************
* Session Start *
*****************/
// PARAM_UUID
// PARAM_DATA_TYPE => "s" for Start
// PARAM_CLIENT_TIME
#define PARAM_SESSION_NUMBER @"nth" // This is the nth session on the device, 1-indexed (int)
/****************
* Session Stop *
****************/
// PARAM_UUID
// PARAM_DATA_TYPE => "c" for Close
// PARAM_CLIENT_TIME
// PARAM_LATITUDE
// PARAM_LONGITUDE
// PARAM_SESSION_UUID => UUID of session being closed
#define PARAM_SESSION_ACTIVE @"cta" // Active time in seconds (time app was active)
#define PARAM_SESSION_TOTAL @"ctl" // Total session length
#define PARAM_SESSION_SCREENFLOW @"fl" // Screens encountered during this session, in order
/*********************
* Application Event *
*********************/
// PARAM_UUID
// PARAM_DATA_TYPE => "e" for Event
// PARAM_CLIENT_TIME
// PARAM_LATITUDE
// PARAM_LONGITUDE
// PARAM_SESSION_UUID => UUID of session event occured in
// PARAM_ATTRIBUTES => dictionary containing attributes for this event as key-value string pairs
#define PARAM_EVENT_NAME @"n" // Event Name, (eg. 'Button Click')
#define PARAM_REPORT_ATTRIBUTES @"rattrs" // Attributes used in custom reports
/********************
* Application flow *
********************/
// PARAM_UUID
// PARAM_DATA_TYPE => "f" for Flow
// PARAM_CLIENT_TIME
#define PARAM_SESSION_START @"ss" // Start time for the current session.
#define PARAM_NEW_FLOW_EVENTS @"nw" // Events and screens encountered during this session that have NOT been staged for upload.
#define PARAM_OLD_FLOW_EVENTS @"od" // Events and screens encountered during this session that HAVE been staged for upload.

View File

@ -56,7 +56,6 @@
DA95D5F614DF0B9F008D1B94 /* IASKPSTextFieldSpecifierViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA95D5CC14DF0691008D1B94 /* IASKPSTextFieldSpecifierViewCell.xib */; };
DA95D5F714DF0B9F008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */; };
DA95D5F814DF0B9F008D1B94 /* IASKSpecifierValuesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */; };
DA95D60614DF3E67008D1B94 /* libTestFlight.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA95D60114DF3E67008D1B94 /* libTestFlight.a */; };
DAB8D45D15036BCF00CED3BC /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */; };
DAB8D45E15036BCF00CED3BC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D43F15036BCF00CED3BC /* InfoPlist.strings */; };
DAB8D45F15036BCF00CED3BC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D44115036BCF00CED3BC /* main.m */; };
@ -712,9 +711,6 @@
DAB8D97A15036BF700CED3BC /* tip_location_wood.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F815036BF600CED3BC /* tip_location_wood.png */; };
DAB8D97B15036BF700CED3BC /* tip_location_wood@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6F915036BF600CED3BC /* tip_location_wood@2x.png */; };
DAB8D97C1503718B00CED3BC /* jquery-1.6.1.min.js in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D6AB15036BF600CED3BC /* jquery-1.6.1.min.js */; };
DABB9809150FF40100B05417 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DABB980F150FF40100B05417 /* SendToMac.m in Sources */ = {isa = PBXBuildFile; fileRef = DABB980E150FF40100B05417 /* SendToMac.m */; };
DABB9814150FF5C100B05417 /* libSendToMac.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB9808150FF40100B05417 /* libSendToMac.a */; };
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
@ -722,10 +718,29 @@
DAC63278148680740075AEA5 /* libjrswizzle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6326C148680650075AEA5 /* libjrswizzle.a */; };
DAC6327B1486809A0075AEA5 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC632791486809A0075AEA5 /* JRSwizzle.h */; };
DAC6327C1486809A0075AEA5 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC6327A1486809A0075AEA5 /* JRSwizzle.m */; };
DAC6327F148680B10075AEA5 /* UIColor-Expanded.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC6327D148680B10075AEA5 /* UIColor-Expanded.h */; };
DAC63280148680B10075AEA5 /* UIColor-Expanded.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC6327E148680B10075AEA5 /* UIColor-Expanded.m */; };
DAC632891486D9690075AEA5 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; };
DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DAD3126715528C9C00A3F9ED /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3125F15528C9C00A3F9ED /* Crashlytics.framework */; };
DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */ = {isa = PBXBuildFile; fileRef = DAD3126015528C9C00A3F9ED /* Crashlytics.plist */; };
DAD3126915528C9C00A3F9ED /* libTestFlight.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3126215528C9C00A3F9ED /* libTestFlight.a */; };
DAD3126C15528C9C00A3F9ED /* TestFlight.plist in Resources */ = {isa = PBXBuildFile; fileRef = DAD3126615528C9C00A3F9ED /* TestFlight.plist */; };
DAD3127215528CD200A3F9ED /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DAD3128715528D0F00A3F9ED /* LocalyticsDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = DAD3127E15528D0F00A3F9ED /* LocalyticsDatabase.h */; };
DAD3128815528D0F00A3F9ED /* LocalyticsDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD3127F15528D0F00A3F9ED /* LocalyticsDatabase.m */; };
DAD3128915528D0F00A3F9ED /* LocalyticsSession.h in Headers */ = {isa = PBXBuildFile; fileRef = DAD3128015528D0F00A3F9ED /* LocalyticsSession.h */; };
DAD3128A15528D0F00A3F9ED /* LocalyticsSession.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD3128115528D0F00A3F9ED /* LocalyticsSession.m */; };
DAD3128B15528D0F00A3F9ED /* LocalyticsUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = DAD3128215528D0F00A3F9ED /* LocalyticsUploader.h */; };
DAD3128C15528D0F00A3F9ED /* LocalyticsUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD3128315528D0F00A3F9ED /* LocalyticsUploader.m */; };
DAD3128D15528D0F00A3F9ED /* UploaderThread.h in Headers */ = {isa = PBXBuildFile; fileRef = DAD3128415528D0F00A3F9ED /* UploaderThread.h */; };
DAD3128E15528D0F00A3F9ED /* UploaderThread.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD3128515528D0F00A3F9ED /* UploaderThread.m */; };
DAD3128F15528D0F00A3F9ED /* WebserviceConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = DAD3128615528D0F00A3F9ED /* WebserviceConstants.h */; };
DAD3129015528D1600A3F9ED /* Localytics.plist in Resources */ = {isa = PBXBuildFile; fileRef = DAD3127D15528D0F00A3F9ED /* Localytics.plist */; };
DAD312BB1552977200A3F9ED /* UIColor+Expanded.h in Headers */ = {isa = PBXBuildFile; fileRef = DAD312B71552977200A3F9ED /* UIColor+Expanded.h */; };
DAD312BC1552977200A3F9ED /* UIColor+Expanded.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD312B81552977200A3F9ED /* UIColor+Expanded.m */; };
DAD312BD1552977200A3F9ED /* UIColor+HSV.h in Headers */ = {isa = PBXBuildFile; fileRef = DAD312B91552977200A3F9ED /* UIColor+HSV.h */; };
DAD312BE1552977200A3F9ED /* UIColor+HSV.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD312BA1552977200A3F9ED /* UIColor+HSV.m */; };
DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD3127115528CD200A3F9ED /* libLocalytics.a */; };
DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD312C01552A20800A3F9ED /* libsqlite3.dylib */; };
DAEBC45314F6364500987BF6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; };
DAFE4A1315039824003ABA7C /* NSObject_PearlExport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */; };
DAFE4A1415039824003ABA7C /* NSObject_PearlExport.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45D915039823003ABA7C /* NSObject_PearlExport.m */; };
@ -890,8 +905,6 @@
DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKPSToggleSwitchSpecifierViewCell.xib; sourceTree = "<group>"; };
DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKSpecifierValuesView.xib; sourceTree = "<group>"; };
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
DA95D60114DF3E67008D1B94 /* libTestFlight.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libTestFlight.a; sourceTree = "<group>"; };
DA95D60414DF3E67008D1B94 /* TestFlight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestFlight.h; sourceTree = "<group>"; };
DAB8D43D15036BCF00CED3BC /* MasterPassword.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MasterPassword.xcdatamodel; sourceTree = "<group>"; };
DAB8D44015036BCF00CED3BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
DAB8D44115036BCF00CED3BC /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@ -1560,7 +1573,6 @@
DAB8D6F715036BF600CED3BC /* tip_location_teal@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_teal@2x.png"; sourceTree = "<group>"; };
DAB8D6F815036BF600CED3BC /* tip_location_wood.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_location_wood.png; sourceTree = "<group>"; };
DAB8D6F915036BF600CED3BC /* tip_location_wood@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_location_wood@2x.png"; sourceTree = "<group>"; };
DABB9808150FF40100B05417 /* libSendToMac.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSendToMac.a; sourceTree = BUILT_PRODUCTS_DIR; };
DABB980C150FF40100B05417 /* SendToMac-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SendToMac-Prefix.pch"; sourceTree = "<group>"; };
DABB980D150FF40100B05417 /* SendToMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SendToMac.h; sourceTree = "<group>"; };
DABB980E150FF40100B05417 /* SendToMac.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SendToMac.m; sourceTree = "<group>"; };
@ -1569,11 +1581,30 @@
DAC6326C148680650075AEA5 /* libjrswizzle.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjrswizzle.a; sourceTree = BUILT_PRODUCTS_DIR; };
DAC632791486809A0075AEA5 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JRSwizzle.h; path = External/Pearl/External/jrswizzle/JRSwizzle.h; sourceTree = SOURCE_ROOT; };
DAC6327A1486809A0075AEA5 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JRSwizzle.m; path = External/Pearl/External/jrswizzle/JRSwizzle.m; sourceTree = SOURCE_ROOT; };
DAC6327D148680B10075AEA5 /* UIColor-Expanded.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor-Expanded.h"; path = "External/Pearl/External/uicolor-utilities/UIColor-Expanded.h"; sourceTree = SOURCE_ROOT; };
DAC6327E148680B10075AEA5 /* UIColor-Expanded.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIColor-Expanded.m"; path = "External/Pearl/External/uicolor-utilities/UIColor-Expanded.m"; sourceTree = SOURCE_ROOT; };
DAC632871486D95D0075AEA5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
DAC77CAD148291A600BCF976 /* libPearl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPearl.a; sourceTree = BUILT_PRODUCTS_DIR; };
DAC77CB1148291A600BCF976 /* Pearl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Pearl-Prefix.pch"; sourceTree = "<group>"; };
DAD3125F15528C9C00A3F9ED /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
DAD3126015528C9C00A3F9ED /* Crashlytics.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Crashlytics.plist; sourceTree = "<group>"; };
DAD3126215528C9C00A3F9ED /* libTestFlight.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libTestFlight.a; sourceTree = "<group>"; };
DAD3126515528C9C00A3F9ED /* TestFlight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestFlight.h; sourceTree = "<group>"; };
DAD3126615528C9C00A3F9ED /* TestFlight.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TestFlight.plist; sourceTree = "<group>"; };
DAD3127115528CD200A3F9ED /* libLocalytics.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLocalytics.a; sourceTree = BUILT_PRODUCTS_DIR; };
DAD3127D15528D0F00A3F9ED /* Localytics.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Localytics.plist; sourceTree = "<group>"; };
DAD3127E15528D0F00A3F9ED /* LocalyticsDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalyticsDatabase.h; sourceTree = "<group>"; };
DAD3127F15528D0F00A3F9ED /* LocalyticsDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalyticsDatabase.m; sourceTree = "<group>"; };
DAD3128015528D0F00A3F9ED /* LocalyticsSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalyticsSession.h; sourceTree = "<group>"; };
DAD3128115528D0F00A3F9ED /* LocalyticsSession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalyticsSession.m; sourceTree = "<group>"; };
DAD3128215528D0F00A3F9ED /* LocalyticsUploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalyticsUploader.h; sourceTree = "<group>"; };
DAD3128315528D0F00A3F9ED /* LocalyticsUploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalyticsUploader.m; sourceTree = "<group>"; };
DAD3128415528D0F00A3F9ED /* UploaderThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UploaderThread.h; sourceTree = "<group>"; };
DAD3128515528D0F00A3F9ED /* UploaderThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UploaderThread.m; sourceTree = "<group>"; };
DAD3128615528D0F00A3F9ED /* WebserviceConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebserviceConstants.h; sourceTree = "<group>"; };
DAD312B71552977200A3F9ED /* UIColor+Expanded.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor+Expanded.h"; path = "External/Pearl/External/uicolor-utilities/UIColor+Expanded.h"; sourceTree = SOURCE_ROOT; };
DAD312B81552977200A3F9ED /* UIColor+Expanded.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIColor+Expanded.m"; path = "External/Pearl/External/uicolor-utilities/UIColor+Expanded.m"; sourceTree = SOURCE_ROOT; };
DAD312B91552977200A3F9ED /* UIColor+HSV.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor+HSV.h"; path = "External/Pearl/External/uicolor-utilities/UIColor+HSV.h"; sourceTree = SOURCE_ROOT; };
DAD312BA1552977200A3F9ED /* UIColor+HSV.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIColor+HSV.m"; path = "External/Pearl/External/uicolor-utilities/UIColor+HSV.m"; sourceTree = SOURCE_ROOT; };
DAD312C01552A20800A3F9ED /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
DAEBC45214F6364500987BF6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
DAFE45D815039823003ABA7C /* NSObject_PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSObject_PearlExport.h; sourceTree = "<group>"; };
DAFE45D915039823003ABA7C /* NSObject_PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObject_PearlExport.m; sourceTree = "<group>"; };
@ -1657,8 +1688,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */,
DAD312BF1552A1BD00A3F9ED /* libLocalytics.a in Frameworks */,
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */,
DABB9814150FF5C100B05417 /* libSendToMac.a in Frameworks */,
DA672D3014F9413D004A189C /* libPearl.a in Frameworks */,
DA672D2F14F92C6B004A189C /* libz.dylib in Frameworks */,
DAEBC45314F6364500987BF6 /* QuartzCore.framework in Frameworks */,
@ -1670,7 +1702,8 @@
DA5BFA4B147E415C00F98B1E /* Foundation.framework in Frameworks */,
DA5BFA4D147E415C00F98B1E /* CoreGraphics.framework in Frameworks */,
DA5BFA4F147E415C00F98B1E /* CoreData.framework in Frameworks */,
DA95D60614DF3E67008D1B94 /* libTestFlight.a in Frameworks */,
DAD3126715528C9C00A3F9ED /* Crashlytics.framework in Frameworks */,
DAD3126915528C9C00A3F9ED /* libTestFlight.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1682,14 +1715,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DABB9805150FF40100B05417 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DABB9809150FF40100B05417 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DAC6325A1486805C0075AEA5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -1717,6 +1742,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DAD3126E15528CD200A3F9ED /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DAD3127215528CD200A3F9ED /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -1741,7 +1774,9 @@
DAC6325F1486805C0075AEA5 /* uicolor-utilities */,
DAC6326E148680650075AEA5 /* jrswizzle */,
DA95D59E14DF063C008D1B94 /* InAppSettingsKit */,
DA95D5FF14DF3E67008D1B94 /* TestFlightSDK */,
DAD3125E15528C9C00A3F9ED /* Crashlytics */,
DAD3126115528C9C00A3F9ED /* TestFlight */,
DAD3127315528CD200A3F9ED /* Localytics */,
DA5BFA47147E415C00F98B1E /* Frameworks */,
DA5BFA45147E415C00F98B1E /* Products */,
);
@ -1755,7 +1790,7 @@
DAC6325D1486805C0075AEA5 /* libuicolor-utilities.a */,
DAC6326C148680650075AEA5 /* libjrswizzle.a */,
DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */,
DABB9808150FF40100B05417 /* libSendToMac.a */,
DAD3127115528CD200A3F9ED /* libLocalytics.a */,
);
name = Products;
sourceTree = "<group>";
@ -1763,6 +1798,7 @@
DA5BFA47147E415C00F98B1E /* Frameworks */ = {
isa = PBXGroup;
children = (
DAD312C01552A20800A3F9ED /* libsqlite3.dylib */,
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */,
DABB981515100B4000B05417 /* SystemConfiguration.framework */,
DA672D2E14F92C6B004A189C /* libz.dylib */,
@ -1787,10 +1823,10 @@
DA600C2615056427008E9AB6 /* MPConfig.h */,
DA600C2715056427008E9AB6 /* MPConfig.m */,
DAB8D45515036BCF00CED3BC /* MPElementStoredEntity.m */,
DAB8D45915036BCF00CED3BC /* MPTypes.h */,
DAB8D45615036BCF00CED3BC /* MPTypes.m */,
DAB8D45715036BCF00CED3BC /* MPElementEntity.h */,
DAB8D45815036BCF00CED3BC /* MPElementEntity.m */,
DAB8D45915036BCF00CED3BC /* MPTypes.h */,
DAB8D45A15036BCF00CED3BC /* MPElementGeneratedEntity.h */,
DAB8D45B15036BCF00CED3BC /* MPElementGeneratedEntity.m */,
DAB8D45C15036BCF00CED3BC /* MPElementStoredEntity.h */,
@ -1877,16 +1913,6 @@
path = External/InAppSettingsKit/InAppSettingsKit/Xibs;
sourceTree = SOURCE_ROOT;
};
DA95D5FF14DF3E67008D1B94 /* TestFlightSDK */ = {
isa = PBXGroup;
children = (
DA95D60114DF3E67008D1B94 /* libTestFlight.a */,
DA95D60414DF3E67008D1B94 /* TestFlight.h */,
);
name = TestFlightSDK;
path = External/TestFlightSDK;
sourceTree = "<group>";
};
DAB8D43E15036BCF00CED3BC /* iOS */ = {
isa = PBXGroup;
children = (
@ -2653,8 +2679,10 @@
DAC6325F1486805C0075AEA5 /* uicolor-utilities */ = {
isa = PBXGroup;
children = (
DAC6327D148680B10075AEA5 /* UIColor-Expanded.h */,
DAC6327E148680B10075AEA5 /* UIColor-Expanded.m */,
DAD312B71552977200A3F9ED /* UIColor+Expanded.h */,
DAD312B81552977200A3F9ED /* UIColor+Expanded.m */,
DAD312B91552977200A3F9ED /* UIColor+HSV.h */,
DAD312BA1552977200A3F9ED /* UIColor+HSV.m */,
);
path = "uicolor-utilities";
sourceTree = "<group>";
@ -2687,6 +2715,42 @@
name = "Supporting Files";
sourceTree = "<group>";
};
DAD3125E15528C9C00A3F9ED /* Crashlytics */ = {
isa = PBXGroup;
children = (
DAD3125F15528C9C00A3F9ED /* Crashlytics.framework */,
DAD3126015528C9C00A3F9ED /* Crashlytics.plist */,
);
path = Crashlytics;
sourceTree = "<group>";
};
DAD3126115528C9C00A3F9ED /* TestFlight */ = {
isa = PBXGroup;
children = (
DAD3126215528C9C00A3F9ED /* libTestFlight.a */,
DAD3126515528C9C00A3F9ED /* TestFlight.h */,
DAD3126615528C9C00A3F9ED /* TestFlight.plist */,
);
path = TestFlight;
sourceTree = "<group>";
};
DAD3127315528CD200A3F9ED /* Localytics */ = {
isa = PBXGroup;
children = (
DAD3127D15528D0F00A3F9ED /* Localytics.plist */,
DAD3127E15528D0F00A3F9ED /* LocalyticsDatabase.h */,
DAD3127F15528D0F00A3F9ED /* LocalyticsDatabase.m */,
DAD3128015528D0F00A3F9ED /* LocalyticsSession.h */,
DAD3128115528D0F00A3F9ED /* LocalyticsSession.m */,
DAD3128215528D0F00A3F9ED /* LocalyticsUploader.h */,
DAD3128315528D0F00A3F9ED /* LocalyticsUploader.m */,
DAD3128415528D0F00A3F9ED /* UploaderThread.h */,
DAD3128515528D0F00A3F9ED /* UploaderThread.m */,
DAD3128615528D0F00A3F9ED /* WebserviceConstants.h */,
);
path = Localytics;
sourceTree = "<group>";
};
DAFE45D715039823003ABA7C /* Pearl */ = {
isa = PBXGroup;
children = (
@ -2827,18 +2891,12 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DABB9806150FF40100B05417 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
DAC6325B1486805C0075AEA5 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
DAC6327F148680B10075AEA5 /* UIColor-Expanded.h in Headers */,
DAD312BB1552977200A3F9ED /* UIColor+Expanded.h in Headers */,
DAD312BD1552977200A3F9ED /* UIColor+HSV.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -2895,6 +2953,18 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DAD3126F15528CD200A3F9ED /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
DAD3128715528D0F00A3F9ED /* LocalyticsDatabase.h in Headers */,
DAD3128915528D0F00A3F9ED /* LocalyticsSession.h in Headers */,
DAD3128B15528D0F00A3F9ED /* LocalyticsUploader.h in Headers */,
DAD3128D15528D0F00A3F9ED /* UploaderThread.h in Headers */,
DAD3128F15528D0F00A3F9ED /* WebserviceConstants.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
@ -2905,7 +2975,8 @@
DA5BFA40147E415C00F98B1E /* Sources */,
DA5BFA41147E415C00F98B1E /* Frameworks */,
DA5BFA42147E415C00F98B1E /* Resources */,
DA6556E314D55F3000841C99 /* ShellScript */,
DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */,
DAD3125D155288AA00A3F9ED /* ShellScript */,
);
buildRules = (
);
@ -2933,23 +3004,6 @@
productReference = DA95D59C14DF063C008D1B94 /* libInAppSettingsKit.a */;
productType = "com.apple.product-type.library.static";
};
DABB9807150FF40100B05417 /* SendToMac */ = {
isa = PBXNativeTarget;
buildConfigurationList = DABB9810150FF40100B05417 /* Build configuration list for PBXNativeTarget "SendToMac" */;
buildPhases = (
DABB9804150FF40100B05417 /* Sources */,
DABB9805150FF40100B05417 /* Frameworks */,
DABB9806150FF40100B05417 /* Headers */,
);
buildRules = (
);
dependencies = (
);
name = SendToMac;
productName = SendToMac;
productReference = DABB9808150FF40100B05417 /* libSendToMac.a */;
productType = "com.apple.product-type.library.static";
};
DAC6325C1486805C0075AEA5 /* uicolor-utilities */ = {
isa = PBXNativeTarget;
buildConfigurationList = DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */;
@ -3003,6 +3057,23 @@
productReference = DAC77CAD148291A600BCF976 /* libPearl.a */;
productType = "com.apple.product-type.library.static";
};
DAD3127015528CD200A3F9ED /* Localytics */ = {
isa = PBXNativeTarget;
buildConfigurationList = DAD3127915528CD200A3F9ED /* Build configuration list for PBXNativeTarget "Localytics" */;
buildPhases = (
DAD3126D15528CD200A3F9ED /* Sources */,
DAD3126E15528CD200A3F9ED /* Frameworks */,
DAD3126F15528CD200A3F9ED /* Headers */,
);
buildRules = (
);
dependencies = (
);
name = Localytics;
productName = Localytics;
productReference = DAD3127115528CD200A3F9ED /* libLocalytics.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -3037,7 +3108,7 @@
DAC6325C1486805C0075AEA5 /* uicolor-utilities */,
DAC6326B148680650075AEA5 /* jrswizzle */,
DA95D59B14DF063C008D1B94 /* InAppSettingsKit */,
DABB9807150FF40100B05417 /* SendToMac */,
DAD3127015528CD200A3F9ED /* Localytics */,
);
};
/* End PBXProject section */
@ -3720,13 +3791,31 @@
DAB8D97A15036BF700CED3BC /* tip_location_wood.png in Resources */,
DAB8D97B15036BF700CED3BC /* tip_location_wood@2x.png in Resources */,
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */,
DAD3126815528C9C00A3F9ED /* Crashlytics.plist in Resources */,
DAD3126C15528C9C00A3F9ED /* TestFlight.plist in Resources */,
DAD3129015528D1600A3F9ED /* Localytics.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
DA6556E314D55F3000841C99 /* ShellScript */ = {
DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script: GIT version -> Info.plist";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = "/bin/bash -e";
shellScript = "PATH+=:/usr/libexec\n\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Set :'$key' $value\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :'$key'\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n \n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\nbuild=$(git describe --tags --always --dirty --long)\ntag=$(git describe --tags | sed 's/-\\([^-]*\\)-[^-]*$/.\\1/')\n\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n";
showEnvVarsInLog = 0;
};
DAD3125D155288AA00A3F9ED /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -3737,7 +3826,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = "/bin/bash -e";
shellScript = "PATH+=:/usr/libexec\n\nsetPlistWithKey() {\n local key=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n\n PlistBuddy -c \"Set :$key $value\" \"$plist\"\n}\ngetPlistWithKey() {\n local key=$1 plist=${2:-\"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"}\n \n PlistBuddy -c \"Print :$key\" \"$plist\"\n}\nsetSettingWithTitle() {\n local i title=$1 value=$2 plist=${3:-\"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Settings.bundle/Root.plist\"}\n \n for (( i=0; 1; ++i )); do\n PlistBuddy -c \"Print :PreferenceSpecifiers:$i\" \"$plist\" &>/dev/null || break\n echo \"Checking preference specifier $i\"\n \n [[ $(PlistBuddy -c \"Print :PreferenceSpecifiers:$i:Title\" \"$plist\" 2>/dev/null) = $title ]] || continue\n\n echo \"Correct title, setting value.\"\n PlistBuddy -c \"Set :PreferenceSpecifiers:$i:DefaultValue $value\" \"$plist\"\n break\n done\n}\n\nbuild=$(git describe --tags --always --dirty --long)\ntag=$(git describe --tags | sed 's/-\\([^-]*\\)-[^-]*$/.\\1/')\n\nsetPlistWithKey CFBundleVersion \"$build\"\nsetPlistWithKey CFBundleShortVersionString \"$tag\"\n\nsetSettingWithTitle \"Build\" \"$build\"\nsetSettingWithTitle \"Version\" \"$tag\"\nsetSettingWithTitle \"Copyright\" \"$(getPlistWithKey NSHumanReadableCopyright)\"\n";
shellScript = "Crashlytics/Crashlytics.framework/run \"$(/usr/libexec/PlistBuddy -c \"Print :'API Key'\" Crashlytics/Crashlytics.plist)\"";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@ -3787,19 +3876,12 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DABB9804150FF40100B05417 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DABB980F150FF40100B05417 /* SendToMac.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DAC632591486805C0075AEA5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DAC63280148680B10075AEA5 /* UIColor-Expanded.m in Sources */,
DAD312BC1552977200A3F9ED /* UIColor+Expanded.m in Sources */,
DAD312BE1552977200A3F9ED /* UIColor+HSV.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3851,6 +3933,17 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DAD3126D15528CD200A3F9ED /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DAD3128815528D0F00A3F9ED /* LocalyticsDatabase.m in Sources */,
DAD3128A15528D0F00A3F9ED /* LocalyticsSession.m in Sources */,
DAD3128C15528D0F00A3F9ED /* LocalyticsUploader.m in Sources */,
DAD3128E15528D0F00A3F9ED /* UploaderThread.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -3891,131 +3984,226 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"TESTFLIGHT=1",
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES;
GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES;
GCC_WARN_CHECK_SWITCH_STATEMENTS = YES;
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_MISSING_PARENTHESES = YES;
GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = NO;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}";
SDKROOT = iphoneos;
STRIP_INSTALLED_PRODUCT = NO;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
DA5BFA6C147E415C00F98B1E /* Release */ = {
DA5BFA6C147E415C00F98B1E /* AdHoc */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"TESTFLIGHT=1",
"ADHOC=1",
"$(inherited)",
"NDEBUG=1",
"NS_BLOCK_ASSERTIONS=1",
);
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES;
GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES;
GCC_WARN_CHECK_SWITCH_STATEMENTS = YES;
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_MISSING_PARENTHESES = YES;
GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
SDKROOT = iphoneos;
STRIP_INSTALLED_PRODUCT = NO;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
name = AdHoc;
};
DA5BFA6E147E415C00F98B1E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MasterPassword/iOS/MasterPassword.entitlements;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/Crashlytics\"",
);
GCC_PREFIX_HEADER = "MasterPassword/iOS/MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword/iOS/MasterPassword-Info.plist";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/External/TestFlightSDK\"",
"\"$(SRCROOT)/TestFlight\"",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
name = Debug;
};
DA5BFA6F147E415C00F98B1E /* Release */ = {
DA5BFA6F147E415C00F98B1E /* AdHoc */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MasterPassword/iOS/MasterPassword.entitlements;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/Crashlytics\"",
);
GCC_PREFIX_HEADER = "MasterPassword/iOS/MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword/iOS/MasterPassword-Info.plist";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/External/TestFlightSDK\"",
"\"$(SRCROOT)/TestFlight\"",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
name = Release;
name = AdHoc;
};
DA95D5A514DF063C008D1B94 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
DSTROOT = /tmp/InAppSettingsKit.dst;
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = InAppSettingsKit;
SKIP_INSTALL = YES;
};
name = Debug;
};
DA95D5A614DF063C008D1B94 /* Release */ = {
DA95D5A614DF063C008D1B94 /* AdHoc */ = {
isa = XCBuildConfiguration;
buildSettings = {
DSTROOT = /tmp/InAppSettingsKit.dst;
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = InAppSettingsKit;
SKIP_INSTALL = YES;
};
name = Release;
name = AdHoc;
};
DA95D60914DF3F3B008D1B94 /* AppStore */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"APPSTORE=1",
"$(inherited)",
"NDEBUG=1",
"NS_BLOCK_ASSERTIONS=1",
);
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES;
GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES;
GCC_WARN_CHECK_SWITCH_STATEMENTS = YES;
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_MISSING_PARENTHESES = YES;
GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
SDKROOT = iphoneos;
STRIP_INSTALLED_PRODUCT = NO;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@ -4026,16 +4214,17 @@
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MasterPassword/iOS/MasterPassword.entitlements;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/Crashlytics\"",
);
GCC_PREFIX_HEADER = "MasterPassword/iOS/MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword/iOS/MasterPassword-Info.plist";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/External/TestFlightSDK\"",
"\"$(SRCROOT)/TestFlight\"",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
name = AppStore;
};
@ -4081,60 +4270,13 @@
isa = XCBuildConfiguration;
buildSettings = {
DSTROOT = /tmp/InAppSettingsKit.dst;
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = InAppSettingsKit;
SKIP_INSTALL = YES;
};
name = AppStore;
};
DABB9811150FF40100B05417 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
DSTROOT = /tmp/SendToMac.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "SendToMac/SendToMac-Prefix.pch";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_UNINITIALIZED_AUTOS = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
DABB9812150FF40100B05417 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COPY_PHASE_STRIP = YES;
DSTROOT = /tmp/SendToMac.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "SendToMac/SendToMac-Prefix.pch";
GCC_WARN_UNINITIALIZED_AUTOS = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
DABB9813150FF40100B05417 /* AppStore */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COPY_PHASE_STRIP = YES;
DSTROOT = /tmp/SendToMac.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "SendToMac/SendToMac-Prefix.pch";
GCC_WARN_UNINITIALIZED_AUTOS = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = AppStore;
};
DAC632661486805C0075AEA5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -4147,7 +4289,7 @@
};
name = Debug;
};
DAC632671486805C0075AEA5 /* Release */ = {
DAC632671486805C0075AEA5 /* AdHoc */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
@ -4157,7 +4299,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
name = AdHoc;
};
DAC63275148680650075AEA5 /* Debug */ = {
isa = XCBuildConfiguration;
@ -4171,7 +4313,7 @@
};
name = Debug;
};
DAC63276148680650075AEA5 /* Release */ = {
DAC63276148680650075AEA5 /* AdHoc */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
@ -4181,7 +4323,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
name = AdHoc;
};
DAC77CB5148291A600BCF976 /* Debug */ = {
isa = XCBuildConfiguration;
@ -4197,7 +4339,7 @@
};
name = Debug;
};
DAC77CB6148291A600BCF976 /* Release */ = {
DAC77CB6148291A600BCF976 /* AdHoc */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
@ -4209,7 +4351,28 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
name = AdHoc;
};
DAD3127A15528CD200A3F9ED /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
};
name = Debug;
};
DAD3127B15528CD200A3F9ED /* AdHoc */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
};
name = AdHoc;
};
DAD3127C15528CD200A3F9ED /* AppStore */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
};
name = AppStore;
};
/* End XCBuildConfiguration section */
@ -4218,70 +4381,71 @@
isa = XCConfigurationList;
buildConfigurations = (
DA5BFA6B147E415C00F98B1E /* Debug */,
DA5BFA6C147E415C00F98B1E /* Release */,
DA5BFA6C147E415C00F98B1E /* AdHoc */,
DA95D60914DF3F3B008D1B94 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
defaultConfigurationName = AdHoc;
};
DA5BFA6D147E415C00F98B1E /* Build configuration list for PBXNativeTarget "MasterPassword" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA5BFA6E147E415C00F98B1E /* Debug */,
DA5BFA6F147E415C00F98B1E /* Release */,
DA5BFA6F147E415C00F98B1E /* AdHoc */,
DA95D60A14DF3F3B008D1B94 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
defaultConfigurationName = AdHoc;
};
DA95D5A414DF063C008D1B94 /* Build configuration list for PBXNativeTarget "InAppSettingsKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA95D5A514DF063C008D1B94 /* Debug */,
DA95D5A614DF063C008D1B94 /* Release */,
DA95D5A614DF063C008D1B94 /* AdHoc */,
DA95D60E14DF3F3B008D1B94 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
DABB9810150FF40100B05417 /* Build configuration list for PBXNativeTarget "SendToMac" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DABB9811150FF40100B05417 /* Debug */,
DABB9812150FF40100B05417 /* Release */,
DABB9813150FF40100B05417 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = AdHoc;
};
DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DAC632661486805C0075AEA5 /* Debug */,
DAC632671486805C0075AEA5 /* Release */,
DAC632671486805C0075AEA5 /* AdHoc */,
DA95D60C14DF3F3B008D1B94 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
defaultConfigurationName = AdHoc;
};
DAC63274148680650075AEA5 /* Build configuration list for PBXNativeTarget "jrswizzle" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DAC63275148680650075AEA5 /* Debug */,
DAC63276148680650075AEA5 /* Release */,
DAC63276148680650075AEA5 /* AdHoc */,
DA95D60D14DF3F3B008D1B94 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
defaultConfigurationName = AdHoc;
};
DAC77CB7148291A600BCF976 /* Build configuration list for PBXNativeTarget "Pearl" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DAC77CB5148291A600BCF976 /* Debug */,
DAC77CB6148291A600BCF976 /* Release */,
DAC77CB6148291A600BCF976 /* AdHoc */,
DA95D60B14DF3F3B008D1B94 /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
defaultConfigurationName = AdHoc;
};
DAD3127915528CD200A3F9ED /* Build configuration list for PBXNativeTarget "Localytics" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DAD3127A15528CD200A3F9ED /* Debug */,
DAD3127B15528CD200A3F9ED /* AdHoc */,
DAD3127C15528CD200A3F9ED /* AppStore */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = AdHoc;
};
/* End XCConfigurationList section */

View File

@ -68,14 +68,14 @@
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
buildConfiguration = "AdHoc"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
buildConfiguration = "AdHoc"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -42,9 +42,7 @@ static NSDictionary *keyHashQuery() {
[PearlKeyChain deleteItemForQuery:keyHashQuery()];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
#endif
}
- (void)signOut {
@ -63,9 +61,7 @@ static NSDictionary *keyHashQuery() {
// Key should not be stored in keychain. Delete it.
dbg(@"Deleting key from key chain.");
[PearlKeyChain deleteItemForQuery:keyQuery()];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
#endif
}
}
@ -96,15 +92,11 @@ static NSDictionary *keyHashQuery() {
if (![keyHash isEqual:tryKeyHash]) {
dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyHash, tryKeyHash);
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
#endif
return NO;
}
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointMPAsked];
#endif
[self updateKey:tryKey];
return YES;
@ -142,9 +134,7 @@ static NSDictionary *keyHashQuery() {
nil]];
}
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSetKeyphraseLength, key.length]];
#endif
}
}

View File

@ -13,6 +13,6 @@
@interface MPElementGeneratedEntity : MPElementEntity
@property (nonatomic, assign) uint16_t counter;
@property (nonatomic, assign) int16_t counter;
@end

View File

@ -22,7 +22,7 @@
return nil;
if (self.type & MPElementTypeClassCalculated)
return MPCalculateContent(self.type, self.name, [MPAppDelegate get].key, self.counter);
return MPCalculateContent((unsigned)self.type, self.name, [MPAppDelegate get].key, self.counter);
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"Unsupported type: %d", self.type] userInfo:nil];

View File

@ -32,10 +32,10 @@ typedef enum {
MPElementTypeStoredDevicePrivate = MPElementTypeClassStored | 0x02,
} MPElementType;
#ifdef TESTFLIGHT
#define MPTestFlightCheckpointAction @"MPTestFlightCheckpointAction"
#define MPTestFlightCheckpointHelpChapter @"MPTestFlightCheckpointHelpChapter_%@"
#define MPTestFlightCheckpointCopyToPasteboard @"MPTestFlightCheckpointCopyToPasteboard"
#define MPTestFlightCheckpointResetPasswordCounter @"MPTestFlightCheckpointResetPasswordCounter"
#define MPTestFlightCheckpointIncrementPasswordCounter @"MPTestFlightCheckpointIncrementPasswordCounter"
#define MPTestFlightCheckpointEditPassword @"MPTestFlightCheckpointEditPassword"
#define MPTestFlightCheckpointCloseAlert @"MPTestFlightCheckpointCloseAlert"
@ -56,7 +56,6 @@ typedef enum {
#define MPTestFlightCheckpointMPAsked @"MPTestFlightCheckpointMPAsked"
#define MPTestFlightCheckpointStoreIncompatible @"MPTestFlightCheckpointStoreIncompatible"
#define MPTestFlightCheckpointSetKeyphraseLength @"MPTestFlightCheckpointSetKeyphraseLength_%d"
#endif
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
#define MPNotificationKeySet @"MPNotificationKeySet"
@ -69,4 +68,4 @@ NSData *keyHashForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint16_t counter);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int16_t counter);

View File

@ -102,7 +102,7 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
}
static NSDictionary *MPTypes_ciphers = nil;
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, uint16_t counter) {
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, int16_t counter) {
assert(type & MPElementTypeClassCalculated);

View File

@ -11,6 +11,23 @@
#import "MPMainViewController.h"
#import "IASKSettingsReader.h"
#import "LocalyticsSession.h"
#import "TestFlight.h"
#import <Crashlytics/Crashlytics.h>
@interface MPAppDelegate ()
- (NSString *)testFlightInfo;
- (NSString *)testFlightToken;
- (NSString *)crashlyticsInfo;
- (NSString *)crashlyticsAPIKey;
- (NSString *)localyticsInfo;
- (NSString *)localyticsKey;
@end
@implementation MPAppDelegate
@ -28,30 +45,70 @@
#ifdef DEBUG
[PearlLogger get].autoprintLevel = PearlLogLevelDebug;
[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
// [NSClassFromString(@"WebView") performSelector:NSSelectorFromString(@"_enableRemoteInspector")];
#endif
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#ifdef TESTFLIGHT
@try {
[TestFlight takeOff:@"bd44885deee7adce0645ce8e5498d80a_NDQ5NDQyMDExLTEyLTAyIDExOjM1OjQ4LjQ2NjM4NA"];
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], @"logToConsole",
[NSNumber numberWithBool:NO], @"logToSTDERR",
nil]];
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelInfo)
TFLog(@"%@", message);
#ifndef DEBUG
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
@try {
NSString *testFlightToken = [self testFlightToken];
if ([testFlightToken length]) {
dbg(@"Initializing TestFlight");
[TestFlight addCustomEnvironmentInformation:@"Anonymous" forKey:@"username"];
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], @"logToConsole",
[NSNumber numberWithBool:NO], @"logToSTDERR",
nil]];
[TestFlight takeOff:testFlightToken];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelInfo)
TFLog(@"%@", message);
return YES;
}];
}
@catch (NSException *exception) {
err(@"TestFlight: %@", exception);
}
return YES;
}];
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
}
}
@catch (NSException *exception) {
err(@"TestFlight: %@", exception);
}
@try {
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
if ([crashlyticsAPIKey length]) {
dbg(@"Initializing Crashlytics");
//[Crashlytics sharedInstance].debugMode = YES;
[Crashlytics startWithAPIKey:crashlyticsAPIKey afterDelay:0];
}
}
@catch (NSException *exception) {
err(@"Crashlytics: %@", exception);
}
@try {
NSString *localyticsKey = [self localyticsKey];
if ([localyticsKey length]) {
dbg(@"Initializing Localytics");
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
if (message.level >= PearlLogLevelError)
[[LocalyticsSession sharedLocalyticsSession] tagEvent:@"Problem" attributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[message levelDescription],
@"level",
message.message,
@"message",
nil]];
return YES;
}];
}
}
@catch (NSException *exception) {
err(@"Localytics exception: %@", exception);
}
});
#endif
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
@ -59,10 +116,10 @@
[[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,
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, -1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
nil]];
UIImage *navBarButton = [[UIImage imageNamed:@"ui_navbar_button"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
@ -73,10 +130,10 @@
[[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,
[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextColor,
[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowColor,
[NSValue valueWithUIOffset:UIOffsetMake(0, 1)], UITextAttributeTextShadowOffset,
[UIFont fontWithName:@"Helvetica-Neue" size:0.0f], UITextAttributeFont,
nil]
forState:UIControlStateNormal];
@ -118,7 +175,7 @@
[self loadKey:YES];
}];
#ifdef TESTFLIGHT
#ifdef ADHOC
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
@"Thank you for taking the time to test Master Password.\n\n"
@"Please provide any feedback, however minor it may seem, via the Feedback action item accessible from the top right.\n\n"
@ -143,18 +200,14 @@
else
[self loadKey:NO];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
#endif
}
- (void)showGuide {
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
#endif
}
- (void)loadKey:(BOOL)animated {
@ -172,6 +225,34 @@
});
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload];
[super applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[LocalyticsSession sharedLocalyticsSession] resume];
[[LocalyticsSession sharedLocalyticsSession] upload];
[super applicationWillEnterForeground:application];
}
- (void)applicationWillTerminate:(UIApplication *)application {
[self saveContext];
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated];
[[LocalyticsSession sharedLocalyticsSession] close];
[[LocalyticsSession sharedLocalyticsSession] upload];
[super applicationWillTerminate:application];
}
- (void)applicationWillResignActive:(UIApplication *)application {
[self saveContext];
@ -179,18 +260,7 @@
if (![[MPiOSConfig get].rememberKey boolValue])
[self updateKey:nil];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
#endif
}
- (void)applicationWillTerminate:(UIApplication *)application {
[self saveContext];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointTerminated];
#endif
}
+ (NSManagedObjectContext *)managedObjectContext {
@ -279,9 +349,7 @@
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
#endif
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointStoreIncompatible];
#endif
@throw [NSException exceptionWithName:error.domain reason:error.localizedDescription
userInfo:[NSDictionary dictionaryWithObject:error forKey:@"cause"]];
@ -306,4 +374,70 @@
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
#pragma mark - TestFlight
static NSDictionary *testFlightInfo = nil;
- (NSDictionary *)testFlightInfo {
if (testFlightInfo == nil)
testFlightInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"TestFlight" withExtension:@"plist"]];
return testFlightInfo;
}
- (NSString *)testFlightToken {
return NSNullToNil([[self testFlightInfo] valueForKeyPath:@"Team Token"]);
}
#pragma mark - Crashlytics
static NSDictionary *crashlyticsInfo = nil;
- (NSDictionary *)crashlyticsInfo {
if (crashlyticsInfo == nil)
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
return crashlyticsInfo;
}
- (NSString *)crashlyticsAPIKey {
return NSNullToNil([[self crashlyticsInfo] valueForKeyPath:@"API Key"]);
}
#pragma mark - Localytics
static NSDictionary *localyticsInfo = nil;
- (NSDictionary *)localyticsInfo {
if (localyticsInfo == nil)
localyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
[[NSBundle mainBundle] URLForResource:@"Localytics" withExtension:@"plist"]];
return localyticsInfo;
}
- (NSString *)localyticsKey {
#ifdef DEBUG
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.development"]);
#elif defined(LITE)
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution.lite"]);
#else
return NSNullToNil([[self localyticsInfo] valueForKeyPath:@"Key.distribution"]);
#endif
}
@end

View File

@ -34,6 +34,7 @@
- (IBAction)copyContent;
- (IBAction)incrementPasswordCounter;
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender;
- (IBAction)editPassword;
- (IBAction)closeAlert;
- (IBAction)action:(UIBarButtonItem *)sender;

View File

@ -21,7 +21,8 @@
- (void)updateWasAnimated:(BOOL)animated;
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon;
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message;
- (void)updateElement:(void (^)(void))updateElement;
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task;
- (void)changeElementWithoutWarningDo:(void (^)(void))task;
@end
@ -163,9 +164,9 @@
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)
[self.typeButton setTitle:NSStringFromMPElementType((unsigned)self.activeElement.type)
forState:UIControlStateNormal];
self.typeButton.alpha = NSStringFromMPElementType(self.activeElement.type).length? 1: 0;
self.typeButton.alpha = NSStringFromMPElementType((unsigned)self.activeElement.type).length? 1: 0;
self.contentField.enabled = NO;
@ -212,9 +213,7 @@
- (void)setHelpChapter:(NSString *)chapter {
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointHelpChapter, chapter]];
#endif
dispatch_async(dispatch_get_main_queue(), ^{
[self.helpView loadRequest:
@ -227,7 +226,7 @@
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
ClassNameFromMPElementType(self.activeElement.type)]];
ClassNameFromMPElementType((unsigned)self.activeElement.type)]];
if (error.length)
err(@"helpView.setClass: %@", error);
}
@ -285,42 +284,76 @@
[self showContentTip:@"Copied!" withIcon:nil];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointCopyToPasteboard];
#endif
}
- (IBAction)incrementPasswordCounter {
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
// Not of a type that supports a password counter;
// Not of a type that supports a password counter.
return;
[self updateElement:^{
++((MPElementGeneratedEntity *) self.activeElement).counter;
}];
[self changeElementWithWarning:
@"You are incrementing the site's password counter.\n\n"
@"If you continue, a new password will be generated for this site. "
@"You will then need to update your account's old password to this newly generated password.\n"
@"You can reset the counter by holding down on this button."
do:^{
++((MPElementGeneratedEntity *) self.activeElement).counter;
}];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointIncrementPasswordCounter];
#endif
}
- (void)updateElement:(void (^)(void))updateElement {
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender {
// Update password counter.
if (![self.activeElement isKindOfClass:[MPElementGeneratedEntity class]])
// Not of a type that supports a password counter.
return;
if (((MPElementGeneratedEntity *)self.activeElement).counter == 1)
// Counter has initial value, no point resetting.
return;
[self changeElementWithWarning:
@"You are resetting the site's password counter.\n\n"
@"If you continue, the site's password will change back to its original value. "
@"You will then need to update your account's password back to this original value."
do:^{
((MPElementGeneratedEntity *) self.activeElement).counter = 1;
}];
[TestFlight passCheckpoint:MPTestFlightCheckpointResetPasswordCounter];
}
- (void)changeElementWithWarning:(NSString *)warning do:(void (^)(void))task; {
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[self changeElementWithoutWarningDo:task];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
- (void)changeElementWithoutWarningDo:(void (^)(void))task; {
// Update element, keeping track of the old password.
NSString *oldPassword = self.activeElement.description;
updateElement();
task();
NSString *newPassword = self.activeElement.description;
[self updateAnimated:YES];
// Show new and old password.
if ([oldPassword length] && ![oldPassword isEqualToString:newPassword])
[self showAlertWithTitle:@"Password Changed!" message:l(@"The password for %@ has changed.\n\n"
@"IMPORTANT:\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) {
@ -328,9 +361,7 @@
[self.contentField becomeFirstResponder];
}
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointEditPassword];
#endif
}
- (IBAction)closeAlert {
@ -342,9 +373,7 @@
self.alertBody.text = nil;
}];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointCloseAlert];
#endif
}
- (IBAction)action:(id)sender {
@ -371,7 +400,7 @@
[self.navigationController pushViewController:settingsVC animated:YES];
break;
}
#ifdef TESTFLIGHT
#ifdef ADHOC
case 4:
[TestFlight openFeedbackView];
break;
@ -386,13 +415,11 @@
}
}
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointAction];
#endif
} cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:nil
otherTitles:
[self isHelpVisible]? @"Hide Help": @"Show Help", @"FAQ", @"Tutorial", @"Settings",
#ifdef TESTFLIGHT
#ifdef ADHOC
@"Feedback",
#endif
@"Sign Out",
@ -401,36 +428,38 @@
- (MPElementType)selectedType {
return self.activeElement.type;
return (unsigned)self.activeElement.type;
}
- (void)didSelectType:(MPElementType)type {
[self updateElement:^{
// Update password type.
if (ClassFromMPElementType(type) != ClassFromMPElementType(self.activeElement.type))
// Type requires a different class of element. Recreate the element.
[[MPAppDelegate managedObjectContext] performBlockAndWait:^{
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromMPElementType(type)
inManagedObjectContext:[MPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name;
newElement.mpHashHex = self.activeElement.mpHashHex;
newElement.uses = self.activeElement.uses;
newElement.lastUsed = self.activeElement.lastUsed;
[self changeElementWithWarning:
@"You are about to change the type of this password.\n\n"
@"If you continue, the password for this site will change. "
@"You will need to update your account's old password to the new one."
do:^{
// Update password type.
if (ClassFromMPElementType(type) != ClassFromMPElementType((unsigned)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;
}];
[[MPAppDelegate managedObjectContext] deleteObject:self.activeElement];
self.activeElement = newElement;
}];
self.activeElement.type = type;
self.activeElement.type = type;
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
#endif
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
if (type & MPElementTypeClassStored && ![self.activeElement.description length])
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
}];
if (type & MPElementTypeClassStored && ![self.activeElement.description length])
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
}];
}
- (void)didSelectElement:(MPElementEntity *)element {
@ -443,17 +472,14 @@
[self showAlertWithTitle:@"New Site" message:
l(@"You've just created a password for %@.\n\n"
@"IMPORTANT:\n"
@"Don't forget to set or change the password for your account at %@ to the password above. "
@"It's best to do this right away. If you forget it, may get confusing later on "
@"to remember what password you need to use for logging into the site.",
@"Go to %@ and set or change the password for your account to the password above.\n"
@"Do this right away: if you forget, you may have trouble remembering which password to use to log into the site later on.",
self.activeElement.name, self.activeElement.name)];
[self.searchDisplayController setActive:NO animated:YES];
self.searchDisplayController.searchBar.text = self.activeElement.name;
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointSelectElement];
#endif
}
[self updateAnimated:YES];
@ -479,7 +505,7 @@
// Content hasn't changed.
return;
[self updateElement:^{
[self changeElementWithoutWarningDo:^{
((MPElementStoredEntity *) self.activeElement).content = self.contentField.text;
}];
}
@ -489,9 +515,7 @@
navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointExternalLink];
#endif
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;

View File

@ -41,8 +41,8 @@
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
UITableView *tableView = self.searchDisplayController.searchResultsTableView;
for (NSUInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
NSUInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
for (NSInteger section = 0; section < [self numberOfSectionsInTableView:tableView]; ++section) {
NSInteger rowCount = [self tableView:tableView numberOfRowsInSection:section];
if (!rowCount)
continue;
@ -54,9 +54,7 @@
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointCancelSearch];
#endif
[self.delegate didSelectElement:nil];
}
@ -189,13 +187,13 @@
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.fetchedResultsController sections] count] + ([self.query length]? 1: 0);
return (signed)[[self.fetchedResultsController sections] count] + ([self.query length]? 1: 0);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section < [[self.fetchedResultsController sections] count])
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
if (section < (signed)[[self.fetchedResultsController sections] count])
return (signed)[[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] numberOfObjects];
return 1;
}
@ -230,7 +228,7 @@
- (void)configureCell:(UITableViewCell *)cell inTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < [[self.fetchedResultsController sections] count]) {
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count]) {
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = element.name;
@ -245,7 +243,7 @@
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < [[self.fetchedResultsController sections] count])
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count])
[self.delegate didSelectElement:[self.fetchedResultsController objectAtIndexPath:indexPath]];
else {
@ -263,7 +261,7 @@
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
assert([element isKindOfClass:ClassFromMPElementType((unsigned)element.type)]);
assert([MPAppDelegate get].keyHashHex);
element.name = siteName;
@ -279,8 +277,8 @@
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (section < [[self.fetchedResultsController sections] count])
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
if (section < (signed)[[self.fetchedResultsController sections] count])
return [[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] name];
return @"";
}
@ -297,15 +295,13 @@
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section < [[self.fetchedResultsController sections] count]) {
if (indexPath.section < (signed)[[self.fetchedResultsController sections] count]) {
if (editingStyle == UITableViewCellEditingStyleDelete)
[self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.fetchedResultsController.managedObjectContext deleteObject:element];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointDeleteElement];
#endif
}];
}
}

View File

@ -173,7 +173,7 @@ typedef enum {
dispatch_async(dispatch_get_main_queue(), ^{
[self showMessage:@"Success!" state:MPLockscreenSuccess];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 1.5f), dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long)(NSEC_PER_SEC * 1.5f)), dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES];
});
});
@ -214,9 +214,7 @@ typedef enum {
[[MPAppDelegate get] loadKey:YES];
#ifdef TESTFLIGHT
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
#endif
}
cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil];

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2177" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="1.1" toolsVersion="2182" systemVersion="11D50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="KZF-fe-y9n">
<dependencies>
<deployment defaultVersion="1296" identifier="iOS"/>
<development defaultVersion="4200" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1173"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="1181"/>
</dependencies>
<scenes>
<!--Type View Controller - Type-->
@ -399,6 +400,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
<button opaque="NO" alpha="0.50000000000000011" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="jec-mu-nPt">
<rect key="frame" x="272.5" y="18.5" width="36.5" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<gestureRecognizers/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<inset key="contentEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
<state key="normal" image="icon_plus.png">
@ -410,6 +412,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you lose you
</state>
<connections>
<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>
</button>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9FS-fS-xH6">
@ -593,6 +596,12 @@ L4m3P4sSw0rD</string>
<outlet property="typeButton" destination="Cei-5z-uWE" id="4M1-d7-5Bh"/>
</connections>
</viewController>
<pongPressGestureRecognizer allowableMovement="10" minimumPressDuration="0.5" id="cZr-Fj-eBw">
<connections>
<action selector="resetPasswordCounter:" destination="PQa-Xl-A3x" id="JL9-Ob-AjQ"/>
<outlet property="delegate" destination="PQa-Xl-A3x" id="PMQ-so-Cj7"/>
</connections>
</pongPressGestureRecognizer>
</objects>
<point key="canvasLocation" x="455" y="182"/>
</scene>

View File

@ -15,9 +15,7 @@
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#ifdef TESTFLIGHT
#import "TestFlight.h"
#endif
#import "TestFlight.h"
#import "MPTypes.h"
#import "MPiOSConfig.h"

View File

@ -1,18 +1,19 @@
Thanks for downloading the TestFlight SDK 0.8.3!
Thanks for downloading the TestFlight SDK 1.0!
This document is also available on the web at https://www.testflightapp.com/sdk/doc
1. Why use the TestFlight SDK?
2. Considerations
3. How do I integrate the SDK into my project?
4. Using the Checkpoint API
5. Using the Feedback API
6. Upload your build
7. Questions API
8. View your results
9. Advanced Exception Handling
10. Remote Logging
11. iOS 3
4. Beta Testing and Release Differentiation
5. Using the Checkpoint API
6. Using the Feedback API
7. Upload your build
8. Questions API
9. View your results
10. Advanced Exception Handling
11. Remote Logging
12. iOS 3
START
@ -96,14 +97,26 @@ This SDK can be run from both the iPhone Simulator and Device and has been teste
2. Strip Debug Symbols During Copy
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.
`[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.
5. Using the Feedback API
6. Using the Feedback API
To launch unguided feedback call the `openFeedbackView` method. We recommend that you call this from a GUI element.
@ -122,11 +135,11 @@ The above sample assumes that [self getUserFeedback] is implemented such that it
Once users have submitted feedback from inside of the application you can view it in the feedback area of your build page.
6. Upload your build.
7. 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/>
7. Add Questions to Checkpoints
8. 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.
@ -138,11 +151,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.
8. View your results.
9. 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.
9. Advanced Exception Handling
10. 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:
@ -182,7 +195,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.
10. Remote Logging
11. 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
@ -214,9 +227,16 @@ The STDERR logger sends log messages to STDERR so that you can see your log stat
The default option is YES.
11. iOS3
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 you can follow
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

View File

@ -6,7 +6,7 @@
// Copyright 2011 TestFlight. All rights reserved.
#import <Foundation/Foundation.h>
#define TESTFLIGHT_SDK_VERSION @"0.8.3"
#define TESTFLIGHT_SDK_VERSION @"1.0"
#undef TFLog
#if __cplusplus
@ -78,4 +78,19 @@ extern "C" {
*/
+ (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

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Team Token</key>
<string></string>
</dict>
</plist>

View File

@ -1,3 +1,10 @@
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.