forked from cory/tildefriends
		
	
		
			
				
	
	
		
			392 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			392 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "ssb.import.h"
 | |
| 
 | |
| #include "log.h"
 | |
| #include "mem.h"
 | |
| #include "ssb.db.h"
 | |
| #include "ssb.h"
 | |
| #include "util.js.h"
 | |
| 
 | |
| #include "quickjs.h"
 | |
| #include "sqlite3.h"
 | |
| #include "unzip.h"
 | |
| #include "uv.h"
 | |
| 
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <strings.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;
 | |
| 	sqlite3* db = tf_ssb_acquire_db_writer(ssb);
 | |
| 	if (sqlite3_prepare_v2(db, "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_v2(db, "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);
 | |
| 	}
 | |
| 	tf_ssb_release_db_writer(ssb, db);
 | |
| 	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
 | |
| #if defined(__HAIKU__)
 | |
| 				|| ent.type == UV_DIRENT_UNKNOWN
 | |
| #endif
 | |
| 			)
 | |
| 			{
 | |
| 				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.\n", 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;
 | |
| 	sqlite3* db = tf_ssb_acquire_db_writer(ssb);
 | |
| 	if (sqlite3_prepare_v2(db,
 | |
| 			"INSERT INTO properties (id, key, value) VALUES (?1, 'path:' || ?2, ?3) ON CONFLICT DO UPDATE SET value = excluded.value WHERE value != excluded.value", -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(db) != 0;
 | |
| 		}
 | |
| 		sqlite3_finalize(statement);
 | |
| 	}
 | |
| 	tf_ssb_release_db_writer(ssb, db);
 | |
| 	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)
 | |
| {
 | |
| 	if (!path)
 | |
| 	{
 | |
| 		return;
 | |
| 	}
 | |
| 	else if (strlen(path) > strlen(".json") && strcasecmp(path + strlen(path) - strlen(".json"), ".json") == 0)
 | |
| 	{
 | |
| 		_tf_ssb_import_app_json(ssb, tf_ssb_get_loop(ssb), tf_ssb_get_context(ssb), user, path);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		uv_fs_t req = { 0 };
 | |
| 		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 (strlen(ent.name) > strlen(".json") && 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.\n", path, uv_strerror(r));
 | |
| 		}
 | |
| 		uv_fs_req_cleanup(&req);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static char* _tf_ssb_import_read_current_file_from_zip(unzFile zip, size_t* size)
 | |
| {
 | |
| 	char* result = NULL;
 | |
| 	unz_file_info64 info = { 0 };
 | |
| 	if (unzGetCurrentFileInfo64(zip, &info, NULL, 0, NULL, 0, NULL, 0) == UNZ_OK)
 | |
| 	{
 | |
| 		char* buffer = tf_malloc(info.uncompressed_size + 1);
 | |
| 		if (unzOpenCurrentFile(zip) == UNZ_OK)
 | |
| 		{
 | |
| 			if (unzReadCurrentFile(zip, buffer, info.uncompressed_size) == (int)info.uncompressed_size)
 | |
| 			{
 | |
| 				*size = info.uncompressed_size;
 | |
| 				buffer[info.uncompressed_size] = '\0';
 | |
| 				result = buffer;
 | |
| 				buffer = NULL;
 | |
| 			}
 | |
| 			unzCloseCurrentFile(zip);
 | |
| 		}
 | |
| 		tf_free(buffer);
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| static void _tf_ssb_import_recursive_add_files_from_zip(tf_ssb_t* ssb, unzFile zip, JSContext* context, JSValue files, const char* root)
 | |
| {
 | |
| 	if (root && unzGoToFirstFile(zip) == UNZ_OK)
 | |
| 	{
 | |
| 		do
 | |
| 		{
 | |
| 			char file_path[256];
 | |
| 			unz_file_info64 info = { 0 };
 | |
| 			if (unzGetCurrentFileInfo64(zip, &info, file_path, sizeof(file_path), NULL, 0, NULL, 0) == UNZ_OK)
 | |
| 			{
 | |
| 				if (strncmp(file_path, root, strlen(root)) == 0 && file_path[strlen(root)] == '/')
 | |
| 				{
 | |
| 					size_t size = 0;
 | |
| 					char* blob = _tf_ssb_import_read_current_file_from_zip(zip, &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", file_path, id);
 | |
| 					}
 | |
| 					JS_SetPropertyStr(context, files, file_path + strlen(root) + 1, JS_NewString(context, id));
 | |
| 
 | |
| 					tf_free(blob);
 | |
| 				}
 | |
| 			}
 | |
| 		} while (unzGoToNextFile(zip) == UNZ_OK);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void _tf_ssb_import_app_json_from_zip(tf_ssb_t* ssb, unzFile zip, JSContext* context, const char* user, const char* path)
 | |
| {
 | |
| 	size_t size = 0;
 | |
| 	char* file = _tf_ssb_import_read_current_file_from_zip(zip, &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_from_zip(ssb, zip, context, files, 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);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void tf_ssb_import_from_zip(tf_ssb_t* ssb, const char* zip_path, const char* user, const char* path)
 | |
| {
 | |
| 	unzFile zip = unzOpen(zip_path);
 | |
| 	if (zip && path)
 | |
| 	{
 | |
| 		tf_printf("Importing from %s.\n", zip_path);
 | |
| 		if (unzGoToFirstFile(zip) == UNZ_OK)
 | |
| 		{
 | |
| 			do
 | |
| 			{
 | |
| 				char file_path[256];
 | |
| 				unz_file_info64 info = { 0 };
 | |
| 				if (unzGetCurrentFileInfo64(zip, &info, file_path, sizeof(file_path), NULL, 0, NULL, 0) == UNZ_OK)
 | |
| 				{
 | |
| 					if (strncmp(file_path, path, strlen(path)) == 0 && !strchr(file_path + strlen(path) + 1, '/') && strlen(file_path) > strlen(".json") &&
 | |
| 						strcasecmp(file_path + strlen(file_path) - strlen(".json"), ".json") == 0)
 | |
| 					{
 | |
| 						unz_file_pos pos = { 0 };
 | |
| 						unzGetFilePos(zip, &pos);
 | |
| 						_tf_ssb_import_app_json_from_zip(ssb, zip, tf_ssb_get_context(ssb), user, file_path);
 | |
| 						unzGoToFilePos(zip, &pos);
 | |
| 					}
 | |
| 				}
 | |
| 			} while (unzGoToNextFile(zip) == UNZ_OK);
 | |
| 		}
 | |
| 		unzClose(zip);
 | |
| 	}
 | |
| }
 |