-
-
Notifications
You must be signed in to change notification settings - Fork 177
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
11797d8
commit 71e7823
Showing
5 changed files
with
277 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,253 @@ | ||
#include <napi-macros.h> | ||
#include <node_api.h> | ||
#include "napi.h" | ||
#include <leveldb/db.h> | ||
#include <leveldb/cache.h> | ||
#include <leveldb/filter_policy.h> | ||
|
||
// TODO add to napi-macros.h | ||
#define NAPI_RETURN_UNDEFINED() \ | ||
return 0; | ||
/** | ||
* Owns the LevelDB storage together with cache and filter | ||
* policy. | ||
*/ | ||
struct DbContext { | ||
DbContext (napi_env env) | ||
: env_(env), db_(NULL), blockCache_(NULL), filterPolicy_(NULL) {} | ||
|
||
~DbContext () { | ||
if (db_ != NULL) { | ||
delete db_; | ||
db_ = NULL; | ||
} | ||
} | ||
|
||
napi_env env_; | ||
leveldb::DB* db_; | ||
leveldb::Cache* blockCache_; | ||
const leveldb::FilterPolicy* filterPolicy_; | ||
}; | ||
|
||
/** | ||
* Runs when a DbContext is garbage collected. | ||
*/ | ||
static void FinalizeDbContext(napi_env env, void* data, void* hint) { | ||
if (data) { | ||
delete (DbContext*)data; | ||
} | ||
} | ||
|
||
/** | ||
* Returns a context object for a database. | ||
* E.g. `var dbContext = leveldown()` | ||
*/ | ||
NAPI_METHOD(leveldown) { | ||
DbContext* dbContext = new DbContext(env); | ||
|
||
napi_value result; | ||
NAPI_STATUS_THROWS(napi_create_external(env, dbContext, | ||
FinalizeDbContext, | ||
NULL, &result)); | ||
return result; | ||
} | ||
|
||
// TODO BooleanProperty() and Uint32Property() can be refactored | ||
|
||
/** | ||
* Returns a boolean property 'key' from 'obj'. | ||
* Returns 'DEFAULT' if the property doesn't exist. | ||
*/ | ||
static bool BooleanProperty(napi_env env, | ||
napi_value obj, | ||
const char* key, | ||
bool DEFAULT) | ||
{ | ||
bool exists = false; | ||
napi_value _key; | ||
napi_create_string_utf8(env, key, strlen(key), &_key); | ||
napi_has_property(env, obj, _key, &exists); | ||
|
||
if (exists) { | ||
napi_value value; | ||
napi_get_property(env, obj, _key, &value); | ||
bool result; | ||
napi_get_value_bool(env, value, &result); | ||
return result; | ||
} | ||
|
||
return DEFAULT; | ||
} | ||
|
||
/** | ||
* Returns a uint32 property 'key' from 'obj' | ||
* Returns 'DEFAULT' if the property doesn't exist. | ||
*/ | ||
static uint32_t Uint32Property(napi_env env, | ||
napi_value obj, | ||
const char* key, | ||
uint32_t DEFAULT) | ||
{ | ||
bool exists = false; | ||
napi_value _key; | ||
napi_create_string_utf8(env, key, strlen(key), &_key); | ||
napi_has_property(env, obj, _key, &exists); | ||
|
||
if (exists) { | ||
napi_value value; | ||
napi_get_property(env, obj, _key, &value); | ||
uint32_t result; | ||
napi_get_value_uint32(env, value, &result); | ||
return result; | ||
} | ||
|
||
return DEFAULT; | ||
} | ||
|
||
/** | ||
* Worker class for opening the database | ||
*/ | ||
struct OpenWorker { | ||
OpenWorker(napi_env env, | ||
DbContext* dbContext, | ||
char* location, | ||
bool createIfMissing, | ||
bool errorIfExists, | ||
bool compression, | ||
uint32_t writeBufferSize, | ||
uint32_t blockSize, | ||
uint32_t maxOpenFiles, | ||
uint32_t blockRestartInterval, | ||
uint32_t maxFileSize, | ||
napi_value callback) | ||
: env_(env), dbContext_(dbContext), location_(location) { | ||
options_.block_cache = dbContext->blockCache_; | ||
options_.filter_policy = dbContext->filterPolicy_; | ||
options_.create_if_missing = createIfMissing; | ||
options_.error_if_exists = errorIfExists; | ||
options_.compression = compression | ||
? leveldb::kSnappyCompression | ||
: leveldb::kNoCompression; | ||
options_.write_buffer_size = writeBufferSize; | ||
options_.block_size = blockSize; | ||
options_.max_open_files = maxOpenFiles; | ||
options_.block_restart_interval = blockRestartInterval; | ||
options_.max_file_size = maxFileSize; | ||
|
||
// Create reference to callback with ref count set to one. | ||
// TODO move to base class constructor | ||
napi_create_reference(env_, callback, 1, &callbackRef_); | ||
napi_value asyncResourceName; | ||
napi_create_string_utf8(env_, "leveldown::open", | ||
NAPI_AUTO_LENGTH, | ||
&asyncResourceName); | ||
napi_create_async_work(env_, callback, | ||
asyncResourceName, | ||
OpenWorker::Execute, | ||
OpenWorker::Complete, | ||
this, | ||
&asyncWork_); | ||
} | ||
|
||
~OpenWorker() { | ||
free(location_); | ||
// TODO move to base class destructor | ||
napi_delete_reference(env_, callbackRef_); | ||
napi_delete_async_work(env_, asyncWork_); | ||
} | ||
|
||
// TODO move to base class | ||
void Queue() { | ||
napi_queue_async_work(env_, asyncWork_); | ||
} | ||
|
||
static void Execute(napi_env env, void* data) { | ||
OpenWorker* self = (OpenWorker*)data; | ||
DbContext* dbContext = self->dbContext_; | ||
self->status_ = leveldb::DB::Open(self->options_, | ||
self->location_, | ||
&dbContext->db_); | ||
} | ||
|
||
static void Complete(napi_env env, napi_status status, void* data) { | ||
OpenWorker* self = (OpenWorker*)data; | ||
|
||
// TODO most of the things below can be moved to a | ||
// base class, all operations either calling back with | ||
// NULL or an error have identical logic. | ||
|
||
const int argc = 1; | ||
napi_value argv[argc]; | ||
|
||
napi_value global; | ||
napi_get_global(env, &global); | ||
napi_value callback; | ||
napi_get_reference_value(env, self->callbackRef_, &callback); | ||
|
||
if (self->status_.ok()) { | ||
napi_get_null(env, &argv[0]); | ||
} else { | ||
const char* str = self->status_.ToString().c_str(); | ||
napi_value msg; | ||
napi_create_string_utf8(env, str, strlen(str), &msg); | ||
napi_create_error(env, NULL, msg, &argv[0]); | ||
} | ||
|
||
napi_call_function(env, global, callback, argc, argv, NULL); | ||
|
||
delete self; | ||
} | ||
|
||
// TODO move to base class | ||
napi_env env_; | ||
napi_ref callbackRef_; | ||
napi_async_work asyncWork_; | ||
DbContext* dbContext_; | ||
leveldb::Status status_; | ||
|
||
leveldb::Options options_; | ||
char* location_; | ||
}; | ||
|
||
/** | ||
* Opens a database. | ||
*/ | ||
NAPI_METHOD(open) { | ||
NAPI_ARGV(4); | ||
NAPI_DB_CONTEXT(); | ||
NAPI_ARGV_UTF8_MALLOC(location, 1); | ||
|
||
// Options object and properties. | ||
napi_value options = argv[2]; | ||
bool createIfMissing = BooleanProperty(env, options, "createIfMissing", true); | ||
bool errorIfExists = BooleanProperty(env, options, "errorIfExists", false); | ||
bool compression = BooleanProperty(env, options, "compression", true); | ||
|
||
uint32_t cacheSize = Uint32Property(env, options, "cacheSize", 8 << 20); | ||
uint32_t writeBufferSize = Uint32Property(env, options , "writeBufferSize" , 4 << 20); | ||
uint32_t blockSize = Uint32Property(env, options, "blockSize", 4096); | ||
uint32_t maxOpenFiles = Uint32Property(env, options, "maxOpenFiles", 1000); | ||
uint32_t blockRestartInterval = Uint32Property(env, options, | ||
"blockRestartInterval", 16); | ||
uint32_t maxFileSize = Uint32Property(env, options, "maxFileSize", 2 << 20); | ||
|
||
// TODO clean these up in close() | ||
dbContext->blockCache_ = leveldb::NewLRUCache(cacheSize); | ||
dbContext->filterPolicy_ = leveldb::NewBloomFilterPolicy(10); | ||
|
||
napi_value callback = argv[3]; | ||
OpenWorker* worker = new OpenWorker(env, | ||
dbContext, | ||
location, | ||
createIfMissing, | ||
errorIfExists, | ||
compression, | ||
writeBufferSize, | ||
blockSize, | ||
maxOpenFiles, | ||
blockRestartInterval, | ||
maxFileSize, | ||
callback); | ||
worker->Queue(); | ||
NAPI_RETURN_UNDEFINED(); | ||
} | ||
|
||
NAPI_INIT() { | ||
NAPI_EXPORT_FUNCTION(leveldown); | ||
NAPI_EXPORT_FUNCTION(open); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#include <napi-macros.h> | ||
#include <node_api.h> | ||
|
||
#define NAPI_DB_CONTEXT() \ | ||
DbContext* dbContext = NULL; \ | ||
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&dbContext)); | ||
|
||
// TODO move to napi-macros.h | ||
#define NAPI_RETURN_UNDEFINED() \ | ||
return 0; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
require('./leveldown-test') | ||
require('./abstract-leveldown-test') | ||
// require('./approximate-size-test') | ||
// require('./cleanup-hanging-iterators-test') | ||
// require('./compact-range-test') | ||
// require('./compression-test') | ||
// require('./destroy-test') | ||
// require('./getproperty-test') | ||
// require('./iterator-gc-test') | ||
// require('./iterator-recursion-test') | ||
// require('./iterator-test') | ||
// require('./port-libuv-fix-test') | ||
// require('./repair-test') | ||
// require('./segfault-test') |