2021-08-22 19:34:28 +00:00
# include "ssb.import.h"
2023-03-07 17:50:17 +00:00
# include "log.h"
2022-06-04 17:04:51 +00:00
# include "mem.h"
2021-08-22 19:41:27 +00:00
# include "ssb.db.h"
2021-08-22 19:34:28 +00:00
# include "ssb.h"
2022-07-09 15:13:35 +00:00
# include "util.js.h"
2021-08-22 19:34:28 +00:00
2023-05-21 21:36:51 +00:00
# include "quickjs.h"
# include "sqlite3.h"
# include "unzip.h"
# include "uv.h"
2021-08-22 19:34:28 +00:00
# include <stdlib.h>
# include <string.h>
# include <strings.h>
2022-01-26 02:49:45 +00:00
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 ;
2023-06-15 00:27:49 +00:00
sqlite3 * db = tf_ssb_acquire_db_writer ( ssb ) ;
2023-10-24 01:27:35 +00:00
if ( sqlite3_prepare ( db , " SELECT value FROM properties WHERE id = ?1 AND key = 'apps' " , - 1 , & statement , NULL ) = = SQLITE_OK )
2022-01-26 02:49:45 +00:00
{
2024-02-15 23:35:01 +00:00
if ( sqlite3_bind_text ( statement , 1 , user , - 1 , NULL ) = = SQLITE_OK & & sqlite3_step ( statement ) = = SQLITE_ROW )
2022-01-26 02:49:45 +00:00
{
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 ) ;
}
2023-01-29 01:58:57 +00:00
2022-11-27 02:19:40 +00:00
int32_t length = tf_util_get_length ( context , apps ) ;
2022-01-26 02:49:45 +00:00
JS_SetPropertyUint32 ( context , apps , length , JS_NewString ( context , app ) ) ;
2023-01-29 01:58:57 +00:00
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 ;
}
2023-02-02 02:09:05 +00:00
JS_FreeCString ( context , last_added ) ;
2023-01-29 01:58:57 +00:00
JSValue json = JS_JSONStringify ( context , out_apps , JS_NULL , JS_NULL ) ;
2022-01-26 02:49:45 +00:00
const char * text = JS_ToCString ( context , json ) ;
2023-10-24 01:27:35 +00:00
if ( sqlite3_prepare ( db , " INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, 'apps', ?2) " , - 1 , & statement , NULL ) = = SQLITE_OK )
2022-01-26 02:49:45 +00:00
{
2024-02-17 19:22:02 +00:00
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 )
2022-01-26 02:49:45 +00:00
{
}
sqlite3_finalize ( statement ) ;
}
2023-06-15 00:27:49 +00:00
tf_ssb_release_db_writer ( ssb , db ) ;
2022-01-26 02:49:45 +00:00
JS_FreeCString ( context , text ) ;
JS_FreeValue ( context , json ) ;
2023-01-29 01:58:57 +00:00
JS_FreeValue ( context , out_apps ) ;
2022-01-26 02:49:45 +00:00
JS_FreeValue ( context , apps ) ;
}
2023-02-02 02:09:05 +00:00
static char * _tf_ssb_import_read_file ( uv_loop_t * loop , const char * path , size_t * out_size )
2021-08-22 19:34:28 +00:00
{
2023-02-02 02:09:05 +00:00
char * data = NULL ;
uv_fs_t req = { 0 } ;
int handle = uv_fs_open ( loop , & req , path , 0 , 0 , NULL ) ;
if ( handle > = 0 )
2021-10-10 21:51:38 +00:00
{
2023-02-02 02:09:05 +00:00
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 )
2021-10-10 21:51:38 +00:00
{
2023-02-02 02:09:05 +00:00
data [ r ] = ' \0 ' ;
* out_size = r ;
2021-10-10 21:51:38 +00:00
}
else
{
2023-03-07 17:50:17 +00:00
tf_printf ( " Failed to read %s: %s. \n " , path , uv_strerror ( r ) ) ;
2023-02-02 02:09:05 +00:00
}
uv_fs_req_cleanup ( & read_req ) ;
uv_fs_t close_req = { 0 } ;
r = uv_fs_close ( loop , & close_req , handle , NULL ) ;
if ( r )
{
2023-03-07 17:50:17 +00:00
tf_printf ( " Failed to close %s: %s. \n " , path , uv_strerror ( r ) ) ;
2023-02-02 02:09:05 +00:00
}
}
else
{
2023-03-07 17:50:17 +00:00
tf_printf ( " Failed to open %s: %s. \n " , path , uv_strerror ( handle ) ) ;
2023-02-02 02:09:05 +00:00
}
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 )
{
2024-03-05 20:49:16 -05:00
if ( ent . type = = UV_DIRENT_FILE
# if defined(__HAIKU__)
2024-03-05 20:49:30 -05:00
| | ent . type = = UV_DIRENT_UNKNOWN
2024-03-05 20:49:16 -05:00
# endif
)
2021-10-10 21:51:38 +00:00
{
2023-02-02 02:09:05 +00:00
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 )
{
2023-03-07 17:50:17 +00:00
tf_printf ( " Stored %s as %s. \n " , full_path , id ) ;
2023-02-02 02:09:05 +00:00
}
JS_SetPropertyStr ( context , files , full_path + strlen ( root ) + 1 , JS_NewString ( context , id ) ) ;
tf_free ( blob ) ;
tf_free ( full_path ) ;
2021-08-22 19:34:28 +00:00
}
}
}
2023-02-02 02:09:05 +00:00
else
{
2024-09-30 12:13:57 -04:00
tf_printf ( " Failed to scan directory %s: %s. \n " , path , uv_strerror ( r ) ) ;
2023-02-02 02:09:05 +00:00
}
uv_fs_req_cleanup ( & req ) ;
}
2023-02-03 14:09:53 +00:00
static bool _tf_ssb_register_app ( tf_ssb_t * ssb , const char * user , const char * app , const char * id )
{
bool result = false ;
2023-06-15 00:27:49 +00:00
sqlite3_stmt * statement ;
sqlite3 * db = tf_ssb_acquire_db_writer ( ssb ) ;
2025-04-19 21:08:23 -04:00
if ( sqlite3_prepare ( 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 )
2023-06-15 00:27:49 +00:00
{
2024-02-15 23:35:01 +00:00
if ( sqlite3_bind_text ( statement , 1 , user , - 1 , NULL ) = = SQLITE_OK & & sqlite3_bind_text ( statement , 2 , app , - 1 , NULL ) = = SQLITE_OK & &
2024-02-17 19:22:02 +00:00
sqlite3_bind_text ( statement , 3 , id , - 1 , NULL ) = = SQLITE_OK & & sqlite3_step ( statement ) = = SQLITE_DONE )
2023-06-15 00:27:49 +00:00
{
2024-02-15 23:35:01 +00:00
result = sqlite3_changes ( db ) ! = 0 ;
2023-06-15 00:27:49 +00:00
}
sqlite3_finalize ( statement ) ;
}
tf_ssb_release_db_writer ( ssb , db ) ;
return result ;
2023-02-03 14:09:53 +00:00
}
2023-02-02 02:09:05 +00:00
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 ;
2023-02-03 14:09:53 +00:00
if ( _tf_ssb_register_app ( ssb , user , app , id ) )
{
2023-06-15 00:27:49 +00:00
tf_printf ( " Registered %s path:%s as %s. \n " , user , app , id ) ;
2023-02-03 14:09:53 +00:00
_tf_ssb_import_add_app ( ssb , user , app ) ;
}
2023-02-02 02:09:05 +00:00
}
JS_FreeCString ( context , blob ) ;
JS_FreeValue ( context , json ) ;
tf_free ( dir ) ;
}
JS_FreeValue ( context , app ) ;
tf_free ( file ) ;
}
}
else
{
2023-03-07 17:50:17 +00:00
tf_printf ( " Failed to open %s: %s. \n " , path , uv_strerror ( r ) ) ;
2023-02-02 02:09:05 +00:00
}
uv_fs_req_cleanup ( & req ) ;
2021-08-22 19:34:28 +00:00
}
void tf_ssb_import ( tf_ssb_t * ssb , const char * user , const char * path )
{
2024-08-14 19:40:20 -04:00
if ( ! path )
{
return ;
}
else if ( strlen ( path ) > strlen ( " .json " ) & & strcasecmp ( path + strlen ( path ) - strlen ( " .json " ) , " .json " ) = = 0 )
2021-10-10 21:51:38 +00:00
{
2024-04-30 21:43:14 -04:00
_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 )
2023-02-02 02:09:05 +00:00
{
2024-04-30 21:43:14 -04:00
uv_dirent_t ent ;
while ( uv_fs_scandir_next ( & req , & ent ) = = 0 )
2023-02-02 02:09:05 +00:00
{
2024-04-30 21:43:14 -04:00
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 ) ;
2023-02-02 02:09:05 +00:00
}
}
2024-04-30 21:43:14 -04:00
else
{
2024-09-30 12:13:57 -04:00
tf_printf ( " Failed to scan directory %s: %s. \n " , path , uv_strerror ( r ) ) ;
2024-04-30 21:43:14 -04:00
}
uv_fs_req_cleanup ( & req ) ;
2021-08-22 19:34:28 +00:00
}
}
2023-03-09 01:39:48 +00:00
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 )
{
2023-10-26 02:56:33 +00:00
if ( root & & unzGoToFirstFile ( zip ) = = UNZ_OK )
2023-03-09 01:39:48 +00:00
{
2023-10-22 19:25:52 +00:00
do
2023-03-09 01:39:48 +00:00
{
char file_path [ 256 ] ;
unz_file_info64 info = { 0 } ;
if ( unzGetCurrentFileInfo64 ( zip , & info , file_path , sizeof ( file_path ) , NULL , 0 , NULL , 0 ) = = UNZ_OK )
{
2024-02-15 23:35:01 +00:00
if ( strncmp ( file_path , root , strlen ( root ) ) = = 0 & & file_path [ strlen ( root ) ] = = ' / ' )
2023-03-09 01:39:48 +00:00
{
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 ) ;
}
}
2024-02-15 23:35:01 +00:00
} while ( unzGoToNextFile ( zip ) = = UNZ_OK ) ;
2023-03-09 01:39:48 +00:00
}
}
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 ) )
{
2023-06-15 00:27:49 +00:00
tf_printf ( " Registered %s path:%s as %s. \n " , user , app , id ) ;
2023-03-09 01:39:48 +00:00
_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 ) ;
2023-10-26 02:56:33 +00:00
if ( zip & & path )
2023-03-09 01:39:48 +00:00
{
2023-03-11 13:57:17 +00:00
tf_printf ( " Importing from %s. \n " , zip_path ) ;
2023-03-09 01:39:48 +00:00
if ( unzGoToFirstFile ( zip ) = = UNZ_OK )
{
2023-10-22 19:25:52 +00:00
do
2023-03-09 01:39:48 +00:00
{
char file_path [ 256 ] ;
unz_file_info64 info = { 0 } ;
if ( unzGetCurrentFileInfo64 ( zip , & info , file_path , sizeof ( file_path ) , NULL , 0 , NULL , 0 ) = = UNZ_OK )
{
2024-02-15 23:35:01 +00:00
if ( strncmp ( file_path , path , strlen ( path ) ) = = 0 & & ! strchr ( file_path + strlen ( path ) + 1 , ' / ' ) & & strlen ( file_path ) > strlen ( " .json " ) & &
2024-02-17 19:22:02 +00:00
strcasecmp ( file_path + strlen ( file_path ) - strlen ( " .json " ) , " .json " ) = = 0 )
2023-03-09 01:39:48 +00:00
{
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 ) ;
}
}
2024-02-15 23:35:01 +00:00
} while ( unzGoToNextFile ( zip ) = = UNZ_OK ) ;
2023-03-09 01:39:48 +00:00
}
unzClose ( zip ) ;
}
}