From a0a40e6cb2c10d2db95866245c7b621e4ec30457 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Tue, 27 May 2025 12:30:24 -0400 Subject: [PATCH] ssb: Implement blob file export from CLI. #121 --- src/main.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/main.c b/src/main.c index e0dc10e2..30f15f9c 100644 --- a/src/main.c +++ b/src/main.c @@ -148,6 +148,7 @@ static int _tf_command_private(const char* file, int argc, char* argv[]); static int _tf_command_run(const char* file, int argc, char* argv[]); static int _tf_command_sandbox(const char* file, int argc, char* argv[]); static int _tf_command_has_blob(const char* file, int argc, char* argv[]); +static int _tf_command_get_blob(const char* file, int argc, char* argv[]); static int _tf_command_store_blob(const char* file, int argc, char* argv[]); static int _tf_command_create_invite(const char* file, int argc, char* argv[]); static int _tf_command_get_sequence(const char* file, int argc, char* argv[]); @@ -178,6 +179,7 @@ const command_t k_commands[] = { { "get_profile", _tf_command_get_profile, "Get profile information for the given identity." }, { "get_contacts", _tf_command_get_contacts, "Get information about followed, blocked, and friend identities." }, { "has_blob", _tf_command_has_blob, "Check whether a blob is in the blob store." }, + { "get_blob", _tf_command_get_blob, "Read a file from the blob store." }, { "store_blob", _tf_command_store_blob, "Write a file to the blob store." }, { "verify", _tf_command_verify, "Verify a feed." }, { "test", _tf_command_test, "Test SSB." }, @@ -779,6 +781,113 @@ static int _tf_command_store_blob(const char* file, int argc, char* argv[]) return EXIT_SUCCESS; } +static int _tf_command_get_blob(const char* file, int argc, char* argv[]) +{ + const char* default_db_path = _get_db_path(); + const char* db_path = default_db_path; + const char* output = NULL; + const char* blob_id = NULL; + bool show_usage = false; + + while (!show_usage) + { + static const struct option k_options[] = { + { "db-path", required_argument, NULL, 'd' }, + { "blob", required_argument, NULL, 'b' }, + { "output", required_argument, NULL, 'o' }, + { "help", no_argument, NULL, 'h' }, + { 0 }, + }; + int c = getopt_long(argc, argv, "d:b:o:h", k_options, NULL); + if (c == -1) + { + break; + } + + switch (c) + { + case '?': + case 'h': + default: + show_usage = true; + break; + case 'd': + db_path = optarg; + break; + case 'b': + blob_id = optarg; + break; + case 'o': + output = optarg; + break; + } + } + + if (show_usage || !blob_id) + { + tf_printf("\n%s store_blob [options]\n\n", file); + tf_printf("options:\n"); + tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); + tf_printf(" -b, --blob blob_id Blob identifier to retrieve.\n"); + tf_printf(" -o, --output file_path Location to write the retrieved blob.\n"); + tf_printf(" -h, --help Show this usage information.\n"); + tf_free((void*)default_db_path); + return EXIT_FAILURE; + } + + uint8_t* blob = NULL; + size_t size = 0; + + _create_directories_for_file(db_path, 0700); + tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); + tf_ssb_set_quiet(ssb, true); + bool fetched = tf_ssb_db_blob_get(ssb, blob_id, &blob, &size); + tf_ssb_destroy(ssb); + + if (!fetched) + { + tf_printf("Failed to fetch blob: %s.\n", blob_id); + tf_free((void*)default_db_path); + return EXIT_FAILURE; + } + + if (output) + { + FILE* blob_file = fopen(output, "wb"); + if (!blob_file) + { + tf_printf("Failed to open %s: %s.\n", output, strerror(errno)); + tf_free((void*)default_db_path); + tf_free(blob); + return EXIT_FAILURE; + } + + if (fwrite(blob, 1, size, blob_file) != size) + { + tf_printf("Failed to write %s: %s\n", output, strerror(errno)); + tf_free(blob); + tf_free((void*)default_db_path); + fclose(blob_file); + return EXIT_FAILURE; + } + + fclose(blob_file); + } + else + { + if (fwrite(blob, 1, size, stdout) != size) + { + tf_free(blob); + tf_free((void*)default_db_path); + return EXIT_FAILURE; + } + } + + tf_free(blob); + tf_free((void*)default_db_path); + return EXIT_SUCCESS; +} + static int _tf_command_has_blob(const char* file, int argc, char* argv[]) { const char* default_db_path = _get_db_path();