From db073f0690b9d2b53c5233d36868ad298e3cdfb2 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Fri, 5 Feb 2021 16:43:51 -0500 Subject: [PATCH] WIP: GDTCORStoragePromiseAdapter --- .../GDTCORLibrary/GDTCORRegistrar.m | 4 +- .../GDTCORStoragePromiseAdapter.m | 167 ++++++++++++++++++ .../Internal/GDTCORStoragePromiseAdapter.h | 31 ++++ .../Internal/GDTCORStorageProtocol.h | 5 + 4 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 GoogleDataTransport/GDTCORLibrary/GDTCORStoragePromiseAdapter.m create mode 100644 GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStoragePromiseAdapter.h diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m index d7e9b09..1c01c13 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m @@ -18,6 +18,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStoragePromiseAdapter.h" id _Nullable GDTCORStorageInstanceForTarget(GDTCORTarget target) { return [GDTCORRegistrar sharedInstance].targetToStorage[@(target)]; @@ -78,7 +79,8 @@ id _Nullable GDTCORStoragePromiseInstanceForTarget GDTCORRegistrar *strongSelf = weakSelf; if (strongSelf) { GDTCORLogDebug(@"Registered storage: %@ for target:%ld", storage, (long)target); - strongSelf->_targetToStorage[@(target)] = storage; + GDTCORStoragePromiseAdapter *storageAdapter = [[GDTCORStoragePromiseAdapter alloc] initWithStorage:storage]; + strongSelf->_targetToStorage[@(target)] = storageAdapter; } }); } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORStoragePromiseAdapter.m b/GoogleDataTransport/GDTCORLibrary/GDTCORStoragePromiseAdapter.m new file mode 100644 index 0000000..ef531e4 --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORStoragePromiseAdapter.m @@ -0,0 +1,167 @@ +/* + * Copyright 2021 Google LLC + * + * 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/Internal/GDTCORStoragePromiseAdapter.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORStoragePromiseAdapter () + +@property(nonatomic, readonly) dispatch_queue_t storageQueue; + +@end + +@implementation GDTCORStoragePromiseAdapter + +- (instancetype)initWithStorage:(id)storage { + self = [super init]; + if (self) { + _storage = storage; + if ([storage respondsToSelector:@selector(storageQueue)]) { + _storageQueue = [storage storageQueue]; + } else { + _storageQueue = dispatch_queue_create("GDTCORStoragePromiseAdapter", DISPATCH_QUEUE_SERIAL); + } + } + return self; +} + +- (FBLPromise *> *)batchIDsForTarget:(GDTCORTarget)target { + return [FBLPromise onQueue:self.storageQueue + wrapObjectCompletion:^(FBLPromiseObjectCompletion _Nonnull handler) { + [self.storage batchIDsForTarget:target onComplete:handler]; + }]; +} + +- (FBLPromise *)removeBatchWithID:(NSNumber *)batchID deleteEvents:(BOOL)deleteEvents { + return [FBLPromise onQueue:self.storageQueue + wrapCompletion:^(FBLPromiseCompletion _Nonnull handler) { + [self.storage removeBatchWithID:batchID deleteEvents:deleteEvents onComplete:handler]; + }]; +} + +- (FBLPromise *)removeBatchesWithIDs:(NSSet *)batchIDs + deleteEvents:(BOOL)deleteEvents { + NSMutableArray *removeBatchPromises = + [NSMutableArray arrayWithCapacity:batchIDs.count]; + for (NSNumber *batchID in batchIDs) { + [removeBatchPromises addObject:[self removeBatchWithID:batchID deleteEvents:deleteEvents]]; + } + + return [FBLPromise onQueue:self.storageQueue all:[removeBatchPromises copy]].thenOn( + self.storageQueue, ^id(id result) { + return [FBLPromise resolvedWith:[NSNull null]]; + }); +} + +- (FBLPromise *)removeAllBatchesForTarget:(GDTCORTarget)target + deleteEvents:(BOOL)deleteEvents { + return + [self batchIDsForTarget:target].thenOn(self.storageQueue, ^id(NSSet *batchIDs) { + return [self removeBatchesWithIDs:batchIDs deleteEvents:NO]; + }); +} + +- (FBLPromise *)hasEventsForTarget:(GDTCORTarget)target { + return [FBLPromise onQueue:self.storageQueue + wrapBoolCompletion:^(FBLPromiseBoolCompletion _Nonnull handler) { + [self.storage hasEventsForTarget:target onComplete:handler]; + }]; +} + +- (FBLPromise *)batchWithEventSelector: + (GDTCORStorageEventSelector *)eventSelector + batchExpiration:(NSDate *)expiration { + return [FBLPromise + onQueue:self.storageQueue + async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) { + [self.storage batchWithEventSelector:eventSelector + batchExpiration:expiration + onComplete:^(NSNumber *_Nullable newBatchID, + NSSet *_Nullable batchEvents) { + if (newBatchID == nil || batchEvents == nil) { + reject([self genericRejectedPromiseErrorWithReason: + @"There are no events for the selector."]); + } else { + fulfill([[GDTCORUploadBatch alloc] initWithBatchID:newBatchID + events:batchEvents]); + } + }]; + }]; +} + +// TODO: More comprehensive error. +- (NSError *)genericRejectedPromiseErrorWithReason:(NSString *)reason { + return [NSError errorWithDomain:@"GDTCORFlatFileStorage" + code:-1 + userInfo:@{NSLocalizedFailureReasonErrorKey : reason}]; +} + +#pragma mark - + +- (void)storeEvent:(nonnull GDTCOREvent *)event onComplete:(void (^ _Nullable)(BOOL, NSError * _Nullable))completion { + [self.storage storeEvent:event onComplete:completion]; +} + +- (void)hasEventsForTarget:(GDTCORTarget)target onComplete:(nonnull void (^)(BOOL))onComplete { + [self.storage hasEventsForTarget:target onComplete:onComplete]; +} + +- (void)batchWithEventSelector:(nonnull GDTCORStorageEventSelector *)eventSelector batchExpiration:(nonnull NSDate *)expiration onComplete:(nonnull GDTCORStorageBatchBlock)onComplete { + [self.storage batchWithEventSelector:eventSelector batchExpiration:expiration onComplete:onComplete]; +} + +- (void)removeBatchWithID:(nonnull NSNumber *)batchID deleteEvents:(BOOL)deleteEvents onComplete:(void (^ _Nullable)(void))onComplete { + [self.storage removeBatchWithID:batchID deleteEvents:deleteEvents onComplete:onComplete]; +} + +- (void)batchIDsForTarget:(GDTCORTarget)target onComplete:(nonnull void (^)(NSSet * _Nullable))onComplete { + [self.storage batchIDsForTarget:target onComplete:onComplete]; +} + +- (void)checkForExpirations { + [self.storage checkForExpirations]; +} + +- (void)storeLibraryData:(nonnull NSData *)data forKey:(nonnull NSString *)key onComplete:(nullable void (^)(NSError * _Nullable))onComplete { + [self.storage storeLibraryData:data forKey:key onComplete:onComplete]; +} + +- (void)libraryDataForKey:(nonnull NSString *)key onFetchComplete:(nonnull void (^)(NSData * _Nullable, NSError * _Nullable))onFetchComplete setNewValue:(NSData * _Nullable (^ _Nullable)(void))setValueBlock { + [self.storage libraryDataForKey:key onFetchComplete:onFetchComplete setNewValue:setValueBlock]; +} + + +- (void)removeLibraryDataForKey:(nonnull NSString *)key onComplete:(nonnull void (^)(NSError * _Nullable))onComplete { + [self.storage removeLibraryDataForKey:key onComplete:onComplete]; +} + + +- (void)storageSizeWithCallback:(nonnull void (^)(GDTCORStorageSizeBytes))onComplete { + [self.storage storageSizeWithCallback:onComplete]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStoragePromiseAdapter.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStoragePromiseAdapter.h new file mode 100644 index 0000000..0b3ee58 --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStoragePromiseAdapter.h @@ -0,0 +1,31 @@ +/* + * Copyright 2021 Google LLC + * + * 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 + +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORStoragePromiseAdapter : NSObject + +@property(nonatomic, readonly) id storage; + +- (instancetype)initWithStorage:(id)storage; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h index 14dc268..b8c5a4b 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h @@ -121,6 +121,11 @@ typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, */ - (void)storageSizeWithCallback:(void (^)(GDTCORStorageSizeBytes storageSize))onComplete; +@optional + +/** A dispatch queue to sync storage operations. For internal use only, e.g. tests or extensions like GDTCORStoragePromiseAdapter. */ +- (dispatch_queue_t)storageQueue; + @end // TODO: Consider a different place for this interface.