tildefriends/src/ssb.import.c

324 lines
9.5 KiB
C

#include "ssb.import.h"
#include "mem.h"
#include "ssb.db.h"
#include "ssb.h"
#include "util.js.h"
#include <quickjs.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sqlite3.h>
#include <uv.h>
static void _tf_ssb_import_add_app(tf_ssb_t* ssb, const char* user, const char* app)
{
sqlite3_stmt* statement;
JSContext* context = tf_ssb_get_context(ssb);
JSValue apps = JS_UNDEFINED;
if (sqlite3_prepare(tf_ssb_get_db(ssb), "SELECT value FROM properties WHERE id = $1 AND key = 'apps'", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
{
const char* json = (const char*)sqlite3_column_text(statement, 0);
apps = JS_ParseJSON(context, json, strlen(json), NULL);
}
sqlite3_finalize(statement);
}
if (!JS_IsArray(context, apps))
{
JS_FreeValue(context, apps);
apps = JS_NewArray(context);
}
int32_t length = tf_util_get_length(context, apps);
JS_SetPropertyUint32(context, apps, length, JS_NewString(context, app));
JSValue sort = JS_GetPropertyStr(context, apps, "sort");
JS_FreeValue(context, JS_Call(context, sort, apps, 0, NULL));
JS_FreeValue(context, sort);
length++;
JSValue out_apps = JS_NewArray(context);
const char* last_added = NULL;
int write_index = 0;
for (int read_index = 0; read_index < length; read_index++)
{
JSValue read_value = JS_GetPropertyUint32(context, apps, read_index);
const char* read_string = JS_ToCString(context, read_value);
if (read_string && (!last_added || strcmp(read_string, last_added)))
{
JS_SetPropertyUint32(context, out_apps, write_index++, read_value);
}
else
{
JS_FreeValue(context, read_value);
}
JS_FreeCString(context, last_added);
last_added = read_string;
}
JS_FreeCString(context, last_added);
JSValue json = JS_JSONStringify(context, out_apps, JS_NULL, JS_NULL);
const char* text = JS_ToCString(context, json);
if (sqlite3_prepare(tf_ssb_get_db(ssb), "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, 'apps', $2)", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 2, text, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_OK)
{
}
sqlite3_finalize(statement);
}
JS_FreeCString(context, text);
JS_FreeValue(context, json);
JS_FreeValue(context, out_apps);
JS_FreeValue(context, apps);
}
/*
static void _tf_ssb_import_file_read(uv_fs_t* req)
{
tf_import_file_t* file = req->data;
char id[k_id_base64_len];
if (req->result >= 0)
{
bool is_new = false;
bool is_app_json = false;
if (strcasecmp(file->name + strlen(file->name) - strlen(".json"), ".json") == 0)
{
JSContext* context = tf_ssb_get_context(file->ssb);
JSValue object = JS_ParseJSON(context, file->data, req->result, NULL);
JSValue type = JS_GetPropertyStr(context, object, "type");
if (!JS_IsUndefined(type))
{
const char* type_string = JS_ToCString(context, type);
if (type_string && strcmp(type_string, "tf-application") == 0)
{
is_app_json = true;
sqlite3_stmt* statement;
if (sqlite3_prepare(tf_ssb_get_db(file->ssb), "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, 'path:' || $2, $3)", -1, &statement, NULL) == SQLITE_OK)
{
((char*)file->name)[strlen(file->name) - strlen(".json")] = '\0';
if (sqlite3_bind_text(statement, 1, file->user, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 2, file->name, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE)
{
if (sqlite3_changes(tf_ssb_get_db(file->ssb)))
{
printf("Registered %s path:%s as %s.\n", file->user, file->name, id);
}
_tf_ssb_import_add_app(file->ssb, file->user, file->name);
}
sqlite3_finalize(statement);
}
}
JS_FreeCString(context, type_string);
}
JS_FreeValue(context, object);
}
if (!is_app_json &&
tf_ssb_db_blob_store(file->ssb, (const uint8_t*)file->data, req->result, id, sizeof(id), &is_new) &&
is_new)
{
printf("Stored %s/%s as %s.\n", file->parent, file->name, id);
}
}
uv_fs_req_cleanup(req);
uv_fs_close(tf_ssb_get_loop(file->ssb), req, file->file, _tf_ssb_import_file_close);
}
static void _tf_ssb_import_file_open(uv_fs_t* req)
{
tf_import_file_t* file = req->data;
file->file = req->result;
uv_fs_req_cleanup(req);
uv_fs_read(tf_ssb_get_loop(file->ssb), req, file->file, &(uv_buf_t) { .base = file->data, .len = k_ssb_blob_bytes_max }, 1, 0, _tf_ssb_import_file_read);
}
static void _tf_ssb_import_register_app(tf_ssb_t* ssb, const char* use, const char* app, const char* id)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(tf_ssb_get_db(file->ssb), "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, 'path:' || $2, $3)", -1, &statement, NULL) == SQLITE_OK)
{
((char*)file->name)[strlen(file->name) - strlen(".json")] = '\0';
if (sqlite3_bind_text(statement, 1, file->user, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 2, file->name, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, id, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE)
{
if (sqlite3_changes(tf_ssb_get_db(file->ssb)))
{
printf("Registered %s path:%s as %s.\n", file->user, file->name, id);
}
_tf_ssb_import_add_app(file->ssb, file->user, file->name);
}
sqlite3_finalize(statement);
}
}
*/
typedef struct _tf_import_t
{
tf_ssb_t* ssb;
const char* user;
const char* parent;
uv_fs_t req;
} tf_import_t;
static char* _tf_ssb_import_read_file(uv_loop_t* loop, const char* path, size_t* out_size)
{
char* data = NULL;
uv_fs_t req = { 0 };
int handle = uv_fs_open(loop, &req, path, 0, 0, NULL);
if (handle >= 0)
{
uv_fs_t read_req = { 0 };
data = tf_malloc(k_ssb_blob_bytes_max);
int r = uv_fs_read(loop, &read_req, handle, &(uv_buf_t) { .base = data, .len = k_ssb_blob_bytes_max }, 1, 0, NULL);
if (r >= 0 && r < k_ssb_blob_bytes_max)
{
data[r] = '\0';
*out_size = r;
}
else
{
printf("Failed to read %s: %s.\n", path, uv_strerror(r));
}
uv_fs_req_cleanup(&read_req);
uv_fs_t close_req = { 0 };
r = uv_fs_close(loop, &close_req, handle, NULL);
if (r)
{
printf("Failed to close %s: %s.\n", path, uv_strerror(r));
}
}
else
{
printf("Failed to open %s: %s.\n", path, uv_strerror(handle));
}
uv_fs_req_cleanup(&req);
return data;
}
static void _tf_ssb_import_recursive_add_files(tf_ssb_t* ssb, uv_loop_t* loop, JSContext* context, JSValue files, const char* root, const char* path)
{
uv_fs_t req = { 0 };
int r = uv_fs_scandir(loop, &req, path, 0, NULL);
if (r >= 0)
{
uv_dirent_t ent;
while (uv_fs_scandir_next(&req, &ent) == 0)
{
if (ent.type == UV_DIRENT_FILE)
{
size_t len = strlen(path) + strlen(ent.name) + 2;
char* full_path = tf_malloc(len);
snprintf(full_path, len, "%s/%s", path, ent.name);
size_t size = 0;
char* blob = _tf_ssb_import_read_file(loop, full_path, &size);
char id[k_id_base64_len] = { 0 };
bool is_new = false;
if (tf_ssb_db_blob_store(ssb, (const uint8_t*)blob, size, id, sizeof(id), &is_new) && is_new)
{
printf("Stored %s as %s.\n", full_path, id);
}
JS_SetPropertyStr(context, files, full_path + strlen(root) + 1, JS_NewString(context, id));
tf_free(blob);
tf_free(full_path);
}
}
}
else
{
printf("Failed to scan directory %s: %s.", path, uv_strerror(r));
}
uv_fs_req_cleanup(&req);
}
static void _tf_ssb_import_app_json(tf_ssb_t* ssb, uv_loop_t* loop, JSContext* context, const char* user, const char* path)
{
uv_fs_t req = { 0 };
int r = uv_fs_open(loop, &req, path, 0, 0, NULL);
if (r >= 0)
{
size_t size = 0;
char* file = _tf_ssb_import_read_file(loop, path, &size);
if (file)
{
JSValue app = JS_ParseJSON(context, file, size, NULL);
if (!tf_util_report_error(context, app))
{
char* dir = tf_strdup(path);
dir[strlen(dir) - strlen(".json")] = '\0';
JSValue files = JS_NewObject(context);
_tf_ssb_import_recursive_add_files(ssb, loop, context, files, dir, dir);
JS_SetPropertyStr(context, app, "files", files);
JSValue json = JS_JSONStringify(context, app, JS_NULL, JS_NULL);
size_t size = 0;
const char* blob = JS_ToCStringLen(context, &size, json);
char id[k_id_base64_len] = { 0 };
if (tf_ssb_db_blob_store(ssb, (const uint8_t*)blob, size, id, sizeof(id), NULL))
{
const char* app = dir;
char* slash = strrchr(dir, '/');
app = slash ? slash + 1 : app;
_tf_ssb_import_add_app(ssb, user, app);
}
JS_FreeCString(context, blob);
JS_FreeValue(context, json);
tf_free(dir);
}
JS_FreeValue(context, app);
tf_free(file);
}
}
else
{
printf("Failed to open %s: %s.\n", path, uv_strerror(r));
}
uv_fs_req_cleanup(&req);
}
void tf_ssb_import(tf_ssb_t* ssb, const char* user, const char* path)
{
uv_fs_t req = { 0 };
sqlite3_busy_timeout(tf_ssb_get_db(ssb), 10000);
int r = uv_fs_scandir(tf_ssb_get_loop(ssb), &req, path, 0, NULL);
if (r >= 0)
{
uv_dirent_t ent;
while (uv_fs_scandir_next(&req, &ent) == 0)
{
size_t len = strlen(path) + strlen(ent.name) + 2;
char* full_path = tf_malloc(len);
snprintf(full_path, len, "%s/%s", path, ent.name);
if (strcasecmp(ent.name + strlen(ent.name) - strlen(".json"), ".json") == 0)
{
_tf_ssb_import_app_json(ssb, tf_ssb_get_loop(ssb), tf_ssb_get_context(ssb), user, full_path);
}
tf_free(full_path);
}
}
else
{
printf("Failed to scan directory %s: %s.", path, uv_strerror(r));
}
uv_fs_req_cleanup(&req);
}