From 698f37569d05d1605befe1a47c2b37fcea721e21 Mon Sep 17 00:00:00 2001 From: Andrea Prearo Date: Mon, 22 May 2017 13:10:52 -0700 Subject: [PATCH 1/6] Migrate CLUReportComposer to Swift Changes: - Migrate CLUReportComposer to Swift as ReportComposer. - Swap CLUReportComposer with ReportComposer. --- Clue.xcodeproj/project.pbxproj | 16 +-- Clue/Classes/Composers/CLUReportComposer.h | 73 ------------ Clue/Classes/Composers/CLUReportComposer.m | 115 ------------------ Clue/Classes/Composers/ReportComposer.swift | 124 ++++++++++++++++++++ Clue/Clue.h | 1 + Clue/ClueController.m | 5 +- 6 files changed, 133 insertions(+), 201 deletions(-) delete mode 100644 Clue/Classes/Composers/CLUReportComposer.h delete mode 100644 Clue/Classes/Composers/CLUReportComposer.m create mode 100644 Clue/Classes/Composers/ReportComposer.swift diff --git a/Clue.xcodeproj/project.pbxproj b/Clue.xcodeproj/project.pbxproj index aec945d..ad56566 100644 --- a/Clue.xcodeproj/project.pbxproj +++ b/Clue.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ AF2B52641EBF9CC30026594F /* NetworkModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF2B52631EBF9CC30026594F /* NetworkModule.swift */; }; AF34EE2A1ED347C5005E05FC /* VideoWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE291ED347C5005E05FC /* VideoWriterTests.swift */; }; AF34EE2C1ED354F9005E05FC /* VideoModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE2B1ED354F9005E05FC /* VideoModule.swift */; }; + AF34EE2E1ED37174005E05FC /* ReportComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE2D1ED37174005E05FC /* ReportComposer.swift */; }; AF422CBB1EC2166C0028AA33 /* ObserveModuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF422CBA1EC2166C0028AA33 /* ObserveModuleTests.swift */; }; AF4B1EB11ED301160024AB1C /* VideoWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4B1EB01ED301160024AB1C /* VideoWriter.swift */; }; AF4FB5D41EBACCB500FB9F0B /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4FB5D31EBACCB500FB9F0B /* Result.swift */; }; @@ -30,7 +31,7 @@ AFBD5DB41EC09C0D00858442 /* UserInteractionModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFBD5DB31EC09C0D00858442 /* UserInteractionModule.swift */; }; AFD9F7121EBEC79E001EE2A7 /* ObserveModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFD9F7111EBEC79E001EE2A7 /* ObserveModule.swift */; }; E20F23401D30F94700654690 /* CLUInfoModule.h in Headers */ = {isa = PBXBuildFile; fileRef = E20F233F1D30F94700654690 /* CLUInfoModule.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E20F23481D31947300654690 /* CLUReportFileManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E20F23461D31947200654690 /* CLUReportFileManager.h */; }; + E20F23481D31947300654690 /* CLUReportFileManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E20F23461D31947200654690 /* CLUReportFileManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; E20F23491D31947300654690 /* CLUReportFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E20F23471D31947200654690 /* CLUReportFileManager.m */; }; E215639A1D25A15A005EDF61 /* NSException+CLUExceptionAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = E21563981D25A15A005EDF61 /* NSException+CLUExceptionAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; E215639B1D25A15A005EDF61 /* NSException+CLUExceptionAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = E21563991D25A15A005EDF61 /* NSException+CLUExceptionAdditions.m */; }; @@ -83,8 +84,6 @@ E28610131CEFB9C300FA9BAF /* Karla-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E286100F1CEFB9C300FA9BAF /* Karla-BoldItalic.ttf */; }; E28610141CEFB9C300FA9BAF /* Karla-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E28610101CEFB9C300FA9BAF /* Karla-Italic.ttf */; }; E28610151CEFB9C300FA9BAF /* Karla-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E28610111CEFB9C300FA9BAF /* Karla-Regular.ttf */; }; - E28CC08D1CEE214B00C9DFD9 /* CLUReportComposer.h in Headers */ = {isa = PBXBuildFile; fileRef = E28CC08B1CEE214B00C9DFD9 /* CLUReportComposer.h */; }; - E28CC08E1CEE214B00C9DFD9 /* CLUReportComposer.m in Sources */ = {isa = PBXBuildFile; fileRef = E28CC08C1CEE214B00C9DFD9 /* CLUReportComposer.m */; }; E28CC0951CEE619000C9DFD9 /* ClueController.h in Headers */ = {isa = PBXBuildFile; fileRef = E28CC0931CEE619000C9DFD9 /* ClueController.h */; settings = {ATTRIBUTES = (Public, ); }; }; E28CC0961CEE619000C9DFD9 /* ClueController.m in Sources */ = {isa = PBXBuildFile; fileRef = E28CC0941CEE619000C9DFD9 /* ClueController.m */; }; E291DEAA1D14A04F009AB23F /* CLUURLProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = E291DEA81D14A04F009AB23F /* CLUURLProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -203,6 +202,7 @@ AF2B52631EBF9CC30026594F /* NetworkModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkModule.swift; sourceTree = ""; }; AF34EE291ED347C5005E05FC /* VideoWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoWriterTests.swift; sourceTree = ""; }; AF34EE2B1ED354F9005E05FC /* VideoModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoModule.swift; sourceTree = ""; }; + AF34EE2D1ED37174005E05FC /* ReportComposer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportComposer.swift; sourceTree = ""; }; AF422CBA1EC2166C0028AA33 /* ObserveModuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserveModuleTests.swift; sourceTree = ""; }; AF4B1EB01ED301160024AB1C /* VideoWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoWriter.swift; sourceTree = ""; }; AF4FB5D31EBACCB500FB9F0B /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Utils/Result.swift; sourceTree = ""; }; @@ -279,8 +279,6 @@ E286100F1CEFB9C300FA9BAF /* Karla-BoldItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Karla-BoldItalic.ttf"; sourceTree = ""; }; E28610101CEFB9C300FA9BAF /* Karla-Italic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Karla-Italic.ttf"; sourceTree = ""; }; E28610111CEFB9C300FA9BAF /* Karla-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Karla-Regular.ttf"; sourceTree = ""; }; - E28CC08B1CEE214B00C9DFD9 /* CLUReportComposer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUReportComposer.h; sourceTree = ""; }; - E28CC08C1CEE214B00C9DFD9 /* CLUReportComposer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLUReportComposer.m; sourceTree = ""; }; E28CC0931CEE619000C9DFD9 /* ClueController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ClueController.h; path = ../ClueController.h; sourceTree = ""; }; E28CC0941CEE619000C9DFD9 /* ClueController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ClueController.m; path = ../ClueController.m; sourceTree = ""; }; E291DEA81D14A04F009AB23F /* CLUURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUURLProtocol.h; sourceTree = ""; }; @@ -610,8 +608,7 @@ E28CC08A1CEE210D00C9DFD9 /* Composers */ = { isa = PBXGroup; children = ( - E28CC08B1CEE214B00C9DFD9 /* CLUReportComposer.h */, - E28CC08C1CEE214B00C9DFD9 /* CLUReportComposer.m */, + AF34EE2D1ED37174005E05FC /* ReportComposer.swift */, ); path = Composers; sourceTree = ""; @@ -798,6 +795,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E20F23481D31947300654690 /* CLUReportFileManager.h in Headers */, E215639A1D25A15A005EDF61 /* NSException+CLUExceptionAdditions.h in Headers */, E20F23401D30F94700654690 /* CLUInfoModule.h in Headers */, E22F60EF1D265DBE0062D6B8 /* CLUTouch.h in Headers */, @@ -823,7 +821,6 @@ E2CDA6A51D3905D60079F784 /* prng.h in Headers */, E24AEE021D06E9DC001F31FF /* UITouch+CLUUserInteractionAdditions.h in Headers */, E28CC0951CEE619000C9DFD9 /* ClueController.h in Headers */, - E20F23481D31947300654690 /* CLUReportFileManager.h in Headers */, E2CDA6B71D3905D60079F784 /* ZipArchive.h in Headers */, E2CDA6A71D3905D60079F784 /* pwd2key.h in Headers */, E2CDA6A91D3905D60079F784 /* sha1.h in Headers */, @@ -834,7 +831,6 @@ E2CDA6AA1D3905D60079F784 /* Common.h in Headers */, E2CDA6B51D3905D60079F784 /* SSZipArchive.h in Headers */, E2CDA6951D3905D60079F784 /* aes.h in Headers */, - E28CC08D1CEE214B00C9DFD9 /* CLUReportComposer.h in Headers */, E2CDA69B1D3905D60079F784 /* aestab.h in Headers */, E2CDA69C1D3905D60079F784 /* brg_endian.h in Headers */, E29233D51D358B6300DA88C2 /* CLUMailHelper.h in Headers */, @@ -1036,6 +1032,7 @@ AF5314621EB018E8005A5146 /* JSONWriter.swift in Sources */, E232BA931CF830DC00F5DB52 /* UILabel+CLUViewRecordableAdditions.m in Sources */, AF2B52641EBF9CC30026594F /* NetworkModule.swift in Sources */, + AF34EE2E1ED37174005E05FC /* ReportComposer.swift in Sources */, AF5314631EB018E8005A5146 /* DataWriterError.swift in Sources */, E2CDA6981D3905D60079F784 /* aeskey.c in Sources */, E2CDA6B31D3905D60079F784 /* zip.c in Sources */, @@ -1047,7 +1044,6 @@ E24E6CCE1EAA11C100A2F0C1 /* NSMutableDictionary+CLUUtilsAdditions.m in Sources */, E2CDA6A61D3905D60079F784 /* pwd2key.c in Sources */, E20F23491D31947300654690 /* CLUReportFileManager.m in Sources */, - E28CC08E1CEE214B00C9DFD9 /* CLUReportComposer.m in Sources */, E2CDA6AD1D3905D60079F784 /* ioapi.c in Sources */, E291DEAB1D14A04F009AB23F /* CLUURLProtocol.m in Sources */, E2CDA69A1D3905D60079F784 /* aestab.c in Sources */, diff --git a/Clue/Classes/Composers/CLUReportComposer.h b/Clue/Classes/Composers/CLUReportComposer.h deleted file mode 100644 index 6e00afb..0000000 --- a/Clue/Classes/Composers/CLUReportComposer.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// CLUReportComposer.h -// Clue -// -// Created by Ahmed Sulaiman on 5/19/16. -// Copyright © 2016 Ahmed Sulaiman. All rights reserved. -// - -#import -#import -#import "CLURecordableModule.h" -#import "CLUInfoModule.h" - -/** - `CLUReportComposer` is a class responsible for composing final Clue report from many pieces/modules. This class initialize all recordable and info modules and actually start recording. `CLUReportComposer` also calling `addNewFrameWithTimestamp:` method from `CLURecordableModule` protocol for every recordable module and `recordInfoData` method from `CLUInfoModule` protocol for every info module. - */ -@interface CLUReportComposer : NSObject - -/** - All info modules which will record their data only once during recording - */ -@property (nonatomic, readonly) NSMutableArray> *infoModules; - -/** - All recordable modules which will record their data during recording with specific timestamp for each new data entity - */ -@property (nonatomic, readonly) NSMutableArray> *recordableModules; - -/** - BOOL property which is indicate whether record has started or not - */ -@property (nonatomic, readonly) BOOL isRecording; - -/** - Initialize new `CLUReportComposer` instance with recordable modules array. So you can do recording even without info modules, but you have to have recordable modules for report recording - - @param modulesArray Recordable modules array which will record their data during recording with specific timestamp for each new data entity - @return New `CLUReportComposer` instance - */ -- (instancetype)initWithModulesArray:(NSArray> *)modulesArray; - -/** - Add more recordable modules to `CLUReportComposer.recordableModules` array - - @param module Array with recordable modules which will be added to `CLUReportComposer.recordableModules` - */ -- (void)addRecordableModule:(id )module; - -/** - Remove single recordable module from `CLUReportComposer.recordableModules` array - - @param module Single recordable module which will be removed from `CLUReportComposer.recordableModules` - */ -- (void)removeRecordableModule:(id )module; - -/** - Set info modules array to be used once during recording. Needs to be setup before actual recording. - - @param infoModules Info modules array which will record their data only once during recording - */ -- (void)setInfoModules:(NSMutableArray> *)infoModules; - -/** - Start actual recording - */ -- (void)startRecording; - -/** - Stop actual recording - */ -- (void)stopRecording; - -@end diff --git a/Clue/Classes/Composers/CLUReportComposer.m b/Clue/Classes/Composers/CLUReportComposer.m deleted file mode 100644 index 03b1cc2..0000000 --- a/Clue/Classes/Composers/CLUReportComposer.m +++ /dev/null @@ -1,115 +0,0 @@ -// -// CLUReportComposer.m -// Clue -// -// Created by Ahmed Sulaiman on 5/19/16. -// Copyright © 2016 Ahmed Sulaiman. All rights reserved. -// - -#import "CLUReportComposer.h" -#import "CLUReportFileManager.h" - -@interface CLUReportComposer() - -@property (nonatomic) CADisplayLink *displayLink; -@property (nonatomic) CFTimeInterval firstTimestemp; - -@end - -@implementation CLUReportComposer { - dispatch_queue_t _mainRecordQueue; - dispatch_queue_t _moduleRecordQueue; - dispatch_semaphore_t _recordSemaphore; -} - -- (instancetype)initWithModulesArray:(NSArray> *)modulesArray { - self = [super init]; - if (!self || !modulesArray) { - return nil; - } - - _isRecording = NO; - _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onScreenUpdate)]; - _recordableModules = [[NSMutableArray alloc] initWithArray:modulesArray]; - - _moduleRecordQueue = dispatch_queue_create("CLUReportComposer.module_record_queue", DISPATCH_QUEUE_SERIAL); - _mainRecordQueue = dispatch_queue_create("CLUReportComposer.main_record_queue", DISPATCH_QUEUE_SERIAL); - dispatch_set_target_queue(_mainRecordQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); - _recordSemaphore = dispatch_semaphore_create(1); - - return self; -} - -- (instancetype)init { - _recordableModules = [[NSMutableArray alloc] init]; - self = [self initWithModulesArray:_recordableModules]; - return self; -} - -- (void)addRecordableModule:(id )module { - if (module && !_isRecording) { - [_recordableModules addObject:module]; - } -} - -- (void)removeRecordableModule:(id )module { - if (module && !_isRecording) { - [_recordableModules removeObject:module]; - } -} - -- (void)setInfoModules:(NSMutableArray> *)infoModules { - if (infoModules && !_isRecording) { - _infoModules = infoModules; - } -} - -- (void)startRecording { - if (!_isRecording) { - _isRecording = YES; - [[CLUReportFileManager sharedManager] createReportFile]; - - if (_infoModules) { - for (id module in _infoModules) { - [module recordInfoData]; - } - } - - for (id module in _recordableModules) { - [module startRecording]; - } - - [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; - } -} - -- (void)stopRecording { - if (_isRecording) { - _isRecording = NO; - for (id module in _recordableModules) { - [module stopRecording]; - } - [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; - } -} - -- (void)onScreenUpdate { - if (dispatch_semaphore_wait(_recordSemaphore, DISPATCH_TIME_NOW) != 0) { - return; - } - dispatch_async(_mainRecordQueue, ^{ - for (id module in _recordableModules) { - dispatch_async(_moduleRecordQueue, ^{ - CFTimeInterval timestamp = _displayLink.timestamp; - if (!_firstTimestemp) { - _firstTimestemp = timestamp; - } - CFTimeInterval elapsedTimeInterval = timestamp - _firstTimestemp; - [module addNewFrameWithTimestamp:elapsedTimeInterval]; - }); - } - dispatch_semaphore_signal(_recordSemaphore); - }); -} - -@end diff --git a/Clue/Classes/Composers/ReportComposer.swift b/Clue/Classes/Composers/ReportComposer.swift new file mode 100644 index 0000000..1315f49 --- /dev/null +++ b/Clue/Classes/Composers/ReportComposer.swift @@ -0,0 +1,124 @@ +// +// ReportComposer.swift +// Clue +// +// Created by Prearo, Andrea on 5/22/17. +// Copyright © 2017 Ahmed Sulaiman. All rights reserved. +// + +import Foundation + +public class ReportComposer: NSObject { + // MARK: - Private Properties + fileprivate var recordableModules: [CLURecordableModule]? + fileprivate var infoModules: [CLUInfoModule]? + fileprivate var mainRecordQueue: DispatchQueue? + fileprivate var moduleRecordQueue: DispatchQueue? + fileprivate var recordSemaphore: DispatchSemaphore? + fileprivate var displayLink: CADisplayLink? + fileprivate var firstTimestamp: CFTimeInterval? + + fileprivate(set) public var isRecording = false + + /// Initializes a new `ReportComposer` instance with recordable modules array. + /// Recording works even without info modules. But having at least one recordable module is required. + /// + /// - Parameter modules: The recordable modules array. Each items of the array will record its own data, + /// during the recording, and add the specific timestamp for each new entry. + public init(with modules: [CLURecordableModule]) { + super.init() + isRecording = false + displayLink = CADisplayLink.init(target: self, selector: #selector(onScreenUpdate)) + recordableModules = modules + + moduleRecordQueue = DispatchQueue(label: "ReportComposer.moduleRecordQueue") + mainRecordQueue = DispatchQueue(label: "ReportComposer.mainRecordQueue", + qos: .default, attributes: .concurrent, + autoreleaseFrequency: .inherit, + target: DispatchQueue.global()) + recordSemaphore = DispatchSemaphore(value: 1) + } + + /// Initializes a new `ReportComposer` instance with recordable modules array. + public convenience override init() { + self.init(with: []) + } + + /// Sets the info modules array to be used once during recording. + /// Needs to be set up before startic the actual recording. + /// + /// - Parameter infoModules: <#infoModules description#> + public func setInfoModules(_ infoModules: [CLUInfoModule]) { + if !isRecording { + self.infoModules = infoModules + } + } + + /// Starts the recording for each module (CLURecordableModule and CLUInfoModule). + public func startRecording() { + if !isRecording { + isRecording = true + CLUReportFileManager.shared().createReportFile() + + if let infoModules = infoModules { + for module in infoModules { + module.recordInfoData() + } + } + + if let recordableModules = recordableModules { + for module in recordableModules { + module.startRecording() + } + } + + displayLink?.add(to: RunLoop.main, forMode: .commonModes) + } + } + + /// Stops the recording. + public func stopRecording() { + if isRecording { + isRecording = false + + if let recordableModules = recordableModules { + for module in recordableModules { + module.stopRecording() + } + } + + displayLink?.remove(from: RunLoop.main, forMode: .commonModes) + } + } +} + +// MARK: - Private Methods +fileprivate extension ReportComposer { + @objc func onScreenUpdate() { + if recordSemaphore?.wait(timeout: DispatchTime.now()) != .success { + return + } + + mainRecordQueue?.async { [weak self] in + guard let strongSelf = self else { + return + } + if let recordableModules = strongSelf.recordableModules { + for module in recordableModules { + strongSelf.moduleRecordQueue?.async { + if strongSelf.firstTimestamp == nil { + strongSelf.firstTimestamp = strongSelf.displayLink?.timestamp + } + if let timestamp = strongSelf.displayLink?.timestamp, + let firstTimestamp = strongSelf.firstTimestamp { + let elapsedTimeInterval = timestamp - firstTimestamp + module.addNewFrame(withTimestamp: elapsedTimeInterval) + } + } + } + } + + strongSelf.recordSemaphore?.signal() + } + } +} diff --git a/Clue/Clue.h b/Clue/Clue.h index d7bded4..1836f8d 100644 --- a/Clue/Clue.h +++ b/Clue/Clue.h @@ -33,3 +33,4 @@ FOUNDATION_EXPORT const unsigned char ClueVersionString[]; #import "CLUTouch.h" #import "CLUInfoModule.h" #import "NSException+CLUExceptionAdditions.h" +#import "CLUReportFileManager.h" diff --git a/Clue/ClueController.m b/Clue/ClueController.m index bd072fc..3994d0d 100644 --- a/Clue/ClueController.m +++ b/Clue/ClueController.m @@ -7,7 +7,6 @@ // #import "ClueController.h" -#import "CLUReportComposer.h" #import "CLUReportFileManager.h" #import "CLUMailHelper.h" @@ -21,7 +20,7 @@ @interface ClueController() @property (nonatomic) BOOL isEnabled; @property (nonatomic) BOOL isRecording; @property (nonatomic) CLUOptions *options; -@property (nonatomic) CLUReportComposer *reportComposer; +@property (nonatomic) ReportComposer *reportComposer; @property (nonatomic) CLUMailDelegate *mailDelegate; @end @@ -38,7 +37,7 @@ - (instancetype)init { _isEnabled = NO; NSMutableArray *modulesArray = [self configureRecordableModules]; NSMutableArray *infoModulesArray = [self configureInfoModules]; - _reportComposer = [[CLUReportComposer alloc] initWithModulesArray:modulesArray]; + _reportComposer = [[ReportComposer alloc] initWith:modulesArray]; [_reportComposer setInfoModules:infoModulesArray]; NSSetUncaughtExceptionHandler(&didReceiveUncaughtException); _waitVideoRenderingQueue = dispatch_queue_create("ClueController.waitVideoRenderingQueue", DISPATCH_QUEUE_SERIAL); From 5bf956eb887590fe7731f778749d3868dc25414f Mon Sep 17 00:00:00 2001 From: Andrea Prearo Date: Mon, 22 May 2017 13:59:25 -0700 Subject: [PATCH 2/6] Migrate writers related protocols to Swift Changes: - Migrate CLUInfoModule to Swift as InfoModule. - Migrate CLURecordableModule to Swift as RecordableModule. - Migrate CLUWritable to Swift as Writable. --- Clue.xcodeproj/project.pbxproj | 24 +++++----- Clue/Classes/Protocols/CLUInfoModule.h | 34 -------------- Clue/Classes/Protocols/CLURecordableModule.h | 46 ------------------- Clue/Classes/Protocols/CLUWritable.h | 35 -------------- Clue/Classes/Protocols/InfoModule.swift | 27 +++++++++++ Clue/Classes/Protocols/RecordableModule.swift | 42 +++++++++++++++++ Clue/Classes/Protocols/Writable.swift | 23 ++++++++++ Clue/Clue.h | 3 -- 8 files changed, 104 insertions(+), 130 deletions(-) delete mode 100644 Clue/Classes/Protocols/CLUInfoModule.h delete mode 100644 Clue/Classes/Protocols/CLURecordableModule.h delete mode 100644 Clue/Classes/Protocols/CLUWritable.h create mode 100644 Clue/Classes/Protocols/InfoModule.swift create mode 100644 Clue/Classes/Protocols/RecordableModule.swift create mode 100644 Clue/Classes/Protocols/Writable.swift diff --git a/Clue.xcodeproj/project.pbxproj b/Clue.xcodeproj/project.pbxproj index ad56566..c939e52 100644 --- a/Clue.xcodeproj/project.pbxproj +++ b/Clue.xcodeproj/project.pbxproj @@ -13,6 +13,9 @@ AF34EE2A1ED347C5005E05FC /* VideoWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE291ED347C5005E05FC /* VideoWriterTests.swift */; }; AF34EE2C1ED354F9005E05FC /* VideoModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE2B1ED354F9005E05FC /* VideoModule.swift */; }; AF34EE2E1ED37174005E05FC /* ReportComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE2D1ED37174005E05FC /* ReportComposer.swift */; }; + AF34EE301ED37FEC005E05FC /* InfoModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE2F1ED37FEC005E05FC /* InfoModule.swift */; }; + AF34EE321ED38264005E05FC /* RecordableModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE311ED38264005E05FC /* RecordableModule.swift */; }; + AF34EE341ED38682005E05FC /* Writable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE331ED38682005E05FC /* Writable.swift */; }; AF422CBB1EC2166C0028AA33 /* ObserveModuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF422CBA1EC2166C0028AA33 /* ObserveModuleTests.swift */; }; AF4B1EB11ED301160024AB1C /* VideoWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4B1EB01ED301160024AB1C /* VideoWriter.swift */; }; AF4FB5D41EBACCB500FB9F0B /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4FB5D31EBACCB500FB9F0B /* Result.swift */; }; @@ -30,7 +33,6 @@ AFBD5DB21EC0947300858442 /* NSMutableDictionary+CLUUtilsAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = E24E6CCB1EAA11C100A2F0C1 /* NSMutableDictionary+CLUUtilsAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; AFBD5DB41EC09C0D00858442 /* UserInteractionModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFBD5DB31EC09C0D00858442 /* UserInteractionModule.swift */; }; AFD9F7121EBEC79E001EE2A7 /* ObserveModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFD9F7111EBEC79E001EE2A7 /* ObserveModule.swift */; }; - E20F23401D30F94700654690 /* CLUInfoModule.h in Headers */ = {isa = PBXBuildFile; fileRef = E20F233F1D30F94700654690 /* CLUInfoModule.h */; settings = {ATTRIBUTES = (Public, ); }; }; E20F23481D31947300654690 /* CLUReportFileManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E20F23461D31947200654690 /* CLUReportFileManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; E20F23491D31947300654690 /* CLUReportFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E20F23471D31947200654690 /* CLUReportFileManager.m */; }; E215639A1D25A15A005EDF61 /* NSException+CLUExceptionAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = E21563981D25A15A005EDF61 /* NSException+CLUExceptionAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -91,8 +93,6 @@ E291DEAD1D14A741009AB23F /* CLUNetworkObserverDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E291DEAC1D14A741009AB23F /* CLUNetworkObserverDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; E29233D51D358B6300DA88C2 /* CLUMailHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = E29233D31D358B6300DA88C2 /* CLUMailHelper.h */; }; E29233D61D358B6300DA88C2 /* CLUMailHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = E29233D41D358B6300DA88C2 /* CLUMailHelper.m */; }; - E2A30C871CED01CB00A48C61 /* CLURecordableModule.h in Headers */ = {isa = PBXBuildFile; fileRef = E2A30C861CED01CB00A48C61 /* CLURecordableModule.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E2A30C8D1CED08DA00A48C61 /* CLUWritable.h in Headers */ = {isa = PBXBuildFile; fileRef = E2A30C8C1CED08DA00A48C61 /* CLUWritable.h */; settings = {ATTRIBUTES = (Public, ); }; }; E2ABF6471D424531006646B3 /* CLURecordIndicatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = E2ABF6451D424531006646B3 /* CLURecordIndicatorView.h */; }; E2ABF6481D424531006646B3 /* CLURecordIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = E2ABF6461D424531006646B3 /* CLURecordIndicatorView.m */; }; E2ABF64B1D425108006646B3 /* CLURecordIndicatorViewManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E2ABF6491D425108006646B3 /* CLURecordIndicatorViewManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -203,6 +203,9 @@ AF34EE291ED347C5005E05FC /* VideoWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoWriterTests.swift; sourceTree = ""; }; AF34EE2B1ED354F9005E05FC /* VideoModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoModule.swift; sourceTree = ""; }; AF34EE2D1ED37174005E05FC /* ReportComposer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportComposer.swift; sourceTree = ""; }; + AF34EE2F1ED37FEC005E05FC /* InfoModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoModule.swift; sourceTree = ""; }; + AF34EE311ED38264005E05FC /* RecordableModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordableModule.swift; sourceTree = ""; }; + AF34EE331ED38682005E05FC /* Writable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Writable.swift; sourceTree = ""; }; AF422CBA1EC2166C0028AA33 /* ObserveModuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserveModuleTests.swift; sourceTree = ""; }; AF4B1EB01ED301160024AB1C /* VideoWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoWriter.swift; sourceTree = ""; }; AF4FB5D31EBACCB500FB9F0B /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Utils/Result.swift; sourceTree = ""; }; @@ -217,7 +220,6 @@ AF7879DE1EC516E500938263 /* UIDevice+Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Util.swift"; sourceTree = ""; }; AFBD5DB31EC09C0D00858442 /* UserInteractionModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInteractionModule.swift; sourceTree = ""; }; AFD9F7111EBEC79E001EE2A7 /* ObserveModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserveModule.swift; sourceTree = ""; }; - E20F233F1D30F94700654690 /* CLUInfoModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUInfoModule.h; sourceTree = ""; }; E20F23461D31947200654690 /* CLUReportFileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUReportFileManager.h; sourceTree = ""; }; E20F23471D31947200654690 /* CLUReportFileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLUReportFileManager.m; sourceTree = ""; }; E21563981D25A15A005EDF61 /* NSException+CLUExceptionAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSException+CLUExceptionAdditions.h"; sourceTree = ""; }; @@ -286,8 +288,6 @@ E291DEAC1D14A741009AB23F /* CLUNetworkObserverDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUNetworkObserverDelegate.h; sourceTree = ""; }; E29233D31D358B6300DA88C2 /* CLUMailHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUMailHelper.h; sourceTree = ""; }; E29233D41D358B6300DA88C2 /* CLUMailHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLUMailHelper.m; sourceTree = ""; }; - E2A30C861CED01CB00A48C61 /* CLURecordableModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLURecordableModule.h; sourceTree = ""; }; - E2A30C8C1CED08DA00A48C61 /* CLUWritable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUWritable.h; sourceTree = ""; }; E2ABF6451D424531006646B3 /* CLURecordIndicatorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLURecordIndicatorView.h; sourceTree = ""; }; E2ABF6461D424531006646B3 /* CLURecordIndicatorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLURecordIndicatorView.m; sourceTree = ""; }; E2ABF6491D425108006646B3 /* CLURecordIndicatorViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLURecordIndicatorViewManager.h; sourceTree = ""; }; @@ -655,12 +655,12 @@ E2A30C851CED017F00A48C61 /* Protocols */ = { isa = PBXGroup; children = ( - E2A30C861CED01CB00A48C61 /* CLURecordableModule.h */, - E2A30C8C1CED08DA00A48C61 /* CLUWritable.h */, E232BA891CF8214D00F5DB52 /* CLUViewRecordableProperties.h */, E24AEDF91D06DA18001F31FF /* CLUInteractionObserverDelegate.h */, E291DEAC1D14A741009AB23F /* CLUNetworkObserverDelegate.h */, - E20F233F1D30F94700654690 /* CLUInfoModule.h */, + AF34EE2F1ED37FEC005E05FC /* InfoModule.swift */, + AF34EE311ED38264005E05FC /* RecordableModule.swift */, + AF34EE331ED38682005E05FC /* Writable.swift */, ); path = Protocols; sourceTree = ""; @@ -797,7 +797,6 @@ files = ( E20F23481D31947300654690 /* CLUReportFileManager.h in Headers */, E215639A1D25A15A005EDF61 /* NSException+CLUExceptionAdditions.h in Headers */, - E20F23401D30F94700654690 /* CLUInfoModule.h in Headers */, E22F60EF1D265DBE0062D6B8 /* CLUTouch.h in Headers */, E24AEDFE1D06DD28001F31FF /* CLUGeneralGestureRecognizer.h in Headers */, E24AEDFA1D06DA18001F31FF /* CLUInteractionObserverDelegate.h in Headers */, @@ -810,11 +809,9 @@ E291DEAD1D14A741009AB23F /* CLUNetworkObserverDelegate.h in Headers */, E232BA8A1CF8214D00F5DB52 /* CLUViewRecordableProperties.h in Headers */, E232BA8E1CF821D400F5DB52 /* UIView+CLUViewRecordableAdditions.h in Headers */, - E2A30C871CED01CB00A48C61 /* CLURecordableModule.h in Headers */, E2ABF64B1D425108006646B3 /* CLURecordIndicatorViewManager.h in Headers */, E232BA921CF830DC00F5DB52 /* UILabel+CLUViewRecordableAdditions.h in Headers */, E2CDA69F1D3905D60079F784 /* entropy.h in Headers */, - E2A30C8D1CED08DA00A48C61 /* CLUWritable.h in Headers */, E2CDA6B01D3905D60079F784 /* mztools.h in Headers */, E272DD861CD5223300F1FECA /* Clue.h in Headers */, E2EBC7F71D3AE26D00E52D5D /* CLUMailDelegate.h in Headers */, @@ -1025,6 +1022,7 @@ E215639B1D25A15A005EDF61 /* NSException+CLUExceptionAdditions.m in Sources */, E2CDA6A41D3905D60079F784 /* prng.c in Sources */, AF4FB5D41EBACCB500FB9F0B /* Result.swift in Sources */, + AF34EE341ED38682005E05FC /* Writable.swift in Sources */, E2CDA6B11D3905D60079F784 /* unzip.c in Sources */, E23891481D1BDF9300D688CD /* NSURLRequest+CLUNetworkAdditions.m in Sources */, E2CDA6A81D3905D60079F784 /* sha1.c in Sources */, @@ -1033,6 +1031,7 @@ E232BA931CF830DC00F5DB52 /* UILabel+CLUViewRecordableAdditions.m in Sources */, AF2B52641EBF9CC30026594F /* NetworkModule.swift in Sources */, AF34EE2E1ED37174005E05FC /* ReportComposer.swift in Sources */, + AF34EE321ED38264005E05FC /* RecordableModule.swift in Sources */, AF5314631EB018E8005A5146 /* DataWriterError.swift in Sources */, E2CDA6981D3905D60079F784 /* aeskey.c in Sources */, E2CDA6B31D3905D60079F784 /* zip.c in Sources */, @@ -1062,6 +1061,7 @@ E2ABF64C1D425108006646B3 /* CLURecordIndicatorViewManager.m in Sources */, E21840D51CFCD7F80053422C /* UIImageView+CLUViewRecordableAdditions.m in Sources */, E21840D91CFCDA4B0053422C /* UITextField+CLUViewRecordableAdditions.m in Sources */, + AF34EE301ED37FEC005E05FC /* InfoModule.swift in Sources */, E22F60F01D265DBE0062D6B8 /* CLUTouch.m in Sources */, E258F4001CE3D037001E9F92 /* CLUOptions.m in Sources */, E24AEE031D06E9DC001F31FF /* UITouch+CLUUserInteractionAdditions.m in Sources */, diff --git a/Clue/Classes/Protocols/CLUInfoModule.h b/Clue/Classes/Protocols/CLUInfoModule.h deleted file mode 100644 index dfa11b7..0000000 --- a/Clue/Classes/Protocols/CLUInfoModule.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// CLUInfoModule.h -// Clue -// -// Created by Ahmed Sulaiman on 7/9/16. -// Copyright © 2016 Ahmed Sulaiman. All rights reserved. -// - -#import -#import "CLUWritable.h" - -/** - `CLUInfoModule` protocol describe info modules (like Device Info module or Exception module), static one-time modules which needs to write their data only once during recording. - - @warning Every info modules have to implement this protocol to be able to work normally inside the system - */ -@protocol CLUInfoModule - -@required - -/** - Initialize info module with specific writer which implements `CLUWritable` protocol. So module will be able to record/write required information. - - @param writer Writer object which implements `CLUWritable` protocol. Responsible for actual writing information to some specific file - @return New instance of info module - */ -- (instancetype)initWithWriter:(id )writer; - -/** - Record actual information once and cleanup everything - */ -- (void)recordInfoData; - -@end diff --git a/Clue/Classes/Protocols/CLURecordableModule.h b/Clue/Classes/Protocols/CLURecordableModule.h deleted file mode 100644 index d756a32..0000000 --- a/Clue/Classes/Protocols/CLURecordableModule.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// CLURecordableModule.h -// Clue -// -// Created by Ahmed Sulaiman on 5/18/16. -// Copyright © 2016 Ahmed Sulaiman. All rights reserved. -// - -#import -#import "CLUWritable.h" - -/** - `CLURecordableModule` protocol describe recordable module (like Video, View Structure, Network modules etc) which needs to track or inspect some specific information over time (like view structure for example) and record this information with specific timestamp using Writers - - @warning Every recordable modules have to implement this protocol to be able to work normally inside the system - */ -@protocol CLURecordableModule - -@required - -/** - Initialize recordable module with specific writer which implements `CLUWritable` protocol. So module will be able to record/write required information. - - @param writer Writer object which implements `CLUWritable` protocol. Responsible for actual writing information to some specific file (it could be video file, text file and etc) - @return New instance of recordable module - */ -- (instancetype)initWithWriter:(id )writer; - -/** - Start recording for current recordable module. Usually you can do all preparations here and start actual recording (which is specific for your recordable module). - */ -- (void)startRecording; - -/** - Stop recording for current recordable module. Usually you can do all cleanup here and stop actual recording (which is specific for your recordable module). - */ -- (void)stopRecording; - -/** - This method will be called by third-party (see `CLUReportComposer`) when new frame with new timestamp is available. So usually your module needs to handle all recording related operations here and setup specific timestamp for new data entity. - - @param timestamp `CFTimeInterval` timestamp of new frame. So module can add new data if available for this timestamp - */ -- (void)addNewFrameWithTimestamp:(CFTimeInterval)timestamp; - -@end diff --git a/Clue/Classes/Protocols/CLUWritable.h b/Clue/Classes/Protocols/CLUWritable.h deleted file mode 100644 index d4da33a..0000000 --- a/Clue/Classes/Protocols/CLUWritable.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// CLUWritable.h -// Clue -// -// Created by Ahmed Sulaiman on 5/18/16. -// Copyright © 2016 Ahmed Sulaiman. All rights reserved. -// - -#import - -/** - `CLUWritable` protocol describe writers (like `CLUVideoWriter`) which needs to actually write new data to specific file (could be text file, video file etc.) - */ -@protocol CLUWritable - -@required - -/** - Get writer status, whether Writer is ready to write new data or not - - @return BOOL value which specify whether Writer is ready to write new data or not - */ -- (BOOL)isReadyForWriting; - -/** - Finish actual writing with all necessary cleanup - */ -- (void)finishWriting; - -/** - Start actual writing with all necessary preparations - */ -- (void)startWriting; - -@end diff --git a/Clue/Classes/Protocols/InfoModule.swift b/Clue/Classes/Protocols/InfoModule.swift new file mode 100644 index 0000000..7450784 --- /dev/null +++ b/Clue/Classes/Protocols/InfoModule.swift @@ -0,0 +1,27 @@ +// +// InfoModule.swift +// Clue +// +// Created by Andrea Prearo on 5/22/17. +// Copyright © 2017 Ahmed Sulaiman. All rights reserved. +// + +import Foundation + +/** + `InfoModule` protocol describe info modules (like Device Info module or Exception module), + static one-time modules which needs to write their data only once during recording. + + @warning Every info module has to implement this protocol to be able to work as expected inside the system. + */ +public protocol InfoModule { + /// Initializes the info module with a specific writer which implements the `Writable` protocol. + /// This will in turn allow the info module to record/write all required data. + /// + /// - Parameter writer: The Writer object which implements the `Writable` protocol. + /// It will be responsible for writing the actual data to a specific file. + init(writer: Writable) + + /// Triggers the recording of the actual data and takes care of the cleanup process. + func recordInfoData() +} diff --git a/Clue/Classes/Protocols/RecordableModule.swift b/Clue/Classes/Protocols/RecordableModule.swift new file mode 100644 index 0000000..8bccb15 --- /dev/null +++ b/Clue/Classes/Protocols/RecordableModule.swift @@ -0,0 +1,42 @@ +// +// RecordableModule.swift +// Clue +// +// Created by Andrea Prearo on 5/22/17. +// Copyright © 2017 Ahmed Sulaiman. All rights reserved. +// + +import Foundation + +/** + `RecordableModule` protocol describe recordable module (like Video, View Structure, Network modules etc) + which needs to track or inspect some specific information over time (like view structure for example) + and record this information with specific timestamp using Writers. + + @warning Every recordable module has to implement this protocol to be able to work as expected inside the system. + */ +public protocol RecordableModule { + /// Initializes the recordable module with a specific writer which implements the `Writable` protocol. + /// This will in turn allow the info module to record/write all required data. + /// + /// - Parameter writer: The Writer object which implements the `Writable` protocol. + /// It will be responsible for writing the actual data to a specific file. + init(writer: Writable) + + /// Starts the recording for current recordable module. + /// Usually you perform the required set up here and then start the actual recording + /// (which is specific for your recordable module). + func startRecording() + + /// Stops the recording for the current recordable module. + /// Usually you can perform the required cleanup here and then stop the actual recording + /// (which is specific for your recordable module). + func stopRecording() + + /// This method will be called by a third-party (see `ReportComposer`) when new frame with new timestamp is available. + /// Usually your module needs to handle all the recording related operations here and set up the specific timestamp for ech new frame. + /// + /// - Parameter timestamp: The timestamp for the new frame. + /// The underlying module will append the new frame for this timestamp, if available. + func addNewFrame(for timestamp: TimeInterval) +} diff --git a/Clue/Classes/Protocols/Writable.swift b/Clue/Classes/Protocols/Writable.swift new file mode 100644 index 0000000..96886a2 --- /dev/null +++ b/Clue/Classes/Protocols/Writable.swift @@ -0,0 +1,23 @@ +// +// Writable.swift +// Clue +// +// Created by Andrea Prearo on 5/22/17. +// Copyright © 2017 Ahmed Sulaiman. All rights reserved. +// + +import Foundation + +/** + `Writable` protocol describe writers (like `VideoWriter`) which needs to actually write new data to specific file (could be text file, video file etc.) + */ +public protocol Writable { + /// Returns whether the writer instance is ready to write new data or not + func isReadyForWriting() -> Bool + + /// Finishes the actual writing an performs all the necessary cleanup. + func finishWriting() + + /// Starts the actual writing and performs all the necessary set up. + func startWriting() +} diff --git a/Clue/Clue.h b/Clue/Clue.h index 1836f8d..cf623f4 100644 --- a/Clue/Clue.h +++ b/Clue/Clue.h @@ -17,8 +17,6 @@ FOUNDATION_EXPORT const unsigned char ClueVersionString[]; #import #import -#import -#import "CLURecordableModule.h" #import "CLURecordIndicatorViewManager.h" #import "UIView+CLUViewRecordableAdditions.h" #import "CLUNetworkObserverDelegate.h" @@ -31,6 +29,5 @@ FOUNDATION_EXPORT const unsigned char ClueVersionString[]; #import "CLUGeneralGestureRecognizer.h" #import "CLUInteractionObserverDelegate.h" #import "CLUTouch.h" -#import "CLUInfoModule.h" #import "NSException+CLUExceptionAdditions.h" #import "CLUReportFileManager.h" From fd7fea0df6109119df04e14b352f6919be8a2ee0 Mon Sep 17 00:00:00 2001 From: Andrea Prearo Date: Tue, 23 May 2017 05:13:12 -0700 Subject: [PATCH 3/6] Update writers and modules Changes: - Update writers to conform to the new Writable protocol. - Update modules to conform to the new InfoModule and RecordableModule protocols. - Update record composer. --- Clue/Classes/Composers/ReportComposer.swift | 24 +++++++++---------- Clue/Classes/Modules/DeviceInfoModule.swift | 4 ++-- .../Classes/Modules/ExceptionInfoModule.swift | 6 ++--- Clue/Classes/Modules/NetworkModule.swift | 2 +- Clue/Classes/Modules/ObserveModule.swift | 12 +++++----- .../Modules/UserInteractionModule.swift | 2 +- Clue/Classes/Modules/VideoModule.swift | 12 +++++----- .../Classes/Modules/ViewStructureModule.swift | 6 ++--- Clue/Classes/Writers/DataWriter.swift | 4 ++-- Clue/Classes/Writers/VideoWriter.swift | 16 +++++++++---- 10 files changed, 48 insertions(+), 40 deletions(-) diff --git a/Clue/Classes/Composers/ReportComposer.swift b/Clue/Classes/Composers/ReportComposer.swift index 1315f49..db7f812 100644 --- a/Clue/Classes/Composers/ReportComposer.swift +++ b/Clue/Classes/Composers/ReportComposer.swift @@ -2,7 +2,7 @@ // ReportComposer.swift // Clue // -// Created by Prearo, Andrea on 5/22/17. +// Created by Andrea Prearo on 5/22/17. // Copyright © 2017 Ahmed Sulaiman. All rights reserved. // @@ -10,26 +10,26 @@ import Foundation public class ReportComposer: NSObject { // MARK: - Private Properties - fileprivate var recordableModules: [CLURecordableModule]? - fileprivate var infoModules: [CLUInfoModule]? + fileprivate var recordableModules: [RecordableModule]? + fileprivate var infoModules: [InfoModule]? fileprivate var mainRecordQueue: DispatchQueue? fileprivate var moduleRecordQueue: DispatchQueue? fileprivate var recordSemaphore: DispatchSemaphore? fileprivate var displayLink: CADisplayLink? - fileprivate var firstTimestamp: CFTimeInterval? + fileprivate var firstTimestamp: TimeInterval? fileprivate(set) public var isRecording = false /// Initializes a new `ReportComposer` instance with recordable modules array. /// Recording works even without info modules. But having at least one recordable module is required. /// - /// - Parameter modules: The recordable modules array. Each items of the array will record its own data, + /// - Parameter recordableModules: The recordable modules array. Each items of the array will record its own data, /// during the recording, and add the specific timestamp for each new entry. - public init(with modules: [CLURecordableModule]) { + public init(recordableModules: [RecordableModule]?) { super.init() isRecording = false displayLink = CADisplayLink.init(target: self, selector: #selector(onScreenUpdate)) - recordableModules = modules + self.recordableModules = recordableModules moduleRecordQueue = DispatchQueue(label: "ReportComposer.moduleRecordQueue") mainRecordQueue = DispatchQueue(label: "ReportComposer.mainRecordQueue", @@ -41,20 +41,20 @@ public class ReportComposer: NSObject { /// Initializes a new `ReportComposer` instance with recordable modules array. public convenience override init() { - self.init(with: []) + self.init(recordableModules: []) } /// Sets the info modules array to be used once during recording. /// Needs to be set up before startic the actual recording. /// - /// - Parameter infoModules: <#infoModules description#> - public func setInfoModules(_ infoModules: [CLUInfoModule]) { + /// - Parameter infoModules: The info modules array which will record the required data + public func setInfoModules(_ infoModules: [InfoModule]) { if !isRecording { self.infoModules = infoModules } } - /// Starts the recording for each module (CLURecordableModule and CLUInfoModule). + /// Starts the recording for each module (RecordableModule and InfoModule). public func startRecording() { if !isRecording { isRecording = true @@ -112,7 +112,7 @@ fileprivate extension ReportComposer { if let timestamp = strongSelf.displayLink?.timestamp, let firstTimestamp = strongSelf.firstTimestamp { let elapsedTimeInterval = timestamp - firstTimestamp - module.addNewFrame(withTimestamp: elapsedTimeInterval) + module.addNewFrame(for: elapsedTimeInterval) } } } diff --git a/Clue/Classes/Modules/DeviceInfoModule.swift b/Clue/Classes/Modules/DeviceInfoModule.swift index 1aa4156..355df43 100644 --- a/Clue/Classes/Modules/DeviceInfoModule.swift +++ b/Clue/Classes/Modules/DeviceInfoModule.swift @@ -9,11 +9,11 @@ import Foundation /// `DeviceInfoModule` is a info module (with static, one-time informations) for current devices' information recording on start. -public class DeviceInfoModule: NSObject, CLUInfoModule { +public class DeviceInfoModule: NSObject, InfoModule { fileprivate let writer: JSONWriter // MARK: - Lifecycle - public required init(writer: CLUWritable) { + public required init(writer: Writable) { self.writer = writer as! JSONWriter } diff --git a/Clue/Classes/Modules/ExceptionInfoModule.swift b/Clue/Classes/Modules/ExceptionInfoModule.swift index efef11c..4f6c529 100644 --- a/Clue/Classes/Modules/ExceptionInfoModule.swift +++ b/Clue/Classes/Modules/ExceptionInfoModule.swift @@ -10,16 +10,16 @@ import Foundation /// `ExceptionInfoModule` is a info module (with static, one-time informations) for /// unexpected exception recording if occurred. -public class ExceptionInfoModule: NSObject, CLUInfoModule { +public class ExceptionInfoModule: NSObject, InfoModule { fileprivate let writer: JSONWriter fileprivate var exception: NSException? // MARK: - Lifecycle - public required init(writer: CLUWritable) { + public required init(writer: Writable) { self.writer = writer as! JSONWriter } - public convenience init(writer: CLUWritable, exception: NSException) { + public convenience init(writer: Writable, exception: NSException) { self.init(writer: writer) self.exception = exception } diff --git a/Clue/Classes/Modules/NetworkModule.swift b/Clue/Classes/Modules/NetworkModule.swift index 6bb95d5..9fbfac9 100644 --- a/Clue/Classes/Modules/NetworkModule.swift +++ b/Clue/Classes/Modules/NetworkModule.swift @@ -15,7 +15,7 @@ import Foundation /// `NetworkModule` can add this data to buffer with specific timestamp. public class NetworkModule: ObserveModule {} -// MARK: - NetworkModule + CLURecordableModule +// MARK: - NetworkModule + RecordableModule extension NetworkModule { override public func startRecording() { if !isRecording { diff --git a/Clue/Classes/Modules/ObserveModule.swift b/Clue/Classes/Modules/ObserveModule.swift index 474604c..25c47bf 100644 --- a/Clue/Classes/Modules/ObserveModule.swift +++ b/Clue/Classes/Modules/ObserveModule.swift @@ -25,11 +25,11 @@ public class ObserveModule: NSObject { /// Indicates whether video recording has started or not fileprivate(set) public var isRecording: Bool = false /// Current available timestamp. This property updating constantly with - /// `addNewFrameWithTimestamp:` from `CLURecordableModule` protocol + /// `addNewFrameWithTimestamp:` from `RecordableModule` protocol var currentTimestamp: TimeInterval = 0.0 // MARK: - Lifecycle - public required init(writer: CLUWritable) { + public required init(writer: Writable) { self.writer = writer as! JSONWriter bufferArray = [[AnyHashable: Any]]() currentTimestamp = 0 @@ -39,7 +39,7 @@ public class ObserveModule: NSObject { // MARK: - Public Methods /// Adds a new entry to buffer, so it could be saved to file via Writer - /// on next iteration of `addNewFrameWithTimestamp:` from `CLURecordableModule` protocol. + /// on next iteration of `addNewFrameWithTimestamp:` from `RecordableModule` protocol. /// /// - Parameter bufferItem: The entry which you need to save to buffer. public func addData(bufferItem: [AnyHashable: Any]) { @@ -61,8 +61,8 @@ public class ObserveModule: NSObject { } } -// MARK: - ObserveModule + CLURecordableModule -extension ObserveModule: CLURecordableModule { +// MARK: - ObserveModule + RecordableModule +extension ObserveModule: RecordableModule { public func startRecording() { if !isRecording { isRecording = true @@ -84,7 +84,7 @@ extension ObserveModule: CLURecordableModule { /// /// - Parameter timestamp: `TimeInterval` timestamp of new frame. So module can add /// new data if available for this timestamp - public func addNewFrame(withTimestamp timestamp: TimeInterval) { + public func addNewFrame(for timestamp: TimeInterval) { currentTimestamp = timestamp if frameRecordingSemaphore?.wait(timeout: DispatchTime.now()) != .success { return diff --git a/Clue/Classes/Modules/UserInteractionModule.swift b/Clue/Classes/Modules/UserInteractionModule.swift index 810cb69..e1f406b 100644 --- a/Clue/Classes/Modules/UserInteractionModule.swift +++ b/Clue/Classes/Modules/UserInteractionModule.swift @@ -22,7 +22,7 @@ public class UserInteractionModule: ObserveModule { fileprivate var gestureRecognizer = CLUGeneralGestureRecognizer() } -// MARK: - UserInteractionModule + CLURecordableModule +// MARK: - UserInteractionModule + RecordableModule extension UserInteractionModule { override public func startRecording() { if !isRecording { diff --git a/Clue/Classes/Modules/VideoModule.swift b/Clue/Classes/Modules/VideoModule.swift index 094777c..ce88f53 100644 --- a/Clue/Classes/Modules/VideoModule.swift +++ b/Clue/Classes/Modules/VideoModule.swift @@ -2,7 +2,7 @@ // VideoModule.swift // Clue // -// Created by Prearo, Andrea on 5/22/17. +// Created by Andrea Prearo on 5/22/17. // Copyright © 2017 Ahmed Sulaiman. All rights reserved. // @@ -10,7 +10,7 @@ import Foundation import AVFoundation /** - `VideoModule` is a class (module) for screen recording which implements `CLURecordableModule` protocol. + `VideoModule` is a class (module) for screen recording which implements `RecordableModule` protocol. It's responsible for thread safety while video recording, operations with `CVPixelBuffer` and current view hierarchy drawing (see `UIView` `drawViewHierarchyInRect:afterScreenUpdates:`) and frames overlapping while recording. */ @@ -24,7 +24,7 @@ public class VideoModule: NSObject { fileprivate(set) public var isRecording = false - public required init(writer: CLUWritable) { + public required init(writer: Writable) { self.writer = writer as! VideoWriter isRecording = false @@ -35,8 +35,8 @@ public class VideoModule: NSObject { } } -// MARK: - VideoModule + CLURecordableModule -extension VideoModule: CLURecordableModule { +// MARK: - VideoModule + RecordableModule +extension VideoModule: RecordableModule { public func startRecording() { if !isRecording { writer.startWriting() @@ -51,7 +51,7 @@ extension VideoModule: CLURecordableModule { } } - public func addNewFrame(withTimestamp timestamp: CFTimeInterval) { + public func addNewFrame(for timestamp: TimeInterval) { // Throttle the number of frames to prevent meltdown. // Technique gleaned from Brad Larson's answer here: http://stackoverflow.com/a/5956119 if frameRenderingSemaphore?.wait(timeout: DispatchTime.now()) != .success { diff --git a/Clue/Classes/Modules/ViewStructureModule.swift b/Clue/Classes/Modules/ViewStructureModule.swift index 4cf848e..306b23a 100644 --- a/Clue/Classes/Modules/ViewStructureModule.swift +++ b/Clue/Classes/Modules/ViewStructureModule.swift @@ -15,8 +15,8 @@ import Foundation public class ViewStructureModule: ObserveModule { fileprivate var lastRecordedViewStructure: [AnyHashable: Any]? = [:] - // MARK: - Override CLURecordableModule - override public func addNewFrame(withTimestamp timestamp: TimeInterval) { + // MARK: - Override RecordableModule + override public func addNewFrame(for timestamp: TimeInterval) { recordQueue?.sync { let currentViewStructure: [AnyHashable: Any]? = { guard let view = CLURecordIndicatorViewManager.currentViewController().view, @@ -31,7 +31,7 @@ public class ViewStructureModule: ObserveModule { self.lastRecordedViewStructure = currentViewStructure addViewStructureProperties(self.lastRecordedViewStructure, timestamp: timestamp) } - super.addNewFrame(withTimestamp: timestamp) + super.addNewFrame(for: timestamp) } } } diff --git a/Clue/Classes/Writers/DataWriter.swift b/Clue/Classes/Writers/DataWriter.swift index 1c7931b..d5396b3 100644 --- a/Clue/Classes/Writers/DataWriter.swift +++ b/Clue/Classes/Writers/DataWriter.swift @@ -83,8 +83,8 @@ extension DataWriter: StreamDelegate { } } -// MARK: - DataWriter + CLUWritable -extension DataWriter: CLUWritable { +// MARK: - DataWriter + Writable +extension DataWriter: Writable { public func isReadyForWriting() -> Bool { return outputStream.streamStatus == .open } diff --git a/Clue/Classes/Writers/VideoWriter.swift b/Clue/Classes/Writers/VideoWriter.swift index 198d8a2..f2155a6 100644 --- a/Clue/Classes/Writers/VideoWriter.swift +++ b/Clue/Classes/Writers/VideoWriter.swift @@ -62,7 +62,12 @@ public class VideoWriter: DataWriter { } CVPixelBufferLockBaseAddress(buffer, []) let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) - let bitmapContext = CGContext(data: CVPixelBufferGetBaseAddress(buffer), width: CVPixelBufferGetWidth(buffer), height: CVPixelBufferGetHeight(buffer), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(buffer), space: colorSpace, bitmapInfo: bitmapInfo.rawValue) + let bitmapContext = CGContext(data: CVPixelBufferGetBaseAddress(buffer), + width: CVPixelBufferGetWidth(buffer), + height: CVPixelBufferGetHeight(buffer), + bitsPerComponent: 8, + bytesPerRow: CVPixelBufferGetBytesPerRow(buffer), + space: colorSpace, bitmapInfo: bitmapInfo.rawValue) guard let context = bitmapContext else { assertionFailure("Failed to create the bitmap context") return (nil, bitmapContext) @@ -100,7 +105,7 @@ public class VideoWriter: DataWriter { } } -// MARK: - VideoWriter + CLUWritable +// MARK: - VideoWriter + Writable public extension VideoWriter { override func isReadyForWriting() -> Bool { return videoWriterInput?.isReadyForMoreMediaData ?? false @@ -150,7 +155,8 @@ fileprivate extension VideoWriter { AVVideoHeightKey: viewSize.height * viewScale, AVVideoCompressionPropertiesKey: videoCompression ] - videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings) + videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, + outputSettings: videoSettings) guard let videoWriterInput = videoWriterInput else { assertionFailure("Failed to create the asset writer input") return @@ -164,7 +170,9 @@ fileprivate extension VideoWriter { assertionFailure("Asset writer input not initialized") return } - videoWriterAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput, sourcePixelBufferAttributes: nil) + videoWriterAdaptor = + AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput, + sourcePixelBufferAttributes: nil) guard let _ = videoWriterAdaptor else { assertionFailure("Failed to create the asset writer adaptor") return From 0b7715992aadfbfc7e61033971fee1523b357a0a Mon Sep 17 00:00:00 2001 From: Andrea Prearo Date: Tue, 23 May 2017 05:15:11 -0700 Subject: [PATCH 4/6] Code cleanup --- Clue/Classes/ModulesUtils/CLUMailHelper.h | 2 +- Clue/Classes/ModulesUtils/CLUMailHelper.m | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Clue/Classes/ModulesUtils/CLUMailHelper.h b/Clue/Classes/ModulesUtils/CLUMailHelper.h index 903dd05..76bf71c 100644 --- a/Clue/Classes/ModulesUtils/CLUMailHelper.h +++ b/Clue/Classes/ModulesUtils/CLUMailHelper.h @@ -21,7 +21,7 @@ @param option `CLUOptions` object which contains email property where `CLUMailHelper` will send mail with .clue report. Get this options from first Clue Controller configuration @return `CLUMailHelper` instance with configure options */ -- (instancetype)initWithOption:(CLUOptions *)option; +- (instancetype)initWithOptions:(CLUOptions *)options; /** Show mail composer modal view for specific view controller diff --git a/Clue/Classes/ModulesUtils/CLUMailHelper.m b/Clue/Classes/ModulesUtils/CLUMailHelper.m index d240336..bad2478 100644 --- a/Clue/Classes/ModulesUtils/CLUMailHelper.m +++ b/Clue/Classes/ModulesUtils/CLUMailHelper.m @@ -17,16 +17,16 @@ @interface CLUMailHelper() @implementation CLUMailHelper -- (instancetype)initWithOption:(CLUOptions *)option { +- (instancetype)initWithOptions:(CLUOptions *)options { self = [super init]; - if (!self || !option) { + if (!self || !options) { return nil; } _mailComposeViewController = [[MFMailComposeViewController alloc] init]; NSString *currentReportSubject = [self currentReportSubject]; [_mailComposeViewController setSubject:currentReportSubject]; - if (option.email) { - [_mailComposeViewController setToRecipients:@[option.email]]; + if (options.email) { + [_mailComposeViewController setToRecipients:@[options.email]]; } BOOL isZipFileCreatedSuccessfully = [[CLUReportFileManager sharedManager] createZipReportFile]; if (isZipFileCreatedSuccessfully) { From dd6f8ee442092b9cfc0b7896e41da3e680de28a5 Mon Sep 17 00:00:00 2001 From: Andrea Prearo Date: Tue, 23 May 2017 05:16:08 -0700 Subject: [PATCH 5/6] Migrate ClueController to Swift Changes: - Migrate ClueController to Swift. - Update sample apps. --- Clue.xcodeproj/project.pbxproj | 20 +- Clue/Classes/ClueController.swift | 265 +++++++++++++++++ Clue/Classes/Composers/ReportComposer.swift | 2 +- .../Classes/Modules/ExceptionInfoModule.swift | 4 +- Clue/Clue.h | 6 +- Clue/ClueController.h | 62 ---- Clue/ClueController.m | 270 ------------------ ClueExampleApp/AppDelegate.m | 2 +- ClueExampleApp/ViewController.m | 2 +- ClueSwiftExampleApp/AppDelegate.swift | 2 +- .../MasterViewController.swift | 2 +- 11 files changed, 284 insertions(+), 353 deletions(-) create mode 100644 Clue/Classes/ClueController.swift delete mode 100644 Clue/ClueController.h delete mode 100644 Clue/ClueController.m diff --git a/Clue.xcodeproj/project.pbxproj b/Clue.xcodeproj/project.pbxproj index c939e52..bae71b5 100644 --- a/Clue.xcodeproj/project.pbxproj +++ b/Clue.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ AF34EE301ED37FEC005E05FC /* InfoModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE2F1ED37FEC005E05FC /* InfoModule.swift */; }; AF34EE321ED38264005E05FC /* RecordableModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE311ED38264005E05FC /* RecordableModule.swift */; }; AF34EE341ED38682005E05FC /* Writable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE331ED38682005E05FC /* Writable.swift */; }; + AF34EE361ED388C5005E05FC /* ClueController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF34EE351ED388C5005E05FC /* ClueController.swift */; }; AF422CBB1EC2166C0028AA33 /* ObserveModuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF422CBA1EC2166C0028AA33 /* ObserveModuleTests.swift */; }; AF4B1EB11ED301160024AB1C /* VideoWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4B1EB01ED301160024AB1C /* VideoWriter.swift */; }; AF4FB5D41EBACCB500FB9F0B /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4FB5D31EBACCB500FB9F0B /* Result.swift */; }; @@ -86,12 +87,10 @@ E28610131CEFB9C300FA9BAF /* Karla-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E286100F1CEFB9C300FA9BAF /* Karla-BoldItalic.ttf */; }; E28610141CEFB9C300FA9BAF /* Karla-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E28610101CEFB9C300FA9BAF /* Karla-Italic.ttf */; }; E28610151CEFB9C300FA9BAF /* Karla-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E28610111CEFB9C300FA9BAF /* Karla-Regular.ttf */; }; - E28CC0951CEE619000C9DFD9 /* ClueController.h in Headers */ = {isa = PBXBuildFile; fileRef = E28CC0931CEE619000C9DFD9 /* ClueController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E28CC0961CEE619000C9DFD9 /* ClueController.m in Sources */ = {isa = PBXBuildFile; fileRef = E28CC0941CEE619000C9DFD9 /* ClueController.m */; }; E291DEAA1D14A04F009AB23F /* CLUURLProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = E291DEA81D14A04F009AB23F /* CLUURLProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; E291DEAB1D14A04F009AB23F /* CLUURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = E291DEA91D14A04F009AB23F /* CLUURLProtocol.m */; }; E291DEAD1D14A741009AB23F /* CLUNetworkObserverDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E291DEAC1D14A741009AB23F /* CLUNetworkObserverDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E29233D51D358B6300DA88C2 /* CLUMailHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = E29233D31D358B6300DA88C2 /* CLUMailHelper.h */; }; + E29233D51D358B6300DA88C2 /* CLUMailHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = E29233D31D358B6300DA88C2 /* CLUMailHelper.h */; settings = {ATTRIBUTES = (Public, ); }; }; E29233D61D358B6300DA88C2 /* CLUMailHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = E29233D41D358B6300DA88C2 /* CLUMailHelper.m */; }; E2ABF6471D424531006646B3 /* CLURecordIndicatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = E2ABF6451D424531006646B3 /* CLURecordIndicatorView.h */; }; E2ABF6481D424531006646B3 /* CLURecordIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = E2ABF6461D424531006646B3 /* CLURecordIndicatorView.m */; }; @@ -144,7 +143,7 @@ E2D9A4451E8F0EC20099B0AA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E2D9A4431E8F0EC20099B0AA /* Main.storyboard */; }; E2D9A4471E8F0EC20099B0AA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2D9A4461E8F0EC20099B0AA /* Assets.xcassets */; }; E2D9A44A1E8F0EC20099B0AA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E2D9A4481E8F0EC20099B0AA /* LaunchScreen.storyboard */; }; - E2EBC7F71D3AE26D00E52D5D /* CLUMailDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E2EBC7F51D3AE26D00E52D5D /* CLUMailDelegate.h */; }; + E2EBC7F71D3AE26D00E52D5D /* CLUMailDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E2EBC7F51D3AE26D00E52D5D /* CLUMailDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; E2EBC7F81D3AE26D00E52D5D /* CLUMailDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E2EBC7F61D3AE26D00E52D5D /* CLUMailDelegate.m */; }; /* End PBXBuildFile section */ @@ -206,6 +205,7 @@ AF34EE2F1ED37FEC005E05FC /* InfoModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoModule.swift; sourceTree = ""; }; AF34EE311ED38264005E05FC /* RecordableModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordableModule.swift; sourceTree = ""; }; AF34EE331ED38682005E05FC /* Writable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Writable.swift; sourceTree = ""; }; + AF34EE351ED388C5005E05FC /* ClueController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClueController.swift; sourceTree = ""; }; AF422CBA1EC2166C0028AA33 /* ObserveModuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserveModuleTests.swift; sourceTree = ""; }; AF4B1EB01ED301160024AB1C /* VideoWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoWriter.swift; sourceTree = ""; }; AF4FB5D31EBACCB500FB9F0B /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Utils/Result.swift; sourceTree = ""; }; @@ -281,8 +281,6 @@ E286100F1CEFB9C300FA9BAF /* Karla-BoldItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Karla-BoldItalic.ttf"; sourceTree = ""; }; E28610101CEFB9C300FA9BAF /* Karla-Italic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Karla-Italic.ttf"; sourceTree = ""; }; E28610111CEFB9C300FA9BAF /* Karla-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Karla-Regular.ttf"; sourceTree = ""; }; - E28CC0931CEE619000C9DFD9 /* ClueController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ClueController.h; path = ../ClueController.h; sourceTree = ""; }; - E28CC0941CEE619000C9DFD9 /* ClueController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ClueController.m; path = ../ClueController.m; sourceTree = ""; }; E291DEA81D14A04F009AB23F /* CLUURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUURLProtocol.h; sourceTree = ""; }; E291DEA91D14A04F009AB23F /* CLUURLProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLUURLProtocol.m; sourceTree = ""; }; E291DEAC1D14A741009AB23F /* CLUNetworkObserverDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUNetworkObserverDelegate.h; sourceTree = ""; }; @@ -525,8 +523,7 @@ E272DD9C1CD5225B00F1FECA /* Classes */ = { isa = PBXGroup; children = ( - E28CC0931CEE619000C9DFD9 /* ClueController.h */, - E28CC0941CEE619000C9DFD9 /* ClueController.m */, + AF34EE351ED388C5005E05FC /* ClueController.swift */, E28CC08A1CEE210D00C9DFD9 /* Composers */, E2A30C851CED017F00A48C61 /* Protocols */, E2A30C841CED017200A48C61 /* Modules */, @@ -795,6 +792,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E29233D51D358B6300DA88C2 /* CLUMailHelper.h in Headers */, + E2EBC7F71D3AE26D00E52D5D /* CLUMailDelegate.h in Headers */, E20F23481D31947300654690 /* CLUReportFileManager.h in Headers */, E215639A1D25A15A005EDF61 /* NSException+CLUExceptionAdditions.h in Headers */, E22F60EF1D265DBE0062D6B8 /* CLUTouch.h in Headers */, @@ -814,10 +813,8 @@ E2CDA69F1D3905D60079F784 /* entropy.h in Headers */, E2CDA6B01D3905D60079F784 /* mztools.h in Headers */, E272DD861CD5223300F1FECA /* Clue.h in Headers */, - E2EBC7F71D3AE26D00E52D5D /* CLUMailDelegate.h in Headers */, E2CDA6A51D3905D60079F784 /* prng.h in Headers */, E24AEE021D06E9DC001F31FF /* UITouch+CLUUserInteractionAdditions.h in Headers */, - E28CC0951CEE619000C9DFD9 /* ClueController.h in Headers */, E2CDA6B71D3905D60079F784 /* ZipArchive.h in Headers */, E2CDA6A71D3905D60079F784 /* pwd2key.h in Headers */, E2CDA6A91D3905D60079F784 /* sha1.h in Headers */, @@ -830,7 +827,6 @@ E2CDA6951D3905D60079F784 /* aes.h in Headers */, E2CDA69B1D3905D60079F784 /* aestab.h in Headers */, E2CDA69C1D3905D60079F784 /* brg_endian.h in Headers */, - E29233D51D358B6300DA88C2 /* CLUMailHelper.h in Headers */, E2CDA6A11D3905D60079F784 /* fileenc.h in Headers */, E2ABF6471D424531006646B3 /* CLURecordIndicatorView.h in Headers */, E2CDA6A31D3905D60079F784 /* hmac.h in Headers */, @@ -1016,6 +1012,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AF34EE361ED388C5005E05FC /* ClueController.swift in Sources */, E2CDA6971D3905D60079F784 /* aescrypt.c in Sources */, E24AEDFF1D06DD28001F31FF /* CLUGeneralGestureRecognizer.m in Sources */, AF34EE2C1ED354F9005E05FC /* VideoModule.swift in Sources */, @@ -1055,7 +1052,6 @@ E23891401D1BDC3C00D688CD /* NSURLResponse+CLUNetworkAdditions.m in Sources */, AF15549B1EB568F1005D1046 /* DataWriter.swift in Sources */, AF73FF131EC0C45400B8EA4D /* ExceptionInfoModule.swift in Sources */, - E28CC0961CEE619000C9DFD9 /* ClueController.m in Sources */, AF7879DF1EC516E500938263 /* UIDevice+Util.swift in Sources */, AF4B1EB11ED301160024AB1C /* VideoWriter.swift in Sources */, E2ABF64C1D425108006646B3 /* CLURecordIndicatorViewManager.m in Sources */, diff --git a/Clue/Classes/ClueController.swift b/Clue/Classes/ClueController.swift new file mode 100644 index 0000000..8fbdb8d --- /dev/null +++ b/Clue/Classes/ClueController.swift @@ -0,0 +1,265 @@ +// +// ClueController.swift +// Clue +// +// Created by Andrea Prearo on 5/22/17. +// Copyright © 2017 Ahmed Sulaiman. All rights reserved. +// + +import Foundation + +/** + `ClueController` is a singleton class and main Clue controller which is also the only + public interface for framework user. Here user can turn on/off Clue and start/stop report recording. + */ +public class ClueController: NSObject { + /// Singleton instance + public static let shared = ClueController() + + // MARK: - Private Properties + fileprivate var recordableModules: [RecordableModule]? + fileprivate var infoModules: [InfoModule]? + fileprivate var isEnabled = false + fileprivate var isRecording = false + fileprivate var waitVideoRenderingQueue: DispatchQueue? + fileprivate var options: CLUOptions? + fileprivate var reportComposer: ReportComposer? + fileprivate var mailDelegate: CLUMailDelegate? + + static let recordableModulesDirectory = CLUReportFileManager.shared().recordableModulesDirectoryURL + static let infoModulesDirectory = CLUReportFileManager.shared().infoModulesDirectoryURL + + // Making this private because this class is a singleton. + fileprivate override init() { + super.init() + recordableModules = setUpRecordableModules() + infoModules = setUpInfoModules() + reportComposer = ReportComposer(recordableModules: recordableModules) + reportComposer?.setInfoModules(infoModules) + setUpUncaughtExceptionHandler() + waitVideoRenderingQueue = DispatchQueue(label: "ClueController.waitVideoRenderingQueue") + mailDelegate = CLUMailDelegate() + } + + /// Starts the actual recording. + /// You should call this method directly only if you want to start recording with your own custom UI element. + /// It's recommended to use the `handleShake()` instance method. + public func startRecording() { + guard let viewController = CLURecordIndicatorViewManager.currentViewController() else { + return + } + if CLUReportFileManager.shared().isReportZipFileAvailable() { + showAlert(title: "Send Previous Clue Report", + message: "Do you want to send your previous Clue Report caused by internal excpetion?", + successActionTitle: "Send Report", failureActionTitle: "Delete Report", + successHandler: { [weak self] in + self?.sendReportWithEmailService() + }, + failureHandler: { + CLUReportFileManager.shared().removeReportZipFile() + }, + viewController: viewController) + return + } + + if isRecording { + return + } + isRecording = true + reportComposer?.startRecording() + + let maxTime = CLURecordIndicatorViewManager.defaultMaxTime() + CLURecordIndicatorViewManager.showRecordIndicator(in: viewController, withMaxTime: maxTime, target: self, andAction: #selector(stopRecording)) + } + + /// Stops the actual recording. + /// You should call this method directly only if you want to stop recording with your own custom UI element. + /// It's recommended to use the `handleShake()` instance method. + public func stopRecording() { + if !isRecording { + return + } + isRecording = false + reportComposer?.stopRecording() + + CLURecordIndicatorViewManager.switchRecordIndicatorToWaitingMode() + // Delay before zipping report, video rendering have to end properly + waitVideoRenderingQueue?.async { + // TODO: come up with better approach + Thread.sleep(forTimeInterval: 4) + DispatchQueue.main.sync { [weak self] in + CLURecordIndicatorViewManager.hideRecordIndicator() + self?.sendReportWithEmailService() + } + } + } + + /// Handles a shake gesture to start/stop recording. + /// + /// - Parameter motion: The motion event type + public func handleShake(_ motion: UIEventSubtype) { + guard motion == .motionShake && isEnabled else { + // TODO: print warning message + return + } + if isRecording { + stopRecording() + } else { + startRecording() + } + } + + /// Enables the controller functionality. + /// + /// - Parameter options: The options to configure the recording behavior. + public func enable(with options: CLUOptions?) { + if !isEnabled { + isEnabled = true + self.options = options + } + } + + /// Enables the controller functionality. + public func enable() { + enable(with: nil) + } + + /// Disables the controller functionality. + public func disable() { + if isEnabled { + isEnabled = false + // TODO: clear everything redundant + } + } +} + +// MARK: - Set Up Methods +fileprivate extension ClueController { + func setUpUncaughtExceptionHandler() { + NSSetUncaughtExceptionHandler { exception in + ClueController.shared.handleException(exception) + } + } + + // MARK: - Set Up Recordable Modules + func setUpRecordableModules() -> [RecordableModule]? { + let videoModule = setUpVideoModule() + let viewStructureModule = setUpViewStructureModule() + let userInteractionModule = setUpUserInteractionModule() + let networkModule = setUpNetworkModule() + let modules: [RecordableModule] = [ + videoModule, + viewStructureModule, + userInteractionModule, + networkModule + ].filter { $0 != nil }.map { $0 as! RecordableModule } + return modules + } + + func setUpVideoModule() -> VideoModule? { + let viewSize = UIScreen.main.bounds.size + let viewScale = UIScreen.main.scale + guard let outputURL = ClueController.recordableModulesDirectory?.appendingPathComponent("module_video.mp4"), + let videoWriter = VideoWriter(outputURL: outputURL, viewSize: viewSize, viewScale: viewScale) else { + // TODO: handle errors + return nil + } + return VideoModule(writer: videoWriter) + } + + func setUpViewStructureModule() -> ViewStructureModule? { + guard let outputURL = ClueController.recordableModulesDirectory?.appendingPathComponent("module_view.json"), + let jsonWriter = JSONWriter(outputURL: outputURL) else { + // TODO: handle errors + return nil + } + return ViewStructureModule(writer: jsonWriter) + } + + func setUpUserInteractionModule() -> UserInteractionModule? { + guard let outputURL = ClueController.recordableModulesDirectory?.appendingPathComponent("module_interaction.json"), + let jsonWriter = JSONWriter(outputURL: outputURL) else { + // TODO: handle errors + return nil + } + return UserInteractionModule(writer: jsonWriter) + } + + func setUpNetworkModule() -> NetworkModule? { + guard let outputURL = ClueController.recordableModulesDirectory?.appendingPathComponent("module_network.json"), + let jsonWriter = JSONWriter(outputURL: outputURL) else { + // TODO: handle errors + return nil + } + return NetworkModule(writer: jsonWriter) + } + + // MARK: - Set Up Info Modules + func setUpInfoModules() -> [InfoModule]? { + let deviceInfoModule = setUpDeviceInfoModule() + let modules: [InfoModule] = [ + deviceInfoModule + ].filter { $0 != nil }.map { $0! } + return modules + } + + func setUpDeviceInfoModule() -> DeviceInfoModule? { + guard let outputURL = ClueController.infoModulesDirectory?.appendingPathComponent("info_device.json"), + let jsonWriter = JSONWriter(outputURL: outputURL) else { + // TODO: handle errors + return nil + } + return DeviceInfoModule(writer: jsonWriter) + } +} + +// MARK: - Private Methods +fileprivate extension ClueController { + func sendReportWithEmailService() { + guard let viewController = CLURecordIndicatorViewManager.currentViewController(), + let mailDelegate = mailDelegate, + let mailHelper = CLUMailHelper(options: options) else { + return + } + mailHelper.setMailDelegate(mailDelegate) + // TODO: test it on real device. Mail isn't working on simulator + mailHelper.showMailComposeWindow(with: viewController) + } + + func handleException(_ exception: NSException) { + guard isEnabled, isRecording, + let outputURL = ClueController.infoModulesDirectory?.appendingPathComponent("info_exception.json"), + let jsonWriter = JSONWriter(outputURL: outputURL) else { + return + } + let exceptionInfoModule = ExceptionInfoModule(writer: jsonWriter, exception: exception) + exceptionInfoModule.recordInfoData() + + waitVideoRenderingQueue?.sync { + ClueController.shared.stopRecording() + CLUReportFileManager.shared().createZipReportFile() + // Crazy hack! If exception occurs wait till video writer finish async handler: `AVAssetWriter` `finishWritingWithCompletionHandler`. + // TODO: come up with better approach + Thread.sleep(forTimeInterval: 4) + } + } + + func showAlert(title: String, + message: String, + successActionTitle: String, + failureActionTitle: String, + successHandler: (() -> Void)? = nil, + failureHandler: (() -> Void)? = nil, + viewController: UIViewController) { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + let successAction = UIAlertAction(title: successActionTitle, style: .default) { _ in + successHandler?() + } + let failureAction = UIAlertAction(title: failureActionTitle, style: .default) { _ in + failureHandler?() + } + alertController.addAction(successAction) + alertController.addAction(failureAction) + viewController.present(alertController, animated: true, completion: nil) + } +} diff --git a/Clue/Classes/Composers/ReportComposer.swift b/Clue/Classes/Composers/ReportComposer.swift index db7f812..5f69a4d 100644 --- a/Clue/Classes/Composers/ReportComposer.swift +++ b/Clue/Classes/Composers/ReportComposer.swift @@ -48,7 +48,7 @@ public class ReportComposer: NSObject { /// Needs to be set up before startic the actual recording. /// /// - Parameter infoModules: The info modules array which will record the required data - public func setInfoModules(_ infoModules: [InfoModule]) { + public func setInfoModules(_ infoModules: [InfoModule]?) { if !isRecording { self.infoModules = infoModules } diff --git a/Clue/Classes/Modules/ExceptionInfoModule.swift b/Clue/Classes/Modules/ExceptionInfoModule.swift index 4f6c529..c24b329 100644 --- a/Clue/Classes/Modules/ExceptionInfoModule.swift +++ b/Clue/Classes/Modules/ExceptionInfoModule.swift @@ -11,8 +11,10 @@ import Foundation /// `ExceptionInfoModule` is a info module (with static, one-time informations) for /// unexpected exception recording if occurred. public class ExceptionInfoModule: NSObject, InfoModule { + var exception: NSException? + + // MARK: - Private Properties fileprivate let writer: JSONWriter - fileprivate var exception: NSException? // MARK: - Lifecycle public required init(writer: Writable) { diff --git a/Clue/Clue.h b/Clue/Clue.h index cf623f4..5d917c8 100644 --- a/Clue/Clue.h +++ b/Clue/Clue.h @@ -14,9 +14,6 @@ FOUNDATION_EXPORT double ClueVersionNumber; //! Project version string for Clue. FOUNDATION_EXPORT const unsigned char ClueVersionString[]; -#import -#import - #import "CLURecordIndicatorViewManager.h" #import "UIView+CLUViewRecordableAdditions.h" #import "CLUNetworkObserverDelegate.h" @@ -31,3 +28,6 @@ FOUNDATION_EXPORT const unsigned char ClueVersionString[]; #import "CLUTouch.h" #import "NSException+CLUExceptionAdditions.h" #import "CLUReportFileManager.h" +#import "CLUOptions.h" +#import "CLUMailDelegate.h" +#import "CLUMailHelper.h" diff --git a/Clue/ClueController.h b/Clue/ClueController.h deleted file mode 100644 index c0b0bed..0000000 --- a/Clue/ClueController.h +++ /dev/null @@ -1,62 +0,0 @@ -// -// Clue.h -// Clue -// -// Created by Ahmed Sulaiman on 5/11/16. -// Copyright © 2016 Ahmed Sulaiman. All rights reserved. -// - -#import -#import "CLUOptions.h" - -/** - `ClueController` is a singleton class and main Clue controller which is also the only public interface for framework user. Here user can turn on/off Clue and start/stop report recording. - */ -@interface ClueController : NSObject - -/** - Returns the shared singleton instance of `ClueController` - - @return Shared singleton instance of `ClueController` - */ -+ (instancetype)sharedInstance; - -/** - Start actual recording. - You should call this method directly only if you want to start recording with your own custom UI element. - It's recommended to use `[ClueController handleShake:]` - */ -- (void)startRecording; - -/** - Stop actual recording. - You should call this method directly only if you want to stop recording with your own custom UI element. - It's recommended to use `[ClueController handleShake:]` - */ -- (void)stopRecording; - -/** - Handle shake gesture. Which will start recording if it's stopped or stop it if it's recording now - - @param motion `UIEventSubtype` motion. This method will activate only with `UIEventSubtypeMotionShake` events - */ -- (void)handleShake:(UIEventSubtype)motion; - -/** - Enable Clue. Recording will start only with enabled Clue - */ -- (void)enable; - -/** - Disable Clue. Clue won't start recording if it's disabled. - */ -- (void)disable; - -/** - Enable Clue with `CLUOptions` options (like receiver's email). Recording will start only with enabled Clue - - @param options `CLUOptions` options object which contains configuration properties - */ -- (void)enableWithOptions:(CLUOptions *)options; - -@end diff --git a/Clue/ClueController.m b/Clue/ClueController.m deleted file mode 100644 index 3994d0d..0000000 --- a/Clue/ClueController.m +++ /dev/null @@ -1,270 +0,0 @@ -// -// Clue.m -// Clue -// -// Created by Ahmed Sulaiman on 5/11/16. -// Copyright © 2016 Ahmed Sulaiman. All rights reserved. -// - -#import "ClueController.h" - -#import "CLUReportFileManager.h" -#import "CLUMailHelper.h" -#import "CLUMailDelegate.h" -#import "CLURecordIndicatorViewManager.h" - -#import - -@interface ClueController() - -@property (nonatomic) BOOL isEnabled; -@property (nonatomic) BOOL isRecording; -@property (nonatomic) CLUOptions *options; -@property (nonatomic) ReportComposer *reportComposer; -@property (nonatomic) CLUMailDelegate *mailDelegate; - -@end - -@implementation ClueController { - dispatch_queue_t _waitVideoRenderingQueue; -} - -- (instancetype)init { - self = [super init]; - if (!self) { - return nil; - } - _isEnabled = NO; - NSMutableArray *modulesArray = [self configureRecordableModules]; - NSMutableArray *infoModulesArray = [self configureInfoModules]; - _reportComposer = [[ReportComposer alloc] initWith:modulesArray]; - [_reportComposer setInfoModules:infoModulesArray]; - NSSetUncaughtExceptionHandler(&didReceiveUncaughtException); - _waitVideoRenderingQueue = dispatch_queue_create("ClueController.waitVideoRenderingQueue", DISPATCH_QUEUE_SERIAL); - _mailDelegate = [[CLUMailDelegate alloc] init]; - - return self; -} - -+ (instancetype)sharedInstance { - static dispatch_once_t once; - static ClueController *instance; - dispatch_once(&once, ^{ - instance = [[ClueController alloc] init]; - }); - return instance; -} - -void didReceiveUncaughtException(NSException *exception) { - [[ClueController sharedInstance] handleException:exception]; -} - -- (void)enable { - [self enableWithOptions:nil]; -} - -- (void)enableWithOptions:(CLUOptions *)options { - if (!_isEnabled) { - _isEnabled = YES; - [self configureWithOptions:options]; - } -} - -- (void)disable { - if (_isEnabled) { - _isEnabled = NO; - // TODO: clear everything redundant - } -} - -- (void)configureWithOptions:(nullable CLUOptions *)options { - if (!options) { - options = [[CLUOptions alloc] init]; - } - _options = options; -} - -- (void)handleException:(NSException *)exception { - if (!exception || !_isEnabled || !_isRecording) { - return; - } - - NSURL *infoModulesDirectory = [[CLUReportFileManager sharedManager] infoModulesDirectoryURL]; - NSURL *outputURL = [infoModulesDirectory URLByAppendingPathComponent:@"info_exception.json"]; - JSONWriter *dataWriter = [[JSONWriter alloc] initWithOutputURL:outputURL]; - ExceptionInfoModule *exceptionModule = [[ExceptionInfoModule alloc] initWithWriter:dataWriter exception:exception]; - [exceptionModule recordInfoData]; - - dispatch_sync(_waitVideoRenderingQueue, ^{ - [self stopRecording]; - [[CLUReportFileManager sharedManager] createZipReportFile]; - // Crazy hack! If exception occurs wait till video writer finish async handler -[AVAssetWriter finishWritingWithCompletionHandler] - // TODO: come up with better approach - [NSThread sleepForTimeInterval:4]; - }); -} - -- (void)handleShake:(UIEventSubtype)motion { - if (motion != UIEventSubtypeMotionShake || !_isEnabled) { - // TODO: print warning message - return; - } - if (_isRecording) { - [self stopRecording]; - } else { - [self startRecording]; - } -} - -- (void)startRecording { - UIViewController *currentViewController = [CLURecordIndicatorViewManager currentViewController]; - // If user has previous report file (caused by exception) suggest him to resend it - if ([[CLUReportFileManager sharedManager] isReportZipFileAvailable]) { - [self showAlertWithTitle:@"Send Previous Clue Report" - message:@"Do you want to send your previous Clue Report caused by internal excpetion?" - successActionTitle:@"Send Report" - failureActionTitle:@"Delete Report" - successHandler:^{ - [self sendReportWithEmailService]; - } failureHandler:^{ - [[CLUReportFileManager sharedManager] removeReportZipFile]; - } - inViewController:currentViewController]; - return; - } - - if (!_isRecording) { - _isRecording = YES; - [_reportComposer startRecording]; - NSDateComponents *maxTime = [CLURecordIndicatorViewManager defaultMaxTime]; - [CLURecordIndicatorViewManager showRecordIndicatorInViewController:currentViewController - withMaxTime:maxTime - target:self - andAction:@selector(stopRecording)]; - } -} - -- (void)stopRecording { - if (_isRecording) { - _isRecording = NO; - [_reportComposer stopRecording]; - - [CLURecordIndicatorViewManager switchRecordIndicatorToWaitingMode]; - // Delay before zipping report, video rendering have to end properly - ClueController __weak *weakSelf = self; - dispatch_async(_waitVideoRenderingQueue, ^{ - // TODO: come up with better approach - [NSThread sleepForTimeInterval:4]; - dispatch_sync(dispatch_get_main_queue(), ^{ - [CLURecordIndicatorViewManager hideRecordIndicator]; - }); - [weakSelf sendReportWithEmailService]; - }); - } -} - -- (void)sendReportWithEmailService { - UIViewController *currentViewController = [CLURecordIndicatorViewManager currentViewController]; - CLUMailHelper *mailHelper = [[CLUMailHelper alloc] initWithOption:_options]; - [mailHelper setMailDelegate:_mailDelegate]; - // TODO: test it on real device. Mail isn't working on simulator - if (currentViewController) { - [mailHelper showMailComposeWindowWithViewController:currentViewController]; - } -} - -- (void)showAlertWithTitle:(NSString *)title - message:(NSString *)message - successActionTitle:(NSString *)successActionTitle - failureActionTitle:(NSString *)failureActionTitle - successHandler:(void (^)())success - failureHandler:(void (^)())failure - inViewController:(UIViewController *)viewController { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *successAction = [UIAlertAction actionWithTitle:successActionTitle - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * _Nonnull action) { - if (success) { - success(); - } - }]; - UIAlertAction *failureAction = [UIAlertAction actionWithTitle:failureActionTitle - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * _Nonnull action) { - if (failure) { - failure(); - } - }]; - [alertController addAction:successAction]; - [alertController addAction:failureAction]; - [viewController presentViewController:alertController animated:YES completion:nil]; -} - -- (NSMutableArray *)configureRecordableModules { - VideoModule *videoModule = [self configureVideoModule]; - ViewStructureModule *viewStructureModule = [self configureViewStructureModule]; - UserInteractionModule *userInteractionModule = [self configureUserInteractionModule]; - NetworkModule *networkModule = [self configureNetworkModule]; - - NSMutableArray *modulesArray = [[NSMutableArray alloc] initWithObjects:videoModule, - viewStructureModule, - userInteractionModule, - networkModule, nil]; - return modulesArray; -} - -- (NSMutableArray *)configureInfoModules { - DeviceInfoModule *deviceModule = [self configureDeviceInfoModule]; - NSMutableArray *modulesArray = [[NSMutableArray alloc] initWithObjects:deviceModule, nil]; - return modulesArray; -} - -#pragma mark - Configure Recoradble modules - -- (VideoModule *)configureVideoModule { - NSURL *recordableModulesDirectory = [[CLUReportFileManager sharedManager] recordableModulesDirectoryURL]; - CGSize viewSize = [UIApplication sharedApplication].delegate.window.bounds.size; - CGFloat viewScale = [UIScreen mainScreen].scale; - NSURL *outputURL = [recordableModulesDirectory URLByAppendingPathComponent:@"module_video.mp4"]; - VideoWriter *videoWriter = [[VideoWriter alloc] initWithOutputURL:outputURL viewSize:viewSize viewScale:viewScale]; - VideoModule *videoModule = [[VideoModule alloc] initWithWriter:videoWriter]; - return videoModule; -} - -- (ViewStructureModule *)configureViewStructureModule { - NSURL *recordableModulesDirectory = [[CLUReportFileManager sharedManager] recordableModulesDirectoryURL]; - NSURL *outputURL = [recordableModulesDirectory URLByAppendingPathComponent:@"module_view.json"]; - JSONWriter *dataWriter = [[JSONWriter alloc] initWithOutputURL:outputURL]; - ViewStructureModule *viewStructureModule = [[ViewStructureModule alloc] initWithWriter:dataWriter]; - return viewStructureModule; -} - -- (UserInteractionModule *)configureUserInteractionModule { - NSURL *recordableModulesDirectory = [[CLUReportFileManager sharedManager] recordableModulesDirectoryURL]; - NSURL *outputURL = [recordableModulesDirectory URLByAppendingPathComponent:@"module_interaction.json"]; - JSONWriter *dataWriter = [[JSONWriter alloc] initWithOutputURL:outputURL]; - UserInteractionModule *userInteractionModule = [[UserInteractionModule alloc] initWithWriter:dataWriter]; - return userInteractionModule; -} - -- (NetworkModule *)configureNetworkModule { - NSURL *recordableModulesDirectory = [[CLUReportFileManager sharedManager] recordableModulesDirectoryURL]; - NSURL *outputURL = [recordableModulesDirectory URLByAppendingPathComponent:@"module_network.json"]; - JSONWriter *dataWriter = [[JSONWriter alloc] initWithOutputURL:outputURL]; - NetworkModule *networkModule = [[NetworkModule alloc] initWithWriter:dataWriter]; - return networkModule; -} - -#pragma mark - Configure Info modules - -- (DeviceInfoModule *)configureDeviceInfoModule { - NSURL *infoModulesDirectory = [[CLUReportFileManager sharedManager] infoModulesDirectoryURL]; - NSURL *outputURL = [infoModulesDirectory URLByAppendingPathComponent:@"info_device.json"]; - JSONWriter *dataWriter = [[JSONWriter alloc] initWithOutputURL:outputURL]; - DeviceInfoModule *deviceModule = [[DeviceInfoModule alloc] initWithWriter:dataWriter]; - return deviceModule; -} - -@end diff --git a/ClueExampleApp/AppDelegate.m b/ClueExampleApp/AppDelegate.m index ac4524b..4b18dd6 100644 --- a/ClueExampleApp/AppDelegate.m +++ b/ClueExampleApp/AppDelegate.m @@ -17,7 +17,7 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [[ClueController sharedInstance] enableWithOptions:[CLUOptions optionsWithEmail:@"ahmed.sulajman@gmail.com"]]; + [[ClueController shared] enableWith:[CLUOptions optionsWithEmail:@"ahmed.sulajman@gmail.com"]]; return YES; } diff --git a/ClueExampleApp/ViewController.m b/ClueExampleApp/ViewController.m index bef41db..f85ea1f 100644 --- a/ClueExampleApp/ViewController.m +++ b/ClueExampleApp/ViewController.m @@ -17,7 +17,7 @@ @implementation ViewController // Handle shake gesture to start/stop recording - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { - [[ClueController sharedInstance] handleShake:motion]; + [[ClueController shared] handleShake:motion]; } - (void)viewDidLoad { diff --git a/ClueSwiftExampleApp/AppDelegate.swift b/ClueSwiftExampleApp/AppDelegate.swift index 4fcd6f0..ea5bfc2 100644 --- a/ClueSwiftExampleApp/AppDelegate.swift +++ b/ClueSwiftExampleApp/AppDelegate.swift @@ -22,7 +22,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele splitViewController.delegate = self // Enable Clue - ClueController.sharedInstance().enable(with: CLUOptions(email: "ahmed.sulajman@gmail.com")) + ClueController.shared.enable(with: CLUOptions(email: "ahmed.sulajman@gmail.com")) return true } diff --git a/ClueSwiftExampleApp/MasterViewController.swift b/ClueSwiftExampleApp/MasterViewController.swift index f4c600d..216b532 100644 --- a/ClueSwiftExampleApp/MasterViewController.swift +++ b/ClueSwiftExampleApp/MasterViewController.swift @@ -16,7 +16,7 @@ class MasterViewController: UITableViewController { // Handle shake gesture to start/stop recording override func motionBegan(_ motion: UIEventSubtype, with event: UIEvent?) { - ClueController.sharedInstance().handleShake(motion); + ClueController.shared.handleShake(motion); } override func viewDidLoad() { From b932c47342cda883e3da6e079b9d9624f9363661 Mon Sep 17 00:00:00 2001 From: Andrea Prearo Date: Wed, 14 Jun 2017 08:05:34 -0700 Subject: [PATCH 6/6] Update Travis CI configuration Changes: - Update simulator version to 10.3.1. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3d6b286..f416494 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,4 @@ branches: - /^\d+\.\d+(\.\d+)?(-\S*)?$/ # Execute tests for every tag with name in format "1.2.3" - develop script: - - xcodebuild test -project Clue.xcodeproj -scheme ClueTests -destination "platform=iOS Simulator,name=iPhone 7,OS=10.3" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO + - xcodebuild test -project Clue.xcodeproj -scheme ClueTests -destination "platform=iOS Simulator,name=iPhone 7,OS=10.3.1" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO