diff --git a/apps/admin.json b/apps/admin.json index b04c06cae..ac0280b49 100644 --- a/apps/admin.json +++ b/apps/admin.json @@ -1 +1,3 @@ -{"type":"tildefriends-app","files":{"app.js":"&uhGJsy5+qBgOgEgMqCTDasK+C+GWGptHKfPiAsD5eGA=.sha256","index.html":"&D3JwdPXy/QsLXkmwNDrBFXdzxfqO1/JGxfqEArnS5v4=.sha256","lit.min.js":"&3FfrVflmGr0n4lvN0GriN1Qz1lEw31SbZxRSJrcXR28=.sha256","script.js":"&TZ2ymD6cFVUjQleGcDslt8apjp7k3xLlfv2F8rQVM4I=.sha256"}} \ No newline at end of file +{ + "type": "tildefriends-app" +} \ No newline at end of file diff --git a/apps/api.json b/apps/api.json index febd470f9..ac0280b49 100644 --- a/apps/api.json +++ b/apps/api.json @@ -1 +1,3 @@ -{"type":"tildefriends-app","files":{"app.js":"&p35JmopfHf8hFh3Y9x6LrIxiUwaJZ5Nabzi2sVXpKoo=.sha256"}} \ No newline at end of file +{ + "type": "tildefriends-app" +} \ No newline at end of file diff --git a/apps/apps.json b/apps/apps.json index fcb285d6c..ac0280b49 100644 --- a/apps/apps.json +++ b/apps/apps.json @@ -1 +1,3 @@ -{"type":"tildefriends-app","files":{"app.js":"&qEJDfZ43KazIxiZl8OCKb2uaDOsPkxnIohEzQ1LLFpg=.sha256"}} \ No newline at end of file +{ + "type": "tildefriends-app" +} \ No newline at end of file diff --git a/apps/db.json b/apps/db.json index c475e39de..ac0280b49 100644 --- a/apps/db.json +++ b/apps/db.json @@ -1 +1,3 @@ -{"type":"tildefriends-app","files":{"app.js":"&V5o5IM9/OUyIsVkjkMW/X0i/tflQOSVJuJBmHdMT9aM=.sha256"}} \ No newline at end of file +{ + "type": "tildefriends-app" +} \ No newline at end of file diff --git a/apps/docs.json b/apps/docs.json index ee03c731e..ac0280b49 100644 --- a/apps/docs.json +++ b/apps/docs.json @@ -1 +1,3 @@ -{"type":"tildefriends-app","files":{"app.js":"&WEvJYebSMi5d2eXgUwJJmvR/Q4slFg3zHYB8Q2mXJII=.sha256","index.md":"&79+ntX4sRvg+MboV5nMFz01BSicxsWIQRx719VHS8uk=.sha256","todo.md":"&hQABwP24zFFhdHagRMF3Am7rV2yH19e+0xJ4wnZ4kfM=.sha256","structure.md":"&jph8x/fMXKOd4I0ZiUVb0ZLTfPQ7gBWoxJPrvtX6vtw=.sha256","guide.md":"&SgnGL0+rjetY2o9A2+lVRbNvHIkqKwMnZr9gXWneIlc=.sha256","ssb.md":"&JH1JfoTaCcUifCpnAwhImKBACI0PHoLhoOw1WAnWpLw=.sha256","vision.md":"&v2wu2MGlhNvaALQQ9rGna7ZeEQWSghFgQcDfD5xEyE0=.sha256"}} \ No newline at end of file +{ + "type": "tildefriends-app" +} \ No newline at end of file diff --git a/apps/follow.json b/apps/follow.json index eaef11d62..ac0280b49 100644 --- a/apps/follow.json +++ b/apps/follow.json @@ -1 +1,3 @@ -{"type":"tildefriends-app","files":{"app.js":"&3d9ABFgRwQvWsYbFv/rzimtnLDnVrWlGtdw7serFIGw=.sha256"}} \ No newline at end of file +{ + "type": "tildefriends-app" +} \ No newline at end of file diff --git a/apps/ssb.json b/apps/ssb.json index 4b1181df0..ac0280b49 100644 --- a/apps/ssb.json +++ b/apps/ssb.json @@ -1 +1,3 @@ -{"type":"tildefriends-app","files":{"app.js":"&1HWTkyCc1doft6dyKF5FDxtRAErNeY25CBrfZbKPpyo=.sha256","lit-all.min.js":"&FjJo4WtPgPmzEeF6Uhkhzv1FA7tyYfYSRuQz5zwc4fU=.sha256","index.html":"&TxhFekB9ov7tf/fmkAg7x5797i27oLidhgxEfDKC0T0=.sha256","script.js":"&G8puK9Q4MngHy3D4ppcKyT49WKbHD2OCeUcAw2ghTDE=.sha256","lit-all.min.js.map":"&H9HlMb6gIKYsWSNd+Kp0HwwpPwnlagEgi8B583aOEQQ=.sha256","tf-id-picker.js":"&maN8DUFrmRxW5nsVyOAMk5k1ekcz/pfzvSS99ac3jo8=.sha256","tf-app.js":"&F0fyawIO410YFidrzFjlHeY++sZy6ledf6CAXB+45U4=.sha256","tf-message.js":"&WYh0KTqqtlZF7ahldWokUt1yLdlThO5j1g2xlpfqI3U=.sha256","tf-user.js":"&bXTedgBudTQLXEBPY9R8OLfQ/ZLpo8YRU9Oq/wuGG3Y=.sha256","tf-utils.js":"&lYNeL7cVlDgcqrfkoRIe69DHZeqSZMiHhZIieblHbU0=.sha256","commonmark.min.js":"&bfBaMLU19d1p/vPBF9hlARqDX002KXG/UOfxOahZhe4=.sha256","tf-compose.js":"&AA04w0u0erTaBbJdMmnm1mukslXzCqozpv6UQPLZszE=.sha256","emojis.json":"&h3P4pez+AI4aYdsN0dJ3pbUEFR0276t9AM20caj/W/s=.sha256","emojis.js":"&tOkUocccQWBzkNzSEf9VMltkTSHcUALYSPYVWmJMoBc=.sha256","tf-styles.js":"&2Za+CmlZKGhUnWvGQdZBe8Plxv59WZ3nX5u5rbfwwkY=.sha256","tf-profile.js":"&vRKjsnYvOiHCQahzEfznCvP5YDwUPtltlpWf+pxwZ1Y=.sha256","commonmark-linkify.js":"&X+hNNkmSRvKY86khyAun+cXksquXbMakZdINbGbx30g=.sha256","tf-tab-search.js":"&ESt2vMG19sH5j6ungKua/ZuvIGslyuWyb3juXdOCecg=.sha256","tf-tab-news.js":"&fY+thANurOKU2/RhDt411ZtkxW0nV24+hLEf00Z1sTY=.sha256","tf-tab-connections.js":"&ywqBz3w63R6naH09kZ+01A0SfmtuSfk8QPBXWsli0yg=.sha256","tf-news.js":"&Zn+vxLUqVJbo/q6RcW8ezvbdilzllvXhZRyXk8kYwL0=.sha256","tribute.css":"&9FogMzZHKXCfGb7mlh7z+/wiNZzBsOB/tKoh6MfYJno=.sha256","tribute.esm.js":"&P1wKqCfYULpR/ahSB98JP8xaxfikuZwwtT6I/SAo7/Y=.sha256","commonmark-hashtag.js":"&fudY0YdvcMjVCSZ0oiCqUt0+bVT0a06j5TcjWaCDO8E=.sha256"}} \ No newline at end of file +{ + "type": "tildefriends-app" +} \ No newline at end of file diff --git a/apps/ssb/tf-connections.js b/apps/ssb/tf-connections.js deleted file mode 100644 index bea179635..000000000 --- a/apps/ssb/tf-connections.js +++ /dev/null @@ -1,57 +0,0 @@ -import {LitElement, html} from './lit-all.min.js'; -import * as tfrpc from '/static/tfrpc.js'; - -class TfConnectionsElement extends LitElement { - static get properties() { - return { - broadcasts: {type: Array}, - identities: {type: Array}, - connections: {type: Array}, - users: {type: Object}, - } - } - - constructor() { - super(); - let self = this; - this.broadcasts = []; - this.identities = []; - this.connections = []; - this.users = {}; - tfrpc.rpc.getAllIdentities().then(function(identities) { - self.identities = identities || []; - }); - } - - _emit_change() { - let changed_event = new Event('change', { - srcElement: this, - }); - this.dispatchEvent(changed_event); - } - - changed(event) { - this.selected = event.srcElement.value; - tfrpc.rpc.localStorageSet('whoami', this.selected); - this._emit_change(); - } - - render() { - return html` -

Broadcasts

- -

Connections

- -

Local Accounts

- - `; - } -} - -customElements.define('tf-connections', TfConnectionsElement); \ No newline at end of file diff --git a/apps/todo.json b/apps/todo.json index 839542318..ac0280b49 100644 --- a/apps/todo.json +++ b/apps/todo.json @@ -1 +1,3 @@ -{"type":"tildefriends-app","files":{"app.js":"&QUR1tKa15B5Or8AfPX/8Zs87teSeX0Mh/HF7PEPBom0=.sha256","index.html":"&QXhwvxhHc9fa8iL6088hGDu9FgWdY7wkXgvU2BMNv0A=.sha256","lit-core.min.js":"&tP9KhbgwF1chFqPtkNZ12Yx9AfkpnSjFiPcX5Pw5J9g=.sha256","script.js":"&KgOaUVjBM4MzSy7PpUVQHETuvgXAx2JGPJABksBg+QY=.sha256"}} \ No newline at end of file +{ + "type": "tildefriends-app" +} \ No newline at end of file diff --git a/src/main.c b/src/main.c index 0c151be62..3b2ad6ee3 100644 --- a/src/main.c +++ b/src/main.c @@ -257,12 +257,12 @@ static int _tf_command_export(const char* file, int argc, char* argv[]) xoptOption options[] = { { "db-path", 'd', offsetof(args_t, db_path), NULL, XOPT_TYPE_STRING, NULL, "Sqlite database path (default: db.sqlite)." }, - { "user", 'u', offsetof(args_t, user), NULL, XOPT_TYPE_STRING, NULL, "User into whose apps will be exported (default: \"cory\")." }, + { "user", 'u', offsetof(args_t, user), NULL, XOPT_TYPE_STRING, NULL, "User into whose apps will be exported (default: \"core\")." }, { "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." }, XOPT_NULLOPTION, }; - args_t args = { .user = "cory" }; + args_t args = { .user = "core" }; const char** extras = NULL; int extra_count = 0; const char *err = NULL; diff --git a/src/ssb.export.c b/src/ssb.export.c index 00379dbca..d9986c197 100644 --- a/src/ssb.export.c +++ b/src/ssb.export.c @@ -9,7 +9,7 @@ #include #include -static void _write_file(const char* path, void* blob, size_t size) +static void _write_file(const char* path, const void* blob, size_t size) { FILE* file = fopen(path, "wb"); if (file) @@ -35,6 +35,45 @@ static void _make_dir(const char* path) } } +typedef struct _tf_export_t +{ + tf_ssb_t* ssb; + const char* parent; + JSValue files; + uv_fs_t req; + bool done; +} tf_export_t; + +static void _tf_ssb_export_scandir(uv_fs_t* req) +{ + tf_export_t* export = req->data; + JSContext* context = tf_ssb_get_context(export->ssb); + uv_dirent_t ent; + while (uv_fs_scandir_next(req, &ent) == 0) + { + if (ent.type == UV_DIRENT_FILE) + { + JSValue found = JS_GetPropertyStr(context, export->files, ent.name); + if (JS_IsUndefined(found)) + { + size_t len = strlen(export->parent) + strlen(ent.name) + 2; + char* path = tf_malloc(len); + snprintf(path, len, "%s/%s", export->parent, ent.name); + uv_fs_t req = { 0 }; + int r = uv_fs_unlink(tf_ssb_get_loop(export->ssb), &req, path, NULL); + if (r) + { + printf("Failed to unlink %s: %s.", path, uv_strerror(r)); + } + uv_fs_req_cleanup(&req); + tf_free(path); + } + JS_FreeValue(context, found); + } + } + export->done = true; +} + void tf_ssb_export(tf_ssb_t* ssb, const char* key) { char user[256] = { 0 }; @@ -80,12 +119,9 @@ void tf_ssb_export(tf_ssb_t* ssb, const char* key) } char file_path[1024]; _make_dir("apps/"); - snprintf(file_path, sizeof(file_path), "apps/%s", user); + snprintf(file_path, sizeof(file_path), "apps/%s", path); _make_dir(file_path); - snprintf(file_path, sizeof(file_path), "apps/%s/%s", user, path); - _make_dir(file_path); - snprintf(file_path, sizeof(file_path), "apps/%s/%s.json", user, path); - _write_file(file_path, blob, size); + snprintf(file_path, sizeof(file_path), "apps/%s.json", path); JSContext* context = tf_ssb_get_context(ssb); JSValue app = JS_ParseJSON(context, (const char*)blob, size, NULL); tf_free(blob); @@ -108,7 +144,7 @@ void tf_ssb_export(tf_ssb_t* ssb, const char* key) size_t file_size = 0; if (tf_ssb_db_blob_get(ssb, blob_id, &file_blob, &file_size)) { - snprintf(file_path, sizeof(file_path), "apps/%s/%s/%s", user, path, file_name); + snprintf(file_path, sizeof(file_path), "apps/%s/%s", path, file_name); _write_file(file_path, file_blob, file_size); tf_free(file_blob); } @@ -129,6 +165,40 @@ void tf_ssb_export(tf_ssb_t* ssb, const char* key) } js_free(context, ptab); + JSAtom files_atom = JS_NewAtom(context, "files"); + JS_DeleteProperty(context, app, files_atom, 0); + JS_FreeAtom(context, files_atom); + + JSValue json = JS_JSONStringify(context, app, JS_NULL, JS_NewInt32(context, 2)); + size_t length = 0; + const char* string = JS_ToCStringLen(context, &length, json); + snprintf(file_path, sizeof(file_path), "apps/%s.json", path); + _write_file(file_path, string, length); + JS_FreeCString(context, string); + JS_FreeValue(context, json); + + snprintf(file_path, sizeof(file_path), "apps//%s", path); + tf_export_t export = + { + .parent = file_path, + .ssb = ssb, + .files = files, + .req = + { + .data = &export, + }, + }; + int r = uv_fs_scandir(tf_ssb_get_loop(ssb), &export.req, file_path, 0, _tf_ssb_export_scandir); + if (r) + { + printf("Failed to scan directory %s: %s.", file_path, uv_strerror(r)); + } + while (!export.done) + { + uv_run(tf_ssb_get_loop(ssb), UV_RUN_ONCE); + } + uv_fs_req_cleanup(&export.req); + JS_FreeValue(context, files); JS_FreeValue(context, app); } diff --git a/src/ssb.import.c b/src/ssb.import.c index 6cfb9e3e5..657f515c0 100644 --- a/src/ssb.import.c +++ b/src/ssb.import.c @@ -12,25 +12,6 @@ #include #include -typedef struct _tf_import_file_t { - uv_fs_t req; - uv_file file; - tf_ssb_t* ssb; - const char* user; - const char* parent; - const char* name; - char data[k_ssb_blob_bytes_max]; - int* work_left; -} tf_import_file_t; - -static void _tf_ssb_import_file_close(uv_fs_t* req) -{ - tf_import_file_t* file = req->data; - (*file->work_left)--; - uv_fs_req_cleanup(req); - tf_free(req->data); -} - static void _tf_ssb_import_add_app(tf_ssb_t* ssb, const char* user, const char* app) { sqlite3_stmt* statement; @@ -80,6 +61,7 @@ static void _tf_ssb_import_add_app(tf_ssb_t* ssb, const char* user, const char* 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); @@ -99,6 +81,7 @@ static void _tf_ssb_import_add_app(tf_ssb_t* ssb, const char* user, const char* JS_FreeValue(context, apps); } +/* static void _tf_ssb_import_file_read(uv_fs_t* req) { tf_import_file_t* file = req->data; @@ -106,32 +89,49 @@ static void _tf_ssb_import_file_read(uv_fs_t* req) if (req->result >= 0) { bool is_new = false; - if (tf_ssb_db_blob_store(file->ssb, (const uint8_t*)file->data, req->result, id, sizeof(id), &is_new)) + bool is_app_json = false; + + if (strcasecmp(file->name + strlen(file->name) - strlen(".json"), ".json") == 0) { - if (is_new) + 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)) { - printf("Stored %s/%s as %s.\n", file->parent, file->name, id); - } - if (strcasecmp(file->name + strlen(file->name) - strlen(".json"), ".json") == 0) - { - 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) + const char* type_string = JS_ToCString(context, type); + if (type_string && strcmp(type_string, "tf-application") == 0) { - ((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) + 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) { - if (sqlite3_changes(tf_ssb_get_db(file->ssb))) + ((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) { - printf("Registered %s path:%s as %s.\n", file->user, file->name, id); + 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); } - _tf_ssb_import_add_app(file->ssb, file->user, file->name); + sqlite3_finalize(statement); } - 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); @@ -146,74 +146,178 @@ static void _tf_ssb_import_file_open(uv_fs_t* 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); } -typedef struct _tf_import_t { +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; - int work_left; } tf_import_t; -static void _tf_ssb_import_scandir(uv_fs_t* req) +static char* _tf_ssb_import_read_file(uv_loop_t* loop, const char* path, size_t* out_size) { - tf_import_t* import = req->data; - uv_dirent_t ent; - while (uv_fs_scandir_next(req, &ent) == 0) + char* data = NULL; + uv_fs_t req = { 0 }; + int handle = uv_fs_open(loop, &req, path, 0, 0, NULL); + if (handle >= 0) { - size_t len = strlen(import->parent) + strlen(ent.name) + 2; - char* path = tf_malloc(len); - snprintf(path, len, "%s/%s", import->parent, ent.name); - if (ent.type == UV_DIRENT_DIR) + 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) { - tf_ssb_import(import->ssb, import->user, path); + data[r] = '\0'; + *out_size = r; } else { - size_t size = sizeof(tf_import_file_t) + strlen(import->parent) +1 + strlen(ent.name) + 1; - tf_import_file_t* file = tf_malloc(size); - memset(file, 0, size); - file->ssb = import->ssb; - file->user = import->user; - file->parent = (void*)(file + 1); - file->name = file->parent + strlen(import->parent) + 1; - file->req.data = file; - file->work_left = &import->work_left; - memcpy((char*)file->parent, import->parent, strlen(import->parent) + 1); - memcpy((char*)file->name, ent.name, strlen(ent.name) + 1); + printf("Failed to read %s: %s.\n", path, uv_strerror(r)); + } + uv_fs_req_cleanup(&read_req); - import->work_left++; - int r = uv_fs_open(tf_ssb_get_loop(import->ssb), &file->req, path, 0, 0, _tf_ssb_import_file_open); - if (r < 0) + 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) { - printf("Failed to open %s: %s.\n", path, uv_strerror(r)); - tf_free(file); - import->work_left--; + 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); } } - tf_free(path); } - import->work_left--; + 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) { - tf_import_t import = { - .ssb = ssb, - .user = user, - .parent = path, - .work_left = 1, - }; - import.req.data = &import; + uv_fs_t req = { 0 }; sqlite3_busy_timeout(tf_ssb_get_db(ssb), 10000); - int r = uv_fs_scandir(tf_ssb_get_loop(ssb), &import.req, path, 0, _tf_ssb_import_scandir); - if (r) + 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)); } - - while (import.work_left > 0) - { - uv_run(tf_ssb_get_loop(ssb), UV_RUN_ONCE); - } - uv_fs_req_cleanup(&import.req); + uv_fs_req_cleanup(&req); }