From de6d02fd5879e6cccb08766759508f057d95e56b Mon Sep 17 00:00:00 2001 From: Florian Stinglmayr Date: Wed, 3 Jul 2019 21:14:23 +0200 Subject: [PATCH] suddenly friend listing support --- libdc/include/dc/account.h | 7 +++ libdc/include/dc/api.h | 10 ++-- libdc/include/dc/loop.h | 5 ++ libdc/src/account.c | 38 +++++++++++- libdc/src/api.c | 74 +++++++++++++++++++---- libdc/src/internal.h | 2 +- libdc/src/loop.c | 6 ++ ncdc/CMakeLists.txt | 1 + ncdc/include/ncdc/cmds.h | 6 +- ncdc/include/ncdc/ncdc.h | 8 +++ ncdc/src/cmds.c | 116 ++++++++++++++++++++++++++++++++++--- ncdc/src/friends.c | 52 +++++++++++++++++ ncdc/src/login.c | 33 ++++++----- ncdc/src/ncdc.c | 96 ++++++++++++++++++------------ ncdc/src/textview.c | 6 +- ncdc/src/util.c | 19 ++++++ 16 files changed, 400 insertions(+), 79 deletions(-) create mode 100644 ncdc/src/friends.c diff --git a/libdc/include/dc/account.h b/libdc/include/dc/account.h index adbaa65..30a49cf 100644 --- a/libdc/include/dc/account.h +++ b/libdc/include/dc/account.h @@ -3,6 +3,7 @@ #include #include +#include struct dc_account_; typedef struct dc_account_ *dc_account_t; @@ -31,4 +32,10 @@ void dc_account_set_token(dc_account_t a, char const *token); char const *dc_account_token(dc_account_t a); bool dc_account_has_token(dc_account_t a); +/* relationships + */ +void dc_account_set_friends(dc_account_t a, dc_account_t *ptr, size_t len); +dc_account_t dc_account_nthfriend(dc_account_t a, size_t i); +size_t dc_account_friends_size(dc_account_t a); + #endif diff --git a/libdc/include/dc/api.h b/libdc/include/dc/api.h index 9dc7754..7ac129e 100644 --- a/libdc/include/dc/api.h +++ b/libdc/include/dc/api.h @@ -45,7 +45,9 @@ bool dc_api_authenticate(dc_api_t api, dc_account_t account); /** * Retrieve basic user information for the given account. The first * parameter is the user account holding login info, while the second - * is the account you wish to retrieve information about. + * is the account you wish to retrieve information about. If you wish + * to retrieve information about the login user, simply pass the account + * twice. */ bool dc_api_get_userinfo(dc_api_t api, dc_account_t login, dc_account_t user); @@ -58,9 +60,9 @@ bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **guilds); /** - * Fetch a list of friends of the login account "login". Returns a GPtrArray - * of dc_account_t which automatically cleans itself up. + * Fetch a list of friends of the login account "login". The friends are stored + * within the login object. */ -bool dc_api_get_friends(dc_api_t api, dc_account_t login, GPtrArray **friends); +bool dc_api_get_friends(dc_api_t api, dc_account_t login); #endif diff --git a/libdc/include/dc/loop.h b/libdc/include/dc/loop.h index 6588dc7..51fc876 100644 --- a/libdc/include/dc/loop.h +++ b/libdc/include/dc/loop.h @@ -45,4 +45,9 @@ void dc_loop_add_api(dc_loop_t loop, dc_api_t api); */ bool dc_loop_once(dc_loop_t l); +/** + * Abort the whole event looping shenanigans + */ +void dc_loop_abort(dc_loop_t l); + #endif diff --git a/libdc/src/account.c b/libdc/src/account.c index 67be29c..63b4b9d 100644 --- a/libdc/src/account.c +++ b/libdc/src/account.c @@ -30,6 +30,10 @@ struct dc_account_ /* authentication token */ char *token; + + /* friends we have + */ + GPtrArray *friends; }; static void dc_account_free(dc_account_t ptr) @@ -44,6 +48,11 @@ static void dc_account_free(dc_account_t ptr) free(ptr->full); free(ptr->token); + if (ptr->friends != NULL) { + g_ptr_array_unref(ptr->friends); + ptr->friends = NULL; + } + free(ptr); } @@ -53,6 +62,10 @@ dc_account_t dc_account_new(void) ptr->ref.cleanup = (dc_cleanup_t)dc_account_free; + ptr->friends = g_ptr_array_new_with_free_func( + (GDestroyNotify)dc_unref + ); + return dc_ref(ptr); } @@ -144,7 +157,7 @@ void dc_account_update_full(dc_account_t a) free(a->full); a->full = NULL; - asprintf(&a->full, "%s/%s", + asprintf(&a->full, "%s#%s", (a->username != NULL ? a->username : ""), (a->discriminator != NULL ? a->discriminator : "") ); @@ -185,3 +198,26 @@ char const *dc_account_full_username(dc_account_t a) return_if_true(a == NULL, NULL); return a->full; } + +void dc_account_set_friends(dc_account_t a, dc_account_t *friends, size_t len) +{ + size_t i = 0; + return_if_true(a == NULL || a->friends == NULL,); + + g_ptr_array_remove_range(a->friends, 0, a->friends->len); + for (i = 0; i < len; i++) { + g_ptr_array_add(a->friends, friends[i]); + } +} + +dc_account_t dc_account_nthfriend(dc_account_t a, size_t i) +{ + return_if_true(a == NULL || a->friends == NULL, NULL); + return (dc_account_t)g_ptr_array_index(a->friends, i); +} + +size_t dc_account_friends_size(dc_account_t a) +{ + return_if_true(a == NULL || a->friends == NULL, 0); + return a->friends->len; +} diff --git a/libdc/src/api.c b/libdc/src/api.c index 2eddfaa..3d4f4e3 100644 --- a/libdc/src/api.c +++ b/libdc/src/api.c @@ -322,6 +322,33 @@ cleanup: return ret; } +static dc_account_t dc_api_account_from_json(json_t *j) +{ + dc_account_t user = dc_account_new(); + json_t *val = NULL; + + goto_if_true(!json_is_object(j), error); + + val = json_object_get(j, "username"); + goto_if_true(val == NULL || !json_is_string(val), error); + dc_account_set_username(user, json_string_value(val)); + + val = json_object_get(j, "discriminator"); + goto_if_true(val == NULL || !json_is_string(val), error); + dc_account_set_discriminator(user, json_string_value(val)); + + val = json_object_get(j, "id"); + goto_if_true(val == NULL || !json_is_string(val), error); + dc_account_set_id(user, json_string_value(val)); + + return user; + +error: + + dc_unref(user); + return NULL; +} + bool dc_api_get_userinfo(dc_api_t api, dc_account_t login, dc_account_t user) { @@ -333,7 +360,11 @@ bool dc_api_get_userinfo(dc_api_t api, dc_account_t login, return_if_true(login == NULL, false); return_if_true(user == NULL, false); - asprintf(&url, "users/%s", dc_account_id(user)); + if (user == login) { + url = strdup("users/@me"); + } else { + asprintf(&url, "users/%s", dc_account_id(user)); + } reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL); goto_if_true(reply == NULL, cleanup); @@ -360,25 +391,49 @@ cleanup: return ret; } -bool dc_api_get_friends(dc_api_t api, dc_account_t login, GPtrArray **friends) +bool dc_api_get_friends(dc_api_t api, dc_account_t login) { - char *url = NULL; - json_t *reply = NULL; + char const *url = "users/@me/relationships"; + json_t *reply = NULL, *c = NULL, *val = NULL; bool ret = false; + size_t i = 0; + GPtrArray *f = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref); return_if_true(api == NULL, false); return_if_true(login == NULL, false); - asprintf(&url, "users/%s/guilds", dc_account_id(login)); - reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL); goto_if_true(reply == NULL, cleanup); - dc_util_dump_json(reply); + goto_if_true(!json_is_array(reply), cleanup); + + json_array_foreach(reply, i, c) { + /* the return is an array of objects, with a "user" member + * type 1 is probably a friend + */ + val = json_object_get(c, "user"); + if (val == NULL) { + continue; + } + + dc_account_t a = dc_api_account_from_json(val); + if (a == NULL) { + continue; + } + g_ptr_array_add(f, a); + } + + if (f->len == 0) { + /* me_irl :-( + */ + } + + dc_account_set_friends(login, (dc_account_t*)f->pdata, f->len); ret = true; cleanup: + g_ptr_array_free(f, FALSE); json_decref(reply); reply = NULL; @@ -387,7 +442,7 @@ cleanup: bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out) { - char *url = NULL; + char const *url = "users/@me/guilds"; json_t *reply = NULL, *c = NULL, *val = NULL; size_t i = 0; bool ret = false; @@ -398,8 +453,6 @@ bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out) return_if_true(api == NULL, false); return_if_true(login == NULL, false); - asprintf(&url, "users/%s/guilds", dc_account_id(login)); - reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL); goto_if_true(reply == NULL, cleanup); @@ -426,7 +479,6 @@ bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out) cleanup: - free(url); json_decref(reply); if (guilds) { diff --git a/libdc/src/internal.h b/libdc/src/internal.h index 3959aa9..4229468 100644 --- a/libdc/src/internal.h +++ b/libdc/src/internal.h @@ -23,7 +23,7 @@ #include -#define DEBUG +//#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) diff --git a/libdc/src/loop.c b/libdc/src/loop.c index f25aa99..ff68b29 100644 --- a/libdc/src/loop.c +++ b/libdc/src/loop.c @@ -191,6 +191,12 @@ void dc_loop_add_api(dc_loop_t l, dc_api_t a) g_ptr_array_add(l->apis, p); } +void dc_loop_abort(dc_loop_t l) +{ + return_if_true(l == NULL || l->base == NULL,); + event_base_loopbreak(l->base); +} + bool dc_loop_once(dc_loop_t l) { return_if_true(l == NULL, false); diff --git a/ncdc/CMakeLists.txt b/ncdc/CMakeLists.txt index 71a6612..83ee8df 100644 --- a/ncdc/CMakeLists.txt +++ b/ncdc/CMakeLists.txt @@ -16,6 +16,7 @@ SET(SOURCES "src/cmds.c" "src/config.c" "src/emacs.c" + "src/friends.c" "src/input.c" "src/login.c" "src/mainwindow.c" diff --git a/ncdc/include/ncdc/cmds.h b/ncdc/include/ncdc/cmds.h index 0708ea4..a4956c6 100644 --- a/ncdc/include/ncdc/cmds.h +++ b/ncdc/include/ncdc/cmds.h @@ -14,9 +14,13 @@ typedef struct { extern ncdc_commands_t cmds[]; +bool ncdc_dispatch_init(void); +bool ncdc_dispatch_deinit(void); + bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s); -bool ncdc_cmd_quit(ncdc_mainwindow_t n, size_t ac, wchar_t **av); +bool ncdc_cmd_friends(ncdc_mainwindow_t n, size_t ac, wchar_t **av); bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av); +bool ncdc_cmd_quit(ncdc_mainwindow_t n, size_t ac, wchar_t **av); #endif diff --git a/ncdc/include/ncdc/ncdc.h b/ncdc/include/ncdc/ncdc.h index f5260d2..a51d0ac 100644 --- a/ncdc/include/ncdc/ncdc.h +++ b/ncdc/include/ncdc/ncdc.h @@ -44,12 +44,20 @@ struct ncdc_account_ { typedef struct ncdc_account_ *ncdc_account_t; extern GHashTable *accounts; +extern dc_account_t current_account; + +extern dc_api_t api; + extern char *ncdc_private_dir; extern void *config; +extern void *mainwindow; + +void exit_main(void); int strwidth(char const *string); char *read_char(FILE *stream); +int aswprintf(wchar_t **buffer, wchar_t const *fmt, ...); char *w_convert(wchar_t const *w); wchar_t* wcsndup(const wchar_t* string, size_t maxlen); size_t w_strlenv(wchar_t **s); diff --git a/ncdc/src/cmds.c b/ncdc/src/cmds.c index 81a3d1b..419849e 100644 --- a/ncdc/src/cmds.c +++ b/ncdc/src/cmds.c @@ -1,36 +1,136 @@ #include ncdc_commands_t cmds[] = { + { L"friends", ncdc_cmd_friends }, { L"login", ncdc_cmd_login }, { L"quit", ncdc_cmd_quit }, { NULL, NULL } }; +static GQueue *queue = NULL; +static pthread_t thr; +static pthread_mutex_t mtx; +static pthread_cond_t cnd; + +typedef struct { + ncdc_commands_t *cmd; + size_t ac; + wchar_t **av; + ncdc_mainwindow_t mainwindow; +} queue_item; + +static void *async_dispatcher(void *arg) +{ + queue_item *item = NULL; + + while (true) { + pthread_mutex_lock(&mtx); + pthread_cond_wait(&cnd, &mtx); + + /* of course it is "get_length", and not "size" or + * "length", or something else. srsly. + */ + while (g_queue_get_length(queue) > 0) { + item = g_queue_pop_head(queue); + if (item == NULL) { + /* end of working orders + */ + pthread_mutex_unlock(&mtx); + printf("got exit\n"); + return NULL; + } else { + /* call the handler + */ + item->cmd->handler(item->mainwindow, item->ac, item->av); + + w_strfreev(item->av); + free(item); + } + } + + pthread_mutex_unlock(&mtx); + } + + return NULL; +} + +bool ncdc_dispatch_init(void) +{ + return_if_true(queue != NULL, true); + + queue = g_queue_new(); + return_if_true(queue == NULL, false); + + pthread_mutex_init(&mtx, NULL); + pthread_cond_init(&cnd, NULL); + + pthread_create(&thr, NULL, async_dispatcher, NULL); + + return true; +} + +bool ncdc_dispatch_deinit(void) +{ + return_if_true(queue == NULL, true); + + pthread_mutex_lock(&mtx); + g_queue_push_tail(queue, NULL); + pthread_cond_signal(&cnd); + pthread_mutex_unlock(&mtx); + + pthread_join(thr, NULL); + + pthread_mutex_destroy(&mtx); + pthread_cond_destroy(&cnd); + + g_queue_free(queue); + queue = NULL; + + return true; +} + bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s) { wchar_t **tokens = NULL; - size_t size = 0; size_t i = 0; - bool ret = false; + ncdc_commands_t *it = NULL; + queue_item *item = NULL; tokens = w_tokenise(s); return_if_true(tokens == NULL, false); - size = w_strlenv(tokens); - for (i = 0; cmds[i].name != NULL; i++) { if (wcscmp(cmds[i].name, tokens[0]) == 0) { - ret = cmds[i].handler(n, size, tokens); + it = cmds+i; break; } } - w_strfreev(tokens); + if (it == NULL) { + /* no such command + */ + LOG(n, L"error: no such command \"%ls\"", tokens[0]); + w_strfreev(tokens); + return false; + } - return ret; + item = calloc(1, sizeof(queue_item)); + + item->ac = w_strlenv(tokens); + item->av = tokens; + item->cmd = it; + item->mainwindow = n; + + pthread_mutex_lock(&mtx); + g_queue_push_tail(queue, item); + pthread_cond_broadcast(&cnd); + pthread_mutex_unlock(&mtx); + + return true; } bool ncdc_cmd_quit(ncdc_mainwindow_t n, size_t ac, wchar_t **av) { - exit(0); + exit_main(); + return true; } diff --git a/ncdc/src/friends.c b/ncdc/src/friends.c new file mode 100644 index 0000000..0698e38 --- /dev/null +++ b/ncdc/src/friends.c @@ -0,0 +1,52 @@ +#include +#include + +static bool +ncdc_cmd_friends_list(ncdc_mainwindow_t n, size_t ac, wchar_t **av) +{ + bool ret = false; + size_t i = 0; + + ret = dc_api_get_friends(api, current_account); + if (!ret) { + LOG(n, L"friends: list: failed to fetch your friends"); + return false; + } + + LOG(n, L"/FRIENDS list"); + for (i = 0; i < dc_account_friends_size(current_account); i++) { + dc_account_t acc = dc_account_nthfriend(current_account, i); + LOG(n, L" %s", dc_account_full_username(acc)); + } + LOG(n, L"End of /FRIENDS list"); + + return true; +} + +bool ncdc_cmd_friends(ncdc_mainwindow_t n, size_t ac, wchar_t **av) +{ + wchar_t *subcmd = NULL; + + if (ac <= 1) { + return false; + } + + if (current_account == NULL || + !dc_account_has_token(current_account)) { + LOG(n, L"friends: not logged in"); + return false; + } + + subcmd = av[1]; + + --ac; + ++av; + + if (wcscmp(subcmd, L"list") == 0) { + return ncdc_cmd_friends_list(n, ac, av); + } else { + return false; + } + + return true; +} diff --git a/ncdc/src/login.c b/ncdc/src/login.c index a44f380..e84dee8 100644 --- a/ncdc/src/login.c +++ b/ncdc/src/login.c @@ -6,7 +6,6 @@ bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av) { char *arg = NULL; bool ret = false; - ncdc_account_t ptr = NULL; dc_account_t acc = NULL; goto_if_true(ac <= 1, cleanup); @@ -14,23 +13,29 @@ bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av) arg = w_convert(av[1]); goto_if_true(arg == NULL, cleanup); - if (g_hash_table_lookup(accounts, arg) != NULL) { - LOG(n, L"login: %ls: this account is already logged in", av[1]); - goto cleanup; - } - - acc = ncdc_config_account(config, arg); + acc = g_hash_table_lookup(accounts, arg); if (acc == NULL) { - LOG(n, L"login: %ls: no such account in configuration", av[1]); + acc = ncdc_config_account(config, arg); + if (acc == NULL) { + LOG(n, L"login: %ls: no such account in configuration", av[1]); + goto cleanup; + } + + g_hash_table_insert(accounts, arg, acc); + } else { + if (dc_account_has_token(acc)) { + LOG(n, L"login: %ls: this account is already logged in", av[1]); + goto cleanup; + } + } + + if (!dc_api_authenticate(api, acc)) { + LOG(n, L"login: %ls: authentication failed; wrong password?", av[1]); goto cleanup; } - ptr = calloc(1, sizeof(struct ncdc_account_)); - goto_if_true(ptr == NULL, cleanup); - - ptr->account = acc; - g_hash_table_insert(accounts, arg, ptr); - + current_account = acc; + LOG(n, L"login: %ls: authentication successful", av[1]); ret = true; cleanup: diff --git a/ncdc/src/ncdc.c b/ncdc/src/ncdc.c index 70a5e57..4fb29bb 100644 --- a/ncdc/src/ncdc.c +++ b/ncdc/src/ncdc.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -15,53 +16,41 @@ ncdc_mainwindow_t mainwin = NULL; /* we loop in a different thread */ -static bool done = false; +bool main_done = false; +static pthread_t event_thread; +static struct event_base *base = NULL; /* all the accounts we have logged into */ GHashTable *accounts = NULL; +dc_account_t current_account = NULL; char *ncdc_private_dir = NULL; void *config = NULL; dc_loop_t loop = NULL; + +/* API handle we use for everything + */ dc_api_t api = NULL; -static void ncdc_account_free(void *ptr) -{ - ncdc_account_t a = (ncdc_account_t)ptr; - - return_if_true(ptr == NULL,); - - if (a->friends != NULL) { - g_ptr_array_unref(a->friends); - } - - if (a->guilds != NULL) { - g_ptr_array_unref(a->guilds); - } - - dc_unref(a->account); - free(ptr); -} - -static void sighandler(int sig) -{ - endwin(); - exit(3); -} - static void cleanup(void) { endwin(); + main_done = true; + dc_loop_abort(loop); + pthread_join(event_thread, NULL); + if (stdin_ev != NULL) { event_del(stdin_ev); event_free(stdin_ev); stdin_ev = NULL; } - done = true; + event_base_loopbreak(base); + event_base_free(base); + base = NULL; if (accounts != NULL) { g_hash_table_unref(accounts); @@ -75,6 +64,12 @@ static void cleanup(void) dc_unref(mainwin); } +static void sighandler(int sig) +{ + cleanup(); + exit(3); +} + static void stdin_handler(int sock, short what, void *data) { if ((what & EV_READ) == EV_READ) { @@ -82,18 +77,38 @@ static void stdin_handler(int sock, short what, void *data) } } +static void *looper(void *arg) +{ + while (!main_done) { + if (!dc_loop_once(loop)) { + break; + } + + usleep(10 * 1000); + } + + return NULL; +} + static bool init_everything(void) { + int ret = 0; + evthread_use_pthreads(); setlocale(LC_CTYPE, ""); + return_if_true(!ncdc_dispatch_init(), false); + + base = event_base_new(); + return_if_true(base == NULL, false); + loop = dc_loop_new(); return_if_true(loop == NULL, false); /* add handle for STDIN, this info will then be fed to the UI */ - stdin_ev = event_new(dc_loop_event_base(loop), 0 /* stdin */, + stdin_ev = event_new(base, 0 /* stdin */, EV_READ|EV_PERSIST, stdin_handler, NULL ); @@ -111,18 +126,25 @@ static bool init_everything(void) return_if_true(config == NULL, false); accounts = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, ncdc_account_free + g_free, dc_unref ); return_if_true(accounts == NULL, false); + ret = pthread_create(&event_thread, NULL, looper, NULL); + return_if_true(ret != 0, false); + return true; } +void exit_main(void) +{ + main_done = true; + event_base_loopbreak(base); +} + int main(int ac, char **av) { - bool done = false; - - atexit(cleanup); + int ret = 0; signal(SIGINT, sighandler); @@ -144,8 +166,6 @@ int main(int ac, char **av) return 3; } - done = false; - initscr(); noecho(); nonl(); @@ -165,17 +185,19 @@ int main(int ac, char **av) return 3; } - while (!done) { + while (!main_done) { ncdc_mainwindow_refresh(mainwin); doupdate(); - if (!dc_loop_once(loop)) { + ret = event_base_loop(base, EVLOOP_ONCE|EVLOOP_NONBLOCK); + if (ret < 0) { break; } + + usleep(10*1000); } - dc_unref(mainwin); - endwin(); + cleanup(); return 0; } diff --git a/ncdc/src/textview.c b/ncdc/src/textview.c index 3c0cf19..9a7163e 100644 --- a/ncdc/src/textview.c +++ b/ncdc/src/textview.c @@ -40,8 +40,10 @@ void ncdc_textview_append(ncdc_textview_t v, wchar_t const *w) while ((p = wcschr(p, '\n')) != NULL) { wchar_t *dup = wcsndup(p, p - last); - g_ptr_array_add(v->par, dup); - last = p; + if (dup != NULL) { + g_ptr_array_add(v->par, dup); + last = p; + } } g_ptr_array_add(v->par, wcsdup(last)); diff --git a/ncdc/src/util.c b/ncdc/src/util.c index 70c0a71..6890fd8 100644 --- a/ncdc/src/util.c +++ b/ncdc/src/util.c @@ -14,6 +14,25 @@ wchar_t const *w_next_word(wchar_t const *w, ssize_t len) return w+i; } +int aswprintf(wchar_t **buffer, wchar_t const *fmt, ...) +{ + size_t sz = 0; + FILE *f = NULL; + va_list lst; + + f = open_wmemstream(buffer, &sz); + if (f == NULL) { + return -1; + } + + va_start(lst, fmt); + vfwprintf(f, fmt, lst); + va_end(lst); + + fclose(f); + return sz; +} + wchar_t* wcsndup(const wchar_t* string, size_t maxlen) { size_t n = wcsnlen(string, maxlen) + 1;