From 46460920551297e4152f4adf4fb348083ae3cd2a Mon Sep 17 00:00:00 2001 From: Rychagov Evgeny Date: Wed, 15 Mar 2017 14:46:55 +0300 Subject: [PATCH 01/76] Added base DB logic --- .../MobileCenter.xcodeproj/project.pbxproj | 28 ++- .../Internals/Channel/MSLogManagerDefault.m | 5 +- .../Internals/Storage/MSDBStorage.h | 6 + .../Internals/Storage/MSDBStorage.m | 102 +++++++++ .../Internals/Storage/MSDatabaseConnection.h | 10 + .../Internals/Storage/MSSqliteConnection.h | 5 + .../Internals/Storage/MSSqliteConnection.m | 196 ++++++++++++++++++ Vendor/SQLite/libtclsqlite3.dylib | Bin 0 -> 110720 bytes 8 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h create mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m create mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h create mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.h create mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m create mode 100755 Vendor/SQLite/libtclsqlite3.dylib diff --git a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj index c8825e0246..0f582d27e1 100644 --- a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj +++ b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj @@ -112,10 +112,16 @@ B2FD53651E567BCF0050F909 /* MSDeviceHistoryInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B2FD53641E567BCF0050F909 /* MSDeviceHistoryInfoTests.m */; }; BA68281EA947BB6E7F78354F /* MSIngestionSender.h in Headers */ = {isa = PBXBuildFile; fileRef = BA6824A001520825F18DFC42 /* MSIngestionSender.h */; }; BA682E63DDB55EED56809831 /* MSIngestionSender.m in Sources */ = {isa = PBXBuildFile; fileRef = BA6822A89D38F04D8D95CE83 /* MSIngestionSender.m */; }; + D341DBCE1E77F04300D385F9 /* MSDBStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = D341DBCD1E77EF8600D385F9 /* MSDBStorage.h */; }; + D341DBD61E77FD8B00D385F9 /* MSDBStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D341DBD51E77FD8B00D385F9 /* MSDBStorage.m */; }; + D35D61821E785FBA00D81A0F /* MSSqliteConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = D35D61811E785FBA00D81A0F /* MSSqliteConnection.m */; }; + D35D61841E785FD500D81A0F /* MSDatabaseConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D35D61831E785FD500D81A0F /* MSDatabaseConnection.h */; }; + D35D61851E78617400D81A0F /* MSSqliteConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D35D617F1E785FAB00D81A0F /* MSSqliteConnection.h */; }; + D35D618E1E79397D00D81A0F /* libtclsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D35D618D1E79397D00D81A0F /* libtclsqlite3.dylib */; }; D38023E81E6EFC7C00466558 /* MSStartServiceLog.m in Sources */ = {isa = PBXBuildFile; fileRef = D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */; }; D38023E91E6EFC7C00466558 /* MSStartServiceLog.h in Headers */ = {isa = PBXBuildFile; fileRef = D38023E71E6EFC7C00466558 /* MSStartServiceLog.h */; }; - D38024121E7130C700466558 /* MSStartServiceLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D38024111E7130C700466558 /* MSStartServiceLogTests.m */; }; D38024061E7126F500466558 /* MSServiceAbstract.m in Sources */ = {isa = PBXBuildFile; fileRef = D38024051E7126F500466558 /* MSServiceAbstract.m */; }; + D38024121E7130C700466558 /* MSStartServiceLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D38024111E7130C700466558 /* MSStartServiceLogTests.m */; }; E8010E6A1D2DD4EF0035196F /* MSLogWithProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = E8010E661D2DD4EF0035196F /* MSLogWithProperties.h */; }; E8010E6B1D2DD4EF0035196F /* MSLogWithProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = E8010E671D2DD4EF0035196F /* MSLogWithProperties.m */; }; E80EB1071D50273700C9003F /* MSSenderCall.h in Headers */ = {isa = PBXBuildFile; fileRef = E80EB1051D50273700C9003F /* MSSenderCall.h */; }; @@ -272,10 +278,16 @@ B2FD53641E567BCF0050F909 /* MSDeviceHistoryInfoTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDeviceHistoryInfoTests.m; sourceTree = ""; }; BA6822A89D38F04D8D95CE83 /* MSIngestionSender.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSIngestionSender.m; sourceTree = ""; }; BA6824A001520825F18DFC42 /* MSIngestionSender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSIngestionSender.h; sourceTree = ""; }; + D341DBCD1E77EF8600D385F9 /* MSDBStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSDBStorage.h; sourceTree = ""; }; + D341DBD51E77FD8B00D385F9 /* MSDBStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDBStorage.m; sourceTree = ""; }; + D35D617F1E785FAB00D81A0F /* MSSqliteConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSSqliteConnection.h; sourceTree = ""; }; + D35D61811E785FBA00D81A0F /* MSSqliteConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSSqliteConnection.m; sourceTree = ""; }; + D35D61831E785FD500D81A0F /* MSDatabaseConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSDatabaseConnection.h; sourceTree = ""; }; + D35D618D1E79397D00D81A0F /* libtclsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libtclsqlite3.dylib; path = ../../../../Vendor/SQLite/libtclsqlite3.dylib; sourceTree = ""; }; D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStartServiceLog.m; sourceTree = ""; }; D38023E71E6EFC7C00466558 /* MSStartServiceLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSStartServiceLog.h; sourceTree = ""; }; - D38024111E7130C700466558 /* MSStartServiceLogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStartServiceLogTests.m; sourceTree = ""; }; D38024051E7126F500466558 /* MSServiceAbstract.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSServiceAbstract.m; sourceTree = ""; }; + D38024111E7130C700466558 /* MSStartServiceLogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStartServiceLogTests.m; sourceTree = ""; }; E8010E661D2DD4EF0035196F /* MSLogWithProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSLogWithProperties.h; sourceTree = ""; }; E8010E671D2DD4EF0035196F /* MSLogWithProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSLogWithProperties.m; sourceTree = ""; }; E80EB1051D50273700C9003F /* MSSenderCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSSenderCall.h; sourceTree = ""; }; @@ -312,6 +324,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D35D618E1E79397D00D81A0F /* libtclsqlite3.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -544,6 +557,7 @@ E83283C21D46C62E000B029E /* Vendor */ = { isa = PBXGroup; children = ( + D35D618D1E79397D00D81A0F /* libtclsqlite3.dylib */, E83283C31D46C62E000B029E /* Reachability */, ); path = Vendor; @@ -590,6 +604,11 @@ 6E7E3B771D2DCEBD0019EA6D /* MSFile.m */, E8A8D1E91D3057A90022931E /* MSUserDefaults.h */, E8A8D1EA1D3057A90022931E /* MSUserDefaults.m */, + D341DBCD1E77EF8600D385F9 /* MSDBStorage.h */, + D341DBD51E77FD8B00D385F9 /* MSDBStorage.m */, + D35D617F1E785FAB00D81A0F /* MSSqliteConnection.h */, + D35D61811E785FBA00D81A0F /* MSSqliteConnection.m */, + D35D61831E785FD500D81A0F /* MSDatabaseConnection.h */, ); path = Storage; sourceTree = ""; @@ -632,6 +651,7 @@ E8A8D1EB1D3057A90022931E /* MSUserDefaults.h in Headers */, E8831A9A1D41A58600931F11 /* MSDeviceTrackerPrivate.h in Headers */, 38F1944E1DADB93100D3E0FE /* MSHttpSenderPrivate.h in Headers */, + D35D61841E785FD500D81A0F /* MSDatabaseConnection.h in Headers */, E80EB1071D50273700C9003F /* MSSenderCall.h in Headers */, 387C758F1D64E50800D68CC1 /* MSServiceCommon.h in Headers */, B2C3C1901DB9864600CB83F7 /* MSDevice.h in Headers */, @@ -650,6 +670,7 @@ E8010E6A1D2DD4EF0035196F /* MSLogWithProperties.h in Headers */, 6E04016D1D1C9E460051BCFA /* MobileCenter+Internal.h in Headers */, 6E0684671D36BCD500A8CC6C /* MSChannel.h in Headers */, + D35D61851E78617400D81A0F /* MSSqliteConnection.h in Headers */, 384772A61DA5691F009365DE /* MSSenderDelegate.h in Headers */, D38023E91E6EFC7C00466558 /* MSStartServiceLog.h in Headers */, 387C76FD1D6C9CF100D68CC1 /* MSServiceAbstract.h in Headers */, @@ -676,6 +697,7 @@ E84B8E321D235209006FD231 /* MSSender.h in Headers */, E84B8E2E1D2351DB006FD231 /* MSLogManager.h in Headers */, 385AD9221D95898D008B354A /* MSServiceAbstractProtected.h in Headers */, + D341DBCE1E77F04300D385F9 /* MSDBStorage.h in Headers */, 38EAD9AC1DA6A8C500CE6030 /* MSEnable.h in Headers */, 6E2BA1AF1D2499F9002D7B88 /* MSFileUtil.h in Headers */, 381C91E91D3DB65D004512F1 /* MSDeviceTracker.h in Headers */, @@ -816,6 +838,7 @@ files = ( 6E0401571D1C9AAA0051BCFA /* MSMobileCenter.m in Sources */, 6E2BA1B01D2499F9002D7B88 /* MSFileUtil.m in Sources */, + D341DBD61E77FD8B00D385F9 /* MSDBStorage.m in Sources */, 3592ABA81DC90E3600EF4592 /* MSLogger.m in Sources */, 6EF628F51D371B1600CAFF64 /* MSChannelConfiguration.m in Sources */, 6E171B611D234717000DC480 /* MSLogContainer.m in Sources */, @@ -831,6 +854,7 @@ 381C91EA1D3DB65D004512F1 /* MSDeviceTracker.m in Sources */, 045BC3171E3FD88600B6C960 /* MSKeychainUtil.m in Sources */, 35D0B7541DDFABFD003EACCD /* MSWrapperLogger.m in Sources */, + D35D61821E785FBA00D81A0F /* MSSqliteConnection.m in Sources */, 38CA60ED1D949F5000B82420 /* MSFileStorage.m in Sources */, 6E0684641D36BC8D00A8CC6C /* MSChannelDefault.m in Sources */, E80EB1081D50273700C9003F /* MSSenderCall.m in Sources */, diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m index e3cc255b7f..a479618839 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m @@ -1,11 +1,12 @@ +#import "MobileCenter+Internal.h" #import "MSChannelDefault.h" +#import "MSDBStorage.h" #import "MSFileStorage.h" #import "MSHttpSender.h" #import "MSIngestionSender.h" #import "MSLogManagerDefault.h" #import "MSLogManagerDefaultPrivate.h" #import "MSMobileCenterErrors.h" -#import "MobileCenter+Internal.h" static char *const MSlogsDispatchQueue = "com.microsoft.azure.mobile.mobilecenter.LogManagerQueue"; @@ -37,7 +38,7 @@ - (instancetype)initWithAppSecret:(NSString *)appSecret installId:(NSUUID *)inst } reachability:[MS_Reachability reachabilityForInternetConnection] retryIntervals:@[ @(10), @(5 * 60), @(20 * 60) ]] - storage:[[MSFileStorage alloc] init]]; + storage:[[MSDBStorage alloc] init]]; return self; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h new file mode 100644 index 0000000000..d16e7a20cb --- /dev/null +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h @@ -0,0 +1,6 @@ +#import +#import "MSStorage.h" + +@interface MSDBStorage : NSObject + +@end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m new file mode 100644 index 0000000000..29d5175e9e --- /dev/null +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -0,0 +1,102 @@ +#import "MSDatabaseConnection.h" +#import "MSDBStorage.h" +#import "MSSqliteConnection.h" + +static NSString *const kMSLogEntityName = @"MSDBLog"; +static NSString *const kMSDBFileName = @"MSDBLogs.sqlite"; +static NSString *const kMSLogTableName = @"MSLog"; +static NSString *const kMSStorageKeyColumnName = @"storageKey"; +static NSString *const kMSDataColumnName = @"data"; + +@interface MSDBStorage() + +@property (nonatomic) id connection; + +@end + +@implementation MSDBStorage + +@synthesize bucketFileCountLimit; +@synthesize bucketFileLogCountLimit; +@synthesize connection; + +#pragma mark - Initialization + +- (instancetype)init { + self = [super init]; + if (self) { + self.connection = [[MSSqliteConnection alloc] initWithDatabaseFilename:kMSDBFileName]; + [self initTables]; + } + return self; +} + +-(void)initTables { + NSString *createLogTableQuery = [NSString stringWithFormat:@"create table if not exists %@ (%@ text, %@ blob);", + kMSLogTableName, kMSStorageKeyColumnName, kMSDataColumnName]; + [self.connection executeQuery:createLogTableQuery]; +} + +#pragma mark - Public + +- (BOOL)saveLog:(id )log withStorageKey:(NSString *)storageKey { + if (!log) { + return NO; + } + + NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; + + NSString *addLogQuery = [NSString stringWithFormat:@"insert or replace into %@ values (%@, %@)", + kMSLogTableName, storageKey, logData]; + + return [self.connection executeQuery:addLogQuery]; +} + +- (NSArray *)deleteLogsForStorageKey:(NSString *)storageKey { + NSArray *logs = [self getLogsWith:storageKey]; + [self deleteLogsWith:storageKey]; + return logs; +} + +- (void)deleteLogsForId:(NSString *)logsId withStorageKey:(NSString *)storageKey { + + // FIXME: logsId ? + [self deleteLogsWith:storageKey]; +} + +- (BOOL)loadLogsForStorageKey:(NSString *)storageKey withCompletion:(nullable MSLoadDataCompletionBlock)completion { + NSArray *logs = [self getLogsWith:storageKey]; + + if (completion) { + + // FIXME: batchId ? + completion(logs.count > 0, logs, nil); + } + + return logs.count > 0; +} + +- (void)closeBatchWithStorageKey:(NSString *)storageKey { + // TODO: +} + +- (NSArray*) getLogsWith:(NSString*)storageKey { + NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == %@", + kMSLogTableName, kMSStorageKeyColumnName, storageKey]; + NSArray *result = [self.connection loadDataFromDB:selectLogQuery]; + NSMutableArray *logs = [NSMutableArray arrayWithCapacity:result.count]; + for (NSArray *row in result) { + NSString *data = row[1]; + + [logs addObject:[NSKeyedUnarchiver unarchiveObjectWithData:data]]; + } + return logs; +} + +- (void) deleteLogsWith:(NSString*)storageKey { + NSString *deleteLogQuery = [NSString stringWithFormat:@"delete from %@ where %@ == %@", + kMSLogTableName, kMSStorageKeyColumnName, storageKey]; + [self.connection executeQuery:deleteLogQuery]; +} + +@end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h new file mode 100644 index 0000000000..c160c910e3 --- /dev/null +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h @@ -0,0 +1,10 @@ +#import + +@protocol MSDatabaseConnection + +-(instancetype)initWithDatabaseFilename:(NSString *)dbFilename; + +-(BOOL)executeQuery:(NSString *)query; +-(NSArray *)loadDataFromDB:(NSString *)query; + +@end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.h b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.h new file mode 100644 index 0000000000..e8723ddc56 --- /dev/null +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.h @@ -0,0 +1,5 @@ +#import "MSDatabaseConnection.h" + +@interface MSSqliteConnection : NSObject + +@end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m new file mode 100644 index 0000000000..8a00776c91 --- /dev/null +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -0,0 +1,196 @@ +#import +#import +#import "MSSqliteConnection.h" + +@interface MSSqliteConnection() + +@property (nonatomic, strong) NSString *documentsDirectory; +@property (nonatomic, strong) NSString *databaseFilename; +@property (nonatomic, strong) NSMutableArray *arrResults; +@property (nonatomic, strong) NSMutableArray *arrColumnNames; +@property (nonatomic) int affectedRows; +@property (nonatomic) long long lastInsertedRowID; + +@end + +@implementation MSSqliteConnection + +@synthesize documentsDirectory; +@synthesize databaseFilename; + +-(instancetype)initWithDatabaseFilename:(NSString *)dbFilename { + self = [super init]; + if (self) { + + // Set the documents directory path to the documentsDirectory property. + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + self.documentsDirectory = [paths objectAtIndex:0]; + + // Keep the database filename. + self.databaseFilename = dbFilename; + + // Copy the database file into the documents directory if necessary. + [self copyDatabaseIntoDocumentsDirectory]; + } + return self; +} + +#pragma mark - Private + +-(void)copyDatabaseIntoDocumentsDirectory{ + + // Check if the database file exists in the documents directory. + NSString *destinationPath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename]; + if (![[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) { + + // The database file does not exist in the documents directory, so copy it from the main bundle now. + NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:self.databaseFilename]; + NSError *error; + [[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:destinationPath error:&error]; + + // Check if any error occurred during copying and display it. + if (error != nil) { + NSLog(@"%@", [error localizedDescription]); + } + } +} + + +-(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ + + // Total query result + BOOL result = YES; + + // Create a sqlite object. + sqlite3 *sqlite3Database; + + // Set the database file path. + NSString *databasePath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename]; + + // Initialize the results array. + if (self.arrResults != nil) { + [self.arrResults removeAllObjects]; + self.arrResults = nil; + } + self.arrResults = [[NSMutableArray alloc] init]; + + // Initialize the column names array. + if (self.arrColumnNames != nil) { + [self.arrColumnNames removeAllObjects]; + self.arrColumnNames = nil; + } + self.arrColumnNames = [[NSMutableArray alloc] init]; + + // Open the database. + BOOL openDatabaseResult = (BOOL) sqlite3_open([databasePath UTF8String], &sqlite3Database); + if(openDatabaseResult == SQLITE_OK) { + + // Declare a sqlite3_stmt object in which will be stored the query after having been compiled into a SQLite statement. + sqlite3_stmt *compiledStatement; + + // Load all data from database to memory. + BOOL prepareStatementResult = (BOOL) sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL); + if(prepareStatementResult == SQLITE_OK) { + + // Check if the query is non-executable. + if (!queryExecutable) { + + // In this case data must be loaded from the database. + + // Declare an array to keep the data for each fetched row. + NSMutableArray *arrDataRow; + + // Loop through the results and add them to the results array row by row. + while(sqlite3_step(compiledStatement) == SQLITE_ROW) { + + // Initialize the mutable array that will contain the data of a fetched row. + arrDataRow = [[NSMutableArray alloc] init]; + + // Get the total number of columns. + unsigned int totalColumns = sqlite3_column_count(compiledStatement); + + // Go through all columns and fetch each column data. + for (unsigned int i = 0; i < totalColumns; ++i) { + + // Convert the column data to text (characters). + const char *dbDataAsChars = (const char*) sqlite3_column_text(compiledStatement, i); + + // If there are contents in the currenct column (field) then add them to the current row array. + if (dbDataAsChars != NULL) { + + // Convert the characters to string. + [arrDataRow addObject:[NSString stringWithUTF8String:dbDataAsChars]]; + } + + // Keep the current column name. + if (self.arrColumnNames.count != totalColumns) { + dbDataAsChars = sqlite3_column_name(compiledStatement, i); + [self.arrColumnNames addObject:[NSString stringWithUTF8String:dbDataAsChars]]; + } + } + + // Store each fetched data row in the results array, but first check if there is actually data. + if (arrDataRow.count > 0) { + [self.arrResults addObject:arrDataRow]; + } + } + } else { + + // This is the case of an executable query (insert, update, ...). + + // Execute the query. + BOOL executeQueryResults = (BOOL) sqlite3_step(compiledStatement); + if (executeQueryResults == SQLITE_DONE) { + + // Keep the affected rows. + self.affectedRows = sqlite3_changes(sqlite3Database); + + // Keep the last inserted row ID. + self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database); + } else { + + // If could not execute the query show the error message on the debugger. + NSLog(@"DB Error: %s", sqlite3_errmsg(sqlite3Database)); + + result = NO; + } + } + } else { + + // In the database cannot be opened then show the error message on the debugger. + NSLog(@"%s", sqlite3_errmsg(sqlite3Database)); + + result = NO; + } + + // Release the compiled statement from memory. + sqlite3_finalize(compiledStatement); + } else { + result = NO; + } + + // Close the database. + sqlite3_close(sqlite3Database); + + return result; +} + +#pragma mark - Public method implementation + +-(NSArray *)loadDataFromDB:(NSString *)query{ + + // Run the query and indicate that is not executable. + // The query string is converted to a char* object. + [self runQuery:[query UTF8String] isQueryExecutable:NO]; + + // Returned the loaded results. + return (NSArray *)self.arrResults; +} + +-(BOOL)executeQuery:(NSString *)query{ + + // Run the query and indicate that is executable. + return [self runQuery:[query UTF8String] isQueryExecutable:YES]; +} + +@end diff --git a/Vendor/SQLite/libtclsqlite3.dylib b/Vendor/SQLite/libtclsqlite3.dylib new file mode 100755 index 0000000000000000000000000000000000000000..d7d2d9866f1e3e6aa73f7a4177a236e8e7348717 GIT binary patch literal 110720 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 Date: Thu, 16 Mar 2017 15:26:13 +0300 Subject: [PATCH 02/76] Base DB logic --- Demo/Demo.xcodeproj/project.pbxproj | 28 ++++++++++- .../MobileCenter.xcodeproj/project.pbxproj | 2 + .../Internals/Storage/MSDBStorage.m | 48 +++++++++++++++---- .../Internals/Storage/MSDatabaseConnection.h | 2 +- .../Internals/Storage/MSSqliteConnection.m | 12 ++--- MobileCenter/MobileCenter/MSDBLogs.sqlite | 0 6 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 MobileCenter/MobileCenter/MSDBLogs.sqlite diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index 79bca42b87..26edf36426 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ 6E2307E31D22083C0031C598 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E2307E11D22083C0031C598 /* Main.storyboard */; }; 6E2307E51D22083C0031C598 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6E2307E41D22083C0031C598 /* Assets.xcassets */; }; 6E2307E81D22083C0031C598 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E2307E61D22083C0031C598 /* LaunchScreen.storyboard */; }; + D36136721E7A79D7004AE043 /* libtclsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D36136711E7A79D7004AE043 /* libtclsqlite3.dylib */; }; + D36136731E7A7AF5004AE043 /* libtclsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D36136711E7A79D7004AE043 /* libtclsqlite3.dylib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -70,6 +72,7 @@ 6E2307E41D22083C0031C598 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6E2307E71D22083C0031C598 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 6E2307E91D22083C0031C598 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D36136711E7A79D7004AE043 /* libtclsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libtclsqlite3.dylib; path = ../Vendor/SQLite/libtclsqlite3.dylib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -77,6 +80,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D36136721E7A79D7004AE043 /* libtclsqlite3.dylib in Frameworks */, 382EC1DB1DDB805400A60DBD /* NotificationCenter.framework in Frameworks */, 382EC2001DDBBB6800A60DBD /* MobileCenterCrashes.framework in Frameworks */, 382EC2011DDBBB6800A60DBD /* MobileCenterAnalytics.framework in Frameworks */, @@ -88,6 +92,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D36136731E7A7AF5004AE043 /* libtclsqlite3.dylib in Frameworks */, 04CCCA481D88AA9F005D8180 /* MobileCenterCrashes.framework in Frameworks */, 04CCCA461D88AA93005D8180 /* MobileCenterAnalytics.framework in Frameworks */, 04CCCA441D88AA86005D8180 /* MobileCenter.framework in Frameworks */, @@ -100,6 +105,7 @@ 382EC1D91DDB805400A60DBD /* Frameworks */ = { isa = PBXGroup; children = ( + D36136711E7A79D7004AE043 /* libtclsqlite3.dylib */, 382EC1DA1DDB805400A60DBD /* NotificationCenter.framework */, ); name = Frameworks; @@ -222,11 +228,11 @@ TargetAttributes = { 382EC1D71DDB805400A60DBD = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = L4RARDNZ2Y; + DevelopmentTeam = 285W24646W; }; 6E2307D41D22083C0031C598 = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = L4RARDNZ2Y; + DevelopmentTeam = 285W24646W; }; }; }; @@ -330,11 +336,14 @@ 382EC1E71DDB805400A60DBD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + DEVELOPMENT_TEAM = 285W24646W; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)/**"; INFOPLIST_FILE = DemoWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + OTHER_LDFLAGS = "-lsqlite3"; + OTHER_LIBTOOLFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.demo.DemoWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -344,11 +353,14 @@ 382EC1E81DDB805400A60DBD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + DEVELOPMENT_TEAM = 285W24646W; ENABLE_BITCODE = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)/**"; INFOPLIST_FILE = DemoWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + OTHER_LDFLAGS = "-lsqlite3"; + OTHER_LIBTOOLFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.demo.DemoWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -396,9 +408,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ""; IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../Vendor/**"; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "-lsqlite3"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -439,8 +454,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ""; IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../Vendor/**"; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = "-lsqlite3"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -452,11 +470,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Brand Assets"; + DEVELOPMENT_TEAM = 285W24646W; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Demo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-lsqlite3"; + OTHER_LIBTOOLFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.demo; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -467,11 +488,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "Brand Assets"; + DEVELOPMENT_TEAM = 285W24646W; ENABLE_BITCODE = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Demo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-lsqlite3"; + OTHER_LIBTOOLFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.demo; PRODUCT_NAME = "$(TARGET_NAME)"; }; diff --git a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj index 0f582d27e1..88bf12d8c8 100644 --- a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj +++ b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj @@ -284,6 +284,7 @@ D35D61811E785FBA00D81A0F /* MSSqliteConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSSqliteConnection.m; sourceTree = ""; }; D35D61831E785FD500D81A0F /* MSDatabaseConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSDatabaseConnection.h; sourceTree = ""; }; D35D618D1E79397D00D81A0F /* libtclsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libtclsqlite3.dylib; path = ../../../../Vendor/SQLite/libtclsqlite3.dylib; sourceTree = ""; }; + D36136771E7A80E7004AE043 /* MSDBLogs.sqlite */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = MSDBLogs.sqlite; path = MobileCenter/MSDBLogs.sqlite; sourceTree = ""; }; D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStartServiceLog.m; sourceTree = ""; }; D38023E71E6EFC7C00466558 /* MSStartServiceLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSStartServiceLog.h; sourceTree = ""; }; D38024051E7126F500466558 /* MSServiceAbstract.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSServiceAbstract.m; sourceTree = ""; }; @@ -357,6 +358,7 @@ 6E0400FA1D1C98220051BCFA = { isa = PBXGroup; children = ( + D36136771E7A80E7004AE043 /* MSDBLogs.sqlite */, 6E0401051D1C98220051BCFA /* MobileCenter */, 6E2395791D22EF4F00E543C8 /* MobileCenterTests */, 6E0401041D1C98220051BCFA /* Products */, diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 29d5175e9e..0899005eb6 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -1,5 +1,6 @@ #import "MSDatabaseConnection.h" #import "MSDBStorage.h" +#import "MSLogger.h" #import "MSSqliteConnection.h" static NSString *const kMSLogEntityName = @"MSDBLog"; @@ -40,16 +41,26 @@ -(void)initTables { #pragma mark - Public - (BOOL)saveLog:(id )log withStorageKey:(NSString *)storageKey { + + MSLogVerbose(@"DBStorage", @"saving log with storage key %@", storageKey); + if (!log) { return NO; } NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; + NSString *base64Data = [logData base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength]; + + MSLogVerbose(@"MSDBStorage", @"Saving base64String %@", base64Data); + + NSString *addLogQuery = [NSString stringWithFormat:@"insert or replace into %@ values ('%@', '%@')", + kMSLogTableName, storageKey, base64Data]; + + BOOL result = [self.connection executeQuery:addLogQuery]; - NSString *addLogQuery = [NSString stringWithFormat:@"insert or replace into %@ values (%@, %@)", - kMSLogTableName, storageKey, logData]; + MSLogVerbose(@"DBStorage", @"Log has been saved successfully %d", result); - return [self.connection executeQuery:addLogQuery]; + return result; } - (NSArray *)deleteLogsForStorageKey:(NSString *)storageKey { @@ -81,20 +92,39 @@ - (void)closeBatchWithStorageKey:(NSString *)storageKey { } - (NSArray*) getLogsWith:(NSString*)storageKey { - NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == %@", + NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@'", kMSLogTableName, kMSStorageKeyColumnName, storageKey]; - NSArray *result = [self.connection loadDataFromDB:selectLogQuery]; - NSMutableArray *logs = [NSMutableArray arrayWithCapacity:result.count]; + NSArray*> *result = [self.connection loadDataFromDB:selectLogQuery]; + NSMutableArray *logs = [NSMutableArray arrayWithCapacity:100]; + + for (NSArray *row in result) { - NSString *data = row[1]; + NSString *base64Data = row[1]; + NSData *rawData = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters]; + // + MSLogVerbose(@"MSDBStorage", @"rawData not nil? %@", rawData != nil ? @"YES, not nil" : @"NO, it's nil"); + if(rawData) { + MSLogVerbose(@"MSDBStorage", @"rawData: %@", rawData); + } + + NSKeyedUnarchiver *un = [[NSKeyedUnarchiver alloc] initForReadingWithData:rawData]; + MSLogVerbose(@"MSDBStorage", @"un not nil? %@", un != nil ? @"YES, not nil" : @"NO, it's nil"); + + + NSDictionary *result2 =[un dictionaryWithValuesForKeys:row]; + MSLogVerbose(@"MSDBStorage", @"result2 not nil? %@", result2 != nil ? @"YES, not nil" : @"NO, it's nil"); + // + id log = [NSKeyedUnarchiver unarchiveObjectWithData:rawData]; + + MSLogVerbose(@"MSDBStorage", @"Restored log not nil? %@", log != nil ? @"YES, not nil" : @"NO, it's nil"); - [logs addObject:[NSKeyedUnarchiver unarchiveObjectWithData:data]]; + [logs addObject:log]; } return logs; } - (void) deleteLogsWith:(NSString*)storageKey { - NSString *deleteLogQuery = [NSString stringWithFormat:@"delete from %@ where %@ == %@", + NSString *deleteLogQuery = [NSString stringWithFormat:@"delete from %@ where %@ == '%@'", kMSLogTableName, kMSStorageKeyColumnName, storageKey]; [self.connection executeQuery:deleteLogQuery]; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h index c160c910e3..ca3c132d9a 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h @@ -5,6 +5,6 @@ -(instancetype)initWithDatabaseFilename:(NSString *)dbFilename; -(BOOL)executeQuery:(NSString *)query; --(NSArray *)loadDataFromDB:(NSString *)query; +-(NSArray*> *)loadDataFromDB:(NSString *)query; @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index 8a00776c91..ba1bc6dc58 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -6,7 +6,7 @@ @interface MSSqliteConnection() @property (nonatomic, strong) NSString *documentsDirectory; @property (nonatomic, strong) NSString *databaseFilename; -@property (nonatomic, strong) NSMutableArray *arrResults; +@property (nonatomic, strong) NSMutableArray*> *arrResults; @property (nonatomic, strong) NSMutableArray *arrColumnNames; @property (nonatomic) int affectedRows; @property (nonatomic) long long lastInsertedRowID; @@ -72,7 +72,7 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ [self.arrResults removeAllObjects]; self.arrResults = nil; } - self.arrResults = [[NSMutableArray alloc] init]; + self.arrResults = [NSMutableArray*> new]; // Initialize the column names array. if (self.arrColumnNames != nil) { @@ -98,13 +98,13 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // In this case data must be loaded from the database. // Declare an array to keep the data for each fetched row. - NSMutableArray *arrDataRow; + NSMutableArray *arrDataRow; // Loop through the results and add them to the results array row by row. while(sqlite3_step(compiledStatement) == SQLITE_ROW) { // Initialize the mutable array that will contain the data of a fetched row. - arrDataRow = [[NSMutableArray alloc] init]; + arrDataRow = [NSMutableArray new]; // Get the total number of columns. unsigned int totalColumns = sqlite3_column_count(compiledStatement); @@ -177,14 +177,14 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ #pragma mark - Public method implementation --(NSArray *)loadDataFromDB:(NSString *)query{ +-(NSArray*> *)loadDataFromDB:(NSString *)query{ // Run the query and indicate that is not executable. // The query string is converted to a char* object. [self runQuery:[query UTF8String] isQueryExecutable:NO]; // Returned the loaded results. - return (NSArray *)self.arrResults; + return (NSArray*> *)self.arrResults; } -(BOOL)executeQuery:(NSString *)query{ diff --git a/MobileCenter/MobileCenter/MSDBLogs.sqlite b/MobileCenter/MobileCenter/MSDBLogs.sqlite new file mode 100644 index 0000000000..e69de29bb2 From 0268435c28b24fec156045cc9c074e9b86743112 Mon Sep 17 00:00:00 2001 From: Rychagov Evgeny Date: Thu, 16 Mar 2017 18:08:16 +0300 Subject: [PATCH 03/76] Correct save and read mslog data --- .../Internals/Storage/MSDBStorage.m | 35 ++++++++----------- .../Internals/Storage/MSSqliteConnection.m | 3 ++ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 0899005eb6..9c678397ce 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -33,7 +33,7 @@ - (instancetype)init { } -(void)initTables { - NSString *createLogTableQuery = [NSString stringWithFormat:@"create table if not exists %@ (%@ text, %@ blob);", + NSString *createLogTableQuery = [NSString stringWithFormat:@"create table if not exists %@ (%@ text, %@ text);", kMSLogTableName, kMSStorageKeyColumnName, kMSDataColumnName]; [self.connection executeQuery:createLogTableQuery]; } @@ -42,23 +42,26 @@ -(void)initTables { - (BOOL)saveLog:(id )log withStorageKey:(NSString *)storageKey { - MSLogVerbose(@"DBStorage", @"saving log with storage key %@", storageKey); + MSLogVerbose(@"MSDBStorage", @"saving log with storage key %@", storageKey); if (!log) { return NO; } NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; - NSString *base64Data = [logData base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength]; + NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; - MSLogVerbose(@"MSDBStorage", @"Saving base64String %@", base64Data); + ///////// + MSLogVerbose(@"MSDBStorage", @"base64Data: %@", base64Data); + ///////// NSString *addLogQuery = [NSString stringWithFormat:@"insert or replace into %@ values ('%@', '%@')", kMSLogTableName, storageKey, base64Data]; + BOOL result = [self.connection executeQuery:addLogQuery]; - MSLogVerbose(@"DBStorage", @"Log has been saved successfully %d", result); + MSLogVerbose(@"MSDBStorage", @"Log has been saved successfully %d", result); return result; } @@ -81,7 +84,7 @@ - (BOOL)loadLogsForStorageKey:(NSString *)storageKey withCompletion:(nullable MS if (completion) { // FIXME: batchId ? - completion(logs.count > 0, logs, nil); + completion(logs.count > 0, logs, @""); } return logs.count > 0; @@ -91,30 +94,22 @@ - (void)closeBatchWithStorageKey:(NSString *)storageKey { // TODO: } +//------ - (NSArray*) getLogsWith:(NSString*)storageKey { NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@'", kMSLogTableName, kMSStorageKeyColumnName, storageKey]; NSArray*> *result = [self.connection loadDataFromDB:selectLogQuery]; NSMutableArray *logs = [NSMutableArray arrayWithCapacity:100]; - for (NSArray *row in result) { NSString *base64Data = row[1]; - NSData *rawData = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters]; - // - MSLogVerbose(@"MSDBStorage", @"rawData not nil? %@", rawData != nil ? @"YES, not nil" : @"NO, it's nil"); - if(rawData) { - MSLogVerbose(@"MSDBStorage", @"rawData: %@", rawData); - } - - NSKeyedUnarchiver *un = [[NSKeyedUnarchiver alloc] initForReadingWithData:rawData]; - MSLogVerbose(@"MSDBStorage", @"un not nil? %@", un != nil ? @"YES, not nil" : @"NO, it's nil"); + NSData *logData = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters]; + ////// + MSLogVerbose(@"MSDBStorage", @"base64Data: %@", base64Data); + ////// - NSDictionary *result2 =[un dictionaryWithValuesForKeys:row]; - MSLogVerbose(@"MSDBStorage", @"result2 not nil? %@", result2 != nil ? @"YES, not nil" : @"NO, it's nil"); - // - id log = [NSKeyedUnarchiver unarchiveObjectWithData:rawData]; + id log = [NSKeyedUnarchiver unarchiveObjectWithData:logData]; MSLogVerbose(@"MSDBStorage", @"Restored log not nil? %@", log != nil ? @"YES, not nil" : @"NO, it's nil"); diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index ba1bc6dc58..3f769c5660 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -115,6 +115,8 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // Convert the column data to text (characters). const char *dbDataAsChars = (const char*) sqlite3_column_text(compiledStatement, i); + NSLog(@"dbDataAsChars %s", dbDataAsChars); + // If there are contents in the currenct column (field) then add them to the current row array. if (dbDataAsChars != NULL) { @@ -190,6 +192,7 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ -(BOOL)executeQuery:(NSString *)query{ // Run the query and indicate that is executable. + NSLog(@"query %@", query); return [self runQuery:[query UTF8String] isQueryExecutable:YES]; } From 9d53c5193083da4d1e2cf78f0f45765234b50dfb Mon Sep 17 00:00:00 2001 From: Rychagov Evgeny Date: Fri, 17 Mar 2017 11:50:29 +0300 Subject: [PATCH 04/76] Added perfomance tests --- Demo/Demo.xcodeproj/project.pbxproj | 6 - .../MobileCenter.xcodeproj/project.pbxproj | 10 +- .../Internals/Channel/MSLogManagerDefault.m | 3 +- .../Internals/Storage/MSDBStorage.m | 25 +-- .../Internals/Storage/MSSqliteConnection.m | 3 - .../Support/MobileCenter.xcconfig | 2 +- .../MSStoragePerfomanceTests.m | 167 ++++++++++++++++++ Vendor/SQLite/libtclsqlite3.dylib | Bin 110720 -> 0 bytes 8 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m delete mode 100755 Vendor/SQLite/libtclsqlite3.dylib diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index 26edf36426..f03ecac917 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -23,8 +23,6 @@ 6E2307E31D22083C0031C598 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E2307E11D22083C0031C598 /* Main.storyboard */; }; 6E2307E51D22083C0031C598 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6E2307E41D22083C0031C598 /* Assets.xcassets */; }; 6E2307E81D22083C0031C598 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E2307E61D22083C0031C598 /* LaunchScreen.storyboard */; }; - D36136721E7A79D7004AE043 /* libtclsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D36136711E7A79D7004AE043 /* libtclsqlite3.dylib */; }; - D36136731E7A7AF5004AE043 /* libtclsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D36136711E7A79D7004AE043 /* libtclsqlite3.dylib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -72,7 +70,6 @@ 6E2307E41D22083C0031C598 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6E2307E71D22083C0031C598 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 6E2307E91D22083C0031C598 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D36136711E7A79D7004AE043 /* libtclsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libtclsqlite3.dylib; path = ../Vendor/SQLite/libtclsqlite3.dylib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -80,7 +77,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D36136721E7A79D7004AE043 /* libtclsqlite3.dylib in Frameworks */, 382EC1DB1DDB805400A60DBD /* NotificationCenter.framework in Frameworks */, 382EC2001DDBBB6800A60DBD /* MobileCenterCrashes.framework in Frameworks */, 382EC2011DDBBB6800A60DBD /* MobileCenterAnalytics.framework in Frameworks */, @@ -92,7 +88,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D36136731E7A7AF5004AE043 /* libtclsqlite3.dylib in Frameworks */, 04CCCA481D88AA9F005D8180 /* MobileCenterCrashes.framework in Frameworks */, 04CCCA461D88AA93005D8180 /* MobileCenterAnalytics.framework in Frameworks */, 04CCCA441D88AA86005D8180 /* MobileCenter.framework in Frameworks */, @@ -105,7 +100,6 @@ 382EC1D91DDB805400A60DBD /* Frameworks */ = { isa = PBXGroup; children = ( - D36136711E7A79D7004AE043 /* libtclsqlite3.dylib */, 382EC1DA1DDB805400A60DBD /* NotificationCenter.framework */, ); name = Frameworks; diff --git a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj index 88bf12d8c8..308564adb0 100644 --- a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj +++ b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj @@ -117,7 +117,7 @@ D35D61821E785FBA00D81A0F /* MSSqliteConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = D35D61811E785FBA00D81A0F /* MSSqliteConnection.m */; }; D35D61841E785FD500D81A0F /* MSDatabaseConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D35D61831E785FD500D81A0F /* MSDatabaseConnection.h */; }; D35D61851E78617400D81A0F /* MSSqliteConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D35D617F1E785FAB00D81A0F /* MSSqliteConnection.h */; }; - D35D618E1E79397D00D81A0F /* libtclsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D35D618D1E79397D00D81A0F /* libtclsqlite3.dylib */; }; + D36136831E7BB338004AE043 /* MSStoragePerfomanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */; }; D38023E81E6EFC7C00466558 /* MSStartServiceLog.m in Sources */ = {isa = PBXBuildFile; fileRef = D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */; }; D38023E91E6EFC7C00466558 /* MSStartServiceLog.h in Headers */ = {isa = PBXBuildFile; fileRef = D38023E71E6EFC7C00466558 /* MSStartServiceLog.h */; }; D38024061E7126F500466558 /* MSServiceAbstract.m in Sources */ = {isa = PBXBuildFile; fileRef = D38024051E7126F500466558 /* MSServiceAbstract.m */; }; @@ -283,8 +283,8 @@ D35D617F1E785FAB00D81A0F /* MSSqliteConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSSqliteConnection.h; sourceTree = ""; }; D35D61811E785FBA00D81A0F /* MSSqliteConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSSqliteConnection.m; sourceTree = ""; }; D35D61831E785FD500D81A0F /* MSDatabaseConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSDatabaseConnection.h; sourceTree = ""; }; - D35D618D1E79397D00D81A0F /* libtclsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libtclsqlite3.dylib; path = ../../../../Vendor/SQLite/libtclsqlite3.dylib; sourceTree = ""; }; D36136771E7A80E7004AE043 /* MSDBLogs.sqlite */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = MSDBLogs.sqlite; path = MobileCenter/MSDBLogs.sqlite; sourceTree = ""; }; + D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStoragePerfomanceTests.m; sourceTree = ""; }; D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStartServiceLog.m; sourceTree = ""; }; D38023E71E6EFC7C00466558 /* MSStartServiceLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSStartServiceLog.h; sourceTree = ""; }; D38024051E7126F500466558 /* MSServiceAbstract.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSServiceAbstract.m; sourceTree = ""; }; @@ -325,7 +325,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D35D618E1E79397D00D81A0F /* libtclsqlite3.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -529,6 +528,7 @@ 04FD126A1E4103CC007ABFE7 /* MSKeychainUtilTests.m */, B2FD53641E567BCF0050F909 /* MSDeviceHistoryInfoTests.m */, D38024111E7130C700466558 /* MSStartServiceLogTests.m */, + D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */, ); path = MobileCenterTests; sourceTree = ""; @@ -559,7 +559,6 @@ E83283C21D46C62E000B029E /* Vendor */ = { isa = PBXGroup; children = ( - D35D618D1E79397D00D81A0F /* libtclsqlite3.dylib */, E83283C31D46C62E000B029E /* Reachability */, ); path = Vendor; @@ -882,6 +881,7 @@ 385FC0551D37EBD700A1799F /* MSDeviceTrackerTests.m in Sources */, 6EB1F40E1D2443B7005F9F99 /* MSChannelDefaultTests.m in Sources */, 384959D51D491D4F008F6B3A /* MSMobileCenterTests.m in Sources */, + D36136831E7BB338004AE043 /* MSStoragePerfomanceTests.m in Sources */, 6E5D8A801D25B91F00C033B1 /* MSFileUtilTests.m in Sources */, 386E8D931E25932100EECF0F /* MSHttpTestUtil.m in Sources */, B2FD53651E567BCF0050F909 /* MSDeviceHistoryInfoTests.m in Sources */, @@ -1011,7 +1011,6 @@ buildSettings = { "ARCHS[sdk=iphonesimulator*]" = "$(MS_SIM_ARCHS)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include; PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.mobilecenter; PRODUCT_NAME = MobileCenter; @@ -1026,7 +1025,6 @@ buildSettings = { "ARCHS[sdk=iphonesimulator*]" = "$(MS_SIM_ARCHS)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include; PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.mobilecenter; PRODUCT_NAME = MobileCenter; diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m index a479618839..40159a4fb8 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m @@ -38,7 +38,8 @@ - (instancetype)initWithAppSecret:(NSString *)appSecret installId:(NSUUID *)inst } reachability:[MS_Reachability reachabilityForInternetConnection] retryIntervals:@[ @(10), @(5 * 60), @(20 * 60) ]] - storage:[[MSDBStorage alloc] init]]; +// storage:[MSDBStorage new]; + storage:[MSFileStorage new]]; return self; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 9c678397ce..5da3e75ef5 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -41,29 +41,14 @@ -(void)initTables { #pragma mark - Public - (BOOL)saveLog:(id )log withStorageKey:(NSString *)storageKey { - - MSLogVerbose(@"MSDBStorage", @"saving log with storage key %@", storageKey); - if (!log) { return NO; } - NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; - - ///////// - MSLogVerbose(@"MSDBStorage", @"base64Data: %@", base64Data); - ///////// - NSString *addLogQuery = [NSString stringWithFormat:@"insert or replace into %@ values ('%@', '%@')", kMSLogTableName, storageKey, base64Data]; - - - BOOL result = [self.connection executeQuery:addLogQuery]; - - MSLogVerbose(@"MSDBStorage", @"Log has been saved successfully %d", result); - - return result; + return [self.connection executeQuery:addLogQuery]; } - (NSArray *)deleteLogsForStorageKey:(NSString *)storageKey { @@ -104,15 +89,7 @@ - (void)closeBatchWithStorageKey:(NSString *)storageKey { for (NSArray *row in result) { NSString *base64Data = row[1]; NSData *logData = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters]; - - ////// - MSLogVerbose(@"MSDBStorage", @"base64Data: %@", base64Data); - ////// - id log = [NSKeyedUnarchiver unarchiveObjectWithData:logData]; - - MSLogVerbose(@"MSDBStorage", @"Restored log not nil? %@", log != nil ? @"YES, not nil" : @"NO, it's nil"); - [logs addObject:log]; } return logs; diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index 3f769c5660..ba1bc6dc58 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -115,8 +115,6 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // Convert the column data to text (characters). const char *dbDataAsChars = (const char*) sqlite3_column_text(compiledStatement, i); - NSLog(@"dbDataAsChars %s", dbDataAsChars); - // If there are contents in the currenct column (field) then add them to the current row array. if (dbDataAsChars != NULL) { @@ -192,7 +190,6 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ -(BOOL)executeQuery:(NSString *)query{ // Run the query and indicate that is executable. - NSLog(@"query %@", query); return [self runQuery:[query UTF8String] isQueryExecutable:YES]; } diff --git a/MobileCenter/MobileCenter/Support/MobileCenter.xcconfig b/MobileCenter/MobileCenter/Support/MobileCenter.xcconfig index b2f00b5362..48457d0f4f 100644 --- a/MobileCenter/MobileCenter/Support/MobileCenter.xcconfig +++ b/MobileCenter/MobileCenter/Support/MobileCenter.xcconfig @@ -1,3 +1,3 @@ #include "../../../Global.xcconfig" -OTHER_LDFLAGS = $(inherited) -framework Foundation -framework SystemConfiguration -framework UIKit; +OTHER_LDFLAGS = $(inherited) -framework Foundation -framework SystemConfiguration -framework UIKit -lsqlite3; diff --git a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m new file mode 100644 index 0000000000..49f885a8be --- /dev/null +++ b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m @@ -0,0 +1,167 @@ +#import +#import "MSStartServiceLog.h" +#import "MSDBStorage.h" +#import "MSFileStorage.h" + +const int numLogs = 100; +const int numServices = 100; + +@interface MSStoragePerfomanceTests : XCTestCase +@end + +@interface MSStoragePerfomanceTests() + +@property(nonatomic) MSDBStorage *dbStorage; +@property(nonatomic) MSFileStorage *fStorage; + +@end + +@implementation MSStoragePerfomanceTests + +@synthesize dbStorage; +@synthesize fStorage; + +- (void)setUp { + [super setUp]; + + self.dbStorage = [MSDBStorage new]; + self.fStorage = [MSFileStorage new]; +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; + + [self.dbStorage deleteLogsForStorageKey:@"anyKey"]; + [self.fStorage deleteLogsForStorageKey:@"anyKey"]; +} + +#pragma mark - Database storage tests + +- (void)testDatabaseWriteShortLogsPerformance { + NSArray* arrayOfLogs = [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; + + [self measureBlock:^{ + for (MSStartServiceLog *log in arrayOfLogs) { + [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + } + }]; +} + +- (void)testDatabaseWriteLongLogsPerformance { + NSArray* arrayOfLogs = [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; + + [self measureBlock:^{ + for (MSStartServiceLog *log in arrayOfLogs) { + [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + } + }]; +} + +- (void)testDatabaseWriteVeryLongLogsPerformance { + NSArray* arrayOfLogs = [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; + + [self measureBlock:^{ + for (MSStartServiceLog *log in arrayOfLogs) { + [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + } + }]; +} + +#pragma mark - File storage tests + +- (void)testFileStorageWriteShortLogsPerformance { + NSMutableArray* arrayOfLogs = [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; + + [self measureBlock:^{ + for (MSStartServiceLog *log in arrayOfLogs) { + [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + } + }]; +} + +- (void)testFileStorageWriteLongLogsPerformance { + NSMutableArray* arrayOfLogs = [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; + + [self measureBlock:^{ + for (MSStartServiceLog *log in arrayOfLogs) { + [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + } + }]; +} + +- (void)testFileStorageWriteVeryLongLogsPerformance { + NSMutableArray* arrayOfLogs = [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; + + [self measureBlock:^{ + for (MSStartServiceLog *log in arrayOfLogs) { + [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + } + }]; +} + +#pragma mark - Private + +- (NSArray*)generateLogsWithShortServicesNames:(int) numLogs withNumService:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for(int i = 0; i < numLogs; ++i) { + MSStartServiceLog *log = [MSStartServiceLog new]; + log.services = [self generateServicesWithShortNames:numServices]; + [dic addObject:log]; + } + return dic; +} + +- (NSArray*)generateLogsWithLongServicesNames:(int) numLogs withNumService:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for(int i = 0; i < numLogs; ++i) { + MSStartServiceLog *log = [MSStartServiceLog new]; + log.services = [self generateServicesWithLongNames:numServices]; + [dic addObject:log]; + } + return dic; +} + +- (NSArray*)generateLogsWithVeryLongServicesNames:(int) numLogs withNumService:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for(int i = 0; i < numLogs; ++i) { + MSStartServiceLog *log = [MSStartServiceLog new]; + log.services = [self generateServicesWithVeryLongNames:numServices]; + [dic addObject:log]; + } + return dic; +} + +- (NSArray*)generateServicesWithShortNames:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for(int i = 0; i < numServices; ++i) { + [dic addObject:[[NSUUID UUID] UUIDString]]; + } + return dic; +} + +- (NSArray*)generateServicesWithLongNames:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for(int i = 0; i < numServices; ++i) { + NSString *value = @""; + for(int j = 0; j < 10; ++j) { + value = [value stringByAppendingString:[[NSUUID UUID] UUIDString]]; + } + [dic addObject:value]; + } + return dic; +} + +- (NSArray*)generateServicesWithVeryLongNames:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for(int i = 0; i < numServices; ++i) { + NSString *value = @""; + for(int j = 0; j < 50; ++j) { + value = [value stringByAppendingString:[[NSUUID UUID] UUIDString]]; + } + [dic addObject:value]; + } + return dic; +} + +@end diff --git a/Vendor/SQLite/libtclsqlite3.dylib b/Vendor/SQLite/libtclsqlite3.dylib deleted file mode 100755 index d7d2d9866f1e3e6aa73f7a4177a236e8e7348717..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110720 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 Date: Tue, 21 Mar 2017 09:35:09 +0300 Subject: [PATCH 05/76] Some changes for performance tests. Add additional info to DB logs --- Demo/Demo/AppDelegate.m | 5 +++++ .../MobileCenter/Internals/Channel/MSLogManagerDefault.m | 4 ++-- .../MobileCenter/Internals/Storage/MSSqliteConnection.m | 8 +++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Demo/Demo/AppDelegate.m b/Demo/Demo/AppDelegate.m index 8c110adb95..da47a077c2 100644 --- a/Demo/Demo/AppDelegate.m +++ b/Demo/Demo/AppDelegate.m @@ -23,6 +23,11 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // Print the install Id. NSLog(@"%@ Install Id: %@", kDEMLogTag, [[MSMobileCenter installId] UUIDString]); + + for(int i = 0; i < 100; ++i) { + [MSAnalytics trackEvent:[NSString stringWithFormat:@"%d", i]]; + } + return YES; } diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m index 40159a4fb8..7899ecedaa 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m @@ -38,8 +38,8 @@ - (instancetype)initWithAppSecret:(NSString *)appSecret installId:(NSUUID *)inst } reachability:[MS_Reachability reachabilityForInternetConnection] retryIntervals:@[ @(10), @(5 * 60), @(20 * 60) ]] -// storage:[MSDBStorage new]; - storage:[MSFileStorage new]]; + storage:[MSDBStorage new]]; +// storage:[MSFileStorage new]]; return self; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index ba1bc6dc58..505133bf92 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -139,7 +139,7 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // This is the case of an executable query (insert, update, ...). // Execute the query. - BOOL executeQueryResults = (BOOL) sqlite3_step(compiledStatement); + int executeQueryResults = sqlite3_step(compiledStatement); if (executeQueryResults == SQLITE_DONE) { // Keep the affected rows. @@ -150,7 +150,7 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ } else { // If could not execute the query show the error message on the debugger. - NSLog(@"DB Error: %s", sqlite3_errmsg(sqlite3Database)); + NSLog(@"DB Error: %s\nerror code: %d", sqlite3_errmsg(sqlite3Database), executeQueryResults); result = NO; } @@ -158,7 +158,7 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ } else { // In the database cannot be opened then show the error message on the debugger. - NSLog(@"%s", sqlite3_errmsg(sqlite3Database)); + NSLog(@"DB Error: %s\nquery: %s", sqlite3_errmsg(sqlite3Database), query); result = NO; } @@ -172,6 +172,8 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // Close the database. sqlite3_close(sqlite3Database); + NSLog(@"Successfull query: %@", result ? @"YES" : @"NO"); + return result; } From 8a1f6b00c82de501084903b88323c28b199e5d1b Mon Sep 17 00:00:00 2001 From: "Benjamin Scholtysik (Reimold)" Date: Thu, 6 Apr 2017 13:31:55 -0700 Subject: [PATCH 06/76] add sqlite3 dependency --- Puppet/Puppet.xcodeproj/project.pbxproj | 6 ++++++ Sasquatch/Sasquatch.xcodeproj/project.pbxproj | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Puppet/Puppet.xcodeproj/project.pbxproj b/Puppet/Puppet.xcodeproj/project.pbxproj index 86cd0c5aef..f1f88c835a 100644 --- a/Puppet/Puppet.xcodeproj/project.pbxproj +++ b/Puppet/Puppet.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ B27397A51DE3D27700AC7761 /* CrashLibIOS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B27397A31DE3D27700AC7761 /* CrashLibIOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B296D4211E5F5DEF0089F013 /* MobileCenterDistributeResources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B2C070CB1E5F59A90076D6A9 /* MobileCenterDistributeResources.bundle */; }; B2E611F61DDE72BA00A9DF86 /* MSFakeCXXClass.mm in Sources */ = {isa = PBXBuildFile; fileRef = B2E611F41DDE72BA00A9DF86 /* MSFakeCXXClass.mm */; }; + B2F3FA8B1E96DB5A0065A3BE /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B2F3FA8A1E96DB5A0065A3BE /* libsqlite3.tbd */; }; + B2F3FA981E96DB690065A3BE /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B2F3FA8A1E96DB5A0065A3BE /* libsqlite3.tbd */; }; BA6827C9F0410233C245D989 /* MSDistributeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BA682342F94E25F75943E4F6 /* MSDistributeViewController.m */; }; E82E1B641D1CA58D00D281C1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E82E1B631D1CA58D00D281C1 /* main.m */; }; E82E1B671D1CA58D00D281C1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E82E1B661D1CA58D00D281C1 /* AppDelegate.m */; }; @@ -225,6 +227,7 @@ B27397A31DE3D27700AC7761 /* CrashLibIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CrashLibIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B2E611F41DDE72BA00A9DF86 /* MSFakeCXXClass.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MSFakeCXXClass.mm; sourceTree = ""; }; B2E611F51DDE72BA00A9DF86 /* MSFakeCXXClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSFakeCXXClass.h; sourceTree = ""; }; + B2F3FA8A1E96DB5A0065A3BE /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; BA682342F94E25F75943E4F6 /* MSDistributeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDistributeViewController.m; sourceTree = ""; }; BA68238A942EEAA10612D006 /* MSDistributeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSDistributeViewController.h; sourceTree = ""; }; E82E1B5F1D1CA58D00D281C1 /* Puppet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Puppet.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -253,6 +256,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B2F3FA981E96DB690065A3BE /* libsqlite3.tbd in Frameworks */, 380A4DC91DD68D3F00E99219 /* libc++.tbd in Frameworks */, 380A4DC81DD68D2B00E99219 /* CrashReporter.framework in Frameworks */, 380A4DC41DD68D1000E99219 /* libMobileCenter.a in Frameworks */, @@ -266,6 +270,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B2F3FA8B1E96DB5A0065A3BE /* libsqlite3.tbd in Frameworks */, 38484F6D1E53D57E00C5631C /* SafariServices.framework in Frameworks */, 6EC99A281D4152FA0016C325 /* libc++.tbd in Frameworks */, 6EC99A211D4151A00016C325 /* CrashReporter.framework in Frameworks */, @@ -293,6 +298,7 @@ 380A4D971DD6892F00E99219 /* Frameworks */ = { isa = PBXGroup; children = ( + B2F3FA8A1E96DB5A0065A3BE /* libsqlite3.tbd */, 38F2606E1E649FE5004F0FDC /* Security.framework */, 38484F691E537B9300C5631C /* SafariServices.framework */, 380A4D981DD6892F00E99219 /* NotificationCenter.framework */, diff --git a/Sasquatch/Sasquatch.xcodeproj/project.pbxproj b/Sasquatch/Sasquatch.xcodeproj/project.pbxproj index 228d00f4fd..7ca5cf8bb2 100644 --- a/Sasquatch/Sasquatch.xcodeproj/project.pbxproj +++ b/Sasquatch/Sasquatch.xcodeproj/project.pbxproj @@ -40,6 +40,8 @@ 84D63FF71E8BA838004C9F86 /* MSCrashesDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D63FF61E8BA838004C9F86 /* MSCrashesDetailViewController.swift */; }; 84D63FF81E8BA838004C9F86 /* MSCrashesDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D63FF61E8BA838004C9F86 /* MSCrashesDetailViewController.swift */; }; 84D63FF91E8BAA90004C9F86 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 845964A91E8A7E6600BABA51 /* Main.storyboard */; }; + B2F3FA9A1E96DB7E0065A3BE /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B2F3FA991E96DB7E0065A3BE /* libsqlite3.tbd */; }; + B2F3FA9B1E96DB870065A3BE /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B2F3FA991E96DB7E0065A3BE /* libsqlite3.tbd */; }; F89D79091E93DA3A0094521F /* CrashLibIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F89D79081E93DA3A0094521F /* CrashLibIOS.framework */; }; F89D790A1E93DA3A0094521F /* CrashLibIOS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F89D79081E93DA3A0094521F /* CrashLibIOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F89D790C1E93DA500094521F /* CrashLibIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F89D79081E93DA3A0094521F /* CrashLibIOS.framework */; }; @@ -108,6 +110,7 @@ 84A0FF681E8A87C9000E7A28 /* MSAnalyticsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSAnalyticsViewController.swift; sourceTree = ""; }; 84A0FF6B1E8A87E1000E7A28 /* MSDistributeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSDistributeViewController.swift; sourceTree = ""; }; 84D63FF61E8BA838004C9F86 /* MSCrashesDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSCrashesDetailViewController.swift; sourceTree = ""; }; + B2F3FA991E96DB7E0065A3BE /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; F89D79081E93DA3A0094521F /* CrashLibIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CrashLibIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -116,6 +119,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B2F3FA9B1E96DB870065A3BE /* libsqlite3.tbd in Frameworks */, F89D79091E93DA3A0094521F /* CrashLibIOS.framework in Frameworks */, 84226D361E89302900798417 /* MobileCenter.framework in Frameworks */, 84226D371E89302900798417 /* MobileCenterAnalytics.framework in Frameworks */, @@ -128,6 +132,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B2F3FA9A1E96DB7E0065A3BE /* libsqlite3.tbd in Frameworks */, F89D790C1E93DA500094521F /* CrashLibIOS.framework in Frameworks */, 84226D321E89301B00798417 /* MobileCenter.framework in Frameworks */, 84226D331E89301B00798417 /* MobileCenterAnalytics.framework in Frameworks */, @@ -246,6 +251,7 @@ 84226D0F1E8921DE00798417 /* Frameworks */ = { isa = PBXGroup; children = ( + B2F3FA991E96DB7E0065A3BE /* libsqlite3.tbd */, 849A3E741E8AA78E008711CB /* CrashLibIOS.framework */, 84226D2E1E89301A00798417 /* MobileCenter.framework */, 84226D2F1E89301B00798417 /* MobileCenterAnalytics.framework */, From dafe86f74273179de03f5134a63364cd836638a2 Mon Sep 17 00:00:00 2001 From: Rychagov Evgeny Date: Wed, 12 Apr 2017 13:34:00 +0300 Subject: [PATCH 07/76] Implement new MSStorage protocol. Fix tests --- .../Internals/Storage/MSDBStorage.m | 27 +++++++++---------- .../MSStoragePerfomanceTests.m | 26 +++++++++--------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 5da3e75ef5..4a061e0b29 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -40,46 +40,43 @@ -(void)initTables { #pragma mark - Public -- (BOOL)saveLog:(id )log withStorageKey:(NSString *)storageKey { +- (BOOL)saveLog:(id)log withGroupID:(NSString *)groupID { if (!log) { return NO; } NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *addLogQuery = [NSString stringWithFormat:@"insert or replace into %@ values ('%@', '%@')", - kMSLogTableName, storageKey, base64Data]; + kMSLogTableName, groupID, base64Data]; return [self.connection executeQuery:addLogQuery]; } -- (NSArray *)deleteLogsForStorageKey:(NSString *)storageKey { - NSArray *logs = [self getLogsWith:storageKey]; - [self deleteLogsWith:storageKey]; +- (NSArray *)deleteLogsForGroupID:(NSString *)groupID { + NSArray *logs = [self getLogsWith:groupID]; + [self deleteLogsWith:groupID]; return logs; } -- (void)deleteLogsForId:(NSString *)logsId withStorageKey:(NSString *)storageKey { +- (void)deleteLogsForId:(NSString *)logsId withGroupID:(NSString *)groupID { // FIXME: logsId ? - [self deleteLogsWith:storageKey]; + [self deleteLogsWith:groupID]; } -- (BOOL)loadLogsForStorageKey:(NSString *)storageKey withCompletion:(nullable MSLoadDataCompletionBlock)completion { - NSArray *logs = [self getLogsWith:storageKey]; - +- (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDataCompletionBlock)completion { + NSArray *logs = [self getLogsWith:groupID]; if (completion) { - - // FIXME: batchId ? completion(logs.count > 0, logs, @""); } - return logs.count > 0; } -- (void)closeBatchWithStorageKey:(NSString *)storageKey { +- (void)closeBatchWithGroupID:(NSString *)groupID { // TODO: } -//------ +#pragma mark - Private + - (NSArray*) getLogsWith:(NSString*)storageKey { NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@'", kMSLogTableName, kMSStorageKeyColumnName, storageKey]; diff --git a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m index 49f885a8be..255d647087 100644 --- a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m +++ b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m @@ -3,8 +3,8 @@ #import "MSDBStorage.h" #import "MSFileStorage.h" -const int numLogs = 100; -const int numServices = 100; +static const int numLogs = 100; +static const int numServices = 100; @interface MSStoragePerfomanceTests : XCTestCase @end @@ -32,8 +32,8 @@ - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; - [self.dbStorage deleteLogsForStorageKey:@"anyKey"]; - [self.fStorage deleteLogsForStorageKey:@"anyKey"]; + [self.dbStorage deleteLogsForGroupID:@"anyKey"]; + [self.fStorage deleteLogsForGroupID:@"anyKey"]; } #pragma mark - Database storage tests @@ -43,7 +43,7 @@ - (void)testDatabaseWriteShortLogsPerformance { [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + [self.dbStorage saveLog:log withGroupID:@"anyKey"]; } }]; } @@ -53,7 +53,7 @@ - (void)testDatabaseWriteLongLogsPerformance { [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + [self.dbStorage saveLog:log withGroupID:@"anyKey"]; } }]; } @@ -63,7 +63,7 @@ - (void)testDatabaseWriteVeryLongLogsPerformance { [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + [self.dbStorage saveLog:log withGroupID:@"anyKey"]; } }]; } @@ -71,31 +71,31 @@ - (void)testDatabaseWriteVeryLongLogsPerformance { #pragma mark - File storage tests - (void)testFileStorageWriteShortLogsPerformance { - NSMutableArray* arrayOfLogs = [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; + NSArray* arrayOfLogs = [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + [self.dbStorage saveLog:log withGroupID:@"anyKey"]; } }]; } - (void)testFileStorageWriteLongLogsPerformance { - NSMutableArray* arrayOfLogs = [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; + NSArray* arrayOfLogs = [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + [self.dbStorage saveLog:log withGroupID:@"anyKey"]; } }]; } - (void)testFileStorageWriteVeryLongLogsPerformance { - NSMutableArray* arrayOfLogs = [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; + NSArray* arrayOfLogs = [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withStorageKey:@"anyKey"]; + [self.dbStorage saveLog:log withGroupID:@"anyKey"]; } }]; } From a2c75b9461e6f27747357fc468e1fe4d93c0422a Mon Sep 17 00:00:00 2001 From: Rychagov Evgeny Date: Wed, 12 Apr 2017 14:11:05 +0300 Subject: [PATCH 08/76] Add sqlite3 library --- .../Support/MobileCenterAnalytics.xcconfig | 2 +- .../MobileCenterCrashes/Support/MobileCenterCrashes.xcconfig | 2 +- .../Support/MobileCenterDistribute.xcconfig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MobileCenterAnalytics/MobileCenterAnalytics/Support/MobileCenterAnalytics.xcconfig b/MobileCenterAnalytics/MobileCenterAnalytics/Support/MobileCenterAnalytics.xcconfig index 5966774f09..c3925f84df 100644 --- a/MobileCenterAnalytics/MobileCenterAnalytics/Support/MobileCenterAnalytics.xcconfig +++ b/MobileCenterAnalytics/MobileCenterAnalytics/Support/MobileCenterAnalytics.xcconfig @@ -1,3 +1,3 @@ #include "../../../Global.xcconfig" -OTHER_LDFLAGS = $(inherited) -framework Foundation -framework UIKit -framework CoreTelephony; +OTHER_LDFLAGS = $(inherited) -framework Foundation -framework UIKit -framework CoreTelephony -lsqlite3; diff --git a/MobileCenterCrashes/MobileCenterCrashes/Support/MobileCenterCrashes.xcconfig b/MobileCenterCrashes/MobileCenterCrashes/Support/MobileCenterCrashes.xcconfig index 3f10866e77..72f4f60f80 100644 --- a/MobileCenterCrashes/MobileCenterCrashes/Support/MobileCenterCrashes.xcconfig +++ b/MobileCenterCrashes/MobileCenterCrashes/Support/MobileCenterCrashes.xcconfig @@ -1,4 +1,4 @@ #include "../../../Global.xcconfig" OTHER_CFLAGS = $(OTHER_CFLAGS) -iframework "$(SRCROOT)/../Vendor/PLCrashReporter"; -OTHER_LDFLAGS = $(inherited) -framework CrashReporter -framework Foundation -framework UIKit -lc++ -lz; +OTHER_LDFLAGS = $(inherited) -framework CrashReporter -framework Foundation -framework UIKit -lc++ -lz -lsqlite3; diff --git a/MobileCenterDistribute/MobileCenterDistribute/Support/MobileCenterDistribute.xcconfig b/MobileCenterDistribute/MobileCenterDistribute/Support/MobileCenterDistribute.xcconfig index 8b28d4082a..3216ae1c12 100644 --- a/MobileCenterDistribute/MobileCenterDistribute/Support/MobileCenterDistribute.xcconfig +++ b/MobileCenterDistribute/MobileCenterDistribute/Support/MobileCenterDistribute.xcconfig @@ -1,3 +1,3 @@ #include "../../../Global.xcconfig" -OTHER_LDFLAGS=$(inherited) -framework Foundation -framework UIKit -framework CoreTelephony -weak_framework SafariServices +OTHER_LDFLAGS = $(inherited) -framework Foundation -framework UIKit -framework CoreTelephony -weak_framework SafariServices -lsqlite3; From 479e825141c2952d165f82c2ec07f9bf50c6f0ca Mon Sep 17 00:00:00 2001 From: Rychagov Evgeny Date: Wed, 12 Apr 2017 19:34:42 +0300 Subject: [PATCH 09/76] Fix TODO --- MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 4a061e0b29..b3e4862caa 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -72,7 +72,7 @@ - (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDa } - (void)closeBatchWithGroupID:(NSString *)groupID { - // TODO: + [self deleteLogsForGroupID:groupID]; } #pragma mark - Private From 9deca115b5a9d3b8098682d6c0dd924547856378 Mon Sep 17 00:00:00 2001 From: Rychagov Evgeny Date: Thu, 13 Apr 2017 14:45:22 +0300 Subject: [PATCH 10/76] Add flag -lsqlite3 only for test projects --- .../MobileCenterAnalytics.xcodeproj/project.pbxproj | 8 ++++++++ .../Support/MobileCenterAnalytics.xcconfig | 2 +- .../MobileCenterCrashes.xcodeproj/project.pbxproj | 2 ++ .../Support/MobileCenterCrashes.xcconfig | 2 +- .../MobileCenterDistribute.xcodeproj/project.pbxproj | 8 ++++++++ .../Support/MobileCenterDistribute.xcconfig | 2 +- 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/MobileCenterAnalytics/MobileCenterAnalytics.xcodeproj/project.pbxproj b/MobileCenterAnalytics/MobileCenterAnalytics.xcodeproj/project.pbxproj index f93d5c8567..7fc981a63a 100644 --- a/MobileCenterAnalytics/MobileCenterAnalytics.xcodeproj/project.pbxproj +++ b/MobileCenterAnalytics/MobileCenterAnalytics.xcodeproj/project.pbxproj @@ -665,6 +665,10 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../Vendor\"/**"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-lsqlite3", + ); PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.mobilecenteranalyticstests; PRODUCT_NAME = MobileCenterAnalytics; USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../MobileCenter/MobileCenter\"/**"; @@ -683,6 +687,10 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../Vendor\"/**"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-lsqlite3", + ); PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.mobilecenteranalyticstests; PRODUCT_NAME = MobileCenterAnalytics; USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../MobileCenter/MobileCenter\"/**"; diff --git a/MobileCenterAnalytics/MobileCenterAnalytics/Support/MobileCenterAnalytics.xcconfig b/MobileCenterAnalytics/MobileCenterAnalytics/Support/MobileCenterAnalytics.xcconfig index c3925f84df..5966774f09 100644 --- a/MobileCenterAnalytics/MobileCenterAnalytics/Support/MobileCenterAnalytics.xcconfig +++ b/MobileCenterAnalytics/MobileCenterAnalytics/Support/MobileCenterAnalytics.xcconfig @@ -1,3 +1,3 @@ #include "../../../Global.xcconfig" -OTHER_LDFLAGS = $(inherited) -framework Foundation -framework UIKit -framework CoreTelephony -lsqlite3; +OTHER_LDFLAGS = $(inherited) -framework Foundation -framework UIKit -framework CoreTelephony; diff --git a/MobileCenterCrashes/MobileCenterCrashes.xcodeproj/project.pbxproj b/MobileCenterCrashes/MobileCenterCrashes.xcodeproj/project.pbxproj index 7fb8ba1264..c156329feb 100644 --- a/MobileCenterCrashes/MobileCenterCrashes.xcodeproj/project.pbxproj +++ b/MobileCenterCrashes/MobileCenterCrashes.xcodeproj/project.pbxproj @@ -916,6 +916,7 @@ OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", + "-lsqlite3", ); PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.mobilecentercrashestests; PRODUCT_NAME = MobileCenterCrashes; @@ -938,6 +939,7 @@ OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", + "-lsqlite3", ); PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.mobilecentercrashestests; PRODUCT_NAME = MobileCenterCrashes; diff --git a/MobileCenterCrashes/MobileCenterCrashes/Support/MobileCenterCrashes.xcconfig b/MobileCenterCrashes/MobileCenterCrashes/Support/MobileCenterCrashes.xcconfig index 72f4f60f80..3f10866e77 100644 --- a/MobileCenterCrashes/MobileCenterCrashes/Support/MobileCenterCrashes.xcconfig +++ b/MobileCenterCrashes/MobileCenterCrashes/Support/MobileCenterCrashes.xcconfig @@ -1,4 +1,4 @@ #include "../../../Global.xcconfig" OTHER_CFLAGS = $(OTHER_CFLAGS) -iframework "$(SRCROOT)/../Vendor/PLCrashReporter"; -OTHER_LDFLAGS = $(inherited) -framework CrashReporter -framework Foundation -framework UIKit -lc++ -lz -lsqlite3; +OTHER_LDFLAGS = $(inherited) -framework CrashReporter -framework Foundation -framework UIKit -lc++ -lz; diff --git a/MobileCenterDistribute/MobileCenterDistribute.xcodeproj/project.pbxproj b/MobileCenterDistribute/MobileCenterDistribute.xcodeproj/project.pbxproj index 648c7aa32b..13b2effbc3 100644 --- a/MobileCenterDistribute/MobileCenterDistribute.xcodeproj/project.pbxproj +++ b/MobileCenterDistribute/MobileCenterDistribute.xcodeproj/project.pbxproj @@ -889,6 +889,10 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../Vendor\"/**"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-lsqlite3", + ); PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.mobilecenterdistributetests; PRODUCT_NAME = MobileCenterDistribute; USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../MobileCenter/MobileCenter\"/**"; @@ -907,6 +911,10 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../Vendor\"/**"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-lsqlite3", + ); PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.azure.mobile.mobilecenterdistributetests; PRODUCT_NAME = MobileCenterDistribute; USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../MobileCenter/MobileCenter\"/**"; diff --git a/MobileCenterDistribute/MobileCenterDistribute/Support/MobileCenterDistribute.xcconfig b/MobileCenterDistribute/MobileCenterDistribute/Support/MobileCenterDistribute.xcconfig index 3216ae1c12..0c05c2235f 100644 --- a/MobileCenterDistribute/MobileCenterDistribute/Support/MobileCenterDistribute.xcconfig +++ b/MobileCenterDistribute/MobileCenterDistribute/Support/MobileCenterDistribute.xcconfig @@ -1,3 +1,3 @@ #include "../../../Global.xcconfig" -OTHER_LDFLAGS = $(inherited) -framework Foundation -framework UIKit -framework CoreTelephony -weak_framework SafariServices -lsqlite3; +OTHER_LDFLAGS = $(inherited) -framework Foundation -framework UIKit -framework CoreTelephony -weak_framework SafariServices; From 1605813824bf940c13eff1ccbf423f50358f1951 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 14 Apr 2017 14:39:10 +0300 Subject: [PATCH 11/76] MSStorage doesn't return logs more then we expect. Add tests for this case. --- MobileCenter/MSDBStorageTests.m | 46 +++++++++++++++++++ .../MobileCenter.xcodeproj/project.pbxproj | 10 ++-- .../Internals/Storage/MSDBStorage.h | 13 ++++++ .../Internals/Storage/MSDBStorage.m | 9 +++- .../Internals/Storage/MSFileStorage.m | 7 ++- .../MobileCenterTests/MSFileStorageTests.m | 35 ++++++++++++++ 6 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 MobileCenter/MSDBStorageTests.m diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m new file mode 100644 index 0000000000..4fea818384 --- /dev/null +++ b/MobileCenter/MSDBStorageTests.m @@ -0,0 +1,46 @@ +#import +#import +#import +#import "MSAbstractLog.h" +#import "MSDBStorage.h" + +@interface MSDBStorageTests : XCTestCase + +@property (nonatomic) MSDBStorage *sut; + +@end + +@implementation MSDBStorageTests + +#pragma mark - Setup +- (void)setUp { + [super setUp]; + self.sut = [MSDBStorage new]; +} + +- (void)testLoadTooManyLogs { + + // If + id partialMock = OCMPartialMock(self.sut); + OCMStub([partialMock bucketFileLogCountLimit]).andReturn(50); + OCMStub([partialMock getLogsWith:[OCMArg any]]).andReturn([self generateLogs:self.sut.bucketFileLogCountLimit]); + + // When + [self.sut loadLogsForGroupID:@"" withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { + + // Then + XCTAssertTrue(succeeded); + XCTAssertTrue(self.sut.bucketFileLogCountLimit == logArray.count); + }]; +} + +- (NSArray*)generateLogs:(NSUInteger)maxCountLogs { + NSUInteger totalLogs = maxCountLogs * 2; + NSMutableArray *logs = [NSMutableArray arrayWithCapacity:totalLogs]; + for (NSUInteger i = 0; i < totalLogs; ++i) { + [logs addObject:[MSAbstractLog new]]; + } + return logs; +} + +@end diff --git a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj index 296e7f9669..1ce06cbffc 100644 --- a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj +++ b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ 38EAD9AC1DA6A8C500CE6030 /* MSEnable.h in Headers */ = {isa = PBXBuildFile; fileRef = 38EAD9AB1DA6A8C500CE6030 /* MSEnable.h */; }; 38F1944E1DADB93100D3E0FE /* MSHttpSenderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38F1944D1DADB93100D3E0FE /* MSHttpSenderPrivate.h */; }; 59493FB23FC790090683581A /* MSChannelDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 5949380E604D45340244FEF1 /* MSChannelDelegate.h */; }; + 5C7877921EA0CFF3002263CC /* MSDBStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C7877911EA0CFF3002263CC /* MSDBStorageTests.m */; }; 6E0401571D1C9AAA0051BCFA /* MSMobileCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E0401561D1C9AAA0051BCFA /* MSMobileCenter.m */; }; 6E04016A1D1C9E1F0051BCFA /* MSMobileCenter.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E0401551D1C9AAA0051BCFA /* MSMobileCenter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6E04016B1D1C9E1F0051BCFA /* MSConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E04013F1D1C99AC0051BCFA /* MSConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -125,11 +126,11 @@ BA682E63DDB55EED56809831 /* MSIngestionSender.m in Sources */ = {isa = PBXBuildFile; fileRef = BA6822A89D38F04D8D95CE83 /* MSIngestionSender.m */; }; D341DBCE1E77F04300D385F9 /* MSDBStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = D341DBCD1E77EF8600D385F9 /* MSDBStorage.h */; }; D341DBD61E77FD8B00D385F9 /* MSDBStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D341DBD51E77FD8B00D385F9 /* MSDBStorage.m */; }; -D377A30D1E83A05900B2C97A /* MSMockUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = D377A30C1E83A05900B2C97A /* MSMockUserDefaults.m */; }; D35D61821E785FBA00D81A0F /* MSSqliteConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = D35D61811E785FBA00D81A0F /* MSSqliteConnection.m */; }; D35D61841E785FD500D81A0F /* MSDatabaseConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D35D61831E785FD500D81A0F /* MSDatabaseConnection.h */; }; D35D61851E78617400D81A0F /* MSSqliteConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D35D617F1E785FAB00D81A0F /* MSSqliteConnection.h */; }; D36136831E7BB338004AE043 /* MSStoragePerfomanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */; }; + D377A30D1E83A05900B2C97A /* MSMockUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = D377A30C1E83A05900B2C97A /* MSMockUserDefaults.m */; }; D38023E81E6EFC7C00466558 /* MSStartServiceLog.m in Sources */ = {isa = PBXBuildFile; fileRef = D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */; }; D38023E91E6EFC7C00466558 /* MSStartServiceLog.h in Headers */ = {isa = PBXBuildFile; fileRef = D38023E71E6EFC7C00466558 /* MSStartServiceLog.h */; }; D38024061E7126F500466558 /* MSServiceAbstract.m in Sources */ = {isa = PBXBuildFile; fileRef = D38024051E7126F500466558 /* MSServiceAbstract.m */; }; @@ -223,6 +224,7 @@ D377A30D1E83A05900B2C97A /* MSMockUserDefaults.m in Sources */ = {isa = PBXBuild 38EAD9AB1DA6A8C500CE6030 /* MSEnable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSEnable.h; sourceTree = ""; }; 38F1944D1DADB93100D3E0FE /* MSHttpSenderPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSHttpSenderPrivate.h; sourceTree = ""; }; 5949380E604D45340244FEF1 /* MSChannelDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSChannelDelegate.h; sourceTree = ""; }; + 5C7877911EA0CFF3002263CC /* MSDBStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDBStorageTests.m; sourceTree = SOURCE_ROOT; }; 6E0401031D1C98220051BCFA /* libMobileCenter.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMobileCenter.a; sourceTree = BUILT_PRODUCTS_DIR; }; 6E04013F1D1C99AC0051BCFA /* MSConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSConstants.h; sourceTree = ""; }; 6E0401401D1C99AC0051BCFA /* MobileCenter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MobileCenter.h; sourceTree = ""; }; @@ -301,13 +303,13 @@ D377A30D1E83A05900B2C97A /* MSMockUserDefaults.m in Sources */ = {isa = PBXBuild BA6824A001520825F18DFC42 /* MSIngestionSender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSIngestionSender.h; sourceTree = ""; }; D341DBCD1E77EF8600D385F9 /* MSDBStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSDBStorage.h; sourceTree = ""; }; D341DBD51E77FD8B00D385F9 /* MSDBStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDBStorage.m; sourceTree = ""; }; - D377A30B1E83A04600B2C97A /* MSMockUserDefaults.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSMockUserDefaults.h; sourceTree = ""; }; - D377A30C1E83A05900B2C97A /* MSMockUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSMockUserDefaults.m; sourceTree = ""; }; D35D617F1E785FAB00D81A0F /* MSSqliteConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSSqliteConnection.h; sourceTree = ""; }; D35D61811E785FBA00D81A0F /* MSSqliteConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSSqliteConnection.m; sourceTree = ""; }; D35D61831E785FD500D81A0F /* MSDatabaseConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSDatabaseConnection.h; sourceTree = ""; }; D36136771E7A80E7004AE043 /* MSDBLogs.sqlite */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = MSDBLogs.sqlite; path = MobileCenter/MSDBLogs.sqlite; sourceTree = ""; }; D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStoragePerfomanceTests.m; sourceTree = ""; }; + D377A30B1E83A04600B2C97A /* MSMockUserDefaults.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSMockUserDefaults.h; sourceTree = ""; }; + D377A30C1E83A05900B2C97A /* MSMockUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSMockUserDefaults.m; sourceTree = ""; }; D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStartServiceLog.m; sourceTree = ""; }; D38023E71E6EFC7C00466558 /* MSStartServiceLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSStartServiceLog.h; sourceTree = ""; }; D38024051E7126F500466558 /* MSServiceAbstract.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSServiceAbstract.m; sourceTree = ""; }; @@ -560,6 +562,7 @@ D377A30D1E83A05900B2C97A /* MSMockUserDefaults.m in Sources */ = {isa = PBXBuild D38024111E7130C700466558 /* MSStartServiceLogTests.m */, D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */, 04B7BBEE1E5FAD4D001A0CE1 /* MSSenderUtilTests.m */, + 5C7877911EA0CFF3002263CC /* MSDBStorageTests.m */, ); path = MobileCenterTests; sourceTree = ""; @@ -925,6 +928,7 @@ D377A30D1E83A05900B2C97A /* MSMockUserDefaults.m in Sources */ = {isa = PBXBuild 6EB1F40E1D2443B7005F9F99 /* MSChannelDefaultTests.m in Sources */, 384959D51D491D4F008F6B3A /* MSMobileCenterTests.m in Sources */, D36136831E7BB338004AE043 /* MSStoragePerfomanceTests.m in Sources */, + 5C7877921EA0CFF3002263CC /* MSDBStorageTests.m in Sources */, 6E5D8A801D25B91F00C033B1 /* MSFileUtilTests.m in Sources */, 386E8D931E25932100EECF0F /* MSHttpTestUtil.m in Sources */, B2FD53651E567BCF0050F909 /* MSDeviceHistoryInfoTests.m in Sources */, diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h index d16e7a20cb..c2e2e260ce 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h @@ -3,4 +3,17 @@ @interface MSDBStorage : NSObject +/** + * Return all logs with storageKey + * + * @return All founded logs + */ +- (NSMutableArray*) getLogsWith:(NSString*)storageKey; + + +/** + * Delete all logs with storageKey + */ +- (void) deleteLogsWith:(NSString*)storageKey; + @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index b3e4862caa..1b5ca42e89 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -64,7 +64,12 @@ - (void)deleteLogsForId:(NSString *)logsId withGroupID:(NSString *)groupID { } - (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDataCompletionBlock)completion { - NSArray *logs = [self getLogsWith:groupID]; + NSMutableArray *logs = [self getLogsWith:groupID]; + + // Remove excess logs + if (logs.count > self.bucketFileLogCountLimit) { + [logs removeObjectsInRange:NSMakeRange(self.bucketFileLogCountLimit, logs.count - self.bucketFileLogCountLimit)]; + } if (completion) { completion(logs.count > 0, logs, @""); } @@ -77,7 +82,7 @@ - (void)closeBatchWithGroupID:(NSString *)groupID { #pragma mark - Private -- (NSArray*) getLogsWith:(NSString*)storageKey { +- (NSMutableArray*) getLogsWith:(NSString*)storageKey { NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@'", kMSLogTableName, kMSStorageKeyColumnName, storageKey]; NSArray*> *result = [self.connection loadDataFromDB:selectLogQuery]; diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m index ade1a376b7..d08baae36a 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m @@ -106,7 +106,7 @@ - (void)deleteLogsForId:(NSString *)logsId withGroupID:(NSString *)groupID { } - (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDataCompletionBlock)completion { - NSArray *logs; + NSMutableArray *logs; NSString *fileId; MSStorageBucket *bucket = [self bucketForGroupID:groupID]; @@ -122,6 +122,11 @@ - (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDa [bucket.availableFiles removeLastObject]; } + // Remove excess logs + if (logs.count > self.bucketFileLogCountLimit) { + [logs removeObjectsInRange:NSMakeRange(self.bucketFileLogCountLimit, logs.count - self.bucketFileLogCountLimit)]; + } + // Load fails if no logs found. if (completion) { completion((logs.count > 0), logs, fileId); diff --git a/MobileCenter/MobileCenterTests/MSFileStorageTests.m b/MobileCenter/MobileCenterTests/MSFileStorageTests.m index 6932ee2254..34e23ce5f3 100644 --- a/MobileCenter/MobileCenterTests/MSFileStorageTests.m +++ b/MobileCenter/MobileCenterTests/MSFileStorageTests.m @@ -295,4 +295,39 @@ - (void)testLoadBatchWillCreateNewCurrentFile { assertThat(bucket.currentFile, isNot(equalTo(currentFile))); } +- (void)testLoadTooManyLogs { + id classMock = OCMClassMock([NSKeyedUnarchiver class]); + OCMStub([classMock unarchiveObjectWithData:[OCMArg any]]).andReturn([self generateLogs:self.sut.bucketFileLogCountLimit]); + + // If + NSString *groupID = @"GroupID"; + self.sut.buckets[groupID] = [MSStorageBucket new]; + MSStorageBucket *bucket = self.sut.buckets[groupID]; + + MSFile *availableFile1 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"1"] + fileId:@"1" + creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; + bucket.availableFiles = [@[ availableFile1 ] mutableCopy]; + + // When + [self.sut loadLogsForGroupID:groupID withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { + + // Then + XCTAssertTrue(succeeded); + XCTAssertTrue(self.sut.bucketFileLogCountLimit == logArray.count); + }]; + + // Restoring the class + [classMock stopMocking]; +} + +- (NSArray*)generateLogs:(NSUInteger)maxCountLogs { + NSUInteger totalLogs = maxCountLogs * 2; + NSMutableArray *logs = [NSMutableArray arrayWithCapacity:totalLogs]; + for (NSUInteger i = 0; i < totalLogs; ++i) { + [logs addObject:[MSAbstractLog new]]; + } + return logs; +} + @end From b144d885b92d1df99634fc21647af4c0211a6290 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 14 Apr 2017 14:48:03 +0300 Subject: [PATCH 12/76] Remove excess method --- .../MobileCenter/Internals/Channel/MSChannelDefault.m | 1 - .../MobileCenter/Internals/Storage/MSDBStorage.m | 4 ---- .../MobileCenter/Internals/Storage/MSFileStorage.m | 4 ---- MobileCenter/MobileCenter/Internals/Storage/MSStorage.h | 9 --------- 4 files changed, 18 deletions(-) diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m index 7bb8922ca0..068ab18800 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m @@ -145,7 +145,6 @@ - (void)flushQueue { // Still close the current batch it will be flushed later. if (self.itemsCount >= self.configuration.batchSizeLimit) { - [self.storage closeBatchWithGroupID:self.configuration.groupID]; // That batch becomes available. self.availableBatchFromStorage = YES; diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 1b5ca42e89..1db0e961f8 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -76,10 +76,6 @@ - (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDa return logs.count > 0; } -- (void)closeBatchWithGroupID:(NSString *)groupID { - [self deleteLogsForGroupID:groupID]; -} - #pragma mark - Private - (NSMutableArray*) getLogsWith:(NSString*)storageKey { diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m index d08baae36a..8c1f8772fd 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m @@ -136,10 +136,6 @@ - (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDa return (bucket.availableFiles.count > 0); } -- (void)closeBatchWithGroupID:(NSString *)groupID { - [self renewCurrentFileForGroupID:groupID]; -} - #pragma mark - Helper - (MSStorageBucket *)createNewBucketForGroupID:(NSString *)groupID { diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h index 9042447240..1bdd6b96cd 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h @@ -63,15 +63,6 @@ typedef void (^MSLoadDataCompletionBlock)(BOOL succeeded, NSArray *logArr */ - (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDataCompletionBlock)completion; -/** - * FIXME: The number of logs per batch and the number of logs per files are currently tied together. The storage loads - * what's contained in the available file and this could be higher than the batch max size going to be sent. To mitigate - * this kind of scenario the file is closed when the max size of the log batch is reached. - * - * @param groupID The key used for grouping. - */ -- (void)closeBatchWithGroupID:(NSString *)groupID; - @end NS_ASSUME_NONNULL_END From 294f6539385d3bd51061d1f8dd05c3c5d7356560 Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Mon, 17 Apr 2017 14:26:53 -0700 Subject: [PATCH 13/76] Remove file storage for logs --- .../MobileCenter.xcodeproj/project.pbxproj | 54 --- .../Internals/Channel/MSLogManagerDefault.m | 2 - .../Internals/Storage/MSDBStorage.m | 1 - .../Internals/Storage/MSFileStorage.h | 40 --- .../Internals/Storage/MSFileStorage.m | 200 ----------- .../Internals/Storage/MSStorage.h | 7 - .../Internals/Storage/MSStorageBucket.h | 63 ---- .../Internals/Storage/MSStorageBucket.m | 56 --- .../MobileCenterTests/MSFileStorageTests.m | 333 ------------------ MobileCenter/MobileCenterTests/MSFileTests.m | 45 --- .../MobileCenterTests/MSFileUtilTests.m | 261 -------------- .../MobileCenterTests/MSStorageBucketTests.m | 137 ------- .../MSStoragePerfomanceTests.m | 5 - .../Util/MSStorageTestUtil.h | 17 - .../Util/MSStorageTestUtil.m | 55 --- 15 files changed, 1276 deletions(-) delete mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.h delete mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m delete mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSStorageBucket.h delete mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSStorageBucket.m delete mode 100644 MobileCenter/MobileCenterTests/MSFileStorageTests.m delete mode 100644 MobileCenter/MobileCenterTests/MSFileTests.m delete mode 100644 MobileCenter/MobileCenterTests/MSFileUtilTests.m delete mode 100644 MobileCenter/MobileCenterTests/MSStorageBucketTests.m delete mode 100644 MobileCenter/MobileCenterTests/Util/MSStorageTestUtil.h delete mode 100644 MobileCenter/MobileCenterTests/Util/MSStorageTestUtil.m diff --git a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj index 1ce06cbffc..11047574c4 100644 --- a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj +++ b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj @@ -58,7 +58,6 @@ 387C76FD1D6C9CF100D68CC1 /* MSServiceAbstract.h in Headers */ = {isa = PBXBuildFile; fileRef = 387C76FC1D6C9CF100D68CC1 /* MSServiceAbstract.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3889932F1E29829700C27B36 /* MSMobileCenterErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 3889932E1E29829700C27B36 /* MSMobileCenterErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3889933A1E29B26D00C27B36 /* MSMobileCenterErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 388993391E29B26D00C27B36 /* MSMobileCenterErrors.m */; }; - 38CA60ED1D949F5000B82420 /* MSFileStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E40D4621D2700000046D85B /* MSFileStorage.m */; }; 38E1B67A1DDE3FDF000EFED1 /* MSMobileCenterPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38E1B6791DDE3FDF000EFED1 /* MSMobileCenterPrivate.h */; }; 38EAD9AC1DA6A8C500CE6030 /* MSEnable.h in Headers */ = {isa = PBXBuildFile; fileRef = 38EAD9AB1DA6A8C500CE6030 /* MSEnable.h */; }; 38F1944E1DADB93100D3E0FE /* MSHttpSenderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38F1944D1DADB93100D3E0FE /* MSHttpSenderPrivate.h */; }; @@ -75,29 +74,17 @@ 6E0684631D36BC8D00A8CC6C /* MSChannelDefault.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E0684611D36BC8D00A8CC6C /* MSChannelDefault.h */; }; 6E0684641D36BC8D00A8CC6C /* MSChannelDefault.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E0684621D36BC8D00A8CC6C /* MSChannelDefault.m */; }; 6E0684671D36BCD500A8CC6C /* MSChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E0684651D36BCD500A8CC6C /* MSChannel.h */; }; - 6E0CD69E1D27198A0045FE8C /* MSStorageBucket.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E0CD69C1D27198A0045FE8C /* MSStorageBucket.h */; }; - 6E0CD69F1D27198A0045FE8C /* MSStorageBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E0CD69D1D27198A0045FE8C /* MSStorageBucket.m */; }; 6E171ADC1D22F6ED000DC480 /* OCHamcrestIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E171ADB1D22F6ED000DC480 /* OCHamcrestIOS.framework */; }; 6E171B1F1D22FC2E000DC480 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E171B131D22FC2E000DC480 /* libOCMock.a */; }; 6E171B601D234717000DC480 /* MSLogContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E171B581D234717000DC480 /* MSLogContainer.h */; }; 6E171B611D234717000DC480 /* MSLogContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E171B591D234717000DC480 /* MSLogContainer.m */; }; 6E23957D1D22EF4F00E543C8 /* libMobileCenter.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E0401031D1C98220051BCFA /* libMobileCenter.a */; }; - 6E2BA1AF1D2499F9002D7B88 /* MSFileUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E2BA1AD1D2499F9002D7B88 /* MSFileUtil.h */; }; - 6E2BA1B01D2499F9002D7B88 /* MSFileUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E2BA1AE1D2499F9002D7B88 /* MSFileUtil.m */; }; - 6E363AE51D2C2C320079043D /* MSFileStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E363AE41D2C2C320079043D /* MSFileStorageTests.m */; }; - 6E363AF21D2C84EA0079043D /* MSStorageTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E363AF11D2C84EA0079043D /* MSStorageTestUtil.m */; }; 6E3E2C9E1D35701000B1EE50 /* MSLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E3E2C9D1D35701000B1EE50 /* MSLog.h */; }; 6E3E2CC11D3596AE00B1EE50 /* MSDeviceLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E3E2CC01D3596AE00B1EE50 /* MSDeviceLogTests.m */; }; - 6E40D4631D2700000046D85B /* MSFileStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E40D4611D2700000046D85B /* MSFileStorage.h */; }; 6E48A5A41D3831FE006E8B5F /* MSChannelConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E48A5A31D3831FE006E8B5F /* MSChannelConfigurationTests.m */; }; 6E48A5A71D383893006E8B5F /* MSLogManagerDefaultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E48A5A61D383893006E8B5F /* MSLogManagerDefaultTests.m */; }; 6E48A5A81D3856F5006E8B5F /* MSConstants+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = E829E4241D25D07600F19DA1 /* MSConstants+Internal.h */; }; - 6E58E2B71D2F12A70039D062 /* MSStorageBucketTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E58E2B61D2F12A70039D062 /* MSStorageBucketTests.m */; }; - 6E58E2BE1D2F18DA0039D062 /* MSFileTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E58E2BD1D2F18DA0039D062 /* MSFileTests.m */; }; - 6E5D8A801D25B91F00C033B1 /* MSFileUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E5D8A7F1D25B91F00C033B1 /* MSFileUtilTests.m */; }; 6E7D5C7B1D3E94F8009EC9AC /* MSSerializableObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E7D5C791D3E94F8009EC9AC /* MSSerializableObject.h */; }; - 6E7E3B781D2DCEBD0019EA6D /* MSFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E7E3B761D2DCEBD0019EA6D /* MSFile.h */; }; - 6E7E3B791D2DCEBD0019EA6D /* MSFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E7E3B771D2DCEBD0019EA6D /* MSFile.m */; }; 6EB1F40E1D2443B7005F9F99 /* MSChannelDefaultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6EB1F40D1D2443B7005F9F99 /* MSChannelDefaultTests.m */; }; 6EF628F41D371B1600CAFF64 /* MSChannelConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EF628F21D371B1600CAFF64 /* MSChannelConfiguration.h */; }; 6EF628F51D371B1600CAFF64 /* MSChannelConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 6EF628F31D371B1600CAFF64 /* MSChannelConfiguration.m */; }; @@ -239,8 +226,6 @@ 6E0684611D36BC8D00A8CC6C /* MSChannelDefault.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MSChannelDefault.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 6E0684621D36BC8D00A8CC6C /* MSChannelDefault.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MSChannelDefault.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 6E0684651D36BCD500A8CC6C /* MSChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MSChannel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 6E0CD69C1D27198A0045FE8C /* MSStorageBucket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSStorageBucket.h; sourceTree = ""; }; - 6E0CD69D1D27198A0045FE8C /* MSStorageBucket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStorageBucket.m; sourceTree = ""; }; 6E171ADB1D22F6ED000DC480 /* OCHamcrestIOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCHamcrestIOS.framework; path = ../../Vendor/OCHamcrest/OCHamcrestIOS.framework; sourceTree = ""; }; 6E171B131D22FC2E000DC480 /* libOCMock.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libOCMock.a; sourceTree = ""; }; 6E171B151D22FC2E000DC480 /* NSNotificationCenter+OCMAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+OCMAdditions.h"; sourceTree = ""; }; @@ -257,23 +242,11 @@ 6E171B591D234717000DC480 /* MSLogContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSLogContainer.m; sourceTree = ""; }; 6E2395781D22EF4F00E543C8 /* MobileCenter.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MobileCenter.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6E23957C1D22EF4F00E543C8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 6E2BA1AD1D2499F9002D7B88 /* MSFileUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSFileUtil.h; sourceTree = ""; }; - 6E2BA1AE1D2499F9002D7B88 /* MSFileUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSFileUtil.m; sourceTree = ""; }; - 6E363AE41D2C2C320079043D /* MSFileStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSFileStorageTests.m; sourceTree = ""; }; - 6E363AF01D2C84EA0079043D /* MSStorageTestUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSStorageTestUtil.h; sourceTree = ""; }; - 6E363AF11D2C84EA0079043D /* MSStorageTestUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStorageTestUtil.m; sourceTree = ""; }; 6E3E2C9D1D35701000B1EE50 /* MSLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSLog.h; sourceTree = ""; }; 6E3E2CC01D3596AE00B1EE50 /* MSDeviceLogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDeviceLogTests.m; sourceTree = ""; }; - 6E40D4611D2700000046D85B /* MSFileStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSFileStorage.h; sourceTree = ""; }; - 6E40D4621D2700000046D85B /* MSFileStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MSFileStorage.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 6E48A5A31D3831FE006E8B5F /* MSChannelConfigurationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSChannelConfigurationTests.m; sourceTree = ""; }; 6E48A5A61D383893006E8B5F /* MSLogManagerDefaultTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSLogManagerDefaultTests.m; sourceTree = ""; }; - 6E58E2B61D2F12A70039D062 /* MSStorageBucketTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStorageBucketTests.m; sourceTree = ""; }; - 6E58E2BD1D2F18DA0039D062 /* MSFileTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSFileTests.m; sourceTree = ""; }; - 6E5D8A7F1D25B91F00C033B1 /* MSFileUtilTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSFileUtilTests.m; sourceTree = ""; }; 6E7D5C791D3E94F8009EC9AC /* MSSerializableObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSSerializableObject.h; sourceTree = ""; }; - 6E7E3B761D2DCEBD0019EA6D /* MSFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSFile.h; sourceTree = ""; }; - 6E7E3B771D2DCEBD0019EA6D /* MSFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSFile.m; sourceTree = ""; }; 6EB1F40D1D2443B7005F9F99 /* MSChannelDefaultTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MSChannelDefaultTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 6EF628F21D371B1600CAFF64 /* MSChannelConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSChannelConfiguration.h; sourceTree = ""; }; 6EF628F31D371B1600CAFF64 /* MSChannelConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSChannelConfiguration.m; sourceTree = ""; }; @@ -543,13 +516,9 @@ 6E23957C1D22EF4F00E543C8 /* Info.plist */, 6EB1F40D1D2443B7005F9F99 /* MSChannelDefaultTests.m */, 385FC0541D37EBD700A1799F /* MSDeviceTrackerTests.m */, - 6E5D8A7F1D25B91F00C033B1 /* MSFileUtilTests.m */, E829E4221D25C8BA00F19DA1 /* MSIngestionSenderTests.m */, E88EBBFA1D2C8CC7007E7785 /* MSLogContainerTests.m */, 045660FA1D99EEEB002F7055 /* MSLogWithPropertiesTests.m */, - 6E363AE41D2C2C320079043D /* MSFileStorageTests.m */, - 6E58E2B61D2F12A70039D062 /* MSStorageBucketTests.m */, - 6E58E2BD1D2F18DA0039D062 /* MSFileTests.m */, 6E3E2CC01D3596AE00B1EE50 /* MSDeviceLogTests.m */, 6E48A5A31D3831FE006E8B5F /* MSChannelConfigurationTests.m */, 6E48A5A61D383893006E8B5F /* MSLogManagerDefaultTests.m */, @@ -582,8 +551,6 @@ children = ( 386E8D921E25932100EECF0F /* MSHttpTestUtil.h */, 386E8D911E25932100EECF0F /* MSHttpTestUtil.m */, - 6E363AF01D2C84EA0079043D /* MSStorageTestUtil.h */, - 6E363AF11D2C84EA0079043D /* MSStorageTestUtil.m */, E88D17041D35B6B500A5EA57 /* MSMockLog.h */, E88D17051D35B6B500A5EA57 /* MSMockLog.m */, D377A30B1E83A04600B2C97A /* MSMockUserDefaults.h */, @@ -631,14 +598,6 @@ isa = PBXGroup; children = ( E84B8E381D235246006FD231 /* MSStorage.h */, - 6E2BA1AD1D2499F9002D7B88 /* MSFileUtil.h */, - 6E2BA1AE1D2499F9002D7B88 /* MSFileUtil.m */, - 6E40D4611D2700000046D85B /* MSFileStorage.h */, - 6E40D4621D2700000046D85B /* MSFileStorage.m */, - 6E0CD69C1D27198A0045FE8C /* MSStorageBucket.h */, - 6E0CD69D1D27198A0045FE8C /* MSStorageBucket.m */, - 6E7E3B761D2DCEBD0019EA6D /* MSFile.h */, - 6E7E3B771D2DCEBD0019EA6D /* MSFile.m */, E8A8D1E91D3057A90022931E /* MSUserDefaults.h */, E8A8D1EA1D3057A90022931E /* MSUserDefaults.m */, D341DBCD1E77EF8600D385F9 /* MSDBStorage.h */, @@ -699,7 +658,6 @@ 04FD126F1E415697007ABFE7 /* MSLogManagerDefaultPrivate.h in Headers */, 38E1B67A1DDE3FDF000EFED1 /* MSMobileCenterPrivate.h in Headers */, 387C75811D6270A300D68CC1 /* MSServiceAbstractInternal.h in Headers */, - 6E0CD69E1D27198A0045FE8C /* MSStorageBucket.h in Headers */, 387C76DA1D677EC100D68CC1 /* MSServiceAbstractPrivate.h in Headers */, E8753D6E1D4BE53F00241513 /* MSSenderUtil.h in Headers */, E8E48F951D50FF4300A8C1B0 /* MSSenderCallDelegate.h in Headers */, @@ -721,14 +679,12 @@ E84B8E391D235246006FD231 /* MSStorage.h in Headers */, 3592ABA71DC90E3600EF4592 /* MSLogger.h in Headers */, 6E7D5C7B1D3E94F8009EC9AC /* MSSerializableObject.h in Headers */, - 6E40D4631D2700000046D85B /* MSFileStorage.h in Headers */, 6E0684631D36BC8D00A8CC6C /* MSChannelDefault.h in Headers */, E8AEE9821D5970A400C0FF6C /* MSLogManagerDelegate.h in Headers */, 6E04016C1D1C9E1F0051BCFA /* MobileCenter.h in Headers */, E84B8E351D235226006FD231 /* MSHttpSender.h in Headers */, 6E171B601D234717000DC480 /* MSLogContainer.h in Headers */, E88EBBEC1D2C612E007E7785 /* MSAbstractLog.h in Headers */, - 6E7E3B781D2DCEBD0019EA6D /* MSFile.h in Headers */, 6EF628F41D371B1600CAFF64 /* MSChannelConfiguration.h in Headers */, 6E04016F1D1C9E460051BCFA /* MSMobileCenterInternal.h in Headers */, 6E0401701D1C9E460051BCFA /* MSService.h in Headers */, @@ -740,7 +696,6 @@ D341DBCE1E77F04300D385F9 /* MSDBStorage.h in Headers */, 38EAD9AC1DA6A8C500CE6030 /* MSEnable.h in Headers */, 842C13201E7FCF5300F16EA3 /* MSUtility+Application.h in Headers */, - 6E2BA1AF1D2499F9002D7B88 /* MSFileUtil.h in Headers */, 381C91E91D3DB65D004512F1 /* MSDeviceTracker.h in Headers */, 842C13221E7FCF5300F16EA3 /* MSUtility+ApplicationPrivate.h in Headers */, E84B8E2F1D2351DB006FD231 /* MSLogManagerDefault.h in Headers */, @@ -879,7 +834,6 @@ buildActionMask = 2147483647; files = ( 6E0401571D1C9AAA0051BCFA /* MSMobileCenter.m in Sources */, - 6E2BA1B01D2499F9002D7B88 /* MSFileUtil.m in Sources */, D341DBD61E77FD8B00D385F9 /* MSDBStorage.m in Sources */, 3592ABA81DC90E3600EF4592 /* MSLogger.m in Sources */, 6EF628F51D371B1600CAFF64 /* MSChannelConfiguration.m in Sources */, @@ -900,16 +854,13 @@ 045BC3171E3FD88600B6C960 /* MSKeychainUtil.m in Sources */, 35D0B7541DDFABFD003EACCD /* MSWrapperLogger.m in Sources */, D35D61821E785FBA00D81A0F /* MSSqliteConnection.m in Sources */, - 38CA60ED1D949F5000B82420 /* MSFileStorage.m in Sources */, 6E0684641D36BC8D00A8CC6C /* MSChannelDefault.m in Sources */, E80EB1081D50273700C9003F /* MSSenderCall.m in Sources */, - 6E0CD69F1D27198A0045FE8C /* MSStorageBucket.m in Sources */, E83283C71D46C62E000B029E /* MS_Reachability.m in Sources */, E8010E6B1D2DD4EF0035196F /* MSLogWithProperties.m in Sources */, E8A8D1EC1D3057A90022931E /* MSUserDefaults.m in Sources */, B2C3C1931DB9864600CB83F7 /* MSWrapperSdk.m in Sources */, 842C13241E7FCF5300F16EA3 /* MSUtility+Date.m in Sources */, - 6E7E3B791D2DCEBD0019EA6D /* MSFile.m in Sources */, 842C131F1E7FCF5300F16EA3 /* MSUtility.m in Sources */, BA682E63DDB55EED56809831 /* MSIngestionSender.m in Sources */, ); @@ -920,8 +871,6 @@ buildActionMask = 2147483647; files = ( D377A30D1E83A05900B2C97A /* MSMockUserDefaults.m in Sources */, - 6E58E2B71D2F12A70039D062 /* MSStorageBucketTests.m in Sources */, - 6E363AE51D2C2C320079043D /* MSFileStorageTests.m in Sources */, 045660FB1D99EEEB002F7055 /* MSLogWithPropertiesTests.m in Sources */, 6E3E2CC11D3596AE00B1EE50 /* MSDeviceLogTests.m in Sources */, 385FC0551D37EBD700A1799F /* MSDeviceTrackerTests.m in Sources */, @@ -929,12 +878,10 @@ 384959D51D491D4F008F6B3A /* MSMobileCenterTests.m in Sources */, D36136831E7BB338004AE043 /* MSStoragePerfomanceTests.m in Sources */, 5C7877921EA0CFF3002263CC /* MSDBStorageTests.m in Sources */, - 6E5D8A801D25B91F00C033B1 /* MSFileUtilTests.m in Sources */, 386E8D931E25932100EECF0F /* MSHttpTestUtil.m in Sources */, B2FD53651E567BCF0050F909 /* MSDeviceHistoryInfoTests.m in Sources */, 380A4DCB1DD6908A00E99219 /* MSUtilityTests.m in Sources */, E829E4231D25C8BA00F19DA1 /* MSIngestionSenderTests.m in Sources */, - 6E363AF21D2C84EA0079043D /* MSStorageTestUtil.m in Sources */, E88EBBFB1D2C8CC7007E7785 /* MSLogContainerTests.m in Sources */, 04FD126B1E4103CC007ABFE7 /* MSKeychainUtilTests.m in Sources */, D38024121E7130C700466558 /* MSStartServiceLogTests.m in Sources */, @@ -942,7 +889,6 @@ 6E48A5A41D3831FE006E8B5F /* MSChannelConfigurationTests.m in Sources */, B24F3F171D93A3FF00827213 /* MSLoggerTests.m in Sources */, 04B7BBEF1E5FAD4D001A0CE1 /* MSSenderUtilTests.m in Sources */, - 6E58E2BE1D2F18DA0039D062 /* MSFileTests.m in Sources */, E88D17061D35B6B500A5EA57 /* MSMockLog.m in Sources */, 6E48A5A71D383893006E8B5F /* MSLogManagerDefaultTests.m in Sources */, ); diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m index b094865e25..3334615572 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m @@ -1,7 +1,6 @@ #import "MobileCenter+Internal.h" #import "MSChannelDefault.h" #import "MSDBStorage.h" -#import "MSFileStorage.h" #import "MSHttpSender.h" #import "MSIngestionSender.h" #import "MSLogManagerDefault.h" @@ -33,7 +32,6 @@ - (instancetype)initWithAppSecret:(NSString *)appSecret installId:(NSUUID *)inst appSecret:appSecret installId:[installId UUIDString]] storage:[MSDBStorage new]]; - //storage:[[MSFileStorage alloc] init]]; return self; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 1db0e961f8..3dbf3ba1d9 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -17,7 +17,6 @@ @interface MSDBStorage() @implementation MSDBStorage -@synthesize bucketFileCountLimit; @synthesize bucketFileLogCountLimit; @synthesize connection; diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.h deleted file mode 100644 index b7cc20525b..0000000000 --- a/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.h +++ /dev/null @@ -1,40 +0,0 @@ -#import "MSStorage.h" -#import "MSStorageBucket.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface MSFileStorage : NSObject - -/** - * The directory for saving SDK related files within the app's folder. - */ -@property(nonatomic, copy) NSURL *baseDirectoryURL; - -/** - * A dictionary containing file names and their status for certain storage keys. - */ -@property(nonatomic) NSMutableDictionary *buckets; - -/** - * Returns the file path to a log file based on its id and storage key. - * - * @param groupID A groupID which identifies the group of the log file. - * @param logsId The internal Id of the file. - * - * @return the file url - */ -- (NSURL *) fileURLForGroupID:(NSString *)groupID logsId:(NSString *)logsId; - -/** - * Returns the bucket for a given storage key or creates a new one if it doesn't exist, yet. - * - * @param groupID The groupID for the bucket. - * - * @return The bucket for a given storage key. - */ -- (MSStorageBucket *)bucketForGroupID:(NSString *)groupID; - -@end - -NS_ASSUME_NONNULL_END diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m deleted file mode 100644 index 8c1f8772fd..0000000000 --- a/MobileCenter/MobileCenter/Internals/Storage/MSFileStorage.m +++ /dev/null @@ -1,200 +0,0 @@ -#import "MSFile.h" -#import "MSFileStorage.h" -#import "MSFileUtil.h" -#import "MSLogger.h" -#import "MSMobileCenterInternal.h" - -static NSString *const kMSLogsDirectory = @"com.microsoft.azure.mobile.mobilecenter/logs"; -static NSString *const kMSFileExtension = @"ms"; -// FIXME Need a different storage such as database to make it work properly. -// For now, persistence will maintain up to 350 logs and remove the oldest 50 logs in a file. -// Plus, the requirement is to keep 300 logs for all the logs stored across the buckets but the limit is -// currently only applied per bucket. -static NSUInteger const MSDefaultFileCountLimit = 7; -static NSUInteger const MSDefaultLogCountLimit = 50; - -@implementation MSFileStorage - -@synthesize bucketFileCountLimit = _bucketFileCountLimit; -@synthesize bucketFileLogCountLimit = _bucketFileLogCountLimit; - -#pragma mark - Initialisation - -- (instancetype)init { - if ((self = [super init])) { - _buckets = [NSMutableDictionary new]; - _bucketFileCountLimit = MSDefaultFileCountLimit; - _bucketFileLogCountLimit = MSDefaultLogCountLimit; - } - return self; -} - -#pragma mark - Public - -- (BOOL)saveLog:(id)log withGroupID:(NSString *)groupID { - if (!log) { - return NO; - } - - MSStorageBucket *bucket = [self bucketForGroupID:groupID]; - - if (bucket.currentLogs.count >= self.bucketFileLogCountLimit) { - [bucket.currentLogs removeAllObjects]; - [self renewCurrentFileForGroupID:groupID]; - } - - if (bucket.currentLogs.count == 0) { - - // Drop oldest files if needed - if (bucket.availableFiles.count >= self.bucketFileCountLimit) { - MSFile *oldestFile = [bucket.availableFiles lastObject]; - [self deleteLogsForId:oldestFile.fileId withGroupID:groupID]; - } - - // Make current file available and create new current file - [bucket.availableFiles insertObject:bucket.currentFile atIndex:0]; - } - - [bucket.currentLogs addObject:log]; - NSData *logsData = [NSKeyedArchiver archivedDataWithRootObject:bucket.currentLogs]; - - return [MSFileUtil writeData:logsData toFile:bucket.currentFile]; -} - -- (NSArray *)deleteLogsForGroupID:(NSString *)groupID { - - // Cache deleted logs - NSMutableArray *deletedLogs = [NSMutableArray new]; - - // Remove all files from the bucket. - MSStorageBucket *bucket = self.buckets[groupID]; - NSArray *allFiles = [bucket removeAllFiles]; - - // Delete all files. - for (MSFile *file in allFiles) { - [deletedLogs addObjectsFromArray:[self deleteFile:file fromBucket:bucket]]; - } - - // Get ready for next time. - [self renewCurrentFileForGroupID:groupID]; - return deletedLogs; -} - -- (void)deleteLogsForId:(NSString *)logsId withGroupID:(NSString *)groupID { - MSStorageBucket *bucket = self.buckets[groupID]; - [self deleteFile:[bucket fileWithId:logsId] fromBucket:bucket]; -} - -- (NSArray *)deleteFile:(MSFile *)file fromBucket:(MSStorageBucket *)bucket { - NSMutableArray *deletedLogs = [NSMutableArray new]; - if (file) { - - // Cache logs from file. - NSData *data = [MSFileUtil dataForFile:file]; - if (data) { - NSArray *logs = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData * _Nonnull)data]; - if (logs) { - [deletedLogs addObjectsFromArray:logs]; - } - } - - // Wipe it. - [MSFileUtil deleteFile:file]; - [bucket removeFile:file]; - } - return deletedLogs; -} - -- (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDataCompletionBlock)completion { - NSMutableArray *logs; - NSString *fileId; - MSStorageBucket *bucket = [self bucketForGroupID:groupID]; - - [self renewCurrentFileForGroupID:groupID]; - - // Get data of oldest file - if (bucket.availableFiles.count > 0) { - MSFile *file = bucket.availableFiles.lastObject; - fileId = file.fileId; - NSData *logData = [MSFileUtil dataForFile:file]; - logs = [NSKeyedUnarchiver unarchiveObjectWithData:logData]; - [bucket.blockedFiles addObject:file]; - [bucket.availableFiles removeLastObject]; - } - - // Remove excess logs - if (logs.count > self.bucketFileLogCountLimit) { - [logs removeObjectsInRange:NSMakeRange(self.bucketFileLogCountLimit, logs.count - self.bucketFileLogCountLimit)]; - } - - // Load fails if no logs found. - if (completion) { - completion((logs.count > 0), logs, fileId); - } - - // Return YES if there are more logs to send. - return (bucket.availableFiles.count > 0); -} - -#pragma mark - Helper - -- (MSStorageBucket *)createNewBucketForGroupID:(NSString *)groupID { - MSStorageBucket *bucket = [MSStorageBucket new]; - NSURL *storageDirectory = [self directoryURLForGroupID:groupID]; - NSArray *existingFiles = [MSFileUtil filesForDirectory:storageDirectory withFileExtension:kMSFileExtension]; - if (existingFiles) { - [bucket.availableFiles addObjectsFromArray:existingFiles]; - [bucket sortAvailableFilesByCreationDate]; - } - self.buckets[groupID] = bucket; - [self renewCurrentFileForGroupID:groupID]; - - return bucket; -} - -- (MSStorageBucket *)bucketForGroupID:(NSString *)groupID { - MSStorageBucket *bucket = self.buckets[groupID]; - if (!bucket) { - bucket = [self createNewBucketForGroupID:groupID]; - } - - return bucket; -} - -- (void)renewCurrentFileForGroupID:(NSString *)groupID { - MSStorageBucket *bucket = [self bucketForGroupID:groupID]; - NSDate *creationDate = [NSDate date]; - NSString *fileId = MS_UUID_STRING; - NSURL *fileURL = [self fileURLForGroupID:groupID logsId:fileId]; - MSFile *file = [[MSFile alloc] initWithURL:fileURL fileId:fileId creationDate:creationDate]; - bucket.currentFile = file; - [bucket.currentLogs removeAllObjects]; -} - -- (NSURL *)directoryURLForGroupID:(NSString *)groupID { - NSURL *fileURL = [self.baseDirectoryURL URLByAppendingPathComponent:groupID]; - - return fileURL; -} - -- (NSURL *)fileURLForGroupID:(NSString *)groupID logsId:(nonnull NSString *)logsId { - NSString *fileName = [logsId stringByAppendingPathExtension:kMSFileExtension]; - NSURL *fileURL = [[self directoryURLForGroupID:groupID] URLByAppendingPathComponent:fileName]; - - return fileURL; -} - -- (NSURL *)baseDirectoryURL { - if (!_baseDirectoryURL) { - NSURL *appSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject]; - if (appSupportURL) { - _baseDirectoryURL = (NSURL * _Nonnull)[appSupportURL URLByAppendingPathComponent:kMSLogsDirectory]; - } - NSURL *url = _baseDirectoryURL; - MSLogVerbose([MSMobileCenter logTag], @"Storage Path:\n%@", url); - } - - return _baseDirectoryURL; -} - -@end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h index 1bdd6b96cd..16117e588b 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h @@ -11,13 +11,6 @@ typedef void (^MSLoadDataCompletionBlock)(BOOL succeeded, NSArray *logArr */ @protocol MSStorage -/** - * Defines the maximum count of app log files per storage key on the file system. - * - * Default: 7 - */ -@property(nonatomic) NSUInteger bucketFileCountLimit; - /** * Defines the maximum count of app logs per storage key in a file. * diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSStorageBucket.h b/MobileCenter/MobileCenter/Internals/Storage/MSStorageBucket.h deleted file mode 100644 index ac0c26975e..0000000000 --- a/MobileCenter/MobileCenter/Internals/Storage/MSStorageBucket.h +++ /dev/null @@ -1,63 +0,0 @@ -#import "MSFile.h" -#import "MSLog.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - A class which manages the files inside a subdirectory on the file system. - */ -@interface MSStorageBucket : NSObject - -/** - * The file instance representing the current file used for adding new logs. - */ -@property(nonatomic) MSFile *currentFile; - -/** - * A in-memory list of all items that have been added to the current batch. - */ -@property(nonatomic) NSMutableArray *currentLogs; - -/** - * A list of file names that are currently used by other components. - */ -@property(nonatomic) NSMutableArray *blockedFiles; - -/** - * A list of file names that can be accessed by other components. - */ -@property(nonatomic) NSMutableArray *availableFiles; - -/** - * Returns the file with the given id. - * - * @param fileId the Id of the requested file - * - * @return the file for the given id - */ -- (MSFile *)fileWithId:(NSString *)fileId; - -/** - * Sorts the list of available files by creation date. The most recent file will - * be at the last index. - */ -- (void)sortAvailableFilesByCreationDate; - -/** - * Removes the given file from the bucket's internal available or blocked list. - * - * @param file The file to delete. - */ -- (void)removeFile:(MSFile *)file; - -/** - * Removes all files from the bucket's internal available or blocked list. - * - * @return An array containing the removed files. - */ -- (NSArray *)removeAllFiles; - -@end - -NS_ASSUME_NONNULL_END diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSStorageBucket.m b/MobileCenter/MobileCenter/Internals/Storage/MSStorageBucket.m deleted file mode 100644 index 83198cba93..0000000000 --- a/MobileCenter/MobileCenter/Internals/Storage/MSStorageBucket.m +++ /dev/null @@ -1,56 +0,0 @@ -#import "MSStorageBucket.h" - -@implementation MSStorageBucket - -- (instancetype)init { - if ((self = [super init])) { - _availableFiles = [NSMutableArray new]; - _blockedFiles = [NSMutableArray new]; - _currentLogs = [NSMutableArray new]; - } - return self; -} - -- (MSFile *)fileWithId:(NSString *)fileId { - NSString *propertyName = @"fileId"; - NSPredicate *predicte = [NSPredicate predicateWithFormat:@"%K = %@", propertyName, fileId]; - - NSArray *results = [self.blockedFiles filteredArrayUsingPredicate:predicte]; - if (!results || !results.lastObject) { - results = [self.availableFiles filteredArrayUsingPredicate:predicte]; - } - - return (MSFile *_Nonnull) results.lastObject; -} - -- (void)sortAvailableFilesByCreationDate { - NSArray *sortedBatches = - [self.availableFiles sortedArrayUsingComparator:^NSComparisonResult(MSFile *b1, MSFile *b2) { - return [b1.creationDate compare:b2.creationDate]; - }]; - self.availableFiles = [sortedBatches mutableCopy]; -} - -- (void)removeFile:(MSFile *)file { - if ([self.availableFiles containsObject:file]) { - [self.availableFiles removeObject:file]; - } - if ([self.blockedFiles containsObject:file]) { - [self.blockedFiles removeObject:file]; - } -} - -- (NSArray *)removeAllFiles { - NSMutableArray *allFiles = [NSMutableArray new]; - - // Transfer all available files - [allFiles addObjectsFromArray:self.availableFiles]; - [self.availableFiles removeAllObjects]; - - // Transfer all blocked files - [allFiles addObjectsFromArray:self.blockedFiles]; - [self.blockedFiles removeAllObjects]; - return [allFiles copy]; -} - -@end diff --git a/MobileCenter/MobileCenterTests/MSFileStorageTests.m b/MobileCenter/MobileCenterTests/MSFileStorageTests.m deleted file mode 100644 index 34e23ce5f3..0000000000 --- a/MobileCenter/MobileCenterTests/MSFileStorageTests.m +++ /dev/null @@ -1,333 +0,0 @@ -#import -#import -#import -#import - -#import "MSAbstractLog.h" -#import "MSFile.h" -#import "MSFileUtil.h" -#import "MSFileStorage.h" -#import "MSStorageTestUtil.h" - -@interface MSFileStorageTests : XCTestCase - -@property(nonatomic) MSFileStorage *sut; - -@end - -@implementation MSFileStorageTests - -#pragma mark - Houskeeping - -- (void)setUp { - [super setUp]; - self.sut = [MSFileStorage new]; -} - -- (void)tearDown { - [super tearDown]; - [MSStorageTestUtil resetLogsDirectory]; -} - -#pragma mark - Tests - -- (void)testNewInstanceWasInitialisedCorrectly { - assertThat(self.sut, notNilValue()); -} - -- (void)testFileStorageUsesCorrectFilePath { - - // If - NSString *groupID = @"TestGroupID"; - NSString *logsId = @"TestId"; - NSString *expected = [MSStorageTestUtil filePathForLogWithId:logsId extension:@"ms" groupID:groupID]; - - // When - NSURL *actual = [self.sut fileURLForGroupID:groupID logsId:logsId]; - - // Then - assertThat(actual, equalTo([NSURL fileURLWithPath:expected])); -} - -- (void)testSavingFirstFileCreatesNewBucket { - - // If - NSString *groupID = @"TestGroupID"; - id fileHelperMock = OCMClassMock([MSFileUtil class]); - MSAbstractLog *log = [MSAbstractLog new]; - MSStorageBucket *bucket = self.sut.buckets[groupID]; - assertThat(bucket, nilValue()); - - // When - BOOL success = [self.sut saveLog:log withGroupID:groupID]; - - // Verify - MSStorageBucket *actualBucket = self.sut.buckets[groupID]; - MSFile *actualCurrentFile = actualBucket.currentFile; - XCTAssertTrue(success); - assertThat(actualCurrentFile, notNilValue()); - assertThat(actualBucket.currentLogs, hasItem(log)); - assertThat(actualCurrentFile.creationDate, notNilValue()); - assertThat(actualCurrentFile.fileId, notNilValue()); - OCMVerify([fileHelperMock writeData:[NSKeyedArchiver archivedDataWithRootObject:actualBucket.currentLogs] - toFile:actualCurrentFile]); -} - -- (void)testCreatingNewBucketsWillLoadExistingFiles { - - // If - NSString *groupID = @"GroupID"; - MSAbstractLog *log = [MSAbstractLog new]; - MSFile *expected = [MSStorageTestUtil createFileWithId:@"test123" - data:[NSData new] - extension:@"ms" - groupID:groupID - creationDate:[NSDate date]]; - assertThat(self.sut.buckets[groupID], nilValue()); - - // When - BOOL success = [self.sut saveLog:log withGroupID:groupID]; - - // Verify - XCTAssertTrue(success); - MSStorageBucket *bucket = self.sut.buckets[groupID]; - MSFile *actual = bucket.availableFiles.lastObject; - assertThat(actual.fileURL, equalTo(expected.fileURL)); - assertThat(actual.fileId, equalTo(expected.fileId)); - - // Sometimes we can get a difference between times in one second and it is a valid result. - double maxAllowedDifference = 1; - double difference = [actual.creationDate timeIntervalSinceDate:expected.creationDate]; - XCTAssertLessThanOrEqual(difference, maxAllowedDifference); -} - -- (void)testSaveFirstLogOfABatchWillNotAddItToCurrentFileIfItIsNil { - - // If - NSString *groupID = @"GroupID"; - MSAbstractLog *log = nil; - MSStorageBucket *bucket = [self.sut bucketForGroupID:groupID]; - - // When - BOOL success = [self.sut saveLog:log withGroupID:groupID]; - - // Verify - XCTAssertFalse(success); - assertThat(bucket.currentLogs, isEmpty()); -} - -- (void)testSaveFirstLogOfABatchWillAddCurrentFileToAvailableList { - - // If - NSString *groupID = @"GroupID"; - MSAbstractLog *log = [MSAbstractLog new]; - assertThat(self.sut.buckets[groupID], nilValue()); - - // When - BOOL success = [self.sut saveLog:log withGroupID:groupID]; - - // Verify - XCTAssertTrue(success); - MSStorageBucket *bucket = self.sut.buckets[groupID]; - MSFile *expected = bucket.currentFile; - MSFile *actual = bucket.availableFiles.lastObject; - assertThat(actual, equalTo(expected)); - assertThat(bucket.availableFiles, hasCountOf(1)); -} - -- (void)testSaveFirstLogOfBatchWillDeleteOldestFileIfFileLimitHasBeenReached { - - // If - NSString *groupID = @"GroupID"; - MSStorageBucket *bucket = [self.sut bucketForGroupID:groupID]; - - MSFile *availableFile1 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"1"] - fileId:@"1" - creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - MSFile *availableFile2 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"2"] - fileId:@"2" - creationDate:[NSDate dateWithTimeIntervalSinceNow:3]]; - MSFile *availableFile3 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"3"] - fileId:@"3" - creationDate:[NSDate dateWithTimeIntervalSinceNow:5]]; - bucket.availableFiles = [@[ availableFile1, availableFile2, availableFile3 ] mutableCopy]; - self.sut.bucketFileCountLimit = bucket.availableFiles.count; - MSAbstractLog *log = [MSAbstractLog new]; - - // When - BOOL success = [self.sut saveLog:log withGroupID:groupID]; - - // Verify - XCTAssertTrue(success); - assertThatInteger(bucket.availableFiles.count, equalToInteger(3)); - assertThat(bucket.availableFiles, containsInRelativeOrder(@[ bucket.currentFile, availableFile1, availableFile2 ])); -} - -- (void)testDeleteFileRemovesLogsIdFromBlockedFilesList { - - // If - NSString *groupID = @"GroupID"; - NSString *batchId = @"12345"; - self.sut.buckets[groupID] = [MSStorageBucket new]; - MSStorageBucket *bucket = self.sut.buckets[groupID]; - MSFile *blockedFile = - [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"333"] fileId:batchId creationDate:[NSDate date]]; - bucket.blockedFiles = [NSMutableArray arrayWithObject:blockedFile]; - - // When - [self.sut deleteLogsForId:batchId withGroupID:groupID]; - - // Verify - assertThatInteger(self.sut.buckets[groupID].blockedFiles.count, equalToInteger(0)); -} - -- (void)testDeleteFileWillCallFileHelperMethod { - - // If - id fileHelperMock = OCMClassMock([MSFileUtil class]); - NSString *groupID = @"GroupID"; - NSString *batchId = @"12345"; - self.sut.buckets[groupID] = [MSStorageBucket new]; - MSStorageBucket *bucket = self.sut.buckets[groupID]; - MSFile *availableFile = - [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"333"] fileId:batchId creationDate:[NSDate date]]; - bucket.availableFiles = [@[ availableFile ] mutableCopy]; - - // When - [self.sut deleteLogsForId:batchId withGroupID:groupID]; - - // Verify - OCMVerify([fileHelperMock deleteFile:availableFile]); -} - -- (void)testLoadBatchWillEmptyCurrentLogs { - - // If - NSString *groupID = @"directory"; - MSAbstractLog *log = [MSAbstractLog new]; - BOOL success = [self.sut saveLog:log withGroupID:groupID]; - assertThatInteger(self.sut.buckets[groupID].currentLogs.count, equalToInteger(1)); - - // When - XCTAssertTrue(success); - [self.sut loadLogsForGroupID:groupID withCompletion:nil]; - - // Verify - assertThat(self.sut.buckets[groupID].currentLogs, isEmpty()); -} - -- (void)testLoadBatchWillReturnOldestFileFirst { - - // If - NSString *groupID = @"GroupID"; - self.sut.buckets[groupID] = [MSStorageBucket new]; - MSStorageBucket *bucket = self.sut.buckets[groupID]; - - MSFile *availableFile1 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"1"] - fileId:@"1" - creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - MSFile *availableFile2 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"2"] - fileId:@"2" - creationDate:[NSDate dateWithTimeIntervalSinceNow:3]]; - MSFile *availableFile3 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"3"] - fileId:@"3" - creationDate:[NSDate dateWithTimeIntervalSinceNow:5]]; - bucket.availableFiles = [@[ availableFile1, availableFile2, availableFile3 ] mutableCopy]; - MSFile *currentFile = - [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"333"] fileId:@"333" creationDate:[NSDate date]]; - bucket.currentFile = currentFile; - - // When - __block NSString *batchId; - [self.sut loadLogsForGroupID:groupID - withCompletion:^(__attribute__((unused)) BOOL succeeded, - __attribute__((unused)) NSArray *> *logs, NSString *logsId) { - batchId = logsId; - }]; - - // Verify - assertThat(batchId, equalTo(availableFile3.fileId)); -} - -- (void)testLoadBatchWillAddItToBlockedFiles { - - // If - NSString *groupID = @"GroupID"; - self.sut.buckets[groupID] = [MSStorageBucket new]; - MSStorageBucket *bucket = self.sut.buckets[groupID]; - - MSFile *availableFile = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"1"] - fileId:@"1" - creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - bucket.availableFiles = [NSMutableArray arrayWithObject:availableFile]; - - // When - [self.sut loadLogsForGroupID:groupID - withCompletion:^(__attribute__((unused)) BOOL succeeded, - __attribute__((unused)) NSArray *> *logs, - __attribute__((unused)) NSString *logsId){ - }]; - - // Verify - assertThatInteger(bucket.availableFiles.count, equalToInteger(0)); - assertThatInteger(bucket.blockedFiles.count, equalToInteger(1)); - assertThat(bucket.blockedFiles, hasItem(availableFile)); -} - -- (void)testLoadBatchWillCreateNewCurrentFile { - - // If - NSString *groupID = @"TestDirectory"; - self.sut.buckets[groupID] = [MSStorageBucket new]; - MSStorageBucket *bucket = self.sut.buckets[groupID]; - MSFile *currentFile = - [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"333"] fileId:@"333" creationDate:[NSDate date]]; - bucket.currentFile = currentFile; - - // When - [self.sut loadLogsForGroupID:groupID - withCompletion:^(__attribute__((unused)) BOOL succeeded, - __attribute__((unused)) NSArray *> *logs, - __attribute__((unused)) NSString *logsId){ - }]; - - // Verify - assertThat(bucket.currentFile, isNot(equalTo(currentFile))); -} - -- (void)testLoadTooManyLogs { - id classMock = OCMClassMock([NSKeyedUnarchiver class]); - OCMStub([classMock unarchiveObjectWithData:[OCMArg any]]).andReturn([self generateLogs:self.sut.bucketFileLogCountLimit]); - - // If - NSString *groupID = @"GroupID"; - self.sut.buckets[groupID] = [MSStorageBucket new]; - MSStorageBucket *bucket = self.sut.buckets[groupID]; - - MSFile *availableFile1 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"1"] - fileId:@"1" - creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - bucket.availableFiles = [@[ availableFile1 ] mutableCopy]; - - // When - [self.sut loadLogsForGroupID:groupID withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { - - // Then - XCTAssertTrue(succeeded); - XCTAssertTrue(self.sut.bucketFileLogCountLimit == logArray.count); - }]; - - // Restoring the class - [classMock stopMocking]; -} - -- (NSArray*)generateLogs:(NSUInteger)maxCountLogs { - NSUInteger totalLogs = maxCountLogs * 2; - NSMutableArray *logs = [NSMutableArray arrayWithCapacity:totalLogs]; - for (NSUInteger i = 0; i < totalLogs; ++i) { - [logs addObject:[MSAbstractLog new]]; - } - return logs; -} - -@end diff --git a/MobileCenter/MobileCenterTests/MSFileTests.m b/MobileCenter/MobileCenterTests/MSFileTests.m deleted file mode 100644 index 349b346bd9..0000000000 --- a/MobileCenter/MobileCenterTests/MSFileTests.m +++ /dev/null @@ -1,45 +0,0 @@ -#import -#import -#import - -#import "MSFile.h" - -@interface MSFileTests : XCTestCase - -@property(nonatomic) MSFile *sut; - -@end - -@implementation MSFileTests - -#pragma mark - Houskeeping - -- (void)setUp { - [super setUp]; - self.sut = [MSFile new]; -} - -- (void)tearDown { - [super tearDown]; -} - -#pragma mark - Tests - -- (void)testNewInstanceWasInitialisedCorrectly { - - // If - NSString *fileId = @"12345"; - NSURL *fileURL = [NSURL fileURLWithPath:@"/Some/Path/To/File"]; - NSDate *creationDate = [NSDate dateWithTimeIntervalSinceNow:18]; - - // When - self.sut = [[MSFile alloc] initWithURL:fileURL fileId:fileId creationDate:creationDate]; - - // Then - assertThat(self.sut, notNilValue()); - assertThat(self.sut.fileId, equalTo(fileId)); - assertThat(self.sut.fileURL, equalTo(fileURL)); - assertThat(self.sut.creationDate, equalTo(creationDate)); -} - -@end diff --git a/MobileCenter/MobileCenterTests/MSFileUtilTests.m b/MobileCenter/MobileCenterTests/MSFileUtilTests.m deleted file mode 100644 index b3f3c61bf8..0000000000 --- a/MobileCenter/MobileCenterTests/MSFileUtilTests.m +++ /dev/null @@ -1,261 +0,0 @@ -#import -#import -#import -#import - -#import "MSFileUtil.h" -#import "MSStorageTestUtil.h" - -@interface MSFileUtilTests : XCTestCase - -@end - -@implementation MSFileUtilTests - -#pragma mark - Houskeeping - -- (void)setUp { - [super setUp]; -} - -- (void)tearDown { - [MSFileUtil setFileManager:nil]; - [MSStorageTestUtil resetLogsDirectory]; - [super tearDown]; -} - -#pragma mark - Tests - -- (void)testDefaultFileManagerIsUsedByDefault { - - // If - NSFileManager *expected = [NSFileManager defaultManager]; - - // When - NSFileManager *actual = [MSFileUtil fileManager]; - - // Then - assertThat(expected, equalTo(actual)); -} - -- (void)testCustomSetFileManagerWorks { - - // If - NSFileManager *expected = [NSFileManager new]; - - // When - [MSFileUtil setFileManager:expected]; - - // Then - NSFileManager *actual = [MSFileUtil fileManager]; - assertThat(expected, equalTo(actual)); -} - -- (void)testStorageSubDirectoriesAreExcludedFromBackupButAppSupportFolderIsNotAffected { - - // Explicitly do not exclude app support folder from backups - NSError *getResourceError = nil; - NSNumber *resourceValue = nil; - NSString *appSupportPath = - [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject]; - XCTAssertTrue([[NSURL fileURLWithPath:appSupportPath] setResourceValue:@NO - forKey:NSURLIsExcludedFromBackupKey - error:&getResourceError]); - - // Create first file and verify that subdirectory is excluded from backups - getResourceError = nil; - resourceValue = nil; - NSString *subDirectory = @"testDirectory"; - NSString *fileId = @"fileId"; - NSString *filePath = [MSStorageTestUtil filePathForLogWithId:fileId extension:@"ms" groupID:subDirectory]; - MSFile *file = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:filePath] fileId:fileId creationDate:[NSDate date]]; - - [MSFileUtil writeData:[NSData new] toFile:file]; - NSString *storagePath = [MSStorageTestUtil storageDirForGroupID:subDirectory]; - [[NSURL fileURLWithPath:storagePath] getResourceValue:&resourceValue - forKey:NSURLIsExcludedFromBackupKey - error:&getResourceError]; - XCTAssertNil(getResourceError); - XCTAssertEqual(resourceValue, @YES); - - // Verify that app support folder still isn't excluded - [[NSURL fileURLWithPath:appSupportPath] getResourceValue:&resourceValue - forKey:NSURLIsExcludedFromBackupKey - error:&getResourceError]; - XCTAssertNil(getResourceError); - XCTAssertEqual(resourceValue, @NO); -} - -- (void)testOnlyExistingFileNamesWithExtensionInDirAreReturned { - - // If - NSString *subDirectory = @"testDirectory"; - NSString *extension = @"ms"; - MSFile *file1 = [MSStorageTestUtil createFileWithId:@"1" - data:[NSData new] - extension:extension - groupID:subDirectory - creationDate:[NSDate date]]; - MSFile *file2 = [MSStorageTestUtil createFileWithId:@"2" - data:[NSData new] - extension:extension - groupID:subDirectory - creationDate:[NSDate date]]; - - // Create files with searched extension - NSArray *expected = @[file1, file2]; - - // Create files with different extension - [MSStorageTestUtil createFileWithId:@"3" - data:[NSData new] - extension:@"foo" - groupID:subDirectory - creationDate:[NSDate date]]; - - // When - NSString *directory = [MSStorageTestUtil storageDirForGroupID:subDirectory]; - NSArray *actual = [MSFileUtil filesForDirectory:[NSURL fileURLWithPath:directory] withFileExtension:extension]; - - // Then - assertThatInteger(actual.count, equalToInteger(expected.count)); - for (NSUInteger i = 0; i < actual.count; i++) { - assertThat(actual[i].fileURL, equalTo(expected[i].fileURL)); - assertThat(actual[i].fileId, equalTo(expected[i].fileId)); - assertThat(actual[i].creationDate.description, equalTo(expected[i].creationDate.description)); - } -} - -- (void)testCallingFileNamesForDirectoryWithNilPathReturnsNil { - - // If - id fileManagerMock = OCMClassMock([NSFileManager class]); - - // When - NSArray *actual = [MSFileUtil filesForDirectory:nil withFileExtension:@"ms"]; - - // Then - assertThat(actual, nilValue()); - OCMReject( - [fileManagerMock contentsOfDirectoryAtPath:[OCMArg any] error:((NSError __autoreleasing **)[OCMArg anyPointer])]); -} - -- (void)testDeletingExistingFileReturnsYes { - - // If - MSFile *file = [MSStorageTestUtil createFileWithId:@"0" - data:[NSData new] - extension:@"ms" - groupID:@"testDirectory" - creationDate:[NSDate date]]; - - // When - BOOL success = [MSFileUtil deleteFile:file]; - - // Then - assertThatBool(success, isTrue()); -} - -- (void)testDeletingNonexistingFileReturnsNo { - - // If - NSString *subDirectory = @"testDirectory"; - NSString *extension = @"ms"; - NSString *fileName = @"foo"; - NSString *filePath = [MSStorageTestUtil filePathForLogWithId:fileName extension:extension groupID:subDirectory]; - MSFile *file = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:filePath] fileId:fileName creationDate:[NSDate date]]; - - // When - BOOL success = [MSFileUtil deleteFile:file]; - - // Then - assertThatBool(success, isFalse()); -} - -- (void)testDeletingFileWithEmptyPathReturnsNo { - - // If - id fileManagerMock = OCMClassMock([NSFileManager class]); - MSFile *file = [MSStorageTestUtil createFileWithId:@"0" - data:[NSData new] - extension:@"ms" - groupID:@"testDirectory" - creationDate:[NSDate date]]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnonnull" - file.fileURL = nil; -#pragma clang diagnostic pop - - // When - BOOL success = [MSFileUtil deleteFile:file]; - - // Then - assertThatBool(success, isFalse()); - OCMReject([fileManagerMock removeItemAtPath:[OCMArg any] error:((NSError __autoreleasing **)[OCMArg anyPointer])]); -} - -- (void)testReadingExistingFileReturnsCorrectContent { - - // If - NSData *expected = [@"0" dataUsingEncoding:NSUTF8StringEncoding]; - MSFile *file = [MSStorageTestUtil createFileWithId:@"0" - data:expected - extension:@"ms" - groupID:@"testDirectory" - creationDate:[NSDate date]]; - - // When - NSData *actual = [MSFileUtil dataForFile:file]; - - // Then - assertThat(actual, equalTo(expected)); -} - -- (void)testReadingNonexistingFileReturnsNil { - - // If - NSString *directory = [MSStorageTestUtil logsDir]; - MSFile *file = [MSFile new]; - file.fileURL = [NSURL fileURLWithPath:[directory stringByAppendingPathComponent:@"0.test"]]; - - // When - NSData *actual = [MSFileUtil dataForFile:file]; - - // Then - assertThat(actual, nilValue()); -} - -- (void)testSuccessfullyWritingDataItemsToFileWorksCorrectly { - - // If - NSArray *items = @[ @"1", @"2" ]; - NSData *expected = [NSKeyedArchiver archivedDataWithRootObject:items]; - NSString *filePath = [MSStorageTestUtil filePathForLogWithId:@"0" extension:@"ms" groupID:@"directory"]; - MSFile *file = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:filePath] fileId:@"0" creationDate:[NSDate date]]; - - // When - BOOL success = [MSFileUtil writeData:expected toFile:file]; - - // Then - assertThatBool(success, isTrue()); - assertThat(expected, equalTo([NSData dataWithContentsOfFile:filePath])); -} - -- (void)testAppendingDataToNonexistingDirWillCreateDirAndFile { - - // If - NSString *fileName = @"0"; - NSString *filePath = [MSStorageTestUtil filePathForLogWithId:fileName extension:@"ms" groupID:@"testDirectory"]; - NSData *expected = [@"123456789" dataUsingEncoding:NSUTF8StringEncoding]; - MSFile *file = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:filePath] fileId:fileName creationDate:[NSDate date]]; - - // When - NSData *actual; - if ([MSFileUtil writeData:expected toFile:file]) { - actual = [MSFileUtil dataForFile:file]; - } - - // Then - assertThat(expected, equalTo(actual)); -} - -@end diff --git a/MobileCenter/MobileCenterTests/MSStorageBucketTests.m b/MobileCenter/MobileCenterTests/MSStorageBucketTests.m deleted file mode 100644 index 6853410f38..0000000000 --- a/MobileCenter/MobileCenterTests/MSStorageBucketTests.m +++ /dev/null @@ -1,137 +0,0 @@ -#import -#import -#import - -#import "MSFile.h" -#import "MSFileStorage.h" - -@interface MSStorageBucketTests : XCTestCase - -@property(nonatomic) MSStorageBucket *sut; - -@end - -@implementation MSStorageBucketTests - -#pragma mark - Houskeeping - -- (void)setUp { - [super setUp]; - self.sut = [MSStorageBucket new]; -} - -- (void)tearDown { - [super tearDown]; -} - -#pragma mark - Tests - -- (void)testNewInstanceWasInitialisedCorrectly { - assertThat(self.sut, notNilValue()); - assertThat(self.sut.availableFiles, notNilValue()); - assertThat(self.sut.availableFiles, isEmpty()); - assertThat(self.sut.blockedFiles, notNilValue()); - assertThat(self.sut.blockedFiles, isEmpty()); -} - -- (void)testSortingFilesByCreationDate { - - // If - MSFile *file1 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"3"] - fileId:@"3" - creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - MSFile *file2 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"2"] - fileId:@"2" - creationDate:[NSDate dateWithTimeIntervalSinceNow:2]]; - MSFile *file3 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"1"] - fileId:@"1" - creationDate:[NSDate dateWithTimeIntervalSinceNow:3]]; - NSMutableArray *unsortedFiles = [NSMutableArray arrayWithObjects:file2, file3, file1, nil]; - self.sut.availableFiles = unsortedFiles; - - // When - NSMutableArray *expected = [NSMutableArray arrayWithObjects:file1, file2, file3, nil]; - [self.sut sortAvailableFilesByCreationDate]; - - // Then - assertThat(self.sut.availableFiles, equalTo(expected)); -} - -- (void)testRequestingFileByIdWillReturnCorrectFile { - - // If - MSFile *availableFile1 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"1"] - fileId:@"1" - creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - MSFile *availableFile2 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"2"] - fileId:@"2" - creationDate:[NSDate dateWithTimeIntervalSinceNow:2]]; - MSFile *availableFile3 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"3"] - fileId:@"3" - creationDate:[NSDate dateWithTimeIntervalSinceNow:3]]; - self.sut.availableFiles = [NSMutableArray arrayWithObjects:availableFile1, availableFile2, availableFile3, nil]; - - MSFile *blockedFile1 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"4"] - fileId:@"4" - creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - MSFile *blockedFile2 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"5"] - fileId:@"5" - creationDate:[NSDate dateWithTimeIntervalSinceNow:2]]; - MSFile *blockedFile3 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"6"] - fileId:@"6" - creationDate:[NSDate dateWithTimeIntervalSinceNow:3]]; - self.sut.blockedFiles = [NSMutableArray arrayWithObjects:blockedFile1, blockedFile2, blockedFile3, nil]; - - // When - MSFile *foundAvailableFile = [self.sut fileWithId:@"3"]; - MSFile *foundBlockedFile = [self.sut fileWithId:@"5"]; - - // Then - assertThat(foundAvailableFile, equalTo(availableFile3)); - assertThat(foundBlockedFile, equalTo(blockedFile2)); -} - -- (void)testRequestingUnexisitngFileByIdWillReturnNil { - - // If - MSFile *availableFile1 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"1"] - fileId:@"1" - creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - MSFile *availableFile2 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"2"] - fileId:@"2" - creationDate:[NSDate dateWithTimeIntervalSinceNow:2]]; - MSFile *availableFile3 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"3"] - fileId:@"3" - creationDate:[NSDate dateWithTimeIntervalSinceNow:3]]; - self.sut.availableFiles = [NSMutableArray arrayWithObjects:availableFile1, availableFile2, availableFile3, nil]; - - // When - MSFile *actual = [self.sut fileWithId:@"4"]; - - // Then - assertThat(actual, nilValue()); -} - -- (void)testRemovingFileRemovesItFromBlockedAndAvailableList { - - // If - MSFile *file1 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"1"] - fileId:@"1" - creationDate:[NSDate dateWithTimeIntervalSinceNow:1]]; - MSFile *file2 = [[MSFile alloc] initWithURL:[NSURL fileURLWithPath:@"2"] - fileId:@"2" - creationDate:[NSDate dateWithTimeIntervalSinceNow:2]]; - self.sut.availableFiles = [NSMutableArray arrayWithObjects:file1, file2, nil]; - self.sut.blockedFiles = [NSMutableArray arrayWithObjects:file2, file1, nil]; - - // When - [self.sut removeFile:file1]; - - // Then - assertThat(self.sut.availableFiles, hasCountOf(1)); - assertThat(self.sut.availableFiles, isNot(hasItem(file1))); - assertThat(self.sut.blockedFiles, hasCountOf(1)); - assertThat(self.sut.blockedFiles, isNot(hasItem(file1))); -} - -@end diff --git a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m index 255d647087..fb9db3d6ba 100644 --- a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m +++ b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m @@ -1,7 +1,6 @@ #import #import "MSStartServiceLog.h" #import "MSDBStorage.h" -#import "MSFileStorage.h" static const int numLogs = 100; static const int numServices = 100; @@ -12,20 +11,17 @@ @interface MSStoragePerfomanceTests : XCTestCase @interface MSStoragePerfomanceTests() @property(nonatomic) MSDBStorage *dbStorage; -@property(nonatomic) MSFileStorage *fStorage; @end @implementation MSStoragePerfomanceTests @synthesize dbStorage; -@synthesize fStorage; - (void)setUp { [super setUp]; self.dbStorage = [MSDBStorage new]; - self.fStorage = [MSFileStorage new]; } - (void)tearDown { @@ -33,7 +29,6 @@ - (void)tearDown { [super tearDown]; [self.dbStorage deleteLogsForGroupID:@"anyKey"]; - [self.fStorage deleteLogsForGroupID:@"anyKey"]; } #pragma mark - Database storage tests diff --git a/MobileCenter/MobileCenterTests/Util/MSStorageTestUtil.h b/MobileCenter/MobileCenterTests/Util/MSStorageTestUtil.h deleted file mode 100644 index fc837624ca..0000000000 --- a/MobileCenter/MobileCenterTests/Util/MSStorageTestUtil.h +++ /dev/null @@ -1,17 +0,0 @@ -#import "MSFile.h" -#import - -@interface MSStorageTestUtil : NSObject - -+ (NSString *)logsDir; -+ (NSString *)storageDirForGroupID:(NSString *)groupID; -+ (NSString *)filePathForLogWithId:(NSString *)logsId extension:(NSString *)extension groupID:(NSString *)groupID; -+ (MSFile *)createFileWithId:(NSString *)logsId - data:(NSData *)data - extension:(NSString *)extension - groupID:(NSString *)groupID - creationDate:(NSDate *)creationDate; -+ (void)createDirectoryAtPath:(NSString *)directoryPath; -+ (void)resetLogsDirectory; - -@end diff --git a/MobileCenter/MobileCenterTests/Util/MSStorageTestUtil.m b/MobileCenter/MobileCenterTests/Util/MSStorageTestUtil.m deleted file mode 100644 index 3ddfef2618..0000000000 --- a/MobileCenter/MobileCenterTests/Util/MSStorageTestUtil.m +++ /dev/null @@ -1,55 +0,0 @@ -#import "MSStorageTestUtil.h" - -@implementation MSStorageTestUtil - -+ (NSString *)logsDir { - NSString *logsPath = @"com.microsoft.azure.mobile.mobilecenter/logs"; - NSString *documentsDir = - [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject]; - return [documentsDir stringByAppendingPathComponent:logsPath]; -} - -+ (NSString *)storageDirForGroupID:(NSString *)groupID { - return [[self logsDir] stringByAppendingPathComponent:groupID]; -} - -+ (NSString *)filePathForLogWithId:(NSString *)logsId extension:(NSString *)extension groupID:(NSString *)groupID { - NSString *fileName = [logsId stringByAppendingPathExtension:extension]; - NSString *logFilePath = [groupID stringByAppendingPathComponent:fileName]; - NSString *logsPath = [self logsDir]; - - return [logsPath stringByAppendingPathComponent:logFilePath]; -} - -+ (MSFile *)createFileWithId:(NSString *)logsId - data:(NSData *)data - extension:(NSString *)extension - groupID:(NSString *)groupID - creationDate:(NSDate *)creationDate { - NSString *storagePath = [self storageDirForGroupID:groupID]; - if (![[NSFileManager defaultManager] fileExistsAtPath:storagePath]) { - [self createDirectoryAtPath:storagePath]; - } - - NSString *filePath = [self filePathForLogWithId:logsId extension:extension groupID:groupID]; - [[NSFileManager defaultManager] createFileAtPath:filePath contents:data attributes:nil]; - - NSURL *fileURL = [NSURL fileURLWithPath:filePath]; - MSFile *file = [[MSFile alloc] initWithURL:fileURL fileId:logsId creationDate:creationDate]; - - return file; -} - -+ (void)createDirectoryAtPath:(NSString *)directoryPath { - NSError *error; - [[NSFileManager defaultManager] createDirectoryAtPath:directoryPath - withIntermediateDirectories:YES - attributes:nil - error:&error]; -} - -+ (void)resetLogsDirectory { - [[NSFileManager defaultManager] removeItemAtPath:[self logsDir] error:nil]; -} - -@end From 2a9f9f6718a2a0851592c3a8f01b8a3aadcb0884 Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Wed, 19 Apr 2017 15:29:37 -0700 Subject: [PATCH 14/76] Set storage load limit from the channel * Set load limit when loading from the storage * Use SQL limit to limit number of logs loaded from the DB * Some renaming, wording * Introduce MSDBStoragePrivate.h to expose members to tests * Update unit tests * Add unit tests to cover logs loading from DB with limit --- MobileCenter/MSDBStorageTests.m | 77 ++++++++++++--- .../MobileCenter.xcodeproj/project.pbxproj | 4 + .../Internals/Channel/MSChannelDefault.m | 94 ++++++++++--------- .../Internals/Model/Utils/MSUserDefaults.m | 17 ++-- .../Internals/Storage/MSDBStorage.h | 13 --- .../Internals/Storage/MSDBStorage.m | 72 ++++++++------ .../Internals/Storage/MSDBStoragePrivate.h | 20 ++++ .../Internals/Storage/MSSqliteConnection.m | 3 +- .../Internals/Storage/MSStorage.h | 32 +++---- .../MobileCenterTests/MSChannelDefaultTests.m | 33 ++++--- 10 files changed, 224 insertions(+), 141 deletions(-) create mode 100644 MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m index 4fea818384..c46543cbb4 100644 --- a/MobileCenter/MSDBStorageTests.m +++ b/MobileCenter/MSDBStorageTests.m @@ -2,11 +2,16 @@ #import #import #import "MSAbstractLog.h" -#import "MSDBStorage.h" +#import "MSDatabaseConnection.h" +#import "MSDBStoragePrivate.h" + +static NSString *const kMSTestGroupID = @"TestGroupID"; @interface MSDBStorageTests : XCTestCase @property (nonatomic) MSDBStorage *sut; +@property (nonatomic) id dbConnectionMock; + @end @@ -16,29 +21,79 @@ @implementation MSDBStorageTests - (void)setUp { [super setUp]; self.sut = [MSDBStorage new]; + self.dbConnectionMock = OCMProtocolMock(@protocol(MSDatabaseConnection)); + self.sut.connection = self.dbConnectionMock; } - (void)testLoadTooManyLogs { // If - id partialMock = OCMPartialMock(self.sut); - OCMStub([partialMock bucketFileLogCountLimit]).andReturn(50); - OCMStub([partialMock getLogsWith:[OCMArg any]]).andReturn([self generateLogs:self.sut.bucketFileLogCountLimit]); + NSUInteger expectedLogsCount = 5; + OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]).andReturn([self generateSerializedLogsWithCount:expectedLogsCount+1]); // When - [self.sut loadLogsForGroupID:@"" withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { + BOOL moreLogsAvailable = [self.sut loadLogsForGroupID:kMSTestGroupID limit:expectedLogsCount withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { // Then XCTAssertTrue(succeeded); - XCTAssertTrue(self.sut.bucketFileLogCountLimit == logArray.count); + XCTAssertTrue(expectedLogsCount == logArray.count); }]; + XCTAssertTrue(moreLogsAvailable); } -- (NSArray*)generateLogs:(NSUInteger)maxCountLogs { - NSUInteger totalLogs = maxCountLogs * 2; - NSMutableArray *logs = [NSMutableArray arrayWithCapacity:totalLogs]; - for (NSUInteger i = 0; i < totalLogs; ++i) { - [logs addObject:[MSAbstractLog new]]; +- (void)testLoadJustEnoughLogs { + + // If + NSUInteger expectedLogsCount = 5; + OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]).andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); + + // When + BOOL moreLogsAvailable = [self.sut loadLogsForGroupID:kMSTestGroupID limit:expectedLogsCount withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { + + // Then + XCTAssertTrue(succeeded); + XCTAssertTrue(expectedLogsCount == logArray.count); + }]; + XCTAssertFalse(moreLogsAvailable); +} + +- (void)testLoadNotEnoughLogs { + + // If + NSUInteger expectedLogsCount = 2; + NSUInteger limit = 5; + OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]).andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); + + // When + BOOL moreLogsAvailable = [self.sut loadLogsForGroupID:kMSTestGroupID limit:limit withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { + + // Then + XCTAssertTrue(succeeded); + XCTAssertTrue(expectedLogsCount == logArray.count); + }]; + XCTAssertFalse(moreLogsAvailable); +} + +- (void)testLoadUnlimitedLogs { + + // If + NSUInteger expectedLogsCount = 42; + OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]).andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); + + // When + NSArray *logs = [self.sut getLogsWithGroupID:kMSTestGroupID]; + + // Then + XCTAssertTrue(expectedLogsCount == logs.count); +} + + +- (NSArray*> *)generateSerializedLogsWithCount:(NSUInteger)count { + NSMutableArray*> *logs = [NSMutableArray arrayWithCapacity:count]; + for (NSUInteger i = 0; i < count; ++i) { + NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:[MSAbstractLog new]]; + NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; + [logs addObject:@[kMSTestGroupID,base64Data]]; } return logs; } diff --git a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj index 11047574c4..2787514113 100644 --- a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj +++ b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 380A4DCB1DD6908A00E99219 /* MSUtilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 380A4DCA1DD6908A00E99219 /* MSUtilityTests.m */; }; 381C91E91D3DB65D004512F1 /* MSDeviceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 381C91E51D3DB65D004512F1 /* MSDeviceTracker.h */; }; 381C91EA1D3DB65D004512F1 /* MSDeviceTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 381C91E61D3DB65D004512F1 /* MSDeviceTracker.m */; }; + 383481731EA7FF6100787F56 /* MSDBStoragePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 383481721EA7FF6100787F56 /* MSDBStoragePrivate.h */; }; 384772A61DA5691F009365DE /* MSSenderDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 384772A51DA5691F009365DE /* MSSenderDelegate.h */; }; 384959D51D491D4F008F6B3A /* MSMobileCenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 384959D41D491D4F008F6B3A /* MSMobileCenterTests.m */; }; 385AD9221D95898D008B354A /* MSServiceAbstractProtected.h in Headers */ = {isa = PBXBuildFile; fileRef = 385AD9211D95898D008B354A /* MSServiceAbstractProtected.h */; }; @@ -193,6 +194,7 @@ 380A4DCA1DD6908A00E99219 /* MSUtilityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSUtilityTests.m; sourceTree = ""; }; 381C91E51D3DB65D004512F1 /* MSDeviceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSDeviceTracker.h; sourceTree = ""; }; 381C91E61D3DB65D004512F1 /* MSDeviceTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDeviceTracker.m; sourceTree = ""; }; + 383481721EA7FF6100787F56 /* MSDBStoragePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSDBStoragePrivate.h; sourceTree = ""; }; 384772A51DA5691F009365DE /* MSSenderDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSSenderDelegate.h; sourceTree = ""; }; 384959D41D491D4F008F6B3A /* MSMobileCenterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSMobileCenterTests.m; sourceTree = ""; }; 385AD9211D95898D008B354A /* MSServiceAbstractProtected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSServiceAbstractProtected.h; sourceTree = ""; }; @@ -600,6 +602,7 @@ E84B8E381D235246006FD231 /* MSStorage.h */, E8A8D1E91D3057A90022931E /* MSUserDefaults.h */, E8A8D1EA1D3057A90022931E /* MSUserDefaults.m */, + 383481721EA7FF6100787F56 /* MSDBStoragePrivate.h */, D341DBCD1E77EF8600D385F9 /* MSDBStorage.h */, D341DBD51E77FD8B00D385F9 /* MSDBStorage.m */, D35D617F1E785FAB00D81A0F /* MSSqliteConnection.h */, @@ -672,6 +675,7 @@ D38023E91E6EFC7C00466558 /* MSStartServiceLog.h in Headers */, 387C76FD1D6C9CF100D68CC1 /* MSServiceAbstract.h in Headers */, B2C3C1841DB83A3E00CB83F7 /* MSDevicePrivate.h in Headers */, + 383481731EA7FF6100787F56 /* MSDBStoragePrivate.h in Headers */, B21E29911E83521A00F9A22D /* MSUtility+StringFormatting.h in Headers */, 842C13251E7FCF5300F16EA3 /* MSUtility+Environment.h in Headers */, 6E3E2C9E1D35701000B1EE50 /* MSLog.h in Headers */, diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m index 068ab18800..faf1c74046 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m @@ -157,6 +157,7 @@ - (void)flushQueue { self.itemsCount = 0; self.availableBatchFromStorage = [self.storage loadLogsForGroupID:self.configuration.groupID + limit:self.configuration.batchSizeLimit withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, NSString *_Nonnull batchId) { // Logs may be deleted from storage before this flush. @@ -184,55 +185,55 @@ - (void)flushQueue { dispatch_async(self.logsDispatchQueue, ^{ if ([self.pendingBatchIds containsObject:senderBatchId]) { - // Success. - if (statusCode == MSHTTPCodesNo200OK) { - MSLogDebug([MSMobileCenter logTag], @"Log(s) sent with success, batch Id:%@.", senderBatchId); - - // Notify delegates. - [self enumerateDelegatesForSelector:@selector(channel:didSucceedSendingLog:) - withBlock:^(id delegate) { - for (id aLog in logArray) { - [delegate channel:self didSucceedSendingLog:aLog]; - } - }]; - - // Remove from pending logs and storage. - [self.pendingBatchIds removeObject:senderBatchId]; - [self.storage deleteLogsForId:senderBatchId withGroupID:self.configuration.groupID]; - - // Try to flush again if batch queue is not full anymore. - if (self.pendingBatchQueueFull && - self.pendingBatchIds.count < self.configuration.pendingBatchesLimit) { - self.pendingBatchQueueFull = NO; - if (self.availableBatchFromStorage) { - [self flushQueue]; + // Success. + if (statusCode == MSHTTPCodesNo200OK) { + MSLogDebug([MSMobileCenter logTag], @"Log(s) sent with success, batch Id:%@.", senderBatchId); + + // Notify delegates. + [self enumerateDelegatesForSelector:@selector(channel:didSucceedSendingLog:) + withBlock:^(id delegate) { + for (id aLog in logArray) { + [delegate channel:self didSucceedSendingLog:aLog]; + } + }]; + + // Remove from pending logs and storage. + [self.pendingBatchIds removeObject:senderBatchId]; + [self.storage deleteLogsForId:senderBatchId withGroupID:self.configuration.groupID]; + + // Try to flush again if batch queue is not full anymore. + if (self.pendingBatchQueueFull && + self.pendingBatchIds.count < self.configuration.pendingBatchesLimit) { + self.pendingBatchQueueFull = NO; + if (self.availableBatchFromStorage) { + [self flushQueue]; + } } } - } - - // Failure. - else { - MSLogDebug([MSMobileCenter logTag], @"Log(s) sent with failure, batch Id:%@, status code:%lu", - senderBatchId, (unsigned long)statusCode); - - // Notify delegates. - [self enumerateDelegatesForSelector:@selector(channel:didFailSendingLog:withError:) - withBlock:^(id delegate) { - for (id aLog in logArray) { - [delegate channel:self didFailSendingLog:aLog withError:error]; - } - }]; - - // Remove from pending logs. - [self.pendingBatchIds removeObject:senderBatchId]; - [self.storage deleteLogsForId:senderBatchId withGroupID:self.configuration.groupID]; - - // Fatal error, disable sender with data deletion. - // This will in turn disable this channel and delete logs. - if (error.code != NSURLErrorCancelled) { - [self.sender setEnabled:NO andDeleteDataOnDisabled:YES]; + + // Failure. + else { + MSLogDebug([MSMobileCenter logTag], @"Log(s) sent with failure, batch Id:%@, status code:%lu", + senderBatchId, (unsigned long)statusCode); + + // Notify delegates. + [self enumerateDelegatesForSelector:@selector(channel:didFailSendingLog:withError:) + withBlock:^(id delegate) { + for (id aLog in logArray) { + [delegate channel:self didFailSendingLog:aLog withError:error]; + } + }]; + + // Remove from pending logs. + [self.pendingBatchIds removeObject:senderBatchId]; + [self.storage deleteLogsForId:senderBatchId withGroupID:self.configuration.groupID]; + + // Fatal error, disable sender with data deletion. + // This will in turn disable this channel and delete logs. + if (error.code != NSURLErrorCancelled) { + [self.sender setEnabled:NO andDeleteDataOnDisabled:YES]; + } } - } } else MSLogWarning([MSMobileCenter logTag], @"Batch Id %@ not expected, ignore.", senderBatchId); }); @@ -347,6 +348,7 @@ - (void)deleteAllLogsWithErrorSync:(NSError *)error { [self.storage deleteLogsForId:batchId withGroupID:self.configuration.groupID]; } + // TODO: What about the database schema, it will stay on disc even after disabling? // Delete remaining logs. deletedLogs = [self.storage deleteLogsForGroupID:self.configuration.groupID]; diff --git a/MobileCenter/MobileCenter/Internals/Model/Utils/MSUserDefaults.m b/MobileCenter/MobileCenter/Internals/Model/Utils/MSUserDefaults.m index e00a4392b8..c9d021d7ce 100644 --- a/MobileCenter/MobileCenter/Internals/Model/Utils/MSUserDefaults.m +++ b/MobileCenter/MobileCenter/Internals/Model/Utils/MSUserDefaults.m @@ -30,36 +30,37 @@ - (void)removeObjectForKey:(NSString *)key { - (NSDictionary *)updateDictionary:(NSDictionary *)dict forKey:(NSString *)key expiration:(float)expiration { NSMutableDictionary *update = [[NSMutableDictionary alloc] initWithDictionary:dict]; - /* Get from local store */ + /* Get from local store. */ NSDictionary *store = [[NSUserDefaults standardUserDefaults] dictionaryForKey:key]; CFAbsoluteTime ts = [store[kMSUserDefaultsTs] doubleValue]; MSLogVerbose([MSMobileCenter logTag], @"Settings:store[%@]=%@", key, store); - /* Force update if timestamp expiration is reached */ + /* Force update if timestamp expiration is reached. */ if (ts <= 0.0 || expiration <= 0.0f || fabs(CFAbsoluteTimeGetCurrent() - ts) < (double)expiration) { - /* Remove if already in store and value is the same */ + + /* Remove if already in store and value is the same. */ for (NSString *k in [store allKeys]) { if (update[k] != nil && [update[k] isEqual:store[k]]) [update removeObjectForKey:k]; } } - /* If still values to update */ + /* If still values to update. */ if ([update count] > 0) { MSLogDebug([MSMobileCenter logTag], @"Settings:update[%@]=%@", key, update); - /* Copy store as a mutable version */ + /* Copy store as a mutable version. */ NSMutableDictionary *d = [store mutableCopy]; if (d == nil) d = [[NSMutableDictionary alloc] initWithCapacity:[update count]]; - /* Append update to the current store */ + /* Append update to the current store. */ [d addEntriesFromDictionary:update]; - /* Set new timestamp */ + /* Set new timestamp. */ d[kMSUserDefaultsTs] = @(CFAbsoluteTimeGetCurrent()); - /* Save */ + /* Save. */ [[NSUserDefaults standardUserDefaults] setObject:d forKey:key]; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h index c2e2e260ce..d16e7a20cb 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h @@ -3,17 +3,4 @@ @interface MSDBStorage : NSObject -/** - * Return all logs with storageKey - * - * @return All founded logs - */ -- (NSMutableArray*) getLogsWith:(NSString*)storageKey; - - -/** - * Delete all logs with storageKey - */ -- (void) deleteLogsWith:(NSString*)storageKey; - @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 3dbf3ba1d9..1aafd4c74f 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -1,23 +1,16 @@ #import "MSDatabaseConnection.h" -#import "MSDBStorage.h" +#import "MSDBStoragePrivate.h" #import "MSLogger.h" #import "MSSqliteConnection.h" static NSString *const kMSLogEntityName = @"MSDBLog"; static NSString *const kMSDBFileName = @"MSDBLogs.sqlite"; static NSString *const kMSLogTableName = @"MSLog"; -static NSString *const kMSStorageKeyColumnName = @"storageKey"; +static NSString *const kMSGroupIDColumnName = @"groupID"; static NSString *const kMSDataColumnName = @"data"; -@interface MSDBStorage() - -@property (nonatomic) id connection; - -@end - @implementation MSDBStorage -@synthesize bucketFileLogCountLimit; @synthesize connection; #pragma mark - Initialization @@ -33,7 +26,7 @@ - (instancetype)init { -(void)initTables { NSString *createLogTableQuery = [NSString stringWithFormat:@"create table if not exists %@ (%@ text, %@ text);", - kMSLogTableName, kMSStorageKeyColumnName, kMSDataColumnName]; + kMSLogTableName, kMSGroupIDColumnName, kMSDataColumnName]; [self.connection executeQuery:createLogTableQuery]; } @@ -51,38 +44,59 @@ - (BOOL)saveLog:(id)log withGroupID:(NSString *)groupID { } - (NSArray *)deleteLogsForGroupID:(NSString *)groupID { - NSArray *logs = [self getLogsWith:groupID]; - [self deleteLogsWith:groupID]; + NSArray *logs = [self getLogsWithGroupID:groupID]; + [self deleteLogsWithGroupID:groupID]; return logs; } -- (void)deleteLogsForId:(NSString *)logsId withGroupID:(NSString *)groupID { +- (void)deleteLogsForId:(NSString *)batchId withGroupID:(NSString *)groupID { - // FIXME: logsId ? - [self deleteLogsWith:groupID]; + // FIXME: Restore batch deletion. + [self deleteLogsWithGroupID:groupID]; } -- (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDataCompletionBlock)completion { - NSMutableArray *logs = [self getLogsWith:groupID]; - - // Remove excess logs - if (logs.count > self.bucketFileLogCountLimit) { - [logs removeObjectsInRange:NSMakeRange(self.bucketFileLogCountLimit, logs.count - self.bucketFileLogCountLimit)]; +- (BOOL)loadLogsForGroupID:(NSString *)groupID limit:(NSUInteger)limit withCompletion:(nullable MSLoadDataCompletionBlock)completion { + + /* + * There is a need to determine if there will be more log available than those under the limit. + * So this is just about knowing if there is at least 1 log above the limit. + * Thus, just 1 log is added to the requested limit and then removed later as needed. + */ + NSMutableArray *logs = (NSMutableArray *)[self getLogsWithGroupID:groupID limit:(limit+1)]; + BOOL moreLogsAvailable = NO; + + // Remove the log in excess, it means there is more logs available. + if (logs.count > limit) { + [logs removeLastObject]; + moreLogsAvailable = YES; } if (completion) { - completion(logs.count > 0, logs, @""); + completion(logs.count > 0, logs, groupID); } - return logs.count > 0; + + // Return YES if more logs available. + return moreLogsAvailable; } #pragma mark - Private -- (NSMutableArray*) getLogsWith:(NSString*)storageKey { +- (NSArray*) getLogsWithGroupID:(NSString*)groupID{ NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@'", - kMSLogTableName, kMSStorageKeyColumnName, storageKey]; - NSArray*> *result = [self.connection loadDataFromDB:selectLogQuery]; - NSMutableArray *logs = [NSMutableArray arrayWithCapacity:100]; + kMSLogTableName, kMSGroupIDColumnName, groupID]; + return [self getLogsWithQwery:selectLogQuery]; +} + +- (NSArray*) getLogsWithGroupID:(NSString*)groupID limit:(NSUInteger)limit{ + NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@' limit %lu", + kMSLogTableName, kMSGroupIDColumnName, groupID, (unsigned long)limit]; + return [self getLogsWithQwery:selectLogQuery]; +} +- (NSArray*) getLogsWithQwery:(NSString*)qwery{ + NSArray*> *result = [self.connection loadDataFromDB:qwery]; + NSMutableArray *logs = [NSMutableArray new]; + + // Deserialize logs from DB. for (NSArray *row in result) { NSString *base64Data = row[1]; NSData *logData = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters]; @@ -92,9 +106,9 @@ - (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDa return logs; } -- (void) deleteLogsWith:(NSString*)storageKey { +- (void) deleteLogsWithGroupID:(NSString*)groupID { NSString *deleteLogQuery = [NSString stringWithFormat:@"delete from %@ where %@ == '%@'", - kMSLogTableName, kMSStorageKeyColumnName, storageKey]; + kMSLogTableName, kMSGroupIDColumnName, groupID]; [self.connection executeQuery:deleteLogQuery]; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h new file mode 100644 index 0000000000..b3325a82a0 --- /dev/null +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h @@ -0,0 +1,20 @@ +#import "MSDBStorage.h" + +@class MSDatabaseConnection; + +@interface MSDBStorage () + +@property (nonatomic) id connection; + +/** + * Get all logs with the given group ID from the storage. + * + * @param groupID The groupID used for grouping logs. + * + * @return Logs corresponding to the given group ID from the storage. + * + */ +- (NSArray*) getLogsWithGroupID:(NSString*)groupID; + +@end + diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index 505133bf92..b44fc76bf8 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -67,6 +67,7 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // Set the database file path. NSString *databasePath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename]; + //TODO looks like we can optimize those 2 initializations. // Initialize the results array. if (self.arrResults != nil) { [self.arrResults removeAllObjects]; @@ -151,7 +152,6 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // If could not execute the query show the error message on the debugger. NSLog(@"DB Error: %s\nerror code: %d", sqlite3_errmsg(sqlite3Database), executeQueryResults); - result = NO; } } @@ -159,7 +159,6 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // In the database cannot be opened then show the error message on the debugger. NSLog(@"DB Error: %s\nquery: %s", sqlite3_errmsg(sqlite3Database), query); - result = NO; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h index 16117e588b..3a16fb6445 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h @@ -7,54 +7,48 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^MSLoadDataCompletionBlock)(BOOL succeeded, NSArray *logArray, NSString *batchId); /** - * Defines the storage component which is responsible for file i/o and file management. + * Defines the storage component which is responsible for persisting logs. */ @protocol MSStorage -/** - * Defines the maximum count of app logs per storage key in a file. - * - * Default: 50 - */ -@property(nonatomic) NSUInteger bucketFileLogCountLimit; - @required /** - * Writes a log to the file system. + * Store a log. * - * @param log The log item that should be written to disk - * @param groupID The groupID used for grouping + * @param log The log to be stored. + * @param groupID The groupID used for grouping logs. * * @return BOOL that indicates if the log was saved successfully. */ - (BOOL)saveLog:(id)log withGroupID:(NSString *)groupID; /** - * Delete logs related to given storage key from the file system. + * Delete logs related to given group from the storage. * - * @param groupID The groupID used for grouping. + * @param groupID The groupID used for grouping logs. * - * @return the list of deleted logs. + * @return The list of deleted logs. */ - (NSArray *)deleteLogsForGroupID:(NSString *)groupID; /** - * Delete a log from the file system. + * Delete a log from the storage. * - * @param logsId The log item that should be deleted from disk. - * @param groupID The key used for grouping. + * @param logsId The log that should be deleted from storage. + * @param groupID The groupID used for grouping logs. */ - (void)deleteLogsForId:(NSString *)logsId withGroupID:(NSString *)groupID; /** - * Returns the most recent logs for a given storage key. + * Return the most recent logs for a Group Id. * * @param groupID The key used for grouping. + * @param limit Limit the maximum number of logs to be loaded from the server. * * @return a list of logs. */ -- (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDataCompletionBlock)completion; +- (BOOL)loadLogsForGroupID:(NSString *)groupID limit:(NSUInteger)limit withCompletion:(nullable MSLoadDataCompletionBlock)completion; @end diff --git a/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m b/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m index accbcad890..1fc46a8747 100644 --- a/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m +++ b/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m @@ -133,6 +133,7 @@ - (void)testBatchQueueLimit { // If [self initChannelEndJobExpectation]; + int batchSizeLimit = 1; __block int currentBatchId = 1; __block NSMutableArray *sentBatchIds = [NSMutableArray new]; NSUInteger expectedMaxPendingBatched = 2; @@ -147,20 +148,20 @@ - (void)testBatchQueueLimit { } }); id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMStub([storageMock loadLogsForGroupID:kMSTestGroupID withCompletion:([OCMArg any])]) + OCMStub([storageMock loadLogsForGroupID:kMSTestGroupID limit:batchSizeLimit + withCompletion:([OCMArg any])]) .andDo(^(NSInvocation *invocation) { - MSLoadDataCompletionBlock loadCallback; // Mock load. - [invocation getArgument:&loadCallback atIndex:3]; + [invocation getArgument:&loadCallback atIndex:4]; loadCallback(YES, ((NSArray *)@[ OCMProtocolMock(@protocol(MSLog)) ]), [@(currentBatchId++) stringValue]); }); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupID:kMSTestGroupID priority:MSPriorityDefault flushInterval:0.0 - batchSizeLimit:1 + batchSizeLimit:batchSizeLimit pendingBatchesLimit:expectedMaxPendingBatched]; self.sut.configuration = config; MSChannelDefault *sut = [[MSChannelDefault alloc] initWithSender:senderMock @@ -175,7 +176,7 @@ - (void)testBatchQueueLimit { [self enqueueChannelEndJobExpectation]; // Then - [self waitForExpectationsWithTimeout:1 + [self waitForExpectationsWithTimeout:100 handler:^(NSError *error) { assertThatUnsignedLong(sut.pendingBatchIds.count, equalToUnsignedLong(expectedMaxPendingBatched)); @@ -200,6 +201,7 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { __block MSSendAsyncCompletionHandler senderBlock; __block MSLogContainer *lastBatchLogContainer; __block int currentBatchId = 1; + int batchSizeLimit = 1; // Init mocks. id senderMock = OCMProtocolMock(@protocol(MSSender)); @@ -213,12 +215,12 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { // Stub the storage load for that log. id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMStub([storageMock loadLogsForGroupID:kMSTestGroupID withCompletion:([OCMArg any])]) + OCMStub([storageMock loadLogsForGroupID:kMSTestGroupID limit:batchSizeLimit withCompletion:([OCMArg any])]) .andDo(^(NSInvocation *invocation) { MSLoadDataCompletionBlock loadCallback; // Get sender bloc for later call. - [invocation getArgument:&loadCallback atIndex:3]; + [invocation getArgument:&loadCallback atIndex:4]; // Mock load. loadCallback(YES, ((NSArray *)@[ OCMProtocolMock(@protocol(MSLog)) ]), [@(currentBatchId) stringValue]); @@ -228,7 +230,7 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupID:kMSTestGroupID priority:MSPriorityDefault flushInterval:0.0 - batchSizeLimit:1 + batchSizeLimit:batchSizeLimit pendingBatchesLimit:1]; self.sut.configuration = config; MSChannelDefault *sut = [[MSChannelDefault alloc] initWithSender:senderMock @@ -283,17 +285,19 @@ - (void)testDontForwardLogsToSenderOnDisabled { // If [self initChannelEndJobExpectation]; + int batchSizeLimit = 1; id mockLog = [self getValidMockLog]; id senderMock = OCMProtocolMock(@protocol(MSSender)); OCMStub([senderMock sendAsync:[OCMArg any] completionHandler:[OCMArg any]]); id storageMock = OCMProtocolMock(@protocol(MSStorage)); OCMStub([storageMock loadLogsForGroupID:kMSTestGroupID + limit:batchSizeLimit withCompletion:([OCMArg invokeBlockWithArgs:@YES, ((NSArray *)@[ mockLog ]), @"1", nil])]); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupID:kMSTestGroupID priority:MSPriorityDefault flushInterval:0.0 - batchSizeLimit:1 + batchSizeLimit:batchSizeLimit pendingBatchesLimit:10]; self.sut.configuration = config; MSChannelDefault *sut = [[MSChannelDefault alloc] initWithSender:senderMock @@ -325,16 +329,18 @@ - (void)testDeleteDataOnDisabled { // If [self initChannelEndJobExpectation]; + int batchSizeLimit = 1; id senderMock = OCMProtocolMock(@protocol(MSSender)); id storageMock = OCMProtocolMock(@protocol(MSStorage)); id mockLog = [self getValidMockLog]; OCMStub([storageMock loadLogsForGroupID:kMSTestGroupID + limit:batchSizeLimit withCompletion:([OCMArg invokeBlockWithArgs:@YES, ((NSArray *)@[ mockLog ]), @"1", nil])]); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupID:kMSTestGroupID priority:MSPriorityDefault flushInterval:0.0 - batchSizeLimit:1 + batchSizeLimit:batchSizeLimit pendingBatchesLimit:10]; self.sut.configuration = config; MSChannelDefault *sut = [[MSChannelDefault alloc] initWithSender:senderMock @@ -398,6 +404,7 @@ - (void)testDelegateCalledOnNonRecouverableError { * If */ [self initChannelEndJobExpectation]; + int batchSizeLimit = 1; NSUInteger expectedHTTPCode = MSHTTPCodesNo404NotFound; NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : kMSMCConnectionHttpErrorDesc, @@ -431,12 +438,12 @@ - (void)testDelegateCalledOnNonRecouverableError { // Stub the storage load for that log. id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMStub([storageMock loadLogsForGroupID:kMSTestGroupID withCompletion:([OCMArg any])]) + OCMStub([storageMock loadLogsForGroupID:kMSTestGroupID limit:batchSizeLimit withCompletion:([OCMArg any])]) .andDo(^(NSInvocation *invocation) { MSLoadDataCompletionBlock loadCallback; // Get sender bloc for later call. - [invocation getArgument:&loadCallback atIndex:3]; + [invocation getArgument:&loadCallback atIndex:4]; // Mock load of the first log. loadCallback(YES, ((NSArray *)@[ expectedLogs[0] ]), @"0"); @@ -456,7 +463,7 @@ - (void)testDelegateCalledOnNonRecouverableError { MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupID:kMSTestGroupID priority:MSPriorityDefault flushInterval:0.0 - batchSizeLimit:1 + batchSizeLimit:batchSizeLimit pendingBatchesLimit:1]; self.sut = [[MSChannelDefault alloc] initWithSender:senderMock storage:storageMock From 0aabb9e7ea28abcb4b6b69e31635500ac625626e Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Wed, 19 Apr 2017 15:50:44 -0700 Subject: [PATCH 15/76] Fix formatting --- MobileCenter/MSDBStorageTests.m | 91 +++++++++++-------- .../Internals/Storage/MSDBStorage.h | 1 + .../Internals/Storage/MSDBStorage.m | 49 +++++----- .../Internals/Storage/MSDBStoragePrivate.h | 7 +- .../Internals/Storage/MSDatabaseConnection.h | 6 +- .../Internals/Storage/MSSqliteConnection.m | 52 +++++------ 6 files changed, 110 insertions(+), 96 deletions(-) diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m index c46543cbb4..1a26ac19e6 100644 --- a/MobileCenter/MSDBStorageTests.m +++ b/MobileCenter/MSDBStorageTests.m @@ -1,17 +1,16 @@ +#import "MSAbstractLog.h" +#import "MSDBStoragePrivate.h" +#import "MSDatabaseConnection.h" #import #import #import -#import "MSAbstractLog.h" -#import "MSDatabaseConnection.h" -#import "MSDBStoragePrivate.h" static NSString *const kMSTestGroupID = @"TestGroupID"; @interface MSDBStorageTests : XCTestCase -@property (nonatomic) MSDBStorage *sut; -@property (nonatomic) id dbConnectionMock; - +@property(nonatomic) MSDBStorage *sut; +@property(nonatomic) id dbConnectionMock; @end @@ -29,71 +28,83 @@ - (void)testLoadTooManyLogs { // If NSUInteger expectedLogsCount = 5; - OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]).andReturn([self generateSerializedLogsWithCount:expectedLogsCount+1]); + OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]) + .andReturn([self generateSerializedLogsWithCount:expectedLogsCount + 1]); // When - BOOL moreLogsAvailable = [self.sut loadLogsForGroupID:kMSTestGroupID limit:expectedLogsCount withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { - - // Then - XCTAssertTrue(succeeded); - XCTAssertTrue(expectedLogsCount == logArray.count); - }]; + BOOL moreLogsAvailable = [self.sut loadLogsForGroupID:kMSTestGroupID + limit:expectedLogsCount + withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, + __attribute__((unused)) NSString * _Nonnull batchId) { + + // Then + XCTAssertTrue(succeeded); + XCTAssertTrue(expectedLogsCount == logArray.count); + }]; XCTAssertTrue(moreLogsAvailable); } - (void)testLoadJustEnoughLogs { - + // If NSUInteger expectedLogsCount = 5; - OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]).andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); - + OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]) + .andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); + // When - BOOL moreLogsAvailable = [self.sut loadLogsForGroupID:kMSTestGroupID limit:expectedLogsCount withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { - - // Then - XCTAssertTrue(succeeded); - XCTAssertTrue(expectedLogsCount == logArray.count); - }]; + BOOL moreLogsAvailable = [self.sut loadLogsForGroupID:kMSTestGroupID + limit:expectedLogsCount + withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, + __attribute__((unused)) NSString * _Nonnull batchId) { + + // Then + XCTAssertTrue(succeeded); + XCTAssertTrue(expectedLogsCount == logArray.count); + }]; XCTAssertFalse(moreLogsAvailable); } - (void)testLoadNotEnoughLogs { - + // If NSUInteger expectedLogsCount = 2; NSUInteger limit = 5; - OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]).andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); - + OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]) + .andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); + // When - BOOL moreLogsAvailable = [self.sut loadLogsForGroupID:kMSTestGroupID limit:limit withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, __attribute__((unused)) NSString *_Nonnull batchId) { - - // Then - XCTAssertTrue(succeeded); - XCTAssertTrue(expectedLogsCount == logArray.count); - }]; + BOOL moreLogsAvailable = [self.sut loadLogsForGroupID:kMSTestGroupID + limit:limit + withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, + __attribute__((unused)) NSString * _Nonnull batchId) { + + // Then + XCTAssertTrue(succeeded); + XCTAssertTrue(expectedLogsCount == logArray.count); + }]; XCTAssertFalse(moreLogsAvailable); } - (void)testLoadUnlimitedLogs { - + // If NSUInteger expectedLogsCount = 42; - OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]).andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); - + OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]) + .andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); + // When - NSArray *logs = [self.sut getLogsWithGroupID:kMSTestGroupID]; - + NSArray *logs = [self.sut getLogsWithGroupID:kMSTestGroupID]; + // Then XCTAssertTrue(expectedLogsCount == logs.count); } - -- (NSArray*> *)generateSerializedLogsWithCount:(NSUInteger)count { - NSMutableArray*> *logs = [NSMutableArray arrayWithCapacity:count]; +- (NSArray *> *)generateSerializedLogsWithCount:(NSUInteger)count { + NSMutableArray *> *logs = [NSMutableArray arrayWithCapacity:count]; for (NSUInteger i = 0; i < count; ++i) { NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:[MSAbstractLog new]]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; - [logs addObject:@[kMSTestGroupID,base64Data]]; + [logs addObject:@[ kMSTestGroupID, base64Data ]]; } return logs; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h index d16e7a20cb..e4bb0b9d6e 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.h @@ -1,4 +1,5 @@ #import + #import "MSStorage.h" @interface MSDBStorage : NSObject diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 1aafd4c74f..ebc1cddddb 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -1,5 +1,5 @@ -#import "MSDatabaseConnection.h" #import "MSDBStoragePrivate.h" +#import "MSDatabaseConnection.h" #import "MSLogger.h" #import "MSSqliteConnection.h" @@ -24,9 +24,9 @@ - (instancetype)init { return self; } --(void)initTables { +- (void)initTables { NSString *createLogTableQuery = [NSString stringWithFormat:@"create table if not exists %@ (%@ text, %@ text);", - kMSLogTableName, kMSGroupIDColumnName, kMSDataColumnName]; + kMSLogTableName, kMSGroupIDColumnName, kMSDataColumnName]; [self.connection executeQuery:createLogTableQuery]; } @@ -38,8 +38,8 @@ - (BOOL)saveLog:(id)log withGroupID:(NSString *)groupID { } NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; - NSString *addLogQuery = [NSString stringWithFormat:@"insert or replace into %@ values ('%@', '%@')", - kMSLogTableName, groupID, base64Data]; + NSString *addLogQuery = [NSString + stringWithFormat:@"insert or replace into %@ values ('%@', '%@')", kMSLogTableName, groupID, base64Data]; return [self.connection executeQuery:addLogQuery]; } @@ -55,14 +55,16 @@ - (void)deleteLogsForId:(NSString *)batchId withGroupID:(NSString *)groupID { [self deleteLogsWithGroupID:groupID]; } -- (BOOL)loadLogsForGroupID:(NSString *)groupID limit:(NSUInteger)limit withCompletion:(nullable MSLoadDataCompletionBlock)completion { - +- (BOOL)loadLogsForGroupID:(NSString *)groupID + limit:(NSUInteger)limit + withCompletion:(nullable MSLoadDataCompletionBlock)completion { + /* * There is a need to determine if there will be more log available than those under the limit. * So this is just about knowing if there is at least 1 log above the limit. * Thus, just 1 log is added to the requested limit and then removed later as needed. */ - NSMutableArray *logs = (NSMutableArray *)[self getLogsWithGroupID:groupID limit:(limit+1)]; + NSMutableArray *logs = (NSMutableArray *)[self getLogsWithGroupID:groupID limit:(limit + 1)]; BOOL moreLogsAvailable = NO; // Remove the log in excess, it means there is more logs available. @@ -73,42 +75,43 @@ - (BOOL)loadLogsForGroupID:(NSString *)groupID limit:(NSUInteger)limit withCompl if (completion) { completion(logs.count > 0, logs, groupID); } - + // Return YES if more logs available. return moreLogsAvailable; } #pragma mark - Private -- (NSArray*) getLogsWithGroupID:(NSString*)groupID{ - NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@'", - kMSLogTableName, kMSGroupIDColumnName, groupID]; +- (NSArray *)getLogsWithGroupID:(NSString *)groupID { + NSString *selectLogQuery = + [NSString stringWithFormat:@"select * from %@ where %@ == '%@'", kMSLogTableName, kMSGroupIDColumnName, groupID]; return [self getLogsWithQwery:selectLogQuery]; } -- (NSArray*) getLogsWithGroupID:(NSString*)groupID limit:(NSUInteger)limit{ - NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@' limit %lu", - kMSLogTableName, kMSGroupIDColumnName, groupID, (unsigned long)limit]; +- (NSArray *)getLogsWithGroupID:(NSString *)groupID limit:(NSUInteger)limit { + NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@' limit %lu", kMSLogTableName, + kMSGroupIDColumnName, groupID, (unsigned long)limit]; return [self getLogsWithQwery:selectLogQuery]; } -- (NSArray*) getLogsWithQwery:(NSString*)qwery{ - NSArray*> *result = [self.connection loadDataFromDB:qwery]; +- (NSArray *)getLogsWithQwery:(NSString *)qwery { + NSArray *> *result = [self.connection loadDataFromDB:qwery]; NSMutableArray *logs = [NSMutableArray new]; - + // Deserialize logs from DB. - for (NSArray *row in result) { + for (NSArray *row in result) { NSString *base64Data = row[1]; - NSData *logData = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters]; + NSData *logData = + [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters]; id log = [NSKeyedUnarchiver unarchiveObjectWithData:logData]; [logs addObject:log]; } return logs; } -- (void) deleteLogsWithGroupID:(NSString*)groupID { - NSString *deleteLogQuery = [NSString stringWithFormat:@"delete from %@ where %@ == '%@'", - kMSLogTableName, kMSGroupIDColumnName, groupID]; +- (void)deleteLogsWithGroupID:(NSString *)groupID { + NSString *deleteLogQuery = + [NSString stringWithFormat:@"delete from %@ where %@ == '%@'", kMSLogTableName, kMSGroupIDColumnName, groupID]; [self.connection executeQuery:deleteLogQuery]; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h index b3325a82a0..c586ec9d3a 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h @@ -1,10 +1,10 @@ #import "MSDBStorage.h" -@class MSDatabaseConnection; +@protocol MSDatabaseConnection; @interface MSDBStorage () -@property (nonatomic) id connection; +@property(nonatomic) id connection; /** * Get all logs with the given group ID from the storage. @@ -14,7 +14,6 @@ * @return Logs corresponding to the given group ID from the storage. * */ -- (NSArray*) getLogsWithGroupID:(NSString*)groupID; +- (NSArray *)getLogsWithGroupID:(NSString *)groupID; @end - diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h index ca3c132d9a..4a0537f338 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h @@ -2,9 +2,9 @@ @protocol MSDatabaseConnection --(instancetype)initWithDatabaseFilename:(NSString *)dbFilename; +- (instancetype)initWithDatabaseFilename:(NSString *)dbFilename; --(BOOL)executeQuery:(NSString *)query; --(NSArray*> *)loadDataFromDB:(NSString *)query; +- (BOOL)executeQuery:(NSString *)query; +- (NSArray *> *)loadDataFromDB:(NSString *)query; @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index b44fc76bf8..93a862a3bc 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -1,15 +1,15 @@ +#import "MSSqliteConnection.h" #import #import -#import "MSSqliteConnection.h" -@interface MSSqliteConnection() +@interface MSSqliteConnection () -@property (nonatomic, strong) NSString *documentsDirectory; -@property (nonatomic, strong) NSString *databaseFilename; -@property (nonatomic, strong) NSMutableArray*> *arrResults; -@property (nonatomic, strong) NSMutableArray *arrColumnNames; -@property (nonatomic) int affectedRows; -@property (nonatomic) long long lastInsertedRowID; +@property(nonatomic, strong) NSString *documentsDirectory; +@property(nonatomic, strong) NSString *databaseFilename; +@property(nonatomic, strong) NSMutableArray *> *arrResults; +@property(nonatomic, strong) NSMutableArray *arrColumnNames; +@property(nonatomic) int affectedRows; +@property(nonatomic) long long lastInsertedRowID; @end @@ -18,7 +18,7 @@ @implementation MSSqliteConnection @synthesize documentsDirectory; @synthesize databaseFilename; --(instancetype)initWithDatabaseFilename:(NSString *)dbFilename { +- (instancetype)initWithDatabaseFilename:(NSString *)dbFilename { self = [super init]; if (self) { @@ -37,7 +37,7 @@ -(instancetype)initWithDatabaseFilename:(NSString *)dbFilename { #pragma mark - Private --(void)copyDatabaseIntoDocumentsDirectory{ +- (void)copyDatabaseIntoDocumentsDirectory { // Check if the database file exists in the documents directory. NSString *destinationPath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename]; @@ -55,8 +55,7 @@ -(void)copyDatabaseIntoDocumentsDirectory{ } } - --(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ +- (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { // Total query result BOOL result = YES; @@ -67,13 +66,13 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // Set the database file path. NSString *databasePath = [self.documentsDirectory stringByAppendingPathComponent:self.databaseFilename]; - //TODO looks like we can optimize those 2 initializations. + // TODO looks like we can optimize those 2 initializations. // Initialize the results array. if (self.arrResults != nil) { [self.arrResults removeAllObjects]; self.arrResults = nil; } - self.arrResults = [NSMutableArray*> new]; + self.arrResults = [NSMutableArray *> new]; // Initialize the column names array. if (self.arrColumnNames != nil) { @@ -83,15 +82,16 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ self.arrColumnNames = [[NSMutableArray alloc] init]; // Open the database. - BOOL openDatabaseResult = (BOOL) sqlite3_open([databasePath UTF8String], &sqlite3Database); - if(openDatabaseResult == SQLITE_OK) { + BOOL openDatabaseResult = (BOOL)sqlite3_open([databasePath UTF8String], &sqlite3Database); + if (openDatabaseResult == SQLITE_OK) { - // Declare a sqlite3_stmt object in which will be stored the query after having been compiled into a SQLite statement. + // Declare a sqlite3_stmt object in which will be stored the query after having been compiled into a SQLite + // statement. sqlite3_stmt *compiledStatement; // Load all data from database to memory. - BOOL prepareStatementResult = (BOOL) sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL); - if(prepareStatementResult == SQLITE_OK) { + BOOL prepareStatementResult = (BOOL)sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL); + if (prepareStatementResult == SQLITE_OK) { // Check if the query is non-executable. if (!queryExecutable) { @@ -99,10 +99,10 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ // In this case data must be loaded from the database. // Declare an array to keep the data for each fetched row. - NSMutableArray *arrDataRow; + NSMutableArray *arrDataRow; // Loop through the results and add them to the results array row by row. - while(sqlite3_step(compiledStatement) == SQLITE_ROW) { + while (sqlite3_step(compiledStatement) == SQLITE_ROW) { // Initialize the mutable array that will contain the data of a fetched row. arrDataRow = [NSMutableArray new]; @@ -114,7 +114,7 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ for (unsigned int i = 0; i < totalColumns; ++i) { // Convert the column data to text (characters). - const char *dbDataAsChars = (const char*) sqlite3_column_text(compiledStatement, i); + const char *dbDataAsChars = (const char *)sqlite3_column_text(compiledStatement, i); // If there are contents in the currenct column (field) then add them to the current row array. if (dbDataAsChars != NULL) { @@ -178,18 +178,18 @@ -(BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ #pragma mark - Public method implementation --(NSArray*> *)loadDataFromDB:(NSString *)query{ +- (NSArray *> *)loadDataFromDB:(NSString *)query { // Run the query and indicate that is not executable. // The query string is converted to a char* object. [self runQuery:[query UTF8String] isQueryExecutable:NO]; // Returned the loaded results. - return (NSArray*> *)self.arrResults; + return (NSArray *> *)self.arrResults; } --(BOOL)executeQuery:(NSString *)query{ - +- (BOOL)executeQuery:(NSString *)query { + // Run the query and indicate that is executable. return [self runQuery:[query UTF8String] isQueryExecutable:YES]; } From 105586cb577e255d036098f90e8500af4723b4d2 Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Fri, 21 Apr 2017 14:02:34 -0700 Subject: [PATCH 16/76] Cosmetic fixes --- MobileCenter/MSDBStorageTests.m | 7 ++++--- .../Internals/Model/Utils/MSUserDefaults.m | 16 ++++++++-------- .../MobileCenter/Internals/Storage/MSDBStorage.m | 4 ++-- .../Internals/Storage/MSSqliteConnection.m | 3 ++- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m index 1a26ac19e6..911dbde062 100644 --- a/MobileCenter/MSDBStorageTests.m +++ b/MobileCenter/MSDBStorageTests.m @@ -1,10 +1,11 @@ -#import "MSAbstractLog.h" -#import "MSDBStoragePrivate.h" -#import "MSDatabaseConnection.h" #import #import #import +#import "MSAbstractLog.h" +#import "MSDBStoragePrivate.h" +#import "MSDatabaseConnection.h" + static NSString *const kMSTestGroupID = @"TestGroupID"; @interface MSDBStorageTests : XCTestCase diff --git a/MobileCenter/MobileCenter/Internals/Model/Utils/MSUserDefaults.m b/MobileCenter/MobileCenter/Internals/Model/Utils/MSUserDefaults.m index c9d021d7ce..7d65356913 100644 --- a/MobileCenter/MobileCenter/Internals/Model/Utils/MSUserDefaults.m +++ b/MobileCenter/MobileCenter/Internals/Model/Utils/MSUserDefaults.m @@ -30,37 +30,37 @@ - (void)removeObjectForKey:(NSString *)key { - (NSDictionary *)updateDictionary:(NSDictionary *)dict forKey:(NSString *)key expiration:(float)expiration { NSMutableDictionary *update = [[NSMutableDictionary alloc] initWithDictionary:dict]; - /* Get from local store. */ + // Get from local store. NSDictionary *store = [[NSUserDefaults standardUserDefaults] dictionaryForKey:key]; CFAbsoluteTime ts = [store[kMSUserDefaultsTs] doubleValue]; MSLogVerbose([MSMobileCenter logTag], @"Settings:store[%@]=%@", key, store); - /* Force update if timestamp expiration is reached. */ + // Force update if timestamp expiration is reached. if (ts <= 0.0 || expiration <= 0.0f || fabs(CFAbsoluteTimeGetCurrent() - ts) < (double)expiration) { - /* Remove if already in store and value is the same. */ + // Remove if already in store and value is the same. for (NSString *k in [store allKeys]) { if (update[k] != nil && [update[k] isEqual:store[k]]) [update removeObjectForKey:k]; } } - /* If still values to update. */ + // If still values to update. if ([update count] > 0) { MSLogDebug([MSMobileCenter logTag], @"Settings:update[%@]=%@", key, update); - /* Copy store as a mutable version. */ + // Copy store as a mutable version. NSMutableDictionary *d = [store mutableCopy]; if (d == nil) d = [[NSMutableDictionary alloc] initWithCapacity:[update count]]; - /* Append update to the current store. */ + // Append update to the current store. [d addEntriesFromDictionary:update]; - /* Set new timestamp. */ + // Set new timestamp. d[kMSUserDefaultsTs] = @(CFAbsoluteTimeGetCurrent()); - /* Save. */ + // Save. [[NSUserDefaults standardUserDefaults] setObject:d forKey:key]; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index ebc1cddddb..113a174516 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -49,7 +49,7 @@ - (BOOL)saveLog:(id)log withGroupID:(NSString *)groupID { return logs; } -- (void)deleteLogsForId:(NSString *)batchId withGroupID:(NSString *)groupID { +- (void)deleteLogsForId:(__attribute__((unused)) NSString *)batchId withGroupID:(NSString *)groupID { // FIXME: Restore batch deletion. [self deleteLogsWithGroupID:groupID]; @@ -60,7 +60,7 @@ - (BOOL)loadLogsForGroupID:(NSString *)groupID withCompletion:(nullable MSLoadDataCompletionBlock)completion { /* - * There is a need to determine if there will be more log available than those under the limit. + * There is a need to determine if there will be more logs available than those under the limit. * So this is just about knowing if there is at least 1 log above the limit. * Thus, just 1 log is added to the requested limit and then removed later as needed. */ diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index 93a862a3bc..1bd5ee1d26 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -1,7 +1,8 @@ -#import "MSSqliteConnection.h" #import #import +#import "MSSqliteConnection.h" + @interface MSSqliteConnection () @property(nonatomic, strong) NSString *documentsDirectory; From 060d8de1c4f2c414455573ae5359a634275f414c Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Fri, 28 Apr 2017 15:00:04 -0700 Subject: [PATCH 17/76] Fix build errors + use MSLogger in MSSqliteConnection --- .../Internals/Storage/MSSqliteConnection.m | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index 1bd5ee1d26..c25b6b9ef1 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -1,6 +1,7 @@ #import #import +#import "MSMobileCenterInternal.h" #import "MSSqliteConnection.h" @interface MSSqliteConnection () @@ -51,7 +52,7 @@ - (void)copyDatabaseIntoDocumentsDirectory { // Check if any error occurred during copying and display it. if (error != nil) { - NSLog(@"%@", [error localizedDescription]); + MSLogError(MSMobileCenter.logTag, @"%@", [error localizedDescription]); } } } @@ -83,7 +84,7 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { self.arrColumnNames = [[NSMutableArray alloc] init]; // Open the database. - BOOL openDatabaseResult = (BOOL)sqlite3_open([databasePath UTF8String], &sqlite3Database); + BOOL openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database); if (openDatabaseResult == SQLITE_OK) { // Declare a sqlite3_stmt object in which will be stored the query after having been compiled into a SQLite @@ -91,7 +92,7 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { sqlite3_stmt *compiledStatement; // Load all data from database to memory. - BOOL prepareStatementResult = (BOOL)sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL); + BOOL prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL); if (prepareStatementResult == SQLITE_OK) { // Check if the query is non-executable. @@ -121,13 +122,15 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { if (dbDataAsChars != NULL) { // Convert the characters to string. - [arrDataRow addObject:[NSString stringWithUTF8String:dbDataAsChars]]; + [arrDataRow addObject:(NSString * _Nonnull)[NSString stringWithUTF8String:dbDataAsChars]]; } // Keep the current column name. if (self.arrColumnNames.count != totalColumns) { dbDataAsChars = sqlite3_column_name(compiledStatement, i); - [self.arrColumnNames addObject:[NSString stringWithUTF8String:dbDataAsChars]]; + if (dbDataAsChars != NULL) { + [self.arrColumnNames addObject:(NSString * _Nonnull)[NSString stringWithUTF8String:dbDataAsChars]]; + } } } @@ -150,16 +153,18 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { // Keep the last inserted row ID. self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database); } else { - + // If could not execute the query show the error message on the debugger. - NSLog(@"DB Error: %s\nerror code: %d", sqlite3_errmsg(sqlite3Database), executeQueryResults); + NSString *errorMsg = [NSString stringWithUTF8String:sqlite3_errmsg(sqlite3Database)]; + MSLogError(MSMobileCenter.logTag, @"DB Error: %@\nerror code: %d", errorMsg, executeQueryResults); result = NO; } } } else { // In the database cannot be opened then show the error message on the debugger. - NSLog(@"DB Error: %s\nquery: %s", sqlite3_errmsg(sqlite3Database), query); + NSString *errorMsg = [NSString stringWithUTF8String:sqlite3_errmsg(sqlite3Database)]; + MSLogError(MSMobileCenter.logTag,@"DB Error: %@\nquery: %@", errorMsg, [NSString stringWithUTF8String:query]); result = NO; } @@ -171,9 +176,7 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { // Close the database. sqlite3_close(sqlite3Database); - - NSLog(@"Successfull query: %@", result ? @"YES" : @"NO"); - + MSLogVerbose(MSMobileCenter.logTag, @"Successfull query: %@", result ? @"YES" : @"NO"); return result; } From 6b31602aaa3a38097239e45360eff75db0578a43 Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Fri, 28 Apr 2017 16:01:34 -0700 Subject: [PATCH 18/76] Fix casting issue on MSSqliteConnection --- .../MobileCenter/Internals/Storage/MSSqliteConnection.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index c25b6b9ef1..427307632f 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -84,7 +84,7 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { self.arrColumnNames = [[NSMutableArray alloc] init]; // Open the database. - BOOL openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database); + int openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database); if (openDatabaseResult == SQLITE_OK) { // Declare a sqlite3_stmt object in which will be stored the query after having been compiled into a SQLite @@ -92,7 +92,7 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { sqlite3_stmt *compiledStatement; // Load all data from database to memory. - BOOL prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL); + int prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL); if (prepareStatementResult == SQLITE_OK) { // Check if the query is non-executable. From 03c0f55c079fbcf999c1cf5d53cf4076756b9e0b Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Thu, 8 Jun 2017 15:33:41 -0700 Subject: [PATCH 19/76] Fix delete logs form DB per batch ID * Delete logs in case of deserialization failure * Identify logs with a unique Id in the database * Cache ongoing batches * Allow per batch deletion * Update unit tests * MSLoadDataCompletionBlock success now based on batch Id value * Some renaming --- MobileCenter/MSDBStorageTests.m | 58 +++---- .../Internals/Channel/MSChannelDefault.m | 151 +++++++++-------- .../Internals/Storage/MSDBStorage.m | 160 +++++++++++++----- .../Internals/Storage/MSDBStoragePrivate.h | 10 +- .../Internals/Storage/MSStorage.h | 19 ++- .../MobileCenterTests/MSChannelDefaultTests.m | 83 +++++---- .../MSStoragePerfomanceTests.m | 62 ++++--- 7 files changed, 315 insertions(+), 228 deletions(-) diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m index f59f923689..b7f81cebbc 100644 --- a/MobileCenter/MSDBStorageTests.m +++ b/MobileCenter/MSDBStorageTests.m @@ -33,15 +33,15 @@ - (void)testLoadTooManyLogs { .andReturn([self generateSerializedLogsWithCount:expectedLogsCount + 1]); // When - BOOL moreLogsAvailable = [self.sut loadLogsForGroupId:kMSTestGroupId - limit:expectedLogsCount - withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, - __attribute__((unused)) NSString * _Nonnull batchId) { - - // Then - XCTAssertTrue(succeeded); - XCTAssertTrue(expectedLogsCount == logArray.count); - }]; + BOOL moreLogsAvailable = + [self.sut loadLogsWithGroupId:kMSTestGroupId + limit:expectedLogsCount + withCompletion:^(NSArray *_Nonnull logArray, NSString *_Nonnull batchId) { + + // Then + assertThat(batchId, notNilValue()); + XCTAssertTrue(expectedLogsCount == logArray.count); + }]; XCTAssertTrue(moreLogsAvailable); } @@ -53,15 +53,15 @@ - (void)testLoadJustEnoughLogs { .andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); // When - BOOL moreLogsAvailable = [self.sut loadLogsForGroupId:kMSTestGroupId - limit:expectedLogsCount - withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, - __attribute__((unused)) NSString * _Nonnull batchId) { - - // Then - XCTAssertTrue(succeeded); - XCTAssertTrue(expectedLogsCount == logArray.count); - }]; + BOOL moreLogsAvailable = + [self.sut loadLogsWithGroupId:kMSTestGroupId + limit:expectedLogsCount + withCompletion:^(NSArray *_Nonnull logArray, NSString *_Nonnull batchId) { + + // Then + assertThat(batchId, notNilValue()); + XCTAssertTrue(expectedLogsCount == logArray.count); + }]; XCTAssertFalse(moreLogsAvailable); } @@ -74,15 +74,15 @@ - (void)testLoadNotEnoughLogs { .andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); // When - BOOL moreLogsAvailable = [self.sut loadLogsForGroupId:kMSTestGroupId - limit:limit - withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, - __attribute__((unused)) NSString * _Nonnull batchId) { - - // Then - XCTAssertTrue(succeeded); - XCTAssertTrue(expectedLogsCount == logArray.count); - }]; + BOOL moreLogsAvailable = + [self.sut loadLogsWithGroupId:kMSTestGroupId + limit:limit + withCompletion:^(NSArray *_Nonnull logArray, NSString *_Nonnull batchId) { + + // Then + assertThat(batchId, notNilValue()); + XCTAssertTrue(expectedLogsCount == logArray.count); + }]; XCTAssertFalse(moreLogsAvailable); } @@ -94,7 +94,7 @@ - (void)testLoadUnlimitedLogs { .andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); // When - NSArray *logs = [self.sut getLogsWithGroupId:kMSTestGroupId]; + NSDictionary> *logs = [self.sut getLogsFromDBWithGroupId:kMSTestGroupId]; // Then XCTAssertTrue(expectedLogsCount == logs.count); @@ -105,7 +105,7 @@ - (void)testLoadUnlimitedLogs { for (NSUInteger i = 0; i < count; ++i) { NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:[MSAbstractLog new]]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; - [logs addObject:@[ kMSTestGroupId, base64Data ]]; + [logs addObject:@[ [@(i) stringValue], kMSTestGroupId, base64Data ]]; } return logs; } diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m index d75b94998e..48560a1dda 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m @@ -129,84 +129,87 @@ - (void)flushQueue { // Reset item count and load data from the storage. self.itemsCount = 0; self.availableBatchFromStorage = [self.storage - loadLogsForGroupId:self.configuration.groupId - limit:self.configuration.batchSizeLimit - withCompletion:^(BOOL succeeded, NSArray *_Nonnull logArray, NSString *_Nonnull batchId) { - - // Logs may be deleted from storage before this flush. - if (succeeded) { - [self.pendingBatchIds addObject:batchId]; - if (self.pendingBatchIds.count >= self.configuration.pendingBatchesLimit) { - self.pendingBatchQueueFull = YES; - } - MSLogContainer *container = [[MSLogContainer alloc] initWithBatchId:batchId andLogs:logArray]; - MSLogDebug([MSMobileCenter logTag], @"Sending log(s), batch Id:%@, payload:\n%@", batchId, - [container serializeLogWithPrettyPrinting:YES]); - - // Notify delegates. - [self enumerateDelegatesForSelector:@selector(channel:willSendLog:) - withBlock:^(id delegate) { - for (id aLog in logArray) { - [delegate channel:self willSendLog:aLog]; - } - }]; - - // Forward logs to the sender. - [self.sender - sendAsync:container - completionHandler:^(NSString *senderBatchId, NSUInteger statusCode, __attribute__((unused)) NSData *data, NSError *error) { - dispatch_async(self.logsDispatchQueue, ^{ - if ([self.pendingBatchIds containsObject:senderBatchId]) { - - // Success. - if (statusCode == MSHTTPCodesNo200OK) { - MSLogDebug([MSMobileCenter logTag], @"Log(s) sent with success, batch Id:%@.", senderBatchId); - - // Notify delegates. - [self enumerateDelegatesForSelector:@selector(channel:didSucceedSendingLog:) - withBlock:^(id delegate) { - for (id aLog in logArray) { - [delegate channel:self didSucceedSendingLog:aLog]; - } - }]; - - // Remove from pending logs and storage. - [self.pendingBatchIds removeObject:senderBatchId]; - [self.storage deleteLogsForId:senderBatchId withGroupId:self.configuration.groupId]; - - // Try to flush again if batch queue is not full anymore. - if (self.pendingBatchQueueFull && - self.pendingBatchIds.count < self.configuration.pendingBatchesLimit) { - self.pendingBatchQueueFull = NO; - if (self.availableBatchFromStorage) { - [self flushQueue]; + loadLogsWithGroupId:self.configuration.groupId + limit:self.configuration.batchSizeLimit + withCompletion:^(NSArray *_Nonnull logArray, NSString *batchId) { + + // Logs may be deleted from storage before this flush. + if (batchId.length > 0) { + [self.pendingBatchIds addObject:batchId]; + if (self.pendingBatchIds.count >= self.configuration.pendingBatchesLimit) { + self.pendingBatchQueueFull = YES; + } + MSLogContainer *container = [[MSLogContainer alloc] initWithBatchId:batchId andLogs:logArray]; + MSLogDebug([MSMobileCenter logTag], @"Sending log(s), batch Id:%@, payload:\n%@", batchId, + [container serializeLogWithPrettyPrinting:YES]); + + // Notify delegates. + [self enumerateDelegatesForSelector:@selector(channel:willSendLog:) + withBlock:^(id delegate) { + for (id aLog in logArray) { + [delegate channel:self willSendLog:aLog]; + } + }]; + + // Forward logs to the sender. + [self.sender sendAsync:container + completionHandler:^(NSString *senderBatchId, NSUInteger statusCode, + __attribute__((unused)) NSData *data, NSError *error) { + dispatch_async(self.logsDispatchQueue, ^{ + if ([self.pendingBatchIds containsObject:senderBatchId]) { + + // Success. + if (statusCode == MSHTTPCodesNo200OK) { + MSLogDebug([MSMobileCenter logTag], @"Log(s) sent with success, batch Id:%@.", + senderBatchId); + + // Notify delegates. + [self enumerateDelegatesForSelector:@selector(channel:didSucceedSendingLog:) + withBlock:^(id delegate) { + for (id aLog in logArray) { + [delegate channel:self didSucceedSendingLog:aLog]; + } + }]; + + // Remove from pending logs and storage. + [self.pendingBatchIds removeObject:senderBatchId]; + [self.storage deleteLogsWithBatchId:senderBatchId groupId:self.configuration.groupId]; + + // Try to flush again if batch queue is not full anymore. + if (self.pendingBatchQueueFull && + self.pendingBatchIds.count < self.configuration.pendingBatchesLimit) { + self.pendingBatchQueueFull = NO; + if (self.availableBatchFromStorage) { + [self flushQueue]; + } } } - } - // Failure. - else { - MSLogDebug([MSMobileCenter logTag], @"Log(s) sent with failure, batch Id:%@, status code:%lu", - senderBatchId, (unsigned long)statusCode); + // Failure. + else { + MSLogDebug([MSMobileCenter logTag], + @"Log(s) sent with failure, batch Id:%@, status code:%lu", senderBatchId, + (unsigned long)statusCode); - // Notify delegates. - [self enumerateDelegatesForSelector:@selector(channel:didFailSendingLog:withError:) + // Notify delegates. + [self + enumerateDelegatesForSelector:@selector(channel:didFailSendingLog:withError:) withBlock:^(id delegate) { for (id aLog in logArray) { [delegate channel:self didFailSendingLog:aLog withError:error]; } }]; - // Remove from pending logs. - [self.pendingBatchIds removeObject:senderBatchId]; - [self.storage deleteLogsForId:senderBatchId withGroupId:self.configuration.groupId]; - } - } else - MSLogWarning([MSMobileCenter logTag], @"Batch Id %@ not expected, ignore.", senderBatchId); - }); - }]; - } - }]; + // Remove from pending logs. + [self.pendingBatchIds removeObject:senderBatchId]; + [self.storage deleteLogsWithBatchId:senderBatchId groupId:self.configuration.groupId]; + } + } else + MSLogWarning([MSMobileCenter logTag], @"Batch Id %@ not expected, ignore.", senderBatchId); + }); + }]; + } + }]; // Flush again if there is another batch to send. if (self.availableBatchFromStorage && !self.pendingBatchQueueFull) { @@ -225,7 +228,8 @@ - (void)startTimer { * Cast (NSEC_PER_SEC * self.configuration.flushInterval) to (int64_t) silence warning. The compiler otherwise * complains that we're using a float param (flushInterval) and implicitly downcast to int64_t. */ - dispatch_source_set_timer(self.timerSource, dispatch_walltime(NULL, (int64_t) (NSEC_PER_SEC * self.configuration.flushInterval)), + dispatch_source_set_timer(self.timerSource, + dispatch_walltime(NULL, (int64_t)(NSEC_PER_SEC * self.configuration.flushInterval)), 1ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC); __weak typeof(self) weakSelf = self; dispatch_source_set_event_handler(self.timerSource, ^{ @@ -255,7 +259,7 @@ - (void)setEnabled:(BOOL)isEnabled andDeleteDataOnDisabled:(BOOL)deleteData { if (self.enabled != isEnabled) { self.enabled = isEnabled; if (isEnabled) { - if (!self.sender.suspended){ + if (!self.sender.suspended) { [self resume]; } } else { @@ -312,12 +316,11 @@ - (void)deleteAllLogsWithErrorSync:(NSError *)error { // Delete pending batches first. for (NSString *batchId in self.pendingBatchIds) { - [self.storage deleteLogsForId:batchId withGroupId:self.configuration.groupId]; + [self.storage deleteLogsWithBatchId:batchId groupId:self.configuration.groupId]; } - // TODO: What about the database schema, it will stay on disc even after disabling? // Delete remaining logs. - deletedLogs = [self.storage deleteLogsForGroupId:self.configuration.groupId]; + deletedLogs = [self.storage deleteLogsWithGroupId:self.configuration.groupId]; // Notify failure of remaining logs. for (id log in deletedLogs) { @@ -343,7 +346,7 @@ - (void)senderDidResume:(id)sender { - (void)senderDidReceiveFatalError:(id)sender { (void)sender; - + // Disable and delete data on fatal errors. [self setEnabled:NO andDeleteDataOnDisabled:YES]; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index e4ad254d81..c3b6a0f8d2 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -1,36 +1,40 @@ #import "MSDBStoragePrivate.h" #import "MSDatabaseConnection.h" #import "MSLogger.h" +#import "MSMobileCenterInternal.h" #import "MSSqliteConnection.h" +#import "MSUtility.h" static NSString *const kMSLogEntityName = @"MSDBLog"; static NSString *const kMSDBFileName = @"MSDBLogs.sqlite"; static NSString *const kMSLogTableName = @"MSLog"; +static NSString *const kMSIdColumnName = @"id"; static NSString *const kMSGroupIdColumnName = @"groupId"; static NSString *const kMSDataColumnName = @"data"; @implementation MSDBStorage -@synthesize connection; - #pragma mark - Initialization - (instancetype)init { self = [super init]; if (self) { - self.connection = [[MSSqliteConnection alloc] initWithDatabaseFilename:kMSDBFileName]; + _connection = [[MSSqliteConnection alloc] initWithDatabaseFilename:kMSDBFileName]; + _batches = [NSMutableDictionary *> new]; [self initTables]; } return self; } - (void)initTables { - NSString *createLogTableQuery = [NSString stringWithFormat:@"create table if not exists %@ (%@ text, %@ text);", - kMSLogTableName, kMSGroupIdColumnName, kMSDataColumnName]; + NSString *createLogTableQuery = [NSString + stringWithFormat: + @"CREATE TABLE IF NOT EXISTS %@ (%@ INTEGER PRIMARY KEY AUTOINCREMENT, %@ TEXT NOT NULL, %@ TEXT NOT NULL);", + kMSLogTableName, kMSIdColumnName, kMSGroupIdColumnName, kMSDataColumnName]; [self.connection executeQuery:createLogTableQuery]; } -#pragma mark - Public +#pragma mark - Save logs - (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { if (!log) { @@ -38,81 +42,145 @@ - (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { } NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; - NSString *addLogQuery = [NSString - stringWithFormat:@"insert or replace into %@ values ('%@', '%@')", kMSLogTableName, groupId, base64Data]; + NSString *addLogQuery = + [NSString stringWithFormat:@"INSERT INTO %@ ('%@', '%@') VALUES ('%@', '%@')", kMSLogTableName, + kMSGroupIdColumnName, kMSDataColumnName, groupId, base64Data]; return [self.connection executeQuery:addLogQuery]; } -- (NSArray *)deleteLogsForGroupId:(NSString *)groupId { - NSArray *logs = [self getLogsWithGroupId:groupId]; - [self deleteLogsWithGroupId:groupId]; - return logs; -} - -- (void)deleteLogsForId:(__attribute__((unused)) NSString *)batchId withGroupId:(NSString *)groupId { +#pragma mark - Load logs - // FIXME: Restore batch deletion. - [self deleteLogsWithGroupId:groupId]; -} - -- (BOOL)loadLogsForGroupId:(NSString *)groupId - limit:(NSUInteger)limit - withCompletion:(nullable MSLoadDataCompletionBlock)completion { +// TODO check all NSArray are NSArray> +- (BOOL)loadLogsWithGroupId:(NSString *)groupId + limit:(NSUInteger)limit + withCompletion:(nullable MSLoadDataCompletionBlock)completion { /* * There is a need to determine if there will be more logs available than those under the limit. - * So this is just about knowing if there is at least 1 log above the limit. - * Thus, just 1 log is added to the requested limit and then removed later as needed. + * This is just about knowing if there is at least 1 log above the limit. */ - NSMutableArray *logs = (NSMutableArray *)[self getLogsWithGroupId:groupId limit:(limit + 1)]; + NSMutableDictionary> *logs = + [[self getLogsFromDBWithGroupId:groupId limit:(limit + 1)] mutableCopy]; + BOOL logsAvailable = NO; BOOL moreLogsAvailable = NO; + NSString *batchId; - // Remove the log in excess, it means there is more logs available. - if (logs.count > limit) { - [logs removeLastObject]; + // More logs available for the next batch, remove the log in excess for this batch. + if (logs.count > 0 && logs.count > limit) { + [logs removeObjectForKey:(NSString * _Nonnull)[[logs allKeys] lastObject]]; moreLogsAvailable = YES; } + + // Generate batch Id. + logsAvailable = logs.count > 0; + if (logsAvailable) { + batchId = MS_UUID_STRING; + [self.batches setObject:(NSArray * _Nonnull)[logs allKeys] + forKey:[groupId stringByAppendingString:batchId]]; + } + + // Load completed. if (completion) { - completion(logs.count > 0, logs, groupId); + completion([logs allValues], batchId); } // Return YES if more logs available. return moreLogsAvailable; } -#pragma mark - Private +#pragma mark - Delete logs + +- (NSArray> *)deleteLogsWithGroupId:(NSString *)groupId { + NSArray> *logs = [[self getLogsFromDBWithGroupId:groupId] allValues]; + + // Delete logs + [self deleteLogsFromDBWithColumnValue:groupId columnName:kMSGroupIdColumnName]; + + // Delete related batches. + for (NSString *batchKey in [self.batches allKeys]) { + if ([batchKey hasPrefix:groupId]) { + [self.batches removeObjectForKey:batchKey]; + } + } + return logs; +} -- (NSArray *)getLogsWithGroupId:(NSString *)groupId { +- (void)deleteLogsWithBatchId:(NSString *)batchId groupId:(NSString *)groupId { + + // Get log Ids. + NSArray *Ids = self.batches[[groupId stringByAppendingString:batchId]]; + + // Delete logs. + [self deleteLogsFromDBWithColumnValues:Ids columnName:kMSIdColumnName]; +} + +#pragma mark - DB selection + +- (NSDictionary> *)getLogsFromDBWithGroupId:(NSString *)groupId { NSString *selectLogQuery = - [NSString stringWithFormat:@"select * from %@ where %@ == '%@'", kMSLogTableName, kMSGroupIdColumnName, groupId]; - return [self getLogsWithQwery:selectLogQuery]; + [NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@ == '%@'", kMSLogTableName, kMSGroupIdColumnName, groupId]; + return [self getLogsFromDBWithQwery:selectLogQuery]; } -- (NSArray *)getLogsWithGroupId:(NSString *)groupId limit:(NSUInteger)limit { - NSString *selectLogQuery = [NSString stringWithFormat:@"select * from %@ where %@ == '%@' limit %lu", kMSLogTableName, +- (NSDictionary> *)getLogsFromDBWithGroupId:(NSString *)groupId limit:(NSUInteger)limit { + NSString *selectLogQuery = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@ == '%@' LIMIT %lu", kMSLogTableName, kMSGroupIdColumnName, groupId, (unsigned long)limit]; - return [self getLogsWithQwery:selectLogQuery]; + return [self getLogsFromDBWithQwery:selectLogQuery]; } -- (NSArray *)getLogsWithQwery:(NSString *)qwery { +- (NSDictionary> *)getLogsFromDBWithQwery:(NSString *)qwery { NSArray *> *result = [self.connection loadDataFromDB:qwery]; - NSMutableArray *logs = [NSMutableArray new]; + NSMutableDictionary> *logs = [NSMutableDictionary> new]; - // Deserialize logs from DB. + // Get logs from DB. for (NSArray *row in result) { - NSString *base64Data = row[1]; + + // TODO use constants for DB column indexes. + NSString *Id = row[0]; + NSString *base64Data = row[2]; NSData *logData = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters]; - id log = [NSKeyedUnarchiver unarchiveObjectWithData:logData]; - [logs addObject:log]; + id log; + + // Deserialize the log. + @try { + log = [NSKeyedUnarchiver unarchiveObjectWithData:logData]; + } @catch (NSException *exception) { + + // The archived log is not valid. + MSLogError([MSMobileCenter logTag], @"Deserialization failed for log with Id %@: %@", Id, exception); + [self deleteLogFromDBWithId:Id]; + continue; + } + [logs setObject:log forKey:Id]; } return logs; } -- (void)deleteLogsWithGroupId:(NSString *)groupId { - NSString *deleteLogQuery = - [NSString stringWithFormat:@"delete from %@ where %@ == '%@'", kMSLogTableName, kMSGroupIdColumnName, groupId]; - [self.connection executeQuery:deleteLogQuery]; +#pragma mark - DB deletion + +- (void)deleteLogFromDBWithId:(NSString *)Id { + [self deleteLogsFromDBWithColumnValue:Id columnName:kMSIdColumnName]; +} + +- (void)deleteLogsFromDBWithColumnValue:(NSString *)columnValue columnName:(NSString *)columnName { + [self deleteLogsFromDBWithColumnValues:@[ columnValue ] columnName:columnName]; +} + +- (void)deleteLogsFromDBWithColumnValues:(NSArray *)columnValues columnName:(NSString *)columnName { + NSString *deletionTrace = [NSString + stringWithFormat:@"Deletion of log(s) by %@ with value(s) '%@'", columnName, [columnValues componentsJoinedByString:@"','"]]; + + // Build up delete query. + NSString *deleteLogsQuery = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN ('%@')", kMSLogTableName, + columnName, [columnValues componentsJoinedByString:@"','"]]; + + // Execute. + if ([self.connection executeQuery:deleteLogsQuery]) { + MSLogVerbose([MSMobileCenter logTag], @"%@ %@", deletionTrace, @"succeded"); + } else { + MSLogError([MSMobileCenter logTag], @"%@ %@", deletionTrace, @"failed"); + } } @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h index 52e9f7c2d3..d15c09ebf1 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h @@ -4,8 +4,16 @@ @interface MSDBStorage () +/** + * Connection to SQLite database. + */ @property(nonatomic) id connection; +/** + * Keep track of logs batches per group Id associated with their logs Ids. + */ +@property(nonatomic) NSMutableDictionary *> *batches; + /** * Get all logs with the given group Id from the storage. * @@ -14,6 +22,6 @@ * @return Logs corresponding to the given group Id from the storage. * */ -- (NSArray *)getLogsWithGroupId:(NSString *)groupId; +- (NSDictionary> *)getLogsFromDBWithGroupId:(NSString *)groupId; @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h index e3e84a1422..f327c517c0 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h @@ -4,7 +4,13 @@ NS_ASSUME_NONNULL_BEGIN -typedef void (^MSLoadDataCompletionBlock)(BOOL succeeded, NSArray *logArray, NSString *batchId); +/** + * Completion block triggered when data is loaded from the storage. + * + * @param logArray Array of logs loaded from the storage. + * @param batchId Batch Id associated with the logs, `nil` if no logs available. + */ +typedef void (^MSLoadDataCompletionBlock)(NSArray> *_Nullable logArray, NSString *_Nullable batchId); /** * Defines the storage component which is responsible for persisting logs. @@ -30,15 +36,15 @@ typedef void (^MSLoadDataCompletionBlock)(BOOL succeeded, NSArray *logArr * * @return The list of deleted logs. */ -- (NSArray *)deleteLogsForGroupId:(NSString *)groupId; +- (NSArray> *)deleteLogsWithGroupId:(NSString *)groupId; /** * Delete a log from the storage. * - * @param logsId The log that should be deleted from storage. + * @param batchId Id of the log to be deleted from storage. * @param groupId The key used for grouping logs. */ -- (void)deleteLogsForId:(NSString *)logsId withGroupId:(NSString *)groupId; +- (void)deleteLogsWithBatchId:(NSString *)batchId groupId:(NSString *)groupId; /** * Return the most recent logs for a Group Id. @@ -48,8 +54,9 @@ typedef void (^MSLoadDataCompletionBlock)(BOOL succeeded, NSArray *logArr * * @return a list of logs. */ -- (BOOL)loadLogsForGroupId:(NSString *)groupId limit:(NSUInteger)limit withCompletion:(nullable MSLoadDataCompletionBlock)completion; - +- (BOOL)loadLogsWithGroupId:(NSString *)groupId + limit:(NSUInteger)limit + withCompletion:(nullable MSLoadDataCompletionBlock)completion; @end diff --git a/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m b/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m index a73dc3af4f..2a34689af7 100644 --- a/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m +++ b/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m @@ -150,15 +150,13 @@ - (void)testBatchQueueLimit { } }); id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMStub([storageMock loadLogsForGroupId:kMSTestGroupId limit:batchSizeLimit - withCompletion:([OCMArg any])]) + OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId limit:batchSizeLimit withCompletion:([OCMArg any])]) .andDo(^(NSInvocation *invocation) { MSLoadDataCompletionBlock loadCallback; // Mock load. [invocation getArgument:&loadCallback atIndex:4]; - loadCallback(YES, ((NSArray *)@[ OCMProtocolMock(@protocol(MSLog)) ]), - [@(currentBatchId++) stringValue]); + loadCallback(((NSArray *)@[ OCMProtocolMock(@protocol(MSLog)) ]), [@(currentBatchId++) stringValue]); }); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupId:kMSTestGroupId priority:MSPriorityDefault @@ -217,7 +215,7 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { // Stub the storage load for that log. id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMStub([storageMock loadLogsForGroupId:kMSTestGroupId limit:batchSizeLimit withCompletion:([OCMArg any])]) + OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId limit:batchSizeLimit withCompletion:([OCMArg any])]) .andDo(^(NSInvocation *invocation) { MSLoadDataCompletionBlock loadCallback; @@ -225,7 +223,7 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { [invocation getArgument:&loadCallback atIndex:4]; // Mock load. - loadCallback(YES, ((NSArray *)@[ OCMProtocolMock(@protocol(MSLog)) ]), [@(currentBatchId) stringValue]); + loadCallback(((NSArray *)@[ OCMProtocolMock(@protocol(MSLog)) ]), [@(currentBatchId) stringValue]); }); // Configure channel. @@ -292,10 +290,9 @@ - (void)testDontForwardLogsToSenderOnDisabled { id senderMock = OCMProtocolMock(@protocol(MSSender)); OCMStub([senderMock sendAsync:[OCMArg any] completionHandler:[OCMArg any]]); id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMStub([storageMock - loadLogsForGroupId:kMSTestGroupId - limit:batchSizeLimit - withCompletion:([OCMArg invokeBlockWithArgs:@YES, ((NSArray *)@[ mockLog ]), @"1", nil])]); + OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId + limit:batchSizeLimit + withCompletion:([OCMArg invokeBlockWithArgs:((NSArray *)@[ mockLog ]), @"1", nil])]); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupId:kMSTestGroupId priority:MSPriorityDefault flushInterval:0.0 @@ -335,10 +332,9 @@ - (void)testDeleteDataOnDisabled { id senderMock = OCMProtocolMock(@protocol(MSSender)); id storageMock = OCMProtocolMock(@protocol(MSStorage)); id mockLog = [self getValidMockLog]; - OCMStub([storageMock - loadLogsForGroupId:kMSTestGroupId - limit:batchSizeLimit - withCompletion:([OCMArg invokeBlockWithArgs:@YES, ((NSArray *)@[ mockLog ]), @"1", nil])]); + OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId + limit:batchSizeLimit + withCompletion:([OCMArg invokeBlockWithArgs:((NSArray *)@[ mockLog ]), @"1", nil])]); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupId:kMSTestGroupId priority:MSPriorityDefault flushInterval:0.0 @@ -359,7 +355,7 @@ - (void)testDeleteDataOnDisabled { handler:^(NSError *error) { // Check that logs as been requested for deletion and that there is no batch left. - OCMVerify([storageMock deleteLogsForGroupId:kMSTestGroupId]); + OCMVerify([storageMock deleteLogsWithGroupId:kMSTestGroupId]); if (error) { XCTFail(@"Expectation Failed with error: %@", error); } @@ -367,16 +363,15 @@ - (void)testDeleteDataOnDisabled { } - (void)testDisableAndDeleteDataOnSenderFatalError { - + // If [self initChannelEndJobExpectation]; id senderMock = OCMProtocolMock(@protocol(MSSender)); id storageMock = OCMProtocolMock(@protocol(MSStorage)); id mockLog = [self getValidMockLog]; - OCMStub([storageMock - loadLogsForGroupId:kMSTestGroupId - limit:2 - withCompletion:([OCMArg invokeBlockWithArgs:@YES, ((NSArray *)@[ mockLog ]), @"1", nil])]); + OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId + limit:2 + withCompletion:([OCMArg invokeBlockWithArgs:((NSArray *)@[ mockLog ]), @"1", nil])]); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupId:kMSTestGroupId priority:MSPriorityDefault flushInterval:0.0 @@ -391,13 +386,13 @@ - (void)testDisableAndDeleteDataOnSenderFatalError { [sut enqueueItem:mockLog withCompletion:nil]; [sut senderDidReceiveFatalError:senderMock]; [self enqueueChannelEndJobExpectation]; - + // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - + // Check that logs as been requested for deletion and that there is no batch left. - OCMVerify([storageMock deleteLogsForGroupId:kMSTestGroupId]); + OCMVerify([storageMock deleteLogsWithGroupId:kMSTestGroupId]); assertThatBool(sut.enabled, isFalse()); if (error) { XCTFail(@"Expectation Failed with error: %@", error); @@ -405,15 +400,15 @@ - (void)testDisableAndDeleteDataOnSenderFatalError { }]; } -- (void)testSuspendOnDisabled{ - +- (void)testSuspendOnDisabled { + // If [self initChannelEndJobExpectation]; [self.sut setEnabled:YES andDeleteDataOnDisabled:NO]; - + // When [self.sut setEnabled:NO andDeleteDataOnDisabled:NO]; - + // Then [self enqueueChannelEndJobExpectation]; [self waitForExpectationsWithTimeout:1 @@ -426,8 +421,8 @@ - (void)testSuspendOnDisabled{ }]; } -- (void)testResumeOnEnabled{ - +- (void)testResumeOnEnabled { + // If __block BOOL result1, result2; [self initChannelEndJobExpectation]; @@ -437,25 +432,25 @@ - (void)testResumeOnEnabled{ dispatch_async(self.logsDispatchQueue, ^{ sender.suspended = NO; }); - + // When [self.sut setEnabled:YES andDeleteDataOnDisabled:NO]; dispatch_async(self.logsDispatchQueue, ^{ result1 = self.sut.suspended; }); - + // If [self.sut setEnabled:NO andDeleteDataOnDisabled:NO]; dispatch_async(self.logsDispatchQueue, ^{ sender.suspended = YES; }); - + // When [self.sut setEnabled:YES andDeleteDataOnDisabled:NO]; dispatch_async(self.logsDispatchQueue, ^{ result2 = self.sut.suspended; }); - + // Then [self enqueueChannelEndJobExpectation]; [self waitForExpectationsWithTimeout:1 @@ -468,22 +463,22 @@ - (void)testResumeOnEnabled{ }]; } -- (void)testSuspendOnSenderSuspended{ - +- (void)testSuspendOnSenderSuspended { + // If __block BOOL result1, result2; [self initChannelEndJobExpectation]; [self.sut setEnabled:NO andDeleteDataOnDisabled:NO]; - + // When [self.sut senderDidSuspend:self.senderMock]; dispatch_async(self.logsDispatchQueue, ^{ result1 = self.sut.suspended; }); - + // If [self.sut setEnabled:YES andDeleteDataOnDisabled:NO]; - + // When [self.sut senderDidSuspend:self.senderMock]; dispatch_async(self.logsDispatchQueue, ^{ @@ -500,29 +495,29 @@ - (void)testSuspendOnSenderSuspended{ }]; } -- (void)testSuspendOnSenderResumed{ - +- (void)testSuspendOnSenderResumed { + // If __block BOOL result1, result2; [self initChannelEndJobExpectation]; [self.sut setEnabled:NO andDeleteDataOnDisabled:NO]; - + // When [self.sut senderDidResume:self.senderMock]; dispatch_async(self.logsDispatchQueue, ^{ result1 = self.sut.suspended; }); - + // If [self.sut setEnabled:YES andDeleteDataOnDisabled:NO]; [self.sut senderDidSuspend:self.senderMock]; - + // When [self.sut senderDidResume:self.senderMock]; dispatch_async(self.logsDispatchQueue, ^{ result2 = self.sut.suspended; }); - + // Then [self enqueueChannelEndJobExpectation]; [self waitForExpectationsWithTimeout:1 diff --git a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m index 4e9a629172..dd13bf0e9f 100644 --- a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m +++ b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m @@ -8,7 +8,7 @@ @interface MSStoragePerfomanceTests : XCTestCase @end -@interface MSStoragePerfomanceTests() +@interface MSStoragePerfomanceTests () @property(nonatomic) MSDBStorage *dbStorage; @@ -28,13 +28,14 @@ - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; - [self.dbStorage deleteLogsForGroupId:@"anyKey"]; + [self.dbStorage deleteLogsWithGroupId:@"anyKey"]; } #pragma mark - Database storage tests - (void)testDatabaseWriteShortLogsPerformance { - NSArray* arrayOfLogs = [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; + NSArray *arrayOfLogs = + [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { @@ -44,7 +45,8 @@ - (void)testDatabaseWriteShortLogsPerformance { } - (void)testDatabaseWriteLongLogsPerformance { - NSArray* arrayOfLogs = [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; + NSArray *arrayOfLogs = + [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { @@ -54,7 +56,8 @@ - (void)testDatabaseWriteLongLogsPerformance { } - (void)testDatabaseWriteVeryLongLogsPerformance { - NSArray* arrayOfLogs = [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; + NSArray *arrayOfLogs = + [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { @@ -66,7 +69,8 @@ - (void)testDatabaseWriteVeryLongLogsPerformance { #pragma mark - File storage tests - (void)testFileStorageWriteShortLogsPerformance { - NSArray* arrayOfLogs = [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; + NSArray *arrayOfLogs = + [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { @@ -76,7 +80,8 @@ - (void)testFileStorageWriteShortLogsPerformance { } - (void)testFileStorageWriteLongLogsPerformance { - NSArray* arrayOfLogs = [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; + NSArray *arrayOfLogs = + [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { @@ -86,7 +91,8 @@ - (void)testFileStorageWriteLongLogsPerformance { } - (void)testFileStorageWriteVeryLongLogsPerformance { - NSArray* arrayOfLogs = [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; + NSArray *arrayOfLogs = + [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { @@ -97,9 +103,9 @@ - (void)testFileStorageWriteVeryLongLogsPerformance { #pragma mark - Private -- (NSArray*)generateLogsWithShortServicesNames:(int) numLogs withNumService:(int)numServices { - NSMutableArray *dic = [NSMutableArray new]; - for(int i = 0; i < numLogs; ++i) { +- (NSArray *)generateLogsWithShortServicesNames:(int)numLogs withNumService:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for (int i = 0; i < numLogs; ++i) { MSStartServiceLog *log = [MSStartServiceLog new]; log.services = [self generateServicesWithShortNames:numServices]; [dic addObject:log]; @@ -107,9 +113,9 @@ - (void)testFileStorageWriteVeryLongLogsPerformance { return dic; } -- (NSArray*)generateLogsWithLongServicesNames:(int) numLogs withNumService:(int)numServices { - NSMutableArray *dic = [NSMutableArray new]; - for(int i = 0; i < numLogs; ++i) { +- (NSArray *)generateLogsWithLongServicesNames:(int)numLogs withNumService:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for (int i = 0; i < numLogs; ++i) { MSStartServiceLog *log = [MSStartServiceLog new]; log.services = [self generateServicesWithLongNames:numServices]; [dic addObject:log]; @@ -117,9 +123,9 @@ - (void)testFileStorageWriteVeryLongLogsPerformance { return dic; } -- (NSArray*)generateLogsWithVeryLongServicesNames:(int) numLogs withNumService:(int)numServices { - NSMutableArray *dic = [NSMutableArray new]; - for(int i = 0; i < numLogs; ++i) { +- (NSArray *)generateLogsWithVeryLongServicesNames:(int)numLogs withNumService:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for (int i = 0; i < numLogs; ++i) { MSStartServiceLog *log = [MSStartServiceLog new]; log.services = [self generateServicesWithVeryLongNames:numServices]; [dic addObject:log]; @@ -127,19 +133,19 @@ - (void)testFileStorageWriteVeryLongLogsPerformance { return dic; } -- (NSArray*)generateServicesWithShortNames:(int)numServices { - NSMutableArray *dic = [NSMutableArray new]; - for(int i = 0; i < numServices; ++i) { +- (NSArray *)generateServicesWithShortNames:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for (int i = 0; i < numServices; ++i) { [dic addObject:[[NSUUID UUID] UUIDString]]; } return dic; } -- (NSArray*)generateServicesWithLongNames:(int)numServices { - NSMutableArray *dic = [NSMutableArray new]; - for(int i = 0; i < numServices; ++i) { +- (NSArray *)generateServicesWithLongNames:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for (int i = 0; i < numServices; ++i) { NSString *value = @""; - for(int j = 0; j < 10; ++j) { + for (int j = 0; j < 10; ++j) { value = [value stringByAppendingString:[[NSUUID UUID] UUIDString]]; } [dic addObject:value]; @@ -147,11 +153,11 @@ - (void)testFileStorageWriteVeryLongLogsPerformance { return dic; } -- (NSArray*)generateServicesWithVeryLongNames:(int)numServices { - NSMutableArray *dic = [NSMutableArray new]; - for(int i = 0; i < numServices; ++i) { +- (NSArray *)generateServicesWithVeryLongNames:(int)numServices { + NSMutableArray *dic = [NSMutableArray new]; + for (int i = 0; i < numServices; ++i) { NSString *value = @""; - for(int j = 0; j < 50; ++j) { + for (int j = 0; j < 50; ++j) { value = [value stringByAppendingString:[[NSUUID UUID] UUIDString]]; } [dic addObject:value]; From e83a597c8b7813b5e954f968559a255f4dbcd4c6 Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Thu, 8 Jun 2017 17:32:04 -0700 Subject: [PATCH 20/76] Add unit tests + rename + fix * Add unit tests to cover DB deletion * Rename NSArray to NSArray> * Fix delete cached batch while deleting logs by batch Id --- MobileCenter/MSDBStorageTests.m | 182 +++++++++++++++++- .../Internals/Model/MSLogContainer.h | 4 +- .../Internals/Model/MSLogContainer.m | 2 +- .../Internals/Storage/MSDBStorage.m | 22 +-- .../Internals/Storage/MSDBStoragePrivate.h | 7 + .../MobileCenterTests/MSChannelDefaultTests.m | 25 +-- .../MSIngestionSenderTests.m | 10 +- .../MobileCenterTests/MSLogContainerTests.m | 2 +- 8 files changed, 219 insertions(+), 35 deletions(-) diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m index b7f81cebbc..235bbfd4f2 100644 --- a/MobileCenter/MSDBStorageTests.m +++ b/MobileCenter/MSDBStorageTests.m @@ -5,8 +5,10 @@ #import "MSAbstractLog.h" #import "MSDBStoragePrivate.h" #import "MSDatabaseConnection.h" +#import "MSUtility.h" static NSString *const kMSTestGroupId = @"TestGroupId"; +static NSString *const kMSAnotherTestGroupId = @"AnotherGroupId"; @interface MSDBStorageTests : XCTestCase @@ -36,7 +38,7 @@ - (void)testLoadTooManyLogs { BOOL moreLogsAvailable = [self.sut loadLogsWithGroupId:kMSTestGroupId limit:expectedLogsCount - withCompletion:^(NSArray *_Nonnull logArray, NSString *_Nonnull batchId) { + withCompletion:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { // Then assertThat(batchId, notNilValue()); @@ -56,7 +58,7 @@ - (void)testLoadJustEnoughLogs { BOOL moreLogsAvailable = [self.sut loadLogsWithGroupId:kMSTestGroupId limit:expectedLogsCount - withCompletion:^(NSArray *_Nonnull logArray, NSString *_Nonnull batchId) { + withCompletion:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { // Then assertThat(batchId, notNilValue()); @@ -77,7 +79,7 @@ - (void)testLoadNotEnoughLogs { BOOL moreLogsAvailable = [self.sut loadLogsWithGroupId:kMSTestGroupId limit:limit - withCompletion:^(NSArray *_Nonnull logArray, NSString *_Nonnull batchId) { + withCompletion:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { // Then assertThat(batchId, notNilValue()); @@ -100,6 +102,180 @@ - (void)testLoadUnlimitedLogs { XCTAssertTrue(expectedLogsCount == logs.count); } +- (void)testDeleteLogsWithGroupId { + + // Test deletion with no batch. + + /* + * If + */ + NSString *expectedQuery = [NSString + stringWithFormat:@"DELETE FROM %@ WHERE %@ IN ('%@')", kMSLogTableName, kMSGroupIdColumnName, kMSTestGroupId]; + [self.sut.batches removeAllObjects]; + + /* + * When + */ + [self.sut deleteLogsWithGroupId:kMSTestGroupId]; + + /* + * Then + */ + OCMVerify([self.dbConnectionMock executeQuery:expectedQuery]); + assertThatInteger(self.sut.batches.count, equalToInteger(0)); + + // Test deletion with only the batch to delete. + + /* + * If + */ + + NSString *batchKeyToDelete = [kMSTestGroupId stringByAppendingString:MS_UUID_STRING]; + [self.sut.batches setObject:@[ @"27", @"35" ] forKey:batchKeyToDelete]; + + /* + * When + */ + [self.sut deleteLogsWithGroupId:kMSTestGroupId]; + + /* + * Then + */ + OCMVerify([self.dbConnectionMock executeQuery:expectedQuery]); + assertThatInteger(self.sut.batches.count, equalToInteger(0)); + + // Test deletion with more than one batch to delete. + + /* + * If + */ + NSString *anotherBatchKeyToDelete = [kMSTestGroupId stringByAppendingString:MS_UUID_STRING]; + NSArray *otherIdsToDelete = @[ @"45" ]; + [self.sut.batches setObject:@[ @"27", @"28" ] forKey:batchKeyToDelete]; + [self.sut.batches setObject:otherIdsToDelete forKey:anotherBatchKeyToDelete]; + + /* + * When + */ + [self.sut deleteLogsWithGroupId:kMSTestGroupId]; + + /* + * Then + */ + OCMVerify([self.dbConnectionMock executeQuery:expectedQuery]); + assertThatInteger(self.sut.batches.count, equalToInteger(0)); + + // Test deletion with the batch to delete and batches from other groups. + + /* + * If + */ + NSString *batchKeyNotToDelete = [kMSAnotherTestGroupId stringByAppendingString:MS_UUID_STRING]; + NSArray *idsNotToDelete = @[ @"42", @"43", @"44" ]; + [self.sut.batches setObject:@[ @"27", @"28" ] forKey:batchKeyToDelete]; + [self.sut.batches setObject:idsNotToDelete forKey:batchKeyNotToDelete]; + + /* + * When + */ + [self.sut deleteLogsWithGroupId:kMSTestGroupId]; + + /* + * Then + */ + OCMVerify([self.dbConnectionMock executeQuery:expectedQuery]); + assertThatInteger(self.sut.batches.count, equalToInteger(1)); + assertThat(self.sut.batches[batchKeyNotToDelete], is(idsNotToDelete)); +} + +- (void)testDeleteLogsWithBatchId { + + // Test deletion with only the batch to delete. + + /* + * If + */ + NSArray *idsToDelete = @[ @"27", @"35" ]; + NSString *expectedQuery = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN ('%@')", kMSLogTableName, + kMSIdColumnName, [idsToDelete componentsJoinedByString:@"','"]]; + NSString *batchToDelete = MS_UUID_STRING; + NSString *batchKeyToDelete = [kMSTestGroupId stringByAppendingString:batchToDelete]; + [self.sut.batches setObject:idsToDelete forKey:batchKeyToDelete]; + + /* + * When + */ + [self.sut deleteLogsWithBatchId:batchToDelete groupId:kMSTestGroupId]; + + /* + * Then + */ + OCMVerify([self.dbConnectionMock executeQuery:expectedQuery]); + assertThatInteger(self.sut.batches.count, equalToInteger(0)); + + // Test deletion with more than one batch to delete. + + /* + * If + */ + NSString *batchKeyNotToDelete = [kMSTestGroupId stringByAppendingString:MS_UUID_STRING]; + NSArray *idsNotToDelete = @[ @"42", @"43", @"44" ]; + [self.sut.batches setObject:@[ @"27", @"28" ] forKey:batchKeyToDelete]; + [self.sut.batches setObject:idsNotToDelete forKey:batchKeyNotToDelete]; + + /* + * When + */ + [self.sut deleteLogsWithBatchId:batchToDelete groupId:kMSTestGroupId]; + + /* + * Then + */ + OCMVerify([self.dbConnectionMock executeQuery:expectedQuery]); + assertThatInteger(self.sut.batches.count, equalToInteger(1)); + assertThat(self.sut.batches[batchKeyNotToDelete], is(idsNotToDelete)); + + // Test deletion with more than one batch to delete. + + /* + * If + */ + batchKeyNotToDelete = [kMSAnotherTestGroupId stringByAppendingString:MS_UUID_STRING]; + [self.sut.batches removeAllObjects]; + [self.sut.batches setObject:@[ @"27", @"28" ] forKey:batchKeyToDelete]; + [self.sut.batches setObject:idsNotToDelete forKey:batchKeyNotToDelete]; + + /* + * When + */ + [self.sut deleteLogsWithBatchId:batchToDelete groupId:kMSTestGroupId]; + + /* + * Then + */ + OCMVerify([self.dbConnectionMock executeQuery:expectedQuery]); + assertThatInteger(self.sut.batches.count, equalToInteger(1)); + assertThat(self.sut.batches[batchKeyNotToDelete], is(idsNotToDelete)); + + // Test deletion with no batch. + + /* + * If + */ + OCMReject([self.dbConnectionMock executeQuery:expectedQuery]); + [self.sut.batches removeAllObjects]; + + /* + * When + */ + [self.sut deleteLogsWithBatchId:MS_UUID_STRING groupId:kMSTestGroupId]; + + /* + * Then + */ + assertThatInteger(self.sut.batches.count, equalToInteger(0)); +} + - (NSArray *> *)generateSerializedLogsWithCount:(NSUInteger)count { NSMutableArray *> *logs = [NSMutableArray arrayWithCapacity:count]; for (NSUInteger i = 0; i < count; ++i) { diff --git a/MobileCenter/MobileCenter/Internals/Model/MSLogContainer.h b/MobileCenter/MobileCenter/Internals/Model/MSLogContainer.h index 6efc9b03e5..27b8f3881a 100644 --- a/MobileCenter/MobileCenter/Internals/Model/MSLogContainer.h +++ b/MobileCenter/MobileCenter/Internals/Model/MSLogContainer.h @@ -11,7 +11,7 @@ /** * The list of logs */ -@property(nonatomic) NSArray *logs; +@property(nonatomic) NSArray> *logs; /** * Initializer. @@ -21,7 +21,7 @@ * * @return A log container instance for the given batch ID. */ -- (id)initWithBatchId:(NSString *)batchId andLogs:(NSArray *)logs; +- (id)initWithBatchId:(NSString *)batchId andLogs:(NSArray> *)logs; /** * Serialize logs into a JSON string. diff --git a/MobileCenter/MobileCenter/Internals/Model/MSLogContainer.m b/MobileCenter/MobileCenter/Internals/Model/MSLogContainer.m index 41423e44df..905b24e47a 100644 --- a/MobileCenter/MobileCenter/Internals/Model/MSLogContainer.m +++ b/MobileCenter/MobileCenter/Internals/Model/MSLogContainer.m @@ -5,7 +5,7 @@ @implementation MSLogContainer -- (id)initWithBatchId:(NSString *)batchId andLogs:(NSArray *)logs { +- (id)initWithBatchId:(NSString *)batchId andLogs:(NSArray> *)logs { if ((self = [super init])) { self.batchId = batchId; self.logs = logs; diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index c3b6a0f8d2..f644058569 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -5,13 +5,6 @@ #import "MSSqliteConnection.h" #import "MSUtility.h" -static NSString *const kMSLogEntityName = @"MSDBLog"; -static NSString *const kMSDBFileName = @"MSDBLogs.sqlite"; -static NSString *const kMSLogTableName = @"MSLog"; -static NSString *const kMSIdColumnName = @"id"; -static NSString *const kMSGroupIdColumnName = @"groupId"; -static NSString *const kMSDataColumnName = @"data"; - @implementation MSDBStorage #pragma mark - Initialization @@ -50,7 +43,6 @@ - (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { #pragma mark - Load logs -// TODO check all NSArray are NSArray> - (BOOL)loadLogsWithGroupId:(NSString *)groupId limit:(NSUInteger)limit withCompletion:(nullable MSLoadDataCompletionBlock)completion { @@ -108,10 +100,14 @@ - (BOOL)loadLogsWithGroupId:(NSString *)groupId - (void)deleteLogsWithBatchId:(NSString *)batchId groupId:(NSString *)groupId { // Get log Ids. - NSArray *Ids = self.batches[[groupId stringByAppendingString:batchId]]; + NSString *batchIdKey = [groupId stringByAppendingString:batchId]; + NSArray *Ids = self.batches[batchIdKey]; - // Delete logs. - [self deleteLogsFromDBWithColumnValues:Ids columnName:kMSIdColumnName]; + // Delete logs and associated batch. + if (Ids.count > 0) { + [self deleteLogsFromDBWithColumnValues:Ids columnName:kMSIdColumnName]; + [self.batches removeObjectForKey:batchIdKey]; + } } #pragma mark - DB selection @@ -168,8 +164,8 @@ - (void)deleteLogsFromDBWithColumnValue:(NSString *)columnValue columnName:(NSSt } - (void)deleteLogsFromDBWithColumnValues:(NSArray *)columnValues columnName:(NSString *)columnName { - NSString *deletionTrace = [NSString - stringWithFormat:@"Deletion of log(s) by %@ with value(s) '%@'", columnName, [columnValues componentsJoinedByString:@"','"]]; + NSString *deletionTrace = [NSString stringWithFormat:@"Deletion of log(s) by %@ with value(s) '%@'", columnName, + [columnValues componentsJoinedByString:@"','"]]; // Build up delete query. NSString *deleteLogsQuery = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN ('%@')", kMSLogTableName, diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h index d15c09ebf1..0e65f43dce 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h @@ -1,5 +1,12 @@ #import "MSDBStorage.h" +static NSString *const kMSLogEntityName = @"MSDBLog"; +static NSString *const kMSDBFileName = @"MSDBLogs.sqlite"; +static NSString *const kMSLogTableName = @"MSLog"; +static NSString *const kMSIdColumnName = @"id"; +static NSString *const kMSGroupIdColumnName = @"groupId"; +static NSString *const kMSDataColumnName = @"data"; + @protocol MSDatabaseConnection; @interface MSDBStorage () diff --git a/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m b/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m index 2a34689af7..fe5773b43f 100644 --- a/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m +++ b/MobileCenter/MobileCenterTests/MSChannelDefaultTests.m @@ -156,7 +156,7 @@ - (void)testBatchQueueLimit { // Mock load. [invocation getArgument:&loadCallback atIndex:4]; - loadCallback(((NSArray *)@[ OCMProtocolMock(@protocol(MSLog)) ]), [@(currentBatchId++) stringValue]); + loadCallback(((NSArray> *)@[ OCMProtocolMock(@protocol(MSLog)) ]), [@(currentBatchId++) stringValue]); }); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupId:kMSTestGroupId priority:MSPriorityDefault @@ -223,7 +223,7 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { [invocation getArgument:&loadCallback atIndex:4]; // Mock load. - loadCallback(((NSArray *)@[ OCMProtocolMock(@protocol(MSLog)) ]), [@(currentBatchId) stringValue]); + loadCallback(((NSArray> *)@[ OCMProtocolMock(@protocol(MSLog)) ]), [@(currentBatchId) stringValue]); }); // Configure channel. @@ -290,9 +290,10 @@ - (void)testDontForwardLogsToSenderOnDisabled { id senderMock = OCMProtocolMock(@protocol(MSSender)); OCMStub([senderMock sendAsync:[OCMArg any] completionHandler:[OCMArg any]]); id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId - limit:batchSizeLimit - withCompletion:([OCMArg invokeBlockWithArgs:((NSArray *)@[ mockLog ]), @"1", nil])]); + OCMStub([storageMock + loadLogsWithGroupId:kMSTestGroupId + limit:batchSizeLimit + withCompletion:([OCMArg invokeBlockWithArgs:((NSArray> *)@[ mockLog ]), @"1", nil])]); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupId:kMSTestGroupId priority:MSPriorityDefault flushInterval:0.0 @@ -332,9 +333,10 @@ - (void)testDeleteDataOnDisabled { id senderMock = OCMProtocolMock(@protocol(MSSender)); id storageMock = OCMProtocolMock(@protocol(MSStorage)); id mockLog = [self getValidMockLog]; - OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId - limit:batchSizeLimit - withCompletion:([OCMArg invokeBlockWithArgs:((NSArray *)@[ mockLog ]), @"1", nil])]); + OCMStub([storageMock + loadLogsWithGroupId:kMSTestGroupId + limit:batchSizeLimit + withCompletion:([OCMArg invokeBlockWithArgs:((NSArray> *)@[ mockLog ]), @"1", nil])]); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupId:kMSTestGroupId priority:MSPriorityDefault flushInterval:0.0 @@ -369,9 +371,10 @@ - (void)testDisableAndDeleteDataOnSenderFatalError { id senderMock = OCMProtocolMock(@protocol(MSSender)); id storageMock = OCMProtocolMock(@protocol(MSStorage)); id mockLog = [self getValidMockLog]; - OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId - limit:2 - withCompletion:([OCMArg invokeBlockWithArgs:((NSArray *)@[ mockLog ]), @"1", nil])]); + OCMStub([storageMock + loadLogsWithGroupId:kMSTestGroupId + limit:2 + withCompletion:([OCMArg invokeBlockWithArgs:((NSArray> *)@[ mockLog ]), @"1", nil])]); MSChannelConfiguration *config = [[MSChannelConfiguration alloc] initWithGroupId:kMSTestGroupId priority:MSPriorityDefault flushInterval:0.0 diff --git a/MobileCenter/MobileCenterTests/MSIngestionSenderTests.m b/MobileCenter/MobileCenterTests/MSIngestionSenderTests.m index d4175b9f42..21fceee52e 100644 --- a/MobileCenter/MobileCenterTests/MSIngestionSenderTests.m +++ b/MobileCenter/MobileCenterTests/MSIngestionSenderTests.m @@ -1,3 +1,5 @@ + + #import #import #import @@ -95,12 +97,12 @@ - (void)testUnrecoverableError { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"HTTP Response 200"]; id delegateMock = OCMProtocolMock(@protocol(MSSenderDelegate)); [self.sut addDelegate:delegateMock]; - + // When [self.sut sendAsync:container completionHandler:^(NSString *batchId, NSUInteger statusCode, __attribute__((unused)) NSData *data, NSError *error) { - + // Then XCTAssertEqual(containerId, batchId); XCTAssertEqual((MSHTTPCodesNo)statusCode, MSHTTPCodesNo404NotFound); @@ -392,7 +394,7 @@ - (void)testInvalidContainer { log.toffset = [NSNumber numberWithLongLong:(long long)([MSUtility nowInMilliseconds])]; // Log does not have device info, therefore, it's an invalid log - MSLogContainer *container = [[MSLogContainer alloc] initWithBatchId:@"1" andLogs:(NSArray *)@[ log ]]; + MSLogContainer *container = [[MSLogContainer alloc] initWithBatchId:@"1" andLogs:(NSArray> *)@[ log ]]; [self.sut sendAsync:container completionHandler:^(__attribute__((unused)) NSString *batchId, __attribute__((unused)) NSUInteger statusCode, @@ -654,7 +656,7 @@ - (MSLogContainer *)createLogContainerWithId:(NSString *)batchId { log2.device = deviceMock; MSLogContainer *logContainer = - [[MSLogContainer alloc] initWithBatchId:batchId andLogs:(NSArray *)@[ log1, log2 ]]; + [[MSLogContainer alloc] initWithBatchId:batchId andLogs:(NSArray> *)@[ log1, log2 ]]; return logContainer; } diff --git a/MobileCenter/MobileCenterTests/MSLogContainerTests.m b/MobileCenter/MobileCenterTests/MSLogContainerTests.m index 439cd0a882..ab2900d2dc 100644 --- a/MobileCenter/MobileCenterTests/MSLogContainerTests.m +++ b/MobileCenter/MobileCenterTests/MSLogContainerTests.m @@ -22,7 +22,7 @@ - (void)testLogContainerSerialization { log2.sid = MS_UUID_STRING; log2.toffset = [NSNumber numberWithLongLong:(long long)([MSUtility nowInMilliseconds])]; - logContainer.logs = (NSArray *)@[ log1, log2 ]; + logContainer.logs = (NSArray> *)@[ log1, log2 ]; // When NSString *jsonString = [logContainer serializeLog]; From 7edfe2045b4bae893b241638caf574433c2e603c Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Mon, 12 Jun 2017 08:39:55 -0700 Subject: [PATCH 21/76] Fix typo and formatting --- MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m | 8 ++++---- MobileCenter/MobileCenterTests/MSIngestionSenderTests.m | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index f644058569..e659036d9c 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -115,17 +115,17 @@ - (void)deleteLogsWithBatchId:(NSString *)batchId groupId:(NSString *)groupId { - (NSDictionary> *)getLogsFromDBWithGroupId:(NSString *)groupId { NSString *selectLogQuery = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@ == '%@'", kMSLogTableName, kMSGroupIdColumnName, groupId]; - return [self getLogsFromDBWithQwery:selectLogQuery]; + return [self getLogsFromDBWithQuery:selectLogQuery]; } - (NSDictionary> *)getLogsFromDBWithGroupId:(NSString *)groupId limit:(NSUInteger)limit { NSString *selectLogQuery = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@ == '%@' LIMIT %lu", kMSLogTableName, kMSGroupIdColumnName, groupId, (unsigned long)limit]; - return [self getLogsFromDBWithQwery:selectLogQuery]; + return [self getLogsFromDBWithQuery:selectLogQuery]; } -- (NSDictionary> *)getLogsFromDBWithQwery:(NSString *)qwery { - NSArray *> *result = [self.connection loadDataFromDB:qwery]; +- (NSDictionary> *)getLogsFromDBWithQuery:(NSString *)query { + NSArray *> *result = [self.connection loadDataFromDB:query]; NSMutableDictionary> *logs = [NSMutableDictionary> new]; // Get logs from DB. diff --git a/MobileCenter/MobileCenterTests/MSIngestionSenderTests.m b/MobileCenter/MobileCenterTests/MSIngestionSenderTests.m index 21fceee52e..337a8a2765 100644 --- a/MobileCenter/MobileCenterTests/MSIngestionSenderTests.m +++ b/MobileCenter/MobileCenterTests/MSIngestionSenderTests.m @@ -1,5 +1,3 @@ - - #import #import #import From 14d0f1bafc079194afd5bf9ba75c824c6e9155b6 Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Mon, 12 Jun 2017 13:04:09 -0700 Subject: [PATCH 22/76] Fix don't load logs from previous batches --- MobileCenter/MSDBStorageTests.m | 1 - .../MobileCenter/Internals/Storage/MSDBStorage.m | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m index 235bbfd4f2..e8f06f3a4a 100644 --- a/MobileCenter/MSDBStorageTests.m +++ b/MobileCenter/MSDBStorageTests.m @@ -129,7 +129,6 @@ - (void)testDeleteLogsWithGroupId { /* * If */ - NSString *batchKeyToDelete = [kMSTestGroupId stringByAppendingString:MS_UUID_STRING]; [self.sut.batches setObject:@[ @"27", @"35" ] forKey:batchKeyToDelete]; diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index e659036d9c..55700bd8d0 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -119,8 +119,20 @@ - (void)deleteLogsWithBatchId:(NSString *)batchId groupId:(NSString *)groupId { } - (NSDictionary> *)getLogsFromDBWithGroupId:(NSString *)groupId limit:(NSUInteger)limit { - NSString *selectLogQuery = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@ == '%@' LIMIT %lu", kMSLogTableName, - kMSGroupIdColumnName, groupId, (unsigned long)limit]; + + // Get ids from batches. + NSMutableArray *idsInBatches; + for (NSString *batchKey in [self.batches allKeys]) { + if ([batchKey hasPrefix:groupId]) { + [idsInBatches addObjectsFromArray:(NSArray * _Nonnull)self.batches[batchKey]]; + } + } + + // Get logs from DB that are not already part of a batch. + NSString *selectLogQuery = + [NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@ == '%@' AND %@ NOT IN ('%@') LIMIT %lu", kMSLogTableName, + kMSGroupIdColumnName, groupId, kMSIdColumnName, + [idsInBatches componentsJoinedByString:@"','"], (unsigned long)limit]; return [self getLogsFromDBWithQuery:selectLogQuery]; } From 15f612df5dfbfee02c27bc8b3da25b180056abfc Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Tue, 13 Jun 2017 15:37:45 -0700 Subject: [PATCH 23/76] Limit the DB storage to 300 logs, oldest log is removed if over quota --- MobileCenter/MSDBStorageTests.m | 69 +++++++++++++++++-- .../Internals/Channel/MSLogManagerDefault.m | 6 +- .../Channel/MSLogManagerDefaultPrivate.h | 2 + .../Internals/Storage/MSDBStorage.m | 49 ++++++++++--- .../Internals/Storage/MSDBStoragePrivate.h | 6 +- .../Internals/Storage/MSDatabaseConnection.h | 2 +- .../Internals/Storage/MSSqliteConnection.m | 2 +- .../Internals/Storage/MSStorage.h | 11 +++ 8 files changed, 126 insertions(+), 21 deletions(-) diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m index e8f06f3a4a..793e152789 100644 --- a/MobileCenter/MSDBStorageTests.m +++ b/MobileCenter/MSDBStorageTests.m @@ -2,12 +2,14 @@ #import #import -#import "MSAbstractLog.h" +#import "MSAbstractLogInternal.h" #import "MSDBStoragePrivate.h" #import "MSDatabaseConnection.h" #import "MSUtility.h" +#import "MSUtility+Date.h" static NSString *const kMSTestGroupId = @"TestGroupId"; +static short const kMSTestMaxCapacity = 50; static NSString *const kMSAnotherTestGroupId = @"AnotherGroupId"; @interface MSDBStorageTests : XCTestCase @@ -22,7 +24,7 @@ @implementation MSDBStorageTests #pragma mark - Setup - (void)setUp { [super setUp]; - self.sut = [MSDBStorage new]; + self.sut = [[MSDBStorage alloc]initWithCapacity:kMSTestMaxCapacity]; self.dbConnectionMock = OCMProtocolMock(@protocol(MSDatabaseConnection)); self.sut.connection = self.dbConnectionMock; } @@ -31,7 +33,7 @@ - (void)testLoadTooManyLogs { // If NSUInteger expectedLogsCount = 5; - OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]) + OCMStub([self.dbConnectionMock selectDataFromDB:[OCMArg any]]) .andReturn([self generateSerializedLogsWithCount:expectedLogsCount + 1]); // When @@ -51,7 +53,7 @@ - (void)testLoadJustEnoughLogs { // If NSUInteger expectedLogsCount = 5; - OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]) + OCMStub([self.dbConnectionMock selectDataFromDB:[OCMArg any]]) .andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); // When @@ -72,7 +74,7 @@ - (void)testLoadNotEnoughLogs { // If NSUInteger expectedLogsCount = 2; NSUInteger limit = 5; - OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]) + OCMStub([self.dbConnectionMock selectDataFromDB:[OCMArg any]]) .andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); // When @@ -92,7 +94,7 @@ - (void)testLoadUnlimitedLogs { // If NSUInteger expectedLogsCount = 42; - OCMStub([self.dbConnectionMock loadDataFromDB:[OCMArg any]]) + OCMStub([self.dbConnectionMock selectDataFromDB:[OCMArg any]]) .andReturn([self generateSerializedLogsWithCount:expectedLogsCount]); // When @@ -275,6 +277,61 @@ - (void)testDeleteLogsWithBatchId { assertThatInteger(self.sut.batches.count, equalToInteger(0)); } +- (void)testStorageCapacity { + + // If + short expectedCapacity = 3; + __block BOOL deletionHappened = NO; + self.sut = [[MSDBStorage alloc]initWithCapacity:expectedCapacity]; + self.sut.connection = self.dbConnectionMock; + NSString *unExpectedQuery = [NSString stringWithFormat:@"DELETE FROM %@ WHERE groupId = '%@' ORDER BY id ASC LIMIT 1", kMSLogTableName, + kMSTestGroupId]; + OCMStub([self.dbConnectionMock executeQuery:[OCMArg any]]).andDo(^(NSInvocation *invocation) { + NSString *query; + [invocation getArgument:&query atIndex:2]; + BOOL returnValue = YES; + if ([query isEqualToString:unExpectedQuery]) { + deletionHappened = YES; + } + [invocation setReturnValue:&returnValue]; + }); + OCMStub([self.dbConnectionMock selectDataFromDB:[OCMArg any]]) + .andReturn(@[@[@"2"]]); + + // When + [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; + + // Then + assertThatBool(deletionHappened, isFalse()); + + // If + expectedCapacity = 2; + self.sut = [[MSDBStorage alloc]initWithCapacity:expectedCapacity]; + self.sut.connection = self.dbConnectionMock; + + // When + for (short i; i < expectedCapacity; i++){ + [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; + } + + // Then + assertThatBool(deletionHappened, isFalse()); + + + // If + expectedCapacity = 1; + self.sut = [[MSDBStorage alloc]initWithCapacity:expectedCapacity]; + self.sut.connection = self.dbConnectionMock; + + // When + for (short i; i < expectedCapacity; i++){ + [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; + } + + // Then + assertThatBool(deletionHappened, isTrue()); +} + - (NSArray *> *)generateSerializedLogsWithCount:(NSUInteger)count { NSMutableArray *> *logs = [NSMutableArray arrayWithCapacity:count]; for (NSUInteger i = 0; i < count; ++i) { diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m index da418a4b54..47f5eafbe9 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m @@ -9,7 +9,7 @@ #import "MSMobileCenterInternal.h" #import "MobileCenter+Internal.h" -static char *const MSlogsDispatchQueue = "com.microsoft.azure.mobile.mobilecenter.LogManagerQueue"; +static char *const kMSlogsDispatchQueue = "com.microsoft.azure.mobile.mobilecenter.LogManagerQueue"; /** * Private declaration of the log manager. @@ -31,13 +31,13 @@ - (instancetype)initWithAppSecret:(NSString *)appSecret installId:(NSUUID *)inst self = [self initWithSender:[[MSIngestionSender alloc] initWithBaseUrl:logUrl appSecret:appSecret installId:[installId UUIDString]] - storage:[MSDBStorage new]]; + storage:[[MSDBStorage alloc] initWithCapacity:kMSStorageMaxCapacity]]; return self; } - (instancetype)initWithSender:(MSHttpSender *)sender storage:(id)storage { if ((self = [self init])) { - dispatch_queue_t serialQueue = dispatch_queue_create(MSlogsDispatchQueue, DISPATCH_QUEUE_SERIAL); + dispatch_queue_t serialQueue = dispatch_queue_create(kMSlogsDispatchQueue, DISPATCH_QUEUE_SERIAL); _enabled = YES; _logsDispatchQueue = serialQueue; _channels = [NSMutableDictionary> new]; diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefaultPrivate.h b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefaultPrivate.h index 4505230e52..5eab2e248c 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefaultPrivate.h +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefaultPrivate.h @@ -9,6 +9,8 @@ NS_ASSUME_NONNULL_BEGIN +static short *const kMSStorageMaxCapacity = 300; + @class MSHttpSender; @interface MSLogManagerDefault () diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 55700bd8d0..2bc9822852 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -14,31 +14,48 @@ - (instancetype)init { if (self) { _connection = [[MSSqliteConnection alloc] initWithDatabaseFilename:kMSDBFileName]; _batches = [NSMutableDictionary *> new]; - [self initTables]; + _capacity = NSIntegerMax; + + // Create the DB. + NSString *createLogTableQuery = [NSString + stringWithFormat: + @"CREATE TABLE IF NOT EXISTS %@ (%@ INTEGER PRIMARY KEY AUTOINCREMENT, %@ TEXT NOT NULL, %@ TEXT NOT NULL);", + kMSLogTableName, kMSIdColumnName, kMSGroupIdColumnName, kMSDataColumnName]; + [self.connection executeQuery:createLogTableQuery]; } return self; } -- (void)initTables { - NSString *createLogTableQuery = [NSString - stringWithFormat: - @"CREATE TABLE IF NOT EXISTS %@ (%@ INTEGER PRIMARY KEY AUTOINCREMENT, %@ TEXT NOT NULL, %@ TEXT NOT NULL);", - kMSLogTableName, kMSIdColumnName, kMSGroupIdColumnName, kMSDataColumnName]; - [self.connection executeQuery:createLogTableQuery]; +- (instancetype)initWithCapacity:(NSInteger)capacity { + self = [self init]; + if (self) { + _capacity = capacity; + } + return self; } #pragma mark - Save logs - (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { + BOOL succeeded = NO; if (!log) { return NO; } + + // Insert this log to the DB. NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *addLogQuery = [NSString stringWithFormat:@"INSERT INTO %@ ('%@', '%@') VALUES ('%@', '%@')", kMSLogTableName, kMSGroupIdColumnName, kMSDataColumnName, groupId, base64Data]; - return [self.connection executeQuery:addLogQuery]; + succeeded = [self.connection executeQuery:addLogQuery]; + + // Max out DB. + if (succeeded && [self countLogsWithGroupId:groupId] > self.capacity){ + [self deleteOldestLogWithGroupId:groupId]; + MSLogDebug([MSMobileCenter logTag],@"Log storage reached its maximum capacity, oldest log deleted."); + } + return succeeded; } #pragma mark - Load logs @@ -137,7 +154,7 @@ - (void)deleteLogsWithBatchId:(NSString *)batchId groupId:(NSString *)groupId { } - (NSDictionary> *)getLogsFromDBWithQuery:(NSString *)query { - NSArray *> *result = [self.connection loadDataFromDB:query]; + NSArray *> *result = [self.connection selectDataFromDB:query]; NSMutableDictionary> *logs = [NSMutableDictionary> new]; // Get logs from DB. @@ -191,4 +208,18 @@ - (void)deleteLogsFromDBWithColumnValues:(NSArray *)columnValues col } } +- (void)deleteOldestLogWithGroupId:(NSString *)groupId { + NSString *deleteLogQuery = + [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = '%@' ORDER BY %@ ASC LIMIT 1", kMSLogTableName, kMSGroupIdColumnName, groupId, kMSIdColumnName]; + [self.connection executeQuery:deleteLogQuery]; +} + +#pragma mark - DB count + +- (NSInteger)countLogsWithGroupId:(NSString*) groupId{ + NSString *countLogQuery = [NSString stringWithFormat:@"SELECT COUNT(*) FROM %@ WHERE %@ = '%@'", kMSLogTableName, kMSGroupIdColumnName, groupId]; + NSArray *> *result = [self.connection selectDataFromDB:countLogQuery]; + return [result[0][0] integerValue]; +} + @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h index 0e65f43dce..205c1fc54c 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h @@ -11,6 +11,11 @@ static NSString *const kMSDataColumnName = @"data"; @interface MSDBStorage () +/** + * Maximum allowed capacity in this storage. + */ +@property(nonatomic, readonly) NSInteger capacity; + /** * Connection to SQLite database. */ @@ -27,7 +32,6 @@ static NSString *const kMSDataColumnName = @"data"; * @param groupId The key used for grouping logs. * * @return Logs corresponding to the given group Id from the storage. - * */ - (NSDictionary> *)getLogsFromDBWithGroupId:(NSString *)groupId; diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h index 4a0537f338..1d1ea29ea5 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h @@ -5,6 +5,6 @@ - (instancetype)initWithDatabaseFilename:(NSString *)dbFilename; - (BOOL)executeQuery:(NSString *)query; -- (NSArray *> *)loadDataFromDB:(NSString *)query; +- (NSArray *> *)selectDataFromDB:(NSString *)query; @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index 427307632f..d85ccf376c 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -182,7 +182,7 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { #pragma mark - Public method implementation -- (NSArray *> *)loadDataFromDB:(NSString *)query { +- (NSArray *> *)selectDataFromDB:(NSString *)query { // Run the query and indicate that is not executable. // The query string is converted to a char* object. diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h index f327c517c0..747b319b1b 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h @@ -19,6 +19,17 @@ typedef void (^MSLoadDataCompletionBlock)(NSArray> *_Nullable logArray @required +/** + * Create a storage with a capacity. + * + * @param capacity Maximum allowed capacity in this storage. + * + * @return Return an instance of this storage. + * + * @discussion The storage removes the oldest log whenever its capacity goes over limit. + */ +- (instancetype)initWithCapacity:(NSInteger)capacity; + /** * Store a log. * From 7fdcb8aaf4a0959b265d350b0b485b1dd3829154 Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Tue, 13 Jun 2017 16:10:30 -0700 Subject: [PATCH 24/76] Wording and formatting --- MobileCenter/MSDBStorageTests.m | 36 +++++++++---------- .../Channel/MSLogManagerDefaultPrivate.h | 2 +- .../Internals/Storage/MSDBStorage.m | 28 ++++++++------- .../Internals/Storage/MSDatabaseConnection.h | 22 ++++++++++++ .../Internals/Storage/MSSqliteConnection.m | 4 +-- 5 files changed, 57 insertions(+), 35 deletions(-) diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m index 793e152789..aa7eb96ccf 100644 --- a/MobileCenter/MSDBStorageTests.m +++ b/MobileCenter/MSDBStorageTests.m @@ -24,7 +24,7 @@ @implementation MSDBStorageTests #pragma mark - Setup - (void)setUp { [super setUp]; - self.sut = [[MSDBStorage alloc]initWithCapacity:kMSTestMaxCapacity]; + self.sut = [[MSDBStorage alloc] initWithCapacity:kMSTestMaxCapacity]; self.dbConnectionMock = OCMProtocolMock(@protocol(MSDatabaseConnection)); self.sut.connection = self.dbConnectionMock; } @@ -278,14 +278,14 @@ - (void)testDeleteLogsWithBatchId { } - (void)testStorageCapacity { - + // If short expectedCapacity = 3; __block BOOL deletionHappened = NO; - self.sut = [[MSDBStorage alloc]initWithCapacity:expectedCapacity]; + self.sut = [[MSDBStorage alloc] initWithCapacity:expectedCapacity]; self.sut.connection = self.dbConnectionMock; - NSString *unExpectedQuery = [NSString stringWithFormat:@"DELETE FROM %@ WHERE groupId = '%@' ORDER BY id ASC LIMIT 1", kMSLogTableName, - kMSTestGroupId]; + NSString *unExpectedQuery = [NSString + stringWithFormat:@"DELETE FROM %@ WHERE groupId = '%@' ORDER BY id ASC LIMIT 1", kMSLogTableName, kMSTestGroupId]; OCMStub([self.dbConnectionMock executeQuery:[OCMArg any]]).andDo(^(NSInvocation *invocation) { NSString *query; [invocation getArgument:&query atIndex:2]; @@ -295,39 +295,37 @@ - (void)testStorageCapacity { } [invocation setReturnValue:&returnValue]; }); - OCMStub([self.dbConnectionMock selectDataFromDB:[OCMArg any]]) - .andReturn(@[@[@"2"]]); + OCMStub([self.dbConnectionMock selectDataFromDB:[OCMArg any]]).andReturn(@[ @[ @"2" ] ]); // When [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; - + // Then assertThatBool(deletionHappened, isFalse()); - + // If expectedCapacity = 2; - self.sut = [[MSDBStorage alloc]initWithCapacity:expectedCapacity]; + self.sut = [[MSDBStorage alloc] initWithCapacity:expectedCapacity]; self.sut.connection = self.dbConnectionMock; - + // When - for (short i; i < expectedCapacity; i++){ + for (short i; i < expectedCapacity; i++) { [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; } - + // Then assertThatBool(deletionHappened, isFalse()); - - + // If expectedCapacity = 1; - self.sut = [[MSDBStorage alloc]initWithCapacity:expectedCapacity]; + self.sut = [[MSDBStorage alloc] initWithCapacity:expectedCapacity]; self.sut.connection = self.dbConnectionMock; - + // When - for (short i; i < expectedCapacity; i++){ + for (short i; i < expectedCapacity; i++) { [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; } - + // Then assertThatBool(deletionHappened, isTrue()); } diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefaultPrivate.h b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefaultPrivate.h index 5eab2e248c..78b53afda6 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefaultPrivate.h +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefaultPrivate.h @@ -24,7 +24,7 @@ static short *const kMSStorageMaxCapacity = 300; * * @return A new `MSLogManager` instance. */ -- (instancetype)initWithSender:(MSHttpSender *)sender storage:(id )storage; +- (instancetype)initWithSender:(MSHttpSender *)sender storage:(id)storage; @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 2bc9822852..056b8d5f19 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -15,12 +15,12 @@ - (instancetype)init { _connection = [[MSSqliteConnection alloc] initWithDatabaseFilename:kMSDBFileName]; _batches = [NSMutableDictionary *> new]; _capacity = NSIntegerMax; - + // Create the DB. - NSString *createLogTableQuery = [NSString - stringWithFormat: - @"CREATE TABLE IF NOT EXISTS %@ (%@ INTEGER PRIMARY KEY AUTOINCREMENT, %@ TEXT NOT NULL, %@ TEXT NOT NULL);", - kMSLogTableName, kMSIdColumnName, kMSGroupIdColumnName, kMSDataColumnName]; + NSString *createLogTableQuery = + [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@ (%@ INTEGER PRIMARY KEY AUTOINCREMENT, %@ TEXT NOT " + @"NULL, %@ TEXT NOT NULL);", + kMSLogTableName, kMSIdColumnName, kMSGroupIdColumnName, kMSDataColumnName]; [self.connection executeQuery:createLogTableQuery]; } return self; @@ -41,7 +41,7 @@ - (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { if (!log) { return NO; } - + // Insert this log to the DB. NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; @@ -49,11 +49,11 @@ - (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { [NSString stringWithFormat:@"INSERT INTO %@ ('%@', '%@') VALUES ('%@', '%@')", kMSLogTableName, kMSGroupIdColumnName, kMSDataColumnName, groupId, base64Data]; succeeded = [self.connection executeQuery:addLogQuery]; - + // Max out DB. - if (succeeded && [self countLogsWithGroupId:groupId] > self.capacity){ + if (succeeded && [self countLogsWithGroupId:groupId] > self.capacity) { [self deleteOldestLogWithGroupId:groupId]; - MSLogDebug([MSMobileCenter logTag],@"Log storage reached its maximum capacity, oldest log deleted."); + MSLogDebug([MSMobileCenter logTag], @"Log storage reached its maximum capacity, oldest log deleted."); } return succeeded; } @@ -202,7 +202,7 @@ - (void)deleteLogsFromDBWithColumnValues:(NSArray *)columnValues col // Execute. if ([self.connection executeQuery:deleteLogsQuery]) { - MSLogVerbose([MSMobileCenter logTag], @"%@ %@", deletionTrace, @"succeded"); + MSLogVerbose([MSMobileCenter logTag], @"%@ %@", deletionTrace, @"succeeded"); } else { MSLogError([MSMobileCenter logTag], @"%@ %@", deletionTrace, @"failed"); } @@ -210,14 +210,16 @@ - (void)deleteLogsFromDBWithColumnValues:(NSArray *)columnValues col - (void)deleteOldestLogWithGroupId:(NSString *)groupId { NSString *deleteLogQuery = - [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = '%@' ORDER BY %@ ASC LIMIT 1", kMSLogTableName, kMSGroupIdColumnName, groupId, kMSIdColumnName]; + [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = '%@' ORDER BY %@ ASC LIMIT 1", kMSLogTableName, + kMSGroupIdColumnName, groupId, kMSIdColumnName]; [self.connection executeQuery:deleteLogQuery]; } #pragma mark - DB count -- (NSInteger)countLogsWithGroupId:(NSString*) groupId{ - NSString *countLogQuery = [NSString stringWithFormat:@"SELECT COUNT(*) FROM %@ WHERE %@ = '%@'", kMSLogTableName, kMSGroupIdColumnName, groupId]; +- (NSInteger)countLogsWithGroupId:(NSString *)groupId { + NSString *countLogQuery = [NSString + stringWithFormat:@"SELECT COUNT(*) FROM %@ WHERE %@ = '%@'", kMSLogTableName, kMSGroupIdColumnName, groupId]; NSArray *> *result = [self.connection selectDataFromDB:countLogQuery]; return [result[0][0] integerValue]; } diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h index 1d1ea29ea5..5d035de434 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDatabaseConnection.h @@ -2,9 +2,31 @@ @protocol MSDatabaseConnection +/** + * Create a storage with a capacity. + * + * @param dbFilename Database filename. + * + * @return Return an instance of database connection. + */ - (instancetype)initWithDatabaseFilename:(NSString *)dbFilename; +/** + * Execute the given SQL query on the database. + * + * @param query SQL query to execute. + * + * @return Return `YES` if the query executed successfully, `NO` otherwise. + */ - (BOOL)executeQuery:(NSString *)query; + +/** + * Select data from the database. + * + * @param query Select query to execute. + * + * @return An array representing the data in the database. + */ - (NSArray *> *)selectDataFromDB:(NSString *)query; @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m index d85ccf376c..6ecf484f49 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSSqliteConnection.m @@ -153,7 +153,7 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { // Keep the last inserted row ID. self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database); } else { - + // If could not execute the query show the error message on the debugger. NSString *errorMsg = [NSString stringWithUTF8String:sqlite3_errmsg(sqlite3Database)]; MSLogError(MSMobileCenter.logTag, @"DB Error: %@\nerror code: %d", errorMsg, executeQueryResults); @@ -164,7 +164,7 @@ - (BOOL)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable { // In the database cannot be opened then show the error message on the debugger. NSString *errorMsg = [NSString stringWithUTF8String:sqlite3_errmsg(sqlite3Database)]; - MSLogError(MSMobileCenter.logTag,@"DB Error: %@\nquery: %@", errorMsg, [NSString stringWithUTF8String:query]); + MSLogError(MSMobileCenter.logTag, @"DB Error: %@\nquery: %@", errorMsg, [NSString stringWithUTF8String:query]); result = NO; } From d900afbd3aa7ad95e78600f82bb9f323bc0a5547 Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Wed, 14 Jun 2017 16:49:55 -0700 Subject: [PATCH 25/76] Delete all the overflow logs from the storage instead of 1 by 1 --- MobileCenter/MSDBStorageTests.m | 94 +++++++++++++++---- .../Internals/Storage/MSDBStorage.m | 18 ++-- 2 files changed, 84 insertions(+), 28 deletions(-) diff --git a/MobileCenter/MSDBStorageTests.m b/MobileCenter/MSDBStorageTests.m index aa7eb96ccf..f39ae144b5 100644 --- a/MobileCenter/MSDBStorageTests.m +++ b/MobileCenter/MSDBStorageTests.m @@ -279,55 +279,109 @@ - (void)testDeleteLogsWithBatchId { - (void)testStorageCapacity { - // If + /** + * If + */ + + // Test just below the limit. short expectedCapacity = 3; __block BOOL deletionHappened = NO; + __block int overflowCount = 0; + __block int logCount = 2; self.sut = [[MSDBStorage alloc] initWithCapacity:expectedCapacity]; self.sut.connection = self.dbConnectionMock; - NSString *unExpectedQuery = [NSString - stringWithFormat:@"DELETE FROM %@ WHERE groupId = '%@' ORDER BY id ASC LIMIT 1", kMSLogTableName, kMSTestGroupId]; + NSString *deleteQuery = [NSString + stringWithFormat:@"DELETE FROM %@ WHERE groupId = '%@' ORDER BY id ASC LIMIT", kMSLogTableName, kMSTestGroupId]; + + // Capture the overflow count and the query. OCMStub([self.dbConnectionMock executeQuery:[OCMArg any]]).andDo(^(NSInvocation *invocation) { NSString *query; - [invocation getArgument:&query atIndex:2]; BOOL returnValue = YES; - if ([query isEqualToString:unExpectedQuery]) { + [invocation retainArguments]; + [invocation getArgument:&query atIndex:2]; + if ([query hasPrefix:deleteQuery]) { + overflowCount = [[query stringByReplacingOccurrencesOfString:deleteQuery withString:@""] intValue]; deletionHappened = YES; } [invocation setReturnValue:&returnValue]; }); - OCMStub([self.dbConnectionMock selectDataFromDB:[OCMArg any]]).andReturn(@[ @[ @"2" ] ]); + + // Setup variable log count. + OCMStub([self.dbConnectionMock selectDataFromDB:[OCMArg any]]).andDo(^(NSInvocation *invocation) { + NSArray *> * returnValue = @[@[[@(logCount) stringValue]]]; + [invocation retainArguments]; + [invocation setReturnValue:&returnValue]; + }); - // When + /** + * When + */ [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; - // Then + /** + * Then + */ assertThatBool(deletionHappened, isFalse()); + + // Test at the limit. - // If + /** + * If + */ expectedCapacity = 2; self.sut = [[MSDBStorage alloc] initWithCapacity:expectedCapacity]; self.sut.connection = self.dbConnectionMock; - // When - for (short i; i < expectedCapacity; i++) { - [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; - } + /** + * When + */ + [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; - // Then + /** + * Then + */ assertThatBool(deletionHappened, isFalse()); + + // Test just over the limit. - // If + /** + * If + */ expectedCapacity = 1; self.sut = [[MSDBStorage alloc] initWithCapacity:expectedCapacity]; self.sut.connection = self.dbConnectionMock; - // When - for (short i; i < expectedCapacity; i++) { - [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; - } + /** + * When + */ + [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; - // Then + /** + * Then + */ + assertThatBool(deletionHappened, isTrue()); + assertThatInt(overflowCount, equalToInt(1)); + + // Test way over the limit. + + /** + * If + */ + logCount = 10; + expectedCapacity = 1; + self.sut = [[MSDBStorage alloc] initWithCapacity:expectedCapacity]; + self.sut.connection = self.dbConnectionMock; + + /** + * When + */ + [self.sut saveLog:[MSAbstractLog new] withGroupId:kMSTestGroupId]; + + /** + * Then + */ assertThatBool(deletionHappened, isTrue()); + assertThatInt(overflowCount, equalToInt(9)); } - (NSArray *> *)generateSerializedLogsWithCount:(NSUInteger)count { diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index 056b8d5f19..a8f5fba083 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -37,7 +37,6 @@ - (instancetype)initWithCapacity:(NSInteger)capacity { #pragma mark - Save logs - (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { - BOOL succeeded = NO; if (!log) { return NO; } @@ -48,12 +47,15 @@ - (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { NSString *addLogQuery = [NSString stringWithFormat:@"INSERT INTO %@ ('%@', '%@') VALUES ('%@', '%@')", kMSLogTableName, kMSGroupIdColumnName, kMSDataColumnName, groupId, base64Data]; - succeeded = [self.connection executeQuery:addLogQuery]; + BOOL succeeded = [self.connection executeQuery:addLogQuery]; + NSInteger logCount = [self countLogsWithGroupId:groupId]; // Max out DB. - if (succeeded && [self countLogsWithGroupId:groupId] > self.capacity) { - [self deleteOldestLogWithGroupId:groupId]; - MSLogDebug([MSMobileCenter logTag], @"Log storage reached its maximum capacity, oldest log deleted."); + if (succeeded && logCount > self.capacity) { + NSInteger overflowCount = logCount - self.capacity; + [self deleteOldestLogsWithGroupId:groupId count:overflowCount]; + MSLogDebug([MSMobileCenter logTag], @"Log storage was over capacity, %ld oldest log(s) deleted.", + (long)overflowCount); } return succeeded; } @@ -208,10 +210,10 @@ - (void)deleteLogsFromDBWithColumnValues:(NSArray *)columnValues col } } -- (void)deleteOldestLogWithGroupId:(NSString *)groupId { +- (void)deleteOldestLogsWithGroupId:(NSString *)groupId count:(NSInteger)count { NSString *deleteLogQuery = - [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = '%@' ORDER BY %@ ASC LIMIT 1", kMSLogTableName, - kMSGroupIdColumnName, groupId, kMSIdColumnName]; + [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = '%@' ORDER BY %@ ASC LIMIT %ld", kMSLogTableName, + kMSGroupIdColumnName, groupId, kMSIdColumnName, (long)count]; [self.connection executeQuery:deleteLogQuery]; } From e3c75417733bee4538e80a30bf1642b9f98b39bd Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Wed, 14 Jun 2017 18:30:57 -0700 Subject: [PATCH 26/76] Use NSUInteger for capacity, same as limit + fix unit tests --- .../Internals/Storage/MSDBStorage.m | 14 ++-- .../Internals/Storage/MSDBStoragePrivate.h | 2 +- .../Internals/Storage/MSStorage.h | 2 +- .../MSStoragePerfomanceTests.m | 70 ++++++++----------- 4 files changed, 41 insertions(+), 47 deletions(-) diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m index a8f5fba083..fe507c59f9 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStorage.m @@ -14,7 +14,7 @@ - (instancetype)init { if (self) { _connection = [[MSSqliteConnection alloc] initWithDatabaseFilename:kMSDBFileName]; _batches = [NSMutableDictionary *> new]; - _capacity = NSIntegerMax; + _capacity = NSUIntegerMax; // Create the DB. NSString *createLogTableQuery = @@ -26,7 +26,7 @@ - (instancetype)init { return self; } -- (instancetype)initWithCapacity:(NSInteger)capacity { +- (instancetype)initWithCapacity:(NSUInteger)capacity { self = [self init]; if (self) { _capacity = capacity; @@ -48,11 +48,11 @@ - (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { [NSString stringWithFormat:@"INSERT INTO %@ ('%@', '%@') VALUES ('%@', '%@')", kMSLogTableName, kMSGroupIdColumnName, kMSDataColumnName, groupId, base64Data]; BOOL succeeded = [self.connection executeQuery:addLogQuery]; - NSInteger logCount = [self countLogsWithGroupId:groupId]; + NSUInteger logCount = [self countLogsWithGroupId:groupId]; // Max out DB. if (succeeded && logCount > self.capacity) { - NSInteger overflowCount = logCount - self.capacity; + NSUInteger overflowCount = logCount - self.capacity; [self deleteOldestLogsWithGroupId:groupId count:overflowCount]; MSLogDebug([MSMobileCenter logTag], @"Log storage was over capacity, %ld oldest log(s) deleted.", (long)overflowCount); @@ -219,11 +219,13 @@ - (void)deleteOldestLogsWithGroupId:(NSString *)groupId count:(NSInteger)count { #pragma mark - DB count -- (NSInteger)countLogsWithGroupId:(NSString *)groupId { +- (NSUInteger)countLogsWithGroupId:(NSString *)groupId { NSString *countLogQuery = [NSString stringWithFormat:@"SELECT COUNT(*) FROM %@ WHERE %@ = '%@'", kMSLogTableName, kMSGroupIdColumnName, groupId]; NSArray *> *result = [self.connection selectDataFromDB:countLogQuery]; - return [result[0][0] integerValue]; + NSNumberFormatter *formatter = [NSNumberFormatter new]; + formatter.numberStyle = NSNumberFormatterDecimalStyle; + return [formatter numberFromString:result[0][0]].unsignedIntegerValue; } @end diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h index 205c1fc54c..84c6804352 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSDBStoragePrivate.h @@ -14,7 +14,7 @@ static NSString *const kMSDataColumnName = @"data"; /** * Maximum allowed capacity in this storage. */ -@property(nonatomic, readonly) NSInteger capacity; +@property(nonatomic, readonly) NSUInteger capacity; /** * Connection to SQLite database. diff --git a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h index 747b319b1b..d449b6961d 100644 --- a/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h +++ b/MobileCenter/MobileCenter/Internals/Storage/MSStorage.h @@ -28,7 +28,7 @@ typedef void (^MSLoadDataCompletionBlock)(NSArray> *_Nullable logArray * * @discussion The storage removes the oldest log whenever its capacity goes over limit. */ -- (instancetype)initWithCapacity:(NSInteger)capacity; +- (instancetype)initWithCapacity:(NSUInteger)capacity; /** * Store a log. diff --git a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m index dd13bf0e9f..cde72be447 100644 --- a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m +++ b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m @@ -2,8 +2,9 @@ #import "MSStartServiceLog.h" #import "MSDBStorage.h" -static const int numLogs = 100; -static const int numServices = 100; +static const int kMSNumLogs = 50; +static const int kMSNumServices = 5; +static NSString *const kMSTestGroupId = @"TestGroupId"; @interface MSStoragePerfomanceTests : XCTestCase @end @@ -20,48 +21,42 @@ @implementation MSStoragePerfomanceTests - (void)setUp { [super setUp]; - self.dbStorage = [MSDBStorage new]; } - (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; - - [self.dbStorage deleteLogsWithGroupId:@"anyKey"]; + [self.dbStorage deleteLogsWithGroupId:kMSTestGroupId]; } #pragma mark - Database storage tests - (void)testDatabaseWriteShortLogsPerformance { NSArray *arrayOfLogs = - [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; - + [self generateLogsWithShortServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:@"anyKey"]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; } }]; } - (void)testDatabaseWriteLongLogsPerformance { NSArray *arrayOfLogs = - [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; - + [self generateLogsWithLongServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:@"anyKey"]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; } }]; } - (void)testDatabaseWriteVeryLongLogsPerformance { NSArray *arrayOfLogs = - [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; - + [self generateLogsWithVeryLongServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:@"anyKey"]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; } }]; } @@ -70,80 +65,77 @@ - (void)testDatabaseWriteVeryLongLogsPerformance { - (void)testFileStorageWriteShortLogsPerformance { NSArray *arrayOfLogs = - [self generateLogsWithShortServicesNames:numLogs withNumService:numServices]; - + [self generateLogsWithShortServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:@"anyKey"]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; } }]; } - (void)testFileStorageWriteLongLogsPerformance { NSArray *arrayOfLogs = - [self generateLogsWithLongServicesNames:numLogs withNumService:numServices]; - + [self generateLogsWithLongServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:@"anyKey"]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; } }]; } - (void)testFileStorageWriteVeryLongLogsPerformance { NSArray *arrayOfLogs = - [self generateLogsWithVeryLongServicesNames:numLogs withNumService:numServices]; - + [self generateLogsWithVeryLongServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:@"anyKey"]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; } }]; } #pragma mark - Private -- (NSArray *)generateLogsWithShortServicesNames:(int)numLogs withNumService:(int)numServices { +- (NSArray *)generateLogsWithShortServicesNames:(int)kMSNumLogs withNumService:(int)kMSNumServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < numLogs; ++i) { + for (int i = 0; i < kMSNumLogs; ++i) { MSStartServiceLog *log = [MSStartServiceLog new]; - log.services = [self generateServicesWithShortNames:numServices]; + log.services = [self generateServicesWithShortNames:kMSNumServices]; [dic addObject:log]; } return dic; } -- (NSArray *)generateLogsWithLongServicesNames:(int)numLogs withNumService:(int)numServices { +- (NSArray *)generateLogsWithLongServicesNames:(int)kMSNumLogs withNumService:(int)kMSNumServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < numLogs; ++i) { + for (int i = 0; i < kMSNumLogs; ++i) { MSStartServiceLog *log = [MSStartServiceLog new]; - log.services = [self generateServicesWithLongNames:numServices]; + log.services = [self generateServicesWithLongNames:kMSNumServices]; [dic addObject:log]; } return dic; } -- (NSArray *)generateLogsWithVeryLongServicesNames:(int)numLogs withNumService:(int)numServices { +- (NSArray *)generateLogsWithVeryLongServicesNames:(int)kMSNumLogs withNumService:(int)kMSNumServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < numLogs; ++i) { + for (int i = 0; i < kMSNumLogs; ++i) { MSStartServiceLog *log = [MSStartServiceLog new]; - log.services = [self generateServicesWithVeryLongNames:numServices]; + log.services = [self generateServicesWithVeryLongNames:kMSNumServices]; [dic addObject:log]; } return dic; } -- (NSArray *)generateServicesWithShortNames:(int)numServices { +- (NSArray *)generateServicesWithShortNames:(int)kMSNumServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < numServices; ++i) { + for (int i = 0; i < kMSNumServices; ++i) { [dic addObject:[[NSUUID UUID] UUIDString]]; } return dic; } -- (NSArray *)generateServicesWithLongNames:(int)numServices { +- (NSArray *)generateServicesWithLongNames:(int)kMSNumServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < numServices; ++i) { + for (int i = 0; i < kMSNumServices; ++i) { NSString *value = @""; for (int j = 0; j < 10; ++j) { value = [value stringByAppendingString:[[NSUUID UUID] UUIDString]]; @@ -153,9 +145,9 @@ - (void)testFileStorageWriteVeryLongLogsPerformance { return dic; } -- (NSArray *)generateServicesWithVeryLongNames:(int)numServices { +- (NSArray *)generateServicesWithVeryLongNames:(int)kMSNumServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < numServices; ++i) { + for (int i = 0; i < kMSNumServices; ++i) { NSString *value = @""; for (int j = 0; j < 50; ++j) { value = [value stringByAppendingString:[[NSUUID UUID] UUIDString]]; From 5634d2b8619b81da3e3bbc819052e85c9afc884b Mon Sep 17 00:00:00 2001 From: Clement Polet Date: Thu, 15 Jun 2017 10:12:13 -0700 Subject: [PATCH 27/76] Fix renaming issue --- .../MSStoragePerfomanceTests.m | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m index cde72be447..94ef76d3e9 100644 --- a/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m +++ b/MobileCenter/MobileCenterTests/MSStoragePerfomanceTests.m @@ -95,47 +95,47 @@ - (void)testFileStorageWriteVeryLongLogsPerformance { #pragma mark - Private -- (NSArray *)generateLogsWithShortServicesNames:(int)kMSNumLogs withNumService:(int)kMSNumServices { +- (NSArray *)generateLogsWithShortServicesNames:(int)numLogs withNumService:(int)numServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < kMSNumLogs; ++i) { + for (int i = 0; i < numLogs; ++i) { MSStartServiceLog *log = [MSStartServiceLog new]; - log.services = [self generateServicesWithShortNames:kMSNumServices]; + log.services = [self generateServicesWithShortNames:numServices]; [dic addObject:log]; } return dic; } -- (NSArray *)generateLogsWithLongServicesNames:(int)kMSNumLogs withNumService:(int)kMSNumServices { +- (NSArray *)generateLogsWithLongServicesNames:(int)numLogs withNumService:(int)numServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < kMSNumLogs; ++i) { + for (int i = 0; i < numLogs; ++i) { MSStartServiceLog *log = [MSStartServiceLog new]; - log.services = [self generateServicesWithLongNames:kMSNumServices]; + log.services = [self generateServicesWithLongNames:numServices]; [dic addObject:log]; } return dic; } -- (NSArray *)generateLogsWithVeryLongServicesNames:(int)kMSNumLogs withNumService:(int)kMSNumServices { +- (NSArray *)generateLogsWithVeryLongServicesNames:(int)numLogs withNumService:(int)numServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < kMSNumLogs; ++i) { + for (int i = 0; i < numLogs; ++i) { MSStartServiceLog *log = [MSStartServiceLog new]; - log.services = [self generateServicesWithVeryLongNames:kMSNumServices]; + log.services = [self generateServicesWithVeryLongNames:numServices]; [dic addObject:log]; } return dic; } -- (NSArray *)generateServicesWithShortNames:(int)kMSNumServices { +- (NSArray *)generateServicesWithShortNames:(int)numServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < kMSNumServices; ++i) { + for (int i = 0; i < numServices; ++i) { [dic addObject:[[NSUUID UUID] UUIDString]]; } return dic; } -- (NSArray *)generateServicesWithLongNames:(int)kMSNumServices { +- (NSArray *)generateServicesWithLongNames:(int)numServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < kMSNumServices; ++i) { + for (int i = 0; i < numServices; ++i) { NSString *value = @""; for (int j = 0; j < 10; ++j) { value = [value stringByAppendingString:[[NSUUID UUID] UUIDString]]; @@ -145,9 +145,9 @@ - (void)testFileStorageWriteVeryLongLogsPerformance { return dic; } -- (NSArray *)generateServicesWithVeryLongNames:(int)kMSNumServices { +- (NSArray *)generateServicesWithVeryLongNames:(int)numServices { NSMutableArray *dic = [NSMutableArray new]; - for (int i = 0; i < kMSNumServices; ++i) { + for (int i = 0; i < numServices; ++i) { NSString *value = @""; for (int j = 0; j < 50; ++j) { value = [value stringByAppendingString:[[NSUUID UUID] UUIDString]]; From 8e578bf83c7e3824614e7621cacd2eef7da07ab0 Mon Sep 17 00:00:00 2001 From: Murat Baysangurov Date: Fri, 16 Jun 2017 00:03:17 +0300 Subject: [PATCH 28/76] MSPropertiesTableDataSource and PropertyViewCell moved to Vendors folder --- Puppet/Puppet.xcodeproj/project.pbxproj | 24 +++++++++---------- .../Utils}/MSPropertiesTableDataSource.h | 0 .../Utils}/MSPropertiesTableDataSource.m | 0 .../Utils}/PropertyViewCell.h | 0 .../Utils}/PropertyViewCell.m | 0 5 files changed, 12 insertions(+), 12 deletions(-) rename {Puppet/Puppet => Vendor/Utils}/MSPropertiesTableDataSource.h (100%) rename {Puppet/Puppet => Vendor/Utils}/MSPropertiesTableDataSource.m (100%) rename {Puppet/Puppet => Vendor/Utils}/PropertyViewCell.h (100%) rename {Puppet/Puppet => Vendor/Utils}/PropertyViewCell.m (100%) diff --git a/Puppet/Puppet.xcodeproj/project.pbxproj b/Puppet/Puppet.xcodeproj/project.pbxproj index 046f58614d..3d39c09fad 100644 --- a/Puppet/Puppet.xcodeproj/project.pbxproj +++ b/Puppet/Puppet.xcodeproj/project.pbxproj @@ -33,8 +33,8 @@ 5CE60DF11EDF0B670060303A /* MSCrashResultViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CE60DF01EDF0B670060303A /* MSCrashResultViewController.m */; }; 6EC99A211D4151A00016C325 /* CrashReporter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC99A201D4151A00016C325 /* CrashReporter.framework */; }; 6EC99A281D4152FA0016C325 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC99A271D4152FA0016C325 /* libc++.tbd */; }; - 806A17EE1EEAD96B00230146 /* PropertyViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 806A17EC1EEAD96B00230146 /* PropertyViewCell.m */; }; - 80AD3DA11EEFF7A600EEECCA /* MSPropertiesTableDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 80AD3DA01EEFF7A600EEECCA /* MSPropertiesTableDataSource.m */; }; + 808A10FC1EF312A000DFD41B /* MSPropertiesTableDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 808A10F91EF312A000DFD41B /* MSPropertiesTableDataSource.m */; }; + 808A10FD1EF312A000DFD41B /* PropertyViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 808A10FB1EF312A000DFD41B /* PropertyViewCell.m */; }; B21B26C71DDE490400FF0378 /* MSCrashesDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B21B26C61DDE490400FF0378 /* MSCrashesDetailViewController.m */; }; B27397A41DE3D27700AC7761 /* CrashLibIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B27397A31DE3D27700AC7761 /* CrashLibIOS.framework */; }; B27397A51DE3D27700AC7761 /* CrashLibIOS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B27397A31DE3D27700AC7761 /* CrashLibIOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -290,10 +290,10 @@ 6EC99A201D4151A00016C325 /* CrashReporter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CrashReporter.framework; path = ../../Vendor/PLCrashReporter/CrashReporter.framework; sourceTree = ""; }; 6EC99A251D4152EB0016C325 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 6EC99A271D4152FA0016C325 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; - 806A17EB1EEAD96B00230146 /* PropertyViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PropertyViewCell.h; sourceTree = ""; }; - 806A17EC1EEAD96B00230146 /* PropertyViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PropertyViewCell.m; sourceTree = ""; }; - 80AD3D9F1EEFF72A00EEECCA /* MSPropertiesTableDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSPropertiesTableDataSource.h; sourceTree = ""; }; - 80AD3DA01EEFF7A600EEECCA /* MSPropertiesTableDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSPropertiesTableDataSource.m; sourceTree = ""; }; + 808A10F81EF312A000DFD41B /* MSPropertiesTableDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSPropertiesTableDataSource.h; path = ../../Vendor/Utils/MSPropertiesTableDataSource.h; sourceTree = ""; }; + 808A10F91EF312A000DFD41B /* MSPropertiesTableDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSPropertiesTableDataSource.m; path = ../../Vendor/Utils/MSPropertiesTableDataSource.m; sourceTree = ""; }; + 808A10FA1EF312A000DFD41B /* PropertyViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PropertyViewCell.h; path = ../../Vendor/Utils/PropertyViewCell.h; sourceTree = ""; }; + 808A10FB1EF312A000DFD41B /* PropertyViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PropertyViewCell.m; path = ../../Vendor/Utils/PropertyViewCell.m; sourceTree = ""; }; B21B26C51DDE490400FF0378 /* MSCrashesDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSCrashesDetailViewController.h; sourceTree = ""; }; B21B26C61DDE490400FF0378 /* MSCrashesDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSCrashesDetailViewController.m; sourceTree = ""; }; B27397A31DE3D27700AC7761 /* CrashLibIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CrashLibIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -430,12 +430,12 @@ 5C2BD1A01ED5DE2100FB20C8 /* Utils */ = { isa = PBXGroup; children = ( - 806A17EB1EEAD96B00230146 /* PropertyViewCell.h */, - 806A17EC1EEAD96B00230146 /* PropertyViewCell.m */, 5C2BD1A11ED5DE4400FB20C8 /* EventLog.h */, 5C2BD1A21ED5DE5700FB20C8 /* EventLog.m */, - 80AD3D9F1EEFF72A00EEECCA /* MSPropertiesTableDataSource.h */, - 80AD3DA01EEFF7A600EEECCA /* MSPropertiesTableDataSource.m */, + 808A10FA1EF312A000DFD41B /* PropertyViewCell.h */, + 808A10FB1EF312A000DFD41B /* PropertyViewCell.m */, + 808A10F81EF312A000DFD41B /* MSPropertiesTableDataSource.h */, + 808A10F91EF312A000DFD41B /* MSPropertiesTableDataSource.m */, ); name = Utils; sourceTree = ""; @@ -905,12 +905,12 @@ B21B26C71DDE490400FF0378 /* MSCrashesDetailViewController.m in Sources */, 5C2BD1A31ED5DE5700FB20C8 /* EventLog.m in Sources */, 5C2BD19B1ED5D44400FB20C8 /* MSAnalyticsResultViewController.m in Sources */, - 80AD3DA11EEFF7A600EEECCA /* MSPropertiesTableDataSource.m in Sources */, B2E611F61DDE72BA00A9DF86 /* MSFakeCXXClass.mm in Sources */, E89E6A2C1D396D7900CAA2CD /* MSCrashesViewController.m in Sources */, - 806A17EE1EEAD96B00230146 /* PropertyViewCell.m in Sources */, + 808A10FC1EF312A000DFD41B /* MSPropertiesTableDataSource.m in Sources */, E89E6A301D39704900CAA2CD /* MSAnalyticsViewController.m in Sources */, E82E1B641D1CA58D00D281C1 /* main.m in Sources */, + 808A10FD1EF312A000DFD41B /* PropertyViewCell.m in Sources */, BA6827C9F0410233C245D989 /* MSDistributeViewController.m in Sources */, 5CE60DF11EDF0B670060303A /* MSCrashResultViewController.m in Sources */, ); diff --git a/Puppet/Puppet/MSPropertiesTableDataSource.h b/Vendor/Utils/MSPropertiesTableDataSource.h similarity index 100% rename from Puppet/Puppet/MSPropertiesTableDataSource.h rename to Vendor/Utils/MSPropertiesTableDataSource.h diff --git a/Puppet/Puppet/MSPropertiesTableDataSource.m b/Vendor/Utils/MSPropertiesTableDataSource.m similarity index 100% rename from Puppet/Puppet/MSPropertiesTableDataSource.m rename to Vendor/Utils/MSPropertiesTableDataSource.m diff --git a/Puppet/Puppet/PropertyViewCell.h b/Vendor/Utils/PropertyViewCell.h similarity index 100% rename from Puppet/Puppet/PropertyViewCell.h rename to Vendor/Utils/PropertyViewCell.h diff --git a/Puppet/Puppet/PropertyViewCell.m b/Vendor/Utils/PropertyViewCell.m similarity index 100% rename from Puppet/Puppet/PropertyViewCell.m rename to Vendor/Utils/PropertyViewCell.m From 726ed90adf80e6c86a7558dc9cdec01fa985d729 Mon Sep 17 00:00:00 2001 From: Murat Baysangurov Date: Fri, 16 Jun 2017 00:04:07 +0300 Subject: [PATCH 29/76] Custom properties added for analytics page in Sasquatch --- Sasquatch/Sasquatch.xcodeproj/project.pbxproj | 24 +++ .../Sasquatch/Base.lproj/Main.storyboard | 153 ++++++++++++++---- .../Sasquatch/MSAnalyticsViewController.swift | 18 ++- .../Sasquatch/Sasquatch-Bridging-Header.h | 2 + 4 files changed, 161 insertions(+), 36 deletions(-) diff --git a/Sasquatch/Sasquatch.xcodeproj/project.pbxproj b/Sasquatch/Sasquatch.xcodeproj/project.pbxproj index 601134ad9e..37d67a5823 100644 --- a/Sasquatch/Sasquatch.xcodeproj/project.pbxproj +++ b/Sasquatch/Sasquatch.xcodeproj/project.pbxproj @@ -18,6 +18,10 @@ 5CE258921EA5F62E00DA8FB9 /* AnalyticsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE258791EA5F5DF00DA8FB9 /* AnalyticsUITests.swift */; }; 5CE258931EA5F62E00DA8FB9 /* CrashesUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE2587B1EA5F5EB00DA8FB9 /* CrashesUITests.swift */; }; 5CE258941EA5F62E00DA8FB9 /* DistributeUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE2587D1EA5F5FC00DA8FB9 /* DistributeUITests.swift */; }; + 808A11021EF3130A00DFD41B /* MSPropertiesTableDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 808A10FF1EF3130A00DFD41B /* MSPropertiesTableDataSource.m */; }; + 808A11031EF3130A00DFD41B /* PropertyViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 808A11011EF3130A00DFD41B /* PropertyViewCell.m */; }; + 808A11041EF31D2500DFD41B /* MSPropertiesTableDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 808A10FF1EF3130A00DFD41B /* MSPropertiesTableDataSource.m */; }; + 808A11051EF31D2800DFD41B /* PropertyViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 808A11011EF3130A00DFD41B /* PropertyViewCell.m */; }; 84226C4B1E88FF4A00798417 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 84226C4A1E88FF4A00798417 /* main.m */; }; 84226C4E1E88FF4A00798417 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 84226C4D1E88FF4A00798417 /* AppDelegate.m */; }; 84226C561E88FF4A00798417 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84226C551E88FF4A00798417 /* Assets.xcassets */; }; @@ -202,6 +206,10 @@ 5CE2587D1EA5F5FC00DA8FB9 /* DistributeUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DistributeUITests.swift; path = SasquatchUITests/DistributeUITests.swift; sourceTree = SOURCE_ROOT; }; 5CE258871EA5F62400DA8FB9 /* SasquatchObjCUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SasquatchObjCUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5CE258961EA5FAE100DA8FB9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 808A10FE1EF3130A00DFD41B /* MSPropertiesTableDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSPropertiesTableDataSource.h; path = ../../Vendor/Utils/MSPropertiesTableDataSource.h; sourceTree = ""; }; + 808A10FF1EF3130A00DFD41B /* MSPropertiesTableDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSPropertiesTableDataSource.m; path = ../../Vendor/Utils/MSPropertiesTableDataSource.m; sourceTree = ""; }; + 808A11001EF3130A00DFD41B /* PropertyViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PropertyViewCell.h; path = ../../Vendor/Utils/PropertyViewCell.h; sourceTree = ""; }; + 808A11011EF3130A00DFD41B /* PropertyViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PropertyViewCell.m; path = ../../Vendor/Utils/PropertyViewCell.m; sourceTree = ""; }; 84226C1C1E88FEFC00798417 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 84226C471E88FF4A00798417 /* SasquatchObjC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SasquatchObjC.app; sourceTree = BUILT_PRODUCTS_DIR; }; 84226C4A1E88FF4A00798417 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -324,6 +332,17 @@ path = SasquatchSwiftUITests; sourceTree = ""; }; + 808A10E81EF30FD600DFD41B /* Utils */ = { + isa = PBXGroup; + children = ( + 808A10FE1EF3130A00DFD41B /* MSPropertiesTableDataSource.h */, + 808A10FF1EF3130A00DFD41B /* MSPropertiesTableDataSource.m */, + 808A11001EF3130A00DFD41B /* PropertyViewCell.h */, + 808A11011EF3130A00DFD41B /* PropertyViewCell.m */, + ); + name = Utils; + sourceTree = ""; + }; 84226C091E88FEFC00798417 = { isa = PBXGroup; children = ( @@ -363,6 +382,7 @@ 849A3E711E8A9922008711CB /* MobileCenterProtocol.swift */, 84226CB11E8909F700798417 /* Storyboards */, 84226CB01E8909EA00798417 /* ViewControllers */, + 808A10E81EF30FD600DFD41B /* Utils */, 84226D081E891FA300798417 /* MSAlertController */, 84226CB21E8909FF00798417 /* Supporting Files */, ); @@ -871,6 +891,8 @@ 84A0FF691E8A87C9000E7A28 /* MSAnalyticsViewController.swift in Sources */, 84226D421E8953F100798417 /* MobileCenterDelegateObjC.m in Sources */, 84A0FF661E8A87B4000E7A28 /* MSCrashesViewController.swift in Sources */, + 808A11031EF3130A00DFD41B /* PropertyViewCell.m in Sources */, + 808A11021EF3130A00DFD41B /* MSPropertiesTableDataSource.m in Sources */, 84226C4E1E88FF4A00798417 /* AppDelegate.m in Sources */, B2B36CE21EEF49B700230341 /* MSCustomPropertiesViewController.swift in Sources */, 84226C4B1E88FF4A00798417 /* main.m in Sources */, @@ -887,9 +909,11 @@ 84A0FF671E8A87B4000E7A28 /* MSCrashesViewController.swift in Sources */, 84A0FF6A1E8A87C9000E7A28 /* MSAnalyticsViewController.swift in Sources */, 84226D3D1E8938D200798417 /* MobileCenterDelegate.swift in Sources */, + 808A11041EF31D2500DFD41B /* MSPropertiesTableDataSource.m in Sources */, B2B36CE31EEF49C400230341 /* MSCustomPropertiesViewController.swift in Sources */, 84226D0E1E891FF100798417 /* MSAlertController.m in Sources */, 845178ED1E8A8B0D004A0A6F /* MSMainViewController.swift in Sources */, + 808A11051EF31D2800DFD41B /* PropertyViewCell.m in Sources */, 84226D3F1E8939B700798417 /* MobileCenterDelegateSwift.swift in Sources */, 84D63FF81E8BA838004C9F86 /* MSCrashesDetailViewController.swift in Sources */, 84A0FF6D1E8A87E1000E7A28 /* MSDistributeViewController.swift in Sources */, diff --git a/Sasquatch/Sasquatch/Base.lproj/Main.storyboard b/Sasquatch/Sasquatch/Base.lproj/Main.storyboard index e57b304103..f45102b711 100644 --- a/Sasquatch/Sasquatch/Base.lproj/Main.storyboard +++ b/Sasquatch/Sasquatch/Base.lproj/Main.storyboard @@ -1,11 +1,12 @@ - + - + + @@ -18,33 +19,44 @@ - + - + - - - + + + - - - + + + + + + - + - - + - - - + + - + - - - - - - + - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -125,7 +211,8 @@ - + + diff --git a/Sasquatch/Sasquatch/MSAnalyticsViewController.swift b/Sasquatch/Sasquatch/MSAnalyticsViewController.swift index 482ddec1df..5aa31f92fc 100644 --- a/Sasquatch/Sasquatch/MSAnalyticsViewController.swift +++ b/Sasquatch/Sasquatch/MSAnalyticsViewController.swift @@ -2,27 +2,39 @@ import UIKit class MSAnalyticsViewController: UITableViewController, MobileCenterProtocol { + @IBOutlet weak var propertiesTable: UITableView! @IBOutlet weak var enabled: UISwitch! var mobileCenter: MobileCenterDelegate! + var propertiesSource: MSPropertiesTableDataSource? override func viewDidLoad() { super.viewDidLoad() self.enabled.isOn = mobileCenter.isAnalyticsEnabled() + propertiesSource = MSPropertiesTableDataSource.init(table: propertiesTable) + } + + @IBAction func onAddProperty() { + propertiesSource?.addNewProperty() + } + + @IBAction func onDeleteProperty() { + propertiesSource?.deleteProperty() } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) + propertiesSource?.updateProperties() switch indexPath.section { - case 0: + case 1: switch indexPath.row { case 0: mobileCenter.trackEvent("myEvent") case 1: - mobileCenter.trackEvent("myEvent", withProperties: ["gender" : "Male", "age" : "20", "title" : "SDE"]) + mobileCenter.trackEvent("myEvent", withProperties: propertiesSource?.properties() as! Dictionary) case 2: mobileCenter.trackPage("myPage") case 3: - mobileCenter.trackPage("myPage", withProperties: ["gender" : "Male", "age" : "28", "title" : "PM"]) + mobileCenter.trackPage("myPage", withProperties: propertiesSource?.properties() as! Dictionary) default: () } break diff --git a/Sasquatch/Sasquatch/Sasquatch-Bridging-Header.h b/Sasquatch/Sasquatch/Sasquatch-Bridging-Header.h index 1ec2465276..6feb5525f6 100644 --- a/Sasquatch/Sasquatch/Sasquatch-Bridging-Header.h +++ b/Sasquatch/Sasquatch/Sasquatch-Bridging-Header.h @@ -4,3 +4,5 @@ #import "CrashLib.h" #import "MSAlertController.h" +#import "PropertyViewCell.h" +#import "MSPropertiesTableDataSource.h" From 9d5ea03a9c9c2824bae54734f8413237399858f6 Mon Sep 17 00:00:00 2001 From: Jae Lim Date: Thu, 15 Jun 2017 15:07:38 -0700 Subject: [PATCH 30/76] Remove channel dependency from services --- .../MobileCenter.xcodeproj/project.pbxproj | 2 +- .../Internals/Channel/MSChannelDelegate.h | 6 +- .../Internals/Channel/MSLogManager.h | 17 +---- .../Internals/Channel/MSLogManagerDefault.h | 5 ++ .../Internals/Channel/MSLogManagerDefault.m | 57 +++++++-------- .../Internals/Channel/MSLogManagerDelegate.h | 70 ++++++++++++++----- .../MSAppDelegateForwarderTests.m | 2 +- .../MSLogManagerDefaultTests.m | 4 +- .../Internals/MSAnalyticsInternal.h | 4 +- .../MobileCenterAnalytics/MSAnalytics.m | 19 ++--- .../MSAnalyticsTests.m | 6 +- .../Internals/MSCrashesPrivate.h | 6 +- .../MobileCenterCrashes/MSCrashes.mm | 19 +---- .../MSCrashesTests.mm | 6 +- 14 files changed, 116 insertions(+), 107 deletions(-) diff --git a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj index 0b3eac151a..0bdcf44165 100644 --- a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj +++ b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj @@ -702,13 +702,13 @@ E84B8E2C1D2351DB006FD231 /* MSLogManagerDefault.h */, E84B8E2D1D2351DB006FD231 /* MSLogManagerDefault.m */, 04FD126E1E415697007ABFE7 /* MSLogManagerDefaultPrivate.h */, + E8AEE9811D5970A400C0FF6C /* MSLogManagerDelegate.h */, 6E0684651D36BCD500A8CC6C /* MSChannel.h */, 382CFCF01EC3817B003FE40B /* MSChannelDefaultPrivate.h */, 6E0684611D36BC8D00A8CC6C /* MSChannelDefault.h */, 6E0684621D36BC8D00A8CC6C /* MSChannelDefault.m */, 6EF628F21D371B1600CAFF64 /* MSChannelConfiguration.h */, 6EF628F31D371B1600CAFF64 /* MSChannelConfiguration.m */, - E8AEE9811D5970A400C0FF6C /* MSLogManagerDelegate.h */, 5949380E604D45340244FEF1 /* MSChannelDelegate.h */, ); path = Channel; diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDelegate.h b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDelegate.h index a700da0bf9..ba946ebed2 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDelegate.h +++ b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDelegate.h @@ -1,8 +1,9 @@ #import + #import "MSChannel.h" -@class MSLogWithProperties; @class MSLog; +@class MSLogWithProperties; @protocol MSChannelDelegate @@ -10,6 +11,7 @@ /** * Callback method that will be called before each log will be send to the server. + * * @param channel Instance of MSChannel. * @param log The log to be sent. */ @@ -17,6 +19,7 @@ /** * Callback method that will be called in case the SDK was able to send a log. + * * @param channel Instance of MSChannel. * @param log The log to be sent. */ @@ -24,6 +27,7 @@ /** * Callback method that will be called in case the SDK was unable to send a log. + * * @param channel Instance of MSChannel. * @param log The log to be sent. * @param error The error that occured. diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManager.h b/MobileCenter/MobileCenter/Internals/Channel/MSLogManager.h index 9796b8711f..cc3a2ff220 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManager.h +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManager.h @@ -1,4 +1,5 @@ #import + #import "MSChannelConfiguration.h" #import "MSEnable.h" #import "MSLog.h" @@ -62,22 +63,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)setEnabled:(BOOL)isEnabled andDeleteDataOnDisabled:(BOOL)deleteData forGroupId:(NSString *)groupId; -/** - * Add a delegate to each channel that has a certain priority. - * - * @param channelDelegate A delegate for the channel. - * @param groupId The groupId of a channel. - */ -- (void)addChannelDelegate:(id)channelDelegate forGroupId:(NSString *)groupId; - -/** - * Remove a delegate to each channel that has a certain priority. - * - * @param channelDelegate A delegate for the channel. - * @param groupId The groupId of a channel. - */ -- (void)removeChannelDelegate:(id)channelDelegate forGroupId:(NSString *)groupId; - /** * Suspend log manager, logs will not be sent but still stored. */ diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.h b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.h index 6e0b895540..2d077d6865 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.h +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.h @@ -33,6 +33,11 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithAppSecret:(NSString *)appSecret installId:(NSUUID *)installId logUrl:(NSString *)logUrl; +/** + * A boolean value set to YES if this instance is enabled or NO otherwise. + */ +@property BOOL enabled; + /** * Hash table of log manager delegate. */ diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m index 0f0990ae31..5474ad11b1 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m @@ -6,7 +6,6 @@ #import "MSLogManagerDefaultPrivate.h" #import "MSMobileCenterErrors.h" #import "MSMobileCenterInternal.h" -#import "MobileCenter+Internal.h" static char *const MSlogsDispatchQueue = "com.microsoft.azure.mobile.mobilecenter.LogManagerQueue"; @@ -15,11 +14,6 @@ */ @interface MSLogManagerDefault () -/** - * A boolean value set to YES if this instance is enabled or NO otherwise. - */ -@property BOOL enabled; - @end @implementation MSLogManagerDefault @@ -72,28 +66,6 @@ - (void)removeDelegate:(id)delegate { } } -#pragma mark - Channel Delegate - -- (void)addChannelDelegate:(id)channelDelegate forGroupId:(NSString *)groupId { - if (channelDelegate) { - if (self.channels[groupId]) { - [self.channels[groupId] addDelegate:channelDelegate]; - } else { - MSLogWarning([MSMobileCenter logTag], @"Channel has not been initialized for the group ID: %@", groupId); - } - } -} - -- (void)removeChannelDelegate:(id)channelDelegate forGroupId:(NSString *)groupId { - if (channelDelegate) { - if (self.channels[groupId]) { - [self.channels[groupId] removeDelegate:channelDelegate]; - } else { - MSLogWarning([MSMobileCenter logTag], @"Channel has not been initialized for the group ID: %@", groupId); - } - } -} - - (void)enumerateDelegatesForSelector:(SEL)selector withBlock:(void (^)(id delegate))block { @synchronized(self) { for (id delegate in self.delegates) { @@ -104,6 +76,35 @@ - (void)enumerateDelegatesForSelector:(SEL)selector withBlock:(void (^)(id)channel willSendLog:(id)log { + [self enumerateDelegatesForSelector:@selector(willSendLog:) + withBlock:^(id delegate) { + if ([[delegate groupId] isEqualToString:[channel.configuration groupId]]) { + [delegate willSendLog:log]; + } + }]; +} + +- (void)channel:(id)channel didSucceedSendingLog:(id)log { + [self enumerateDelegatesForSelector:@selector(didSucceedSendingLog:) + withBlock:^(id delegate) { + if ([[delegate groupId] isEqualToString:[channel.configuration groupId]]) { + [delegate didSucceedSendingLog:log]; + } + }]; +} + +- (void)channel:(id)channel didFailSendingLog:(id)log withError:(NSError *)error { + [self enumerateDelegatesForSelector:@selector(didFailSendingLog:withError:) + withBlock:^(id delegate) { + if ([[delegate groupId] isEqualToString:[channel.configuration groupId]]) { + [delegate didFailSendingLog:log withError:error]; + } + }]; +} + #pragma mark - Process items - (void)processLog:(id)log forGroupId:(NSString *)groupId { diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDelegate.h b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDelegate.h index 41517b7a00..4cabfa9633 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDelegate.h +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDelegate.h @@ -1,15 +1,18 @@ #import + #import "MSConstants+Internal.h" +#import "MSLog.h" +// TODO: We need to pass a sender instance in delegate methods or changing method name with a prefix of sender. @protocol MSLogManagerDelegate @optional /** - * A callback that is called when a log has been enqueued, before a log has been forwarded to persistence, etc. + * A callback that is called when a log has been enqueued, before a log has been forwarded to persistence, etc. * - * @param log The log. - * @param internalId An internal Id that can be used to keep track of logs. + * @param log The log. + * @param internalId An internal Id that can be used to keep track of logs. */ - (void)onEnqueuingLog:(id)log withInternalId:(NSString *)internalId; @@ -17,30 +20,59 @@ * Callback that is called when a log has been persisted successfully. This was introduced to implement the * log buffer for Crashes. * - * @param log The log. - * @param internalId An internal Id that can be used to keep track of logs. + * @param log The log. + * @param internalId An internal Id that can be used to keep track of logs. * - * @discussion We had some discussion about the naming of the method. To match the - * onEnqueueingLog:withInternalId (@see onEnqueueingLog:withInternalId) callback, it should - * also use `enqueuing` in it's signature, yet, as of now, it indicates successful persistence of a log. This - * method's name might change in the future to make a distinction between a successfully enqueued log and a persisted - * log. + * @discussion We had some discussion about the naming of the method. To match the + * onEnqueueingLog:withInternalId (@see onEnqueueingLog:withInternalId) callback, it should + * also use `enqueuing` in it's signature, yet, as of now, it indicates successful persistence of a log. This + * method's name might change in the future to make a distinction between a successfully enqueued log and a persisted + * log. */ - (void)onFinishedPersistingLog:(id)log withInternalId:(NSString *)internalId; /** - * Callback that is called when persisting a log has failed, meaning it has not been saved to disk because the log was - * empty. This was introduced to implement the log buffer for Crashes. + * Callback that is called when persisting a log has failed, meaning it has not been saved to disk because the log was + * empty. This was introduced to implement the log buffer for Crashes. * - * @param log The log. - * @param internalId An internal Id that can be used to keep track of logs. + * @param log The log. + * @param internalId An internal Id that can be used to keep track of logs. * - * @discussion We had some discussion about the naming of the method. To match the - * onEnqueueingLog:withInternalId (@see onEnqueueingLog:withInternalId) callback, it should - * also use `enqueuing` in it's signature, yet, as of now, it indicates successful persistence of a log. This - * method's name might change in the future to make a distinction between a successfully enqueued log and a persisted - * log. + * @discussion We had some discussion about the naming of the method. To match the + * onEnqueueingLog:withInternalId (@see onEnqueueingLog:withInternalId) callback, it should + * also use `enqueuing` in it's signature, yet, as of now, it indicates successful persistence of a log. This + * method's name might change in the future to make a distinction between a successfully enqueued log and a persisted + * log. */ - (void)onFailedPersistingLog:(id)log withInternalId:(NSString *)internalId; +/** + * Callback method that will be called before each log will be send to the server. + * + * @param log The log to be sent. + */ +- (void)willSendLog:(id)log; + +/** + * Callback method that will be called in case the SDK was able to send a log. + * + * @param log The log to be sent. + */ +- (void)didSucceedSendingLog:(id)log; + +/** + * Callback method that will be called in case the SDK was unable to send a log. + * + * @param log The log to be sent. + * @param error The error that occured. + */ +- (void)didFailSendingLog:(id)log withError:(NSError *)error; + +/** + * Get service unique key for storage purpose. + * + * @return A group ID of the service. + */ +- (NSString *)groupId; + @end diff --git a/MobileCenter/MobileCenterTests/MSAppDelegateForwarderTests.m b/MobileCenter/MobileCenterTests/MSAppDelegateForwarderTests.m index 9ac3c4a21d..4076975dc0 100644 --- a/MobileCenter/MobileCenterTests/MSAppDelegateForwarderTests.m +++ b/MobileCenter/MobileCenterTests/MSAppDelegateForwarderTests.m @@ -283,7 +283,7 @@ - (void)testWithoutCustomDelegate { annotation:expectedAnnotation]; // Then - assertThatInt(MSAppDelegateForwarder.delegates.count, equalToInt(0)); + assertThatUnsignedLong(MSAppDelegateForwarder.delegates.count, equalToUnsignedLong(0)); assertThatBool(returnedValue, is(@(expectedReturnedValue))); [self waitForExpectations:@[ originalCalledExpectation ] timeout:1]; } diff --git a/MobileCenter/MobileCenterTests/MSLogManagerDefaultTests.m b/MobileCenter/MobileCenterTests/MSLogManagerDefaultTests.m index c1c1eb7e1d..5aed554c1e 100644 --- a/MobileCenter/MobileCenterTests/MSLogManagerDefaultTests.m +++ b/MobileCenter/MobileCenterTests/MSLogManagerDefaultTests.m @@ -61,8 +61,8 @@ - (void)testInitNewChannel { assertThat(channel, notNilValue()); XCTAssertTrue(channel.configuration.priority == priority); assertThatFloat(channel.configuration.flushInterval, equalToFloat(flushInterval)); - assertThatUnsignedInt(channel.configuration.batchSizeLimit, equalToUnsignedInteger(batchSizeLimit)); - assertThatUnsignedInt(channel.configuration.pendingBatchesLimit, equalToUnsignedInteger(pendingBatchesLimit)); + assertThatUnsignedLong(channel.configuration.batchSizeLimit, equalToUnsignedLong(batchSizeLimit)); + assertThatUnsignedLong(channel.configuration.pendingBatchesLimit, equalToUnsignedLong(pendingBatchesLimit)); } - (void)testProcessingLogWillTriggerOnProcessingCall { diff --git a/MobileCenterAnalytics/MobileCenterAnalytics/Internals/MSAnalyticsInternal.h b/MobileCenterAnalytics/MobileCenterAnalytics/Internals/MSAnalyticsInternal.h index 18abdf93c7..92641fd2b6 100644 --- a/MobileCenterAnalytics/MobileCenterAnalytics/Internals/MSAnalyticsInternal.h +++ b/MobileCenterAnalytics/MobileCenterAnalytics/Internals/MSAnalyticsInternal.h @@ -1,11 +1,11 @@ #import "MSAnalytics.h" #import "MSServiceInternal.h" #import "MSAnalyticsDelegate.h" -#import "MSChannelDelegate.h" +#import "MSLogManagerDelegate.h" NS_ASSUME_NONNULL_BEGIN -@interface MSAnalytics () +@interface MSAnalytics () // Temporarily hiding tracking page feature. /** diff --git a/MobileCenterAnalytics/MobileCenterAnalytics/MSAnalytics.m b/MobileCenterAnalytics/MobileCenterAnalytics/MSAnalytics.m index 95ddd0c49d..aa3140dbad 100644 --- a/MobileCenterAnalytics/MobileCenterAnalytics/MSAnalytics.m +++ b/MobileCenterAnalytics/MobileCenterAnalytics/MSAnalytics.m @@ -87,11 +87,9 @@ - (void)applyEnabledState:(BOOL)isEnabled { // Start session tracker. [self.sessionTracker start]; - // Add delegate to log manager. + // Add delegates to log manager. [self.logManager addDelegate:self.sessionTracker]; - - // Set self as delegate of analytics channel. - [self.logManager addChannelDelegate:self forGroupId:self.groupId]; + [self.logManager addDelegate:self]; // Report current page while auto page tracking is on. if (self.autoPageTrackingEnabled) { @@ -107,7 +105,7 @@ - (void)applyEnabledState:(BOOL)isEnabled { MSLogInfo([MSAnalytics logTag], @"Analytics service has been enabled."); } else { [self.logManager removeDelegate:self.sessionTracker]; - [self.logManager removeChannelDelegate:self forGroupId:self.groupId]; + [self.logManager removeDelegate:self]; [self.sessionTracker stop]; [self.sessionTracker clearSessions]; MSLogInfo([MSAnalytics logTag], @"Analytics service has been disabled."); @@ -307,10 +305,9 @@ + (void)setDelegate:(nullable id)delegate { [[self sharedInstance] setDelegate:delegate]; } -#pragma mark - MSChannelDelegate +#pragma mark - MSLogManagerDelegate -- (void)channel:(id)channel willSendLog:(id)log { - (void)channel; +- (void)willSendLog:(id)log { if (!self.delegate) { return; } @@ -326,8 +323,7 @@ - (void)channel:(id)channel willSendLog:(id)log { } } -- (void)channel:(id)channel didSucceedSendingLog:(id)log { - (void)channel; +- (void)didSucceedSendingLog:(id)log { if (!self.delegate) { return; } @@ -343,8 +339,7 @@ - (void)channel:(id)channel didSucceedSendingLog:(id)log { } } -- (void)channel:(id)channel didFailSendingLog:(id)log withError:(NSError *)error { - (void)channel; +- (void)didFailSendingLog:(id)log withError:(NSError *)error { if (!self.delegate) { return; } diff --git a/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSAnalyticsTests.m b/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSAnalyticsTests.m index bfd779e547..00fc936996 100644 --- a/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSAnalyticsTests.m +++ b/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSAnalyticsTests.m @@ -204,9 +204,9 @@ - (void)testAnalyticsDelegateMethodsAreCalled { self.didFailSendingEventLogWasCalled = false; [[MSAnalytics sharedInstance] setDelegate:self]; MSEventLog *eventLog = [MSEventLog new]; - [[MSAnalytics sharedInstance] channel:nil willSendLog:eventLog]; - [[MSAnalytics sharedInstance] channel:nil didSucceedSendingLog:eventLog]; - [[MSAnalytics sharedInstance] channel:nil didFailSendingLog:eventLog withError:nil]; + [[MSAnalytics sharedInstance] willSendLog:eventLog]; + [[MSAnalytics sharedInstance] didSucceedSendingLog:eventLog]; + [[MSAnalytics sharedInstance] didFailSendingLog:eventLog withError:nil]; XCTAssertTrue(self.willSendEventLogWasCalled); XCTAssertTrue(self.didSucceedSendingEventLogWasCalled); diff --git a/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesPrivate.h b/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesPrivate.h index cf22346bfa..4e2b8cf759 100644 --- a/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesPrivate.h +++ b/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesPrivate.h @@ -1,10 +1,10 @@ -#import "MSCrashes.h" #import - #import #import #import +#import "MSCrashes.h" + @class MSMPLCrashReporter; /** @@ -39,7 +39,7 @@ const int ms_crashes_log_buffer_size = 60; */ extern std::array msCrashesLogBuffer; -@interface MSCrashes () +@interface MSCrashes () /** * Prototype of a callback function used to execute additional user code. Called diff --git a/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.mm b/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.mm index fb528af1b9..56c46b79a2 100644 --- a/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.mm +++ b/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.mm @@ -288,9 +288,6 @@ - (void)applyEnabledState:(BOOL)isEnabled { // Get persisted crash reports. self.crashFiles = [self persistedCrashReports]; - // Set self as delegate of crashes' channel. - [self.logManager addChannelDelegate:self forGroupId:self.groupId]; - // Process PLCrashReports, this will format the PLCrashReport into our schema and then trigger sending. // This mostly happens on the start of the service. if (self.crashFiles.count > 0) { @@ -314,11 +311,6 @@ - (void)applyEnabledState:(BOOL)isEnabled { [self emptyLogBufferFiles]; [self removeAnalyzerFile]; [self.plCrashReporter purgePendingCrashReport]; - - // Remove as ChannelDelegate from LogManager - [self.logManager removeChannelDelegate:self forGroupId:self.groupId]; - [self.logManager removeChannelDelegate:self forGroupId:self.groupId]; - [self.logManager removeChannelDelegate:self forGroupId:self.groupId]; MSLogInfo([MSCrashes logTag], @"Crashes service has been disabled."); } } @@ -474,10 +466,7 @@ - (void)deleteBufferedLogWithInternalId:(NSString *)internalId { } } -#pragma mark - MSChannelDelegate - -- (void)channel:(id)channel willSendLog:(id)log { - (void)channel; +- (void)willSendLog:(id)log { id strongDelegate = self.delegate; if (strongDelegate && [strongDelegate respondsToSelector:@selector(crashes:willSendErrorReport:)]) { NSObject *logObject = static_cast(log); @@ -489,8 +478,7 @@ - (void)channel:(id)channel willSendLog:(id)log { } } -- (void)channel:(id)channel didSucceedSendingLog:(id)log { - (void)channel; +- (void)didSucceedSendingLog:(id)log { id strongDelegate = self.delegate; if (strongDelegate && [strongDelegate respondsToSelector:@selector(crashes:didSucceedSendingErrorReport:)]) { NSObject *logObject = static_cast(log); @@ -502,8 +490,7 @@ - (void)channel:(id)channel didSucceedSendingLog:(id)log { } } -- (void)channel:(id)channel didFailSendingLog:(id)log withError:(NSError *)error { - (void)channel; +- (void)didFailSendingLog:(id)log withError:(NSError *)error { id strongDelegate = self.delegate; if (strongDelegate && [strongDelegate respondsToSelector:@selector(crashes:didFailSendingErrorReport:withError:)]) { NSObject *logObject = static_cast(log); diff --git a/MobileCenterCrashes/MobileCenterCrashesTests/MSCrashesTests.mm b/MobileCenterCrashes/MobileCenterCrashesTests/MSCrashesTests.mm index 52d3d84d0b..f2bb4fb49a 100644 --- a/MobileCenterCrashes/MobileCenterCrashesTests/MSCrashesTests.mm +++ b/MobileCenterCrashes/MobileCenterCrashesTests/MSCrashesTests.mm @@ -130,9 +130,9 @@ - (void)testDelegateMethodsAreCalled { // When [[MSCrashes sharedInstance] setDelegate:self]; MSAppleErrorLog *errorLog = [MSAppleErrorLog new]; - [[MSCrashes sharedInstance] channel:nil willSendLog:errorLog]; - [[MSCrashes sharedInstance] channel:nil didSucceedSendingLog:errorLog]; - [[MSCrashes sharedInstance] channel:nil didFailSendingLog:errorLog withError:nil]; + [[MSCrashes sharedInstance] willSendLog:errorLog]; + [[MSCrashes sharedInstance] didSucceedSendingLog:errorLog]; + [[MSCrashes sharedInstance] didFailSendingLog:errorLog withError:nil]; [[MSCrashes sharedInstance] shouldProcessErrorReport:nil]; // Then From 29d8187c984f9710687f19a8e52bc77c416fecf5 Mon Sep 17 00:00:00 2001 From: Jae Lim Date: Thu, 15 Jun 2017 16:49:10 -0700 Subject: [PATCH 31/76] Add log manager as channel delegate to channel --- .../MobileCenter/Internals/Channel/MSLogManagerDefault.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m index 5474ad11b1..1ad4392517 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSLogManagerDefault.m @@ -1,4 +1,5 @@ #import "MSChannelDefault.h" +#import "MSChannelDelegate.h" #import "MSFileStorage.h" #import "MSHttpSender.h" #import "MSIngestionSender.h" @@ -12,7 +13,7 @@ /** * Private declaration of the log manager. */ -@interface MSLogManagerDefault () +@interface MSLogManagerDefault (MSChannelDelegate) @end @@ -48,6 +49,7 @@ - (void)initChannelWithConfiguration:(MSChannelConfiguration *)configuration { storage:self.storage configuration:configuration logsDispatchQueue:self.logsDispatchQueue]; + [channel addDelegate:(id)self]; self.channels[configuration.groupId] = channel; } } From 0d46a95ea0958a9532c769ad238c1ee5c73ef581 Mon Sep 17 00:00:00 2001 From: Murat Baysangurov Date: Fri, 16 Jun 2017 11:43:36 +0300 Subject: [PATCH 32/76] Added TODO comments --- Vendor/Utils/MSPropertiesTableDataSource.h | 2 ++ Vendor/Utils/MSPropertiesTableDataSource.m | 2 ++ Vendor/Utils/PropertyViewCell.h | 2 ++ Vendor/Utils/PropertyViewCell.m | 2 ++ 4 files changed, 8 insertions(+) diff --git a/Vendor/Utils/MSPropertiesTableDataSource.h b/Vendor/Utils/MSPropertiesTableDataSource.h index 8a4cfe57e0..8260fd1d96 100644 --- a/Vendor/Utils/MSPropertiesTableDataSource.h +++ b/Vendor/Utils/MSPropertiesTableDataSource.h @@ -2,6 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. */ +// TODO: The file should be relocated under iOS sub folder once multiple-platforms branch is merged into develop. + #import @interface MSPropertiesTableDataSource : NSObject diff --git a/Vendor/Utils/MSPropertiesTableDataSource.m b/Vendor/Utils/MSPropertiesTableDataSource.m index 5f0d9babae..5f65fe6e5c 100644 --- a/Vendor/Utils/MSPropertiesTableDataSource.m +++ b/Vendor/Utils/MSPropertiesTableDataSource.m @@ -2,6 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. */ +// TODO: The file should be relocated under iOS sub folder once multiple-platforms branch is merged into develop. + #import "MSPropertiesTableDataSource.h" #import "PropertyViewCell.h" diff --git a/Vendor/Utils/PropertyViewCell.h b/Vendor/Utils/PropertyViewCell.h index abf3d02ddf..a72efbd4c7 100644 --- a/Vendor/Utils/PropertyViewCell.h +++ b/Vendor/Utils/PropertyViewCell.h @@ -2,6 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. */ +// TODO: The file should be relocated under iOS sub folder once multiple-platforms branch is merged into develop. + #import @interface PropertyViewCell : UITableViewCell diff --git a/Vendor/Utils/PropertyViewCell.m b/Vendor/Utils/PropertyViewCell.m index 1754355f70..8f25209ad6 100644 --- a/Vendor/Utils/PropertyViewCell.m +++ b/Vendor/Utils/PropertyViewCell.m @@ -2,6 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. */ +// TODO: The file should be relocated under iOS sub folder once multiple-platforms branch is merged into develop. + #import "PropertyViewCell.h" @implementation PropertyViewCell From 063065a59547abab40df5dc3b4f614aaa23980e6 Mon Sep 17 00:00:00 2001 From: Murat Baysangurov Date: Fri, 16 Jun 2017 11:55:58 +0300 Subject: [PATCH 33/76] Removed autoresizingMask tag --- Sasquatch/Sasquatch/Base.lproj/Main.storyboard | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sasquatch/Sasquatch/Base.lproj/Main.storyboard b/Sasquatch/Sasquatch/Base.lproj/Main.storyboard index f45102b711..398098dac1 100644 --- a/Sasquatch/Sasquatch/Base.lproj/Main.storyboard +++ b/Sasquatch/Sasquatch/Base.lproj/Main.storyboard @@ -123,10 +123,8 @@ - -