diff --git a/src/ssb.c b/src/ssb.c index 50d6635e..6c084d14 100644 --- a/src/ssb.c +++ b/src/ssb.c @@ -133,6 +133,15 @@ typedef struct _tf_ssb_message_added_callback_node_t tf_ssb_message_added_callback_node_t* next; } tf_ssb_message_added_callback_node_t; +typedef struct _tf_ssb_blob_stored_callback_node_t tf_ssb_blob_stored_callback_node_t; +typedef struct _tf_ssb_blob_stored_callback_node_t +{ + tf_ssb_blob_stored_callback_t* callback; + tf_ssb_callback_cleanup_t* cleanup; + void* user_data; + tf_ssb_blob_stored_callback_node_t* next; +} tf_ssb_blob_stored_callback_node_t; + typedef struct _tf_ssb_blob_want_added_callback_node_t tf_ssb_blob_want_added_callback_node_t; typedef struct _tf_ssb_blob_want_added_callback_node_t { @@ -235,6 +244,9 @@ typedef struct _tf_ssb_t tf_ssb_message_added_callback_node_t* message_added; int message_added_count; + tf_ssb_blob_stored_callback_node_t* blob_stored; + int blob_stored_count; + tf_ssb_blob_want_added_callback_node_t* blob_want_added; int blob_want_added_count; @@ -2741,6 +2753,17 @@ void tf_ssb_destroy(tf_ssb_t* ssb) } tf_free(node); } + while (ssb->blob_stored) + { + tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored; + ssb->blob_stored = node->next; + ssb->blob_stored_count--; + if (node->cleanup) + { + node->cleanup(ssb, node->user_data); + } + tf_free(node); + } while (ssb->blob_want_added) { tf_ssb_blob_want_added_callback_node_t* node = ssb->blob_want_added; @@ -3960,9 +3983,53 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca } } +void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void (*cleanup)(tf_ssb_t* ssb, void* user_data), void* user_data) +{ + tf_ssb_blob_stored_callback_node_t* node = tf_malloc(sizeof(tf_ssb_blob_stored_callback_node_t)); + *node = (tf_ssb_blob_stored_callback_node_t) { + .callback = callback, + .cleanup = cleanup, + .user_data = user_data, + .next = ssb->blob_stored, + }; + ssb->blob_stored = node; + ssb->blob_stored_count++; +} + +void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data) +{ + tf_ssb_blob_stored_callback_node_t** it = &ssb->blob_stored; + while (*it) + { + if ((*it)->callback == callback && (*it)->user_data == user_data) + { + tf_ssb_blob_stored_callback_node_t* node = *it; + *it = node->next; + ssb->blob_stored_count--; + if (node->cleanup) + { + node->cleanup(ssb, node->user_data); + } + tf_free(node); + } + else + { + it = &(*it)->next; + } + } +} + void tf_ssb_notify_blob_stored(tf_ssb_t* ssb, const char* id) { + tf_ssb_blob_stored_callback_node_t* next = NULL; ssb->blobs_stored++; + for (tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored; node; node = next) + { + next = node->next; + tf_trace_begin(ssb->trace, "blob stored callback"); + node->callback(ssb, id, node->user_data); + tf_trace_end(ssb->trace); + } } void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_keys) diff --git a/src/ssb.h b/src/ssb.h index 36138b40..51e36bfc 100644 --- a/src/ssb.h +++ b/src/ssb.h @@ -680,6 +680,31 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca */ void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_with_keys); +/** +** A callback called when a blob is added to the database. +** @param ssb The SSB instance. +** @param id The blob identifier. +** @param user_data The user data. +*/ +typedef void(tf_ssb_blob_stored_callback_t)(tf_ssb_t* ssb, const char* id, void* user_data); + +/** +** Register a callback called when a blob is added to the database. +** @param ssb The SSB instance. +** @param callback The callback function. +** @param cleanup A function to call when the callback is removed. +** @param user_data User data to pass to the callback. +*/ +void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data); + +/** +** Remove a callback registered for when a blob is added to the database. +** @param ssb The SSB instance. +** @param callback The callback function. +** @param user_data User data registered with the callback. +*/ +void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data); + /** ** Record that a new blob was stored. ** @param ssb The SSB instance. diff --git a/src/ssb.js.c b/src/ssb.js.c index 9064be63..c3cd7f61 100644 --- a/src/ssb.js.c +++ b/src/ssb.js.c @@ -1627,6 +1627,20 @@ static void _tf_ssb_on_message_added_callback(tf_ssb_t* ssb, const char* author, JS_FreeValue(context, string); } +static void _tf_ssb_on_blob_stored_callback(tf_ssb_t* ssb, const char* id, void* user_data) +{ + JSContext* context = tf_ssb_get_context(ssb); + JSValue callback = JS_MKPTR(JS_TAG_OBJECT, user_data); + JSValue string = JS_NewString(context, id); + JSValue response = JS_Call(context, callback, JS_UNDEFINED, 1, &string); + if (tf_util_report_error(context, response)) + { + tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, user_data); + } + JS_FreeValue(context, response); + JS_FreeValue(context, string); +} + static void _tf_ssb_on_blob_want_added_callback(tf_ssb_t* ssb, const char* id, void* user_data) { JSContext* context = tf_ssb_get_context(ssb); @@ -1747,6 +1761,11 @@ static JSValue _tf_ssb_add_event_listener(JSContext* context, JSValueConst this_ void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); tf_ssb_add_message_added_callback(ssb, _tf_ssb_on_message_added_callback, _tf_ssb_cleanup_value, ptr); } + else if (strcmp(event_name, "blob") == 0) + { + void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); + tf_ssb_add_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, _tf_ssb_cleanup_value, ptr); + } else if (strcmp(event_name, "blob_want_added") == 0) { void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); @@ -1790,6 +1809,11 @@ static JSValue _tf_ssb_remove_event_listener(JSContext* context, JSValueConst th void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); tf_ssb_remove_message_added_callback(ssb, _tf_ssb_on_message_added_callback, ptr); } + else if (strcmp(event_name, "blob") == 0) + { + void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); + tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, ptr); + } else if (strcmp(event_name, "blob_want_added") == 0) { void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); diff --git a/src/ssb.tests.c b/src/ssb.tests.c index 7be8670d..061745e0 100644 --- a/src/ssb.tests.c +++ b/src/ssb.tests.c @@ -152,6 +152,12 @@ static void _wait_stored(tf_ssb_t* ssb, bool* stored) } } +static void _blob_stored(tf_ssb_t* ssb, const char* id, void* user_data) +{ + tf_printf("blob stored %s\n", id); + *(bool*)user_data = true; +} + void tf_ssb_test_ssb(const tf_test_options_t* options) { tf_printf("Testing SSB.\n"); @@ -224,8 +230,13 @@ void tf_ssb_test_ssb(const tf_test_options_t* options) char blob_id[k_id_base64_len] = { 0 }; const char* k_blob = "Hello, blob!"; + bool blob_stored = false; + tf_ssb_add_blob_stored_callback(ssb0, _blob_stored, NULL, &blob_stored); b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL); + tf_ssb_notify_blob_stored(ssb0, blob_id); + tf_ssb_remove_blob_stored_callback(ssb0, _blob_stored, &blob_stored); assert(b); + assert(blob_stored); JSContext* context0 = tf_ssb_get_context(ssb0); JSValue obj = JS_NewObject(context0);