| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831 |
- /*
- * 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 "FViewProcessor.h"
- #import "FAckUserWrite.h"
- #import "FCacheNode.h"
- #import "FChange.h"
- #import "FChildChangeAccumulator.h"
- #import "FChildrenNode.h"
- #import "FCompleteChildSource.h"
- #import "FCompoundWrite.h"
- #import "FEmptyNode.h"
- #import "FIRDataEventType.h"
- #import "FImmutableTree.h"
- #import "FKeyIndex.h"
- #import "FMerge.h"
- #import "FNode.h"
- #import "FNodeFilter.h"
- #import "FOperation.h"
- #import "FOperationSource.h"
- #import "FOverwrite.h"
- #import "FPath.h"
- #import "FViewCache.h"
- #import "FViewProcessorResult.h"
- #import "FWriteTreeRef.h"
- /**
- * An implementation of FCompleteChildSource that never returns any additional
- * children
- */
- @interface FNoCompleteChildSource : NSObject <FCompleteChildSource>
- @end
- @implementation FNoCompleteChildSource
- + (FNoCompleteChildSource *)instance {
- static FNoCompleteChildSource *source = nil;
- static dispatch_once_t once;
- dispatch_once(&once, ^{
- source = [[FNoCompleteChildSource alloc] init];
- });
- return source;
- }
- - (id<FNode>)completeChild:(NSString *)childKey {
- return nil;
- }
- - (FNamedNode *)childByIndex:(id<FIndex>)index
- afterChild:(FNamedNode *)child
- isReverse:(BOOL)reverse {
- return nil;
- }
- @end
- /**
- * An implementation of FCompleteChildSource that uses a FWriteTree in addition
- * to any other server data or old event caches available to calculate complete
- * children.
- */
- @interface FWriteTreeCompleteChildSource : NSObject <FCompleteChildSource>
- @property(nonatomic, strong) FWriteTreeRef *writes;
- @property(nonatomic, strong) FViewCache *viewCache;
- @property(nonatomic, strong) id<FNode> optCompleteServerCache;
- @end
- @implementation FWriteTreeCompleteChildSource
- - (id)initWithWrites:(FWriteTreeRef *)writes
- viewCache:(FViewCache *)viewCache
- serverCache:(id<FNode>)optCompleteServerCache {
- self = [super init];
- if (self) {
- self.writes = writes;
- self.viewCache = viewCache;
- self.optCompleteServerCache = optCompleteServerCache;
- }
- return self;
- }
- - (id<FNode>)completeChild:(NSString *)childKey {
- FCacheNode *node = self.viewCache.cachedEventSnap;
- if ([node isCompleteForChild:childKey]) {
- return [node.node getImmediateChild:childKey];
- } else {
- FCacheNode *serverNode;
- if (self.optCompleteServerCache) {
- // Since we're only ever getting child nodes, we can use the key
- // index here
- FIndexedNode *indexed =
- [FIndexedNode indexedNodeWithNode:self.optCompleteServerCache
- index:[FKeyIndex keyIndex]];
- serverNode = [[FCacheNode alloc] initWithIndexedNode:indexed
- isFullyInitialized:YES
- isFiltered:NO];
- } else {
- serverNode = self.viewCache.cachedServerSnap;
- }
- return [self.writes calculateCompleteChild:childKey cache:serverNode];
- }
- }
- - (FNamedNode *)childByIndex:(id<FIndex>)index
- afterChild:(FNamedNode *)child
- isReverse:(BOOL)reverse {
- id<FNode> completeServerData = self.optCompleteServerCache != nil
- ? self.optCompleteServerCache
- : self.viewCache.completeServerSnap;
- return [self.writes calculateNextNodeAfterPost:child
- completeServerData:completeServerData
- reverse:reverse
- index:index];
- }
- @end
- @interface FViewProcessor ()
- @property(nonatomic, strong) id<FNodeFilter> filter;
- @end
- @implementation FViewProcessor
- - (id)initWithFilter:(id<FNodeFilter>)nodeFilter {
- self = [super init];
- if (self) {
- self.filter = nodeFilter;
- }
- return self;
- }
- - (FViewProcessorResult *)applyOperationOn:(FViewCache *)oldViewCache
- operation:(id<FOperation>)operation
- writesCache:(FWriteTreeRef *)writesCache
- completeCache:(id<FNode>)optCompleteCache {
- FChildChangeAccumulator *accumulator =
- [[FChildChangeAccumulator alloc] init];
- FViewCache *newViewCache;
- if (operation.type == FOperationTypeOverwrite) {
- FOverwrite *overwrite = (FOverwrite *)operation;
- if (operation.source.fromUser) {
- newViewCache = [self applyUserOverwriteTo:oldViewCache
- changePath:overwrite.path
- changedSnap:overwrite.snap
- writesCache:writesCache
- completeCache:optCompleteCache
- accumulator:accumulator];
- } else {
- NSAssert(operation.source.fromServer,
- @"Unknown source for overwrite.");
- // We filter the node if it's a tagged update or the node has been
- // previously filtered and the update is not at the root in which
- // case it is ok (and necessary) to mark the node unfiltered again
- BOOL filterServerNode = overwrite.source.isTagged ||
- (oldViewCache.cachedServerSnap.isFiltered &&
- !overwrite.path.isEmpty);
- newViewCache = [self applyServerOverwriteTo:oldViewCache
- changePath:overwrite.path
- snap:overwrite.snap
- writesCache:writesCache
- completeCache:optCompleteCache
- filterServerNode:filterServerNode
- accumulator:accumulator];
- }
- } else if (operation.type == FOperationTypeMerge) {
- FMerge *merge = (FMerge *)operation;
- if (operation.source.fromUser) {
- newViewCache = [self applyUserMergeTo:oldViewCache
- path:merge.path
- changedChildren:merge.children
- writesCache:writesCache
- completeCache:optCompleteCache
- accumulator:accumulator];
- } else {
- NSAssert(operation.source.fromServer, @"Unknown source for merge.");
- // We filter the node if it's a tagged update or the node has been
- // previously filtered
- BOOL filterServerNode = merge.source.isTagged ||
- oldViewCache.cachedServerSnap.isFiltered;
- newViewCache = [self applyServerMergeTo:oldViewCache
- path:merge.path
- changedChildren:merge.children
- writesCache:writesCache
- completeCache:optCompleteCache
- filterServerNode:filterServerNode
- accumulator:accumulator];
- }
- } else if (operation.type == FOperationTypeAckUserWrite) {
- FAckUserWrite *ackWrite = (FAckUserWrite *)operation;
- if (!ackWrite.revert) {
- newViewCache = [self ackUserWriteOn:oldViewCache
- ackPath:ackWrite.path
- affectedTree:ackWrite.affectedTree
- writesCache:writesCache
- completeCache:optCompleteCache
- accumulator:accumulator];
- } else {
- newViewCache = [self revertUserWriteOn:oldViewCache
- path:ackWrite.path
- writesCache:writesCache
- completeCache:optCompleteCache
- accumulator:accumulator];
- }
- } else if (operation.type == FOperationTypeListenComplete) {
- newViewCache = [self listenCompleteOldCache:oldViewCache
- path:operation.path
- writesCache:writesCache
- serverCache:optCompleteCache
- accumulator:accumulator];
- } else {
- [NSException
- raise:NSInternalInconsistencyException
- format:@"Unknown operation encountered %ld.", (long)operation.type];
- return nil;
- }
- NSArray *changes = [self maybeAddValueFromOldViewCache:oldViewCache
- newViewCache:newViewCache
- changes:accumulator.changes];
- FViewProcessorResult *results =
- [[FViewProcessorResult alloc] initWithViewCache:newViewCache
- changes:changes];
- return results;
- }
- - (NSArray *)maybeAddValueFromOldViewCache:(FViewCache *)oldViewCache
- newViewCache:(FViewCache *)newViewCache
- changes:(NSArray *)changes {
- NSArray *newChanges = changes;
- FCacheNode *eventSnap = newViewCache.cachedEventSnap;
- if (eventSnap.isFullyInitialized) {
- BOOL isLeafOrEmpty =
- eventSnap.node.isLeafNode || eventSnap.node.isEmpty;
- if ([changes count] > 0 ||
- !oldViewCache.cachedEventSnap.isFullyInitialized ||
- (isLeafOrEmpty &&
- ![eventSnap.node isEqual:oldViewCache.completeEventSnap]) ||
- ![eventSnap.node.getPriority
- isEqual:oldViewCache.completeEventSnap.getPriority]) {
- FChange *valueChange =
- [[FChange alloc] initWithType:FIRDataEventTypeValue
- indexedNode:eventSnap.indexedNode];
- NSMutableArray *mutableChanges = [changes mutableCopy];
- [mutableChanges addObject:valueChange];
- newChanges = mutableChanges;
- }
- }
- return newChanges;
- }
- - (FViewCache *)
- generateEventCacheAfterServerEvent:(FViewCache *)viewCache
- path:(FPath *)changePath
- writesCache:(FWriteTreeRef *)writesCache
- source:(id<FCompleteChildSource>)source
- accumulator:(FChildChangeAccumulator *)accumulator {
- FCacheNode *oldEventSnap = viewCache.cachedEventSnap;
- if ([writesCache shadowingWriteAtPath:changePath] != nil) {
- // we have a shadowing write, ignore changes.
- return viewCache;
- } else {
- FIndexedNode *newEventCache;
- if (changePath.isEmpty) {
- // TODO: figure out how this plays with "sliding ack windows"
- NSAssert(
- viewCache.cachedServerSnap.isFullyInitialized,
- @"If change path is empty, we must have complete server data");
- id<FNode> nodeWithLocalWrites;
- if (viewCache.cachedServerSnap.isFiltered) {
- // We need to special case this, because we need to only apply
- // writes to complete children, or we might end up raising
- // events for incomplete children. If the server data is
- // filtered deep writes cannot be guaranteed to be complete
- id<FNode> serverCache = viewCache.completeServerSnap;
- FChildrenNode *completeChildren =
- ([serverCache isKindOfClass:[FChildrenNode class]])
- ? serverCache
- : [FEmptyNode emptyNode];
- nodeWithLocalWrites = [writesCache
- calculateCompleteEventChildrenWithCompleteServerChildren:
- completeChildren];
- } else {
- nodeWithLocalWrites = [writesCache
- calculateCompleteEventCacheWithCompleteServerCache:
- viewCache.completeServerSnap];
- }
- FIndexedNode *indexedNode =
- [FIndexedNode indexedNodeWithNode:nodeWithLocalWrites
- index:self.filter.index];
- newEventCache = [self.filter
- updateFullNode:viewCache.cachedEventSnap.indexedNode
- withNewNode:indexedNode
- accumulator:accumulator];
- } else {
- NSString *childKey = [changePath getFront];
- if ([childKey isEqualToString:@".priority"]) {
- NSAssert(
- changePath.length == 1,
- @"Can't have a priority with additional path components");
- id<FNode> oldEventNode = oldEventSnap.node;
- id<FNode> serverNode = viewCache.cachedServerSnap.node;
- // we might have overwrites for this priority
- id<FNode> updatedPriority = [writesCache
- calculateEventCacheAfterServerOverwriteWithChildPath:
- changePath
- existingEventSnap:
- oldEventNode
- existingServerSnap:
- serverNode];
- if (updatedPriority != nil) {
- newEventCache =
- [self.filter updatePriority:updatedPriority
- forNode:oldEventSnap.indexedNode];
- } else {
- // priority didn't change, keep old node
- newEventCache = oldEventSnap.indexedNode;
- }
- } else {
- FPath *childChangePath = [changePath popFront];
- id<FNode> newEventChild;
- if ([oldEventSnap isCompleteForChild:childKey]) {
- id<FNode> serverNode = viewCache.cachedServerSnap.node;
- id<FNode> eventChildUpdate = [writesCache
- calculateEventCacheAfterServerOverwriteWithChildPath:
- changePath
- existingEventSnap:
- oldEventSnap.node
- existingServerSnap:
- serverNode];
- if (eventChildUpdate != nil) {
- newEventChild =
- [[oldEventSnap.node getImmediateChild:childKey]
- updateChild:childChangePath
- withNewChild:eventChildUpdate];
- } else {
- // Nothing changed, just keep the old child
- newEventChild =
- [oldEventSnap.node getImmediateChild:childKey];
- }
- } else {
- newEventChild = [writesCache
- calculateCompleteChild:childKey
- cache:viewCache.cachedServerSnap];
- }
- if (newEventChild != nil) {
- newEventCache =
- [self.filter updateChildIn:oldEventSnap.indexedNode
- forChildKey:childKey
- newChild:newEventChild
- affectedPath:childChangePath
- fromSource:source
- accumulator:accumulator];
- } else {
- // No complete children available or no change
- newEventCache = oldEventSnap.indexedNode;
- }
- }
- }
- return [viewCache updateEventSnap:newEventCache
- isComplete:(oldEventSnap.isFullyInitialized ||
- changePath.isEmpty)
- isFiltered:self.filter.filtersNodes];
- }
- }
- - (FViewCache *)applyServerOverwriteTo:(FViewCache *)oldViewCache
- changePath:(FPath *)changePath
- snap:(id<FNode>)changedSnap
- writesCache:(FWriteTreeRef *)writesCache
- completeCache:(id<FNode>)optCompleteCache
- filterServerNode:(BOOL)filterServerNode
- accumulator:(FChildChangeAccumulator *)accumulator {
- FCacheNode *oldServerSnap = oldViewCache.cachedServerSnap;
- FIndexedNode *newServerCache;
- id<FNodeFilter> serverFilter =
- filterServerNode ? self.filter : self.filter.indexedFilter;
- if (changePath.isEmpty) {
- FIndexedNode *indexed =
- [FIndexedNode indexedNodeWithNode:changedSnap
- index:serverFilter.index];
- newServerCache = [serverFilter updateFullNode:oldServerSnap.indexedNode
- withNewNode:indexed
- accumulator:nil];
- } else if (serverFilter.filtersNodes && !oldServerSnap.isFiltered) {
- // We want to filter the server node, but we didn't filter the server
- // node yet, so simulate a full update
- NSAssert(![changePath isEmpty],
- @"An empty path should been caught in the other branch");
- NSString *childKey = [changePath getFront];
- FPath *updatePath = [changePath popFront];
- id<FNode> newChild = [[oldServerSnap.node getImmediateChild:childKey]
- updateChild:updatePath
- withNewChild:changedSnap];
- FIndexedNode *indexed =
- [oldServerSnap.indexedNode updateChild:childKey
- withNewChild:newChild];
- newServerCache = [serverFilter updateFullNode:oldServerSnap.indexedNode
- withNewNode:indexed
- accumulator:nil];
- } else {
- NSString *childKey = [changePath getFront];
- if (![oldServerSnap isCompleteForPath:changePath] &&
- changePath.length > 1) {
- // We don't update incomplete nodes with updates intended for other
- // listeners.
- return oldViewCache;
- }
- FPath *childChangePath = [changePath popFront];
- id<FNode> childNode = [oldServerSnap.node getImmediateChild:childKey];
- id<FNode> newChildNode = [childNode updateChild:childChangePath
- withNewChild:changedSnap];
- if ([childKey isEqualToString:@".priority"]) {
- newServerCache =
- [serverFilter updatePriority:newChildNode
- forNode:oldServerSnap.indexedNode];
- } else {
- newServerCache =
- [serverFilter updateChildIn:oldServerSnap.indexedNode
- forChildKey:childKey
- newChild:newChildNode
- affectedPath:childChangePath
- fromSource:[FNoCompleteChildSource instance]
- accumulator:nil];
- }
- }
- FViewCache *newViewCache =
- [oldViewCache updateServerSnap:newServerCache
- isComplete:(oldServerSnap.isFullyInitialized ||
- changePath.isEmpty)
- isFiltered:serverFilter.filtersNodes];
- id<FCompleteChildSource> source =
- [[FWriteTreeCompleteChildSource alloc] initWithWrites:writesCache
- viewCache:newViewCache
- serverCache:optCompleteCache];
- return [self generateEventCacheAfterServerEvent:newViewCache
- path:changePath
- writesCache:writesCache
- source:source
- accumulator:accumulator];
- }
- - (FViewCache *)applyUserOverwriteTo:(FViewCache *)oldViewCache
- changePath:(FPath *)changePath
- changedSnap:(id<FNode>)changedSnap
- writesCache:(FWriteTreeRef *)writesCache
- completeCache:(id<FNode>)optCompleteCache
- accumulator:(FChildChangeAccumulator *)accumulator {
- FCacheNode *oldEventSnap = oldViewCache.cachedEventSnap;
- FViewCache *newViewCache;
- id<FCompleteChildSource> source =
- [[FWriteTreeCompleteChildSource alloc] initWithWrites:writesCache
- viewCache:oldViewCache
- serverCache:optCompleteCache];
- if (changePath.isEmpty) {
- FIndexedNode *newIndexed =
- [FIndexedNode indexedNodeWithNode:changedSnap
- index:self.filter.index];
- FIndexedNode *newEventCache =
- [self.filter updateFullNode:oldEventSnap.indexedNode
- withNewNode:newIndexed
- accumulator:accumulator];
- newViewCache = [oldViewCache updateEventSnap:newEventCache
- isComplete:YES
- isFiltered:self.filter.filtersNodes];
- } else {
- NSString *childKey = [changePath getFront];
- if ([childKey isEqualToString:@".priority"]) {
- FIndexedNode *newEventCache = [self.filter
- updatePriority:changedSnap
- forNode:oldViewCache.cachedEventSnap.indexedNode];
- newViewCache =
- [oldViewCache updateEventSnap:newEventCache
- isComplete:oldEventSnap.isFullyInitialized
- isFiltered:oldEventSnap.isFiltered];
- } else {
- FPath *childChangePath = [changePath popFront];
- id<FNode> oldChild = [oldEventSnap.node getImmediateChild:childKey];
- id<FNode> newChild;
- if (childChangePath.isEmpty) {
- // Child overwrite, we can replace the child
- newChild = changedSnap;
- } else {
- id<FNode> childNode = [source completeChild:childKey];
- if (childNode != nil) {
- if ([[childChangePath getBack]
- isEqualToString:@".priority"] &&
- [childNode getChild:[childChangePath parent]].isEmpty) {
- // This is a priority update on an empty node. If this
- // node exists on the server, the server will send down
- // the priority in the update, so ignore for now
- newChild = childNode;
- } else {
- newChild = [childNode updateChild:childChangePath
- withNewChild:changedSnap];
- }
- } else {
- newChild = [FEmptyNode emptyNode];
- }
- }
- if (![oldChild isEqual:newChild]) {
- FIndexedNode *newEventSnap =
- [self.filter updateChildIn:oldEventSnap.indexedNode
- forChildKey:childKey
- newChild:newChild
- affectedPath:childChangePath
- fromSource:source
- accumulator:accumulator];
- newViewCache = [oldViewCache
- updateEventSnap:newEventSnap
- isComplete:oldEventSnap.isFullyInitialized
- isFiltered:self.filter.filtersNodes];
- } else {
- newViewCache = oldViewCache;
- }
- }
- }
- return newViewCache;
- }
- + (BOOL)cache:(FViewCache *)viewCache hasChild:(NSString *)childKey {
- return [viewCache.cachedEventSnap isCompleteForChild:childKey];
- }
- /**
- * @param changedChildren NSDictionary of child name (NSString*) to child value
- * (id<FNode>)
- */
- - (FViewCache *)applyUserMergeTo:(FViewCache *)viewCache
- path:(FPath *)path
- changedChildren:(FCompoundWrite *)changedChildren
- writesCache:(FWriteTreeRef *)writesCache
- completeCache:(id<FNode>)serverCache
- accumulator:(FChildChangeAccumulator *)accumulator {
- // HACK: In the case of a limit query, there may be some changes that bump
- // things out of the window leaving room for new items. It's important we
- // process these changes first, so we iterate the changes twice, first
- // processing any that affect items currently in view.
- // TODO: I consider an item "in view" if cacheHasChild is true, which checks
- // both the server and event snap. I'm not sure if this will result in edge
- // cases when a child is in one but not the other.
- __block FViewCache *curViewCache = viewCache;
- [changedChildren enumerateWrites:^(FPath *relativePath, id<FNode> childNode,
- BOOL *stop) {
- FPath *writePath = [path child:relativePath];
- if ([FViewProcessor cache:viewCache hasChild:[writePath getFront]]) {
- curViewCache = [self applyUserOverwriteTo:curViewCache
- changePath:writePath
- changedSnap:childNode
- writesCache:writesCache
- completeCache:serverCache
- accumulator:accumulator];
- }
- }];
- [changedChildren enumerateWrites:^(FPath *relativePath, id<FNode> childNode,
- BOOL *stop) {
- FPath *writePath = [path child:relativePath];
- if (![FViewProcessor cache:viewCache hasChild:[writePath getFront]]) {
- curViewCache = [self applyUserOverwriteTo:curViewCache
- changePath:writePath
- changedSnap:childNode
- writesCache:writesCache
- completeCache:serverCache
- accumulator:accumulator];
- }
- }];
- return curViewCache;
- }
- - (FViewCache *)applyServerMergeTo:(FViewCache *)viewCache
- path:(FPath *)path
- changedChildren:(FCompoundWrite *)changedChildren
- writesCache:(FWriteTreeRef *)writesCache
- completeCache:(id<FNode>)serverCache
- filterServerNode:(BOOL)filterServerNode
- accumulator:(FChildChangeAccumulator *)accumulator {
- // If we don't have a cache yet, this merge was intended for a previously
- // listen in the same location. Ignore it and wait for the complete data
- // update coming soon.
- if (viewCache.cachedServerSnap.node.isEmpty &&
- !viewCache.cachedServerSnap.isFullyInitialized) {
- return viewCache;
- }
- // HACK: In the case of a limit query, there may be some changes that bump
- // things out of the window leaving room for new items. It's important we
- // process these changes first, so we iterate the changes twice, first
- // processing any that affect items currently in view.
- // TODO: I consider an item "in view" if cacheHasChild is true, which checks
- // both the server and event snap. I'm not sure if this will result in edge
- // cases when a child is in one but not the other.
- __block FViewCache *curViewCache = viewCache;
- FCompoundWrite *actualMerge;
- if (path.isEmpty) {
- actualMerge = changedChildren;
- } else {
- actualMerge =
- [[FCompoundWrite emptyWrite] addCompoundWrite:changedChildren
- atPath:path];
- }
- id<FNode> serverNode = viewCache.cachedServerSnap.node;
- NSDictionary *childCompoundWrites = actualMerge.childCompoundWrites;
- [childCompoundWrites
- enumerateKeysAndObjectsUsingBlock:^(
- NSString *childKey, FCompoundWrite *childMerge, BOOL *stop) {
- if ([serverNode hasChild:childKey]) {
- id<FNode> serverChild =
- [viewCache.cachedServerSnap.node getImmediateChild:childKey];
- id<FNode> newChild = [childMerge applyToNode:serverChild];
- curViewCache =
- [self applyServerOverwriteTo:curViewCache
- changePath:[[FPath alloc] initWith:childKey]
- snap:newChild
- writesCache:writesCache
- completeCache:serverCache
- filterServerNode:filterServerNode
- accumulator:accumulator];
- }
- }];
- [childCompoundWrites
- enumerateKeysAndObjectsUsingBlock:^(
- NSString *childKey, FCompoundWrite *childMerge, BOOL *stop) {
- bool isUnknownDeepMerge =
- ![viewCache.cachedServerSnap isCompleteForChild:childKey] &&
- childMerge.rootWrite == nil;
- if (![serverNode hasChild:childKey] && !isUnknownDeepMerge) {
- id<FNode> serverChild =
- [viewCache.cachedServerSnap.node getImmediateChild:childKey];
- id<FNode> newChild = [childMerge applyToNode:serverChild];
- curViewCache =
- [self applyServerOverwriteTo:curViewCache
- changePath:[[FPath alloc] initWith:childKey]
- snap:newChild
- writesCache:writesCache
- completeCache:serverCache
- filterServerNode:filterServerNode
- accumulator:accumulator];
- }
- }];
- return curViewCache;
- }
- - (FViewCache *)ackUserWriteOn:(FViewCache *)viewCache
- ackPath:(FPath *)ackPath
- affectedTree:(FImmutableTree *)affectedTree
- writesCache:(FWriteTreeRef *)writesCache
- completeCache:(id<FNode>)optCompleteCache
- accumulator:(FChildChangeAccumulator *)accumulator {
- if ([writesCache shadowingWriteAtPath:ackPath] != nil) {
- return viewCache;
- }
- // Only filter server node if it is currently filtered
- BOOL filterServerNode = viewCache.cachedServerSnap.isFiltered;
- // Essentially we'll just get our existing server cache for the affected
- // paths and re-apply it as a server update now that it won't be shadowed.
- FCacheNode *serverCache = viewCache.cachedServerSnap;
- if (affectedTree.value != nil) {
- // This is an overwrite.
- if ((ackPath.isEmpty && serverCache.isFullyInitialized) ||
- [serverCache isCompleteForPath:ackPath]) {
- return
- [self applyServerOverwriteTo:viewCache
- changePath:ackPath
- snap:[serverCache.node getChild:ackPath]
- writesCache:writesCache
- completeCache:optCompleteCache
- filterServerNode:filterServerNode
- accumulator:accumulator];
- } else if (ackPath.isEmpty) {
- // This is a goofy edge case where we are acking data at this
- // location but don't have full data. We should just re-apply
- // whatever we have in our cache as a merge.
- FCompoundWrite *changedChildren = [FCompoundWrite emptyWrite];
- for (FNamedNode *child in serverCache.node.childEnumerator) {
- changedChildren = [changedChildren addWrite:child.node
- atKey:child.name];
- }
- return [self applyServerMergeTo:viewCache
- path:ackPath
- changedChildren:changedChildren
- writesCache:writesCache
- completeCache:optCompleteCache
- filterServerNode:filterServerNode
- accumulator:accumulator];
- } else {
- return viewCache;
- }
- } else {
- // This is a merge.
- __block FCompoundWrite *changedChildren = [FCompoundWrite emptyWrite];
- [affectedTree forEach:^(FPath *mergePath, id value) {
- FPath *serverCachePath = [ackPath child:mergePath];
- if ([serverCache isCompleteForPath:serverCachePath]) {
- changedChildren = [changedChildren
- addWrite:[serverCache.node getChild:serverCachePath]
- atPath:mergePath];
- }
- }];
- return [self applyServerMergeTo:viewCache
- path:ackPath
- changedChildren:changedChildren
- writesCache:writesCache
- completeCache:optCompleteCache
- filterServerNode:filterServerNode
- accumulator:accumulator];
- }
- }
- - (FViewCache *)revertUserWriteOn:(FViewCache *)viewCache
- path:(FPath *)path
- writesCache:(FWriteTreeRef *)writesCache
- completeCache:(id<FNode>)optCompleteCache
- accumulator:(FChildChangeAccumulator *)accumulator {
- if ([writesCache shadowingWriteAtPath:path] != nil) {
- return viewCache;
- } else {
- id<FCompleteChildSource> source = [[FWriteTreeCompleteChildSource alloc]
- initWithWrites:writesCache
- viewCache:viewCache
- serverCache:optCompleteCache];
- FIndexedNode *oldEventCache = viewCache.cachedEventSnap.indexedNode;
- FIndexedNode *newEventCache;
- if (path.isEmpty || [[path getFront] isEqualToString:@".priority"]) {
- id<FNode> newNode;
- if (viewCache.cachedServerSnap.isFullyInitialized) {
- newNode = [writesCache
- calculateCompleteEventCacheWithCompleteServerCache:
- viewCache.completeServerSnap];
- } else {
- newNode = [writesCache
- calculateCompleteEventChildrenWithCompleteServerChildren:
- viewCache.cachedServerSnap.node];
- }
- FIndexedNode *indexedNode =
- [FIndexedNode indexedNodeWithNode:newNode
- index:self.filter.index];
- newEventCache = [self.filter updateFullNode:oldEventCache
- withNewNode:indexedNode
- accumulator:accumulator];
- } else {
- NSString *childKey = [path getFront];
- id<FNode> newChild =
- [writesCache calculateCompleteChild:childKey
- cache:viewCache.cachedServerSnap];
- if (newChild == nil &&
- [viewCache.cachedServerSnap isCompleteForChild:childKey]) {
- newChild = [oldEventCache.node getImmediateChild:childKey];
- }
- if (newChild != nil) {
- newEventCache = [self.filter updateChildIn:oldEventCache
- forChildKey:childKey
- newChild:newChild
- affectedPath:[path popFront]
- fromSource:source
- accumulator:accumulator];
- } else if (newChild == nil &&
- [viewCache.cachedEventSnap.node hasChild:childKey]) {
- // No complete child available, delete the existing one, if any
- newEventCache =
- [self.filter updateChildIn:oldEventCache
- forChildKey:childKey
- newChild:[FEmptyNode emptyNode]
- affectedPath:[path popFront]
- fromSource:source
- accumulator:accumulator];
- } else {
- newEventCache = oldEventCache;
- }
- if (newEventCache.node.isEmpty &&
- viewCache.cachedServerSnap.isFullyInitialized) {
- // We might have reverted all child writes. Maybe the old event
- // was a leaf node.
- id<FNode> complete = [writesCache
- calculateCompleteEventCacheWithCompleteServerCache:
- viewCache.completeServerSnap];
- if (complete.isLeafNode) {
- FIndexedNode *indexed =
- [FIndexedNode indexedNodeWithNode:complete];
- newEventCache = [self.filter updateFullNode:newEventCache
- withNewNode:indexed
- accumulator:accumulator];
- }
- }
- }
- BOOL complete = viewCache.cachedServerSnap.isFullyInitialized ||
- [writesCache shadowingWriteAtPath:[FPath empty]] != nil;
- return [viewCache updateEventSnap:newEventCache
- isComplete:complete
- isFiltered:self.filter.filtersNodes];
- }
- }
- - (FViewCache *)listenCompleteOldCache:(FViewCache *)viewCache
- path:(FPath *)path
- writesCache:(FWriteTreeRef *)writesCache
- serverCache:(id<FNode>)servercache
- accumulator:(FChildChangeAccumulator *)accumulator {
- FCacheNode *oldServerNode = viewCache.cachedServerSnap;
- FViewCache *newViewCache = [viewCache
- updateServerSnap:oldServerNode.indexedNode
- isComplete:(oldServerNode.isFullyInitialized || path.isEmpty)
- isFiltered:oldServerNode.isFiltered];
- return [self
- generateEventCacheAfterServerEvent:newViewCache
- path:path
- writesCache:writesCache
- source:[FNoCompleteChildSource instance]
- accumulator:accumulator];
- }
- @end
|