From 8972538d1334a0a50df53bd2a2181504b0646e84 Mon Sep 17 00:00:00 2001 From: Florian Stinglmayr Date: Sat, 15 Jun 2019 21:33:46 +0200 Subject: [PATCH] ncdc - discord client --- CMakeLists.txt | 47 ++++++ include/ncdc/account.h | 25 +++ include/ncdc/api.h | 34 ++++ include/ncdc/apisync.h | 20 +++ include/ncdc/ncdc.h | 31 ++++ include/ncdc/refable.h | 16 ++ src/account.c | 121 ++++++++++++++ src/api.c | 363 +++++++++++++++++++++++++++++++++++++++++ src/apisync.c | 128 +++++++++++++++ src/ncdc.c | 280 +++++++++++++++++++++++++++++++ src/refable.c | 25 +++ 11 files changed, 1090 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 include/ncdc/account.h create mode 100644 include/ncdc/api.h create mode 100644 include/ncdc/apisync.h create mode 100644 include/ncdc/ncdc.h create mode 100644 include/ncdc/refable.h create mode 100644 src/account.c create mode 100644 src/api.c create mode 100644 src/apisync.c create mode 100644 src/ncdc.c create mode 100644 src/refable.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..471ad5c --- /dev/null +++ b/CMakeLists.txt @@ -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} + ) diff --git a/include/ncdc/account.h b/include/ncdc/account.h new file mode 100644 index 0000000..1916c86 --- /dev/null +++ b/include/ncdc/account.h @@ -0,0 +1,25 @@ +#ifndef NCDC_ACCOUNT_H +#define NCDC_ACCOUNT_H + +#include + +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 diff --git a/include/ncdc/api.h b/include/ncdc/api.h new file mode 100644 index 0000000..b5b6073 --- /dev/null +++ b/include/ncdc/api.h @@ -0,0 +1,34 @@ +#ifndef NCDC_API_H +#define NCDC_API_H + +#include + +#include +#include + +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 diff --git a/include/ncdc/apisync.h b/include/ncdc/apisync.h new file mode 100644 index 0000000..53a935d --- /dev/null +++ b/include/ncdc/apisync.h @@ -0,0 +1,20 @@ +#ifndef NCDC_API_ASYNC_H +#define NCDC_API_ASYNC_H + +#include + +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 diff --git a/include/ncdc/ncdc.h b/include/ncdc/ncdc.h new file mode 100644 index 0000000..4c00cfe --- /dev/null +++ b/include/ncdc/ncdc.h @@ -0,0 +1,31 @@ +#ifndef NCDC_H +#define NCDC_H + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +//#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 diff --git a/include/ncdc/refable.h b/include/ncdc/refable.h new file mode 100644 index 0000000..8dca10c --- /dev/null +++ b/include/ncdc/refable.h @@ -0,0 +1,16 @@ +#ifndef NCDC_REFABLE_H +#define NCDC_REFABLE_H + +#include + +typedef void (*cleanup_t)(void *); + +typedef struct { + int ref; + cleanup_t cleanup; +} ncdc_refable_t; + +void *ncdc_ref(void *); +void ncdc_unref(void *); + +#endif diff --git a/src/account.c b/src/account.c new file mode 100644 index 0000000..786594d --- /dev/null +++ b/src/account.c @@ -0,0 +1,121 @@ +#include +#include + +#include +#include +#include + +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; +} diff --git a/src/api.c b/src/api.c new file mode 100644 index 0000000..3cb2390 --- /dev/null +++ b/src/api.c @@ -0,0 +1,363 @@ +#include +#include + +#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("D: %s\n", data); break; + case CURLINFO_DATA_OUT: printf("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; +} diff --git a/src/apisync.c b/src/apisync.c new file mode 100644 index 0000000..9fb7bce --- /dev/null +++ b/src/apisync.c @@ -0,0 +1,128 @@ +#include +#include + +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); +} diff --git a/src/ncdc.c b/src/ncdc.c new file mode 100644 index 0000000..e17afea --- /dev/null +++ b/src/ncdc.c @@ -0,0 +1,280 @@ +#include +#include + +#include +#include + +/* 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; +} diff --git a/src/refable.c b/src/refable.c new file mode 100644 index 0000000..32aeb17 --- /dev/null +++ b/src/refable.c @@ -0,0 +1,25 @@ +#include + +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); + } +}