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

391 lines
12 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 <FirebaseCore/FIRLogger.h>
#import "FUtilities.h"
#import "FStringUtilities.h"
#import "FConstants.h"
#import "FAtomicNumber.h"
#define ARC4RANDOM_MAX 0x100000000
#define INTEGER_32_MIN (-2147483648)
#define INTEGER_32_MAX 2147483647
#pragma mark -
#pragma mark C functions
static FLogLevel logLevel = FLogLevelInfo; // Default log level is info
static NSMutableDictionary* options = nil;
BOOL FFIsLoggingEnabled(FLogLevel level) {
return level >= logLevel;
}
void firebaseJobsTroll(void) {
FFLog(@"I-RDB095001", @"password super secret; JFK conspiracy; Hello there! Having fun digging through Firebase? We're always hiring! jobs@firebase.com");
}
#pragma mark -
#pragma mark Private property and singleton specification
@interface FUtilities() {
}
@property (nonatomic, strong) FAtomicNumber* localUid;
+ (FUtilities*)singleton;
@end
@implementation FUtilities
@synthesize localUid;
- (id)init
{
self = [super init];
if (self) {
self.localUid = [[FAtomicNumber alloc] init];
}
return self;
}
// TODO: We really want to be able to set the log level
+ (void) setLoggingEnabled:(BOOL)enabled {
logLevel = enabled ? FLogLevelDebug : FLogLevelInfo;
}
+ (BOOL) getLoggingEnabled {
return logLevel == FLogLevelDebug;
}
+ (FUtilities*) singleton
{
static dispatch_once_t pred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&pred, ^{
_sharedObject = [[self alloc] init]; // or some other init method
});
return _sharedObject;
}
// Refactor as a category of NSString
+ (NSArray *) splitString:(NSString *) str intoMaxSize:(const unsigned int) size {
if(str.length <= size) {
return [NSArray arrayWithObject:str];
}
NSMutableArray* dataSegs = [[NSMutableArray alloc] init];
for(int c = 0; c < str.length; c += size) {
if (c + size > str.length) {
int rangeStart = c;
unsigned long rangeLength = size - ((c + size) - str.length);
[dataSegs addObject:[str substringWithRange:NSMakeRange(rangeStart, rangeLength)]];
}
else {
int rangeStart = c;
int rangeLength = size;
[dataSegs addObject:[str substringWithRange:NSMakeRange(rangeStart, rangeLength)]];
}
}
return dataSegs;
}
+ (NSNumber *) LUIDGenerator {
FUtilities* f = [FUtilities singleton];
return [f.localUid getAndIncrement];
}
+ (NSString *) decodePath:(NSString *)pathString {
NSMutableArray* decodedPieces = [[NSMutableArray alloc] init];
NSArray* pieces = [pathString componentsSeparatedByString:@"/"];
for (NSString* piece in pieces) {
if (piece.length > 0) {
[decodedPieces addObject:[FStringUtilities urlDecoded:piece]];
}
}
return [NSString stringWithFormat:@"/%@", [decodedPieces componentsJoinedByString:@"/"]];
}
+ (FParsedUrl *) parseUrl:(NSString *)url {
NSString* original = url;
//NSURL* n = [[NSURL alloc] initWithString:url]
NSString* host;
NSString* namespace;
bool secure;
NSString* scheme = nil;
FPath* path = nil;
NSRange colonIndex = [url rangeOfString:@"//"];
if (colonIndex.location != NSNotFound) {
scheme = [url substringToIndex:colonIndex.location - 1];
url = [url substringFromIndex:colonIndex.location + 2];
}
NSInteger slashIndex = [url rangeOfString:@"/"].location;
if (slashIndex == NSNotFound) {
slashIndex = url.length;
}
host = [[url substringToIndex:slashIndex] lowercaseString];
if (slashIndex >= url.length) {
url = @"";
} else {
url = [url substringFromIndex:slashIndex + 1];
}
NSArray *parts = [host componentsSeparatedByString:@"."];
if([parts count] == 3) {
NSInteger colonIndex = [[parts objectAtIndex:2] rangeOfString:@":"].location;
if (colonIndex != NSNotFound) {
// we have a port, use the provided scheme
secure = [scheme isEqualToString:@"https"];
} else {
secure = YES;
}
namespace = [[parts objectAtIndex:0] lowercaseString];
NSString* pathString = [self decodePath:[NSString stringWithFormat:@"/%@", url]];
path = [[FPath alloc] initWith:pathString];
}
else {
[NSException raise:@"No Firebase database specified." format:@"No Firebase database found for input: %@", url];
}
FRepoInfo* repoInfo = [[FRepoInfo alloc] initWithHost:host isSecure:secure withNamespace:namespace];
FFLog(@"I-RDB095002", @"---> Parsed (%@) to: (%@,%@); ns=(%@); path=(%@)", original, [repoInfo description], [repoInfo connectionURL], repoInfo.namespace, [path description]);
FParsedUrl* parsedUrl = [[FParsedUrl alloc] init];
parsedUrl.repoInfo = repoInfo;
parsedUrl.path = path;
return parsedUrl;
}
/*
case str: JString => priString + "string:" + str.s;
case bool: JBool => priString + "boolean:" + bool.value;
case double: JDouble => priString + "number:" + double.num;
case int: JInt => priString + "number:" + int.num;
case _ => {
error("Leaf node has value '" + data.value + "' of invalid type '" + data.value.getClass.toString + "'");
"";
}
*/
+ (NSString *) getJavascriptType:(id)obj {
if ([obj isKindOfClass:[NSDictionary class]]) {
return kJavaScriptObject;
} else if([obj isKindOfClass:[NSString class]]) {
return kJavaScriptString;
}
else if ([obj isKindOfClass:[NSNumber class]]) {
// We used to just compare to @encode(BOOL) as suggested at
// http://stackoverflow.com/questions/2518761/get-type-of-nsnumber, but on arm64, @encode(BOOL) returns "B"
// instead of "c" even though objCType still returns 'c' (signed char). So check both.
if(strcmp([obj objCType], @encode(BOOL)) == 0 ||
strcmp([obj objCType], @encode(signed char)) == 0) {
return kJavaScriptBoolean;
}
else {
return kJavaScriptNumber;
}
}
else {
return kJavaScriptNull;
}
}
+ (NSError *) errorForStatus:(NSString *)status andReason:(NSString *)reason {
static dispatch_once_t pred = 0;
__strong static NSDictionary* errorMap = nil;
__strong static NSDictionary* errorCodes = nil;
dispatch_once(&pred, ^{
errorMap = @{
@"permission_denied": @"Permission Denied",
@"unavailable": @"Service is unavailable",
kFErrorWriteCanceled: @"Write cancelled by user"
};
errorCodes = @{
@"permission_denied": @1,
@"unavailable": @2,
kFErrorWriteCanceled: @3
};
});
if ([status isEqualToString:kFWPResponseForActionStatusOk]) {
return nil;
} else {
NSInteger code;
NSString* desc = nil;
if (reason) {
desc = reason;
} else if ([errorMap objectForKey:status] != nil) {
desc = [errorMap objectForKey:status];
} else {
desc = status;
}
if ([errorCodes objectForKey:status] != nil) {
NSNumber* num = [errorCodes objectForKey:status];
code = [num integerValue];
} else {
// XXX what to do here?
code = 9999;
}
return [[NSError alloc] initWithDomain:kFErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: desc}];
}
}
+ (NSNumber *) intForString:(NSString *)string {
static NSCharacterSet *notDigits = nil;
if (!notDigits) {
notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
}
if ([string rangeOfCharacterFromSet:notDigits].length == 0) {
NSInteger num;
NSScanner* scanner = [NSScanner scannerWithString:string];
if ([scanner scanInteger:&num]) {
return [NSNumber numberWithInteger:num];
}
}
return nil;
}
+ (NSString *) ieee754StringForNumber:(NSNumber *)val {
double d = [val doubleValue];
NSData* data = [NSData dataWithBytes:&d length:sizeof(double)];
NSMutableString* str = [[NSMutableString alloc] init];
const unsigned char* buffer = (const unsigned char*)[data bytes];
for (int i = 0; i < data.length; i++) {
unsigned char byte = buffer[7 - i];
[str appendFormat:@"%02x", byte];
}
return str;
}
static inline BOOL tryParseStringToInt(__unsafe_unretained NSString* str, NSInteger* integer) {
// First do some cheap checks (NOTE: The below checks are significantly faster than an equivalent regex :-( ).
NSUInteger length = str.length;
if (length > 11 || length == 0) {
return NO;
}
long long value = 0;
BOOL negative = NO;
NSUInteger i = 0;
if ([str characterAtIndex:0] == '-') {
if (length == 1) {
return NO;
}
negative = YES;
i = 1;
}
for(; i < length; i++) {
unichar c = [str characterAtIndex:i];
// Must be a digit, or '-' if it's the first char.
if (c < '0' || c > '9') {
return NO;
} else {
int charValue = c - '0';
value = value*10 + charValue;
}
}
value = (negative) ? -value : value;
if (value < INTEGER_32_MIN || value > INTEGER_32_MAX) {
return NO;
} else {
*integer = (NSInteger)value;
return YES;
}
}
+ (NSString *) maxName {
static dispatch_once_t once;
static NSString *maxName;
dispatch_once(&once, ^{
maxName = [[NSString alloc] initWithFormat:@"[MAX_NAME]"];
});
return maxName;
}
+ (NSString *) minName {
static dispatch_once_t once;
static NSString *minName;
dispatch_once(&once, ^{
minName = [[NSString alloc] initWithFormat:@"[MIN_NAME]"];
});
return minName;
}
+ (NSComparisonResult) compareKey:(NSString *)a toKey:(NSString *)b {
if (a == b) {
return NSOrderedSame;
} else if (a == [FUtilities minName] || b == [FUtilities maxName]) {
return NSOrderedAscending;
} else if (b == [FUtilities minName] || a == [FUtilities maxName]) {
return NSOrderedDescending;
} else {
NSInteger aAsInt, bAsInt;
if (tryParseStringToInt(a, &aAsInt)) {
if (tryParseStringToInt(b, &bAsInt)) {
if (aAsInt > bAsInt) {
return NSOrderedDescending;
} else if (aAsInt < bAsInt) {
return NSOrderedAscending;
} else if (a.length > b.length) {
return NSOrderedDescending;
} else if (a.length < b.length) {
return NSOrderedAscending;
} else {
return NSOrderedSame;
}
} else {
return (NSComparisonResult) NSOrderedAscending;
}
} else if (tryParseStringToInt(b, &bAsInt)) {
return (NSComparisonResult) NSOrderedDescending;
} else {
// Perform literal character by character search to prevent a > b && b > a issues.
// Note that calling -(NSString *)decomposedStringWithCanonicalMapping also works.
return [a compare:b options:NSLiteralSearch];
}
}
}
+ (NSComparator) keyComparator {
return ^NSComparisonResult(__unsafe_unretained NSString *a, __unsafe_unretained NSString *b) {
return [FUtilities compareKey:a toKey:b];
};
}
+ (NSComparator) stringComparator {
return ^NSComparisonResult(__unsafe_unretained NSString *a, __unsafe_unretained NSString *b) {
return [a compare:b];
};
}
+ (double) randomDouble {
return ((double) arc4random() / ARC4RANDOM_MAX);
}
@end