mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
501 lines
14 KiB
Plaintext
501 lines
14 KiB
Plaintext
//
|
|
// APLevelDB.m
|
|
//
|
|
// Created by Adam Preble on 1/23/12.
|
|
// Copyright (c) 2012 Adam Preble. All rights reserved.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
//
|
|
// Portions of APLevelDB are based on LevelDB-ObjC:
|
|
// https://github.com/hoisie/LevelDB-ObjC
|
|
// Specifically the SliceFromString/StringFromSlice macros, and the structure of
|
|
// the enumeration methods. License for those potions follows:
|
|
//
|
|
// Copyright (c) 2011 Pave Labs
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
|
|
#import "FirebaseDatabase/Sources/third_party/Wrap-leveldb/APLevelDB.h"
|
|
|
|
#import "leveldb/db.h"
|
|
#import "leveldb/options.h"
|
|
#import "leveldb/write_batch.h"
|
|
|
|
NSString * const APLevelDBErrorDomain = @"APLevelDBErrorDomain";
|
|
|
|
#define SliceFromString(_string_) (leveldb::Slice((char *)[_string_ UTF8String], [_string_ lengthOfBytesUsingEncoding:NSUTF8StringEncoding]))
|
|
#define StringFromSlice(_slice_) ([[NSString alloc] initWithBytes:_slice_.data() length:_slice_.size() encoding:NSUTF8StringEncoding])
|
|
|
|
|
|
@interface APLevelDBWriteBatch : NSObject <APLevelDBWriteBatch> {
|
|
@package
|
|
leveldb::WriteBatch _batch;
|
|
}
|
|
|
|
@property (nonatomic, strong) APLevelDB *levelDB;
|
|
|
|
- (id)initWithLevelDB:(APLevelDB *)levelDB;
|
|
@end
|
|
|
|
|
|
#pragma mark - APLevelDB
|
|
|
|
@interface APLevelDB () {
|
|
leveldb::DB *_db;
|
|
leveldb::ReadOptions _readOptions;
|
|
leveldb::WriteOptions _writeOptions;
|
|
}
|
|
- (id)initWithPath:(NSString *)path error:(NSError **)errorOut;
|
|
+ (leveldb::Options)defaultCreateOptions;
|
|
@property (nonatomic, readonly) leveldb::DB *db;
|
|
@end
|
|
|
|
|
|
@implementation APLevelDB
|
|
|
|
@synthesize path = _path;
|
|
@synthesize db = _db;
|
|
|
|
+ (APLevelDB *)levelDBWithPath:(NSString *)path error:(NSError *__autoreleasing *)errorOut
|
|
{
|
|
return [[APLevelDB alloc] initWithPath:path error:errorOut];
|
|
}
|
|
|
|
- (id)initWithPath:(NSString *)path error:(NSError *__autoreleasing *)errorOut
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
_path = path;
|
|
|
|
leveldb::Options options = [[self class] defaultCreateOptions];
|
|
|
|
leveldb::Status status = leveldb::DB::Open(options, [_path UTF8String], &_db);
|
|
|
|
if (!status.ok())
|
|
{
|
|
if (errorOut)
|
|
{
|
|
NSString *statusString = [[NSString alloc] initWithCString:status.ToString().c_str() encoding:NSUTF8StringEncoding];
|
|
*errorOut = [NSError errorWithDomain:APLevelDBErrorDomain
|
|
code:0
|
|
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:statusString, NSLocalizedDescriptionKey, nil]];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
_writeOptions.sync = false;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)close {
|
|
if (_db != NULL) {
|
|
delete _db;
|
|
_db = NULL;
|
|
}
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
if (_db != NULL) {
|
|
delete _db;
|
|
_db = NULL;
|
|
}
|
|
}
|
|
|
|
+ (leveldb::Options)defaultCreateOptions
|
|
{
|
|
leveldb::Options options;
|
|
options.create_if_missing = true;
|
|
return options;
|
|
}
|
|
|
|
- (BOOL)setData:(NSData *)data forKey:(NSString *)key
|
|
{
|
|
leveldb::Slice keySlice = SliceFromString(key);
|
|
leveldb::Slice valueSlice = leveldb::Slice((const char *)[data bytes], (size_t)[data length]);
|
|
leveldb::Status status = _db->Put(_writeOptions, keySlice, valueSlice);
|
|
return (status.ok() == true);
|
|
}
|
|
|
|
- (BOOL)setString:(NSString *)str forKey:(NSString *)key
|
|
{
|
|
// This could have been based on
|
|
leveldb::Slice keySlice = SliceFromString(key);
|
|
leveldb::Slice valueSlice = SliceFromString(str);
|
|
leveldb::Status status = _db->Put(_writeOptions, keySlice, valueSlice);
|
|
return (status.ok() == true);
|
|
}
|
|
|
|
- (NSData *)dataForKey:(NSString *)key
|
|
{
|
|
leveldb::Slice keySlice = SliceFromString(key);
|
|
std::string valueCPPString;
|
|
leveldb::Status status = _db->Get(_readOptions, keySlice, &valueCPPString);
|
|
|
|
if (!status.ok())
|
|
return nil;
|
|
else
|
|
return [NSData dataWithBytes:valueCPPString.data() length:valueCPPString.size()];
|
|
}
|
|
|
|
- (NSString *)stringForKey:(NSString *)key
|
|
{
|
|
leveldb::Slice keySlice = SliceFromString(key);
|
|
std::string valueCPPString;
|
|
leveldb::Status status = _db->Get(_readOptions, keySlice, &valueCPPString);
|
|
|
|
// We assume (dangerously?) UTF-8 string encoding:
|
|
if (!status.ok())
|
|
return nil;
|
|
else
|
|
return [[NSString alloc] initWithBytes:valueCPPString.data() length:valueCPPString.size() encoding:NSUTF8StringEncoding];
|
|
}
|
|
|
|
- (BOOL)removeKey:(NSString *)key
|
|
{
|
|
leveldb::Slice keySlice = SliceFromString(key);
|
|
leveldb::Status status = _db->Delete(_writeOptions, keySlice);
|
|
return (status.ok() == true);
|
|
}
|
|
|
|
- (NSArray *)allKeys
|
|
{
|
|
NSMutableArray *keys = [NSMutableArray array];
|
|
[self enumerateKeys:^(NSString *key, BOOL *stop) {
|
|
[keys addObject:key];
|
|
}];
|
|
return keys;
|
|
}
|
|
|
|
- (void)enumerateKeysAndValuesAsStrings:(void (^)(NSString *key, NSString *value, BOOL *stop))block
|
|
{
|
|
[self enumerateKeysWithPrefix:@"" asStrings:block];
|
|
}
|
|
|
|
- (void)enumerateKeysWithPrefix:(NSString *)prefixString asStrings:(void (^)(NSString *, NSString *, BOOL *))block
|
|
{
|
|
@autoreleasepool {
|
|
BOOL stop = NO;
|
|
leveldb::Iterator* iter = _db->NewIterator(leveldb::ReadOptions());
|
|
leveldb::Slice prefix = SliceFromString(prefixString);
|
|
for (iter->Seek(prefix); iter->Valid(); iter->Next()) {
|
|
leveldb::Slice key = iter->key(), value = iter->value();
|
|
if (key.starts_with(prefix)) {
|
|
NSString *k = StringFromSlice(key);
|
|
NSString *v = [[NSString alloc] initWithBytes:value.data() length:value.size() encoding:NSUTF8StringEncoding];
|
|
block(k, v, &stop);
|
|
if (stop)
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete iter;
|
|
}
|
|
}
|
|
|
|
- (void)enumerateKeys:(void (^)(NSString *key, BOOL *stop))block
|
|
{
|
|
[self enumerateKeysWithPrefix:@"" usingBlock:block];
|
|
}
|
|
|
|
- (void)enumerateKeysWithPrefix:(NSString *)prefixString usingBlock:(void (^)(NSString *key, BOOL *stop))block;
|
|
{
|
|
@autoreleasepool {
|
|
BOOL stop = NO;
|
|
leveldb::Slice prefix = SliceFromString(prefixString);
|
|
leveldb::Iterator* iter = _db->NewIterator(leveldb::ReadOptions());
|
|
for (iter->Seek(prefix); iter->Valid(); iter->Next()) {
|
|
leveldb::Slice key = iter->key();
|
|
if (key.starts_with(prefix)) {
|
|
NSString *k = StringFromSlice(key);
|
|
block(k, &stop);
|
|
if (stop)
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete iter;
|
|
}
|
|
}
|
|
|
|
- (void)enumerateKeysAndValuesAsData:(void (^)(NSString *key, NSData *data, BOOL *stop))block
|
|
{
|
|
[self enumerateKeysWithPrefix:@"" asData:block];
|
|
}
|
|
|
|
- (void)enumerateKeysWithPrefix:(NSString *)prefixString asData:(void (^)(NSString *, NSData *, BOOL *))block
|
|
{
|
|
@autoreleasepool {
|
|
BOOL stop = NO;
|
|
leveldb::Iterator* iter = _db->NewIterator(leveldb::ReadOptions());
|
|
leveldb::Slice prefix = SliceFromString(prefixString);
|
|
for (iter->Seek(prefix); iter->Valid(); iter->Next()) {
|
|
leveldb::Slice key = iter->key(), value = iter->value();
|
|
if (key.starts_with(prefix)) {
|
|
NSString *k = StringFromSlice(key);
|
|
NSData *data = [NSData dataWithBytes:value.data() length:value.size()];
|
|
block(k, data, &stop);
|
|
if (stop)
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete iter;
|
|
}
|
|
}
|
|
|
|
- (NSUInteger)exactSizeFrom:(NSString *)from to:(NSString *)to {
|
|
NSUInteger size = 0;
|
|
leveldb::Iterator* iter = _db->NewIterator(leveldb::ReadOptions());
|
|
leveldb::Slice fromSlice = SliceFromString(from);
|
|
leveldb::Slice toSlice = SliceFromString(to);
|
|
iter->Seek(fromSlice);
|
|
while (iter->Valid() && iter->key().compare(toSlice) <= 0) {
|
|
size += iter->value().size();
|
|
iter->Next();
|
|
}
|
|
delete iter;
|
|
return size;
|
|
}
|
|
|
|
|
|
- (NSUInteger)approximateSizeFrom:(NSString *)from to:(NSString *)to {
|
|
leveldb::Range ranges[1];
|
|
leveldb::Slice fromSlice = SliceFromString(from);
|
|
leveldb::Slice toSlice = SliceFromString(to);
|
|
ranges[0] = leveldb::Range(fromSlice, toSlice);
|
|
uint64_t sizes[1];
|
|
_db->GetApproximateSizes(ranges, 1, sizes);
|
|
return (NSUInteger)sizes[0];
|
|
}
|
|
|
|
#pragma mark - Subscripting Support
|
|
|
|
- (id)objectForKeyedSubscript:(id)key
|
|
{
|
|
if (![key respondsToSelector: @selector(componentsSeparatedByString:)])
|
|
{
|
|
[NSException raise:NSInvalidArgumentException format:@"key must be an NSString"];
|
|
}
|
|
return [self stringForKey:key];
|
|
}
|
|
- (void)setObject:(id)thing forKeyedSubscript:(id<NSCopying>)key
|
|
{
|
|
id idKey = (id) key;
|
|
if (![idKey respondsToSelector: @selector(componentsSeparatedByString:)])
|
|
{
|
|
[NSException raise:NSInvalidArgumentException format:@"key must be NSString or NSData"];
|
|
}
|
|
|
|
if ([thing respondsToSelector:@selector(componentsSeparatedByString:)])
|
|
[self setString:thing forKey:(NSString *)key];
|
|
else if ([thing respondsToSelector:@selector(subdataWithRange:)])
|
|
[self setData:thing forKey:(NSString *)key];
|
|
else
|
|
[NSException raise:NSInvalidArgumentException format:@"object must be NSString or NSData"];
|
|
}
|
|
|
|
#pragma mark - Atomic Updates
|
|
|
|
- (id<APLevelDBWriteBatch>)beginWriteBatch
|
|
{
|
|
APLevelDBWriteBatch *batch = [[APLevelDBWriteBatch alloc] initWithLevelDB:self];
|
|
return batch;
|
|
}
|
|
|
|
- (BOOL)commitWriteBatch:(id<APLevelDBWriteBatch>)theBatch
|
|
{
|
|
if (!theBatch)
|
|
return NO;
|
|
|
|
APLevelDBWriteBatch *batch = theBatch;
|
|
|
|
leveldb::Status status;
|
|
status = _db->Write(_writeOptions, &batch->_batch);
|
|
return (status.ok() == true);
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
#pragma mark - APLevelDBIterator
|
|
|
|
@interface APLevelDBIterator () {
|
|
leveldb::Iterator *_iter;
|
|
}
|
|
|
|
@property (nonatomic, strong) APLevelDB *levelDB;
|
|
@end
|
|
|
|
|
|
|
|
@implementation APLevelDBIterator
|
|
|
|
+ (id)iteratorWithLevelDB:(APLevelDB *)db
|
|
{
|
|
APLevelDBIterator *iter = [[[self class] alloc] initWithLevelDB:db];
|
|
return iter;
|
|
}
|
|
|
|
- (id)initWithLevelDB:(APLevelDB *)db
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
// Hold on to the database so it doesn't get deallocated before the iterator is deallocated
|
|
self->_levelDB = db;
|
|
_iter = db.db->NewIterator(leveldb::ReadOptions());
|
|
_iter->SeekToFirst();
|
|
if (!_iter->Valid())
|
|
return nil;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
[NSException raise:@"BadInitializer" format:@"Use the designated initializer, -initWithLevelDB:, instead."];
|
|
return nil;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
self->_levelDB = nil;
|
|
delete _iter;
|
|
_iter = NULL;
|
|
}
|
|
|
|
- (BOOL)seekToKey:(NSString *)key
|
|
{
|
|
leveldb::Slice target = SliceFromString(key);
|
|
_iter->Seek(target);
|
|
return _iter->Valid() == true;
|
|
}
|
|
|
|
- (void)seekToFirst
|
|
{
|
|
_iter->SeekToFirst();
|
|
}
|
|
|
|
- (void)seekToLast
|
|
{
|
|
_iter->SeekToLast();
|
|
}
|
|
|
|
- (NSString *)nextKey
|
|
{
|
|
_iter->Next();
|
|
return [self key];
|
|
}
|
|
|
|
- (NSString *)key
|
|
{
|
|
if (_iter->Valid() == false)
|
|
return nil;
|
|
leveldb::Slice value = _iter->key();
|
|
return StringFromSlice(value);
|
|
}
|
|
|
|
- (NSString *)valueAsString
|
|
{
|
|
if (_iter->Valid() == false)
|
|
return nil;
|
|
leveldb::Slice value = _iter->value();
|
|
return StringFromSlice(value);
|
|
}
|
|
|
|
- (NSData *)valueAsData
|
|
{
|
|
if (_iter->Valid() == false)
|
|
return nil;
|
|
leveldb::Slice value = _iter->value();
|
|
return [NSData dataWithBytes:value.data() length:value.size()];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
#pragma mark - APLevelDBWriteBatch
|
|
|
|
@implementation APLevelDBWriteBatch
|
|
|
|
- (id)initWithLevelDB:(APLevelDB *)levelDB {
|
|
self = [super init];
|
|
if (self != nil) {
|
|
self->_levelDB = levelDB;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)setData:(NSData *)data forKey:(NSString *)key
|
|
{
|
|
leveldb::Slice keySlice = SliceFromString(key);
|
|
leveldb::Slice valueSlice = leveldb::Slice((const char *)[data bytes], (size_t)[data length]);
|
|
_batch.Put(keySlice, valueSlice);
|
|
}
|
|
- (void)setString:(NSString *)str forKey:(NSString *)key
|
|
{
|
|
leveldb::Slice keySlice = SliceFromString(key);
|
|
leveldb::Slice valueSlice = SliceFromString(str);
|
|
_batch.Put(keySlice, valueSlice);
|
|
}
|
|
|
|
- (void)removeKey:(NSString *)key
|
|
{
|
|
leveldb::Slice keySlice = SliceFromString(key);
|
|
_batch.Delete(keySlice);
|
|
}
|
|
|
|
- (void)clear
|
|
{
|
|
_batch.Clear();
|
|
}
|
|
|
|
- (BOOL)commit {
|
|
return [self.levelDB commitWriteBatch:self];
|
|
}
|
|
|
|
@end
|
|
|