Files
Patrick McDonagh 95467f9161 Adds firebase live data
Also no longer crashes when touching something while loading
2018-05-31 19:55:47 -05:00

237 lines
7.7 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 "FCompoundHash.h"
#import "FLeafNode.h"
#import "FStringUtilities.h"
#import "FSnapshotUtilities.h"
#import "FChildrenNode.h"
@interface FCompoundHashBuilder ()
@property (nonatomic, strong) FCompoundHashSplitStrategy splitStrategy;
@property (nonatomic, strong) NSMutableArray *currentPaths;
@property (nonatomic, strong) NSMutableArray *currentHashes;
@end
@implementation FCompoundHashBuilder {
// NOTE: We use the existence of this to know if we've started building a range (i.e. encountered a leaf node).
NSMutableString *optHashValueBuilder;
// The current path as a stack. This is used in combination with currentPathDepth to simultaneously store the
// last leaf node path. The depth is changed when descending and ascending, at the same time the current key
// is set for the current depth. Because the keys are left unchanged for ascending the path will also contain
// the path of the last visited leaf node (using lastLeafDepth elements)
NSMutableArray *currentPath;
NSInteger lastLeafDepth;
NSInteger currentPathDepth;
BOOL needsComma;
}
- (instancetype)initWithSplitStrategy:(FCompoundHashSplitStrategy)strategy {
self = [super init];
if (self != nil) {
self->_splitStrategy = strategy;
self->optHashValueBuilder = nil;
self->currentPath = [NSMutableArray array];
self->lastLeafDepth = -1;
self->currentPathDepth = 0;
self->needsComma = YES;
self->_currentPaths = [NSMutableArray array];
self->_currentHashes = [NSMutableArray array];
}
return self;
}
- (BOOL)isBuildingRange {
return self->optHashValueBuilder != nil;
}
- (NSUInteger)currentHashLength {
return self->optHashValueBuilder.length;
}
- (FPath *)currentPath {
return [self currentPathWithDepth:self->currentPathDepth];
}
- (FPath *)currentPathWithDepth:(NSInteger)depth {
NSArray *pieces = [self->currentPath subarrayWithRange:NSMakeRange(0, depth)];
return [[FPath alloc] initWithPieces:pieces andPieceNum:0];
}
- (void)enumerateCurrentPathToDepth:(NSInteger)depth withBlock:(void (^) (NSString *key))block {
for (NSInteger i = 0; i < depth; i++) {
block(self->currentPath[i]);
}
}
- (void)appendKey:(NSString *)key toString:(NSMutableString *)string {
[FSnapshotUtilities appendHashV2RepresentationForString:key toString:string];
}
- (void)ensureRange {
if (![self isBuildingRange]) {
optHashValueBuilder = [NSMutableString string];
[optHashValueBuilder appendString:@"("];
[self enumerateCurrentPathToDepth:self->currentPathDepth withBlock:^(NSString *key) {
[self appendKey:key toString:self->optHashValueBuilder];
[self->optHashValueBuilder appendString:@":("];
}];
self->needsComma = NO;
}
}
- (void)processLeaf:(FLeafNode *)leafNode {
[self ensureRange];
self->lastLeafDepth = self->currentPathDepth;
[FSnapshotUtilities appendHashRepresentationForLeafNode:leafNode
toString:self->optHashValueBuilder
hashVersion:FDataHashVersionV2];
self->needsComma = YES;
if (self.splitStrategy(self)) {
[self endRange];
}
}
- (void)startChild:(NSString *)key {
[self ensureRange];
if (self->needsComma) {
[self->optHashValueBuilder appendString:@","];
}
[self appendKey:key toString:self->optHashValueBuilder];
[self->optHashValueBuilder appendString:@":("];
if (self->currentPathDepth == currentPath.count) {
[self->currentPath addObject:key];
} else {
self->currentPath[self->currentPathDepth] = key;
}
self->currentPathDepth++;
self->needsComma = NO;
}
- (void)endChild {
self->currentPathDepth--;
if ([self isBuildingRange]) {
[self->optHashValueBuilder appendString:@")"];
}
self->needsComma = YES;
}
- (void)finishHashing {
NSAssert(self->currentPathDepth == 0, @"Can't finish hashing in the middle of processing a child");
if ([self isBuildingRange] ) {
[self endRange];
}
// Always close with the empty hash for the remaining range to allow simple appending
[self.currentHashes addObject:@""];
}
- (void)endRange {
NSAssert([self isBuildingRange], @"Can't end range without starting a range!");
// Add closing parenthesis for current depth
for (NSUInteger i = 0; i < currentPathDepth; i++) {
[self->optHashValueBuilder appendString:@")"];
}
[self->optHashValueBuilder appendString:@")"];
FPath *lastLeafPath = [self currentPathWithDepth:self->lastLeafDepth];
NSString *hash = [FStringUtilities base64EncodedSha1:self->optHashValueBuilder];
[self.currentHashes addObject:hash];
[self.currentPaths addObject:lastLeafPath];
self->optHashValueBuilder = nil;
}
@end
@interface FCompoundHash ()
@property (nonatomic, strong, readwrite) NSArray *posts;
@property (nonatomic, strong, readwrite) NSArray *hashes;
@end
@implementation FCompoundHash
- (id)initWithPosts:(NSArray *)posts hashes:(NSArray *)hashes {
self = [super init];
if (self != nil) {
if (posts.count != hashes.count - 1) {
[NSException raise:NSInvalidArgumentException format:@"Number of posts need to be n-1 for n hashes in FCompoundHash"];
}
self.posts = posts;
self.hashes = hashes;
}
return self;
}
+ (FCompoundHashSplitStrategy)simpleSizeSplitStrategyForNode:(id<FNode>)node {
NSUInteger estimatedSize = [FSnapshotUtilities estimateSerializedNodeSize:node];
// Splits for
// 1k -> 512 (2 parts)
// 5k -> 715 (7 parts)
// 100k -> 3.2k (32 parts)
// 500k -> 7k (71 parts)
// 5M -> 23k (228 parts)
NSUInteger splitThreshold = MAX(512, (NSUInteger)sqrt(estimatedSize * 100));
return ^BOOL(FCompoundHashBuilder *builder) {
// Never split on priorities
return [builder currentHashLength] > splitThreshold && ![[[builder currentPath] getBack] isEqualToString:@".priority"];
};
}
+ (FCompoundHash *)fromNode:(id<FNode>)node {
return [FCompoundHash fromNode:node splitStrategy:[FCompoundHash simpleSizeSplitStrategyForNode:node]];
}
+ (FCompoundHash *)fromNode:(id<FNode>)node splitStrategy:(FCompoundHashSplitStrategy)strategy {
if ([node isEmpty]) {
return [[FCompoundHash alloc] initWithPosts:@[] hashes:@[@""]];
} else {
FCompoundHashBuilder *builder = [[FCompoundHashBuilder alloc] initWithSplitStrategy:strategy];
[FCompoundHash processNode:node builder:builder];
[builder finishHashing];
return [[FCompoundHash alloc] initWithPosts:builder.currentPaths hashes:builder.currentHashes];
}
}
+ (void)processNode:(id<FNode>)node builder:(FCompoundHashBuilder *)builder {
if ([node isLeafNode]) {
[builder processLeaf:node];
} else {
NSAssert(![node isEmpty], @"Can't calculate hash on empty node!");
FChildrenNode *childrenNode = (FChildrenNode *)node;
[childrenNode enumerateChildrenAndPriorityUsingBlock:^(NSString *key, id<FNode> node, BOOL *stop) {
[builder startChild:key];
[self processNode:node builder:builder];
[builder endChild];
}];
}
}
@end