#include "ssb.import.h" #include "log.h" #include "mem.h" #include "ssb.db.h" #include "ssb.h" #include "util.js.h" #include #include #include #include #include #include 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 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 { tf_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) { tf_printf("Failed to close %s: %s.\n", path, uv_strerror(r)); } } else { tf_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) { tf_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 { tf_printf("Failed to scan directory %s: %s.", path, uv_strerror(r)); } uv_fs_req_cleanup(&req); } static bool _tf_ssb_register_app(tf_ssb_t* ssb, const char* user, const char* app, const char* id) { bool result = false; sqlite3_stmt* statement; if (sqlite3_prepare(tf_ssb_get_db(ssb), "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, 'path:' || $2, $3)", -1, &statement, NULL) == SQLITE_OK) { if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, app, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 3, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE) { result = sqlite3_changes(tf_ssb_get_db(ssb)) != 0; } sqlite3_finalize(statement); } return result; } 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; if (_tf_ssb_register_app(ssb, user, app, id)) { tf_printf("Registered %s path:%s as %s.\n", user, app, id); _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 { tf_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 { tf_printf("Failed to scan directory %s: %s.", path, uv_strerror(r)); } uv_fs_req_cleanup(&req); }