From 301b82f5b1569a9936110c1d1ccfbec3406eeb94 Mon Sep 17 00:00:00 2001 From: Chris Karr Date: Mon, 19 Jun 2017 00:04:27 -0500 Subject: [PATCH] Revamping PDK to better match Android architecture --- PassiveDataKit.xcodeproj/project.pbxproj | 68 +++- PassiveDataKit/PDKBaseGenerator.h | 15 + PassiveDataKit/PDKBaseGenerator.m | 32 ++ PassiveDataKit/PDKDataPointsManager.h | 20 -- PassiveDataKit/PDKDataPointsManager.m | 305 ---------------- PassiveDataKit/PDKDataReportViewController.m | 4 +- PassiveDataKit/PDKEventsGenerator.h | 10 +- PassiveDataKit/PDKEventsGenerator.m | 39 +- PassiveDataKit/PDKGooglePlacesGenerator.h | 3 +- PassiveDataKit/PDKGooglePlacesGenerator.m | 7 +- PassiveDataKit/PDKHttpTransmitter.h | 19 + PassiveDataKit/PDKHttpTransmitter.m | 340 ++++++++++++++++++ PassiveDataKit/PDKLocationGenerator.h | 4 +- PassiveDataKit/PDKLocationGenerator.m | 8 +- PassiveDataKit/PDKMixpanelEventGenerator.m | 10 +- PassiveDataKit/PassiveDataKit-Shared.h | 12 + PassiveDataKit/PassiveDataKit.h | 49 +-- PassiveDataKit/PassiveDataKit.m | 182 +++------- PassiveDataKit/PassiveDataKit.modulemap | 3 + .../Third-Party/NSString+RAInflections.h | 17 + .../Third-Party/NSString+RAInflections.m | 62 ++++ 21 files changed, 686 insertions(+), 523 deletions(-) create mode 100644 PassiveDataKit/PDKBaseGenerator.h create mode 100644 PassiveDataKit/PDKBaseGenerator.m delete mode 100644 PassiveDataKit/PDKDataPointsManager.h delete mode 100644 PassiveDataKit/PDKDataPointsManager.m create mode 100644 PassiveDataKit/PDKHttpTransmitter.h create mode 100644 PassiveDataKit/PDKHttpTransmitter.m create mode 100644 PassiveDataKit/PassiveDataKit-Shared.h create mode 100644 PassiveDataKit/PassiveDataKit.modulemap create mode 100644 PassiveDataKit/Third-Party/NSString+RAInflections.h create mode 100644 PassiveDataKit/Third-Party/NSString+RAInflections.m diff --git a/PassiveDataKit.xcodeproj/project.pbxproj b/PassiveDataKit.xcodeproj/project.pbxproj index e157bcd..68e0ed4 100644 --- a/PassiveDataKit.xcodeproj/project.pbxproj +++ b/PassiveDataKit.xcodeproj/project.pbxproj @@ -28,12 +28,17 @@ 387F9A571D1B8AF3007FA29A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 387F9A561D1B8AF3007FA29A /* CoreGraphics.framework */; }; 387F9A591D1B8AF9007FA29A /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 387F9A581D1B8AF9007FA29A /* QuartzCore.framework */; }; 387F9A5C1D1B8E32007FA29A /* libicucore.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 387F9A5B1D1B8E32007FA29A /* libicucore.tbd */; }; + 388D5E401EF76CD500C9A2A2 /* PassiveDataKit-Shared.h in Headers */ = {isa = PBXBuildFile; fileRef = 388D5E3F1EF76C5000C9A2A2 /* PassiveDataKit-Shared.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; + 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 */; }; 38A6523C1CDA2D9B00AE8B3B /* PDKLocationGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 38A6523A1CDA2D9B00AE8B3B /* PDKLocationGenerator.h */; }; 38A6523D1CDA2D9B00AE8B3B /* PDKLocationGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 38A6523B1CDA2D9B00AE8B3B /* PDKLocationGenerator.m */; }; 38A652431CDA30DB00AE8B3B /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38A652421CDA30DB00AE8B3B /* CoreLocation.framework */; }; - 38C666B01D037FCF00E6A6C8 /* PDKDataPointsManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C666AE1D037FCF00E6A6C8 /* PDKDataPointsManager.h */; }; - 38C666B11D037FCF00E6A6C8 /* PDKDataPointsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C666AF1D037FCF00E6A6C8 /* PDKDataPointsManager.m */; }; 38C666B31D0380CD00E6A6C8 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 38C666B21D0380CD00E6A6C8 /* libsqlite3.tbd */; }; 38C666D71D038A8E00E6A6C8 /* PDKAFAutoPurgingImageCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C666B61D038A8E00E6A6C8 /* PDKAFAutoPurgingImageCache.h */; }; 38C666D81D038A8E00E6A6C8 /* PDKAFAutoPurgingImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C666B71D038A8E00E6A6C8 /* PDKAFAutoPurgingImageCache.m */; }; @@ -146,12 +151,18 @@ 387F9A561D1B8AF3007FA29A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 387F9A581D1B8AF9007FA29A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 387F9A5B1D1B8E32007FA29A /* libicucore.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.tbd; path = usr/lib/libicucore.tbd; sourceTree = SDKROOT; }; + 388D5E3F1EF76C5000C9A2A2 /* PassiveDataKit-Shared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PassiveDataKit-Shared.h"; sourceTree = ""; }; 389DE4AA1EF726B2009C8B27 /* Mixpanel.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Mixpanel.xcodeproj; path = "mixpanel-iphone/Mixpanel.xcodeproj"; sourceTree = ""; }; + 389DE4D01EF7492C009C8B27 /* PDKHttpTransmitter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PDKHttpTransmitter.h; sourceTree = ""; }; + 389DE4D11EF7492C009C8B27 /* PDKHttpTransmitter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PDKHttpTransmitter.m; sourceTree = ""; }; + 389DE4D41EF74DDA009C8B27 /* NSString+RAInflections.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RAInflections.h"; sourceTree = ""; }; + 389DE4D51EF74DDA009C8B27 /* NSString+RAInflections.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RAInflections.m"; sourceTree = ""; }; + 389DE4DC1EF75ADB009C8B27 /* PDKBaseGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PDKBaseGenerator.h; sourceTree = ""; }; + 389DE4DD1EF75ADB009C8B27 /* PDKBaseGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PDKBaseGenerator.m; sourceTree = ""; }; + 389DE4E01EF76597009C8B27 /* PassiveDataKit.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = PassiveDataKit.modulemap; sourceTree = ""; }; 38A6523A1CDA2D9B00AE8B3B /* PDKLocationGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PDKLocationGenerator.h; sourceTree = ""; }; 38A6523B1CDA2D9B00AE8B3B /* PDKLocationGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PDKLocationGenerator.m; sourceTree = ""; }; 38A652421CDA30DB00AE8B3B /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; - 38C666AE1D037FCF00E6A6C8 /* PDKDataPointsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PDKDataPointsManager.h; sourceTree = ""; }; - 38C666AF1D037FCF00E6A6C8 /* PDKDataPointsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PDKDataPointsManager.m; sourceTree = ""; }; 38C666B21D0380CD00E6A6C8 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; 38C666B61D038A8E00E6A6C8 /* PDKAFAutoPurgingImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PDKAFAutoPurgingImageCache.h; sourceTree = ""; }; 38C666B71D038A8E00E6A6C8 /* PDKAFAutoPurgingImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PDKAFAutoPurgingImageCache.m; sourceTree = ""; }; @@ -256,12 +267,13 @@ 3842F7331CDA2021007F843D /* PassiveDataKit */ = { isa = PBXGroup; children = ( + 389DE4E01EF76597009C8B27 /* PassiveDataKit.modulemap */, + 388D5E3F1EF76C5000C9A2A2 /* PassiveDataKit-Shared.h */, 38C666B41D038A8E00E6A6C8 /* Third Party */, 38C666AD1D037FA700E6A6C8 /* Generators */, + 389DE4CA1EF748EB009C8B27 /* Transmitters */, 3842F7341CDA2021007F843D /* PassiveDataKit.h */, 3842F7551CDA20FE007F843D /* PassiveDataKit.m */, - 38C666AE1D037FCF00E6A6C8 /* PDKDataPointsManager.h */, - 38C666AF1D037FCF00E6A6C8 /* PDKDataPointsManager.m */, 383ECC511D1F4F41004E0B2B /* PDKDataReportViewController.h */, 383ECC521D1F4F41004E0B2B /* PDKDataReportViewController.m */, ); @@ -289,6 +301,15 @@ name = Products; sourceTree = ""; }; + 389DE4CA1EF748EB009C8B27 /* Transmitters */ = { + isa = PBXGroup; + children = ( + 389DE4D01EF7492C009C8B27 /* PDKHttpTransmitter.h */, + 389DE4D11EF7492C009C8B27 /* PDKHttpTransmitter.m */, + ); + name = Transmitters; + sourceTree = ""; + }; 38A652441CDA30E800AE8B3B /* Frameworks */ = { isa = PBXGroup; children = ( @@ -308,16 +329,16 @@ 38C666AD1D037FA700E6A6C8 /* Generators */ = { isa = PBXGroup; children = ( + 38DC94851D2346EC00552259 /* View Controllers */, 38DC949C1D2711B900552259 /* Services */, - 38DC94851D2346EC00552259 /* ViewControllers */, 38A6523A1CDA2D9B00AE8B3B /* PDKLocationGenerator.h */, 38A6523B1CDA2D9B00AE8B3B /* PDKLocationGenerator.m */, 383ECC641D1F816D004E0B2B /* PDKEventsGenerator.h */, 383ECC651D1F816D004E0B2B /* PDKEventsGenerator.m */, 383ECC681D1F82F5004E0B2B /* PDKMixpanelEventGenerator.h */, 383ECC691D1F82F5004E0B2B /* PDKMixpanelEventGenerator.m */, - 383ECC6C1D1F9915004E0B2B /* PDKLocationAnnotation.h */, - 383ECC6D1D1F9915004E0B2B /* PDKLocationAnnotation.m */, + 389DE4DC1EF75ADB009C8B27 /* PDKBaseGenerator.h */, + 389DE4DD1EF75ADB009C8B27 /* PDKBaseGenerator.m */, ); name = Generators; sourceTree = ""; @@ -327,6 +348,8 @@ children = ( 389DE4AA1EF726B2009C8B27 /* Mixpanel.xcodeproj */, 38C666B51D038A8E00E6A6C8 /* AFNetworking */, + 389DE4D41EF74DDA009C8B27 /* NSString+RAInflections.h */, + 389DE4D51EF74DDA009C8B27 /* NSString+RAInflections.m */, ); name = "Third Party"; path = "Third-Party"; @@ -372,15 +395,17 @@ path = AFNetworking; sourceTree = ""; }; - 38DC94851D2346EC00552259 /* ViewControllers */ = { + 38DC94851D2346EC00552259 /* View Controllers */ = { isa = PBXGroup; children = ( 38DC94861D23470C00552259 /* PDKLocationGeneratorViewController.h */, 38DC94871D23470C00552259 /* PDKLocationGeneratorViewController.m */, 38DC94A51D281A7300552259 /* PDKEventsGeneratorViewController.h */, 38DC94A61D281A7300552259 /* PDKEventsGeneratorViewController.m */, + 383ECC6C1D1F9915004E0B2B /* PDKLocationAnnotation.h */, + 383ECC6D1D1F9915004E0B2B /* PDKLocationAnnotation.m */, ); - name = ViewControllers; + name = "View Controllers"; sourceTree = ""; }; 38DC949C1D2711B900552259 /* Services */ = { @@ -399,16 +424,23 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 38C666B01D037FCF00E6A6C8 /* PDKDataPointsManager.h in Headers */, + 388D5E401EF76CD500C9A2A2 /* PassiveDataKit-Shared.h in Headers */, + 3842F7351CDA2021007F843D /* PassiveDataKit.h in Headers */, + 389DE4D21EF7492C009C8B27 /* PDKHttpTransmitter.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 */, 38C666F61D038A8E00E6A6C8 /* UIWebView+PDKAFNetworking.h in Headers */, - 38DC949F1D2711C600552259 /* PDKGooglePlacesGenerator.h in Headers */, 38C666DD1D038A8E00E6A6C8 /* PDKAFNetworkActivityIndicatorManager.h in Headers */, 38C666EE1D038A8E00E6A6C8 /* UIImage+PDKAFNetworking.h in Headers */, 383ECC531D1F4F41004E0B2B /* PDKDataReportViewController.h in Headers */, @@ -423,10 +455,6 @@ 38C666F41D038A8E00E6A6C8 /* UIRefreshControl+PDKAFNetworking.h in Headers */, 383ECC6E1D1F9915004E0B2B /* PDKLocationAnnotation.h in Headers */, 38C666D71D038A8E00E6A6C8 /* PDKAFAutoPurgingImageCache.h in Headers */, - 3842F7351CDA2021007F843D /* PassiveDataKit.h in Headers */, - 38A6523C1CDA2D9B00AE8B3B /* PDKLocationGenerator.h in Headers */, - 383ECC6A1D1F82F5004E0B2B /* PDKMixpanelEventGenerator.h in Headers */, - 383ECC661D1F816D004E0B2B /* PDKEventsGenerator.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,7 +603,6 @@ 383ECC671D1F816D004E0B2B /* PDKEventsGenerator.m in Sources */, 38C666F31D038A8E00E6A6C8 /* UIProgressView+PDKAFNetworking.m in Sources */, 383ECC6F1D1F9915004E0B2B /* PDKLocationAnnotation.m in Sources */, - 38C666B11D037FCF00E6A6C8 /* PDKDataPointsManager.m in Sources */, 38C666F71D038A8E00E6A6C8 /* UIWebView+PDKAFNetworking.m in Sources */, 38C666F01D038A8E00E6A6C8 /* UIImageView+PDKAFNetworking.m in Sources */, 38C666E31D038A8E00E6A6C8 /* PDKAFSecurityPolicy.m in Sources */, @@ -589,6 +616,7 @@ 38C666D81D038A8E00E6A6C8 /* PDKAFAutoPurgingImageCache.m in Sources */, 38C666DE1D038A8E00E6A6C8 /* PDKAFNetworkActivityIndicatorManager.m in Sources */, 38DC94A01D2711C600552259 /* PDKGooglePlacesGenerator.m in Sources */, + 389DE4D31EF7492C009C8B27 /* PDKHttpTransmitter.m in Sources */, 383ECC6B1D1F82F5004E0B2B /* PDKMixpanelEventGenerator.m in Sources */, 38C666E51D038A8E00E6A6C8 /* PDKAFURLRequestSerialization.m in Sources */, 38C666E91D038A8E00E6A6C8 /* PDKAFURLSessionManager.m in Sources */, @@ -597,6 +625,8 @@ 38C666E11D038A8E00E6A6C8 /* PDKAFNetworkReachabilityManager.m in Sources */, 38C666DC1D038A8E00E6A6C8 /* PDKAFImageDownloader.m in Sources */, 38C666EB1D038A8E00E6A6C8 /* UIActivityIndicatorView+PDKAFNetworking.m in Sources */, + 389DE4DF1EF75ADB009C8B27 /* PDKBaseGenerator.m in Sources */, + 389DE4D71EF74DDA009C8B27 /* NSString+RAInflections.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -726,6 +756,7 @@ INFOPLIST_FILE = PassiveDataKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = PassiveDataKit/PassiveDataKit.modulemap; PRODUCT_BUNDLE_IDENTIFIER = "com.audacious-software.PassiveDataKit"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -745,6 +776,7 @@ INFOPLIST_FILE = PassiveDataKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = PassiveDataKit/PassiveDataKit.modulemap; PRODUCT_BUNDLE_IDENTIFIER = "com.audacious-software.PassiveDataKit"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/PassiveDataKit/PDKBaseGenerator.h b/PassiveDataKit/PDKBaseGenerator.h new file mode 100644 index 0000000..37f9e6f --- /dev/null +++ b/PassiveDataKit/PDKBaseGenerator.h @@ -0,0 +1,15 @@ +// +// PDKBaseGenerator.h +// PassiveDataKit +// +// Created by Chris Karr on 6/18/17. +// Copyright © 2017 Audacious Software. All rights reserved. +// + +@import Foundation; + +#import "PassiveDataKit.h" + +@interface PDKBaseGenerator : NSObject + +@end diff --git a/PassiveDataKit/PDKBaseGenerator.m b/PassiveDataKit/PDKBaseGenerator.m new file mode 100644 index 0000000..49d2b79 --- /dev/null +++ b/PassiveDataKit/PDKBaseGenerator.m @@ -0,0 +1,32 @@ +// +// PDKBaseGenerator.m +// PassiveDataKit +// +// Created by Chris Karr on 6/18/17. +// Copyright © 2017 Audacious Software. All rights reserved. +// + +#import "PDKBaseGenerator.h" + +#define GENERATOR_ID @"pdk-base-generator" + +@implementation PDKBaseGenerator + +- (NSString *) fullGeneratorName { + return [NSString stringWithFormat:@"%@: %@", [self generatorId], [[PassiveDataKit sharedInstance] userAgent]]; +} + +- (NSString *) generatorId { + return GENERATOR_ID; +} + +- (void) updateOptions:(NSDictionary *) options { + +} + +- (UIView *) visualizationForSize:(CGSize) size { + return nil; +} + + +@end diff --git a/PassiveDataKit/PDKDataPointsManager.h b/PassiveDataKit/PDKDataPointsManager.h deleted file mode 100644 index 651301d..0000000 --- a/PassiveDataKit/PDKDataPointsManager.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// PDKEventManager.h -// PassiveDataKit -// -// Created by Chris Karr on 6/4/16. -// Copyright © 2016 Audacious Software. All rights reserved. -// - -@import Foundation; - -@interface PDKDataPointsManager : NSObject - -+ (PDKDataPointsManager *) sharedInstance; - -- (BOOL) logDataPoint:(NSString *) generator generatorId:(NSString *) generatorId source:(NSString *) source properties:(NSDictionary *) properties; -- (BOOL) logEvent:(NSString *) eventName properties:(NSDictionary *) properties; - -- (void) uploadDataPoints:(NSURL *) url window:(NSTimeInterval) uploadWindow complete:(void (^)(BOOL success, int uploaded)) completed; - -@end diff --git a/PassiveDataKit/PDKDataPointsManager.m b/PassiveDataKit/PDKDataPointsManager.m deleted file mode 100644 index 657afbb..0000000 --- a/PassiveDataKit/PDKDataPointsManager.m +++ /dev/null @@ -1,305 +0,0 @@ -// -// PDKEventManager.m -// PassiveDataKit -// -// Created by Chris Karr on 6/4/16. -// Copyright © 2016 Audacious Software. All rights reserved. -// - -#import - -@import Mixpanel; - -#import "PassiveDataKit.h" -#import "PDKAFHTTPSessionManager.h" -#import "PDKDataPointsManager.h" -#import "PDKMixpanelEventGenerator.h" -#import "PDKEventsGenerator.h" - -@interface PDKDataPointsManager () - -@property sqlite3 * database; - -@end - -@implementation PDKDataPointsManager - -static PDKDataPointsManager * sharedObject = nil; - -+ (PDKDataPointsManager *) sharedInstance -{ - static dispatch_once_t _singletonPredicate; - - dispatch_once(&_singletonPredicate, ^{ - sharedObject = [[super allocWithZone:nil] init]; - - }); - - return sharedObject; -} - -+ (id) allocWithZone:(NSZone *) zone //!OCLINT -{ - return [self sharedInstance]; -} - -- (id) init -{ - if (self = [super init]) - { - self.database = [self openDatabase]; - } - - return self; -} - -- (BOOL) logEvent:(NSString *) eventName properties:(NSDictionary *) properties { - NSMutableDictionary * payload = [NSMutableDictionary dictionary]; - - if (properties != nil) { - [payload addEntriesFromDictionary:properties]; - } - - payload[@"event"] = eventName; - - if ([[PassiveDataKit sharedInstance] mixpanelEnabled]) { - Mixpanel * mixpanel = [Mixpanel sharedInstanceWithToken:[[NSUserDefaults standardUserDefaults] stringForKey:PDKMixpanelToken]]; - - [mixpanel identify:[[PassiveDataKit sharedInstance] identifierForUser]]; - - NSMutableDictionary * info = [NSMutableDictionary dictionaryWithDictionary:[[NSBundle mainBundle] infoDictionary]]; - - if (info[@"CFBundleName"] == nil) { - info[@"CFBundleName"] = @"Passive Data Kit"; - } - - if (info[@"CFBundleShortVersionString"] == nil) { - info[@"CFBundleShortVersionString"] = @"1.0"; - } - - payload[@"$browser"] = info[@"CFBundleName"]; - payload[@"$browser_version"] = info[@"CFBundleShortVersionString"]; - - if (mixpanel != nil) { - [mixpanel track:eventName properties:payload]; - } - - [PDKMixpanelEventGenerator logForReview:payload]; - } - - [PDKEventsGenerator logForReview:payload]; - - return [self logDataPoint:nil generatorId:nil source:nil properties:payload]; -} - -- (BOOL) logDataPoint:(NSString *) generator generatorId:(NSString *) generatorId source:(NSString *) source properties:(NSDictionary *) properties { - if (source == nil) { - source = [[PassiveDataKit sharedInstance] identifierForUser]; //!OCLINT - } - - if (generator == nil) { - generator = [[PassiveDataKit sharedInstance] generator]; //!OCLINT - } - - if (generatorId == nil) { - generatorId = [[PassiveDataKit sharedInstance] generatorId]; //!OCLINT - } - - NSDate * now = [NSDate date]; - - if (properties == nil) { - properties = @{}; //!OCLINT - } - - sqlite3_stmt * stmt; - - NSString * insert = @"INSERT INTO data (source, generator, generatorId, timestamp, properties) VALUES (?, ?, ?, ?, ?);"; - - if(sqlite3_prepare_v2(self.database, [insert UTF8String], -1, &stmt, NULL) == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, [source UTF8String], -1, SQLITE_TRANSIENT); - sqlite3_bind_text(stmt, 2, [generator UTF8String], -1, SQLITE_TRANSIENT); - sqlite3_bind_text(stmt, 3, [generatorId UTF8String], -1, SQLITE_TRANSIENT); - sqlite3_bind_double(stmt, 4, now.timeIntervalSince1970); - - NSError * err = nil; - NSData * jsonData = [NSJSONSerialization dataWithJSONObject:properties options:0 error:&err]; - NSString * jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - - sqlite3_bind_text(stmt, 5, [jsonString UTF8String], -1, SQLITE_TRANSIENT); - - if (SQLITE_DONE != sqlite3_step(stmt)) { - NSLog(@"Error while inserting data. '%s'", sqlite3_errmsg(self.database)); - - return NO; - } - - sqlite3_finalize(stmt); - } - - return YES; -} - -- (void) uploadDataPoints:(NSURL *) url window:(NSTimeInterval) uploadWindow complete:(void (^)(BOOL success, int uploaded)) completed { //!OCLINT - if (uploadWindow == 0) { - uploadWindow = 5; //!OCLINT - } - - NSTimeInterval now = [NSDate date].timeIntervalSince1970; - - sqlite3_stmt * statement = NULL; - - NSString * querySQL = @"SELECT D.id, D.source, D.generator, D.generatorId, D.timestamp, D.properties FROM data D WHERE (D.timestamp < ?) LIMIT 16"; - - const char * query_stmt = [querySQL UTF8String]; - - if (sqlite3_prepare_v2(self.database, query_stmt, -1, &statement, NULL) == SQLITE_OK) - { - sqlite3_bind_double(statement, 1, now); - - NSMutableArray * payload = [NSMutableArray array]; - NSMutableArray * uploaded = [NSMutableArray array]; - - while (sqlite3_step(statement) == SQLITE_ROW) - { - NSInteger pointId = sqlite3_column_int(statement, 0); - - NSString * source = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 1)]; - NSString * generator = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 2)]; - NSString * generatorId = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 3)]; - NSTimeInterval timestamp = sqlite3_column_double(statement, 4); - NSString * jsonString = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 5)]; - - NSError * error = nil; - - NSMutableDictionary * dataPoint = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] - options:(NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves) - error:&error]; - if (error == nil) { - NSMutableDictionary * metadata = [NSMutableDictionary dictionary]; - metadata[@"source"] = source; - metadata[@"generator-id"] = generatorId; - metadata[@"generator"] = generator; - metadata[@"timestamp"] = [NSNumber numberWithDouble:timestamp]; - - [uploaded addObject:[NSNumber numberWithInteger:pointId]]; - - dataPoint[@"passive-data-metadata"] = metadata; - - [payload addObject:dataPoint]; - } - else { - completed(NO, 0); - - return; - } - } - - sqlite3_finalize(statement); - - NSMutableURLRequest *req = [[PDKAFJSONRequestSerializer serializer] requestWithMethod:@"CREATE" - URLString:[url description] - parameters:payload - error:nil]; - [req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - [req setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - - PDKAFURLSessionManager * manager = [[PDKAFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; - - [[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); - - completed(NO, 0); - } - 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]); - - while (sqlite3_step(deleteStatement) == SQLITE_ROW) { //!OCLINT - - } - - sqlite3_finalize(deleteStatement); - } else { - NSLog(@"Error while deleting data. '%s'", sqlite3_errmsg(self.database)); - - completed(NO, 0); - - return; - } - } - - completed(YES, (int) uploaded.count); - - NSTimeInterval interval = [NSDate date].timeIntervalSince1970 - now; - - if (uploadWindow > 0 && interval > uploadWindow && uploaded.count > 0) { - [self uploadDataPoints:url window:(uploadWindow - interval) complete:completed]; - } - } else { - completed(NO, 0); - } - - }] resume]; - } - else { - completed(NO, 0); - } -} - -- (NSString *) databasePath -{ - NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - - NSString * cachePath = paths[0]; - - NSString * dbPath = [cachePath stringByAppendingPathComponent:@"data.sqlite3"]; - - return dbPath; -} - -- (sqlite3 *) openDatabase { - NSString * dbPath = [self databasePath]; - - if ([[NSFileManager defaultManager] fileExistsAtPath:dbPath] == NO) - { - sqlite3 * database = NULL; - - const char * path = [dbPath UTF8String]; - - if (sqlite3_open(path, &database) == SQLITE_OK) { - char * error; - - const char * createStatement = "CREATE TABLE IF NOT EXISTS data (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT, generator TEXT, generatorId TEXT, timestamp REAL, properties TEXT)"; - - if (sqlite3_exec(database, createStatement, NULL, NULL, &error) != SQLITE_OK) { //!OCLINT - - } - - sqlite3_close(database); - } - } - - const char * dbpath = [dbPath UTF8String]; - - sqlite3 * database = NULL; - - if (sqlite3_open(dbpath, &database) == SQLITE_OK) { - return database; - } - - return NULL; -} - -@end diff --git a/PassiveDataKit/PDKDataReportViewController.m b/PassiveDataKit/PDKDataReportViewController.m index 6bee219..ee09854 100644 --- a/PassiveDataKit/PDKDataReportViewController.m +++ b/PassiveDataKit/PDKDataReportViewController.m @@ -70,9 +70,11 @@ - (void) loadVisualization:(NSString *) generator { } else { Class generatorClass = NSClassFromString(generator); + id generator = [generatorClass sharedInstance]; + if (generatorClass != nil) { if ([generatorClass respondsToSelector:@selector(visualizationForSize:)]) { - visualization = [generatorClass visualizationForSize:self.detailsView.bounds.size]; + visualization = [generator visualizationForSize:self.detailsView.bounds.size]; } } diff --git a/PassiveDataKit/PDKEventsGenerator.h b/PassiveDataKit/PDKEventsGenerator.h index 9a00683..88d7240 100644 --- a/PassiveDataKit/PDKEventsGenerator.h +++ b/PassiveDataKit/PDKEventsGenerator.h @@ -8,11 +8,17 @@ @import UIKit; -@interface PDKEventsGenerator : NSObject +#import "PassiveDataKit.h" + +#import "PDKBaseGenerator.h" + +@interface PDKEventsGenerator : PDKBaseGenerator extern NSString *const PDKEventsGeneratorEnabled; extern NSString *const PDKEventsGeneratorCanDisable; -+ (void) logForReview:(NSDictionary *) payload; ++ (PDKEventsGenerator *) sharedInstance; + +- (void) logEvent:(NSString *) eventName properties:(NSDictionary *) properties; @end diff --git a/PassiveDataKit/PDKEventsGenerator.m b/PassiveDataKit/PDKEventsGenerator.m index 9ab7c78..70f767f 100644 --- a/PassiveDataKit/PDKEventsGenerator.m +++ b/PassiveDataKit/PDKEventsGenerator.m @@ -8,9 +8,13 @@ @import UIKit; +#import "PassiveDataKit.h" + #import "PDKEventsGenerator.h" #import "PDKEventsGeneratorViewController.h" +#define GENERATOR_ID @"pdk-app-event" + NSString * const PDKEventsGeneratorEnabled = @"PDKEventsGeneratorEnabled"; //!OCLINT NSString * const PDKEventsGeneratorCanDisable = @"PDKEventsGeneratorCanDisable"; //!OCLINT @@ -18,8 +22,7 @@ @implementation PDKEventsGenerator static PDKEventsGenerator * sharedObject = nil; -+ (PDKEventsGenerator *) sharedInstance -{ ++ (PDKEventsGenerator *) sharedInstance { static dispatch_once_t _singletonPredicate; dispatch_once(&_singletonPredicate, ^{ @@ -30,18 +33,15 @@ + (PDKEventsGenerator *) sharedInstance return sharedObject; } -+ (id) allocWithZone:(NSZone *) zone //!OCLINT -{ ++ (id) allocWithZone:(NSZone *) zone { //!OCLINT return [self sharedInstance]; } -+ (UIView *) visualizationForSize:(CGSize) size { - PDKEventsGenerator * generator = [PDKEventsGenerator sharedInstance]; - +- (UIView *) visualizationForSize:(CGSize) size { UITableView * tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]; - tableView.dataSource = generator; - tableView.delegate = generator; + tableView.dataSource = self; + tableView.delegate = self; return tableView; } @@ -75,7 +75,15 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger return reviewPoints.count; } -+ (void) logForReview:(NSDictionary *) payload { +- (void) logEvent:(NSString *) eventName properties:(NSDictionary *) properties { + NSMutableDictionary * event = [NSMutableDictionary dictionaryWithDictionary:properties]; + + NSDate * recorded = [NSDate date]; + + event[@"event_name"] = eventName; + event[@"event_details"] = properties; + event[@"observed"] = [NSNumber numberWithDouble:(1000 * recorded.timeIntervalSince1970)]; + NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; NSString * key = @"PDKEventGeneratorReviewPoints"; @@ -92,10 +100,7 @@ + (void) logForReview:(NSDictionary *) payload { } } - NSMutableDictionary * reviewPoint = [NSMutableDictionary dictionaryWithDictionary:payload]; - [reviewPoint setValue:[NSDate date] forKey:@"recorded"]; - - [newPoints addObject:reviewPoint]; + [newPoints addObject:event]; [newPoints sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { return [obj2[@"recorded"] compare:obj1[@"recorded"]]; @@ -107,11 +112,17 @@ + (void) logForReview:(NSDictionary *) payload { [defaults setValue:newPoints forKey:key]; [defaults synchronize]; + + [[PassiveDataKit sharedInstance] receivedData:event forGenerator:PDKEvents]; } + (UIViewController *) detailsController { return [[PDKEventsGeneratorViewController alloc] init]; } +- (NSString *) generatorId { + return GENERATOR_ID; +} + @end diff --git a/PassiveDataKit/PDKGooglePlacesGenerator.h b/PassiveDataKit/PDKGooglePlacesGenerator.h index 9e1bd22..177dc05 100644 --- a/PassiveDataKit/PDKGooglePlacesGenerator.h +++ b/PassiveDataKit/PDKGooglePlacesGenerator.h @@ -9,8 +9,9 @@ #import #import "PassiveDataKit.h" +#import "PDKBaseGenerator.h" -@interface PDKGooglePlacesGenerator : NSObject +@interface PDKGooglePlacesGenerator : PDKBaseGenerator + (PDKGooglePlacesGenerator *) sharedInstance; diff --git a/PassiveDataKit/PDKGooglePlacesGenerator.m b/PassiveDataKit/PDKGooglePlacesGenerator.m index 8d5289f..7afec28 100644 --- a/PassiveDataKit/PDKGooglePlacesGenerator.m +++ b/PassiveDataKit/PDKGooglePlacesGenerator.m @@ -12,6 +12,7 @@ #import "PDKAFHTTPSessionManager.h" #import "PDKGooglePlacesGenerator.h" +#import "PDKLocationGenerator.h" @interface PDKGooglePlacesGenerator () @@ -94,7 +95,9 @@ - (void) addListener:(id)listener options:(NSDictionary *) opti [self transmitPlacesForFreetextQuery:self.lastOptions[PDKGooglePlacesFreetextQuery]]; } else { if (self.listeners.count == 1) { - [[PassiveDataKit sharedInstance] registerListener:self forGenerator:PDKLocation options:options]; + [[PassiveDataKit sharedInstance] registerListener:self forGenerator:PDKLocation]; + + [[PDKLocationGenerator sharedInstance] updateOptions:options]; } } } @@ -304,7 +307,7 @@ - (void) receivedData:(NSDictionary *) data forGenerator:(PDKDataGenerator) data [self transmitPlacesForLocation:[data valueForKey:PDKLocationInstance]]; } -+ (UIView *) visualizationForSize:(CGSize) size { +- (UIView *) visualizationForSize:(CGSize) size { UIView * view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]; view.backgroundColor = [UIColor redColor]; diff --git a/PassiveDataKit/PDKHttpTransmitter.h b/PassiveDataKit/PDKHttpTransmitter.h new file mode 100644 index 0000000..f698745 --- /dev/null +++ b/PassiveDataKit/PDKHttpTransmitter.h @@ -0,0 +1,19 @@ +// +// PDKHttpTransmitter.h +// PassiveDataKit +// +// Created by Chris Karr on 6/18/17. +// Copyright © 2017 Audacious Software. All rights reserved. +// + +#import "PassiveDataKit.h" + +#define PDK_SOURCE_KEY @"source" +#define PDK_TRANSMITTER_ID_KEY @"transmitter-id" +#define PDK_TRANSMITTER_UPLOAD_URL_KEY @"upload-url" +#define PDK_TRANSMITTER_REQUIRE_CHARGING_KEY @"require-charging" +#define PDK_TRANSMITTER_REQUIRE_WIFI_KEY @"require-wifi" + +@interface PDKHttpTransmitter : NSObject + +@end diff --git a/PassiveDataKit/PDKHttpTransmitter.m b/PassiveDataKit/PDKHttpTransmitter.m new file mode 100644 index 0000000..19050e6 --- /dev/null +++ b/PassiveDataKit/PDKHttpTransmitter.m @@ -0,0 +1,340 @@ +// +// PDKHttpTransmitter.m +// PassiveDataKit +// +// Created by Chris Karr on 6/18/17. +// Copyright © 2017 Audacious Software. All rights reserved. +// + +#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, + ConnectionType3G, + ConnectionTypeWiFi +} ConnectionType; + +@interface PDKHttpTransmitter () + +@property NSString * source; +@property NSString * transmitterId; +@property NSURL * uploadUrl; +@property BOOL requireCharging; +@property BOOL requireWiFi; + +@property sqlite3 * database; + +@end + +@implementation PDKHttpTransmitter + ++ (ConnectionType) connectionType +{ + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, "8.8.8.8"); + SCNetworkReachabilityFlags flags; + BOOL success = SCNetworkReachabilityGetFlags(reachability, &flags); + CFRelease(reachability); + if (!success) { + return ConnectionTypeUnknown; + } + BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); + BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); + BOOL isNetworkReachable = (isReachable && !needsConnection); + + if (!isNetworkReachable) { + return ConnectionTypeNone; + } else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) { + return ConnectionType3G; + } else { + return ConnectionTypeWiFi; + } +} + +- (id) initWithOptions:(NSDictionary *) options { + if (self = [super init]) { + self.source = options[PDK_SOURCE_KEY]; + + self.transmitterId = options[PDK_TRANSMITTER_ID_KEY]; + + if (self.transmitterId == nil) { + self.transmitterId = PDK_DEFAULT_TRANSMITTER_ID; + } + + if (options[PDK_TRANSMITTER_UPLOAD_URL_KEY] != nil) { + self.uploadUrl = [NSURL URLWithString:options[PDK_TRANSMITTER_UPLOAD_URL_KEY]]; + } else { + self.uploadUrl = nil; + } + + if (options[PDK_TRANSMITTER_REQUIRE_CHARGING_KEY] != nil) { + self.requireCharging = [options[PDK_TRANSMITTER_REQUIRE_CHARGING_KEY] boolValue]; + } else { + self.requireCharging = NO; + } + + if (options[PDK_TRANSMITTER_REQUIRE_WIFI_KEY] != nil) { + self.requireWiFi = [options[PDK_TRANSMITTER_REQUIRE_WIFI_KEY] boolValue]; + } else { + self.requireWiFi = NO; + } + + self.database = [self openDatabase]; + + [[PassiveDataKit sharedInstance] registerListener:self forGenerator:PDKAnyGenerator]; + } + + return self; +} + +- (NSString *) databasePath +{ + NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + + NSString * cachePath = paths[0]; + + NSString * filename = [NSString stringWithFormat:@"%@-http-transmitter.sqlite3", [self.transmitterId slugalize], nil]; + + NSString * dbPath = [cachePath stringByAppendingPathComponent:filename]; + + return dbPath; +} + +- (sqlite3 *) openDatabase { + NSString * dbPath = [self databasePath]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:dbPath] == NO) + { + sqlite3 * database = NULL; + + const char * path = [dbPath UTF8String]; + + if (sqlite3_open(path, &database) == SQLITE_OK) { + char * error; + + const char * createStatement = "CREATE TABLE IF NOT EXISTS data (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp REAL, properties TEXT)"; + + if (sqlite3_exec(database, createStatement, NULL, NULL, &error) != SQLITE_OK) { //!OCLINT + + } + + sqlite3_close(database); + } + } + + const char * dbpath = [dbPath UTF8String]; + + sqlite3 * database = NULL; + + if (sqlite3_open(dbpath, &database) == SQLITE_OK) { + return database; + } + + return NULL; +} + +- (void) transmit:(BOOL) force completionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler { + if (force == NO && self.requireCharging) { + [UIDevice currentDevice].batteryMonitoringEnabled = YES; + + UIDeviceBatteryState batteryState = [UIDevice currentDevice].batteryState; + + [UIDevice currentDevice].batteryMonitoringEnabled = NO; + + if (batteryState == UIDeviceBatteryStateUnplugged || batteryState == UIDeviceBatteryStateUnknown) { + if (completionHandler != nil) { + completionHandler(UIBackgroundFetchResultNoData); + } + + return; + } + } + + if (force == NO && self.requireWiFi) { + if ([PDKHttpTransmitter connectionType] != ConnectionTypeWiFi) { + if (completionHandler != nil) { + completionHandler(UIBackgroundFetchResultNoData); + } + + return; + } + } + + [self transmitReadings:0 completionHandler:completionHandler]; +} + +- (void) transmitReadings:(NSUInteger) uploadWindow completionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler { + if (uploadWindow == 0) { + uploadWindow = 8; //!OCLINT + } + + NSTimeInterval now = [NSDate date].timeIntervalSince1970; + + sqlite3_stmt * statement = NULL; + + NSString * querySQL = @"SELECT D.id, D.properties FROM data D WHERE (D.timestamp < ?) LIMIT 16"; + + const char * query_stmt = [querySQL UTF8String]; + + if (sqlite3_prepare_v2(self.database, query_stmt, -1, &statement, NULL) == SQLITE_OK) + { + sqlite3_bind_double(statement, 1, now); + + NSMutableArray * payload = [NSMutableArray array]; + NSMutableArray * uploaded = [NSMutableArray array]; + + while (sqlite3_step(statement) == SQLITE_ROW) + { + NSInteger pointId = sqlite3_column_int(statement, 0); + NSString * jsonString = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, 1)]; + + NSError * error = nil; + + NSMutableDictionary * dataPoint = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] + options:(NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves) + error:&error]; + if (error == nil) { + [uploaded addObject:[NSNumber numberWithInteger:pointId]]; + [payload addObject:dataPoint]; + } + else { + NSLog(@"Error fetching from PDKHttpTranmitter database: %@", error); + + if (completionHandler != nil) { + completionHandler(UIBackgroundFetchResultFailed); + } + + return; + } + } + + 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]]; + + [[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]); + + 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; + } + } + + 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]; + } +} + +- (NSUInteger) pendingSize { + return -1; +} + +- (NSUInteger) transmittedSize { + return -1; +} + +- (void) receivedData:(NSDictionary *) data forGenerator:(PDKDataGenerator) dataGenerator { + NSMutableDictionary * toStore = [NSMutableDictionary dictionaryWithDictionary:data]; + + if (toStore[PDK_METADATA_KEY] == nil) { + NSMutableDictionary * metadata = [NSMutableDictionary dictionary]; + + id generator = [[PassiveDataKit sharedInstance] generatorInstance:dataGenerator]; + + metadata[PDK_GENERATOR_ID_KEY] = [generator generatorId]; + metadata[PDK_GENERATOR_KEY] = [generator fullGeneratorName]; + + if (self.source != nil) { + metadata[PDK_SOURCE_KEY] = self.source; + } else { + metadata[PDK_SOURCE_KEY] = [[PassiveDataKit sharedInstance] identifierForUser]; + } + + metadata[PDK_TIMESTAMP_KEY] = [NSNumber numberWithDouble:[NSDate date].timeIntervalSince1970]; + + toStore[PDK_METADATA_KEY] = metadata; + } + + 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]); + + 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); + } +} + +@end diff --git a/PassiveDataKit/PDKLocationGenerator.h b/PassiveDataKit/PDKLocationGenerator.h index d41a472..f97c2fb 100644 --- a/PassiveDataKit/PDKLocationGenerator.h +++ b/PassiveDataKit/PDKLocationGenerator.h @@ -11,6 +11,8 @@ #import "PassiveDataKit.h" +#import "PDKBaseGenerator.h" + extern NSString *const PDKLocationAccuracyMode; extern NSString *const PDKLocationAccuracyModeBest; extern NSString *const PDKLocationAccuracyModeRandomized; @@ -21,7 +23,7 @@ extern NSString *const PDKLocationAccuracyModeUserProvidedLatitude; extern NSString *const PDKLocationAccuracyModeUserProvidedLongitude; -@interface PDKLocationGenerator : NSObject +@interface PDKLocationGenerator : PDKBaseGenerator + (PDKLocationGenerator *) sharedInstance; diff --git a/PassiveDataKit/PDKLocationGenerator.m b/PassiveDataKit/PDKLocationGenerator.m index 6556e19..e2da633 100644 --- a/PassiveDataKit/PDKLocationGenerator.m +++ b/PassiveDataKit/PDKLocationGenerator.m @@ -14,6 +14,8 @@ #import "PDKLocationAnnotation.h" #import "PDKLocationGeneratorViewController.h" +#define GENERATOR_ID @"pdk-location" + @interface PDKLocationGenerator () @property NSMutableArray * listeners; @@ -309,7 +311,7 @@ + (UIViewController *) detailsController { return [[PDKLocationGeneratorViewController alloc] init]; } -+ (UIView *) visualizationForSize:(CGSize) size { +- (UIView *) visualizationForSize:(CGSize) size { MKMapView * mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]; mapView.showsUserLocation = NO; @@ -363,4 +365,8 @@ + (UIView *) visualizationForSize:(CGSize) size { return mapView; } +- (NSString *) generatorId { + return GENERATOR_ID; +} + @end diff --git a/PassiveDataKit/PDKMixpanelEventGenerator.m b/PassiveDataKit/PDKMixpanelEventGenerator.m index d999d69..edab3f2 100644 --- a/PassiveDataKit/PDKMixpanelEventGenerator.m +++ b/PassiveDataKit/PDKMixpanelEventGenerator.m @@ -29,13 +29,11 @@ + (id) allocWithZone:(NSZone *) zone //!OCLINT return [self sharedInstance]; } -+ (UIView *) visualizationForSize:(CGSize) size { - PDKMixpanelEventGenerator * generator = [PDKMixpanelEventGenerator sharedInstance]; - +- (UIView *) visualizationForSize:(CGSize) size { UITableView * tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]; - tableView.dataSource = generator; - tableView.delegate = generator; + tableView.dataSource = self; + tableView.delegate = self; return tableView; } @@ -103,4 +101,4 @@ + (void) logForReview:(NSDictionary *) payload { [defaults synchronize]; } -@end \ No newline at end of file +@end diff --git a/PassiveDataKit/PassiveDataKit-Shared.h b/PassiveDataKit/PassiveDataKit-Shared.h new file mode 100644 index 0000000..ab0ae03 --- /dev/null +++ b/PassiveDataKit/PassiveDataKit-Shared.h @@ -0,0 +1,12 @@ +#import + +//! Project version number for IntelliCare-Shared. +FOUNDATION_EXPORT double PassiveDataKit_SharedVersionNumber; + +//! Project version string for IntelliCare-Shared. +FOUNDATION_EXPORT const unsigned char PassiveDataKit_SharedVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +#import "PassiveDataKit.h" +#import "PDKHttpTransmitter.h" diff --git a/PassiveDataKit/PassiveDataKit.h b/PassiveDataKit/PassiveDataKit.h index 9f7e34f..0eda5c7 100644 --- a/PassiveDataKit/PassiveDataKit.h +++ b/PassiveDataKit/PassiveDataKit.h @@ -20,7 +20,6 @@ extern NSString *const PDKLocationAlwaysOn; extern NSString *const PDKLocationRequestedAccuracy; extern NSString *const PDKLocationRequestedDistance; extern NSString *const PDKLocationInstance; -extern NSString *const PDKMixpanelToken; extern NSString *const PDKGooglePlacesSpecificLocation; extern NSString *const PDKGooglePlacesFreetextQuery; @@ -31,8 +30,10 @@ extern NSString *const PDKGooglePlacesInstance; extern NSString *const PDKGooglePlacesIncludeFullDetails; typedef NS_ENUM(NSInteger, PDKDataGenerator) { + PDKAnyGenerator, PDKLocation, - PDKGooglePlaces + PDKGooglePlaces, + PDKEvents }; @protocol PDKDataListener @@ -43,43 +44,51 @@ typedef NS_ENUM(NSInteger, PDKDataGenerator) { @protocol PDKGenerator -- (void) removeListener:(id)listener; -- (void) addListener:(id)listener options:(NSDictionary *) options; - (void) updateOptions:(NSDictionary *) options; +- (NSString *) generatorId; +- (NSString *) fullGeneratorName; +- (UIView *) visualizationForSize:(CGSize) size; + +@end + +@protocol PDKTransmitter -+ (UIView *) visualizationForSize:(CGSize) size; +- (id) initWithOptions:(NSDictionary *) options; +- (NSUInteger) pendingSize; +- (NSUInteger) transmittedSize; +- (void) transmit:(BOOL) force completionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler; @end + @interface PassiveDataKit : NSObject + (PassiveDataKit *) sharedInstance; -- (BOOL) registerListener:(id) listener forGenerator:(PDKDataGenerator) dataGenerator options:(NSDictionary *) options; +- (BOOL) registerListener:(id) listener forGenerator:(PDKDataGenerator) dataGenerator; - (BOOL) unregisterListener:(id) listener forGenerator:(PDKDataGenerator) dataGenerator; + - (NSArray *) activeListeners; -- (BOOL) logDataPoint:(NSString *) generator generatorId:(NSString *) generatorId source:(NSString *) source properties:(NSDictionary *) properties; -- (void) uploadDataPoints:(NSURL *) url window:(NSTimeInterval) uploadWindow complete:(void (^)(BOOL success, int uploaded)) completed; -- (BOOL) logEvent:(NSString *) eventName properties:(NSDictionary *) properties; -- (void) setMandatoryEventLogging:(BOOL) isMandatory; + +- (void) transmit:(BOOL) force; +- (void) transmitWithCompletionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler; + +- (void) logEvent:(NSString *) eventName properties:(NSDictionary *) properties; + +- (void) receivedData:(NSDictionary *) data forGenerator:(PDKDataGenerator) dataGenerator; - (NSString *) identifierForUser; - (BOOL) setIdentifierForUser:(NSString *) newIdentifier; - (void) resetIdentifierForUser; -- (NSString *) generator; -- (BOOL) setGenerator:(NSString *) newGenerator; -- (void) resetGenerator; +- (NSString *) userAgent; -- (NSString *) generatorId; -- (BOOL) setGeneratorId:(NSString *) newIdentifier; -- (void) resetGeneratorId; - -- (BOOL) mixpanelEnabled; -- (void) enableMixpanel:(NSString *) token; -- (void) disableMixpanel; +- (id) generatorInstance:(PDKDataGenerator) generator; - (UIViewController *) dataReportController; ++ (NSString *) keyForGenerator:(PDKDataGenerator) generator; + +- (void) addTransmitter:(id) transmitter; @end diff --git a/PassiveDataKit/PassiveDataKit.m b/PassiveDataKit/PassiveDataKit.m index 74b634a..f703221 100644 --- a/PassiveDataKit/PassiveDataKit.m +++ b/PassiveDataKit/PassiveDataKit.m @@ -8,8 +8,6 @@ #import "PassiveDataKit.h" -#import "PDKDataPointsManager.h" - #import "PDKEventsGenerator.h" #import "PDKLocationGenerator.h" #import "PDKGooglePlacesGenerator.h" @@ -19,6 +17,7 @@ @interface PassiveDataKit () @property NSMutableDictionary * listeners; +@property NSMutableArray * transmitters; @end @@ -54,7 +53,6 @@ + (PassiveDataKit *) sharedInstance dispatch_once(&_singletonPredicate, ^{ sharedObject = [[super allocWithZone:nil] init]; - }); return sharedObject; @@ -70,12 +68,19 @@ - (id) init if (self = [super init]) { self.listeners = [NSMutableDictionary dictionary]; + self.transmitters = [NSMutableArray array]; } return self; } -- (BOOL) registerListener:(id) listener forGenerator:(PDKDataGenerator) dataGenerator options:(NSDictionary *) options { +- (void) addTransmitter:(id) transmitter { + if ([self.transmitters containsObject:transmitter] == NO) { + [self.transmitters addObject:transmitter]; + } +} + +- (BOOL) registerListener:(id) listener forGenerator:(PDKDataGenerator) dataGenerator { NSString * key = [PassiveDataKit keyForGenerator:dataGenerator]; NSMutableArray * dataListeners = [self.listeners valueForKey:key]; @@ -88,31 +93,11 @@ - (BOOL) registerListener:(id) listener forGenerator:(PDKDataGe if ([dataListeners containsObject:listener] == NO) { [dataListeners addObject:listener]; - - [self incrementGenerator:dataGenerator withListener:listener options:options]; - } else { - [self updateGenerator:dataGenerator withOptions:options]; } return YES; } -- (NSArray *) activeListeners { - NSMutableArray * listeners = [NSMutableArray arrayWithArray:[self.listeners allKeys]]; - - NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; - - if ([defaults valueForKey:PDKLastEventLogged] != nil) { - [listeners addObject:PDKEventGenerator]; - - if ([self mixpanelEnabled]) { - [listeners addObject:PDKMixpanelEventGenerator]; - } - } - - return listeners; -} - - (BOOL) unregisterListener:(id) listener forGenerator:(PDKDataGenerator) dataGenerator { NSString * key = [PassiveDataKit keyForGenerator:dataGenerator]; @@ -120,48 +105,30 @@ - (BOOL) unregisterListener:(id) listener forGenerator:(PDKData if (dataListeners != nil) { [dataListeners removeObject:listener]; - - [self decrementGenerator:dataGenerator withListener:listener]; } return YES; } -- (void) decrementGenerator:(PDKDataGenerator) generator withListener:(id) listener { - switch(generator) { //!OCLINT - case PDKLocation: - [[PDKLocationGenerator sharedInstance] removeListener:listener]; - break; - case PDKGooglePlaces: - [[PDKGooglePlacesGenerator sharedInstance] removeListener:listener]; - break; - } -} - -- (void) incrementGenerator:(PDKDataGenerator) generator withListener:(id) listener options:(NSDictionary *) options { - switch(generator) { //!OCLINT - case PDKLocation: - [[PDKLocationGenerator sharedInstance] addListener:listener options:options]; - break; - case PDKGooglePlaces: - [[PDKGooglePlacesGenerator sharedInstance] addListener:listener options:options]; - break; - } +- (NSArray *) activeListeners { + NSMutableArray * listeners = [NSMutableArray arrayWithArray:[self.listeners allKeys]]; + + return listeners; } -- (void) updateGenerator:(PDKDataGenerator) generator withOptions:(NSDictionary *) options { - switch(generator) { //!OCLINT - case PDKLocation: - [[PDKLocationGenerator sharedInstance] updateOptions:options]; +- (void) receivedData:(NSDictionary *) data forGenerator:(PDKDataGenerator) dataGenerator { + NSString * key = [PassiveDataKit keyForGenerator:dataGenerator]; + NSString * anyKey = [PassiveDataKit keyForGenerator:PDKAnyGenerator]; + + NSMutableArray * dataListeners = [NSMutableArray arrayWithArray:[self.listeners valueForKey:key]]; + + [dataListeners addObjectsFromArray:[self.listeners valueForKey:anyKey]]; - break; - case PDKGooglePlaces: - [[PDKGooglePlacesGenerator sharedInstance] updateOptions:options]; - break; + for (id listener in dataListeners) { + [listener receivedData:data forGenerator:dataGenerator]; } } - + (NSString *) keyForGenerator:(PDKDataGenerator) generator { switch(generator) { //!OCLINT @@ -169,34 +136,44 @@ + (NSString *) keyForGenerator:(PDKDataGenerator) generator return @"PDKLocationGenerator"; case PDKGooglePlaces: return @"PDKGooglePlacesGenerator"; + case PDKEvents: + return @"PDKEventsGenerator"; + case PDKAnyGenerator: + return @"PDKAnyGenerator"; } return @"PDKUnknownGenerator"; } -- (void) setMandatoryEventLogging:(BOOL) isMandatory { - NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; - [defaults setBool:(isMandatory == NO) forKey:PDKEventsGeneratorCanDisable]; +- (id) generatorInstance:(PDKDataGenerator) generator { + switch(generator) { //!OCLINT + case PDKLocation: + return [PDKLocationGenerator sharedInstance]; + case PDKGooglePlaces: + return [PDKGooglePlacesGenerator sharedInstance]; + case PDKEvents: + return [PDKEventsGenerator sharedInstance]; + case PDKAnyGenerator: + break; + } - [defaults synchronize]; + return nil; } - -- (BOOL) logDataPoint:(NSString *) generator generatorId:(NSString *) generatorId source:(NSString *) source properties:(NSDictionary *) properties { - return [[PDKDataPointsManager sharedInstance] logDataPoint:generator generatorId:generatorId source:source properties:properties]; +- (void) logEvent:(NSString *) eventName properties:(NSDictionary *) properties { + [[PDKEventsGenerator sharedInstance] logEvent:eventName properties:properties]; } -- (BOOL) logEvent:(NSString *) eventName properties:(NSDictionary *) properties { - NSUserDefaults * defaults= [NSUserDefaults standardUserDefaults]; - - [defaults setValue:[NSDate date] forKey:PDKLastEventLogged]; - [defaults synchronize]; - - return [[PDKDataPointsManager sharedInstance] logEvent:eventName properties:properties]; +- (void) transmit:(BOOL) force { + for (id transmitter in self.transmitters) { + [transmitter transmit:force completionHandler:nil]; + } } -- (void) uploadDataPoints:(NSURL *) url window:(NSTimeInterval) uploadWindow complete:(void (^)(BOOL success, int uploaded)) completed { - [[PDKDataPointsManager sharedInstance] uploadDataPoints:url window:uploadWindow complete:completed]; +- (void) transmitWithCompletionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler { + for (id transmitter in self.transmitters) { + [transmitter transmit:NO completionHandler:completionHandler]; + } } - (NSString *) identifierForUser { @@ -223,7 +200,7 @@ - (void) resetIdentifierForUser { [[NSUserDefaults standardUserDefaults] removeObjectForKey:PDKUserIdentifier]; } -- (NSString *) generator { +- (NSString *) userAgent { NSString * generator = [[NSUserDefaults standardUserDefaults] stringForKey:PDKGenerator]; if (generator != nil) { @@ -240,66 +217,7 @@ - (NSString *) generator { info[@"CFBundleShortVersionString"] = @"1.0"; } - NSOperatingSystemVersion osVer = [NSProcessInfo processInfo].operatingSystemVersion; - - NSString * version = [NSString stringWithFormat:@"%d.%d.%d", (int) osVer.majorVersion, (int) osVer.minorVersion, (int) osVer.patchVersion]; - - return [NSString stringWithFormat:@"%@ %@ (iOS %@)", info[@"CFBundleName"], info[@"CFBundleShortVersionString"], version, nil]; -} - -- (BOOL) setGenerator:(NSString *) newGenerator { - if (newGenerator != nil) { - [[NSUserDefaults standardUserDefaults] setValue:newGenerator forKey:PDKGenerator]; - - return YES; - } - - return NO; -} - -- (void) resetGenerator { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:PDKGenerator]; -} - -- (NSString *) generatorId { - NSString * identifier = [[NSUserDefaults standardUserDefaults] stringForKey:PDKGeneratorIdentifier]; - - if (identifier != nil) { - return identifier; - } - - if ([[NSBundle mainBundle] bundleIdentifier] != nil) { - return [[NSBundle mainBundle] bundleIdentifier]; - } - - return @"passive-data-kit"; -} - -- (BOOL) setGeneratorId:(NSString *) newIdentifier -{ - if (newIdentifier != nil) { - [[NSUserDefaults standardUserDefaults] setValue:newIdentifier forKey:PDKGeneratorIdentifier]; - - return YES; - } - - return NO; -} - -- (void) resetGeneratorId { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:PDKGeneratorIdentifier]; -} - -- (BOOL) mixpanelEnabled { - return [[NSUserDefaults standardUserDefaults] stringForKey:PDKMixpanelToken] != nil; -} - -- (void) enableMixpanel:(NSString *) token { - [[NSUserDefaults standardUserDefaults] setValue:token forKey:PDKMixpanelToken]; -} - -- (void) disableMixpanel { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:PDKMixpanelToken]; + return [NSString stringWithFormat:@"%@/%@", info[@"CFBundleName"], info[@"CFBundleShortVersionString"], nil]; } - (UIViewController *) dataReportController { diff --git a/PassiveDataKit/PassiveDataKit.modulemap b/PassiveDataKit/PassiveDataKit.modulemap new file mode 100644 index 0000000..518d947 --- /dev/null +++ b/PassiveDataKit/PassiveDataKit.modulemap @@ -0,0 +1,3 @@ +framework module PassiveDataKit { + umbrella header "PassiveDataKit-Shared.h" +} diff --git a/PassiveDataKit/Third-Party/NSString+RAInflections.h b/PassiveDataKit/Third-Party/NSString+RAInflections.h new file mode 100644 index 0000000..c1286b0 --- /dev/null +++ b/PassiveDataKit/Third-Party/NSString+RAInflections.h @@ -0,0 +1,17 @@ +// +// NSString+RAInflections.h +// PassiveDataKit +// +// Created by Chris Karr on 6/18/17. +// Copyright © 2017 Audacious Software. All rights reserved. +// + +#import + +// Original version: https://gist.github.com/RobertAudi/5926772 + +@interface NSString (RAInflections) + +- (NSString *)slugalize; + +@end diff --git a/PassiveDataKit/Third-Party/NSString+RAInflections.m b/PassiveDataKit/Third-Party/NSString+RAInflections.m new file mode 100644 index 0000000..4f6307c --- /dev/null +++ b/PassiveDataKit/Third-Party/NSString+RAInflections.m @@ -0,0 +1,62 @@ +// +// NSString+RAInflections.m +// PassiveDataKit +// +// Created by Chris Karr on 6/18/17. +// Copyright © 2017 Audacious Software. All rights reserved. +// + +#import "NSString+RAInflections.h" + +// Original version: https://gist.github.com/RobertAudi/5926772 + +@implementation NSString (RAInflections) + +/** + * Port of the slugalized helper created by @henrik + * https://github.com/RobertAudi/slugalizer + */ +- (NSString *)slugalize +{ + NSString *separator = @"-"; + NSMutableString *slugalizedString = [NSMutableString string]; + NSRange replaceRange = NSMakeRange(0, self.length); + + // Remove all non ASCII characters + NSError *nonASCIICharsRegexError = nil; + NSRegularExpression *nonASCIICharsRegex = [NSRegularExpression regularExpressionWithPattern:@"[^\\x00-\\x7F]+" + options:0 + error:&nonASCIICharsRegexError]; + slugalizedString = [[nonASCIICharsRegex stringByReplacingMatchesInString:self + options:0 + range:replaceRange + withTemplate:@""] mutableCopy]; + + // Turn non-slug characters into separators + NSError *nonSlugCharactersError = nil; + NSRegularExpression *nonSlugCharactersRegex = [NSRegularExpression regularExpressionWithPattern:@"[^a-z0-9\\-_\\+]+" + options:NSRegularExpressionCaseInsensitive + error:&nonSlugCharactersError]; + slugalizedString = [[nonSlugCharactersRegex stringByReplacingMatchesInString:slugalizedString + options:0 + range:replaceRange + withTemplate:separator] mutableCopy]; + + // No more than one of the separator in a row + NSError *repeatingSeparatorsError = nil; + NSRegularExpression *repeatingSeparatorsRegex = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"%@{2,}", separator] + options:0 + error:&repeatingSeparatorsError]; + + slugalizedString = [[repeatingSeparatorsRegex stringByReplacingMatchesInString:slugalizedString + options:0 + range:replaceRange + withTemplate:separator] mutableCopy]; + + // Remove leading/trailing separator + slugalizedString = [[slugalizedString stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:separator]] mutableCopy]; + + return [slugalizedString lowercaseString]; +} + +@end