split into library and applications
This commit is contained in:
123
libdc/src/account.c
Normal file
123
libdc/src/account.c
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <dc/account.h>
|
||||
#include <dc/refable.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
struct dc_account_
|
||||
{
|
||||
dc_refable_t ref; /* do not move anything above ref */
|
||||
|
||||
char *email;
|
||||
char *password;
|
||||
|
||||
/* internal ID
|
||||
*/
|
||||
char *id;
|
||||
|
||||
/* authentication token
|
||||
*/
|
||||
char *token;
|
||||
};
|
||||
|
||||
static void dc_account_free(dc_account_t ptr)
|
||||
{
|
||||
return_if_true(ptr == NULL,);
|
||||
|
||||
free(ptr->email);
|
||||
free(ptr->password);
|
||||
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
dc_account_t dc_account_new(void)
|
||||
{
|
||||
dc_account_t ptr = calloc(1, sizeof(struct dc_account_));
|
||||
|
||||
ptr->ref.cleanup = (dc_cleanup_t)dc_account_free;
|
||||
|
||||
return dc_ref(ptr);
|
||||
}
|
||||
|
||||
dc_account_t dc_account_new2(char const *email, char const *pass)
|
||||
{
|
||||
dc_account_t ptr = dc_account_new();
|
||||
|
||||
if (ptr != NULL) {
|
||||
dc_account_set_email(ptr, email);
|
||||
dc_account_set_password(ptr, pass);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void dc_account_set_email(dc_account_t a, char const *email)
|
||||
{
|
||||
return_if_true(a == NULL,);
|
||||
return_if_true(email == NULL,);
|
||||
|
||||
free(a->email);
|
||||
a->email = strdup(email);
|
||||
}
|
||||
|
||||
char const *dc_account_email(dc_account_t a)
|
||||
{
|
||||
return_if_true(a == NULL, NULL);
|
||||
return a->email;
|
||||
}
|
||||
|
||||
void dc_account_set_password(dc_account_t a, char const *password)
|
||||
{
|
||||
return_if_true(a == NULL,);
|
||||
return_if_true(password == NULL,);
|
||||
|
||||
free(a->password);
|
||||
a->password = strdup(password);
|
||||
}
|
||||
|
||||
char const *dc_account_password(dc_account_t a)
|
||||
{
|
||||
return_if_true(a == NULL, NULL);
|
||||
return a->password;
|
||||
}
|
||||
|
||||
void dc_account_set_token(dc_account_t a, char const *token)
|
||||
{
|
||||
return_if_true(a == NULL,);
|
||||
|
||||
free(a->token);
|
||||
a->token = NULL;
|
||||
|
||||
if (token != NULL) {
|
||||
a->token = strdup(token);
|
||||
}
|
||||
}
|
||||
|
||||
char const *dc_account_token(dc_account_t a)
|
||||
{
|
||||
return_if_true(a == NULL, NULL);
|
||||
return a->token;
|
||||
}
|
||||
|
||||
bool dc_account_has_token(dc_account_t a)
|
||||
{
|
||||
return_if_true(a == NULL, false);
|
||||
return_if_true(a->token == NULL, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void dc_account_set_id(dc_account_t a, char const *id)
|
||||
{
|
||||
return_if_true(a == NULL,);
|
||||
free(a->id);
|
||||
a->id = strdup(id);
|
||||
}
|
||||
|
||||
char const *dc_account_id(dc_account_t a)
|
||||
{
|
||||
return_if_true(a == NULL,NULL);
|
||||
return a->id;
|
||||
}
|
||||
364
libdc/src/api.c
Normal file
364
libdc/src/api.c
Normal file
@@ -0,0 +1,364 @@
|
||||
#include <dc/api.h>
|
||||
#include <dc/refable.h>
|
||||
#include "internal.h"
|
||||
|
||||
#define DISCORD_URL "https://discordapp.com/api/v6"
|
||||
|
||||
#define DISCORD_USERAGENT "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
|
||||
#define DISCORD_API_AUTH "auth/login"
|
||||
|
||||
struct dc_api_
|
||||
{
|
||||
dc_refable_t ref;
|
||||
|
||||
struct event_base *base;
|
||||
CURLM *curl;
|
||||
|
||||
GHashTable *syncs;
|
||||
|
||||
char *cookie;
|
||||
};
|
||||
|
||||
static void dc_api_free(dc_api_t ptr)
|
||||
{
|
||||
return_if_true(ptr == NULL,);
|
||||
|
||||
if (ptr->syncs != NULL) {
|
||||
g_hash_table_unref(ptr->syncs);
|
||||
ptr->syncs = NULL;
|
||||
}
|
||||
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
dc_api_t dc_api_new(void)
|
||||
{
|
||||
dc_api_t ptr = calloc(1, sizeof(struct dc_api_));
|
||||
return_if_true(ptr == NULL, NULL);
|
||||
|
||||
ptr->ref.cleanup = (dc_cleanup_t)dc_api_free;
|
||||
|
||||
ptr->syncs = g_hash_table_new_full(g_direct_hash, g_direct_equal,
|
||||
NULL, dc_unref
|
||||
);
|
||||
if (ptr->syncs == NULL) {
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dc_ref(ptr);
|
||||
}
|
||||
|
||||
void dc_api_set_curl_multi(dc_api_t api, CURLM *curl)
|
||||
{
|
||||
return_if_true(api == NULL,);
|
||||
return_if_true(curl == NULL,);
|
||||
|
||||
api->curl = curl;
|
||||
}
|
||||
|
||||
void dc_api_set_event_base(dc_api_t api, struct event_base *base)
|
||||
{
|
||||
return_if_true(api == NULL,);
|
||||
return_if_true(base == NULL,);
|
||||
|
||||
api->base = base;
|
||||
}
|
||||
|
||||
void dc_api_signal(dc_api_t api, CURL *easy, int code)
|
||||
{
|
||||
dc_api_sync_t sync = NULL;
|
||||
|
||||
return_if_true(api == NULL,);
|
||||
return_if_true(easy == NULL,);
|
||||
|
||||
sync = g_hash_table_lookup(api->syncs, easy);
|
||||
if (sync != NULL) {
|
||||
dc_api_sync_finish(sync, code);
|
||||
g_hash_table_remove(api->syncs, easy);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
static int debug_callback(CURL *handle, curl_infotype type,
|
||||
char *data, size_t size,
|
||||
void *userptr
|
||||
)
|
||||
{
|
||||
switch (type) {
|
||||
case CURLINFO_TEXT: printf("+T: %s", data); break;
|
||||
case CURLINFO_HEADER_IN: printf(">H: %s", data); break;
|
||||
case CURLINFO_HEADER_OUT: printf("<H: %s", data); break;
|
||||
case CURLINFO_DATA_IN: printf(">D: %s\n", data); break;
|
||||
case CURLINFO_DATA_OUT: printf("<D: %s\n", data); break;
|
||||
case CURLINFO_SSL_DATA_IN:
|
||||
case CURLINFO_SSL_DATA_OUT:
|
||||
default: break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int header_callback(char *data, size_t sz, size_t num, dc_api_t api)
|
||||
{
|
||||
char *ptr = NULL;
|
||||
|
||||
if ((ptr = strstr(data, "set-cookie")) != NULL) {
|
||||
free(api->cookie);
|
||||
api->cookie = NULL;
|
||||
|
||||
if ((ptr = strstr(data, ":")) != NULL) {
|
||||
api->cookie = strdup(ptr+1);
|
||||
if ((ptr = strstr(api->cookie, ";")) != NULL) {
|
||||
*ptr = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sz * num;
|
||||
}
|
||||
|
||||
static dc_api_sync_t dc_api_post(dc_api_t api,
|
||||
char const *url,
|
||||
char const *token,
|
||||
char const *data, int64_t len)
|
||||
{
|
||||
return_if_true(api == NULL, NULL);
|
||||
return_if_true(api->curl == NULL, NULL);
|
||||
return_if_true(url == NULL, NULL);
|
||||
|
||||
CURL *c = NULL;
|
||||
bool ret = false;
|
||||
dc_api_sync_t sync = NULL;
|
||||
struct curl_slist *l = NULL;
|
||||
char *tmp = NULL;
|
||||
int ptr = 0;
|
||||
|
||||
c = curl_easy_init();
|
||||
goto_if_true(c == NULL, cleanup);
|
||||
|
||||
sync = dc_api_sync_new(api->curl, c);
|
||||
goto_if_true(c == NULL, cleanup);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_URL, url);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, fwrite);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEDATA, dc_api_sync_stream(sync));
|
||||
curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, header_callback);
|
||||
curl_easy_setopt(c, CURLOPT_HEADERDATA, api);
|
||||
|
||||
if (api->cookie != NULL) {
|
||||
curl_easy_setopt(c, CURLOPT_COOKIE, api->cookie);
|
||||
}
|
||||
|
||||
l = dc_api_sync_list(sync);
|
||||
if (data != NULL) {
|
||||
curl_slist_append(l, "Content-Type: application/json");
|
||||
}
|
||||
curl_slist_append(l, "Accept: application/json");
|
||||
curl_slist_append(l, "User-Agent: " DISCORD_USERAGENT);
|
||||
curl_slist_append(l, "Pragma: no-cache");
|
||||
curl_slist_append(l, "Cache-Control: no-cache");
|
||||
|
||||
if (token != NULL) {
|
||||
asprintf(&tmp, "Authorization: %s", token);
|
||||
curl_slist_append(l, tmp);
|
||||
free(tmp);
|
||||
tmp = NULL;
|
||||
}
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_HTTPHEADER, l);
|
||||
curl_easy_setopt(c, CURLOPT_FORBID_REUSE, 1L);
|
||||
//curl_easy_setopt(c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
|
||||
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
#ifdef DEBUG
|
||||
curl_easy_setopt(c, CURLOPT_VERBOSE, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_DEBUGFUNCTION, debug_callback);
|
||||
#endif
|
||||
|
||||
if (data != NULL) {
|
||||
curl_easy_setopt(c, CURLOPT_POST, 1UL);
|
||||
curl_easy_setopt(c, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
|
||||
curl_easy_setopt(c, CURLOPT_COPYPOSTFIELDS, data);
|
||||
if (len >= 0) {
|
||||
curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE_LARGE, len);
|
||||
}
|
||||
}
|
||||
|
||||
if (curl_multi_add_handle(api->curl, c) != CURLM_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
g_hash_table_insert(api->syncs, c, dc_ref(sync));
|
||||
curl_multi_socket_action(api->curl, CURL_SOCKET_TIMEOUT, 0, &ptr);
|
||||
|
||||
ret = true;
|
||||
|
||||
cleanup:
|
||||
|
||||
if (!ret) {
|
||||
dc_unref(sync);
|
||||
sync = NULL;
|
||||
}
|
||||
|
||||
return sync;
|
||||
}
|
||||
|
||||
dc_api_sync_t dc_api_call(dc_api_t api, char const *token,
|
||||
char const *method, json_t *j)
|
||||
{
|
||||
char *data = NULL;
|
||||
char *url = NULL;
|
||||
dc_api_sync_t s = NULL;
|
||||
|
||||
asprintf(&url, "%s/%s", DISCORD_URL, method);
|
||||
goto_if_true(url == NULL, cleanup);
|
||||
|
||||
if (j != NULL) {
|
||||
data = json_dumps(j, JSON_COMPACT);
|
||||
goto_if_true(data == NULL, cleanup);
|
||||
}
|
||||
|
||||
s = dc_api_post(api, url, token, data, -1);
|
||||
goto_if_true(s == NULL, cleanup);
|
||||
|
||||
cleanup:
|
||||
|
||||
free(data);
|
||||
data = NULL;
|
||||
|
||||
free(url);
|
||||
url = NULL;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
json_t *dc_api_call_sync(dc_api_t api, char const *token,
|
||||
char const *method, json_t *j)
|
||||
{
|
||||
dc_api_sync_t s = NULL;
|
||||
json_t *reply = NULL;
|
||||
|
||||
s = dc_api_call(api, token, method, j);
|
||||
goto_if_true(s == NULL, cleanup);
|
||||
|
||||
if (!dc_api_sync_wait(s)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("api_call_sync: %d\n", dc_api_sync_code(s));
|
||||
#endif
|
||||
|
||||
reply = json_loadb(dc_api_sync_data(s),
|
||||
dc_api_sync_datalen(s),
|
||||
0, NULL
|
||||
);
|
||||
|
||||
cleanup:
|
||||
|
||||
dc_unref(s);
|
||||
s = NULL;
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
static bool dc_api_error(json_t *j, int *code, char const **message)
|
||||
{
|
||||
return_if_true(j == NULL, false);
|
||||
|
||||
bool error = false;
|
||||
json_t *c = NULL, *m = NULL;
|
||||
|
||||
c = json_object_get(j, "code");
|
||||
if (c != NULL) {
|
||||
error = true;
|
||||
if (code != NULL) {
|
||||
*code = json_integer_value(c);
|
||||
}
|
||||
}
|
||||
|
||||
m = json_object_get(j, "message");
|
||||
if (m != NULL) {
|
||||
error = true;
|
||||
if (message != NULL) {
|
||||
*message = json_string_value(m);
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
bool dc_api_authenticate(dc_api_t api, dc_account_t account)
|
||||
{
|
||||
json_t *j = json_object(), *reply = NULL, *token = NULL;
|
||||
bool ret = false;
|
||||
|
||||
json_object_set_new(j, "email",
|
||||
json_string(dc_account_email(account))
|
||||
);
|
||||
json_object_set_new(j, "password",
|
||||
json_string(dc_account_password(account))
|
||||
);
|
||||
|
||||
reply = dc_api_call_sync(api, NULL, DISCORD_API_AUTH, j);
|
||||
goto_if_true(reply == NULL, cleanup);
|
||||
|
||||
if (dc_api_error(j, NULL, NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
token = json_object_get(reply, "token");
|
||||
if (token == NULL || !json_is_string(token)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
dc_account_set_token(account, json_string_value(token));
|
||||
ret = true;
|
||||
|
||||
cleanup:
|
||||
|
||||
if (j != NULL) {
|
||||
json_decref(j);
|
||||
j = NULL;
|
||||
}
|
||||
|
||||
if (reply != NULL) {
|
||||
json_decref(reply);
|
||||
reply = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool dc_api_userinfo(dc_api_t api, dc_account_t login,
|
||||
dc_account_t user)
|
||||
{
|
||||
char *url = NULL;
|
||||
json_t *reply = NULL;
|
||||
bool ret = false;
|
||||
|
||||
return_if_true(api == NULL, false);
|
||||
return_if_true(login == NULL, false);
|
||||
return_if_true(user == NULL, false);
|
||||
|
||||
asprintf(&url, "users/%s", dc_account_id(user));
|
||||
|
||||
reply = dc_api_call_sync(api, dc_account_token(login), url, NULL);
|
||||
goto_if_true(reply == NULL, cleanup);
|
||||
|
||||
/* TODO: parse json and store info in user
|
||||
*/
|
||||
|
||||
ret = true;
|
||||
|
||||
cleanup:
|
||||
|
||||
if (reply != NULL) {
|
||||
json_decref(reply);
|
||||
reply = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
130
libdc/src/apisync.c
Normal file
130
libdc/src/apisync.c
Normal file
@@ -0,0 +1,130 @@
|
||||
#include <dc/apisync.h>
|
||||
#include <dc/refable.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
struct dc_api_sync_
|
||||
{
|
||||
dc_refable_t ref;
|
||||
|
||||
int code;
|
||||
|
||||
char *buffer;
|
||||
size_t bufferlen;
|
||||
FILE *stream;
|
||||
|
||||
pthread_mutex_t mtx;
|
||||
pthread_cond_t cnd;
|
||||
|
||||
CURL *easy;
|
||||
CURLM *curl;
|
||||
struct curl_slist *list;
|
||||
};
|
||||
|
||||
static void dc_api_sync_free(dc_api_sync_t s)
|
||||
{
|
||||
return_if_true(s == NULL,);
|
||||
|
||||
pthread_mutex_lock(&s->mtx);
|
||||
|
||||
pthread_cond_destroy(&s->cnd);
|
||||
pthread_mutex_destroy(&s->mtx);
|
||||
|
||||
curl_multi_remove_handle(s->curl, s->easy);
|
||||
curl_easy_cleanup(s->easy);
|
||||
|
||||
if (s->stream != NULL) {
|
||||
fclose(s->stream);
|
||||
s->stream = NULL;
|
||||
}
|
||||
|
||||
free(s->buffer);
|
||||
s->buffer = NULL;
|
||||
s->bufferlen = 0;
|
||||
|
||||
curl_slist_free_all(s->list);
|
||||
s->list = NULL;
|
||||
|
||||
free(s);
|
||||
}
|
||||
|
||||
dc_api_sync_t dc_api_sync_new(CURLM *curl, CURL *easy)
|
||||
{
|
||||
dc_api_sync_t ptr = calloc(1, sizeof(struct dc_api_sync_));
|
||||
return_if_true(ptr == NULL, NULL);
|
||||
|
||||
ptr->easy = easy;
|
||||
ptr->ref.cleanup = (dc_cleanup_t)dc_api_sync_free;
|
||||
|
||||
ptr->stream = open_memstream(&ptr->buffer, &ptr->bufferlen);
|
||||
if (ptr->stream == NULL) {
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_init(&ptr->mtx, NULL);
|
||||
pthread_cond_init(&ptr->cnd, NULL);
|
||||
|
||||
ptr->list = curl_slist_append(NULL, "");
|
||||
|
||||
return dc_ref(ptr);
|
||||
}
|
||||
|
||||
struct curl_slist *dc_api_sync_list(dc_api_sync_t sync)
|
||||
{
|
||||
return_if_true(sync == NULL, NULL);
|
||||
return sync->list;
|
||||
}
|
||||
|
||||
FILE *dc_api_sync_stream(dc_api_sync_t sync)
|
||||
{
|
||||
return_if_true(sync == NULL, NULL);
|
||||
return sync->stream;
|
||||
}
|
||||
|
||||
char const *dc_api_sync_data(dc_api_sync_t sync)
|
||||
{
|
||||
return_if_true(sync == NULL, NULL);
|
||||
return sync->buffer;
|
||||
}
|
||||
|
||||
size_t dc_api_sync_datalen(dc_api_sync_t sync)
|
||||
{
|
||||
return_if_true(sync == NULL, 0L);
|
||||
return sync->bufferlen;
|
||||
}
|
||||
|
||||
int dc_api_sync_code(dc_api_sync_t sync)
|
||||
{
|
||||
return_if_true(sync == NULL, 0L);
|
||||
return sync->code;
|
||||
}
|
||||
|
||||
bool dc_api_sync_wait(dc_api_sync_t sync)
|
||||
{
|
||||
return_if_true(sync == NULL, false);
|
||||
|
||||
if (sync->stream == NULL && sync->buffer != NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&sync->mtx);
|
||||
pthread_cond_wait(&sync->cnd, &sync->mtx);
|
||||
pthread_mutex_unlock(&sync->mtx);
|
||||
|
||||
return (sync->stream == NULL && sync->buffer != NULL);
|
||||
}
|
||||
|
||||
void dc_api_sync_finish(dc_api_sync_t sync, int code)
|
||||
{
|
||||
return_if_true(sync == NULL,);
|
||||
|
||||
pthread_mutex_lock(&sync->mtx);
|
||||
sync->code = code;
|
||||
if (sync->stream != NULL) {
|
||||
fclose(sync->stream);
|
||||
sync->stream = NULL;
|
||||
}
|
||||
pthread_cond_broadcast(&sync->cnd);
|
||||
pthread_mutex_unlock(&sync->mtx);
|
||||
}
|
||||
29
libdc/src/internal.h
Normal file
29
libdc/src/internal.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef DC_INTERNAL_H
|
||||
#define DC_INTERNAL_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/thread.h>
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#define return_if_true(v,r) do { if (v) return r; } while(0)
|
||||
#define goto_if_true(v,l) do { if (v) goto l; } while(0)
|
||||
|
||||
#endif
|
||||
6
libdc/src/loop.c
Normal file
6
libdc/src/loop.c
Normal file
@@ -0,0 +1,6 @@
|
||||
#include <dc/loop.h>
|
||||
|
||||
struct nc_loop_
|
||||
{
|
||||
nc_refable_t ref;
|
||||
};
|
||||
27
libdc/src/refable.c
Normal file
27
libdc/src/refable.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <dc/refable.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
void *dc_ref(void *arg)
|
||||
{
|
||||
dc_refable_t *ptr = NULL;
|
||||
|
||||
return_if_true(arg == NULL,NULL);
|
||||
|
||||
ptr = (dc_refable_t *)arg;
|
||||
++ptr->ref;
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
void dc_unref(void *arg)
|
||||
{
|
||||
dc_refable_t *ptr = NULL;
|
||||
|
||||
return_if_true(arg == NULL,);
|
||||
|
||||
ptr = (dc_refable_t *)arg;
|
||||
if ((--ptr->ref) <= 0 && ptr->cleanup != NULL) {
|
||||
ptr->cleanup(arg);
|
||||
}
|
||||
}
|
||||
12
libdc/src/util.c
Normal file
12
libdc/src/util.c
Normal file
@@ -0,0 +1,12 @@
|
||||
#include <dc/util.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
void ncdc_util_dump_json(json_t *j)
|
||||
{
|
||||
return_if_true(j == NULL,);
|
||||
char *str = json_dumps(j, JSON_COMPACT);
|
||||
|
||||
printf("%s\n", str);
|
||||
free(str);
|
||||
}
|
||||
Reference in New Issue
Block a user