diff --git a/PassiveDataKit.xcodeproj/project.pbxproj b/PassiveDataKit.xcodeproj/project.pbxproj index 704096c..925c8e9 100644 --- a/PassiveDataKit.xcodeproj/project.pbxproj +++ b/PassiveDataKit.xcodeproj/project.pbxproj @@ -32,7 +32,7 @@ 389DE4BC1EF726BB009C8B27 /* Mixpanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 389DE4B31EF726B2009C8B27 /* Mixpanel.framework */; }; 389DE4D21EF7492C009C8B27 /* PDKHttpTransmitter.h in Headers */ = {isa = PBXBuildFile; fileRef = 389DE4D01EF7492C009C8B27 /* PDKHttpTransmitter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 389DE4D31EF7492C009C8B27 /* PDKHttpTransmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 389DE4D11EF7492C009C8B27 /* PDKHttpTransmitter.m */; }; - 389DE4D61EF74DDA009C8B27 /* NSString+RAInflections.h in Headers */ = {isa = PBXBuildFile; fileRef = 389DE4D41EF74DDA009C8B27 /* NSString+RAInflections.h */; }; + 389DE4D61EF74DDA009C8B27 /* NSString+RAInflections.h in Headers */ = {isa = PBXBuildFile; fileRef = 389DE4D41EF74DDA009C8B27 /* NSString+RAInflections.h */; settings = {ATTRIBUTES = (Public, ); }; }; 389DE4D71EF74DDA009C8B27 /* NSString+RAInflections.m in Sources */ = {isa = PBXBuildFile; fileRef = 389DE4D51EF74DDA009C8B27 /* NSString+RAInflections.m */; }; 389DE4DE1EF75ADB009C8B27 /* PDKBaseGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 389DE4DC1EF75ADB009C8B27 /* PDKBaseGenerator.h */; }; 389DE4DF1EF75ADB009C8B27 /* PDKBaseGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 389DE4DD1EF75ADB009C8B27 /* PDKBaseGenerator.m */; }; @@ -53,9 +53,9 @@ 38C666E11D038A8E00E6A6C8 /* PDKAFNetworkReachabilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C666C01D038A8E00E6A6C8 /* PDKAFNetworkReachabilityManager.m */; }; 38C666E21D038A8E00E6A6C8 /* PDKAFSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C666C11D038A8E00E6A6C8 /* PDKAFSecurityPolicy.h */; }; 38C666E31D038A8E00E6A6C8 /* PDKAFSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C666C21D038A8E00E6A6C8 /* PDKAFSecurityPolicy.m */; }; - 38C666E41D038A8E00E6A6C8 /* PDKAFURLRequestSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C666C31D038A8E00E6A6C8 /* PDKAFURLRequestSerialization.h */; }; + 38C666E41D038A8E00E6A6C8 /* PDKAFURLRequestSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C666C31D038A8E00E6A6C8 /* PDKAFURLRequestSerialization.h */; settings = {ATTRIBUTES = (Public, ); }; }; 38C666E51D038A8E00E6A6C8 /* PDKAFURLRequestSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C666C41D038A8E00E6A6C8 /* PDKAFURLRequestSerialization.m */; }; - 38C666E61D038A8E00E6A6C8 /* PDKAFURLResponseSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C666C51D038A8E00E6A6C8 /* PDKAFURLResponseSerialization.h */; }; + 38C666E61D038A8E00E6A6C8 /* PDKAFURLResponseSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C666C51D038A8E00E6A6C8 /* PDKAFURLResponseSerialization.h */; settings = {ATTRIBUTES = (Public, ); }; }; 38C666E71D038A8E00E6A6C8 /* PDKAFURLResponseSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C666C61D038A8E00E6A6C8 /* PDKAFURLResponseSerialization.m */; }; 38C666E81D038A8E00E6A6C8 /* PDKAFURLSessionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C666C71D038A8E00E6A6C8 /* PDKAFURLSessionManager.h */; }; 38C666E91D038A8E00E6A6C8 /* PDKAFURLSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C666C81D038A8E00E6A6C8 /* PDKAFURLSessionManager.m */; }; @@ -434,16 +434,17 @@ 388D5E401EF76CD500C9A2A2 /* PassiveDataKit-Shared.h in Headers */, 3842F7351CDA2021007F843D /* PassiveDataKit.h in Headers */, 389DE4D21EF7492C009C8B27 /* PDKHttpTransmitter.h in Headers */, + 389DE4D61EF74DDA009C8B27 /* NSString+RAInflections.h in Headers */, + 38C666E61D038A8E00E6A6C8 /* PDKAFURLResponseSerialization.h in Headers */, + 38C666E41D038A8E00E6A6C8 /* PDKAFURLRequestSerialization.h in Headers */, 38C666D91D038A8E00E6A6C8 /* PDKAFHTTPSessionManager.h in Headers */, 38A6523C1CDA2D9B00AE8B3B /* PDKLocationGenerator.h in Headers */, 383ECC6A1D1F82F5004E0B2B /* PDKMixpanelEventGenerator.h in Headers */, 383ECC661D1F816D004E0B2B /* PDKEventsGenerator.h in Headers */, 38DC949F1D2711C600552259 /* PDKGooglePlacesGenerator.h in Headers */, - 38C666E41D038A8E00E6A6C8 /* PDKAFURLRequestSerialization.h in Headers */, 38C666E01D038A8E00E6A6C8 /* PDKAFNetworkReachabilityManager.h in Headers */, 38C666E21D038A8E00E6A6C8 /* PDKAFSecurityPolicy.h in Headers */, 38C666EA1D038A8E00E6A6C8 /* UIActivityIndicatorView+PDKAFNetworking.h in Headers */, - 389DE4D61EF74DDA009C8B27 /* NSString+RAInflections.h in Headers */, 38C666F11D038A8E00E6A6C8 /* UIKit+PDKAFNetworking.h in Headers */, 389DE4DE1EF75ADB009C8B27 /* PDKBaseGenerator.h in Headers */, 38C666E81D038A8E00E6A6C8 /* PDKAFURLSessionManager.h in Headers */, @@ -458,7 +459,6 @@ 38C666DB1D038A8E00E6A6C8 /* PDKAFImageDownloader.h in Headers */, 38C666EC1D038A8E00E6A6C8 /* UIButton+PDKAFNetworking.h in Headers */, 38C666F21D038A8E00E6A6C8 /* UIProgressView+PDKAFNetworking.h in Headers */, - 38C666E61D038A8E00E6A6C8 /* PDKAFURLResponseSerialization.h in Headers */, 38C666F41D038A8E00E6A6C8 /* UIRefreshControl+PDKAFNetworking.h in Headers */, 383ECC6E1D1F9915004E0B2B /* PDKLocationAnnotation.h in Headers */, 38C666D71D038A8E00E6A6C8 /* PDKAFAutoPurgingImageCache.h in Headers */, diff --git a/PassiveDataKit/PDKDataReportViewController.m b/PassiveDataKit/PDKDataReportViewController.m index ee09854..64ec914 100644 --- a/PassiveDataKit/PDKDataReportViewController.m +++ b/PassiveDataKit/PDKDataReportViewController.m @@ -70,7 +70,7 @@ - (void) loadVisualization:(NSString *) generator { } else { Class generatorClass = NSClassFromString(generator); - id generator = [generatorClass sharedInstance]; + id generator = [generatorClass sharedInstance]; if (generatorClass != nil) { if ([generatorClass respondsToSelector:@selector(visualizationForSize:)]) { @@ -196,8 +196,13 @@ - (UIViewController *) detailsControllerForGenerator:(NSString *) key { Class generatorClass = NSClassFromString(key); if (generatorClass != nil) { - if ([generatorClass respondsToSelector:@selector(detailsController)]) { - controller = [generatorClass performSelector:@selector(detailsController)]; + SEL details = NSSelectorFromString(@"detailsController"); + + if ([generatorClass respondsToSelector:details]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + controller = [generatorClass performSelector:details]; +#pragma clang diagnostic pop } } return controller; diff --git a/PassiveDataKit/PDKHttpTransmitter.h b/PassiveDataKit/PDKHttpTransmitter.h index f698745..def7077 100644 --- a/PassiveDataKit/PDKHttpTransmitter.h +++ b/PassiveDataKit/PDKHttpTransmitter.h @@ -7,6 +7,7 @@ // #import "PassiveDataKit.h" +#import "NSString+RAInflections.h" #define PDK_SOURCE_KEY @"source" #define PDK_TRANSMITTER_ID_KEY @"transmitter-id" @@ -14,6 +15,17 @@ #define PDK_TRANSMITTER_REQUIRE_CHARGING_KEY @"require-charging" #define PDK_TRANSMITTER_REQUIRE_WIFI_KEY @"require-wifi" +#define PDK_METADATA_KEY @"passive-data-metadata" +#define PDK_GENERATOR_ID_KEY @"generator-id" +#define PDK_GENERATOR_KEY @"generator" +#define PDK_TIMESTAMP_KEY @"timestamp" + +#define PDK_DEFAULT_TRANSMITTER_ID @"http-transmitter" + @interface PDKHttpTransmitter : NSObject +@property NSString * source; +@property NSString * transmitterId; +@property NSURL * uploadUrl; + @end diff --git a/PassiveDataKit/PDKHttpTransmitter.m b/PassiveDataKit/PDKHttpTransmitter.m index 19050e6..2998f93 100644 --- a/PassiveDataKit/PDKHttpTransmitter.m +++ b/PassiveDataKit/PDKHttpTransmitter.m @@ -9,19 +9,10 @@ #import #import -#import "NSString+RAInflections.h" - #import "PDKAFURLSessionManager.h" #import "PDKHttpTransmitter.h" -#define PDK_METADATA_KEY @"passive-data-metadata" -#define PDK_GENERATOR_ID_KEY @"generator-id" -#define PDK_GENERATOR_KEY @"generator" -#define PDK_TIMESTAMP_KEY @"timestamp" - -#define PDK_DEFAULT_TRANSMITTER_ID @"http-transmitter" - typedef enum { ConnectionTypeUnknown, ConnectionTypeNone, @@ -31,9 +22,6 @@ @interface PDKHttpTransmitter () -@property NSString * source; -@property NSString * transmitterId; -@property NSURL * uploadUrl; @property BOOL requireCharging; @property BOOL requireWiFi; @@ -177,6 +165,21 @@ - (void) transmit:(BOOL) force completionHandler:(void (^)(UIBackgroundFetchResu [self transmitReadings:0 completionHandler:completionHandler]; } +- (NSURLRequest *) uploadRequestForPayload:(NSArray *) payload { + NSMutableURLRequest * request = [[PDKAFJSONRequestSerializer serializer] requestWithMethod:@"CREATE" + URLString:[self.uploadUrl description] + parameters:payload + error:nil]; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; + + return request; +} + +- (NSUInteger) payloadSize { + return 16; +} + - (void) transmitReadings:(NSUInteger) uploadWindow completionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler { if (uploadWindow == 0) { uploadWindow = 8; //!OCLINT @@ -186,7 +189,7 @@ - (void) transmitReadings:(NSUInteger) uploadWindow completionHandler:(void (^)( sqlite3_stmt * statement = NULL; - NSString * querySQL = @"SELECT D.id, D.properties FROM data D WHERE (D.timestamp < ?) LIMIT 16"; + NSString * querySQL = [NSString stringWithFormat:@"SELECT D.id, D.properties FROM data D WHERE (D.timestamp < ?) LIMIT %d", (int) [self payloadSize]]; const char * query_stmt = [querySQL UTF8String]; @@ -224,65 +227,62 @@ - (void) transmitReadings:(NSUInteger) uploadWindow completionHandler:(void (^)( sqlite3_finalize(statement); - NSMutableURLRequest *req = [[PDKAFJSONRequestSerializer serializer] requestWithMethod:@"CREATE" - URLString:[self.uploadUrl description] - parameters:payload - error:nil]; - [req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - [req setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - - NSLog(@"UPLOADING PAYLOAD: %@", payload); - - PDKAFURLSessionManager * manager = [[PDKAFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + NSURLRequest * request = [self uploadRequestForPayload:payload]; - [[manager dataTaskWithRequest:req - uploadProgress:nil - downloadProgress:nil - completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { - if ([responseObject containsObject:@"Data bundle added successfully, and ready for processing."] == NO) { - NSLog(@"Invalid response: %@", responseObject); - } - else if (error == nil) { - for (NSNumber * identifier in uploaded) { - sqlite3_stmt * deleteStatement = NULL; - - NSString * deleteSQL = @"DELETE FROM data WHERE (id = ?)"; - - const char * delete_stmt = [deleteSQL UTF8String]; - - if (sqlite3_prepare_v2(self.database, delete_stmt, -1, &deleteStatement, NULL) == SQLITE_OK) - { - sqlite3_bind_int(deleteStatement, 1, [identifier intValue]); + if (request != nil) { + NSLog(@"UPLOADING PAYLOAD: %@", payload); + + PDKAFURLSessionManager * manager = [[PDKAFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + + [[manager dataTaskWithRequest:request + uploadProgress:nil + downloadProgress:nil + completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { + if ([responseObject containsObject:@"Data bundle added successfully, and ready for processing."] == NO) { + NSLog(@"Invalid response: %@", responseObject); + } + else if (error == nil) { + for (NSNumber * identifier in uploaded) { + sqlite3_stmt * deleteStatement = NULL; - while (sqlite3_step(deleteStatement) == SQLITE_ROW) { //!OCLINT + NSString * deleteSQL = @"DELETE FROM data WHERE (id = ?)"; + + const char * delete_stmt = [deleteSQL UTF8String]; + + if (sqlite3_prepare_v2(self.database, delete_stmt, -1, &deleteStatement, NULL) == SQLITE_OK) + { + sqlite3_bind_int(deleteStatement, 1, [identifier intValue]); + + while (sqlite3_step(deleteStatement) == SQLITE_ROW) { //!OCLINT + + } + sqlite3_finalize(deleteStatement); + } else { + if (completionHandler != nil) { + completionHandler(UIBackgroundFetchResultFailed); + } + + NSLog(@"Error while deleting data. '%s'", sqlite3_errmsg(self.database)); + + return; } - - sqlite3_finalize(deleteStatement); + } + + NSTimeInterval interval = [NSDate date].timeIntervalSince1970 - now; + + if (uploadWindow > 0 && interval > uploadWindow && uploaded.count > 0) { + [self transmitReadings:(uploadWindow - interval) completionHandler:completionHandler]; } else { if (completionHandler != nil) { - completionHandler(UIBackgroundFetchResultFailed); + completionHandler(UIBackgroundFetchResultNewData); } - - NSLog(@"Error while deleting data. '%s'", sqlite3_errmsg(self.database)); - - return; + } } - NSTimeInterval interval = [NSDate date].timeIntervalSince1970 - now; - - if (uploadWindow > 0 && interval > uploadWindow && uploaded.count > 0) { - [self transmitReadings:(uploadWindow - interval) completionHandler:completionHandler]; - } else { - if (completionHandler != nil) { - completionHandler(UIBackgroundFetchResultNewData); - } - - } - } - - }] resume]; + }] resume]; + } } } @@ -294,9 +294,9 @@ - (NSUInteger) transmittedSize { return -1; } -- (void) receivedData:(NSDictionary *) data forGenerator:(PDKDataGenerator) dataGenerator { - NSMutableDictionary * toStore = [NSMutableDictionary dictionaryWithDictionary:data]; - +- (NSDictionary *) processIncomingDataPoint:(NSDictionary *) dataPoint forGenerator:(PDKDataGenerator) dataGenerator { + NSMutableDictionary * toStore = [NSMutableDictionary dictionaryWithDictionary:dataPoint]; + if (toStore[PDK_METADATA_KEY] == nil) { NSMutableDictionary * metadata = [NSMutableDictionary dictionary]; @@ -310,30 +310,41 @@ - (void) receivedData:(NSDictionary *) data forGenerator:(PDKDataGenerator) data } else { metadata[PDK_SOURCE_KEY] = [[PassiveDataKit sharedInstance] identifierForUser]; } - + metadata[PDK_TIMESTAMP_KEY] = [NSNumber numberWithDouble:[NSDate date].timeIntervalSince1970]; toStore[PDK_METADATA_KEY] = metadata; } + + return toStore; +} + + +- (void) receivedData:(NSDictionary *) dataPoint forGenerator:(PDKDataGenerator) dataGenerator { + NSDictionary * toStore = [self processIncomingDataPoint:dataPoint forGenerator:dataGenerator]; - sqlite3_stmt * stmt; - - NSString * insert = @"INSERT INTO data (timestamp, properties) VALUES (?, ?);"; - - if(sqlite3_prepare_v2(self.database, [insert UTF8String], -1, &stmt, NULL) == SQLITE_OK) { - sqlite3_bind_double(stmt, 1, [toStore[PDK_METADATA_KEY][PDK_TIMESTAMP_KEY] doubleValue]); + if (toStore != nil) { + NSLog(@"STORING: %@", toStore); - NSError * err = nil; - NSData * jsonData = [NSJSONSerialization dataWithJSONObject:toStore options:0 error:&err]; - NSString * jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + sqlite3_stmt * stmt; - sqlite3_bind_text(stmt, 2, [jsonString UTF8String], -1, SQLITE_TRANSIENT); + NSString * insert = @"INSERT INTO data (timestamp, properties) VALUES (?, ?);"; - if (SQLITE_DONE != sqlite3_step(stmt)) { - NSLog(@"Error while inserting data. '%s'", sqlite3_errmsg(self.database)); + if(sqlite3_prepare_v2(self.database, [insert UTF8String], -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_double(stmt, 1, [toStore[PDK_METADATA_KEY][PDK_TIMESTAMP_KEY] doubleValue]); + + NSError * err = nil; + NSData * jsonData = [NSJSONSerialization dataWithJSONObject:toStore options:0 error:&err]; + NSString * jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + + sqlite3_bind_text(stmt, 2, [jsonString UTF8String], -1, SQLITE_TRANSIENT); + + if (SQLITE_DONE != sqlite3_step(stmt)) { + NSLog(@"Error while inserting data. '%s'", sqlite3_errmsg(self.database)); + } + + sqlite3_finalize(stmt); } - - sqlite3_finalize(stmt); } } diff --git a/PassiveDataKit/PassiveDataKit-Shared.h b/PassiveDataKit/PassiveDataKit-Shared.h index ab0ae03..71c213a 100644 --- a/PassiveDataKit/PassiveDataKit-Shared.h +++ b/PassiveDataKit/PassiveDataKit-Shared.h @@ -10,3 +10,5 @@ FOUNDATION_EXPORT const unsigned char PassiveDataKit_SharedVersionString[]; #import "PassiveDataKit.h" #import "PDKHttpTransmitter.h" + +#import "PDKAFURLRequestSerialization.h"