Move database.get and database.set off the main thread.
This commit is contained in:
parent
e5fee5c306
commit
c259defab5
47
core/core.js
47
core/core.js
@ -206,7 +206,7 @@ function getUser(caller, process) {
|
|||||||
* @param {*} process
|
* @param {*} process
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function getApps(user, process) {
|
async function getApps(user, process) {
|
||||||
if (
|
if (
|
||||||
process.credentials &&
|
process.credentials &&
|
||||||
process.credentials.session &&
|
process.credentials.session &&
|
||||||
@ -221,10 +221,12 @@ function getApps(user, process) {
|
|||||||
if (user) {
|
if (user) {
|
||||||
let db = new Database(user);
|
let db = new Database(user);
|
||||||
try {
|
try {
|
||||||
let names = JSON.parse(db.get('apps'));
|
let names = JSON.parse(await db.get('apps'));
|
||||||
return Object.fromEntries(
|
let result = {};
|
||||||
names.map((name) => [name, db.get('path:' + name)])
|
for (let name of names) {
|
||||||
);
|
result[name] = await db.get('path:' + name);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
@ -320,9 +322,9 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
user: getUser(process, process),
|
user: getUser(process, process),
|
||||||
users: function () {
|
users: async function () {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(new Database('auth').get('users'));
|
return JSON.parse(await new Database('auth').get('users'));
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -509,25 +511,20 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
setGlobalSettings(gGlobalSettings);
|
setGlobalSettings(gGlobalSettings);
|
||||||
print('Done.');
|
print('Done.');
|
||||||
};
|
};
|
||||||
imports.core.deleteUser = function (user) {
|
imports.core.deleteUser = async function (user) {
|
||||||
return Promise.resolve(
|
await imports.core.permissionTest('delete_user')
|
||||||
imports.core.permissionTest('delete_user')
|
|
||||||
).then(function () {
|
|
||||||
let db = new Database('auth');
|
let db = new Database('auth');
|
||||||
|
|
||||||
db.remove('user:' + user);
|
db.remove('user:' + user);
|
||||||
|
|
||||||
let users = new Set();
|
let users = new Set();
|
||||||
let users_original = db.get('users');
|
let users_original = await db.get('users');
|
||||||
try {
|
try {
|
||||||
users = new Set(JSON.parse(users_original));
|
users = new Set(JSON.parse(users_original));
|
||||||
} catch {}
|
} catch {}
|
||||||
users.delete(user);
|
users.delete(user);
|
||||||
users = JSON.stringify([...users].sort());
|
users = JSON.stringify([...users].sort());
|
||||||
if (users !== users_original) {
|
if (users !== users_original) {
|
||||||
db.set('users', users);
|
await db.set('users', users);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (options.api) {
|
if (options.api) {
|
||||||
@ -806,10 +803,10 @@ async function getProcessBlob(blobId, key, options) {
|
|||||||
* @param {*} settings
|
* @param {*} settings
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function setGlobalSettings(settings) {
|
async function setGlobalSettings(settings) {
|
||||||
gGlobalSettings = settings;
|
gGlobalSettings = settings;
|
||||||
try {
|
try {
|
||||||
return new Database('core').set('settings', JSON.stringify(settings));
|
return await new Database('core').set('settings', JSON.stringify(settings));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
print('Error storing settings:', error);
|
print('Error storing settings:', error);
|
||||||
}
|
}
|
||||||
@ -1052,7 +1049,7 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
let database = new Database(user);
|
let database = new Database(user);
|
||||||
|
|
||||||
let app_object = JSON.parse(utf8Decode(request.body));
|
let app_object = JSON.parse(utf8Decode(request.body));
|
||||||
let previous_id = database.get('path:' + appName);
|
let previous_id = await database.get('path:' + appName);
|
||||||
if (previous_id) {
|
if (previous_id) {
|
||||||
try {
|
try {
|
||||||
let previous_object = JSON.parse(
|
let previous_object = JSON.parse(
|
||||||
@ -1073,7 +1070,7 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
let newBlobId = await ssb.blobStore(JSON.stringify(app_object));
|
let newBlobId = await ssb.blobStore(JSON.stringify(app_object));
|
||||||
|
|
||||||
let apps = new Set();
|
let apps = new Set();
|
||||||
let apps_original = database.get('apps');
|
let apps_original = await database.get('apps');
|
||||||
try {
|
try {
|
||||||
apps = new Set(JSON.parse(apps_original));
|
apps = new Set(JSON.parse(apps_original));
|
||||||
} catch {}
|
} catch {}
|
||||||
@ -1082,9 +1079,9 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
}
|
}
|
||||||
apps = JSON.stringify([...apps].sort());
|
apps = JSON.stringify([...apps].sort());
|
||||||
if (apps != apps_original) {
|
if (apps != apps_original) {
|
||||||
database.set('apps', apps);
|
await database.set('apps', apps);
|
||||||
}
|
}
|
||||||
database.set('path:' + appName, newBlobId);
|
await database.set('path:' + appName, newBlobId);
|
||||||
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
|
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
|
||||||
response.end('/' + newBlobId);
|
response.end('/' + newBlobId);
|
||||||
} else {
|
} else {
|
||||||
@ -1115,10 +1112,10 @@ async function blobHandler(request, response, blobId, uri) {
|
|||||||
let database = new Database(user);
|
let database = new Database(user);
|
||||||
let apps = new Set();
|
let apps = new Set();
|
||||||
try {
|
try {
|
||||||
apps = new Set(JSON.parse(database.get('apps')));
|
apps = new Set(JSON.parse(await database.get('apps')));
|
||||||
} catch {}
|
} catch {}
|
||||||
if (apps.delete(appName)) {
|
if (apps.delete(appName)) {
|
||||||
database.set('apps', JSON.stringify([...apps].sort()));
|
await database.set('apps', JSON.stringify([...apps].sort()));
|
||||||
}
|
}
|
||||||
database.remove('path:' + appName);
|
database.remove('path:' + appName);
|
||||||
} else {
|
} else {
|
||||||
@ -1230,7 +1227,7 @@ ssb.addEventListener('connections', function () {
|
|||||||
async function loadSettings() {
|
async function loadSettings() {
|
||||||
let data = {};
|
let data = {};
|
||||||
try {
|
try {
|
||||||
let settings = new Database('core').get('settings');
|
let settings = await new Database('core').get('settings');
|
||||||
if (settings) {
|
if (settings) {
|
||||||
data = JSON.parse(settings);
|
data = JSON.parse(settings);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "mem.h"
|
#include "mem.h"
|
||||||
#include "ssb.h"
|
#include "ssb.h"
|
||||||
#include "task.h"
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
|
|
||||||
@ -91,57 +92,159 @@ static void _database_finalizer(JSRuntime* runtime, JSValue value)
|
|||||||
--_database_count;
|
--_database_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
typedef struct _database_get_t
|
||||||
{
|
{
|
||||||
JSValue entry = JS_UNDEFINED;
|
const char* id;
|
||||||
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
const char* key;
|
||||||
if (database)
|
size_t key_length;
|
||||||
|
char* out_value;
|
||||||
|
size_t out_length;
|
||||||
|
JSValue promise[2];
|
||||||
|
} database_get_t;
|
||||||
|
|
||||||
|
static void _database_get_work(tf_ssb_t* ssb, void* user_data)
|
||||||
{
|
{
|
||||||
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
|
database_get_t* work = user_data;
|
||||||
sqlite3_stmt* statement;
|
sqlite3_stmt* statement;
|
||||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||||
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
|
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
|
||||||
{
|
{
|
||||||
size_t length;
|
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
|
||||||
const char* keyString = JS_ToCStringLen(context, &length, argv[0]);
|
|
||||||
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, length, NULL) == SQLITE_OK &&
|
|
||||||
sqlite3_step(statement) == SQLITE_ROW)
|
sqlite3_step(statement) == SQLITE_ROW)
|
||||||
{
|
{
|
||||||
entry = JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0));
|
size_t length = sqlite3_column_bytes(statement, 0);
|
||||||
|
char* data = tf_malloc(length + 1);
|
||||||
|
memcpy(data, sqlite3_column_text(statement, 0), length);
|
||||||
|
data[length] = '\0';
|
||||||
|
work->out_value = data;
|
||||||
|
work->out_length = length;
|
||||||
}
|
}
|
||||||
JS_FreeCString(context, keyString);
|
|
||||||
sqlite3_finalize(statement);
|
sqlite3_finalize(statement);
|
||||||
}
|
}
|
||||||
tf_ssb_release_db_reader(ssb, db);
|
tf_ssb_release_db_reader(ssb, db);
|
||||||
}
|
}
|
||||||
return entry;
|
|
||||||
|
static void _database_get_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
database_get_t* work = user_data;
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
if (work->out_value)
|
||||||
|
{
|
||||||
|
result = JS_NewStringLen(context, work->out_value, work->out_length);
|
||||||
|
}
|
||||||
|
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||||
|
tf_util_report_error(context, error);
|
||||||
|
JS_FreeValue(context, error);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
JS_FreeValue(context, work->promise[0]);
|
||||||
|
JS_FreeValue(context, work->promise[1]);
|
||||||
|
tf_free(work->out_value);
|
||||||
|
tf_free(work);
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
{
|
{
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
||||||
if (database)
|
if (database)
|
||||||
{
|
{
|
||||||
sqlite3_stmt* statement;
|
|
||||||
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
|
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
|
||||||
|
|
||||||
|
size_t length;
|
||||||
|
const char* key = JS_ToCStringLen(context, &length, argv[0]);
|
||||||
|
database_get_t* work = tf_malloc(sizeof(database_get_t) + strlen(database->id) + 1 + length + 1);
|
||||||
|
*work = (database_get_t)
|
||||||
|
{
|
||||||
|
.id = (const char*)(work + 1),
|
||||||
|
.key = (const char*)(work + 1) + strlen(database->id) + 1,
|
||||||
|
.key_length = length,
|
||||||
|
};
|
||||||
|
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
|
||||||
|
memcpy((char*)work->key, key, length + 1);
|
||||||
|
JS_FreeCString(context, key);
|
||||||
|
|
||||||
|
tf_ssb_run_work(ssb, _database_get_work, _database_get_after_work, work);
|
||||||
|
result = JS_NewPromiseCapability(context, work->promise);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct _database_set_t
|
||||||
|
{
|
||||||
|
const char* id;
|
||||||
|
const char* key;
|
||||||
|
size_t key_length;
|
||||||
|
const char* value;
|
||||||
|
size_t value_length;
|
||||||
|
bool result;
|
||||||
|
JSValue promise[2];
|
||||||
|
} database_set_t;
|
||||||
|
|
||||||
|
static void _database_set_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
database_set_t* work = user_data;
|
||||||
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||||
|
sqlite3_stmt* statement;
|
||||||
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
|
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
|
||||||
{
|
{
|
||||||
size_t keyLength;
|
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
|
||||||
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
|
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK)
|
||||||
size_t valueLength;
|
|
||||||
const char* valueString = JS_ToCStringLen(context, &valueLength, argv[1]);
|
|
||||||
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK &&
|
|
||||||
sqlite3_bind_text(statement, 3, valueString, valueLength, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK)
|
|
||||||
{
|
{
|
||||||
|
work->result = true;
|
||||||
}
|
}
|
||||||
JS_FreeCString(context, keyString);
|
|
||||||
JS_FreeCString(context, valueString);
|
|
||||||
sqlite3_finalize(statement);
|
sqlite3_finalize(statement);
|
||||||
}
|
}
|
||||||
tf_ssb_release_db_writer(ssb, db);
|
tf_ssb_release_db_writer(ssb, db);
|
||||||
}
|
}
|
||||||
return JS_UNDEFINED;
|
|
||||||
|
static void _database_set_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
database_set_t* work = user_data;
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue result = work->result ? JS_TRUE : JS_UNDEFINED;
|
||||||
|
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||||
|
tf_util_report_error(context, error);
|
||||||
|
JS_FreeValue(context, error);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
JS_FreeValue(context, work->promise[0]);
|
||||||
|
JS_FreeValue(context, work->promise[1]);
|
||||||
|
tf_free(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
|
{
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
||||||
|
if (database)
|
||||||
|
{
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
|
||||||
|
|
||||||
|
size_t key_length = 0;
|
||||||
|
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
|
||||||
|
size_t value_length = 0;
|
||||||
|
const char* value = JS_ToCStringLen(context, &value_length, argv[1]);
|
||||||
|
|
||||||
|
database_set_t* work = tf_malloc(sizeof(database_set_t) + strlen(database->id) + 1 + key_length + 1 + value_length + 1);
|
||||||
|
*work = (database_set_t)
|
||||||
|
{
|
||||||
|
.id = (const char*)(work + 1),
|
||||||
|
.key = (const char*)(work + 1) + strlen(database->id) + 1,
|
||||||
|
.value = (const char*)(work + 1) + strlen(database->id) + 1 + key_length + 1,
|
||||||
|
.key_length = key_length,
|
||||||
|
.value_length = value_length,
|
||||||
|
};
|
||||||
|
|
||||||
|
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
|
||||||
|
memcpy((char*)work->key, key, key_length + 1);
|
||||||
|
memcpy((char*)work->value, value, value_length + 1);
|
||||||
|
|
||||||
|
result = JS_NewPromiseCapability(context, work->promise);
|
||||||
|
tf_ssb_run_work(ssb, _database_set_work, _database_set_after_work, work);
|
||||||
|
JS_FreeCString(context, key);
|
||||||
|
JS_FreeCString(context, value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
|
15
src/tests.c
15
src/tests.c
@ -266,16 +266,17 @@ static void _test_promise_remote_reject(const tf_test_options_t* options)
|
|||||||
static void _test_database(const tf_test_options_t* options)
|
static void _test_database(const tf_test_options_t* options)
|
||||||
{
|
{
|
||||||
_write_file("out/test.js",
|
_write_file("out/test.js",
|
||||||
|
"async function main() {\n"
|
||||||
" var db = new Database('testdb');\n"
|
" var db = new Database('testdb');\n"
|
||||||
"if (db.get('a')) {\n"
|
" if (await db.get('a')) {\n"
|
||||||
" exit(1);\n"
|
" exit(1);\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"db.set('a', 1);\n"
|
" await db.set('a', 1);\n"
|
||||||
"if (db.get('a') != 1) {\n"
|
" if (await db.get('a') != 1) {\n"
|
||||||
" exit(2);\n"
|
" exit(2);\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"db.set('b', 2);\n"
|
" await db.set('b', 2);\n"
|
||||||
"db.set('c', 3);\n"
|
" await db.set('c', 3);\n"
|
||||||
"\n"
|
"\n"
|
||||||
" var expected = ['a', 'b', 'c'];\n"
|
" var expected = ['a', 'b', 'c'];\n"
|
||||||
" var have = db.getAll();\n"
|
" var have = db.getAll();\n"
|
||||||
@ -291,7 +292,9 @@ static void _test_database(const tf_test_options_t* options)
|
|||||||
" if (expected.length) {\n"
|
" if (expected.length) {\n"
|
||||||
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
|
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
|
||||||
" exit(4);\n"
|
" exit(4);\n"
|
||||||
"}\n");
|
" }\n"
|
||||||
|
"}\n"
|
||||||
|
"main();");
|
||||||
|
|
||||||
char command[256];
|
char command[256];
|
||||||
unlink("out/test_db0.sqlite");
|
unlink("out/test_db0.sqlite");
|
||||||
|
Loading…
Reference in New Issue
Block a user