| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806 |
- /*
- * Copyright 2018 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h"
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORAssert.h"
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORLifecycle.h"
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORPlatform.h"
- #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORStorageEventSelector.h"
- #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h"
- #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
- #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
- #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORDirectorySizeTracker.h"
- NS_ASSUME_NONNULL_BEGIN
- /** A library data key this class uses to track batchIDs. */
- static NSString *const gBatchIDCounterKey = @"GDTCORFlatFileStorageBatchIDCounter";
- /** The separator used between metadata elements in filenames. */
- static NSString *const kMetadataSeparator = @"-";
- NSString *const kGDTCOREventComponentsEventIDKey = @"GDTCOREventComponentsEventIDKey";
- NSString *const kGDTCOREventComponentsQoSTierKey = @"GDTCOREventComponentsQoSTierKey";
- NSString *const kGDTCOREventComponentsMappingIDKey = @"GDTCOREventComponentsMappingIDKey";
- NSString *const kGDTCOREventComponentsExpirationKey = @"GDTCOREventComponentsExpirationKey";
- NSString *const kGDTCORBatchComponentsTargetKey = @"GDTCORBatchComponentsTargetKey";
- NSString *const kGDTCORBatchComponentsBatchIDKey = @"GDTCORBatchComponentsBatchIDKey";
- NSString *const kGDTCORBatchComponentsExpirationKey = @"GDTCORBatchComponentsExpirationKey";
- NSString *const GDTCORFlatFileStorageErrorDomain = @"GDTCORFlatFileStorage";
- const uint64_t kGDTCORFlatFileStorageSizeLimit = 20 * 1000 * 1000; // 20 MB.
- @interface GDTCORFlatFileStorage ()
- /** An instance of the size tracker to keep track of the disk space consumed by the storage. */
- @property(nonatomic, readonly) GDTCORDirectorySizeTracker *sizeTracker;
- @end
- @implementation GDTCORFlatFileStorage
- @synthesize sizeTracker = _sizeTracker;
- + (void)load {
- #if !NDEBUG
- [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetTest];
- #endif // !NDEBUG
- [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetCCT];
- [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetFLL];
- [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetCSH];
- [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetINT];
- }
- + (instancetype)sharedInstance {
- static GDTCORFlatFileStorage *sharedStorage;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- sharedStorage = [[GDTCORFlatFileStorage alloc] init];
- });
- return sharedStorage;
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- _storageQueue =
- dispatch_queue_create("com.google.GDTCORFlatFileStorage", DISPATCH_QUEUE_SERIAL);
- _uploadCoordinator = [GDTCORUploadCoordinator sharedInstance];
- }
- return self;
- }
- - (GDTCORDirectorySizeTracker *)sizeTracker {
- if (_sizeTracker == nil) {
- _sizeTracker =
- [[GDTCORDirectorySizeTracker alloc] initWithDirectoryPath:GDTCORRootDirectory().path];
- }
- return _sizeTracker;
- }
- #pragma mark - GDTCORStorageProtocol
- - (void)storeEvent:(GDTCOREvent *)event
- onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion {
- GDTCORLogDebug(@"Saving event: %@", event);
- if (event == nil || event.serializedDataObjectBytes == nil) {
- GDTCORLogDebug(@"%@", @"The event was nil, so it was not saved.");
- if (completion) {
- completion(NO, [NSError errorWithDomain:NSInternalInconsistencyException
- code:-1
- userInfo:nil]);
- }
- return;
- }
- if (!completion) {
- completion = ^(BOOL wasWritten, NSError *_Nullable error) {
- GDTCORLogDebug(@"event %@ stored. success:%@ error:%@", event, wasWritten ? @"YES" : @"NO",
- error);
- };
- }
- __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
- bgID = [[GDTCORApplication sharedApplication]
- beginBackgroundTaskWithName:@"GDTStorage"
- expirationHandler:^{
- // End the background task if it's still valid.
- [[GDTCORApplication sharedApplication] endBackgroundTask:bgID];
- bgID = GDTCORBackgroundIdentifierInvalid;
- }];
- dispatch_async(_storageQueue, ^{
- // Check that a backend implementation is available for this target.
- GDTCORTarget target = event.target;
- NSString *filePath = [GDTCORFlatFileStorage pathForTarget:target
- eventID:event.eventID
- qosTier:@(event.qosTier)
- expirationDate:event.expirationDate
- mappingID:event.mappingID];
- NSError *error;
- NSData *encodedEvent = GDTCOREncodeArchive(event, nil, &error);
- if (error) {
- completion(NO, error);
- return;
- }
- // Check storage size limit before storing the event.
- uint64_t resultingStorageSize = self.sizeTracker.directoryContentSize + encodedEvent.length;
- if (resultingStorageSize > kGDTCORFlatFileStorageSizeLimit) {
- NSError *error = [NSError
- errorWithDomain:GDTCORFlatFileStorageErrorDomain
- code:GDTCORFlatFileStorageErrorSizeLimitReached
- userInfo:@{
- NSLocalizedFailureReasonErrorKey : @"Storage size limit has been reached."
- }];
- completion(NO, error);
- return;
- }
- // Write the encoded event to the file.
- BOOL writeResult = GDTCORWriteDataToFile(encodedEvent, filePath, &error);
- if (writeResult == NO || error) {
- GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, error);
- completion(NO, error);
- return;
- } else {
- GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
- completion(YES, nil);
- }
- // Notify size tracker.
- [self.sizeTracker fileWasAddedAtPath:filePath withSize:encodedEvent.length];
- // Check the QoS, if it's high priority, notify the target that it has a high priority event.
- if (event.qosTier == GDTCOREventQoSFast) {
- // TODO: Remove a direct dependency on the upload coordinator.
- [self.uploadCoordinator forceUploadForTarget:target];
- }
- // Cancel or end the associated background task if it's still valid.
- [[GDTCORApplication sharedApplication] endBackgroundTask:bgID];
- bgID = GDTCORBackgroundIdentifierInvalid;
- });
- }
- - (void)batchWithEventSelector:(nonnull GDTCORStorageEventSelector *)eventSelector
- batchExpiration:(nonnull NSDate *)expiration
- onComplete:
- (nonnull void (^)(NSNumber *_Nullable batchID,
- NSSet<GDTCOREvent *> *_Nullable events))onComplete {
- dispatch_queue_t queue = _storageQueue;
- void (^onPathsForTargetComplete)(NSNumber *, NSSet<NSString *> *_Nonnull) = ^(
- NSNumber *batchID, NSSet<NSString *> *_Nonnull paths) {
- dispatch_async(queue, ^{
- NSMutableSet<GDTCOREvent *> *events = [[NSMutableSet alloc] init];
- for (NSString *eventPath in paths) {
- NSError *error;
- GDTCOREvent *event =
- (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], eventPath, nil, &error);
- if (event == nil || error) {
- GDTCORLogDebug(@"Error deserializing event: %@", error);
- [[NSFileManager defaultManager] removeItemAtPath:eventPath error:nil];
- continue;
- } else {
- NSString *fileName = [eventPath lastPathComponent];
- NSString *batchPath =
- [GDTCORFlatFileStorage batchPathForTarget:eventSelector.selectedTarget
- batchID:batchID
- expirationDate:expiration];
- [[NSFileManager defaultManager] createDirectoryAtPath:batchPath
- withIntermediateDirectories:YES
- attributes:nil
- error:nil];
- NSString *destinationPath = [batchPath stringByAppendingPathComponent:fileName];
- error = nil;
- [[NSFileManager defaultManager] moveItemAtPath:eventPath
- toPath:destinationPath
- error:&error];
- if (error) {
- GDTCORLogDebug(@"An event file wasn't moveable into the batch directory: %@", error);
- }
- [events addObject:event];
- }
- }
- if (onComplete) {
- if (events.count == 0) {
- onComplete(nil, nil);
- } else {
- onComplete(batchID, events);
- }
- }
- });
- };
- void (^onBatchIDFetchComplete)(NSNumber *) = ^(NSNumber *batchID) {
- dispatch_async(queue, ^{
- if (batchID == nil) {
- if (onComplete) {
- onComplete(nil, nil);
- return;
- }
- }
- [self pathsForTarget:eventSelector.selectedTarget
- eventIDs:eventSelector.selectedEventIDs
- qosTiers:eventSelector.selectedQosTiers
- mappingIDs:eventSelector.selectedMappingIDs
- onComplete:^(NSSet<NSString *> *_Nonnull paths) {
- onPathsForTargetComplete(batchID, paths);
- }];
- });
- };
- [self nextBatchID:^(NSNumber *_Nullable batchID) {
- if (batchID == nil) {
- if (onComplete) {
- onComplete(nil, nil);
- }
- } else {
- onBatchIDFetchComplete(batchID);
- }
- }];
- }
- - (void)removeBatchWithID:(nonnull NSNumber *)batchID
- deleteEvents:(BOOL)deleteEvents
- onComplete:(void (^_Nullable)(void))onComplete {
- dispatch_async(_storageQueue, ^{
- [self syncThreadUnsafeRemoveBatchWithID:batchID deleteEvents:deleteEvents];
- if (onComplete) {
- onComplete();
- }
- });
- }
- - (void)batchIDsForTarget:(GDTCORTarget)target
- onComplete:(nonnull void (^)(NSSet<NSNumber *> *_Nullable))onComplete {
- dispatch_async(_storageQueue, ^{
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSError *error;
- NSArray<NSString *> *batchPaths =
- [fileManager contentsOfDirectoryAtPath:[GDTCORFlatFileStorage batchDataStoragePath]
- error:&error];
- if (error || batchPaths.count == 0) {
- if (onComplete) {
- onComplete(nil);
- }
- return;
- }
- NSMutableSet<NSNumber *> *batchIDs = [[NSMutableSet alloc] init];
- for (NSString *path in batchPaths) {
- NSDictionary<NSString *, id> *components = [self batchComponentsFromFilename:path];
- NSNumber *targetNumber = components[kGDTCORBatchComponentsTargetKey];
- NSNumber *batchID = components[kGDTCORBatchComponentsBatchIDKey];
- if (targetNumber.intValue == target) {
- [batchIDs addObject:batchID];
- }
- }
- if (onComplete) {
- onComplete(batchIDs);
- }
- });
- }
- - (void)libraryDataForKey:(nonnull NSString *)key
- onFetchComplete:(nonnull void (^)(NSData *_Nullable, NSError *_Nullable))onFetchComplete
- setNewValue:(NSData *_Nullable (^_Nullable)(void))setValueBlock {
- dispatch_async(_storageQueue, ^{
- NSString *dataPath = [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:key];
- NSError *error;
- NSData *data = [NSData dataWithContentsOfFile:dataPath options:0 error:&error];
- if (onFetchComplete) {
- onFetchComplete(data, error);
- }
- if (setValueBlock) {
- NSData *newValue = setValueBlock();
- // The -isKindOfClass check is necessary because without an explicit 'return nil' in the block
- // the implicit return value will be the block itself. The compiler doesn't detect this.
- if (newValue != nil && [newValue isKindOfClass:[NSData class]] && newValue.length) {
- NSError *newValueError;
- if ([newValue writeToFile:dataPath options:NSDataWritingAtomic error:&newValueError]) {
- // Update storage size.
- [self.sizeTracker fileWasRemovedAtPath:dataPath withSize:data.length];
- [self.sizeTracker fileWasAddedAtPath:dataPath withSize:newValue.length];
- } else {
- GDTCORLogDebug(@"Error writing new value in libraryDataForKey: %@", newValueError);
- }
- }
- }
- });
- }
- - (void)storeLibraryData:(NSData *)data
- forKey:(nonnull NSString *)key
- onComplete:(nullable void (^)(NSError *_Nullable error))onComplete {
- if (!data || data.length <= 0) {
- if (onComplete) {
- onComplete([NSError errorWithDomain:NSInternalInconsistencyException code:-1 userInfo:nil]);
- }
- return;
- }
- dispatch_async(_storageQueue, ^{
- NSError *error;
- NSString *dataPath = [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:key];
- if ([data writeToFile:dataPath options:NSDataWritingAtomic error:&error]) {
- [self.sizeTracker fileWasAddedAtPath:dataPath withSize:data.length];
- }
- if (onComplete) {
- onComplete(error);
- }
- });
- }
- - (void)removeLibraryDataForKey:(nonnull NSString *)key
- onComplete:(nonnull void (^)(NSError *_Nullable error))onComplete {
- dispatch_async(_storageQueue, ^{
- NSError *error;
- NSString *dataPath = [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:key];
- GDTCORStorageSizeBytes fileSize =
- [self.sizeTracker fileSizeAtURL:[NSURL fileURLWithPath:dataPath]];
- if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
- if ([[NSFileManager defaultManager] removeItemAtPath:dataPath error:&error]) {
- [self.sizeTracker fileWasRemovedAtPath:dataPath withSize:fileSize];
- }
- if (onComplete) {
- onComplete(error);
- }
- }
- });
- }
- - (void)hasEventsForTarget:(GDTCORTarget)target onComplete:(void (^)(BOOL hasEvents))onComplete {
- dispatch_async(_storageQueue, ^{
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSString *targetPath = [NSString
- stringWithFormat:@"%@/%ld", [GDTCORFlatFileStorage eventDataStoragePath], (long)target];
- [fileManager createDirectoryAtPath:targetPath
- withIntermediateDirectories:YES
- attributes:nil
- error:nil];
- NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:targetPath];
- BOOL hasEventAtLeastOneEvent = [enumerator nextObject] != nil;
- if (onComplete) {
- onComplete(hasEventAtLeastOneEvent);
- }
- });
- }
- - (void)checkForExpirations {
- dispatch_async(_storageQueue, ^{
- GDTCORLogDebug(@"%@", @"Checking for expired events and batches");
- NSTimeInterval now = [NSDate date].timeIntervalSince1970;
- NSFileManager *fileManager = [NSFileManager defaultManager];
- // TODO: Storage may not have enough context to remove batches because a batch may be being
- // uploaded but the storage has not context of it.
- // Find expired batches and move their events back to the main storage.
- // If a batch contains expired events they are expected to be removed further in the method
- // together with other expired events in the main storage.
- NSString *batchDataPath = [GDTCORFlatFileStorage batchDataStoragePath];
- NSArray<NSString *> *batchDataPaths = [fileManager contentsOfDirectoryAtPath:batchDataPath
- error:nil];
- for (NSString *path in batchDataPaths) {
- NSString *fileName = [path lastPathComponent];
- NSDictionary<NSString *, id> *batchComponents = [self batchComponentsFromFilename:fileName];
- NSDate *expirationDate = batchComponents[kGDTCORBatchComponentsExpirationKey];
- NSNumber *batchID = batchComponents[kGDTCORBatchComponentsBatchIDKey];
- if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now && batchID != nil) {
- NSNumber *batchID = batchComponents[kGDTCORBatchComponentsBatchIDKey];
- // Move all events from the expired batch back to the main storage.
- [self syncThreadUnsafeRemoveBatchWithID:batchID deleteEvents:NO];
- }
- }
- // Find expired events and remove them from the storage.
- NSString *eventDataPath = [GDTCORFlatFileStorage eventDataStoragePath];
- NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:eventDataPath];
- NSString *path;
- while ((path = [enumerator nextObject])) {
- NSString *fileName = [path lastPathComponent];
- NSDictionary<NSString *, id> *eventComponents = [self eventComponentsFromFilename:fileName];
- NSDate *expirationDate = eventComponents[kGDTCOREventComponentsExpirationKey];
- if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now) {
- NSString *pathToDelete = [eventDataPath stringByAppendingPathComponent:path];
- NSError *error;
- [fileManager removeItemAtPath:pathToDelete error:&error];
- if (error != nil) {
- GDTCORLogDebug(@"There was an error deleting an expired item: %@", error);
- } else {
- GDTCORLogDebug(@"Item deleted because it expired: %@", pathToDelete);
- }
- }
- }
- [self.sizeTracker resetCachedSize];
- });
- }
- - (void)storageSizeWithCallback:(void (^)(uint64_t storageSize))onComplete {
- if (!onComplete) {
- return;
- }
- dispatch_async(_storageQueue, ^{
- onComplete([self.sizeTracker directoryContentSize]);
- });
- }
- #pragma mark - Private not thread safe methods
- /** Looks for directory paths containing events for a batch with the specified ID.
- * @param batchID A batch ID.
- * @param outError A pointer to `NSError *` to assign as possible error to.
- * @return An array of an array of paths to directories for event batches with a specified batch ID
- * or `nil` in the case of an error. Usually returns a single path but potentially return more in
- * cases when the app is terminated while uploading a batch.
- */
- - (nullable NSArray<NSString *> *)batchDirPathsForBatchID:(NSNumber *)batchID
- error:(NSError **)outError {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSError *error;
- NSArray<NSString *> *batches =
- [fileManager contentsOfDirectoryAtPath:[GDTCORFlatFileStorage batchDataStoragePath]
- error:&error];
- if (batches == nil) {
- *outError = error;
- GDTCORLogDebug(@"Failed to find event file paths for batchID: %@, error: %@", batchID, error);
- return nil;
- }
- NSMutableArray<NSString *> *batchDirPaths = [NSMutableArray array];
- for (NSString *path in batches) {
- NSDictionary<NSString *, id> *components = [self batchComponentsFromFilename:path];
- NSNumber *pathBatchID = components[kGDTCORBatchComponentsBatchIDKey];
- if ([pathBatchID isEqual:batchID]) {
- NSString *batchDirPath =
- [[GDTCORFlatFileStorage batchDataStoragePath] stringByAppendingPathComponent:path];
- [batchDirPaths addObject:batchDirPath];
- }
- }
- return [batchDirPaths copy];
- }
- /** Makes a copy of the contents of a directory to a directory at the specified path.*/
- - (BOOL)moveContentsOfDirectoryAtPath:(NSString *)sourcePath
- to:(NSString *)destinationPath
- error:(NSError **)outError {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSError *error;
- NSArray<NSString *> *contentsPaths = [fileManager contentsOfDirectoryAtPath:sourcePath
- error:&error];
- if (contentsPaths == nil) {
- *outError = error;
- return NO;
- }
- NSMutableArray<NSError *> *errors = [NSMutableArray array];
- for (NSString *path in contentsPaths) {
- NSString *contentDestinationPath = [destinationPath stringByAppendingPathComponent:path];
- NSString *contentSourcePath = [sourcePath stringByAppendingPathComponent:path];
- NSError *moveError;
- if (![fileManager moveItemAtPath:contentSourcePath
- toPath:contentDestinationPath
- error:&moveError] &&
- moveError) {
- [errors addObject:moveError];
- }
- }
- if (errors.count == 0) {
- return YES;
- } else {
- NSError *combinedError = [NSError errorWithDomain:@"GDTCORFlatFileStorage"
- code:-1
- userInfo:@{NSUnderlyingErrorKey : errors}];
- *outError = combinedError;
- return NO;
- }
- }
- - (void)syncThreadUnsafeRemoveBatchWithID:(nonnull NSNumber *)batchID
- deleteEvents:(BOOL)deleteEvents {
- NSError *error;
- NSArray<NSString *> *batchDirPaths = [self batchDirPathsForBatchID:batchID error:&error];
- if (batchDirPaths == nil) {
- return;
- }
- NSFileManager *fileManager = [NSFileManager defaultManager];
- void (^removeBatchDir)(NSString *batchDirPath) = ^(NSString *batchDirPath) {
- NSError *error;
- if ([fileManager removeItemAtPath:batchDirPath error:&error]) {
- GDTCORLogDebug(@"Batch removed at path: %@", batchDirPath);
- } else {
- GDTCORLogDebug(@"Failed to remove batch at path: %@", batchDirPath);
- }
- };
- for (NSString *batchDirPath in batchDirPaths) {
- if (deleteEvents) {
- removeBatchDir(batchDirPath);
- } else {
- NSString *batchDirName = [batchDirPath lastPathComponent];
- NSDictionary<NSString *, id> *components = [self batchComponentsFromFilename:batchDirName];
- NSNumber *target = components[kGDTCORBatchComponentsTargetKey];
- NSString *destinationPath = [[GDTCORFlatFileStorage eventDataStoragePath]
- stringByAppendingPathComponent:target.stringValue];
- // `- [NSFileManager moveItemAtPath:toPath:error:]` method fails if an item by the
- // destination path already exists (which usually is the case for the current method). Move
- // the events one by one instead.
- if ([self moveContentsOfDirectoryAtPath:batchDirPath to:destinationPath error:&error]) {
- GDTCORLogDebug(@"Batched events at path: %@ moved back to the storage: %@", batchDirPath,
- destinationPath);
- } else {
- GDTCORLogDebug(@"Error encountered whilst moving events back: %@", error);
- }
- // Even if not all events where moved back to the storage, there is not much can be done at
- // this point, so cleanup batch directory now to avoid clattering.
- removeBatchDir(batchDirPath);
- }
- }
- [self.sizeTracker resetCachedSize];
- }
- #pragma mark - Private helper methods
- + (NSString *)eventDataStoragePath {
- static NSString *eventDataPath;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- eventDataPath = [NSString stringWithFormat:@"%@/%@/gdt_event_data", GDTCORRootDirectory().path,
- NSStringFromClass([self class])];
- });
- NSError *error;
- [[NSFileManager defaultManager] createDirectoryAtPath:eventDataPath
- withIntermediateDirectories:YES
- attributes:0
- error:&error];
- GDTCORAssert(error == nil, @"Creating the library data path failed: %@", error);
- return eventDataPath;
- }
- + (NSString *)batchDataStoragePath {
- static NSString *batchDataPath;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- batchDataPath = [NSString stringWithFormat:@"%@/%@/gdt_batch_data", GDTCORRootDirectory().path,
- NSStringFromClass([self class])];
- });
- NSError *error;
- [[NSFileManager defaultManager] createDirectoryAtPath:batchDataPath
- withIntermediateDirectories:YES
- attributes:0
- error:&error];
- GDTCORAssert(error == nil, @"Creating the batch data path failed: %@", error);
- return batchDataPath;
- }
- + (NSString *)libraryDataStoragePath {
- static NSString *libraryDataPath;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- libraryDataPath =
- [NSString stringWithFormat:@"%@/%@/gdt_library_data", GDTCORRootDirectory().path,
- NSStringFromClass([self class])];
- });
- NSError *error;
- [[NSFileManager defaultManager] createDirectoryAtPath:libraryDataPath
- withIntermediateDirectories:YES
- attributes:0
- error:&error];
- GDTCORAssert(error == nil, @"Creating the library data path failed: %@", error);
- return libraryDataPath;
- }
- + (NSString *)batchPathForTarget:(GDTCORTarget)target
- batchID:(NSNumber *)batchID
- expirationDate:(NSDate *)expirationDate {
- return
- [NSString stringWithFormat:@"%@/%ld%@%@%@%llu", [GDTCORFlatFileStorage batchDataStoragePath],
- (long)target, kMetadataSeparator, batchID, kMetadataSeparator,
- ((uint64_t)expirationDate.timeIntervalSince1970)];
- }
- + (NSString *)pathForTarget:(GDTCORTarget)target
- eventID:(NSString *)eventID
- qosTier:(NSNumber *)qosTier
- expirationDate:(NSDate *)expirationDate
- mappingID:(NSString *)mappingID {
- NSMutableCharacterSet *allowedChars = [[NSCharacterSet alphanumericCharacterSet] mutableCopy];
- [allowedChars addCharactersInString:kMetadataSeparator];
- mappingID = [mappingID stringByAddingPercentEncodingWithAllowedCharacters:allowedChars];
- return [NSString stringWithFormat:@"%@/%ld/%@%@%@%@%llu%@%@",
- [GDTCORFlatFileStorage eventDataStoragePath], (long)target,
- eventID, kMetadataSeparator, qosTier, kMetadataSeparator,
- ((uint64_t)expirationDate.timeIntervalSince1970),
- kMetadataSeparator, mappingID];
- }
- - (void)pathsForTarget:(GDTCORTarget)target
- eventIDs:(nullable NSSet<NSString *> *)eventIDs
- qosTiers:(nullable NSSet<NSNumber *> *)qosTiers
- mappingIDs:(nullable NSSet<NSString *> *)mappingIDs
- onComplete:(void (^)(NSSet<NSString *> *paths))onComplete {
- void (^completion)(NSSet<NSString *> *) = onComplete == nil ? ^(NSSet<NSString *> *paths){} : onComplete;
- dispatch_async(_storageQueue, ^{
- NSMutableSet<NSString *> *paths = [[NSMutableSet alloc] init];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSString *targetPath = [NSString
- stringWithFormat:@"%@/%ld", [GDTCORFlatFileStorage eventDataStoragePath], (long)target];
- [fileManager createDirectoryAtPath:targetPath
- withIntermediateDirectories:YES
- attributes:nil
- error:nil];
- NSError *error;
- NSArray<NSString *> *dirPaths = [fileManager contentsOfDirectoryAtPath:targetPath error:&error];
- if (error) {
- GDTCORLogDebug(@"There was an error reading the contents of the target path: %@", error);
- completion(paths);
- return;
- }
- BOOL checkingIDs = eventIDs.count > 0;
- BOOL checkingQosTiers = qosTiers.count > 0;
- BOOL checkingMappingIDs = mappingIDs.count > 0;
- BOOL checkingAnything = checkingIDs == NO && checkingQosTiers == NO && checkingMappingIDs == NO;
- for (NSString *path in dirPaths) {
- // Skip hidden files that are created as part of atomic file creation.
- if ([path hasPrefix:@"."]) {
- continue;
- }
- NSString *filePath = [targetPath stringByAppendingPathComponent:path];
- if (checkingAnything) {
- [paths addObject:filePath];
- continue;
- }
- NSString *filename = [path lastPathComponent];
- NSDictionary<NSString *, id> *eventComponents = [self eventComponentsFromFilename:filename];
- if (!eventComponents) {
- GDTCORLogDebug(@"There was an error reading the filename components: %@", eventComponents);
- continue;
- }
- NSString *eventID = eventComponents[kGDTCOREventComponentsEventIDKey];
- NSNumber *qosTier = eventComponents[kGDTCOREventComponentsQoSTierKey];
- NSString *mappingID = eventComponents[kGDTCOREventComponentsMappingIDKey];
- NSNumber *eventIDMatch = checkingIDs ? @([eventIDs containsObject:eventID]) : nil;
- NSNumber *qosTierMatch = checkingQosTiers ? @([qosTiers containsObject:qosTier]) : nil;
- NSNumber *mappingIDMatch =
- checkingMappingIDs
- ? @([mappingIDs containsObject:[mappingID stringByRemovingPercentEncoding]])
- : nil;
- if ((eventIDMatch == nil || eventIDMatch.boolValue) &&
- (qosTierMatch == nil || qosTierMatch.boolValue) &&
- (mappingIDMatch == nil || mappingIDMatch.boolValue)) {
- [paths addObject:filePath];
- }
- }
- completion(paths);
- });
- }
- - (void)nextBatchID:(void (^)(NSNumber *_Nullable batchID))nextBatchID {
- __block int32_t lastBatchID = -1;
- [self libraryDataForKey:gBatchIDCounterKey
- onFetchComplete:^(NSData *_Nullable data, NSError *_Nullable getValueError) {
- if (!getValueError) {
- [data getBytes:(void *)&lastBatchID length:sizeof(int32_t)];
- }
- if (data == nil) {
- lastBatchID = 0;
- }
- if (nextBatchID) {
- nextBatchID(@(lastBatchID));
- }
- }
- setNewValue:^NSData *_Nullable(void) {
- if (lastBatchID != -1) {
- int32_t incrementedValue = lastBatchID + 1;
- return [NSData dataWithBytes:&incrementedValue length:sizeof(int32_t)];
- }
- return nil;
- }];
- }
- - (nullable NSDictionary<NSString *, id> *)eventComponentsFromFilename:(NSString *)fileName {
- NSArray<NSString *> *components = [fileName componentsSeparatedByString:kMetadataSeparator];
- if (components.count >= 4) {
- NSString *eventID = components[0];
- NSNumber *qosTier = @(components[1].integerValue);
- NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:components[2].longLongValue];
- NSString *mappingID = [[components subarrayWithRange:NSMakeRange(3, components.count - 3)]
- componentsJoinedByString:kMetadataSeparator];
- if (eventID == nil || qosTier == nil || mappingID == nil || expirationDate == nil) {
- GDTCORLogDebug(@"There was an error parsing the event filename components: %@", components);
- return nil;
- }
- return @{
- kGDTCOREventComponentsEventIDKey : eventID,
- kGDTCOREventComponentsQoSTierKey : qosTier,
- kGDTCOREventComponentsExpirationKey : expirationDate,
- kGDTCOREventComponentsMappingIDKey : mappingID
- };
- }
- GDTCORLogDebug(@"The event filename could not be split: %@", fileName);
- return nil;
- }
- - (nullable NSDictionary<NSString *, id> *)batchComponentsFromFilename:(NSString *)fileName {
- NSArray<NSString *> *components = [fileName componentsSeparatedByString:kMetadataSeparator];
- if (components.count == 3) {
- NSNumber *target = @(components[0].integerValue);
- NSNumber *batchID = @(components[1].integerValue);
- NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:components[2].doubleValue];
- if (target == nil || batchID == nil || expirationDate == nil) {
- GDTCORLogDebug(@"There was an error parsing the batch filename components: %@", components);
- return nil;
- }
- return @{
- kGDTCORBatchComponentsTargetKey : target,
- kGDTCORBatchComponentsBatchIDKey : batchID,
- kGDTCORBatchComponentsExpirationKey : expirationDate
- };
- }
- GDTCORLogDebug(@"The batch filename could not be split: %@", fileName);
- return nil;
- }
- #pragma mark - GDTCORLifecycleProtocol
- - (void)appWillBackground:(GDTCORApplication *)app {
- dispatch_async(_storageQueue, ^{
- // Immediately request a background task to run until the end of the current queue of work,
- // and cancel it once the work is done.
- __block GDTCORBackgroundIdentifier bgID =
- [app beginBackgroundTaskWithName:@"GDTStorage"
- expirationHandler:^{
- [app endBackgroundTask:bgID];
- bgID = GDTCORBackgroundIdentifierInvalid;
- }];
- // End the background task if it's still valid.
- [app endBackgroundTask:bgID];
- bgID = GDTCORBackgroundIdentifierInvalid;
- });
- }
- - (void)appWillTerminate:(GDTCORApplication *)application {
- dispatch_sync(_storageQueue, ^{
- });
- }
- @end
- NS_ASSUME_NONNULL_END
|