| 1 | 
           efrain | 
           1 | 
           /*
  | 
        
        
            | 
            | 
           2 | 
            * Copyright 2019 Google
  | 
        
        
            | 
            | 
           3 | 
            *
  | 
        
        
            | 
            | 
           4 | 
            * Licensed under the Apache License, Version 2.0 (the "License");
  | 
        
        
            | 
            | 
           5 | 
            * you may not use this file except in compliance with the License.
  | 
        
        
            | 
            | 
           6 | 
            * You may obtain a copy of the License at
  | 
        
        
            | 
            | 
           7 | 
            *
  | 
        
        
            | 
            | 
           8 | 
            *      http://www.apache.org/licenses/LICENSE-2.0
  | 
        
        
            | 
            | 
           9 | 
            *
  | 
        
        
            | 
            | 
           10 | 
            * Unless required by applicable law or agreed to in writing, software
  | 
        
        
            | 
            | 
           11 | 
            * distributed under the License is distributed on an "AS IS" BASIS,
  | 
        
        
            | 
            | 
           12 | 
            * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  | 
        
        
            | 
            | 
           13 | 
            * See the License for the specific language governing permissions and
  | 
        
        
            | 
            | 
           14 | 
            * limitations under the License.
  | 
        
        
            | 
            | 
           15 | 
            */
  | 
        
        
            | 
            | 
           16 | 
              | 
        
        
            | 
            | 
           17 | 
           #import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
  | 
        
        
            | 
            | 
           18 | 
              | 
        
        
            | 
            | 
           19 | 
           #import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
  | 
        
        
            | 
            | 
           20 | 
           #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
  | 
        
        
            | 
            | 
           21 | 
           #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
  | 
        
        
            | 
            | 
           22 | 
           #import "FirebaseRemoteConfig/Sources/RCNConfigDefines.h"
  | 
        
        
            | 
            | 
           23 | 
           #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
  | 
        
        
            | 
            | 
           24 | 
              | 
        
        
            | 
            | 
           25 | 
           #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  | 
        
        
            | 
            | 
           26 | 
              | 
        
        
            | 
            | 
           27 | 
           @implementation RCNConfigContent {
  | 
        
        
            | 
            | 
           28 | 
             /// Active config data that is currently used.
  | 
        
        
            | 
            | 
           29 | 
             NSMutableDictionary *_activeConfig;
  | 
        
        
            | 
            | 
           30 | 
             /// Pending config (aka Fetched config) data that is latest data from server that might or might
  | 
        
        
            | 
            | 
           31 | 
             /// not be applied.
  | 
        
        
            | 
            | 
           32 | 
             NSMutableDictionary *_fetchedConfig;
  | 
        
        
            | 
            | 
           33 | 
             /// Default config provided by user.
  | 
        
        
            | 
            | 
           34 | 
             NSMutableDictionary *_defaultConfig;
  | 
        
        
            | 
            | 
           35 | 
             /// Active Personalization metadata that is currently used.
  | 
        
        
            | 
            | 
           36 | 
             NSDictionary *_activePersonalization;
  | 
        
        
            | 
            | 
           37 | 
             /// Pending Personalization metadata that is latest data from server that might or might not be
  | 
        
        
            | 
            | 
           38 | 
             /// applied.
  | 
        
        
            | 
            | 
           39 | 
             NSDictionary *_fetchedPersonalization;
  | 
        
        
            | 
            | 
           40 | 
             /// DBManager
  | 
        
        
            | 
            | 
           41 | 
             RCNConfigDBManager *_DBManager;
  | 
        
        
            | 
            | 
           42 | 
             /// Current bundle identifier;
  | 
        
        
            | 
            | 
           43 | 
             NSString *_bundleIdentifier;
  | 
        
        
            | 
            | 
           44 | 
             /// Blocks all config reads until we have read from the database. This only
  | 
        
        
            | 
            | 
           45 | 
             /// potentially blocks on the first read. Should be a no-wait for all subsequent reads once we
  | 
        
        
            | 
            | 
           46 | 
             /// have data read into memory from the database.
  | 
        
        
            | 
            | 
           47 | 
             dispatch_group_t _dispatch_group;
  | 
        
        
            | 
            | 
           48 | 
             /// Boolean indicating if initial DB load of fetched,active and default config has succeeded.
  | 
        
        
            | 
            | 
           49 | 
             BOOL _isConfigLoadFromDBCompleted;
  | 
        
        
            | 
            | 
           50 | 
             /// Boolean indicating that the load from database has initiated at least once.
  | 
        
        
            | 
            | 
           51 | 
             BOOL _isDatabaseLoadAlreadyInitiated;
  | 
        
        
            | 
            | 
           52 | 
           }
  | 
        
        
            | 
            | 
           53 | 
              | 
        
        
            | 
            | 
           54 | 
           /// Default timeout when waiting to read data from database.
  | 
        
        
            | 
            | 
           55 | 
           const NSTimeInterval kDatabaseLoadTimeoutSecs = 30.0;
  | 
        
        
            | 
            | 
           56 | 
              | 
        
        
            | 
            | 
           57 | 
           /// Singleton instance of RCNConfigContent.
  | 
        
        
            | 
            | 
           58 | 
           + (instancetype)sharedInstance {
  | 
        
        
            | 
            | 
           59 | 
             static dispatch_once_t onceToken;
  | 
        
        
            | 
            | 
           60 | 
             static RCNConfigContent *sharedInstance;
  | 
        
        
            | 
            | 
           61 | 
             dispatch_once(&onceToken, ^{
  | 
        
        
            | 
            | 
           62 | 
               sharedInstance =
  | 
        
        
            | 
            | 
           63 | 
                   [[RCNConfigContent alloc] initWithDBManager:[RCNConfigDBManager sharedInstance]];
  | 
        
        
            | 
            | 
           64 | 
             });
  | 
        
        
            | 
            | 
           65 | 
             return sharedInstance;
  | 
        
        
            | 
            | 
           66 | 
           }
  | 
        
        
            | 
            | 
           67 | 
              | 
        
        
            | 
            | 
           68 | 
           - (instancetype)init {
  | 
        
        
            | 
            | 
           69 | 
             NSAssert(NO, @"Invalid initializer.");
  | 
        
        
            | 
            | 
           70 | 
             return nil;
  | 
        
        
            | 
            | 
           71 | 
           }
  | 
        
        
            | 
            | 
           72 | 
              | 
        
        
            | 
            | 
           73 | 
           /// Designated initializer
  | 
        
        
            | 
            | 
           74 | 
           - (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager {
  | 
        
        
            | 
            | 
           75 | 
             self = [super init];
  | 
        
        
            | 
            | 
           76 | 
             if (self) {
  | 
        
        
            | 
            | 
           77 | 
               _activeConfig = [[NSMutableDictionary alloc] init];
  | 
        
        
            | 
            | 
           78 | 
               _fetchedConfig = [[NSMutableDictionary alloc] init];
  | 
        
        
            | 
            | 
           79 | 
               _defaultConfig = [[NSMutableDictionary alloc] init];
  | 
        
        
            | 
            | 
           80 | 
               _activePersonalization = [[NSDictionary alloc] init];
  | 
        
        
            | 
            | 
           81 | 
               _fetchedPersonalization = [[NSDictionary alloc] init];
  | 
        
        
            | 
            | 
           82 | 
               _bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
  | 
        
        
            | 
            | 
           83 | 
               if (!_bundleIdentifier) {
  | 
        
        
            | 
            | 
           84 | 
                 FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038",
  | 
        
        
            | 
            | 
           85 | 
                              @"Main bundle identifier is missing. Remote Config might not work properly.");
  | 
        
        
            | 
            | 
           86 | 
                 _bundleIdentifier = @"";
  | 
        
        
            | 
            | 
           87 | 
               }
  | 
        
        
            | 
            | 
           88 | 
               _DBManager = DBManager;
  | 
        
        
            | 
            | 
           89 | 
               // Waits for both config and Personalization data to load.
  | 
        
        
            | 
            | 
           90 | 
               _dispatch_group = dispatch_group_create();
  | 
        
        
            | 
            | 
           91 | 
               [self loadConfigFromMainTable];
  | 
        
        
            | 
            | 
           92 | 
             }
  | 
        
        
            | 
            | 
           93 | 
             return self;
  | 
        
        
            | 
            | 
           94 | 
           }
  | 
        
        
            | 
            | 
           95 | 
              | 
        
        
            | 
            | 
           96 | 
           // Blocking call that returns true/false once database load completes / times out.
  | 
        
        
            | 
            | 
           97 | 
           // @return Initialization status.
  | 
        
        
            | 
            | 
           98 | 
           - (BOOL)initializationSuccessful {
  | 
        
        
            | 
            | 
           99 | 
             RCN_MUST_NOT_BE_MAIN_THREAD();
  | 
        
        
            | 
            | 
           100 | 
             BOOL isDatabaseLoadSuccessful = [self checkAndWaitForInitialDatabaseLoad];
  | 
        
        
            | 
            | 
           101 | 
             return isDatabaseLoadSuccessful;
  | 
        
        
            | 
            | 
           102 | 
           }
  | 
        
        
            | 
            | 
           103 | 
              | 
        
        
            | 
            | 
           104 | 
           #pragma mark - database
  | 
        
        
            | 
            | 
           105 | 
              | 
        
        
            | 
            | 
           106 | 
           /// This method is only meant to be called at init time. The underlying logic will need to be
  | 
        
        
            | 
            | 
           107 | 
           /// revaluated if the assumption changes at a later time.
  | 
        
        
            | 
            | 
           108 | 
           - (void)loadConfigFromMainTable {
  | 
        
        
            | 
            | 
           109 | 
             if (!_DBManager) {
  | 
        
        
            | 
            | 
           110 | 
               return;
  | 
        
        
            | 
            | 
           111 | 
             }
  | 
        
        
            | 
            | 
           112 | 
              | 
        
        
            | 
            | 
           113 | 
             NSAssert(!_isDatabaseLoadAlreadyInitiated, @"Database load has already been initiated");
  | 
        
        
            | 
            | 
           114 | 
             _isDatabaseLoadAlreadyInitiated = true;
  | 
        
        
            | 
            | 
           115 | 
              | 
        
        
            | 
            | 
           116 | 
             dispatch_group_enter(_dispatch_group);
  | 
        
        
            | 
            | 
           117 | 
             [_DBManager
  | 
        
        
            | 
            | 
           118 | 
                 loadMainWithBundleIdentifier:_bundleIdentifier
  | 
        
        
            | 
            | 
           119 | 
                            completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
  | 
        
        
            | 
            | 
           120 | 
                                                NSDictionary *activeConfig, NSDictionary *defaultConfig) {
  | 
        
        
            | 
            | 
           121 | 
                              self->_fetchedConfig = [fetchedConfig mutableCopy];
  | 
        
        
            | 
            | 
           122 | 
                              self->_activeConfig = [activeConfig mutableCopy];
  | 
        
        
            | 
            | 
           123 | 
                              self->_defaultConfig = [defaultConfig mutableCopy];
  | 
        
        
            | 
            | 
           124 | 
                              dispatch_group_leave(self->_dispatch_group);
  | 
        
        
            | 
            | 
           125 | 
                            }];
  | 
        
        
            | 
            | 
           126 | 
              | 
        
        
            | 
            | 
           127 | 
             // TODO(karenzeng): Refactor personalization to be returned in loadMainWithBundleIdentifier above
  | 
        
        
            | 
            | 
           128 | 
             dispatch_group_enter(_dispatch_group);
  | 
        
        
            | 
            | 
           129 | 
             [_DBManager loadPersonalizationWithCompletionHandler:^(
  | 
        
        
            | 
            | 
           130 | 
                             BOOL success, NSDictionary *fetchedPersonalization,
  | 
        
        
            | 
            | 
           131 | 
                             NSDictionary *activePersonalization, NSDictionary *defaultConfig) {
  | 
        
        
            | 
            | 
           132 | 
               self->_fetchedPersonalization = [fetchedPersonalization copy];
  | 
        
        
            | 
            | 
           133 | 
               self->_activePersonalization = [activePersonalization copy];
  | 
        
        
            | 
            | 
           134 | 
               dispatch_group_leave(self->_dispatch_group);
  | 
        
        
            | 
            | 
           135 | 
             }];
  | 
        
        
            | 
            | 
           136 | 
           }
  | 
        
        
            | 
            | 
           137 | 
              | 
        
        
            | 
            | 
           138 | 
           /// Update the current config result to main table.
  | 
        
        
            | 
            | 
           139 | 
           /// @param values Values in a row to write to the table.
  | 
        
        
            | 
            | 
           140 | 
           /// @param source The source the config data is coming from. It determines which table to write to.
  | 
        
        
            | 
            | 
           141 | 
           - (void)updateMainTableWithValues:(NSArray *)values fromSource:(RCNDBSource)source {
  | 
        
        
            | 
            | 
           142 | 
             [_DBManager insertMainTableWithValues:values fromSource:source completionHandler:nil];
  | 
        
        
            | 
            | 
           143 | 
           }
  | 
        
        
            | 
            | 
           144 | 
              | 
        
        
            | 
            | 
           145 | 
           #pragma mark - update
  | 
        
        
            | 
            | 
           146 | 
           /// This function is for copying dictionary when user set up a default config or when user clicks
  | 
        
        
            | 
            | 
           147 | 
           /// activate. For now the DBSource can only be Active or Default.
  | 
        
        
            | 
            | 
           148 | 
           - (void)copyFromDictionary:(NSDictionary *)fromDict
  | 
        
        
            | 
            | 
           149 | 
                             toSource:(RCNDBSource)DBSource
  | 
        
        
            | 
            | 
           150 | 
                         forNamespace:(NSString *)FIRNamespace {
  | 
        
        
            | 
            | 
           151 | 
             // Make sure database load has completed.
  | 
        
        
            | 
            | 
           152 | 
             [self checkAndWaitForInitialDatabaseLoad];
  | 
        
        
            | 
            | 
           153 | 
             NSMutableDictionary *toDict;
  | 
        
        
            | 
            | 
           154 | 
             if (!fromDict) {
  | 
        
        
            | 
            | 
           155 | 
               FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000007",
  | 
        
        
            | 
            | 
           156 | 
                           @"The source dictionary to copy from does not exist.");
  | 
        
        
            | 
            | 
           157 | 
               return;
  | 
        
        
            | 
            | 
           158 | 
             }
  | 
        
        
            | 
            | 
           159 | 
             FIRRemoteConfigSource source = FIRRemoteConfigSourceRemote;
  | 
        
        
            | 
            | 
           160 | 
             switch (DBSource) {
  | 
        
        
            | 
            | 
           161 | 
               case RCNDBSourceDefault:
  | 
        
        
            | 
            | 
           162 | 
                 toDict = _defaultConfig;
  | 
        
        
            | 
            | 
           163 | 
                 source = FIRRemoteConfigSourceDefault;
  | 
        
        
            | 
            | 
           164 | 
                 break;
  | 
        
        
            | 
            | 
           165 | 
               case RCNDBSourceFetched:
  | 
        
        
            | 
            | 
           166 | 
                 FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000008",
  | 
        
        
            | 
            | 
           167 | 
                               @"This shouldn't happen. Destination dictionary should never be pending type.");
  | 
        
        
            | 
            | 
           168 | 
                 return;
  | 
        
        
            | 
            | 
           169 | 
               case RCNDBSourceActive:
  | 
        
        
            | 
            | 
           170 | 
                 toDict = _activeConfig;
  | 
        
        
            | 
            | 
           171 | 
                 source = FIRRemoteConfigSourceRemote;
  | 
        
        
            | 
            | 
           172 | 
                 [toDict removeObjectForKey:FIRNamespace];
  | 
        
        
            | 
            | 
           173 | 
                 break;
  | 
        
        
            | 
            | 
           174 | 
               default:
  | 
        
        
            | 
            | 
           175 | 
                 toDict = _activeConfig;
  | 
        
        
            | 
            | 
           176 | 
                 source = FIRRemoteConfigSourceRemote;
  | 
        
        
            | 
            | 
           177 | 
                 [toDict removeObjectForKey:FIRNamespace];
  | 
        
        
            | 
            | 
           178 | 
                 break;
  | 
        
        
            | 
            | 
           179 | 
             }
  | 
        
        
            | 
            | 
           180 | 
              | 
        
        
            | 
            | 
           181 | 
             // Completely wipe out DB first.
  | 
        
        
            | 
            | 
           182 | 
             [_DBManager deleteRecordFromMainTableWithNamespace:FIRNamespace
  | 
        
        
            | 
            | 
           183 | 
                                               bundleIdentifier:_bundleIdentifier
  | 
        
        
            | 
            | 
           184 | 
                                                     fromSource:DBSource];
  | 
        
        
            | 
            | 
           185 | 
              | 
        
        
            | 
            | 
           186 | 
             toDict[FIRNamespace] = [[NSMutableDictionary alloc] init];
  | 
        
        
            | 
            | 
           187 | 
             NSDictionary *config = fromDict[FIRNamespace];
  | 
        
        
            | 
            | 
           188 | 
             for (NSString *key in config) {
  | 
        
        
            | 
            | 
           189 | 
               if (DBSource == FIRRemoteConfigSourceDefault) {
  | 
        
        
            | 
            | 
           190 | 
                 NSObject *value = config[key];
  | 
        
        
            | 
            | 
           191 | 
                 NSData *valueData;
  | 
        
        
            | 
            | 
           192 | 
                 if ([value isKindOfClass:[NSData class]]) {
  | 
        
        
            | 
            | 
           193 | 
                   valueData = (NSData *)value;
  | 
        
        
            | 
            | 
           194 | 
                 } else if ([value isKindOfClass:[NSString class]]) {
  | 
        
        
            | 
            | 
           195 | 
                   valueData = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding];
  | 
        
        
            | 
            | 
           196 | 
                 } else if ([value isKindOfClass:[NSNumber class]]) {
  | 
        
        
            | 
            | 
           197 | 
                   NSString *strValue = [(NSNumber *)value stringValue];
  | 
        
        
            | 
            | 
           198 | 
                   valueData = [(NSString *)strValue dataUsingEncoding:NSUTF8StringEncoding];
  | 
        
        
            | 
            | 
           199 | 
                 } else if ([value isKindOfClass:[NSDate class]]) {
  | 
        
        
            | 
            | 
           200 | 
                   NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  | 
        
        
            | 
            | 
           201 | 
                   [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  | 
        
        
            | 
            | 
           202 | 
                   NSString *strValue = [dateFormatter stringFromDate:(NSDate *)value];
  | 
        
        
            | 
            | 
           203 | 
                   valueData = [(NSString *)strValue dataUsingEncoding:NSUTF8StringEncoding];
  | 
        
        
            | 
            | 
           204 | 
                 } else {
  | 
        
        
            | 
            | 
           205 | 
                   continue;
  | 
        
        
            | 
            | 
           206 | 
                 }
  | 
        
        
            | 
            | 
           207 | 
                 toDict[FIRNamespace][key] = [[FIRRemoteConfigValue alloc] initWithData:valueData
  | 
        
        
            | 
            | 
           208 | 
                                                                                 source:source];
  | 
        
        
            | 
            | 
           209 | 
                 NSArray *values = @[ _bundleIdentifier, FIRNamespace, key, valueData ];
  | 
        
        
            | 
            | 
           210 | 
                 [self updateMainTableWithValues:values fromSource:DBSource];
  | 
        
        
            | 
            | 
           211 | 
               } else {
  | 
        
        
            | 
            | 
           212 | 
                 FIRRemoteConfigValue *value = config[key];
  | 
        
        
            | 
            | 
           213 | 
                 toDict[FIRNamespace][key] = [[FIRRemoteConfigValue alloc] initWithData:value.dataValue
  | 
        
        
            | 
            | 
           214 | 
                                                                                 source:source];
  | 
        
        
            | 
            | 
           215 | 
                 NSArray *values = @[ _bundleIdentifier, FIRNamespace, key, value.dataValue ];
  | 
        
        
            | 
            | 
           216 | 
                 [self updateMainTableWithValues:values fromSource:DBSource];
  | 
        
        
            | 
            | 
           217 | 
               }
  | 
        
        
            | 
            | 
           218 | 
             }
  | 
        
        
            | 
            | 
           219 | 
           }
  | 
        
        
            | 
            | 
           220 | 
              | 
        
        
            | 
            | 
           221 | 
           - (void)updateConfigContentWithResponse:(NSDictionary *)response
  | 
        
        
            | 
            | 
           222 | 
                                      forNamespace:(NSString *)currentNamespace {
  | 
        
        
            | 
            | 
           223 | 
             // Make sure database load has completed.
  | 
        
        
            | 
            | 
           224 | 
             [self checkAndWaitForInitialDatabaseLoad];
  | 
        
        
            | 
            | 
           225 | 
             NSString *state = response[RCNFetchResponseKeyState];
  | 
        
        
            | 
            | 
           226 | 
              | 
        
        
            | 
            | 
           227 | 
             if (!state) {
  | 
        
        
            | 
            | 
           228 | 
               FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000049", @"State field in fetch response is nil.");
  | 
        
        
            | 
            | 
           229 | 
               return;
  | 
        
        
            | 
            | 
           230 | 
             }
  | 
        
        
            | 
            | 
           231 | 
             FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000059",
  | 
        
        
            | 
            | 
           232 | 
                         @"Updating config content from Response for namespace:%@ with state: %@",
  | 
        
        
            | 
            | 
           233 | 
                         currentNamespace, response[RCNFetchResponseKeyState]);
  | 
        
        
            | 
            | 
           234 | 
              | 
        
        
            | 
            | 
           235 | 
             if ([state isEqualToString:RCNFetchResponseKeyStateNoChange]) {
  | 
        
        
            | 
            | 
           236 | 
               [self handleNoChangeStateForConfigNamespace:currentNamespace];
  | 
        
        
            | 
            | 
           237 | 
               return;
  | 
        
        
            | 
            | 
           238 | 
             }
  | 
        
        
            | 
            | 
           239 | 
              | 
        
        
            | 
            | 
           240 | 
             /// Handle empty config state
  | 
        
        
            | 
            | 
           241 | 
             if ([state isEqualToString:RCNFetchResponseKeyStateEmptyConfig]) {
  | 
        
        
            | 
            | 
           242 | 
               [self handleEmptyConfigStateForConfigNamespace:currentNamespace];
  | 
        
        
            | 
            | 
           243 | 
               return;
  | 
        
        
            | 
            | 
           244 | 
             }
  | 
        
        
            | 
            | 
           245 | 
              | 
        
        
            | 
            | 
           246 | 
             /// Handle no template state.
  | 
        
        
            | 
            | 
           247 | 
             if ([state isEqualToString:RCNFetchResponseKeyStateNoTemplate]) {
  | 
        
        
            | 
            | 
           248 | 
               [self handleNoTemplateStateForConfigNamespace:currentNamespace];
  | 
        
        
            | 
            | 
           249 | 
               return;
  | 
        
        
            | 
            | 
           250 | 
             }
  | 
        
        
            | 
            | 
           251 | 
              | 
        
        
            | 
            | 
           252 | 
             /// Handle update state
  | 
        
        
            | 
            | 
           253 | 
             if ([state isEqualToString:RCNFetchResponseKeyStateUpdate]) {
  | 
        
        
            | 
            | 
           254 | 
               [self handleUpdateStateForConfigNamespace:currentNamespace
  | 
        
        
            | 
            | 
           255 | 
                                             withEntries:response[RCNFetchResponseKeyEntries]];
  | 
        
        
            | 
            | 
           256 | 
               [self handleUpdatePersonalization:response[RCNFetchResponseKeyPersonalizationMetadata]];
  | 
        
        
            | 
            | 
           257 | 
               return;
  | 
        
        
            | 
            | 
           258 | 
             }
  | 
        
        
            | 
            | 
           259 | 
           }
  | 
        
        
            | 
            | 
           260 | 
              | 
        
        
            | 
            | 
           261 | 
           - (void)activatePersonalization {
  | 
        
        
            | 
            | 
           262 | 
             _activePersonalization = _fetchedPersonalization;
  | 
        
        
            | 
            | 
           263 | 
             [_DBManager insertOrUpdatePersonalizationConfig:_activePersonalization
  | 
        
        
            | 
            | 
           264 | 
                                                  fromSource:RCNDBSourceActive];
  | 
        
        
            | 
            | 
           265 | 
           }
  | 
        
        
            | 
            | 
           266 | 
              | 
        
        
            | 
            | 
           267 | 
           #pragma mark State handling
  | 
        
        
            | 
            | 
           268 | 
           - (void)handleNoChangeStateForConfigNamespace:(NSString *)currentNamespace {
  | 
        
        
            | 
            | 
           269 | 
             if (!_fetchedConfig[currentNamespace]) {
  | 
        
        
            | 
            | 
           270 | 
               _fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
  | 
        
        
            | 
            | 
           271 | 
             }
  | 
        
        
            | 
            | 
           272 | 
           }
  | 
        
        
            | 
            | 
           273 | 
              | 
        
        
            | 
            | 
           274 | 
           - (void)handleEmptyConfigStateForConfigNamespace:(NSString *)currentNamespace {
  | 
        
        
            | 
            | 
           275 | 
             if (_fetchedConfig[currentNamespace]) {
  | 
        
        
            | 
            | 
           276 | 
               [_fetchedConfig[currentNamespace] removeAllObjects];
  | 
        
        
            | 
            | 
           277 | 
             } else {
  | 
        
        
            | 
            | 
           278 | 
               // If namespace has empty status and it doesn't exist in _fetchedConfig, we will
  | 
        
        
            | 
            | 
           279 | 
               // still add an entry for that namespace. Even if it will not be persisted in database.
  | 
        
        
            | 
            | 
           280 | 
               // TODO: Add generics for all collection types.
  | 
        
        
            | 
            | 
           281 | 
               _fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
  | 
        
        
            | 
            | 
           282 | 
             }
  | 
        
        
            | 
            | 
           283 | 
             [_DBManager deleteRecordFromMainTableWithNamespace:currentNamespace
  | 
        
        
            | 
            | 
           284 | 
                                               bundleIdentifier:_bundleIdentifier
  | 
        
        
            | 
            | 
           285 | 
                                                     fromSource:RCNDBSourceFetched];
  | 
        
        
            | 
            | 
           286 | 
           }
  | 
        
        
            | 
            | 
           287 | 
              | 
        
        
            | 
            | 
           288 | 
           - (void)handleNoTemplateStateForConfigNamespace:(NSString *)currentNamespace {
  | 
        
        
            | 
            | 
           289 | 
             // Remove the namespace.
  | 
        
        
            | 
            | 
           290 | 
             [_fetchedConfig removeObjectForKey:currentNamespace];
  | 
        
        
            | 
            | 
           291 | 
             [_DBManager deleteRecordFromMainTableWithNamespace:currentNamespace
  | 
        
        
            | 
            | 
           292 | 
                                               bundleIdentifier:_bundleIdentifier
  | 
        
        
            | 
            | 
           293 | 
                                                     fromSource:RCNDBSourceFetched];
  | 
        
        
            | 
            | 
           294 | 
           }
  | 
        
        
            | 
            | 
           295 | 
           - (void)handleUpdateStateForConfigNamespace:(NSString *)currentNamespace
  | 
        
        
            | 
            | 
           296 | 
                                           withEntries:(NSDictionary *)entries {
  | 
        
        
            | 
            | 
           297 | 
             FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058", @"Update config in DB for namespace:%@",
  | 
        
        
            | 
            | 
           298 | 
                         currentNamespace);
  | 
        
        
            | 
            | 
           299 | 
             // Clear before updating
  | 
        
        
            | 
            | 
           300 | 
             [_DBManager deleteRecordFromMainTableWithNamespace:currentNamespace
  | 
        
        
            | 
            | 
           301 | 
                                               bundleIdentifier:_bundleIdentifier
  | 
        
        
            | 
            | 
           302 | 
                                                     fromSource:RCNDBSourceFetched];
  | 
        
        
            | 
            | 
           303 | 
             if ([_fetchedConfig objectForKey:currentNamespace]) {
  | 
        
        
            | 
            | 
           304 | 
               [_fetchedConfig[currentNamespace] removeAllObjects];
  | 
        
        
            | 
            | 
           305 | 
             } else {
  | 
        
        
            | 
            | 
           306 | 
               _fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
  | 
        
        
            | 
            | 
           307 | 
             }
  | 
        
        
            | 
            | 
           308 | 
              | 
        
        
            | 
            | 
           309 | 
             // Store the fetched config values.
  | 
        
        
            | 
            | 
           310 | 
             for (NSString *key in entries) {
  | 
        
        
            | 
            | 
           311 | 
               NSData *valueData = [entries[key] dataUsingEncoding:NSUTF8StringEncoding];
  | 
        
        
            | 
            | 
           312 | 
               if (!valueData) {
  | 
        
        
            | 
            | 
           313 | 
                 continue;
  | 
        
        
            | 
            | 
           314 | 
               }
  | 
        
        
            | 
            | 
           315 | 
               _fetchedConfig[currentNamespace][key] =
  | 
        
        
            | 
            | 
           316 | 
                   [[FIRRemoteConfigValue alloc] initWithData:valueData source:FIRRemoteConfigSourceRemote];
  | 
        
        
            | 
            | 
           317 | 
               NSArray *values = @[ _bundleIdentifier, currentNamespace, key, valueData ];
  | 
        
        
            | 
            | 
           318 | 
               [self updateMainTableWithValues:values fromSource:RCNDBSourceFetched];
  | 
        
        
            | 
            | 
           319 | 
             }
  | 
        
        
            | 
            | 
           320 | 
           }
  | 
        
        
            | 
            | 
           321 | 
              | 
        
        
            | 
            | 
           322 | 
           - (void)handleUpdatePersonalization:(NSDictionary *)metadata {
  | 
        
        
            | 
            | 
           323 | 
             if (!metadata) {
  | 
        
        
            | 
            | 
           324 | 
               return;
  | 
        
        
            | 
            | 
           325 | 
             }
  | 
        
        
            | 
            | 
           326 | 
             _fetchedPersonalization = metadata;
  | 
        
        
            | 
            | 
           327 | 
             [_DBManager insertOrUpdatePersonalizationConfig:metadata fromSource:RCNDBSourceFetched];
  | 
        
        
            | 
            | 
           328 | 
           }
  | 
        
        
            | 
            | 
           329 | 
              | 
        
        
            | 
            | 
           330 | 
           #pragma mark - getter/setter
  | 
        
        
            | 
            | 
           331 | 
           - (NSDictionary *)fetchedConfig {
  | 
        
        
            | 
            | 
           332 | 
             /// If this is the first time reading the fetchedConfig, we might still be reading it from the
  | 
        
        
            | 
            | 
           333 | 
             /// database.
  | 
        
        
            | 
            | 
           334 | 
             [self checkAndWaitForInitialDatabaseLoad];
  | 
        
        
            | 
            | 
           335 | 
             return _fetchedConfig;
  | 
        
        
            | 
            | 
           336 | 
           }
  | 
        
        
            | 
            | 
           337 | 
              | 
        
        
            | 
            | 
           338 | 
           - (NSDictionary *)activeConfig {
  | 
        
        
            | 
            | 
           339 | 
             /// If this is the first time reading the activeConfig, we might still be reading it from the
  | 
        
        
            | 
            | 
           340 | 
             /// database.
  | 
        
        
            | 
            | 
           341 | 
             [self checkAndWaitForInitialDatabaseLoad];
  | 
        
        
            | 
            | 
           342 | 
             return _activeConfig;
  | 
        
        
            | 
            | 
           343 | 
           }
  | 
        
        
            | 
            | 
           344 | 
              | 
        
        
            | 
            | 
           345 | 
           - (NSDictionary *)defaultConfig {
  | 
        
        
            | 
            | 
           346 | 
             /// If this is the first time reading the fetchedConfig, we might still be reading it from the
  | 
        
        
            | 
            | 
           347 | 
             /// database.
  | 
        
        
            | 
            | 
           348 | 
             [self checkAndWaitForInitialDatabaseLoad];
  | 
        
        
            | 
            | 
           349 | 
             return _defaultConfig;
  | 
        
        
            | 
            | 
           350 | 
           }
  | 
        
        
            | 
            | 
           351 | 
              | 
        
        
            | 
            | 
           352 | 
           - (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace {
  | 
        
        
            | 
            | 
           353 | 
             /// If this is the first time reading the active metadata, we might still be reading it from the
  | 
        
        
            | 
            | 
           354 | 
             /// database.
  | 
        
        
            | 
            | 
           355 | 
             [self checkAndWaitForInitialDatabaseLoad];
  | 
        
        
            | 
            | 
           356 | 
             return @{
  | 
        
        
            | 
            | 
           357 | 
               RCNFetchResponseKeyEntries : _activeConfig[FIRNamespace],
  | 
        
        
            | 
            | 
           358 | 
               RCNFetchResponseKeyPersonalizationMetadata : _activePersonalization
  | 
        
        
            | 
            | 
           359 | 
             };
  | 
        
        
            | 
            | 
           360 | 
           }
  | 
        
        
            | 
            | 
           361 | 
              | 
        
        
            | 
            | 
           362 | 
           /// We load the database async at init time. Block all further calls to active/fetched/default
  | 
        
        
            | 
            | 
           363 | 
           /// configs until load is done.
  | 
        
        
            | 
            | 
           364 | 
           /// @return Database load completion status.
  | 
        
        
            | 
            | 
           365 | 
           - (BOOL)checkAndWaitForInitialDatabaseLoad {
  | 
        
        
            | 
            | 
           366 | 
             /// Wait until load is done. This should be a no-op for subsequent calls.
  | 
        
        
            | 
            | 
           367 | 
             if (!_isConfigLoadFromDBCompleted) {
  | 
        
        
            | 
            | 
           368 | 
               intptr_t isErrorOrTimeout = dispatch_group_wait(
  | 
        
        
            | 
            | 
           369 | 
                   _dispatch_group,
  | 
        
        
            | 
            | 
           370 | 
                   dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDatabaseLoadTimeoutSecs * NSEC_PER_SEC)));
  | 
        
        
            | 
            | 
           371 | 
               if (isErrorOrTimeout) {
  | 
        
        
            | 
            | 
           372 | 
                 FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000048",
  | 
        
        
            | 
            | 
           373 | 
                             @"Timed out waiting for fetched config to be loaded from DB");
  | 
        
        
            | 
            | 
           374 | 
                 return false;
  | 
        
        
            | 
            | 
           375 | 
               }
  | 
        
        
            | 
            | 
           376 | 
               _isConfigLoadFromDBCompleted = true;
  | 
        
        
            | 
            | 
           377 | 
             }
  | 
        
        
            | 
            | 
           378 | 
             return true;
  | 
        
        
            | 
            | 
           379 | 
           }
  | 
        
        
            | 
            | 
           380 | 
              | 
        
        
            | 
            | 
           381 | 
           @end
  |