diff options
Diffstat (limited to 'src/daemon/purple.c')
-rw-r--r-- | src/daemon/purple.c | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/src/daemon/purple.c b/src/daemon/purple.c new file mode 100644 index 0000000..882ea61 --- /dev/null +++ b/src/daemon/purple.c @@ -0,0 +1,254 @@ +/* + * Usurpation – server daemon main logic + * + * Copyright (C) 2019 Gediminas Jakutis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 + * of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <libpurple/purple.h> +#include <glib.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include "purple_private.h" +#include "settings.h" + +static PurpleConversationUiOps conv_uiops = +{ + NULL, /* create_conversation */ + NULL, /* destroy_conversation */ + NULL, /* write_chat */ + NULL, /* write_im */ + NULL, /* write_conv */ + NULL, /* chat_add_users */ + NULL, /* chat_rename_user */ + NULL, /* chat_remove_users */ + NULL, /* chat_update_user */ + NULL, /* present */ + NULL, /* has_focus */ + NULL, /* send_confirm */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +static PurpleEventLoopUiOps eventloops = +{ + g_timeout_add, + g_source_remove, + glib_input_add, + g_source_remove, + NULL, + g_timeout_add_seconds, + + /* padding */ + NULL, + NULL, + NULL +}; + +static gboolean purple_glib_io(GIOChannel *source, GIOCondition cond, gpointer data) +{ + struct glib_io *bloc = data; + PurpleInputCondition pio_cond = 0; + + if (cond & (G_IO_IN | G_IO_HUP | G_IO_ERR)) { + pio_cond |= PURPLE_INPUT_READ; + } + + if (cond & (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + pio_cond |= PURPLE_INPUT_WRITE; + } + + bloc->func(bloc->data, g_io_channel_unix_get_fd(source), pio_cond); + + return 1; +} + +static guint glib_input_add(gint fd, PurpleInputCondition pio_cond, PurpleInputFunction func, gpointer data) +{ + struct glib_io *bloc; + GIOChannel *channel; + GIOCondition cond = 0; + + bloc = g_new0(struct glib_io, 1); + bloc->func = func; + bloc->data = data; + + if (pio_cond & PURPLE_INPUT_READ) { + cond |= G_IO_IN | G_IO_HUP | G_IO_ERR; + } + + if (pio_cond & PURPLE_INPUT_WRITE) { + cond |= G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL; + } + + channel = g_io_channel_unix_new(fd); + bloc->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond, purple_glib_io, bloc, g_free); + g_io_channel_unref(channel); + + return bloc->result; +} + +/* TODO: semi-stub */ +static void iface_init(void) +{ + purple_conversations_set_ui_ops(&conv_uiops); +} + +static void pthread_mutex_unlock_thunk(void *arg) +{ + pthread_mutex_t *mutex = arg; + + (void) pthread_mutex_unlock(mutex); +} + +static void purple_account_destroy_thunk(void *arg) +{ + PurpleAccount *account = arg; + + purple_account_destroy(account); +} + +static void purple_core_destroy(void *arg) +{ + (void) arg; + + purple_timeout_add(0, purple_core_quit_cb, NULL); +} + +static void g_main_loop_unref_thunk(void *arg) +{ + GMainLoop *loop = arg; + + g_main_loop_unref(loop); +} + +static void g_main_loop_quit_thunk(void *arg) +{ + GMainLoop *loop = arg; + + g_main_loop_quit(loop); +} + + +static void *purple_spawn(void *disregard) +{ + GMainLoop *loop; + char *progname = NULL; + char *user = NULL; + char *password = NULL; + char *proto = NULL; + + (void) disregard; + + pthread_cleanup_push(pthread_mutex_unlock_thunk, &state.mutex); + + progname = setting_progname(); + pthread_cleanup_push(free, progname); + + user = setting_im_user(); + pthread_cleanup_push(free, user); + + password = setting_im_password(); + pthread_cleanup_push(free, password); + + proto = setting_im_proto(); + pthread_cleanup_push(free, proto); + + loop = g_main_loop_new(NULL, FALSE); + pthread_cleanup_push(g_main_loop_unref_thunk, loop); + + /* avoid an unholy army of libpurple's DNS resolver process zombies */ + signal(SIGCHLD, SIG_IGN); + + /* do not actually save anything about the account through libpurple */ + purple_util_set_user_dir("/dev/full"); + + /* + * TODO: + * we do allow verbosity here yet + * use this once we do: + * purple_debug_set_enabled(setting_verbose()); + */ + purple_debug_set_enabled(0); + + purple_core_set_ui_ops(&core_uiops); + purple_eventloop_set_ui_ops(&eventloops); + purple_core_init(progname); + purple_prefs_load(); + + pthread_cleanup_push(purple_core_destroy, NULL); + + state.account = purple_account_new(user, proto); + purple_account_set_password(state.account, password); + purple_account_set_enabled(state.account, progname, TRUE); + + pthread_cleanup_push(purple_account_destroy_thunk, state.account); + + g_main_loop_run(loop); + pthread_cleanup_push(g_main_loop_quit_thunk, loop); + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + pthread_exit(NULL); +} + +/* + * The following functions are exported and thus, can be wildly called by other + * modules and thus, are guarded by a mutex to make kind of a singleton, as it + * is NOT safe to access libpurple from more than one context / thread while + * using the same configuration (read: confdir). + */ + +int purple_init(void) +{ + int ret = 0; + + /* aquire the """singleton""" mutex */ + if (pthread_mutex_trylock(&state.mutex)) { + /* TODO: use proper error numbers */ + ret = 1; + } else { + if ((ret = pthread_create(&state.purple, NULL, purple_spawn, NULL))) { + /* thread creation failed, release the mutex */ + pthread_mutex_unlock(&state.mutex); + } + } + + return ret; +} + +void purple_close(void) +{ + + if (pthread_mutex_trylock(&state.mutex) == EBUSY) { + pthread_cancel(state.purple); + pthread_join(state.purple, NULL); + } +} |