Tag screens in Localytics + email fix + psc fix
[FIXED] Sending email with no recipient caused a crash. [FIXED] Duplicate persistence coordinator. [UPDATED] Crashlytics and Localytics SDKs. [UPDATED] Don't continue the Localytics session when device locked. [UPDATED] Put Localytics communication on HTTPS for confidentiality. [ADDED] Tagging screens in Localytics.
This commit is contained in:
parent
6f82cf7f15
commit
bce6b96417
Binary file not shown.
@ -148,6 +148,26 @@ OBJC_EXTERN void CLSNSLog(NSString *format, ...);
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* The CLSCrashReport protocol exposes methods that you can call on crash report objects passed
|
||||
* to delegate methods. If you want these values or the entire object to stay in memory retain
|
||||
* them or copy them.
|
||||
**/
|
||||
@protocol CLSCrashReport <NSObject>
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Returns the session identifier for the crash report.
|
||||
**/
|
||||
- (NSString *)identifier;
|
||||
|
||||
/**
|
||||
* Returns the custom key value data for the crash report.
|
||||
**/
|
||||
- (NSDictionary *)customKeys;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
*
|
||||
* The CrashlyticsDelegate protocol provides a mechanism for your application to take
|
||||
@ -169,4 +189,15 @@ OBJC_EXTERN void CLSNSLog(NSString *format, ...);
|
||||
**/
|
||||
- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics;
|
||||
|
||||
/**
|
||||
*
|
||||
* Just like crashlyticsDidDetectCrashDuringPreviousExecution this delegate method is
|
||||
* called once a Crashlytics instance has determined that the last execution of the
|
||||
* application ended in a crash. A CLSCrashReport is passed back that contains data about
|
||||
* the last crash report that was generated. See the CLSCrashReport protocol for method details.
|
||||
* This method is called after crashlyticsDidDetectCrashDuringPreviousExecution.
|
||||
*
|
||||
**/
|
||||
- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id <CLSCrashReport>)crash;
|
||||
|
||||
@end
|
||||
|
Binary file not shown.
BIN
Crashlytics/Crashlytics.framework/run
vendored
BIN
Crashlytics/Crashlytics.framework/run
vendored
Binary file not shown.
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
||||
Subproject commit 4ceb992dc59e9d085b241239b63df4754da90de7
|
||||
Subproject commit b8cf787af604299c5363846c5b2fcbd9e3083253
|
@ -1,10 +1,12 @@
|
||||
//
|
||||
// LocalyticsDatabase.h
|
||||
// LocalyticsDemo
|
||||
// Copyright (C) 2012 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// Created by jkaufman on 5/26/11.
|
||||
// Copyright 2011 Localytics. All rights reserved.
|
||||
// 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 <Foundation/Foundation.h>
|
||||
#import <sqlite3.h>
|
||||
@ -31,6 +33,8 @@
|
||||
|
||||
- (BOOL)addEventWithBlobString:(NSString *)blob;
|
||||
- (BOOL)addCloseEventWithBlobString:(NSString *)blob;
|
||||
- (BOOL)queueCloseEventWithBlobString:(NSString *)blob;
|
||||
- (NSString *)dequeueCloseEventBlobString;
|
||||
- (BOOL)addFlowEventWithBlobString:(NSString *)blob;
|
||||
- (BOOL)removeLastCloseAndFlowEvents;
|
||||
|
||||
@ -54,4 +58,13 @@
|
||||
- (NSString *)customDimension:(int)dimension;
|
||||
- (BOOL)setCustomDimension:(int)dimension value:(NSString *)value;
|
||||
|
||||
- (NSString *)customerId;
|
||||
- (BOOL)setCustomerId:(NSString *)newCustomerId;
|
||||
|
||||
- (NSInteger)safeIntegerValueFromDictionary:(NSDictionary *)dict forKey:(NSString *)key;
|
||||
- (NSString *)safeStringValueFromDictionary:(NSDictionary *)dict forKey:(NSString *)key;
|
||||
- (NSDictionary *)safeDictionaryFromDictionary:(NSDictionary *)dict forKey:(NSString *)key;
|
||||
- (NSArray *)safeListFromDictionary:(NSDictionary *)dict forKey:(NSString *)key;
|
||||
|
||||
|
||||
@end
|
||||
|
@ -1,10 +1,12 @@
|
||||
//
|
||||
// LocalyticsDatabase.m
|
||||
// LocalyticsDemo
|
||||
// Copyright (C) 2012 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// Created by jkaufman on 5/26/11.
|
||||
// Copyright 2011 Localytics. All rights reserved.
|
||||
// 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 "LocalyticsDatabase.h"
|
||||
|
||||
@ -17,6 +19,10 @@
|
||||
- (void)createSchema;
|
||||
- (void)upgradeToSchemaV2;
|
||||
- (void)upgradeToSchemaV3;
|
||||
- (void)upgradeToSchemaV4;
|
||||
- (void)upgradeToSchemaV5;
|
||||
- (void)upgradeToSchemaV6;
|
||||
- (void)upgradeToSchemaV7;
|
||||
- (void)moveDbToCaches;
|
||||
- (NSString *)randomUUID;
|
||||
@end
|
||||
@ -68,6 +74,12 @@ static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
|
||||
code = sqlite3_open([dbPath UTF8String], &_databaseConnection);
|
||||
}
|
||||
|
||||
// Enable foreign key constraints.
|
||||
if (code == SQLITE_OK) {
|
||||
const char *sql = [@"PRAGMA foreign_keys = ON;" cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
code = sqlite3_exec(_databaseConnection, sql, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
// Check db connection, creating schema if necessary.
|
||||
if (code == SQLITE_OK) {
|
||||
sqlite3_busy_timeout(_databaseConnection, BUSY_TIMEOUT); // Defaults to 0, otherwise.
|
||||
@ -83,6 +95,18 @@ static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
|
||||
if ([self schemaVersion] < 3) {
|
||||
[self upgradeToSchemaV3];
|
||||
}
|
||||
if ([self schemaVersion] < 4) {
|
||||
[self upgradeToSchemaV4];
|
||||
}
|
||||
if ([self schemaVersion] < 5) {
|
||||
[self upgradeToSchemaV5];
|
||||
}
|
||||
if ([self schemaVersion] < 6) {
|
||||
[self upgradeToSchemaV6];
|
||||
}
|
||||
if ([self schemaVersion] < 7) {
|
||||
[self upgradeToSchemaV7];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
@ -286,9 +310,153 @@ static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
|
||||
|
||||
// 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);
|
||||
int code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
sqlite3_exec(_databaseConnection,
|
||||
"ALTER TABLE localytics_info ADD app_key CHAR(64)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"UPDATE localytics_info set schema_version = 3",
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// V4 adds a field for the customer id.
|
||||
- (void)upgradeToSchemaV4 {
|
||||
int code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
sqlite3_exec(_databaseConnection,
|
||||
"ALTER TABLE localytics_info ADD customer_id CHAR(64)",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"UPDATE localytics_info set schema_version = 4",
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// V5 adds AMP related tables.
|
||||
- (void)upgradeToSchemaV5 {
|
||||
|
||||
int code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
|
||||
|
||||
//The AMP DB table was initially created here. in Version 7 it will be dropped and re-added with the correct data types.
|
||||
//therefore the code that creates it is no longer going to be called here.
|
||||
|
||||
//we still want to change the schema version
|
||||
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"UPDATE localytics_info set schema_version = 5",
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// V6 adds a field for the queued close event blob string.
|
||||
- (void)upgradeToSchemaV6 {
|
||||
int code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
sqlite3_exec(_databaseConnection,
|
||||
"ALTER TABLE localytics_info ADD queued_close_event_blob TEXT",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"UPDATE localytics_info set schema_version = 6",
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
-(void)upgradeToSchemaV7 {
|
||||
int code = sqlite3_exec(_databaseConnection, "BEGIN", NULL, NULL, NULL);
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection, "DROP TABLE IF EXISTS localytics_amp_rule", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"CREATE TABLE IF NOT EXISTS localytics_amp_rule ("
|
||||
"rule_id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
"rule_name TEXT UNIQUE, "
|
||||
"expiration INTEGER, "
|
||||
"phone_location TEXT, "
|
||||
"phone_size_width INTEGER, "
|
||||
"phone_size_height INTEGER, "
|
||||
"tablet_location TEXT, "
|
||||
"tablet_size_width INTEGER, "
|
||||
"tablet_size_height INTEGER, "
|
||||
"display_seconds INTEGER, "
|
||||
"display_session INTEGER, "
|
||||
"version INTEGER, "
|
||||
"did_display INTEGER, "
|
||||
"times_to_display INTEGER, "
|
||||
"internet_required INTEGER, "
|
||||
"ab_test TEXT"
|
||||
")",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"CREATE TABLE IF NOT EXISTS localytics_amp_ruleevent ("
|
||||
"rule_id INTEGER, "
|
||||
"event_name TEXT, "
|
||||
"FOREIGN KEY(rule_id) REFERENCES localytics_amp_rule(rule_id) ON DELETE CASCADE "
|
||||
")",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection,
|
||||
"UPDATE localytics_info set schema_version = 7",
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)databaseSize {
|
||||
@ -333,7 +501,7 @@ static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
|
||||
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;
|
||||
lastSessionStart = sqlite3_column_double(selectLastSessionStart, 0);
|
||||
}
|
||||
sqlite3_finalize(selectLastSessionStart);
|
||||
|
||||
@ -517,6 +685,46 @@ static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)queueCloseEventWithBlobString:(NSString *)blob {
|
||||
NSString *t = @"queue_close_event";
|
||||
BOOL success = [self beginTransaction:t];
|
||||
|
||||
// Queue close event.
|
||||
if (success) {
|
||||
sqlite3_stmt *queueCloseEvent;
|
||||
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info SET queued_close_event_blob = ?", -1, &queueCloseEvent, NULL);
|
||||
sqlite3_bind_text(queueCloseEvent, 1, [blob UTF8String], -1, SQLITE_TRANSIENT);
|
||||
int code = sqlite3_step(queueCloseEvent);
|
||||
sqlite3_finalize(queueCloseEvent);
|
||||
success = code == SQLITE_DONE;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
[self releaseTransaction:t];
|
||||
} else {
|
||||
[self rollbackTransaction:t];
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
- (NSString *)dequeueCloseEventBlobString {
|
||||
NSString *value = nil;
|
||||
NSString *query = @"SELECT queued_close_event_blob FROM localytics_info";
|
||||
|
||||
sqlite3_stmt *selectStmt;
|
||||
sqlite3_prepare_v2(_databaseConnection, [query UTF8String], -1, &selectStmt, NULL);
|
||||
int code = sqlite3_step(selectStmt);
|
||||
if (code == SQLITE_ROW && sqlite3_column_text(selectStmt, 0)) {
|
||||
value = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectStmt, 0)];
|
||||
}
|
||||
sqlite3_finalize(selectStmt);
|
||||
|
||||
// Clear the queued close event blob.
|
||||
[self queueCloseEventWithBlobString:nil];
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (BOOL)addFlowEventWithBlobString:(NSString *)blob {
|
||||
NSString *t = @"add_flow_event";
|
||||
BOOL success = [self beginTransaction:t];
|
||||
@ -669,13 +877,23 @@ static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
|
||||
code = sqlite3_exec(_databaseConnection, "DELETE FROM upload_headers", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection, "DELETE FROM localytics_amp_rule", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
code = sqlite3_exec(_databaseConnection, "DELETE FROM localytics_amp_ruleevent", 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",
|
||||
"custom_d0 = null, custom_d1 = null, custom_d2 = null, custom_d3 = null, "
|
||||
"customer_id = null, queued_close_event_blob = null ",
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
if (code == SQLITE_OK) {
|
||||
[self releaseTransaction:t];
|
||||
} else {
|
||||
@ -701,6 +919,82 @@ static LocalyticsDatabase *_sharedLocalyticsDatabase = nil;
|
||||
return [(NSString *)stringUUID autorelease];
|
||||
}
|
||||
|
||||
- (NSString *)customerId {
|
||||
NSString *customerId = nil;
|
||||
|
||||
sqlite3_stmt *selectCustomerId;
|
||||
sqlite3_prepare_v2(_databaseConnection, "SELECT customer_id FROM localytics_info", -1, &selectCustomerId, NULL);
|
||||
int code = sqlite3_step(selectCustomerId);
|
||||
if (code == SQLITE_ROW && sqlite3_column_text(selectCustomerId, 0)) {
|
||||
customerId = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectCustomerId, 0)];
|
||||
}
|
||||
sqlite3_finalize(selectCustomerId);
|
||||
|
||||
return customerId;
|
||||
}
|
||||
|
||||
- (BOOL)setCustomerId:(NSString *)newCustomerId
|
||||
{
|
||||
sqlite3_stmt *updateCustomerId;
|
||||
sqlite3_prepare_v2(_databaseConnection, "UPDATE localytics_info set customer_id = ?", -1, &updateCustomerId, NULL);
|
||||
sqlite3_bind_text (updateCustomerId, 1, [newCustomerId UTF8String], -1, SQLITE_TRANSIENT);
|
||||
int code = sqlite3_step(updateCustomerId);
|
||||
sqlite3_finalize(updateCustomerId);
|
||||
BOOL success = (code == SQLITE_DONE);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
#pragma mark - Safe NSDictionary value methods
|
||||
|
||||
- (NSInteger)safeIntegerValueFromDictionary:(NSDictionary *)dict forKey:(NSString *)key
|
||||
{
|
||||
NSInteger integerValue = 0;
|
||||
id value = [dict objectForKey:key];
|
||||
if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) {
|
||||
integerValue = [value integerValue];
|
||||
} else if ([value isKindOfClass:[NSNull class]]) {
|
||||
integerValue = 0;
|
||||
}
|
||||
|
||||
return integerValue;
|
||||
}
|
||||
|
||||
- (NSString *)safeStringValueFromDictionary:(NSDictionary *)dict forKey:(NSString *)key
|
||||
{
|
||||
NSString *stringValue = nil;
|
||||
id value = [dict objectForKey:key];
|
||||
if ([value isKindOfClass:[NSString class]]) {
|
||||
stringValue = value;
|
||||
} else if ([value isKindOfClass:[NSNumber class]]) {
|
||||
stringValue = [value stringValue];
|
||||
} else if ([value isKindOfClass:[NSNull class]]) {
|
||||
stringValue = nil;
|
||||
}
|
||||
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
- (NSDictionary *)safeDictionaryFromDictionary:(NSDictionary *)dict forKey:(NSString *)key
|
||||
{
|
||||
NSDictionary *dictValue = nil;
|
||||
id value = [dict objectForKey:key];
|
||||
if ([value isKindOfClass:[NSDictionary class]]) {
|
||||
dictValue = value;
|
||||
}
|
||||
return dictValue;
|
||||
}
|
||||
|
||||
- (NSArray *)safeListFromDictionary:(NSDictionary *)dict forKey:(NSString *)key
|
||||
{
|
||||
NSArray *arrayValue = nil;
|
||||
id value = [dict objectForKey:key];
|
||||
if ([value isKindOfClass:[NSArray class]]) {
|
||||
arrayValue = value;
|
||||
}
|
||||
return arrayValue;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
+ (id)allocWithZone:(NSZone *)zone {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// LocalyticsSession.h
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
// Copyright (C) 2012 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
|
||||
@ -8,6 +8,7 @@
|
||||
// Please visit www.localytics.com for more information.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
// Set this to true to enable localytics traces (useful for debugging)
|
||||
#define DO_LOCALYTICS_LOGGING false
|
||||
@ -38,6 +39,7 @@
|
||||
|
||||
@author Localytics
|
||||
*/
|
||||
|
||||
@interface LocalyticsSession : NSObject {
|
||||
|
||||
BOOL _hasInitialized; // Whether or not the session object has been initialized.
|
||||
@ -63,12 +65,24 @@
|
||||
BOOL _sessionHasBeenOpen; // Whether or not this session has ever been open.
|
||||
}
|
||||
|
||||
@property dispatch_queue_t queue;
|
||||
@property dispatch_group_t criticalGroup;
|
||||
@property (nonatomic,readonly) dispatch_queue_t queue;
|
||||
@property (nonatomic,readonly) dispatch_group_t criticalGroup;
|
||||
@property BOOL isSessionOpen;
|
||||
@property BOOL hasInitialized;
|
||||
@property float backgroundSessionTimeout;
|
||||
|
||||
- (void)logMessage:(NSString *)message;
|
||||
@property (nonatomic, assign, readonly) NSTimeInterval lastSessionStartTimestamp;
|
||||
@property (nonatomic, assign, readonly) NSInteger sessionNumber;
|
||||
|
||||
|
||||
/*!
|
||||
@property enableHTTPS
|
||||
@abstract Determines whether or not HTTPS is used when calling the Localytics
|
||||
post URL. The default is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL enableHTTPS; // Defaults to NO.
|
||||
|
||||
#pragma mark Public Methods
|
||||
/*!
|
||||
@method sharedLocalyticsSession
|
||||
@ -140,9 +154,11 @@
|
||||
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;
|
||||
is reopened. It is possible to use the return value to determine whether or not a session was resumed.
|
||||
This may be useful to some customers looking to do conditional instrumentation at the close of a session.
|
||||
It is perfectly reasonable to ignore the return value.
|
||||
@result YES if the sesion was resumed NO if it wasn't (suggesting a new session was created instead).*/
|
||||
- (BOOL)resume;
|
||||
|
||||
/*!
|
||||
@method close
|
||||
@ -213,4 +229,23 @@
|
||||
*/
|
||||
- (void)setCustomDimension:(int)dimension value:(NSString *)value;
|
||||
|
||||
/*!
|
||||
@method setLocation
|
||||
@abstract Stores the user's location. This will be used in all event and session calls.
|
||||
If your application has already collected the user's location, it may be passed to Localytics
|
||||
via this function. This will cause all events and the session close to include the locatin
|
||||
information. It is not required that you call this function.
|
||||
@param deviceLocation The user's location.
|
||||
*/
|
||||
- (void)setLocation:(CLLocationCoordinate2D)deviceLocation;
|
||||
|
||||
/*!
|
||||
@method ampTrigger
|
||||
@abstract Displays the AMP message for the specific event.
|
||||
Is a stub implementation here to prevent crashes if this class is accidentally used inplace of
|
||||
the LocalyticsAmpSession
|
||||
@param event Name of the event.
|
||||
*/
|
||||
- (void)ampTrigger:(NSString *)event;
|
||||
|
||||
@end
|
||||
|
@ -1,5 +1,5 @@
|
||||
// LocalyticsSession.m
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
// Copyright (C) 2012 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
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
#pragma mark Constants
|
||||
#define PREFERENCES_KEY @"_localytics_install_id" // The randomly generated ID for each install of the app
|
||||
#define CLIENT_VERSION @"iOS_2.6" // The version of this library
|
||||
#define CLIENT_VERSION @"iOS_2.12" // The version of this library
|
||||
#define LOCALYTICS_DIR @".localytics" // The directory in which the Localytics database is stored
|
||||
#define IFT_ETHER 0x6 // Ethernet CSMACD
|
||||
#define PATH_TO_APT @"/private/var/lib/apt/"
|
||||
@ -44,6 +44,7 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
@property (nonatomic, retain) NSMutableString *screens;
|
||||
@property (nonatomic, assign) NSTimeInterval sessionActiveDuration;
|
||||
@property (nonatomic, assign) BOOL sessionHasBeenOpen;
|
||||
@property (nonatomic, assign) NSInteger sessionNumber;
|
||||
|
||||
// Private methods.
|
||||
- (void)ll_open;
|
||||
@ -56,22 +57,23 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
- (BOOL)saveApplicationFlowAndRemoveOnResume:(BOOL)removeOnResume;
|
||||
- (NSString *)formatAttributeWithName:(NSString *)paramName value:(NSString *)paramValue;
|
||||
- (NSString *)formatAttributeWithName:(NSString *)paramName value:(NSString *)paramValue first:(BOOL)firstAttribute;
|
||||
- (void)logMessage:(NSString *)message;
|
||||
-(void) uploadCallback:(NSDictionary*)info;
|
||||
|
||||
// Datapoint methods.
|
||||
- (NSString *)customDimensions;
|
||||
- (NSString *)macAddress;
|
||||
- (NSString *)locationDimensions;
|
||||
- (NSString *)hashString:(NSString *)input;
|
||||
- (NSString *)randomUUID;
|
||||
- (NSString *)escapeString:(NSString *)input;
|
||||
- (NSString *)installationId;
|
||||
- (NSString *)uniqueDeviceIdentifier;
|
||||
- (NSString *)appVersion;
|
||||
- (NSTimeInterval)currentTimestamp;
|
||||
- (BOOL)isDeviceJailbroken;
|
||||
- (NSString *)deviceModel;
|
||||
- (NSString *)modelSizeString;
|
||||
- (double)availableMemory;
|
||||
- (NSString *)advertisingIdentifier;
|
||||
- (NSString *)uniqueDeviceIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
@ -92,6 +94,11 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
@synthesize screens = _screens;
|
||||
@synthesize sessionActiveDuration = _sessionActiveDuration;
|
||||
@synthesize sessionHasBeenOpen = _sessionHasBeenOpen;
|
||||
@synthesize sessionNumber = _sessionNumber;
|
||||
@synthesize enableHTTPS = _enableHTTPS;
|
||||
|
||||
// Stores the last location passed in to the app.
|
||||
CLLocationCoordinate2D lastDeviceLocation = {0};
|
||||
|
||||
#pragma mark Singleton
|
||||
|
||||
@ -112,6 +119,7 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
_sessionHasBeenOpen = NO;
|
||||
_queue = dispatch_queue_create("com.Localytics.operations", DISPATCH_QUEUE_SERIAL);
|
||||
_criticalGroup = dispatch_group_create();
|
||||
_enableHTTPS = NO;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
|
||||
|
||||
@ -171,6 +179,15 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
}
|
||||
|
||||
- (void)startSession:(NSString *)appKey {
|
||||
//check app key
|
||||
NSPredicate *matchPred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", @"[A-Fa-f0-9-]+"];
|
||||
BOOL matches = [matchPred evaluateWithObject:appKey];
|
||||
if (matches == NO) {
|
||||
//generate exception
|
||||
NSException *exception = [NSException exceptionWithName:@"Invalid Localytics App Key" reason:@"Application key is not valid. Please look at the iOS integration guidlines at http://www.localytics.com/docs/iphone-integration/" userInfo:nil];
|
||||
[exception raise];
|
||||
}
|
||||
|
||||
[self LocalyticsSession:appKey];
|
||||
[self open];
|
||||
[self upload];
|
||||
@ -183,35 +200,48 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)resume {
|
||||
dispatch_async(_queue, ^{
|
||||
// Do nothing if session is already open
|
||||
if(self.isSessionOpen == YES)
|
||||
return;
|
||||
|
||||
if([self ll_isOptedIn] == false) {
|
||||
[self logMessage:@"Can't resume session because user is opted out."];
|
||||
return;
|
||||
}
|
||||
- (BOOL)resume {
|
||||
__block BOOL resumed = NO;
|
||||
|
||||
// conditions for resuming previous session
|
||||
if(self.sessionHasBeenOpen &&
|
||||
(!self.sessionCloseTime ||
|
||||
[self.sessionCloseTime timeIntervalSinceNow]*-1 <= self.backgroundSessionTimeout)) {
|
||||
// Note that we allow the session to be resumed even if the database size exceeds the
|
||||
// maximum. This is because we don't want to create incomplete sessions. If the DB was large
|
||||
// enough that the previous session could not be opened, there will be nothing to resume. But
|
||||
// if this session caused it to go over it is better to let it complete and stop the next one
|
||||
// from being created.
|
||||
[self logMessage:@"Resume called - Resuming previous session."];
|
||||
[self reopenPreviousSession];
|
||||
} else {
|
||||
// otherwise open new session and upload
|
||||
[self logMessage:@"Resume called - Opening a new session."];
|
||||
[self ll_open];
|
||||
}
|
||||
self.sessionCloseTime = nil;
|
||||
});
|
||||
dispatch_sync(_queue,^{
|
||||
@try {
|
||||
// Do nothing if session is already open
|
||||
if(self.isSessionOpen == YES) {
|
||||
resumed = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
if([self ll_isOptedIn] == false) {
|
||||
[self logMessage:@"Can't resume session because user is opted out."];
|
||||
resumed = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
// conditions for resuming previous session
|
||||
if(self.sessionHasBeenOpen &&
|
||||
(!self.sessionCloseTime ||
|
||||
[self.sessionCloseTime timeIntervalSinceNow]*-1 <= self.backgroundSessionTimeout)) {
|
||||
// Note that we allow the session to be resumed even if the database size exceeds the
|
||||
// maximum. This is because we don't want to create incomplete sessions. If the DB was large
|
||||
// enough that the previous session could not be opened, there will be nothing to resume. But
|
||||
// if this session caused it to go over it is better to let it complete and stop the next one
|
||||
// from being created.
|
||||
[self logMessage:@"Resume called - Resuming previous session."];
|
||||
[self reopenPreviousSession];
|
||||
|
||||
resumed = YES;
|
||||
} else {
|
||||
// otherwise open new session and upload
|
||||
[self logMessage:@"Resume called - Opening a new session."];
|
||||
[self ll_open];
|
||||
|
||||
resumed = NO;
|
||||
}
|
||||
self.sessionCloseTime = nil;
|
||||
} @catch (NSException *e) {}
|
||||
});
|
||||
return resumed;
|
||||
}
|
||||
|
||||
- (void)close {
|
||||
@ -237,13 +267,13 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
[closeEventString appendString:[self formatAttributeWithName:PARAM_DATA_TYPE value:@"c" first:YES]];
|
||||
[closeEventString appendString:[self formatAttributeWithName:PARAM_SESSION_UUID value:self.sessionUUID]];
|
||||
[closeEventString appendString:[self formatAttributeWithName:PARAM_UUID value:[self randomUUID] ]];
|
||||
[closeEventString appendFormat:@",\"%@\":%u", PARAM_SESSION_START, (long)self.lastSessionStartTimestamp];
|
||||
[closeEventString appendFormat:@",\"%@\":%u", PARAM_SESSION_ACTIVE, (long)self.sessionActiveDuration];
|
||||
[closeEventString appendFormat:@",\"%@\":%u", PARAM_CLIENT_TIME, (long)[self currentTimestamp]];
|
||||
[closeEventString appendFormat:@",\"%@\":%ld", PARAM_SESSION_START, (long)self.lastSessionStartTimestamp];
|
||||
[closeEventString appendFormat:@",\"%@\":%ld", PARAM_SESSION_ACTIVE, (long)self.sessionActiveDuration];
|
||||
[closeEventString appendFormat:@",\"%@\":%ld", PARAM_CLIENT_TIME, (long)[self currentTimestamp]];
|
||||
|
||||
// Avoid recording session lengths of users with unreasonable client times (usually caused by developers testing clock change attacks)
|
||||
if(sessionLength > 0 && sessionLength < 400000) {
|
||||
[closeEventString appendFormat:@",\"%@\":%u", PARAM_SESSION_TOTAL, sessionLength];
|
||||
[closeEventString appendFormat:@",\"%@\":%d", PARAM_SESSION_TOTAL, sessionLength];
|
||||
}
|
||||
|
||||
// Open second level - screen flow
|
||||
@ -256,19 +286,16 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
// Append the custom dimensions
|
||||
[closeEventString appendString:[self customDimensions]];
|
||||
|
||||
// Append the location
|
||||
[closeEventString appendString:[self locationDimensions]];
|
||||
|
||||
// Close first level - close blob
|
||||
[closeEventString appendString:@"}\n"];
|
||||
|
||||
BOOL success = [[LocalyticsDatabase sharedLocalyticsDatabase] addCloseEventWithBlobString:[[closeEventString copy] autorelease]];
|
||||
BOOL success = [[LocalyticsDatabase sharedLocalyticsDatabase] queueCloseEventWithBlobString:[[closeEventString copy] autorelease]];
|
||||
|
||||
self.isSessionOpen = NO; // Session is no longer open.
|
||||
|
||||
if (success) {
|
||||
// Record final session flow, opting to remove it from the database if the session happens to resume.
|
||||
// This is safe now that the session has closed because no new events can be added.
|
||||
success = [self saveApplicationFlowAndRemoveOnResume:YES];
|
||||
}
|
||||
|
||||
if (success) {
|
||||
[self logMessage:@"Session succesfully closed."];
|
||||
} else {
|
||||
@ -358,11 +385,14 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
[eventString appendString:[self formatAttributeWithName:PARAM_APP_KEY value:self.applicationKey ]];
|
||||
[eventString appendString:[self formatAttributeWithName:PARAM_SESSION_UUID value:self.sessionUUID ]];
|
||||
[eventString appendString:[self formatAttributeWithName:PARAM_EVENT_NAME value:[self escapeString:event] ]];
|
||||
[eventString appendFormat:@",\"%@\":%u", PARAM_CLIENT_TIME, (long)[self currentTimestamp]];
|
||||
[eventString appendFormat:@",\"%@\":%ld", PARAM_CLIENT_TIME, (long)[self currentTimestamp]];
|
||||
|
||||
// Append the custom dimensions
|
||||
[eventString appendString:[self customDimensions]];
|
||||
|
||||
// Append the location
|
||||
[eventString appendString:[self locationDimensions]];
|
||||
|
||||
// If there are any attributes for this event, add them as a hash
|
||||
int attrIndex = 0;
|
||||
if(attributes != nil)
|
||||
@ -406,6 +436,8 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
// User-originated events should be tracked as application flow.
|
||||
[self addFlowEventWithName:event type:@"e"]; // "e" for Event.
|
||||
|
||||
[self ampTrigger:event];
|
||||
|
||||
[self logMessage:[@"Tagged event: " stringByAppendingString:event]];
|
||||
} else {
|
||||
[self logMessage:@"Failed to tag event."];
|
||||
@ -437,6 +469,11 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setLocation:(CLLocationCoordinate2D)deviceLocation {
|
||||
lastDeviceLocation = deviceLocation;
|
||||
[self logMessage:@"Setting Location"];
|
||||
}
|
||||
|
||||
- (void)setCustomDimension:(int)dimension value:(NSString *)value {
|
||||
dispatch_async(_queue, ^{
|
||||
if(dimension < 0 || dimension > 3) {
|
||||
@ -503,7 +540,7 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
}
|
||||
|
||||
// Begin upload.
|
||||
[[LocalyticsUploader sharedLocalyticsUploader] uploaderWithApplicationKey:self.applicationKey];
|
||||
[[LocalyticsUploader sharedLocalyticsUploader] uploaderWithApplicationKey:self.applicationKey useHTTPS:[self enableHTTPS] installId:[self installationId] resultTarget:self callback:@selector(uploadCallback:)];
|
||||
} else {
|
||||
[db rollbackTransaction:t];
|
||||
[self logMessage:@"Failed to start upload."];
|
||||
@ -515,6 +552,26 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
|
||||
#pragma mark Private Methods
|
||||
|
||||
-(NSString*)libraryVersion {
|
||||
return CLIENT_VERSION;
|
||||
}
|
||||
|
||||
-(void) uploadCallback:(NSDictionary*)info{
|
||||
}
|
||||
|
||||
- (void)dequeueCloseEventBlobString
|
||||
{
|
||||
LocalyticsDatabase *db = [LocalyticsDatabase sharedLocalyticsDatabase];
|
||||
NSString *closeEventString = [db dequeueCloseEventBlobString];
|
||||
if (closeEventString) {
|
||||
BOOL success = [db addCloseEventWithBlobString:closeEventString];
|
||||
if (!success) {
|
||||
// Re-queue the close event.
|
||||
[db queueCloseEventWithBlobString:closeEventString];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)ll_open {
|
||||
// There are a number of conditions in which nothing should be done:
|
||||
if (self.hasInitialized == NO || // the session object has not yet initialized
|
||||
@ -538,6 +595,8 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
return;
|
||||
}
|
||||
|
||||
[self dequeueCloseEventBlobString];
|
||||
|
||||
self.sessionActiveDuration = 0;
|
||||
self.sessionResumeTime = [NSDate date];
|
||||
self.unstagedFlowEvents = [NSMutableString string];
|
||||
@ -548,6 +607,12 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
NSString *t = @"open_session";
|
||||
BOOL success = [db beginTransaction:t];
|
||||
|
||||
// lastSessionStartTimestamp isn't really the last session start time.
|
||||
// It's the sessionResumeTime which is [NSDate date] or now. Therefore,
|
||||
// save the current lastSessionTimestamp value from the database so it
|
||||
// can be used to calculate the elapsed time between session start times.
|
||||
NSTimeInterval previousSessionStartTimeInterval = [db lastSessionStartTimestamp];
|
||||
|
||||
// Save session start time.
|
||||
self.lastSessionStartTimestamp = [self.sessionResumeTime timeIntervalSince1970];
|
||||
if (success) {
|
||||
@ -559,6 +624,7 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
if (success) {
|
||||
success = [db incrementLastSessionNumber:&sessionNumber];
|
||||
}
|
||||
[self setSessionNumber:sessionNumber];
|
||||
|
||||
if (success) {
|
||||
// Prepare session open event.
|
||||
@ -569,10 +635,18 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
[openEventString appendString:@"{"];
|
||||
[openEventString appendString:[self formatAttributeWithName:PARAM_DATA_TYPE value:@"s" first:YES]];
|
||||
[openEventString appendString:[self formatAttributeWithName:PARAM_NEW_SESSION_UUID value:self.sessionUUID]];
|
||||
[openEventString appendFormat:@",\"%@\":%u", PARAM_CLIENT_TIME, (long)self.lastSessionStartTimestamp];
|
||||
[openEventString appendFormat:@",\"%@\":%ld", PARAM_CLIENT_TIME, (long)self.lastSessionStartTimestamp];
|
||||
[openEventString appendFormat:@",\"%@\":%d", PARAM_SESSION_NUMBER, sessionNumber];
|
||||
|
||||
double elapsedTime = 0.0;
|
||||
if (previousSessionStartTimeInterval > 0) {
|
||||
elapsedTime = [self lastSessionStartTimestamp] - previousSessionStartTimeInterval;
|
||||
}
|
||||
NSString *elapsedTimeString = [NSString stringWithFormat:@"%.0f", elapsedTime];
|
||||
[openEventString appendString:[self formatAttributeWithName:PARAM_SESSION_ELAPSE_TIME value:elapsedTimeString]];
|
||||
|
||||
[openEventString appendString:[self customDimensions]];
|
||||
[openEventString appendString:[self locationDimensions]];
|
||||
|
||||
[openEventString appendString:@"}\n"];
|
||||
|
||||
@ -667,12 +741,13 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
NSString *device_language = [english displayNameForKey:NSLocaleIdentifier value:device_locale];
|
||||
NSString *locale_country = [english displayNameForKey:NSLocaleCountryCode value:[locale objectForKey:NSLocaleCountryCode]];
|
||||
NSString *uuid = [self randomUUID];
|
||||
NSString *device_uuid = [self uniqueDeviceIdentifier];
|
||||
NSString *device_uuid = [self uniqueDeviceIdentifier];
|
||||
NSString *device_adid = [self advertisingIdentifier];
|
||||
|
||||
// Open first level - blob information
|
||||
[headerString appendString:@"{"];
|
||||
[headerString appendFormat:@"\"%@\":%d", PARAM_SEQUENCE_NUMBER, nextSequenceNumber];
|
||||
[headerString appendFormat:@",\"%@\":%u", PARAM_PERSISTED_AT, (long)[[LocalyticsDatabase sharedLocalyticsDatabase] createdTimestamp]];
|
||||
[headerString appendFormat:@",\"%@\":%ld", PARAM_PERSISTED_AT, (long)[[LocalyticsDatabase sharedLocalyticsDatabase] createdTimestamp]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_DATA_TYPE value:@"h" ]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_UUID value:uuid ]];
|
||||
|
||||
@ -684,18 +759,22 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_INSTALL_ID value:[self installationId] ]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_APP_KEY value:self.applicationKey ]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_APP_VERSION value:[self appVersion] ]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_LIBRARY_VERSION value:CLIENT_VERSION ]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_LIBRARY_VERSION value:[self libraryVersion] ]];
|
||||
|
||||
// >> Device Information
|
||||
// [headerString appendString:[self formatAttributeWithName:PARAM_DEVICE_UUID value:device_uuid ]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_DEVICE_UUID_HASHED value:[self hashString:device_uuid] ]];
|
||||
if (device_uuid) {
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_DEVICE_UUID_HASHED value:[self hashString:device_uuid] ]];
|
||||
}
|
||||
if (device_adid) {
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_DEVICE_ADID value:device_adid]];
|
||||
}
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_DEVICE_PLATFORM value:[thisDevice model] ]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_DEVICE_OS_VERSION value:[thisDevice systemVersion] ]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_DEVICE_MODEL value:[self deviceModel] ]];
|
||||
|
||||
// MAC Address collection. Uncomment the following line to add Mac address to the mix of collected identifiers
|
||||
// [headerString appendString:[self formatAttributeWithName:PARAM_DEVICE_MAC value:[self hashString:[self macAddress]] ]];
|
||||
[headerString appendString:[NSString stringWithFormat:@",\"%@\":%d", PARAM_DEVICE_MEMORY, (long)[self availableMemory] ]];
|
||||
[headerString appendString:[NSString stringWithFormat:@",\"%@\":%ld", PARAM_DEVICE_MEMORY, (long)[self availableMemory] ]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_LOCALE_LANGUAGE value:device_language]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_LOCALE_COUNTRY value:locale_country]];
|
||||
[headerString appendString:[self formatAttributeWithName:PARAM_DEVICE_COUNTRY value:[locale objectForKey:NSLocaleCountryCode]]];
|
||||
@ -723,9 +802,9 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
NSMutableString *optEventString = [NSMutableString string];
|
||||
[optEventString appendString:@"{"];
|
||||
[optEventString appendString:[self formatAttributeWithName:PARAM_DATA_TYPE value:@"o" first:YES]];
|
||||
[optEventString appendString:[self formatAttributeWithName:PARAM_APP_KEY value:self.applicationKey first:NO]];
|
||||
[optEventString appendString:[NSString stringWithFormat:@",\"%@\":%@", PARAM_OPT_VALUE, (optState ? @"true" : @"false") ]];
|
||||
[optEventString appendFormat:@",\"%@\":%u", PARAM_CLIENT_TIME, (long)[self currentTimestamp]];
|
||||
[optEventString appendString:[self formatAttributeWithName:PARAM_UUID value:[self randomUUID] first:NO ]];
|
||||
[optEventString appendString:[NSString stringWithFormat:@",\"%@\":%@", PARAM_OPT_VALUE, (optState ? @"false" : @"true") ]]; //this actually transmits the opposite of the opt state. The JSON contains whether the user is opted out, not whether the user is opted in.
|
||||
[optEventString appendFormat:@",\"%@\":%ld", PARAM_CLIENT_TIME, (long)[self currentTimestamp]];
|
||||
[optEventString appendString:@"}\n"];
|
||||
|
||||
BOOL success = [[LocalyticsDatabase sharedLocalyticsDatabase] addEventWithBlobString:[[optEventString copy] autorelease]];
|
||||
@ -752,7 +831,7 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
[flowEventString appendString:@"{"];
|
||||
[flowEventString appendString:[self formatAttributeWithName:PARAM_DATA_TYPE value:@"f" first:YES]];
|
||||
[flowEventString appendString:[self formatAttributeWithName:PARAM_UUID value:[self randomUUID] ]];
|
||||
[flowEventString appendFormat:@",\"%@\":%u", PARAM_SESSION_START, (long)self.lastSessionStartTimestamp];
|
||||
[flowEventString appendFormat:@",\"%@\":%ld", PARAM_SESSION_START, (long)self.lastSessionStartTimestamp];
|
||||
|
||||
// Open second level - new flow events
|
||||
[flowEventString appendFormat:@",\"%@\":[", PARAM_NEW_FLOW_EVENTS];
|
||||
@ -814,8 +893,14 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
{
|
||||
NSString *output = [input stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
|
||||
output = [output stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
|
||||
output = [output stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
|
||||
return output;
|
||||
output = [output stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
|
||||
output = [output stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
|
||||
output = [output stringByReplacingOccurrencesOfString:@"\t" withString:@"\\t"];
|
||||
output = [output stringByReplacingOccurrencesOfString:@"\b" withString:@"\\b"];
|
||||
output = [output stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
|
||||
output = [output stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
|
||||
output = [output stringByReplacingOccurrencesOfString:@"\v" withString:@"\\v"];
|
||||
return output;
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(NSNotification *)notification
|
||||
@ -877,6 +962,24 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
return [[dimensions copy] autorelease];
|
||||
}
|
||||
|
||||
/*!
|
||||
@method locationDimensions
|
||||
@abstract Returns the json blob containing the current location if available or nil if no location is available.
|
||||
*/
|
||||
- (NSString *)locationDimensions
|
||||
{
|
||||
if(lastDeviceLocation.latitude == 0 || lastDeviceLocation.longitude == 0) {
|
||||
return @"";
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@",\"lat\":%f,\"lng\":%f",
|
||||
lastDeviceLocation.latitude,
|
||||
lastDeviceLocation.longitude];
|
||||
|
||||
|
||||
return [NSString stringWithFormat:@"%lf", lastDeviceLocation.latitude];
|
||||
}
|
||||
|
||||
/*!
|
||||
@method macAddress
|
||||
@abstract Returns the macAddress of this device.
|
||||
@ -983,6 +1086,7 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
return installId;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
@method uniqueDeviceIdentifier
|
||||
@abstract A unique device identifier is a hash value composed from various hardware identifiers such
|
||||
@ -992,18 +1096,38 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
*/
|
||||
- (NSString *)uniqueDeviceIdentifier {
|
||||
|
||||
// Supress the warning for uniqueIdentifier being deprecated.
|
||||
// We collect it as long as it is available along with a randomly generated ID.
|
||||
// This way, when this becomes unavailable we can map existing users so the
|
||||
// new vs returning counts do not break. This will be removed before it causes grief.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
NSString *systemId = [[UIDevice currentDevice] uniqueIdentifier];
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
NSString *systemId = nil;
|
||||
// We collect it as long as it is available along with a randomly generated ID.
|
||||
// This way, when this becomes unavailable we can map existing users so the
|
||||
// new vs returning counts do not break.
|
||||
//only do this if the OS is less than 6.0
|
||||
if (([[[UIDevice currentDevice] systemVersion] floatValue] < 6.0f)) {
|
||||
SEL udidSelector = NSSelectorFromString(@"uniqueIdentifier");
|
||||
if ([[UIDevice currentDevice] respondsToSelector:udidSelector]) {
|
||||
systemId = [[UIDevice currentDevice] performSelector:udidSelector];
|
||||
}
|
||||
}
|
||||
return systemId;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
@method advertisingIdentifier
|
||||
@abstract An alphanumeric string unique to each device, used for advertising only.
|
||||
From UIDevice documentation.
|
||||
|
||||
@return An identifier unique to this device.
|
||||
*/
|
||||
- (NSString *)advertisingIdentifier {
|
||||
NSString *adId = nil;
|
||||
SEL adidSelector = NSSelectorFromString(@"identifierForAdvertising");
|
||||
if ([[UIDevice currentDevice] respondsToSelector:adidSelector]) {
|
||||
adId = [[[UIDevice currentDevice] performSelector:adidSelector] performSelector:NSSelectorFromString(@"UUIDString")];
|
||||
}
|
||||
return adId;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
@method appVersion
|
||||
@abstract Gets the pretty string for this application's version.
|
||||
@ -1145,4 +1269,10 @@ static LocalyticsSession *_sharedLocalyticsSession = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
#pragma mark - AMP stub
|
||||
- (void)ampTrigger:(NSString *)event {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
@ -1,5 +1,5 @@
|
||||
// LocalyticsUploader.h
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
// Copyright (C) 2012 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
|
||||
@ -9,6 +9,8 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
extern NSString * const kLocalyticsKeyResponseBody;
|
||||
|
||||
/*!
|
||||
@class LocalyticsUploader
|
||||
@discussion Singleton class to handle data uploads
|
||||
@ -35,8 +37,28 @@
|
||||
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.
|
||||
|
||||
This version of the method now just calls the second version of it with a nil target and NULL callback method.
|
||||
@param localyticsApplicationKey the Localytics application ID
|
||||
@param useHTTPS Flag determining whether HTTP or HTTPS is used for the post URL.
|
||||
@param installId Install id passed to the server in the x-install-id header field.
|
||||
*/
|
||||
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey;
|
||||
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey useHTTPS:(BOOL)useHTTPS installId:(NSString *)installId;
|
||||
|
||||
/*!
|
||||
@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
|
||||
@param useHTTPS Flag determining whether HTTP or HTTPS is used for the post URL.
|
||||
@param installId Install id passed to the server in the x-install-id header field.
|
||||
@param resultTarget Result target is the target for the callback method that knows how to handle response data
|
||||
@param callback Callback is the method of the target class that is to be called with the data begin returned by an upload
|
||||
*/
|
||||
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey useHTTPS:(BOOL)useHTTPS installId:(NSString *)installId resultTarget:(id)target callback:(SEL)callbackMethod;
|
||||
|
||||
@end
|
@ -1,5 +1,5 @@
|
||||
// LocalyticsUploader.m
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
// Copyright (C) 2012 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
|
||||
@ -10,16 +10,25 @@
|
||||
#import "LocalyticsUploader.h"
|
||||
#import "LocalyticsSession.h"
|
||||
#import "LocalyticsDatabase.h"
|
||||
#import "WebserviceConstants.h"
|
||||
#import <zlib.h>
|
||||
|
||||
#define LOCALYTICS_URL @"http://analytics.localytics.com/api/v2/applications/%@/uploads"
|
||||
#ifndef LOCALYTICS_URL
|
||||
#define LOCALYTICS_URL @"http://analytics.localytics.com/api/v2/applications/%@/uploads"
|
||||
#endif
|
||||
|
||||
#ifndef LOCALYTICS_URL_SECURED
|
||||
#define LOCALYTICS_URL_SECURED @"https://analytics.localytics.com/api/v2/applications/%@/uploads"
|
||||
#endif
|
||||
static LocalyticsUploader *_sharedUploader = nil;
|
||||
|
||||
NSString * const kLocalyticsKeyResponseBody = @"localytics.key.responseBody";
|
||||
|
||||
@interface LocalyticsUploader ()
|
||||
- (void)finishUpload;
|
||||
- (NSData *)gzipDeflatedDataWithData:(NSData *)data;
|
||||
- (void)logMessage:(NSString *)message;
|
||||
- (NSString *)uploadTimeStamp;
|
||||
|
||||
@property (readwrite) BOOL isUploading;
|
||||
|
||||
@ -40,7 +49,13 @@ static LocalyticsUploader *_sharedUploader = nil;
|
||||
|
||||
#pragma mark - Class Methods
|
||||
|
||||
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey {
|
||||
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey useHTTPS:(BOOL)useHTTPS installId:(NSString *)installId
|
||||
{
|
||||
[self uploaderWithApplicationKey:localyticsApplicationKey useHTTPS:useHTTPS installId:installId resultTarget:nil callback:NULL];
|
||||
}
|
||||
|
||||
- (void)uploaderWithApplicationKey:(NSString *)localyticsApplicationKey useHTTPS:(BOOL)useHTTPS installId:(NSString *)installId resultTarget:(id)target callback:(SEL)callbackMethod;
|
||||
{
|
||||
|
||||
// Do nothing if already uploading.
|
||||
if (self.isUploading == true)
|
||||
@ -77,17 +92,26 @@ static LocalyticsUploader *_sharedUploader = nil;
|
||||
NSData *requestData = [blobString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *myString = [[[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding] autorelease];
|
||||
[self logMessage:[NSString stringWithFormat:@"Uploading data (length: %u)", [myString length]]];
|
||||
[self logMessage:myString];
|
||||
|
||||
// Step 2
|
||||
NSData *deflatedRequestData = [[self gzipDeflatedDataWithData:requestData] retain];
|
||||
|
||||
[pool drain];
|
||||
|
||||
NSString *apiUrlString = [NSString stringWithFormat:LOCALYTICS_URL, [localyticsApplicationKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSString *urlStringFormat;
|
||||
if (useHTTPS) {
|
||||
urlStringFormat = LOCALYTICS_URL_SECURED;
|
||||
} else {
|
||||
urlStringFormat = LOCALYTICS_URL;
|
||||
}
|
||||
NSString *apiUrlString = [NSString stringWithFormat:urlStringFormat, [localyticsApplicationKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSMutableURLRequest *submitRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:apiUrlString]
|
||||
cachePolicy:NSURLRequestReloadIgnoringCacheData
|
||||
timeoutInterval:60.0];
|
||||
[submitRequest setHTTPMethod:@"POST"];
|
||||
[submitRequest setValue:[self uploadTimeStamp] forHTTPHeaderField:HEADER_CLIENT_TIME];
|
||||
[submitRequest setValue:installId forHTTPHeaderField:HEADER_INSTALL_ID];
|
||||
[submitRequest setValue:@"application/x-gzip" forHTTPHeaderField:@"Content-Type"];
|
||||
[submitRequest setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
|
||||
[submitRequest setValue:[NSString stringWithFormat:@"%d", [deflatedRequestData length]] forHTTPHeaderField:@"Content-Length"];
|
||||
@ -100,7 +124,7 @@ static LocalyticsUploader *_sharedUploader = nil;
|
||||
@try {
|
||||
NSURLResponse *response = nil;
|
||||
NSError *responseError = nil;
|
||||
[NSURLConnection sendSynchronousRequest:submitRequest returningResponse:&response error:&responseError];
|
||||
NSData *responseData = [NSURLConnection sendSynchronousRequest:submitRequest returningResponse:&response error:&responseError];
|
||||
NSInteger responseStatusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
|
||||
if (responseError) {
|
||||
@ -123,6 +147,18 @@ static LocalyticsUploader *_sharedUploader = nil;
|
||||
[[LocalyticsDatabase sharedLocalyticsDatabase] deleteUploadedData];
|
||||
}
|
||||
}
|
||||
|
||||
if ([responseData length] > 0) {
|
||||
if (DO_LOCALYTICS_LOGGING) {
|
||||
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
|
||||
[self logMessage:[NSString stringWithFormat:@"Response body: %@", responseString]];
|
||||
[responseString release];
|
||||
}
|
||||
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:responseData forKey:kLocalyticsKeyResponseBody];
|
||||
if (target) {
|
||||
[target performSelector:callbackMethod withObject:userInfo];
|
||||
}
|
||||
}
|
||||
}
|
||||
@catch (NSException * e) {}
|
||||
|
||||
@ -195,6 +231,15 @@ static LocalyticsUploader *_sharedUploader = nil;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@method uploadTimeStamp
|
||||
@abstract Gets the current time, along with local timezone, formatted as a DateTime for the webservice.
|
||||
@return a DateTime of the current local time and timezone.
|
||||
*/
|
||||
- (NSString *)uploadTimeStamp {
|
||||
return [ NSString stringWithFormat:@"%ld", (long)[[NSDate date] timeIntervalSince1970] ];
|
||||
}
|
||||
|
||||
#pragma mark - System Functions
|
||||
+ (id)allocWithZone:(NSZone *)zone {
|
||||
@synchronized(self) {
|
||||
|
@ -1,48 +0,0 @@
|
||||
// UploaderThread.h
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// This code is provided under the Localytics Modified BSD License.
|
||||
// A copy of this license has been distributed in a file called LICENSE
|
||||
// with this source code.
|
||||
//
|
||||
// Please visit www.localytics.com for more information.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/*!
|
||||
@class UploaderThread
|
||||
@discussion Singleton class to handle data uploads
|
||||
*/
|
||||
|
||||
@interface UploaderThread : NSObject {
|
||||
NSURLConnection *_uploadConnection; // The connection which uploads the bits
|
||||
NSInteger _responseStatusCode; // The HTTP response status code for the current connection
|
||||
|
||||
BOOL _isUploading; // A flag to gaurantee only one uploader instance can happen at once
|
||||
}
|
||||
|
||||
@property (nonatomic, retain) NSURLConnection *uploadConnection;
|
||||
|
||||
@property BOOL isUploading;
|
||||
|
||||
/*!
|
||||
@method sharedUploaderThread
|
||||
@abstract Establishes this as a Singleton Class allowing for data persistence.
|
||||
The class is accessed within the code using the following syntax:
|
||||
[[UploaderThread sharedUploaderThread] functionHere]
|
||||
*/
|
||||
+ (UploaderThread *)sharedUploaderThread;
|
||||
|
||||
/*!
|
||||
@method UploaderThread
|
||||
@abstract Creates a thread which uploads all queued header and event data.
|
||||
All files starting with sessionFilePrefix are renamed,
|
||||
uploaded and deleted on upload. This way the sessions can continue
|
||||
writing data regardless of whether or not the upload succeeds. Files
|
||||
which have been renamed still count towards the total number of Localytics
|
||||
files which can be stored on the disk.
|
||||
@param localyticsApplicationKey the Localytics application ID
|
||||
*/
|
||||
- (void)uploaderThreadwithApplicationKey:(NSString *)localyticsApplicationKey;
|
||||
|
||||
@end
|
@ -1,260 +0,0 @@
|
||||
// UploaderThread.m
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
//
|
||||
// This code is provided under the Localytics Modified BSD License.
|
||||
// A copy of this license has been distributed in a file called LICENSE
|
||||
// with this source code.
|
||||
//
|
||||
// Please visit www.localytics.com for more information.
|
||||
|
||||
#import "UploaderThread.h"
|
||||
#import "LocalyticsSession.h"
|
||||
#import "LocalyticsDatabase.h"
|
||||
#import <zlib.h>
|
||||
|
||||
#define LOCALYTICS_URL @"http://analytics.localytics.com/api/v2/applications/%@/uploads" // url to send the
|
||||
|
||||
static UploaderThread *_sharedUploaderThread = nil;
|
||||
|
||||
@interface UploaderThread ()
|
||||
- (void)complete;
|
||||
- (NSData *)gzipDeflatedDataWithData:(NSData *)data;
|
||||
- (void)logMessage:(NSString *)message;
|
||||
@end
|
||||
|
||||
@implementation UploaderThread
|
||||
|
||||
@synthesize uploadConnection = _uploadConnection;
|
||||
@synthesize isUploading = _isUploading;
|
||||
|
||||
#pragma mark Singleton Class
|
||||
+ (UploaderThread *)sharedUploaderThread {
|
||||
@synchronized(self) {
|
||||
if (_sharedUploaderThread == nil)
|
||||
{
|
||||
_sharedUploaderThread = [[self alloc] init];
|
||||
}
|
||||
}
|
||||
return _sharedUploaderThread;
|
||||
}
|
||||
|
||||
#pragma mark Class Methods
|
||||
- (void)uploaderThreadwithApplicationKey:(NSString *)localyticsApplicationKey {
|
||||
|
||||
// Do nothing if already uploading.
|
||||
if (self.uploadConnection != nil || self.isUploading == true)
|
||||
{
|
||||
[self logMessage:@"Upload already in progress. Aborting."];
|
||||
return;
|
||||
}
|
||||
|
||||
[self logMessage:@"Beginning upload process"];
|
||||
self.isUploading = true;
|
||||
|
||||
// Prepare the data for upload. The upload could take a long time, so some effort has to be made to be sure that events
|
||||
// which get written while the upload is taking place don't get lost or duplicated. To achieve this, the logic is:
|
||||
// 1) Append every header row blob string and and those of its associated events to the upload string.
|
||||
// 2) Deflate and upload the data.
|
||||
// 3) On success, delete all blob headers and staged events. Events added while an upload is in process are not
|
||||
// deleted because they are not associated a header (and cannot be until the upload completes).
|
||||
|
||||
// Step 1
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
LocalyticsDatabase *db = [LocalyticsDatabase sharedLocalyticsDatabase];
|
||||
NSString *blobString = [db uploadBlobString];
|
||||
|
||||
if ([blobString length] == 0) {
|
||||
// There is nothing outstanding to upload.
|
||||
[self logMessage:@"Abandoning upload. There are no new events."];
|
||||
|
||||
[pool drain];
|
||||
[self complete];
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *requestData = [blobString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *myString = [[[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding] autorelease];
|
||||
[self logMessage:@"Upload data:"];
|
||||
[self logMessage:myString];
|
||||
|
||||
// Step 2
|
||||
NSData *deflatedRequestData = [[self gzipDeflatedDataWithData:requestData] retain];
|
||||
|
||||
[pool drain];
|
||||
|
||||
NSString *apiUrlString = [NSString stringWithFormat:LOCALYTICS_URL, [localyticsApplicationKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSMutableURLRequest *submitRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:apiUrlString]
|
||||
cachePolicy:NSURLRequestReloadIgnoringCacheData
|
||||
timeoutInterval:60.0];
|
||||
[submitRequest setHTTPMethod:@"POST"];
|
||||
[submitRequest setValue:@"application/x-gzip" forHTTPHeaderField:@"Content-Type"];
|
||||
[submitRequest setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
|
||||
[submitRequest setValue:[NSString stringWithFormat:@"%d", [deflatedRequestData length]] forHTTPHeaderField:@"Content-Length"];
|
||||
[submitRequest setHTTPBody:deflatedRequestData];
|
||||
[deflatedRequestData release];
|
||||
|
||||
// The NSURLConnection Object automatically spawns its own thread as a default behavior.
|
||||
@try
|
||||
{
|
||||
[self logMessage:@"Spawning new thread for upload"];
|
||||
self.uploadConnection = [NSURLConnection connectionWithRequest:submitRequest delegate:self];
|
||||
|
||||
// Step 3 is handled by connectionDidFinishLoading.
|
||||
}
|
||||
@catch (NSException * e)
|
||||
{
|
||||
[self complete];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark **** NSURLConnection FUNCTIONS ****
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
|
||||
// Used to gather response data from server - Not utilized in this version
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
|
||||
// Could receive multiple response callbacks, likely due to redirection.
|
||||
// Record status and act only when connection completes load.
|
||||
_responseStatusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
}
|
||||
|
||||
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
|
||||
// If the connection finished loading, the files should be deleted. While response status codes in the 5xx range
|
||||
// leave upload rows intact, the default case is to delete.
|
||||
if (_responseStatusCode >= 500 && _responseStatusCode < 600)
|
||||
{
|
||||
[self logMessage:[NSString stringWithFormat:@"Upload failed with response status code %d", _responseStatusCode]];
|
||||
} else
|
||||
{
|
||||
// The connection finished loading and uploaded data should be deleted. Because only one instance of the
|
||||
// uploader can be running at a time it should not be possible for new upload rows to appear so there is no
|
||||
// fear of deleting data which has not yet been uploaded.
|
||||
[self logMessage:[NSString stringWithFormat:@"Upload completed successfully. Response code %d", _responseStatusCode]];
|
||||
[[LocalyticsDatabase sharedLocalyticsDatabase] deleteUploadData];
|
||||
}
|
||||
|
||||
// Close upload session
|
||||
[self complete];
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
|
||||
// On error, simply print the error and close the uploader. We have to assume the data was not transmited
|
||||
// so it is not deleted. In the event that we accidently store data which was succesfully uploaded, the
|
||||
// duplicate data will be ignored by the server when it is next uploaded.
|
||||
[self logMessage:[NSString stringWithFormat:
|
||||
@"Error Uploading. Code: %d, Description: %s",
|
||||
[error code],
|
||||
[error localizedDescription]]];
|
||||
|
||||
[self complete];
|
||||
}
|
||||
|
||||
/*!
|
||||
@method complete
|
||||
@abstract closes the upload connection and reports back to the session that the upload is complete
|
||||
*/
|
||||
- (void)complete {
|
||||
_responseStatusCode = 0;
|
||||
self.uploadConnection = nil;
|
||||
self.isUploading = false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@method gzipDeflatedDataWithData
|
||||
@abstract Deflates the provided data using gzip at the default compression level (6). Complete NSData gzip category available on CocoaDev. http://www.cocoadev.com/index.pl?NSDataCategory.
|
||||
@return the deflated data
|
||||
*/
|
||||
- (NSData *)gzipDeflatedDataWithData:(NSData *)data
|
||||
{
|
||||
if ([data length] == 0) return data;
|
||||
|
||||
z_stream strm;
|
||||
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.total_out = 0;
|
||||
strm.next_in=(Bytef *)[data bytes];
|
||||
strm.avail_in = [data length];
|
||||
|
||||
// Compresssion Levels:
|
||||
// Z_NO_COMPRESSION
|
||||
// Z_BEST_SPEED
|
||||
// Z_BEST_COMPRESSION
|
||||
// Z_DEFAULT_COMPRESSION
|
||||
|
||||
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
|
||||
|
||||
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
|
||||
|
||||
do {
|
||||
|
||||
if (strm.total_out >= [compressed length])
|
||||
[compressed increaseLengthBy: 16384];
|
||||
|
||||
strm.next_out = [compressed mutableBytes] + strm.total_out;
|
||||
strm.avail_out = [compressed length] - strm.total_out;
|
||||
|
||||
deflate(&strm, Z_FINISH);
|
||||
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
deflateEnd(&strm);
|
||||
|
||||
[compressed setLength: strm.total_out];
|
||||
return [NSData dataWithData:compressed];
|
||||
}
|
||||
|
||||
/*!
|
||||
@method logMessage
|
||||
@abstract Logs a message with (localytics uploader) prepended to it
|
||||
@param message The message to log
|
||||
*/
|
||||
- (void) logMessage:(NSString *)message {
|
||||
if(DO_LOCALYTICS_LOGGING) {
|
||||
NSLog(@"(localytics uploader) %s\n", [message UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark System Functions
|
||||
+ (id)allocWithZone:(NSZone *)zone {
|
||||
@synchronized(self) {
|
||||
if (_sharedUploaderThread == nil) {
|
||||
_sharedUploaderThread = [super allocWithZone:zone];
|
||||
return _sharedUploaderThread;
|
||||
}
|
||||
}
|
||||
// returns nil on subsequent allocations
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)retain {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (unsigned)retainCount {
|
||||
// maximum value of an unsigned int - prevents additional retains for the class
|
||||
return UINT_MAX;
|
||||
}
|
||||
|
||||
- (oneway void)release {
|
||||
// ignore release commands
|
||||
}
|
||||
|
||||
- (id)autorelease {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_uploadConnection release];
|
||||
[_sharedUploaderThread release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
@ -1,5 +1,5 @@
|
||||
// WebserviceConstants.h
|
||||
// Copyright (C) 2009 Char Software Inc., DBA Localytics
|
||||
// Copyright (C) 2012 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
|
||||
@ -11,6 +11,12 @@
|
||||
// To save disk space and network bandwidth all the keywords have been
|
||||
// abbreviated and are exploded by the server.
|
||||
|
||||
/*****************
|
||||
* Upload Header *
|
||||
*****************/
|
||||
#define HEADER_CLIENT_TIME @"x-upload-time"
|
||||
#define HEADER_INSTALL_ID @"x-install-id"
|
||||
|
||||
/*********************
|
||||
* Shared Attributes *
|
||||
*********************/
|
||||
@ -22,6 +28,7 @@
|
||||
#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)
|
||||
#define PARAM_SESSION_ELAPSE_TIME @"sl" // Number of seconds since the previous session start
|
||||
|
||||
/***************
|
||||
* Blob Header *
|
||||
@ -42,9 +49,8 @@
|
||||
|
||||
// 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_DEVICE_ADID @"adid" // Advertising Identifier
|
||||
#define PARAM_INSTALL_ID @"iu" // Install ID
|
||||
#define PARAM_JAILBROKEN @"j" // Jailbroken (boolean)
|
||||
#define PARAM_LIBRARY_VERSION @"lv" // Client Version
|
||||
@ -52,14 +58,11 @@
|
||||
#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_OPT_VALUE @"out" // Opt Out (boolean)
|
||||
#define PARAM_DEVICE_MEMORY @"dmem" // Device Memory
|
||||
|
||||
/*****************
|
||||
|
@ -46,11 +46,7 @@
|
||||
}];
|
||||
}
|
||||
|
||||
if (![managedObjectContext.persistentStoreCoordinator.persistentStores count])
|
||||
[managedObjectContext performBlockAndWait:^{
|
||||
managedObjectContext.persistentStoreCoordinator = [self storeManager].persistentStoreCoordinator;
|
||||
}];
|
||||
|
||||
[[self storeManager] persistentStoreCoordinator];
|
||||
if (![self storeManager].isReady)
|
||||
return nil;
|
||||
|
||||
|
@ -116,6 +116,7 @@
|
||||
NSString *localyticsKey = [self localyticsKey];
|
||||
if ([localyticsKey length]) {
|
||||
inf(@"Initializing Localytics");
|
||||
[LocalyticsSession sharedLocalyticsSession].enableHTTPS = YES;
|
||||
[[LocalyticsSession sharedLocalyticsSession] startSession:localyticsKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
if (message.level >= PearlLogLevelWarn)
|
||||
@ -321,23 +322,11 @@
|
||||
|
||||
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
|
||||
|
||||
wrn(@"Received memory warning.");
|
||||
inf(@"Received memory warning.");
|
||||
|
||||
[super applicationDidReceiveMemoryWarning:application];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
|
||||
inf(@"Re-activated");
|
||||
[[MPAppDelegate get] checkConfig];
|
||||
|
||||
if (FBSession.activeSession.state == FBSessionStateCreatedOpening)
|
||||
// An old Facebook Login session that wasn't finished. Clean it up.
|
||||
[FBSession.activeSession close];
|
||||
|
||||
[super applicationDidBecomeActive:application];
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] close];
|
||||
@ -369,10 +358,31 @@
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
|
||||
inf(@"Will deactivate");
|
||||
|
||||
[self saveContext];
|
||||
|
||||
if (![[MPiOSConfig get].rememberLogin boolValue])
|
||||
[self signOutAnimated:NO];
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] close];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
|
||||
[super applicationWillResignActive:application];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
|
||||
inf(@"Re-activated");
|
||||
[[MPAppDelegate get] checkConfig];
|
||||
|
||||
if (FBSession.activeSession.state == FBSessionStateCreatedOpening)
|
||||
// An old Facebook Login session that wasn't finished. Clean it up.
|
||||
[FBSession.activeSession close];
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] resume];
|
||||
[[LocalyticsSession sharedLocalyticsSession] upload];
|
||||
|
||||
[super applicationDidBecomeActive:application];
|
||||
}
|
||||
|
||||
#pragma mark - Behavior
|
||||
|
@ -77,6 +77,13 @@
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Apps"];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
|
||||
|
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import "MPGuideViewController.h"
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
|
||||
@implementation MPGuideViewController
|
||||
@ -35,6 +36,8 @@
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Guide"];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
|
@ -149,7 +149,7 @@
|
||||
inf(@"Main will appear");
|
||||
|
||||
// Sometimes, the search bar gets stuck in some sort of first-responder mode that it can't get out of...
|
||||
[self.searchDisplayController.searchBar resignFirstResponder];
|
||||
[[self.view.window findFirstResponderInHierarchy] resignFirstResponder];
|
||||
|
||||
// Needed for when we appear after a modal VC dismisses:
|
||||
// We can't present until the other modal VC has been fully dismissed and presenting in viewDidAppear will fail.
|
||||
@ -204,6 +204,8 @@
|
||||
|
||||
}];
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Main"];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
@ -676,7 +678,7 @@
|
||||
|
||||
- (IBAction)action:(id)sender {
|
||||
|
||||
[PearlSheet showSheetWithTitle:nil message:nil viewStyle:UIActionSheetStyleAutomatic
|
||||
[PearlSheet showSheetWithTitle:nil viewStyle:UIActionSheetStyleAutomatic
|
||||
initSheet:nil
|
||||
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
|
@ -11,6 +11,7 @@
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
@interface MPPreferencesViewController ()
|
||||
|
||||
@ -80,6 +81,13 @@
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Preferences"];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
|
||||
inf(@"Preferences will disappear");
|
||||
@ -152,6 +160,7 @@
|
||||
vc.showDoneButton = NO;
|
||||
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Settings"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import "MPTypeViewController.h"
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
|
||||
@interface MPTypeViewController ()
|
||||
@ -44,6 +45,8 @@
|
||||
}
|
||||
}];
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Type Selection"];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#import "MPAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "LocalyticsSession.h"
|
||||
|
||||
@interface MPUnlockViewController ()
|
||||
|
||||
@ -193,6 +194,8 @@
|
||||
self.uiContainer.alpha = 1;
|
||||
}];
|
||||
|
||||
[[LocalyticsSession sharedLocalyticsSession] tagScreen:@"Unlock"];
|
||||
|
||||
[super viewDidAppear:animated];
|
||||
}
|
||||
|
||||
@ -749,7 +752,7 @@
|
||||
return;
|
||||
|
||||
[PearlSheet showSheetWithTitle:targetedUser.name
|
||||
message:nil viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
@ -823,7 +826,7 @@
|
||||
|
||||
- (IBAction)add:(UIButton *)sender {
|
||||
|
||||
[PearlSheet showSheetWithTitle:@"Follow Master Password" message:nil viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
[PearlSheet showSheetWithTitle:@"Follow Master Password" viewStyle:UIActionSheetStyleBlackTranslucent
|
||||
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
|
||||
if (buttonIndex == [sheet cancelButtonIndex])
|
||||
return;
|
||||
|
Loading…
Reference in New Issue
Block a user