AutorÃa | Ultima modificación | Ver Log |
// Copyright 2019 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 "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"#if __has_include(<FBLPromises/FBLPromises.h>)#import <FBLPromises/FBLPromises.h>#else#import "FBLPromises.h"#endif#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"#import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"#import "Crashlytics/Shared/FIRCLSConstants.h"#import "Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.h"NSString *const CreatedAtKey = @"created_at";NSString *const GoogleAppIDKey = @"google_app_id";NSString *const BuildInstanceID = @"build_instance_id";NSString *const AppVersion = @"app_version";@interface FIRCLSSettings ()@property(nonatomic, strong) FIRCLSFileManager *fileManager;@property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel;@property(nonatomic, strong) NSDictionary<NSString *, id> *settingsDictionary;@property(nonatomic) BOOL isCacheKeyExpired;@end@implementation FIRCLSSettings- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManagerappIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel {self = [super init];if (!self) {return nil;}_fileManager = fileManager;_appIDModel = appIDModel;_settingsDictionary = nil;_isCacheKeyExpired = NO;return self;}#pragma mark - Public Methods- (void)reloadFromCacheWithGoogleAppID:(NSString *)googleAppIDcurrentTimestamp:(NSTimeInterval)currentTimestamp {NSString *settingsFilePath = self.fileManager.settingsFilePath;NSData *data = [self.fileManager dataWithContentsOfFile:settingsFilePath];if (!data) {FIRCLSDebugLog(@"[Crashlytics:Settings] No settings were cached");return;}NSError *error = nil;@synchronized(self) {_settingsDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];}if (!_settingsDictionary) {FIRCLSErrorLog(@"Could not load settings file data with error: %@", error.localizedDescription);// Attempt to remove it, in case it's messed up[self deleteCachedSettings];return;}NSDictionary<NSString *, id> *cacheKey = [self loadCacheKey];if (!cacheKey) {FIRCLSErrorLog(@"Could not load settings cache key");[self deleteCachedSettings];return;}NSString *cachedGoogleAppID = cacheKey[GoogleAppIDKey];if (![cachedGoogleAppID isEqualToString:googleAppID]) {FIRCLSDebugLog(@"[Crashlytics:Settings] Invalidating settings cache because Google App ID changed");[self deleteCachedSettings];return;}NSTimeInterval cacheCreatedAt = [cacheKey[CreatedAtKey] unsignedIntValue];NSTimeInterval cacheDurationSeconds = self.cacheDurationSeconds;if (currentTimestamp > (cacheCreatedAt + cacheDurationSeconds)) {FIRCLSDebugLog(@"[Crashlytics:Settings] Settings TTL expired");@synchronized(self) {self.isCacheKeyExpired = YES;}}NSString *cacheBuildInstanceID = cacheKey[BuildInstanceID];if (![cacheBuildInstanceID isEqualToString:self.appIDModel.buildInstanceID]) {FIRCLSDebugLog(@"[Crashlytics:Settings] Settings expired because build instance changed");@synchronized(self) {self.isCacheKeyExpired = YES;}}NSString *cacheAppVersion = cacheKey[AppVersion];if (![cacheAppVersion isEqualToString:self.appIDModel.synthesizedVersion]) {FIRCLSDebugLog(@"[Crashlytics:Settings] Settings expired because app version changed");@synchronized(self) {self.isCacheKeyExpired = YES;}}}- (void)cacheSettingsWithGoogleAppID:(NSString *)googleAppIDcurrentTimestamp:(NSTimeInterval)currentTimestamp {NSNumber *createdAtTimestamp = [NSNumber numberWithDouble:currentTimestamp];NSDictionary *cacheKey = @{CreatedAtKey : createdAtTimestamp,GoogleAppIDKey : googleAppID,BuildInstanceID : self.appIDModel.buildInstanceID,AppVersion : self.appIDModel.synthesizedVersion,};NSError *error = nil;NSData *jsonData = [NSJSONSerialization dataWithJSONObject:cacheKeyoptions:kNilOptionserror:&error];if (!jsonData) {FIRCLSErrorLog(@"Could not create settings cache key with error: %@",error.localizedDescription);return;}if ([self.fileManager fileExistsAtPath:self.fileManager.settingsCacheKeyPath]) {[self.fileManager removeItemAtPath:self.fileManager.settingsCacheKeyPath];}[self.fileManager createFileAtPath:self.fileManager.settingsCacheKeyPathcontents:jsonDataattributes:nil];// If Settings were expired before, they should no longer be expired after this.// This may be set back to YES if reloading from the cache fails@synchronized(self) {self.isCacheKeyExpired = NO;}[self reloadFromCacheWithGoogleAppID:googleAppID currentTimestamp:currentTimestamp];}#pragma mark - Convenience Methods- (NSDictionary *)loadCacheKey {NSData *cacheKeyData =[self.fileManager dataWithContentsOfFile:self.fileManager.settingsCacheKeyPath];if (!cacheKeyData) {return nil;}NSError *error = nil;NSDictionary *cacheKey = [NSJSONSerialization JSONObjectWithData:cacheKeyDataoptions:NSJSONReadingAllowFragmentserror:&error];return cacheKey;}- (void)deleteCachedSettings {__weak FIRCLSSettings *weakSelf = self;dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{__strong FIRCLSSettings *strongSelf = weakSelf;if ([strongSelf.fileManager fileExistsAtPath:strongSelf.fileManager.settingsFilePath]) {[strongSelf.fileManager removeItemAtPath:strongSelf.fileManager.settingsFilePath];}if ([strongSelf.fileManager fileExistsAtPath:strongSelf.fileManager.settingsCacheKeyPath]) {[strongSelf.fileManager removeItemAtPath:strongSelf.fileManager.settingsCacheKeyPath];}});@synchronized(self) {self.isCacheKeyExpired = YES;_settingsDictionary = nil;}}- (NSDictionary<NSString *, id> *)settingsDictionary {@synchronized(self) {return _settingsDictionary;}}#pragma mark - Settings Groups- (NSDictionary<NSString *, id> *)appSettings {return self.settingsDictionary[@"app"];}- (NSDictionary<NSString *, id> *)sessionSettings {return self.settingsDictionary[@"session"];}- (NSDictionary<NSString *, id> *)featuresSettings {return self.settingsDictionary[@"features"];}- (NSDictionary<NSString *, id> *)fabricSettings {return self.settingsDictionary[@"fabric"];}#pragma mark - Caching- (BOOL)isCacheExpired {if (!self.settingsDictionary) {return YES;}@synchronized(self) {return self.isCacheKeyExpired;}}- (uint32_t)cacheDurationSeconds {id fetchedCacheDuration = self.settingsDictionary[@"cache_duration"];if (fetchedCacheDuration) {return [fetchedCacheDuration unsignedIntValue];}return 60 * 60;}#pragma mark - On / Off Switches- (BOOL)errorReportingEnabled {NSNumber *value = [self featuresSettings][@"collect_logged_exceptions"];if (value != nil) {return [value boolValue];}return YES;}- (BOOL)customExceptionsEnabled {// Right now, recording custom exceptions from the API and// automatically capturing non-fatal errors go hand in handreturn [self errorReportingEnabled];}- (BOOL)collectReportsEnabled {NSNumber *value = [self featuresSettings][@"collect_reports"];if (value != nil) {return value.boolValue;}return YES;}- (BOOL)metricKitCollectionEnabled {NSNumber *value = [self featuresSettings][@"collect_metric_kit"];if (value != nil) {return value.boolValue;}return NO;}#pragma mark - Optional Limit Overrides- (uint32_t)errorLogBufferSize {return [self logBufferSize];}- (uint32_t)logBufferSize {NSNumber *value = [self sessionSettings][@"log_buffer_size"];if (value != nil) {return value.unsignedIntValue;}return 64 * 1000;}- (uint32_t)maxCustomExceptions {NSNumber *value = [self sessionSettings][@"max_custom_exception_events"];if (value != nil) {return value.unsignedIntValue;}return 8;}- (uint32_t)maxCustomKeys {NSNumber *value = [self sessionSettings][@"max_custom_key_value_pairs"];if (value != nil) {return value.unsignedIntValue;}return 64;}#pragma mark - On Demand Reporting Parameters- (double)onDemandUploadRate {NSNumber *value = self.settingsDictionary[@"on_demand_upload_rate_per_minute"];if (value != nil) {return value.doubleValue;}return 10; // on-demand uploads allowed per minute}- (double)onDemandBackoffBase {NSNumber *value = self.settingsDictionary[@"on_demand_backoff_base"];if (value != nil) {return [value doubleValue];}return 1.5; // base of exponent for exponential backoff}- (uint32_t)onDemandBackoffStepDuration {NSNumber *value = self.settingsDictionary[@"on_demand_backoff_step_duration_seconds"];if (value != nil) {return value.unsignedIntValue;}return 6; // step duration for exponential backoff}@end