| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- /*
- * Copyright 2017 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 <FirebaseCore/FIRLogger.h>
- #import "FPersistenceManager.h"
- #import "FLevelDBStorageEngine.h"
- #import "FCacheNode.h"
- #import "FIndexedNode.h"
- #import "FTrackedQueryManager.h"
- #import "FTrackedQuery.h"
- #import "FUtilities.h"
- #import "FPruneForest.h"
- #import "FClock.h"
- @interface FPersistenceManager ()
- @property (nonatomic, strong) id<FStorageEngine> storageEngine;
- @property (nonatomic, strong) id<FCachePolicy> cachePolicy;
- @property (nonatomic, strong) FTrackedQueryManager *trackedQueryManager;
- @property (nonatomic) NSUInteger serverCacheUpdatesSinceLastPruneCheck;
- @end
- @implementation FPersistenceManager
- - (id)initWithStorageEngine:(id<FStorageEngine>)storageEngine cachePolicy:(id<FCachePolicy>)cachePolicy {
- self = [super init];
- if (self != nil) {
- self->_storageEngine = storageEngine;
- self->_cachePolicy = cachePolicy;
- self->_trackedQueryManager = [[FTrackedQueryManager alloc] initWithStorageEngine:self.storageEngine
- clock:[FSystemClock clock]];
- }
- return self;
- }
- - (void)close {
- [self.storageEngine close];
- self.storageEngine = nil;
- self.trackedQueryManager = nil;
- }
- - (void)saveUserOverwrite:(id<FNode>)node atPath:(FPath *)path writeId:(NSUInteger)writeId {
- [self.storageEngine saveUserOverwrite:node atPath:path writeId:writeId];
- }
- - (void)saveUserMerge:(FCompoundWrite *)merge atPath:(FPath *)path writeId:(NSUInteger)writeId {
- [self.storageEngine saveUserMerge:merge atPath:path writeId:writeId];
- }
- - (void)removeUserWrite:(NSUInteger)writeId {
- [self.storageEngine removeUserWrite:writeId];
- }
- - (void)removeAllUserWrites {
- [self.storageEngine removeAllUserWrites];
- }
- - (NSArray *)userWrites {
- return [self.storageEngine userWrites];
- }
- - (FCacheNode *)serverCacheForQuery:(FQuerySpec *)query {
- NSSet *trackedKeys;
- BOOL complete;
- // TODO[offline]: Should we use trackedKeys to find out if this location is a child of a complete query?
- if ([self.trackedQueryManager isQueryComplete:query]) {
- complete = YES;
- FTrackedQuery *trackedQuery = [self.trackedQueryManager findTrackedQuery:query];
- if (!query.loadsAllData && trackedQuery.isComplete) {
- trackedKeys = [self.storageEngine trackedQueryKeysForQuery:trackedQuery.queryId];
- } else {
- trackedKeys = nil;
- }
- } else {
- complete = NO;
- trackedKeys = [self.trackedQueryManager knownCompleteChildrenAtPath:query.path];
- }
- id<FNode> node;
- if (trackedKeys != nil) {
- node = [self.storageEngine serverCacheForKeys:trackedKeys atPath:query.path];
- } else {
- node = [self.storageEngine serverCacheAtPath:query.path];
- }
- FIndexedNode *indexedNode = [FIndexedNode indexedNodeWithNode:node index:query.index];
- return [[FCacheNode alloc] initWithIndexedNode:indexedNode isFullyInitialized:complete isFiltered:(trackedKeys != nil)];
- }
- - (void)updateServerCacheWithNode:(id<FNode>)node forQuery:(FQuerySpec *)query {
- BOOL merge = !query.loadsAllData;
- [self.storageEngine updateServerCache:node atPath:query.path merge:merge];
- [self setQueryComplete:query];
- [self doPruneCheckAfterServerUpdate];
- }
- - (void)updateServerCacheWithMerge:(FCompoundWrite *)merge atPath:(FPath *)path {
- [self.storageEngine updateServerCacheWithMerge:merge atPath:path];
- [self doPruneCheckAfterServerUpdate];
- }
- - (void)applyUserMerge:(FCompoundWrite *)merge toServerCacheAtPath:(FPath *)path {
- // TODO[offline]: rework this to be more efficient
- [merge enumerateWrites:^(FPath *relativePath, id<FNode> node, BOOL *stop) {
- [self applyUserWrite:node toServerCacheAtPath:[path child:relativePath]];
- }];
- }
- - (void)applyUserWrite:(id<FNode>)write toServerCacheAtPath:(FPath *)path {
- // This is a hack to guess whether we already cached this because we got a server data update for this
- // write via an existing active default query. If we didn't, then we'll manually cache this and add a
- // tracked query to mark it complete and keep it cached.
- // Unfortunately this is just a guess and it's possible that we *did* get an update (e.g. via a filtered
- // query) and by overwriting the cache here, we'll actually store an incorrect value (e.g. in the case
- // that we wrote a ServerValue.TIMESTAMP and the server resolved it to a different value).
- // TODO[offline]: Consider reworking.
- if (![self.trackedQueryManager hasActiveDefaultQueryAtPath:path]) {
- [self.storageEngine updateServerCache:write atPath:path merge:NO];
- [self.trackedQueryManager ensureCompleteTrackedQueryAtPath:path];
- }
- }
- - (void)setQueryComplete:(FQuerySpec *)query {
- if (query.loadsAllData) {
- [self.trackedQueryManager setQueriesCompleteAtPath:query.path];
- } else {
- [self.trackedQueryManager setQueryComplete:query];
- }
- }
- - (void)setQueryActive:(FQuerySpec *)spec {
- [self.trackedQueryManager setQueryActive:spec];
- }
- - (void)setQueryInactive:(FQuerySpec *)spec {
- [self.trackedQueryManager setQueryInactive:spec];
- }
- - (void)doPruneCheckAfterServerUpdate {
- self.serverCacheUpdatesSinceLastPruneCheck++;
- if ([self.cachePolicy shouldCheckCacheSize:self.serverCacheUpdatesSinceLastPruneCheck]) {
- FFDebug(@"I-RDB078001", @"Reached prune check threshold. Checking...");
- NSDate *date = [NSDate date];
- self.serverCacheUpdatesSinceLastPruneCheck = 0;
- BOOL canPrune = YES;
- NSUInteger cacheSize = [self.storageEngine serverCacheEstimatedSizeInBytes];
- FFDebug(@"I-RDB078002", @"Server cache size: %lu", (unsigned long)cacheSize);
- while (canPrune && [self.cachePolicy shouldPruneCacheWithSize:cacheSize
- numberOfTrackedQueries:self.trackedQueryManager.numberOfPrunableQueries]) {
- FPruneForest *pruneForest = [self.trackedQueryManager pruneOldQueries:self.cachePolicy];
- if (pruneForest.prunesAnything) {
- [self.storageEngine pruneCache:pruneForest atPath:[FPath empty]];
- } else {
- canPrune = NO;
- }
- cacheSize = [self.storageEngine serverCacheEstimatedSizeInBytes];
- FFDebug(@"I-RDB078003", @"Cache size after pruning: %lu", (unsigned long)cacheSize);
- }
- FFDebug(@"I-RDB078004", @"Pruning round took %fms", [date timeIntervalSinceNow]*-1000);
- }
- }
- - (void)setTrackedQueryKeys:(NSSet *)keys forQuery:(FQuerySpec *)query {
- NSAssert(!query.loadsAllData, @"We should only track keys for filtered queries");
- FTrackedQuery *trackedQuery = [self.trackedQueryManager findTrackedQuery:query];
- NSAssert(trackedQuery.isActive, @"We only expect tracked keys for currently-active queries.");
- [self.storageEngine setTrackedQueryKeys:keys forQueryId:trackedQuery.queryId];
- }
- - (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added removedKeys:(NSSet *)removed forQuery:(FQuerySpec *)query {
- NSAssert(!query.loadsAllData, @"We should only track keys for filtered queries");
- FTrackedQuery *trackedQuery = [self.trackedQueryManager findTrackedQuery:query];
- NSAssert(trackedQuery.isActive, @"We only expect tracked keys for currently-active queries.");
- [self.storageEngine updateTrackedQueryKeysWithAddedKeys:added removedKeys:removed forQueryId:trackedQuery.queryId];
- }
- @end
|