forked from cory/tildefriends
213 lines
5.4 KiB
C
213 lines
5.4 KiB
C
#include "ssb.export.h"
|
|
|
|
#include "log.h"
|
|
#include "mem.h"
|
|
#include "ssb.db.h"
|
|
#include "ssb.h"
|
|
|
|
#include "sqlite3.h"
|
|
#include "uv.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static void _write_file(const char* path, const void* blob, size_t size, bool force_add_trailing_newline)
|
|
{
|
|
FILE* file = fopen(path, "wb");
|
|
if (file)
|
|
{
|
|
fwrite(blob, 1, size, file);
|
|
if (force_add_trailing_newline)
|
|
{
|
|
fputc('\n', file);
|
|
}
|
|
fclose(file);
|
|
}
|
|
else
|
|
{
|
|
tf_printf("Failed to open %s for write: %s.\n", path, strerror(errno));
|
|
}
|
|
}
|
|
|
|
static void _make_dir(const char* path)
|
|
{
|
|
#if defined(_WIN32)
|
|
if (mkdir(path) && errno != EEXIST)
|
|
#else
|
|
if (mkdir(path, 0755) && errno != EEXIST)
|
|
#endif
|
|
{
|
|
tf_printf("Failed to create directory %s: %s.\n", path, strerror(errno));
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
tf_printf("Failed to unlink %s: %s.\n", 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 };
|
|
char path[256] = { 0 };
|
|
if (sscanf(key, "/~%255[^/]/%255s", user, path) != 2)
|
|
{
|
|
tf_printf("Unable to export %s.\n", key);
|
|
return;
|
|
}
|
|
|
|
char app_blob_id[64] = { 0 };
|
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
|
sqlite3_busy_timeout(db, 10000);
|
|
sqlite3_stmt* statement;
|
|
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ?1 AND key = 'path:' || ?2", -1, &statement, NULL) == SQLITE_OK)
|
|
{
|
|
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, path, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
|
{
|
|
int len = sqlite3_column_bytes(statement, 0);
|
|
if (len >= (int)sizeof(app_blob_id))
|
|
{
|
|
len = sizeof(app_blob_id) - 1;
|
|
}
|
|
memcpy(app_blob_id, sqlite3_column_text(statement, 0), len);
|
|
app_blob_id[len] = '\0';
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
tf_ssb_release_db_reader(ssb, db);
|
|
|
|
if (!*app_blob_id)
|
|
{
|
|
tf_printf("Did not find app blob ID for %s.\n", key);
|
|
return;
|
|
}
|
|
|
|
uint8_t* blob = NULL;
|
|
size_t size = 0;
|
|
if (!tf_ssb_db_blob_get(ssb, app_blob_id, &blob, &size))
|
|
{
|
|
tf_printf("Did not find blob for %s: %s.\n", key, app_blob_id);
|
|
return;
|
|
}
|
|
char file_path[1024];
|
|
_make_dir("apps/");
|
|
snprintf(file_path, sizeof(file_path), "apps/%s", path);
|
|
_make_dir(file_path);
|
|
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);
|
|
|
|
JSValue files = JS_GetPropertyStr(context, app, "files");
|
|
JSPropertyEnum* ptab = NULL;
|
|
uint32_t plen = 0;
|
|
if (JS_GetOwnPropertyNames(context, &ptab, &plen, files, JS_GPN_STRING_MASK) == 0)
|
|
{
|
|
for (uint32_t i = 0; i < plen; ++i)
|
|
{
|
|
JSPropertyDescriptor desc;
|
|
if (JS_GetOwnProperty(context, &desc, files, ptab[i].atom) == 1)
|
|
{
|
|
JSValue key = JS_AtomToString(context, ptab[i].atom);
|
|
const char* file_name = JS_ToCString(context, key);
|
|
const char* blob_id = JS_ToCString(context, desc.value);
|
|
|
|
uint8_t* file_blob = NULL;
|
|
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", path, file_name);
|
|
_write_file(file_path, file_blob, file_size, false);
|
|
tf_free(file_blob);
|
|
}
|
|
|
|
JS_FreeCString(context, file_name);
|
|
JS_FreeValue(context, key);
|
|
JS_FreeCString(context, blob_id);
|
|
JS_FreeValue(context, desc.value);
|
|
JS_FreeValue(context, desc.setter);
|
|
JS_FreeValue(context, desc.getter);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < plen; ++i)
|
|
{
|
|
JS_FreeAtom(context, ptab[i].atom);
|
|
}
|
|
js_free(context, ptab);
|
|
|
|
JSAtom files_atom = JS_NewAtom(context, "files");
|
|
JS_DeleteProperty(context, app, files_atom, 0);
|
|
JS_FreeAtom(context, files_atom);
|
|
|
|
JSValue tab = JS_NewString(context, "\t");
|
|
JSValue json = JS_JSONStringify(context, app, JS_NULL, tab);
|
|
JS_FreeValue(context, tab);
|
|
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, true);
|
|
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)
|
|
{
|
|
tf_printf("Failed to scan directory %s: %s.\n", 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);
|
|
}
|