Files

656 lines
36 KiB
Objective-C

/*
* 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 "FCompleteChildSource.h"
#import "FWriteTreeRef.h"
#import "FViewCache.h"
#import "FCacheNode.h"
#import "FNode.h"
#import "FOperation.h"
#import "FOperationSource.h"
#import "FChildChangeAccumulator.h"
#import "FNodeFilter.h"
#import "FOverwrite.h"
#import "FMerge.h"
#import "FAckUserWrite.h"
#import "FViewProcessorResult.h"
#import "FIRDataEventType.h"
#import "FChange.h"
#import "FEmptyNode.h"
#import "FChildrenNode.h"
#import "FPath.h"
#import "FKeyIndex.h"
#import "FCompoundWrite.h"
#import "FImmutableTree.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