ncdc - discord client
This commit is contained in:
commit
8972538d13
47
CMakeLists.txt
Normal file
47
CMakeLists.txt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
|
||||||
|
|
||||||
|
FIND_PACKAGE(PkgConfig)
|
||||||
|
FIND_PACKAGE(Threads)
|
||||||
|
|
||||||
|
PKG_CHECK_MODULES(JANSSON REQUIRED jansson)
|
||||||
|
PKG_CHECK_MODULES(CURL REQUIRED libcurl)
|
||||||
|
PKG_CHECK_MODULES(EVENT REQUIRED libevent libevent_pthreads)
|
||||||
|
PKG_CHECK_MODULES(GLIB2 REQUIRED glib-2.0)
|
||||||
|
|
||||||
|
SET(TARGET "ncdc")
|
||||||
|
|
||||||
|
SET(SOURCES
|
||||||
|
"include/ncdc/account.h"
|
||||||
|
"include/ncdc/api.h"
|
||||||
|
"include/ncdc/apisync.h"
|
||||||
|
"include/ncdc/ncdc.h"
|
||||||
|
"include/ncdc/refable.h"
|
||||||
|
"src/account.c"
|
||||||
|
"src/api.c"
|
||||||
|
"src/apisync.c"
|
||||||
|
"src/ncdc.c"
|
||||||
|
"src/refable.c"
|
||||||
|
)
|
||||||
|
|
||||||
|
ADD_DEFINITIONS("-Wall -Werror -std=c11 -D_GNU_SOURCE")
|
||||||
|
|
||||||
|
INCLUDE_DIRECTORIES("include"
|
||||||
|
${JANSSON_INCLUDE_DIRS}
|
||||||
|
${CURL_INCLUDE_DIRS}
|
||||||
|
${EVENT_INCLUDE_DIRS}
|
||||||
|
${GLIB2_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
LINK_DIRECTORIES(${JANSSON_LIBRARY_DIRS}
|
||||||
|
${CURL_LIBRARY_DIRS}
|
||||||
|
${EVENT_LIBRARY_DIRS}
|
||||||
|
${GLIB2_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
ADD_EXECUTABLE(${TARGET} ${SOURCES})
|
||||||
|
TARGET_LINK_LIBRARIES(${TARGET}
|
||||||
|
${JANSSON_LIBRARIES}
|
||||||
|
${CURL_LIBRARIES}
|
||||||
|
${EVENT_LIBRARIES}
|
||||||
|
${GLIB2_LIBRARIES}
|
||||||
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
)
|
25
include/ncdc/account.h
Normal file
25
include/ncdc/account.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef NCDC_ACCOUNT_H
|
||||||
|
#define NCDC_ACCOUNT_H
|
||||||
|
|
||||||
|
#include <ncdc/ncdc.h>
|
||||||
|
|
||||||
|
struct ncdc_account_;
|
||||||
|
typedef struct ncdc_account_ *ncdc_account_t;
|
||||||
|
|
||||||
|
ncdc_account_t ncdc_account_new(void);
|
||||||
|
ncdc_account_t ncdc_account_new2(char const *email, char const *pass);
|
||||||
|
|
||||||
|
void ncdc_account_set_email(ncdc_account_t a, char const *email);
|
||||||
|
char const *ncdc_account_email(ncdc_account_t a);
|
||||||
|
|
||||||
|
void ncdc_account_set_password(ncdc_account_t a, char const *password);
|
||||||
|
char const *ncdc_account_password(ncdc_account_t a);
|
||||||
|
|
||||||
|
void ncdc_account_set_id(ncdc_account_t a, char const *id);
|
||||||
|
char const *ncdc_account_id(ncdc_account_t a);
|
||||||
|
|
||||||
|
void ncdc_account_set_token(ncdc_account_t a, char const *token);
|
||||||
|
char const *ncdc_account_token(ncdc_account_t a);
|
||||||
|
bool ncdc_account_has_token(ncdc_account_t a);
|
||||||
|
|
||||||
|
#endif
|
34
include/ncdc/api.h
Normal file
34
include/ncdc/api.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#ifndef NCDC_API_H
|
||||||
|
#define NCDC_API_H
|
||||||
|
|
||||||
|
#include <ncdc/ncdc.h>
|
||||||
|
|
||||||
|
#include <ncdc/apisync.h>
|
||||||
|
#include <ncdc/account.h>
|
||||||
|
|
||||||
|
struct ncdc_api_;
|
||||||
|
typedef struct ncdc_api_ *ncdc_api_t;
|
||||||
|
|
||||||
|
ncdc_api_t ncdc_api_new(void);
|
||||||
|
|
||||||
|
void ncdc_api_set_curl_multi(ncdc_api_t api, CURLM *curl);
|
||||||
|
void ncdc_api_set_event_base(ncdc_api_t api, struct event_base *base);
|
||||||
|
|
||||||
|
/* call this function in case the MULTI has told us that some
|
||||||
|
* transfer has finished.
|
||||||
|
*/
|
||||||
|
void ncdc_api_signal(ncdc_api_t api, CURL *easy, int code);
|
||||||
|
|
||||||
|
/* internal curl stuff
|
||||||
|
*/
|
||||||
|
ncdc_api_sync_t ncdc_api_call(ncdc_api_t api, char const *token,
|
||||||
|
char const *method, json_t *j);
|
||||||
|
json_t *ncdc_api_call_sync(ncdc_api_t api, char const *token,
|
||||||
|
char const *method, json_t *j);
|
||||||
|
|
||||||
|
bool ncdc_api_authenticate(ncdc_api_t api, ncdc_account_t account);
|
||||||
|
bool ncdc_api_userinfo(ncdc_api_t api, ncdc_account_t logion,
|
||||||
|
ncdc_account_t user);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
20
include/ncdc/apisync.h
Normal file
20
include/ncdc/apisync.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef NCDC_API_ASYNC_H
|
||||||
|
#define NCDC_API_ASYNC_H
|
||||||
|
|
||||||
|
#include <ncdc/ncdc.h>
|
||||||
|
|
||||||
|
struct ncdc_api_sync_;
|
||||||
|
typedef struct ncdc_api_sync_ *ncdc_api_sync_t;
|
||||||
|
|
||||||
|
ncdc_api_sync_t ncdc_api_sync_new(CURLM *curl, CURL *easy);
|
||||||
|
|
||||||
|
FILE *ncdc_api_sync_stream(ncdc_api_sync_t sync);
|
||||||
|
char const *ncdc_api_sync_data(ncdc_api_sync_t sync);
|
||||||
|
size_t ncdc_api_sync_datalen(ncdc_api_sync_t sync);
|
||||||
|
int ncdc_api_sync_code(ncdc_api_sync_t sync);
|
||||||
|
struct curl_slist *ncdc_api_sync_list(ncdc_api_sync_t sync);
|
||||||
|
|
||||||
|
bool ncdc_api_sync_wait(ncdc_api_sync_t sync);
|
||||||
|
void ncdc_api_sync_finish(ncdc_api_sync_t sync, int code);
|
||||||
|
|
||||||
|
#endif
|
31
include/ncdc/ncdc.h
Normal file
31
include/ncdc/ncdc.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef NCDC_H
|
||||||
|
#define NCDC_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)
|
||||||
|
|
||||||
|
extern char *ncdc_private_dir;
|
||||||
|
|
||||||
|
#endif
|
16
include/ncdc/refable.h
Normal file
16
include/ncdc/refable.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#ifndef NCDC_REFABLE_H
|
||||||
|
#define NCDC_REFABLE_H
|
||||||
|
|
||||||
|
#include <ncdc/ncdc.h>
|
||||||
|
|
||||||
|
typedef void (*cleanup_t)(void *);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int ref;
|
||||||
|
cleanup_t cleanup;
|
||||||
|
} ncdc_refable_t;
|
||||||
|
|
||||||
|
void *ncdc_ref(void *);
|
||||||
|
void ncdc_unref(void *);
|
||||||
|
|
||||||
|
#endif
|
121
src/account.c
Normal file
121
src/account.c
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#include <ncdc/account.h>
|
||||||
|
#include <ncdc/refable.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
struct ncdc_account_
|
||||||
|
{
|
||||||
|
ncdc_refable_t ref; /* do not move anything above ref */
|
||||||
|
|
||||||
|
char *email;
|
||||||
|
char *password;
|
||||||
|
|
||||||
|
/* internal ID
|
||||||
|
*/
|
||||||
|
char *id;
|
||||||
|
|
||||||
|
/* authentication token
|
||||||
|
*/
|
||||||
|
char *token;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void ncdc_account_free(ncdc_account_t ptr)
|
||||||
|
{
|
||||||
|
return_if_true(ptr == NULL,);
|
||||||
|
|
||||||
|
free(ptr->email);
|
||||||
|
free(ptr->password);
|
||||||
|
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
ncdc_account_t ncdc_account_new(void)
|
||||||
|
{
|
||||||
|
ncdc_account_t ptr = calloc(1, sizeof(struct ncdc_account_));
|
||||||
|
|
||||||
|
ptr->ref.cleanup = (cleanup_t)ncdc_account_free;
|
||||||
|
|
||||||
|
return ncdc_ref(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
ncdc_account_t ncdc_account_new2(char const *email, char const *pass)
|
||||||
|
{
|
||||||
|
ncdc_account_t ptr = ncdc_account_new();
|
||||||
|
|
||||||
|
if (ptr != NULL) {
|
||||||
|
ncdc_account_set_email(ptr, email);
|
||||||
|
ncdc_account_set_password(ptr, pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncdc_account_set_email(ncdc_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 *ncdc_account_email(ncdc_account_t a)
|
||||||
|
{
|
||||||
|
return_if_true(a == NULL, NULL);
|
||||||
|
return a->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncdc_account_set_password(ncdc_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 *ncdc_account_password(ncdc_account_t a)
|
||||||
|
{
|
||||||
|
return_if_true(a == NULL, NULL);
|
||||||
|
return a->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncdc_account_set_token(ncdc_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 *ncdc_account_token(ncdc_account_t a)
|
||||||
|
{
|
||||||
|
return_if_true(a == NULL, NULL);
|
||||||
|
return a->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ncdc_account_has_token(ncdc_account_t a)
|
||||||
|
{
|
||||||
|
return_if_true(a == NULL, false);
|
||||||
|
return_if_true(a->token == NULL, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncdc_account_set_id(ncdc_account_t a, char const *id)
|
||||||
|
{
|
||||||
|
return_if_true(a == NULL,);
|
||||||
|
free(a->id);
|
||||||
|
a->id = strdup(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *ncdc_account_id(ncdc_account_t a)
|
||||||
|
{
|
||||||
|
return_if_true(a == NULL,NULL);
|
||||||
|
return a->id;
|
||||||
|
}
|
363
src/api.c
Normal file
363
src/api.c
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
#include <ncdc/api.h>
|
||||||
|
#include <ncdc/refable.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 ncdc_api_
|
||||||
|
{
|
||||||
|
ncdc_refable_t ref;
|
||||||
|
|
||||||
|
struct event_base *base;
|
||||||
|
CURLM *curl;
|
||||||
|
|
||||||
|
GHashTable *syncs;
|
||||||
|
|
||||||
|
char *cookie;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void ncdc_api_free(ncdc_api_t ptr)
|
||||||
|
{
|
||||||
|
return_if_true(ptr == NULL,);
|
||||||
|
|
||||||
|
if (ptr->syncs != NULL) {
|
||||||
|
g_hash_table_unref(ptr->syncs);
|
||||||
|
ptr->syncs = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
ncdc_api_t ncdc_api_new(void)
|
||||||
|
{
|
||||||
|
ncdc_api_t ptr = calloc(1, sizeof(struct ncdc_api_));
|
||||||
|
return_if_true(ptr == NULL, NULL);
|
||||||
|
|
||||||
|
ptr->ref.cleanup = (cleanup_t)ncdc_api_free;
|
||||||
|
|
||||||
|
ptr->syncs = g_hash_table_new_full(g_direct_hash, g_direct_equal,
|
||||||
|
NULL, ncdc_unref
|
||||||
|
);
|
||||||
|
if (ptr->syncs == NULL) {
|
||||||
|
free(ptr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ncdc_ref(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncdc_api_set_curl_multi(ncdc_api_t api, CURLM *curl)
|
||||||
|
{
|
||||||
|
return_if_true(api == NULL,);
|
||||||
|
return_if_true(curl == NULL,);
|
||||||
|
|
||||||
|
api->curl = curl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncdc_api_set_event_base(ncdc_api_t api, struct event_base *base)
|
||||||
|
{
|
||||||
|
return_if_true(api == NULL,);
|
||||||
|
return_if_true(base == NULL,);
|
||||||
|
|
||||||
|
api->base = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncdc_api_signal(ncdc_api_t api, CURL *easy, int code)
|
||||||
|
{
|
||||||
|
ncdc_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) {
|
||||||
|
ncdc_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, ncdc_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 ncdc_api_sync_t ncdc_api_post(ncdc_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;
|
||||||
|
ncdc_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 = ncdc_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, ncdc_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 = ncdc_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, ncdc_ref(sync));
|
||||||
|
curl_multi_socket_action(api->curl, CURL_SOCKET_TIMEOUT, 0, &ptr);
|
||||||
|
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
ncdc_unref(sync);
|
||||||
|
sync = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sync;
|
||||||
|
}
|
||||||
|
|
||||||
|
ncdc_api_sync_t ncdc_api_call(ncdc_api_t api, char const *token,
|
||||||
|
char const *method, json_t *j)
|
||||||
|
{
|
||||||
|
char *data = NULL;
|
||||||
|
char *url = NULL;
|
||||||
|
ncdc_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 = ncdc_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 *ncdc_api_call_sync(ncdc_api_t api, char const *token,
|
||||||
|
char const *method, json_t *j)
|
||||||
|
{
|
||||||
|
ncdc_api_sync_t s = NULL;
|
||||||
|
json_t *reply = NULL;
|
||||||
|
|
||||||
|
s = ncdc_api_call(api, token, method, j);
|
||||||
|
goto_if_true(s == NULL, cleanup);
|
||||||
|
|
||||||
|
if (!ncdc_api_sync_wait(s)) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("api_call_sync: %d\n", ncdc_api_sync_code(s));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
reply = json_loadb(ncdc_api_sync_data(s),
|
||||||
|
ncdc_api_sync_datalen(s),
|
||||||
|
0, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
ncdc_unref(s);
|
||||||
|
s = NULL;
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ncdc_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 ncdc_api_authenticate(ncdc_api_t api, ncdc_account_t account)
|
||||||
|
{
|
||||||
|
json_t *j = json_object(), *reply = NULL, *token = NULL;
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
json_object_set_new(j, "email",
|
||||||
|
json_string(ncdc_account_email(account))
|
||||||
|
);
|
||||||
|
json_object_set_new(j, "password",
|
||||||
|
json_string(ncdc_account_password(account))
|
||||||
|
);
|
||||||
|
|
||||||
|
reply = ncdc_api_call_sync(api, NULL, DISCORD_API_AUTH, j);
|
||||||
|
goto_if_true(reply == NULL, cleanup);
|
||||||
|
|
||||||
|
if (ncdc_api_error(j, NULL, NULL)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
token = json_object_get(reply, "token");
|
||||||
|
if (token == NULL || !json_is_string(token)) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
ncdc_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 ncdc_api_userinfo(ncdc_api_t api, ncdc_account_t login,
|
||||||
|
ncdc_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", ncdc_account_id(user));
|
||||||
|
|
||||||
|
reply = ncdc_api_call_sync(api, ncdc_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;
|
||||||
|
}
|
128
src/apisync.c
Normal file
128
src/apisync.c
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#include <ncdc/apisync.h>
|
||||||
|
#include <ncdc/refable.h>
|
||||||
|
|
||||||
|
struct ncdc_api_sync_
|
||||||
|
{
|
||||||
|
ncdc_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 ncdc_api_sync_free(ncdc_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
ncdc_api_sync_t ncdc_api_sync_new(CURLM *curl, CURL *easy)
|
||||||
|
{
|
||||||
|
ncdc_api_sync_t ptr = calloc(1, sizeof(struct ncdc_api_sync_));
|
||||||
|
return_if_true(ptr == NULL, NULL);
|
||||||
|
|
||||||
|
ptr->easy = easy;
|
||||||
|
ptr->ref.cleanup = (cleanup_t)ncdc_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 ncdc_ref(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct curl_slist *ncdc_api_sync_list(ncdc_api_sync_t sync)
|
||||||
|
{
|
||||||
|
return_if_true(sync == NULL, NULL);
|
||||||
|
return sync->list;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *ncdc_api_sync_stream(ncdc_api_sync_t sync)
|
||||||
|
{
|
||||||
|
return_if_true(sync == NULL, NULL);
|
||||||
|
return sync->stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *ncdc_api_sync_data(ncdc_api_sync_t sync)
|
||||||
|
{
|
||||||
|
return_if_true(sync == NULL, NULL);
|
||||||
|
return sync->buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ncdc_api_sync_datalen(ncdc_api_sync_t sync)
|
||||||
|
{
|
||||||
|
return_if_true(sync == NULL, 0L);
|
||||||
|
return sync->bufferlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ncdc_api_sync_code(ncdc_api_sync_t sync)
|
||||||
|
{
|
||||||
|
return_if_true(sync == NULL, 0L);
|
||||||
|
return sync->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ncdc_api_sync_wait(ncdc_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 ncdc_api_sync_finish(ncdc_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);
|
||||||
|
}
|
280
src/ncdc.c
Normal file
280
src/ncdc.c
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
#include <ncdc/ncdc.h>
|
||||||
|
#include <ncdc/api.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* event base for libevent
|
||||||
|
*/
|
||||||
|
struct event_base *base = NULL;
|
||||||
|
struct event *stdin_ev = NULL;
|
||||||
|
struct event *timer = NULL;
|
||||||
|
|
||||||
|
/* we loop in a different thread
|
||||||
|
*/
|
||||||
|
static bool done = false;
|
||||||
|
static pthread_t looper;
|
||||||
|
|
||||||
|
char *ncdc_private_dir = NULL;
|
||||||
|
char *ncdc_config_file = NULL;
|
||||||
|
|
||||||
|
static GKeyFile *config = NULL;
|
||||||
|
|
||||||
|
/* global curl multi for API access
|
||||||
|
*/
|
||||||
|
CURLM *curl = NULL;
|
||||||
|
|
||||||
|
ncdc_api_t api = NULL;
|
||||||
|
|
||||||
|
static void handle_multi_info(void);
|
||||||
|
|
||||||
|
static void sighandler(int sig)
|
||||||
|
{
|
||||||
|
exit(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanup(void)
|
||||||
|
{
|
||||||
|
if (stdin_ev != NULL) {
|
||||||
|
event_del(stdin_ev);
|
||||||
|
event_free(stdin_ev);
|
||||||
|
stdin_ev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer != NULL) {
|
||||||
|
evtimer_del(timer);
|
||||||
|
event_free(timer);
|
||||||
|
timer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
done = true;
|
||||||
|
pthread_join(looper, NULL);
|
||||||
|
|
||||||
|
curl_multi_cleanup(curl);
|
||||||
|
curl = NULL;
|
||||||
|
|
||||||
|
event_base_free(base);
|
||||||
|
base = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stdin_handler(int sock, short what, void *data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcurl_socket_handler(int sock, short what, void *data)
|
||||||
|
{
|
||||||
|
int unused = 0;
|
||||||
|
|
||||||
|
if ((what & EV_READ) == EV_READ) {
|
||||||
|
curl_multi_socket_action(curl, sock, CURL_CSELECT_IN, &unused);
|
||||||
|
} else if ((what & EV_WRITE) == EV_WRITE) {
|
||||||
|
curl_multi_socket_action(curl, sock, CURL_CSELECT_OUT, &unused);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void timer_handler(int sock, short what, void *data)
|
||||||
|
{
|
||||||
|
int running = 0;
|
||||||
|
curl_multi_socket_action(curl, CURL_SOCKET_TIMEOUT, 0, &running);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mcurl_timer(CURLM *curl, long timeout, void *ptr)
|
||||||
|
{
|
||||||
|
int running = 0;
|
||||||
|
struct timeval tm;
|
||||||
|
|
||||||
|
if (timeout == -1) {
|
||||||
|
evtimer_del(timer);
|
||||||
|
} else if (timeout == 0) {
|
||||||
|
curl_multi_socket_action(curl, CURL_SOCKET_TIMEOUT, 0, &running);
|
||||||
|
} else if (timeout > 0) {
|
||||||
|
tm.tv_sec = timeout / 1000;
|
||||||
|
tm.tv_usec = (timeout % 1000) * 1000;
|
||||||
|
evtimer_add(timer, &tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mcurl_handler(CURL *easy, curl_socket_t s, int what, void *userp, void *socketp)
|
||||||
|
{
|
||||||
|
struct event *event = (struct event *)socketp;
|
||||||
|
|
||||||
|
if (what == CURL_POLL_REMOVE) {
|
||||||
|
if (event != NULL) {
|
||||||
|
event_del(event);
|
||||||
|
event_free(event);
|
||||||
|
curl_multi_assign(curl, s, NULL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int stat =
|
||||||
|
((what & CURL_POLL_IN) ? EV_READ : 0) |
|
||||||
|
((what & CURL_POLL_OUT) ? EV_WRITE : 0) |
|
||||||
|
EV_PERSIST
|
||||||
|
;
|
||||||
|
|
||||||
|
if (event == NULL) {
|
||||||
|
event = event_new(base, s, stat, mcurl_socket_handler, NULL);
|
||||||
|
if (event == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
curl_multi_assign(curl, s, event);
|
||||||
|
} else {
|
||||||
|
event_del(event);
|
||||||
|
event_assign(event, base, s, stat, mcurl_socket_handler, NULL);
|
||||||
|
event_add(event, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool init_everything(void)
|
||||||
|
{
|
||||||
|
evthread_use_pthreads();
|
||||||
|
|
||||||
|
base = event_base_new();
|
||||||
|
return_if_true(base == NULL, false);
|
||||||
|
|
||||||
|
/* add handle for STDIN, this info will then be fed to the UI
|
||||||
|
*/
|
||||||
|
stdin_ev = event_new(base, 0 /* stdin */, EV_READ|EV_PERSIST,
|
||||||
|
stdin_handler, NULL
|
||||||
|
);
|
||||||
|
return_if_true(stdin_ev == NULL, false);
|
||||||
|
event_add(stdin_ev, NULL);
|
||||||
|
|
||||||
|
timer = evtimer_new(base, timer_handler, NULL);
|
||||||
|
return_if_true(timer == NULL, false);
|
||||||
|
|
||||||
|
/* create curl multi and feed that to the API too
|
||||||
|
*/
|
||||||
|
curl = curl_multi_init();
|
||||||
|
return_if_true(curl == NULL, false);
|
||||||
|
|
||||||
|
curl_multi_setopt(curl, CURLMOPT_SOCKETFUNCTION, mcurl_handler);
|
||||||
|
curl_multi_setopt(curl, CURLMOPT_TIMERFUNCTION, mcurl_timer);
|
||||||
|
|
||||||
|
/* initialise event
|
||||||
|
*/
|
||||||
|
api = ncdc_api_new();
|
||||||
|
return_if_true(api == NULL, false);
|
||||||
|
|
||||||
|
ncdc_api_set_event_base(api, base);
|
||||||
|
ncdc_api_set_curl_multi(api, curl);
|
||||||
|
|
||||||
|
config = g_key_file_new();
|
||||||
|
return_if_true(config == NULL, false);
|
||||||
|
|
||||||
|
g_key_file_load_from_file(config, ncdc_config_file, 0, NULL);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_multi_info(void)
|
||||||
|
{
|
||||||
|
struct CURLMsg *msg = NULL;
|
||||||
|
int remain = 0;
|
||||||
|
|
||||||
|
/* check for finished multi curls
|
||||||
|
*/
|
||||||
|
msg = curl_multi_info_read(curl, &remain);
|
||||||
|
if (msg != NULL) {
|
||||||
|
if (remain <= 0) {
|
||||||
|
if (evtimer_pending(timer, NULL)) {
|
||||||
|
evtimer_del(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg->msg == CURLMSG_DONE) {
|
||||||
|
ncdc_api_signal(api, msg->easy_handle, msg->data.result);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
usleep(10 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *loop_thread(void *arg)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
ret = event_base_loop(base, EVLOOP_ONCE|EVLOOP_NONBLOCK);
|
||||||
|
if (ret < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_multi_info();
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ncdc_account_t account_from_config(void)
|
||||||
|
{
|
||||||
|
char const *email = NULL;
|
||||||
|
char const *password = NULL;
|
||||||
|
void *ptr = NULL;
|
||||||
|
|
||||||
|
email = g_key_file_get_string(config, "account", "email", NULL);
|
||||||
|
password = g_key_file_get_string(config, "account", "password", NULL);
|
||||||
|
|
||||||
|
return_if_true(email == NULL || password == NULL, NULL);
|
||||||
|
|
||||||
|
ptr = ncdc_account_new2(email, password);
|
||||||
|
ncdc_account_set_id(ptr, "@me");
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ac, char **av)
|
||||||
|
{
|
||||||
|
bool done = false;
|
||||||
|
|
||||||
|
atexit(cleanup);
|
||||||
|
|
||||||
|
signal(SIGINT, sighandler);
|
||||||
|
|
||||||
|
if (getenv("HOME") == NULL) {
|
||||||
|
fprintf(stderr, "your environment doesn't contain HOME; pls fix\n");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
asprintf(&ncdc_private_dir, "%s/.ncdc", getenv("HOME"));
|
||||||
|
if (mkdir(ncdc_private_dir, 0755) < 0) {
|
||||||
|
if (errno != EEXIST) {
|
||||||
|
fprintf(stderr, "failed to make %s: %s\n", ncdc_private_dir,
|
||||||
|
strerror(errno));
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asprintf(&ncdc_config_file, "%s/config", ncdc_private_dir);
|
||||||
|
|
||||||
|
if (!init_everything()) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
done = false;
|
||||||
|
if (pthread_create(&looper, NULL, loop_thread, &done)) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
ncdc_account_t a = account_from_config();
|
||||||
|
if (a == NULL) {
|
||||||
|
fprintf(stderr, "no account specified in config file; sho-sho!\n");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ncdc_api_authenticate(api, a)) {
|
||||||
|
fprintf(stderr, "authentication failed, wrong password?\n");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ncdc_api_userinfo(api, a, a)) {
|
||||||
|
fprintf(stderr, "failed to get user information\n");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
25
src/refable.c
Normal file
25
src/refable.c
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#include <ncdc/refable.h>
|
||||||
|
|
||||||
|
void *ncdc_ref(void *arg)
|
||||||
|
{
|
||||||
|
ncdc_refable_t *ptr = NULL;
|
||||||
|
|
||||||
|
return_if_true(arg == NULL,NULL);
|
||||||
|
|
||||||
|
ptr = (ncdc_refable_t *)arg;
|
||||||
|
++ptr->ref;
|
||||||
|
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncdc_unref(void *arg)
|
||||||
|
{
|
||||||
|
ncdc_refable_t *ptr = NULL;
|
||||||
|
|
||||||
|
return_if_true(arg == NULL,);
|
||||||
|
|
||||||
|
ptr = (ncdc_refable_t *)arg;
|
||||||
|
if ((--ptr->ref) <= 0 && ptr->cleanup != NULL) {
|
||||||
|
ptr->cleanup(arg);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user