feat: add e_storage_spr, fix memory management for resp

This commit is contained in:
gmega 2026-01-30 17:33:19 -03:00
parent b2d4337f9a
commit 0f131937a8
No known key found for this signature in database
GPG Key ID: 6290D34EAD824B18
5 changed files with 110 additions and 29 deletions

View File

@ -1,4 +1,4 @@
# easystorage
# libeasystorage
A simplified, higher level C wrapper around [libstorage](https://github.com/status-im/logos-storage-nim) built to showcase it. Allows filesharing in the Logos Storage network. Comes with an example console application to interact with it.

View File

@ -1,6 +1,7 @@
#include "easystorage.h"
#include "libstorage.h"
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@ -17,8 +18,11 @@ typedef struct {
size_t len;
progress_callback pcb;
int bytes_done;
bool unreferenced;
} resp;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static resp *resp_alloc(void) {
resp *r = calloc(1, sizeof(resp));
r->ret = -1;
@ -27,7 +31,7 @@ static resp *resp_alloc(void) {
static void resp_destroy(resp *r) {
if (!r) return;
free(r->msg);
if (r->msg) free(r->msg);
free(r);
}
@ -42,9 +46,12 @@ static void on_complete(int ret, const char *msg, size_t len, void *userData) {
resp *r = userData;
if (!r) return;
free(r->msg);
r->msg = NULL;
r->len = 0;
pthread_mutex_lock(&mutex);
if (r->unreferenced) {
resp_destroy(r);
pthread_mutex_unlock(&mutex);
return;
}
if (msg && len > 0) {
r->msg = malloc(len + 1);
@ -56,6 +63,8 @@ static void on_complete(int ret, const char *msg, size_t len, void *userData) {
}
r->ret = ret;
r->unreferenced = true;
pthread_mutex_unlock(&mutex);
}
// Callback for operations that report progress before completing.
@ -63,19 +72,23 @@ static void on_progress(int ret, const char *msg, size_t len, void *userData) {
resp *r = userData;
if (!r) return;
pthread_mutex_lock(&mutex);
if (r->unreferenced) {
resp_destroy(r);
pthread_mutex_unlock(&mutex);
return;
}
if (ret == RET_PROGRESS) {
r->bytes_done += (int) len;
if (r->pcb) {
r->pcb(0, r->bytes_done, ret);
}
pthread_mutex_unlock(&mutex);
return; // don't set r->ret yet — still in progress
}
// Final callback (RET_OK or RET_ERR)
free(r->msg);
r->msg = NULL;
r->len = 0;
if (msg && len > 0) {
r->msg = malloc(len + 1);
if (r->msg) {
@ -86,11 +99,15 @@ static void on_progress(int ret, const char *msg, size_t len, void *userData) {
}
r->ret = ret;
r->unreferenced = true;
pthread_mutex_unlock(&mutex);
}
// Dispatches an async call, waits for completion, extracts the result.
// Returns RET_OK/RET_ERR. If dispatch_ret != RET_OK, returns RET_ERR immediately.
// If out is non-NULL and the response has a message, *out receives a strdup'd copy.
// If **out is non-NULL, allocates a buffer and copies the content of
// resp->msg onto it, which the caller must then free.
static int call_wait(int dispatch_ret, resp *r, char **out) {
if (dispatch_ret != RET_OK) {
resp_destroy(r);
@ -99,13 +116,19 @@ static int call_wait(int dispatch_ret, resp *r, char **out) {
resp_wait(r);
pthread_mutex_lock(&mutex);
int result = (r->ret == RET_OK) ? RET_OK : RET_ERR;
if (out) {
*out = r->msg ? strdup(r->msg) : NULL;
}
resp_destroy(r);
if (r->unreferenced) {
resp_destroy(r);
} else {
r->unreferenced = true;
}
pthread_mutex_unlock(&mutex);
return result;
}
@ -160,19 +183,22 @@ STORAGE_NODE e_storage_new(node_config config) {
}
int e_storage_start(STORAGE_NODE node) {
if (!node) return RET_ERR;
if (!node)
return RET_ERR;
resp *r = resp_alloc();
return call_wait(storage_start(node, (StorageCallback) on_complete, r), r, NULL);
}
int e_storage_stop(STORAGE_NODE node) {
if (!node) return RET_ERR;
if (!node)
return RET_ERR;
resp *r = resp_alloc();
return call_wait(storage_stop(node, (StorageCallback) on_complete, r), r, NULL);
}
int e_storage_destroy(STORAGE_NODE node) {
if (!node) return RET_ERR;
if (!node)
return RET_ERR;
// Close first (tolerate failure)
resp *r = resp_alloc();
@ -183,8 +209,21 @@ int e_storage_destroy(STORAGE_NODE node) {
return call_wait(storage_destroy(node, (StorageCallback) on_complete, r), r, NULL);
}
char *e_storage_spr(STORAGE_NODE node) {
if (!node)
return NULL;
resp *r = resp_alloc();
char *spr = NULL;
int ret = call_wait(storage_spr(node, (StorageCallback) on_complete, r), r, &spr);
if (ret != RET_OK) {
return NULL;
}
return spr;
}
char *e_storage_upload(STORAGE_NODE node, const char *filepath, progress_callback cb) {
if (!node || !filepath) return NULL;
if (!node || !filepath)
return NULL;
// Init upload session
resp *r = resp_alloc();
@ -216,14 +255,15 @@ char *e_storage_upload(STORAGE_NODE node, const char *filepath, progress_callbac
}
int e_storage_download(STORAGE_NODE node, const char *cid, const char *filepath, progress_callback cb) {
if (!node || !cid || !filepath) return RET_ERR;
if (!node || !cid || !filepath)
return RET_ERR;
// Init download
resp *r = resp_alloc();
int ret =
call_wait(storage_download_init(node, cid, DEFAULT_CHUNK_SIZE, false, (StorageCallback) on_complete, r), r,
NULL);
if (ret != RET_OK) return RET_ERR;
int ret = call_wait(storage_download_init(node, cid, DEFAULT_CHUNK_SIZE, false, (StorageCallback) on_complete, r),
r, NULL);
if (ret != RET_OK)
return RET_ERR;
// Stream to file with progress
r = resp_alloc();

View File

@ -20,6 +20,9 @@ int e_storage_start(STORAGE_NODE node);
int e_storage_stop(STORAGE_NODE node);
int e_storage_destroy(STORAGE_NODE node);
// Retrieves the node's SPR (caller must free), or NULL on failure.
char *e_storage_spr(STORAGE_NODE node);
// Uploads a file. Returns CID string on success (caller must free), or NULL on failure.
char *e_storage_upload(STORAGE_NODE node, const char *filepath, progress_callback cb);

View File

@ -18,7 +18,8 @@ void *storage_new(const char *configJson, StorageCallback callback, void *userDa
}
int storage_start(void *ctx, StorageCallback callback, void *userData) {
if (!ctx) return RET_ERR;
if (!ctx)
return RET_ERR;
if (callback) {
callback(RET_OK, "started", 7, userData);
}
@ -26,7 +27,8 @@ int storage_start(void *ctx, StorageCallback callback, void *userData) {
}
int storage_stop(void *ctx, StorageCallback callback, void *userData) {
if (!ctx) return RET_ERR;
if (!ctx)
return RET_ERR;
if (callback) {
callback(RET_OK, "stopped", 7, userData);
}
@ -34,7 +36,8 @@ int storage_stop(void *ctx, StorageCallback callback, void *userData) {
}
int storage_close(void *ctx, StorageCallback callback, void *userData) {
if (!ctx) return RET_ERR;
if (!ctx)
return RET_ERR;
if (callback) {
callback(RET_OK, "closed", 6, userData);
}
@ -42,7 +45,8 @@ int storage_close(void *ctx, StorageCallback callback, void *userData) {
}
int storage_destroy(void *ctx, StorageCallback callback, void *userData) {
if (!ctx) return RET_ERR;
if (!ctx)
return RET_ERR;
if (callback) {
callback(RET_OK, "destroyed", 9, userData);
}
@ -50,7 +54,8 @@ int storage_destroy(void *ctx, StorageCallback callback, void *userData) {
}
int storage_upload_init(void *ctx, const char *filepath, size_t chunkSize, StorageCallback callback, void *userData) {
if (!ctx) return RET_ERR;
if (!ctx)
return RET_ERR;
// Return a fake session ID
const char *session_id = "mock-session-123";
if (callback) {
@ -60,7 +65,8 @@ int storage_upload_init(void *ctx, const char *filepath, size_t chunkSize, Stora
}
int storage_upload_file(void *ctx, const char *sessionId, StorageCallback callback, void *userData) {
if (!ctx) return RET_ERR;
if (!ctx)
return RET_ERR;
// Fire a progress callback first, then final OK with CID
if (callback) {
callback(RET_PROGRESS, "chunk", 5, userData);
@ -72,7 +78,8 @@ int storage_upload_file(void *ctx, const char *sessionId, StorageCallback callba
int storage_download_init(void *ctx, const char *cid, size_t chunkSize, bool local, StorageCallback callback,
void *userData) {
if (!ctx) return RET_ERR;
if (!ctx)
return RET_ERR;
if (callback) {
callback(RET_OK, "init", 4, userData);
}
@ -81,10 +88,27 @@ int storage_download_init(void *ctx, const char *cid, size_t chunkSize, bool loc
int storage_download_stream(void *ctx, const char *cid, size_t chunkSize, bool local, const char *filepath,
StorageCallback callback, void *userData) {
if (!ctx) return RET_ERR;
if (!ctx)
return RET_ERR;
if (callback) {
callback(RET_PROGRESS, "data", 4, userData);
callback(RET_OK, "done", 4, userData);
}
return RET_OK;
}
int storage_spr(void *ctx, StorageCallback callback, void *userData) {
const char *resp = "spr:"
"CiUIAhIhAjWYLRhJho1LoZbaxILgJVTrHptSiejsvLKAqlumo4c4EgIDARpJCicAJQgCEiECNZgtGEmGjUuhltrEguAlVOs"
"em1KJ6Oy8soCqW6ajhzgQsdDzywYaCwoJBLtZShSRAh-"
"aGgsKCQS7WUoUkQIfmipHMEUCIQChjWYvEJE4bE55vtwyAuFvHoGqvpRouXasY7CaIg6nCQIgeCxTWoFmSCNEgMYinM7HuG"
"oCKfoRkcC1pAycNnk-z3A";
if (!ctx)
return RET_ERR;
if (callback) {
callback(RET_OK, resp, strlen(resp), userData);
}
return RET_OK;
}

View File

@ -114,6 +114,19 @@ static void test_download_null(void) {
assert(ret == RET_ERR);
}
static void test_get_should_get_node_spr(void) {
node_config cfg = default_config();
STORAGE_NODE node = e_storage_new(cfg);
assert(node != NULL);
assert(e_storage_start(node) == RET_OK);
const char *sprprefix = "spr:CiUIAhIhA";
const char *spr = e_storage_spr(node);
assert(spr != NULL);
assert(strlen(spr) > 0);
assert(strncmp(sprprefix, spr, strlen(sprprefix)) == 0);
}
static void test_full_lifecycle(void) {
node_config cfg = default_config();
cfg.bootstrap_node = "spr:node1";
@ -148,6 +161,7 @@ int main(void) {
RUN_TEST(test_upload_null);
RUN_TEST(test_download);
RUN_TEST(test_download_null);
RUN_TEST(test_get_should_get_node_spr);
RUN_TEST(test_full_lifecycle);
printf("\n%d/%d tests passed.\n", tests_passed, tests_run);