2017-05-15 12:27:07 -07:00
|
|
|
/*
|
|
|
|
|
* 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 "FMockStorageEngine.h"
|
|
|
|
|
|
|
|
|
|
#import "FCompoundWrite.h"
|
|
|
|
|
#import "FEmptyNode.h"
|
2019-08-05 13:27:47 -07:00
|
|
|
#import "FNode.h"
|
2017-05-15 12:27:07 -07:00
|
|
|
#import "FPruneForest.h"
|
2019-08-05 13:27:47 -07:00
|
|
|
#import "FTrackedQuery.h"
|
|
|
|
|
#import "FWriteRecord.h"
|
2017-05-15 12:27:07 -07:00
|
|
|
|
|
|
|
|
@interface FMockStorageEngine ()
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
@property(nonatomic) BOOL closed;
|
|
|
|
|
@property(nonatomic, strong) NSMutableDictionary *userWritesDict;
|
|
|
|
|
@property(nonatomic, strong) FCompoundWrite *serverCache;
|
|
|
|
|
@property(nonatomic, strong) NSMutableDictionary *trackedQueries;
|
|
|
|
|
@property(nonatomic, strong) NSMutableDictionary *trackedQueryKeys;
|
2017-05-15 12:27:07 -07:00
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation FMockStorageEngine
|
|
|
|
|
|
|
|
|
|
- (id)init {
|
2019-08-05 13:27:47 -07:00
|
|
|
self = [super init];
|
|
|
|
|
if (self != nil) {
|
|
|
|
|
self->_userWritesDict = [NSMutableDictionary dictionary];
|
|
|
|
|
self->_serverCache = [FCompoundWrite emptyWrite];
|
|
|
|
|
self->_trackedQueries = [NSMutableDictionary dictionary];
|
|
|
|
|
self->_trackedQueryKeys = [NSMutableDictionary dictionary];
|
|
|
|
|
}
|
|
|
|
|
return self;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)close {
|
2019-08-05 13:27:47 -07:00
|
|
|
self.closed = YES;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)saveUserOverwrite:(id<FNode>)node atPath:(FPath *)path writeId:(NSUInteger)writeId {
|
2019-08-05 13:27:47 -07:00
|
|
|
FWriteRecord *writeRecord = [[FWriteRecord alloc] initWithPath:path
|
|
|
|
|
overwrite:node
|
|
|
|
|
writeId:writeId
|
|
|
|
|
visible:YES];
|
|
|
|
|
self.userWritesDict[@(writeId)] = writeRecord;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)saveUserMerge:(FCompoundWrite *)merge atPath:(FPath *)path writeId:(NSUInteger)writeId {
|
2019-08-05 13:27:47 -07:00
|
|
|
FWriteRecord *writeRecord = [[FWriteRecord alloc] initWithPath:path merge:merge writeId:writeId];
|
|
|
|
|
self.userWritesDict[@(writeId)] = writeRecord;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)removeUserWrite:(NSUInteger)writeId {
|
2019-08-05 13:27:47 -07:00
|
|
|
[self.userWritesDict removeObjectForKey:@(writeId)];
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)removeAllUserWrites {
|
2019-08-05 13:27:47 -07:00
|
|
|
[self.userWritesDict removeAllObjects];
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSArray *)userWrites {
|
2019-08-05 13:27:47 -07:00
|
|
|
return [[self.userWritesDict allValues]
|
|
|
|
|
sortedArrayUsingComparator:^NSComparisonResult(FWriteRecord *obj1, FWriteRecord *obj2) {
|
2017-05-15 12:27:07 -07:00
|
|
|
if (obj1.writeId < obj2.writeId) {
|
2019-08-05 13:27:47 -07:00
|
|
|
return NSOrderedAscending;
|
2017-05-15 12:27:07 -07:00
|
|
|
} else if (obj1.writeId > obj2.writeId) {
|
2019-08-05 13:27:47 -07:00
|
|
|
return NSOrderedDescending;
|
2017-05-15 12:27:07 -07:00
|
|
|
} else {
|
2019-08-05 13:27:47 -07:00
|
|
|
return NSOrderedSame;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
2019-08-05 13:27:47 -07:00
|
|
|
}];
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id<FNode>)serverCacheAtPath:(FPath *)path {
|
2019-08-05 13:27:47 -07:00
|
|
|
return [[self.serverCache childCompoundWriteAtPath:path] applyToNode:[FEmptyNode emptyNode]];
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id<FNode>)serverCacheForKeys:(NSSet *)keys atPath:(FPath *)path {
|
2019-08-05 13:27:47 -07:00
|
|
|
__block id<FNode> children = [FEmptyNode emptyNode];
|
|
|
|
|
id<FNode> fullNode =
|
|
|
|
|
[[self.serverCache childCompoundWriteAtPath:path] applyToNode:[FEmptyNode emptyNode]];
|
|
|
|
|
[keys enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) {
|
|
|
|
|
children = [children updateImmediateChild:key withNewChild:[fullNode getImmediateChild:key]];
|
|
|
|
|
}];
|
|
|
|
|
return children;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateServerCache:(id<FNode>)node atPath:(FPath *)path merge:(BOOL)merge {
|
2019-08-05 13:27:47 -07:00
|
|
|
if (merge) {
|
|
|
|
|
[node enumerateChildrenUsingBlock:^(NSString *key, id<FNode> childNode, BOOL *stop) {
|
|
|
|
|
self.serverCache = [self.serverCache addWrite:childNode atPath:[path childFromString:key]];
|
|
|
|
|
}];
|
|
|
|
|
} else {
|
|
|
|
|
self.serverCache = [self.serverCache addWrite:node atPath:path];
|
|
|
|
|
}
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateServerCacheWithMerge:(FCompoundWrite *)merge atPath:(FPath *)path {
|
2019-08-05 13:27:47 -07:00
|
|
|
self.serverCache = [self.serverCache addCompoundWrite:merge atPath:path];
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSUInteger)serverCacheEstimatedSizeInBytes {
|
2019-08-05 13:27:47 -07:00
|
|
|
id data = [[self.serverCache applyToNode:[FEmptyNode emptyNode]] valForExport:YES];
|
|
|
|
|
return [NSJSONSerialization dataWithJSONObject:data options:0 error:nil].length;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)pruneCache:(FPruneForest *)pruneForest atPath:(FPath *)prunePath {
|
2019-08-05 13:27:47 -07:00
|
|
|
[self.serverCache enumerateWrites:^(FPath *absolutePath, id<FNode> node, BOOL *stop) {
|
|
|
|
|
NSAssert([prunePath isEqual:absolutePath] || ![absolutePath contains:prunePath],
|
|
|
|
|
@"Pruning at %@ but we found data higher up!", prunePath);
|
|
|
|
|
if ([prunePath contains:absolutePath]) {
|
|
|
|
|
FPath *relativePath = [FPath relativePathFrom:prunePath to:absolutePath];
|
|
|
|
|
if ([pruneForest shouldPruneUnkeptDescendantsAtPath:relativePath]) {
|
|
|
|
|
__block FCompoundWrite *newCache = [FCompoundWrite emptyWrite];
|
|
|
|
|
[[pruneForest childAtPath:relativePath] enumarateKeptNodesUsingBlock:^(FPath *keepPath) {
|
|
|
|
|
newCache = [newCache addWrite:[node getChild:keepPath] atPath:keepPath];
|
|
|
|
|
}];
|
|
|
|
|
self.serverCache =
|
|
|
|
|
[[self.serverCache removeWriteAtPath:absolutePath] addCompoundWrite:newCache
|
|
|
|
|
atPath:absolutePath];
|
|
|
|
|
} else {
|
|
|
|
|
// NOTE: This is technically a valid scenario (e.g. you ask to prune at / but only want to
|
|
|
|
|
// prune 'foo' and 'bar' and ignore everything else). But currently our pruning will
|
|
|
|
|
// explicitly prune or keep everything we know about, so if we hit this it means our tracked
|
|
|
|
|
// queries and the server cache are out of sync.
|
|
|
|
|
NSAssert([pruneForest shouldKeepPath:relativePath],
|
|
|
|
|
@"We have data at %@ that is neither pruned nor kept.", relativePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}];
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSArray *)loadTrackedQueries {
|
2019-08-05 13:27:47 -07:00
|
|
|
return self.trackedQueries.allValues;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)removeTrackedQuery:(NSUInteger)queryId {
|
2019-08-05 13:27:47 -07:00
|
|
|
[self.trackedQueries removeObjectForKey:@(queryId)];
|
|
|
|
|
[self.trackedQueryKeys removeObjectForKey:@(queryId)];
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)saveTrackedQuery:(FTrackedQuery *)query {
|
2019-08-05 13:27:47 -07:00
|
|
|
self.trackedQueries[@(query.queryId)] = query;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)setTrackedQueryKeys:(NSSet *)keys forQueryId:(NSUInteger)queryId {
|
2019-08-05 13:27:47 -07:00
|
|
|
self.trackedQueryKeys[@(queryId)] = keys;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
2019-08-05 13:27:47 -07:00
|
|
|
- (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added
|
|
|
|
|
removedKeys:(NSSet *)removed
|
|
|
|
|
forQueryId:(NSUInteger)queryId {
|
|
|
|
|
NSSet *oldKeys = [self trackedQueryKeysForQuery:queryId];
|
|
|
|
|
NSMutableSet *newKeys = [NSMutableSet setWithSet:oldKeys];
|
|
|
|
|
[newKeys minusSet:removed];
|
|
|
|
|
[newKeys unionSet:added];
|
|
|
|
|
self.trackedQueryKeys[@(queryId)] = newKeys;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSSet *)trackedQueryKeysForQuery:(NSUInteger)queryId {
|
2019-08-05 13:27:47 -07:00
|
|
|
NSSet *keys = self.trackedQueryKeys[@(queryId)];
|
|
|
|
|
return keys != nil ? keys : [NSSet set];
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|