AutorÃa | Ultima modificación | Ver Log |
/** 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 <TargetConditionals.h>#if TARGET_OS_IOS || TARGET_OS_TV#import <UIKit/UIKit.h>#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"#import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"#import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClearcutUploader.h"#import "FirebaseInAppMessaging/Sources/Private/Util/FIRIAMTimeFetcher.h"#import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutHttpRequestSender.h"#import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutLogStorage.h"// a macro for turning a millisecond value into seconds#define MILLS_TO_SECONDS(x) (((long)x) / 1000)@implementation FIRIAMClearcutStrategy- (instancetype)initWithMinWaitTimeInMills:(NSInteger)minWaitTimeInMillsmaxWaitTimeInMills:(NSInteger)maxWaitTimeInMillsfailureBackoffTimeInMills:(NSInteger)failureBackoffTimeInMillsbatchSendSize:(NSInteger)batchSendSize {if (self = [super init]) {_minimalWaitTimeInMills = minWaitTimeInMills;_maximumWaitTimeInMills = maxWaitTimeInMills;_failureBackoffTimeInMills = failureBackoffTimeInMills;_batchSendSize = batchSendSize;}return self;}- (NSString *)description {return [NSString stringWithFormat:@"min wait time in seconds:%ld;max wait time in seconds:%ld;""failure backoff time in seconds:%ld;batch send size:%d",MILLS_TO_SECONDS(self.minimalWaitTimeInMills),MILLS_TO_SECONDS(self.maximumWaitTimeInMills),MILLS_TO_SECONDS(self.failureBackoffTimeInMills),(int)self.batchSendSize];}@end@interface FIRIAMClearcutUploader () {dispatch_queue_t _queue;BOOL _nextSendScheduled;}@property(readwrite, nonatomic) FIRIAMClearcutHttpRequestSender *requestSender;@property(nonatomic, assign) int64_t nextValidSendTimeInMills;@property(nonatomic, readonly) id<FIRIAMTimeFetcher> timeFetcher;@property(nonatomic, readonly) FIRIAMClearcutLogStorage *logStorage;@property(nonatomic, readonly) FIRIAMClearcutStrategy *strategy;@property(nonatomic, readonly) NSUserDefaults *userDefaults;@endstatic NSString *FIRIAM_UserDefaultsKeyForNextValidClearcutUploadTimeInMills =@"firebase-iam-next-clearcut-upload-timestamp-in-mills";/*** The high level behavior in this implementation is like this* 1 New records always pushed into FIRIAMClearcutLogStorage first.* 2 Upload log records in batches.* 3 If prior upload was successful, next upload would wait for the time parsed out of the* clearcut response body.* 4 If prior upload failed, next upload attempt would wait for failureBackoffTimeInMills defined* in strategy* 5 When app*/@implementation FIRIAMClearcutUploader- (instancetype)initWithRequestSender:(FIRIAMClearcutHttpRequestSender *)requestSendertimeFetcher:(id<FIRIAMTimeFetcher>)timeFetcherlogStorage:(FIRIAMClearcutLogStorage *)logStorageusingStrategy:(FIRIAMClearcutStrategy *)strategyusingUserDefaults:(nullable NSUserDefaults *)userDefaults {if (self = [super init]) {_nextSendScheduled = NO;_timeFetcher = timeFetcher;_requestSender = requestSender;_logStorage = logStorage;_strategy = strategy;_queue = dispatch_queue_create("com.google.firebase.inappmessaging.clearcut_upload", NULL);[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(scheduleNextSendFromForeground:)name:UIApplicationWillEnterForegroundNotificationobject:nil];#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000if (@available(iOS 13.0, tvOS 13.0, *)) {[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(scheduleNextSendFromForeground:)name:UISceneWillEnterForegroundNotificationobject:nil];}#endif // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000_userDefaults = userDefaults ? userDefaults : [NSUserDefaults standardUserDefaults];// it would be 0 if it does not exist, which is equvilent to saying that// you can send now_nextValidSendTimeInMills = (int64_t)[_userDefaults doubleForKey:FIRIAM_UserDefaultsKeyForNextValidClearcutUploadTimeInMills];NSArray<FIRIAMClearcutLogRecord *> *availableLogs =[logStorage popStillValidRecordsForUpTo:strategy.batchSendSize];if (availableLogs.count) {[self scheduleNextSend];}FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260001",@"FIRIAMClearcutUploader created with strategy as %@", self.strategy);}return self;}- (void)dealloc {[[NSNotificationCenter defaultCenter] removeObserver:self];}- (void)scheduleNextSendFromForeground:(NSNotification *)notification {FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260010",@"App foregrounded, FIRIAMClearcutUploader will seed next send");[self scheduleNextSend];}- (void)addNewLogRecord:(FIRIAMClearcutLogRecord *)record {FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260002",@"New log record sent to clearcut uploader");[self.logStorage pushRecords:@[ record ]];[self scheduleNextSend];}- (void)attemptUploading {NSArray<FIRIAMClearcutLogRecord *> *availableLogs =[self.logStorage popStillValidRecordsForUpTo:self.strategy.batchSendSize];if (availableLogs.count > 0) {FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260011", @"Deliver %d clearcut records",(int)availableLogs.count);[self.requestSendersendClearcutHttpRequestForLogs:availableLogswithCompletion:^(BOOL success, BOOL shouldRetryLogs,int64_t waitTimeInMills) {if (success) {FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260003",@"Delivering %d clearcut records was successful",(int)availableLogs.count);// make sure the effective wait time is between two bounds// defined in strategywaitTimeInMills =MAX(self.strategy.minimalWaitTimeInMills, waitTimeInMills);waitTimeInMills =MIN(waitTimeInMills, self.strategy.maximumWaitTimeInMills);} else {// failed to deliverFIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260004",@"Failed to attempt the delivery of %d clearcut "@"records and should-retry for them is %@",(int)availableLogs.count, shouldRetryLogs ? @"YES" : @"NO");if (shouldRetryLogs) {/*** Note that there is a chance that the app crashes before we can* call pushRecords: on the logStorage below which means we lost* these log records permanently. This is a trade-off between handling* duplicate records on server side vs taking the risk of lossing* data. This implementation picks the latter.*/FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260007",@"Push failed log records back to storage");[self.logStorage pushRecords:availableLogs];}waitTimeInMills = (int64_t)self.strategy.failureBackoffTimeInMills;}FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260005",@"Wait for at least %ld seconds before next upload attempt",MILLS_TO_SECONDS(waitTimeInMills));self.nextValidSendTimeInMills =(int64_t)[self.timeFetcher currentTimestampInSeconds] * 1000 +waitTimeInMills;// persisted so that it can be recovered next time the app runs[self.userDefaultssetDouble:(double)self.nextValidSendTimeInMillsforKey:FIRIAM_UserDefaultsKeyForNextValidClearcutUploadTimeInMills];@synchronized(self) {self->_nextSendScheduled = NO;}[self scheduleNextSend];}];} else {FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260007", @"No clearcut records to be uploaded");@synchronized(self) {_nextSendScheduled = NO;}}}- (void)scheduleNextSend {@synchronized(self) {if (_nextSendScheduled) {return;}}int64_t delayTimeInMills =self.nextValidSendTimeInMills - (int64_t)[self.timeFetcher currentTimestampInSeconds] * 1000;if (delayTimeInMills <= 0) {delayTimeInMills = 0; // no need to delay since we can send now}FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260006",@"Next upload attempt scheduled in %d seconds", (int)delayTimeInMills / 1000);dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayTimeInMills * (int64_t)NSEC_PER_MSEC),_queue, ^{[self attemptUploading];});@synchronized(self) {_nextSendScheduled = YES;}}@end#endif // TARGET_OS_IOS || TARGET_OS_TV