diff --git a/degesch.c b/degesch.c index d1faae4..de093c6 100644 --- a/degesch.c +++ b/degesch.c @@ -1013,10 +1013,18 @@ struct server { struct app_context *ctx; ///< Application context + char *name; ///< Server identifier + struct buffer *buffer; ///< The buffer for this server + + // Configuration: + + struct config_item_ *config; ///< Configuration root bool reconnect; ///< Whether to reconnect on conn. fail. unsigned long reconnect_delay; ///< Reconnect delay in seconds bool manual_disconnect; ///< Don't reconnect + // Connection: + enum server_state state; ///< Connection state struct connector *connector; ///< Connection establisher @@ -1030,8 +1038,7 @@ struct server // TODO: an output queue to prevent excess floods (this will be needed // especially for away status polling) - char *name; ///< Server identifier - struct buffer *buffer; ///< The buffer for this server + // IRC: struct str_map irc_users; ///< IRC user data struct str_map irc_channels; ///< IRC channel data @@ -1055,6 +1062,8 @@ static void irc_initiate_connect (struct server *s); static void server_init (struct server *self, struct poller *poller) { + memset (self, 0, sizeof *self); + self->socket = -1; str_init (&self->read_buffer); self->state = IRC_DISCONNECTED; @@ -1111,6 +1120,13 @@ server_free (struct server *self) str_map_free (&self->irc_buffer_map); } +static void +server_destroy (void *self) +{ + server_free (self); + free (self); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct app_context @@ -1122,7 +1138,7 @@ struct app_context bool no_colors; ///< Colour output mode bool isolate_buffers; ///< Isolate global/server buffers - struct server server; ///< Our only server so far + struct str_map servers; ///< Our servers // Events: @@ -1138,14 +1154,13 @@ struct app_context struct buffer *buffers; ///< All our buffers in order struct buffer *buffers_tail; ///< The tail of our buffers + struct buffer *global_buffer; ///< The global buffer + struct buffer *current_buffer; ///< The current buffer struct buffer *last_buffer; ///< Last used buffer // TODO: make buffer names fully unique like weechat does struct str_map buffers_by_name; ///< Buffers by name - struct buffer *global_buffer; ///< The global buffer - struct buffer *current_buffer; ///< The current buffer - // TODO: So that we always output proper date change messages time_t last_displayed_msg_time; ///< Time of last displayed message @@ -1171,9 +1186,9 @@ app_context_init (struct app_context *self) config_init (&self->config); poller_init (&self->poller); - server_init (&self->server, &self->poller); - self->server.ctx = self; - self->server.name = xstrdup ("server"); + str_map_init (&self->servers); + self->servers.free = server_destroy; + self->servers.key_xfrm = irc_strxfrm; str_map_init (&self->buffers_by_name); self->buffers_by_name.key_xfrm = irc_strxfrm; @@ -1213,7 +1228,7 @@ app_context_free (struct app_context *self) buffer_destroy (iter); str_map_free (&self->buffers_by_name); - server_free (&self->server); + str_map_free (&self->servers); poller_free (&self->poller); iconv_close (self->latin1_to_utf8); @@ -1415,9 +1430,9 @@ register_config_modules (struct app_context *ctx) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static const char * -get_config_string (struct app_context *ctx, const char *key) +get_config_string (struct config_item_ *root, const char *key) { - struct config_item_ *item = config_item_get (ctx->config.root, key, NULL); + struct config_item_ *item = config_item_get (root, key, NULL); hard_assert (item); if (item->type == CONFIG_ITEM_NULL) return NULL; @@ -1426,9 +1441,10 @@ get_config_string (struct app_context *ctx, const char *key) } static bool -set_config_string (struct app_context *ctx, const char *key, const char *value) +set_config_string + (struct config_item_ *root, const char *key, const char *value) { - struct config_item_ *item = config_item_get (ctx->config.root, key, NULL); + struct config_item_ *item = config_item_get (root, key, NULL); hard_assert (item); struct str s; @@ -1448,17 +1464,17 @@ set_config_string (struct app_context *ctx, const char *key, const char *value) } static int64_t -get_config_integer (struct app_context *ctx, const char *key) +get_config_integer (struct config_item_ *root, const char *key) { - struct config_item_ *item = config_item_get (ctx->config.root, key, NULL); + struct config_item_ *item = config_item_get (root, key, NULL); hard_assert (item && item->type == CONFIG_ITEM_INTEGER); return item->value.integer; } static bool -get_config_boolean (struct app_context *ctx, const char *key) +get_config_boolean (struct config_item_ *root, const char *key) { - struct config_item_ *item = config_item_get (ctx->config.root, key, NULL); + struct config_item_ *item = config_item_get (root, key, NULL); hard_assert (item && item->type == CONFIG_ITEM_BOOLEAN); return item->value.boolean; } @@ -1687,7 +1703,7 @@ init_attribute (struct app_context *ctx, int id, const char *default_) #undef XX }; - const char *user = get_config_string (ctx, table[id]); + const char *user = get_config_string (ctx->config.root, table[id]); if (user) ctx->attrs[id] = xstrdup (user); else @@ -2483,19 +2499,19 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer) if (buffer->user) str_map_set (&s->irc_buffer_map, buffer->user->nickname, NULL); - str_map_set (&ctx->buffers_by_name, buffer->name, NULL); - LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); - buffer_destroy (buffer); - - if (buffer == ctx->last_buffer) - ctx->last_buffer = NULL; - // It's not a good idea to remove these buffers, but it's even a worse // one to leave the pointers point to invalid memory if (buffer == ctx->global_buffer) ctx->global_buffer = NULL; - if (buffer == ctx->server.buffer) - ctx->server.buffer = NULL; + if (buffer->type == BUFFER_SERVER) + buffer->server->buffer = NULL; + + if (buffer == ctx->last_buffer) + ctx->last_buffer = NULL; + + str_map_set (&ctx->buffers_by_name, buffer->name, NULL); + LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); + buffer_destroy (buffer); refresh_prompt (ctx); } @@ -2635,19 +2651,11 @@ buffer_get_index (struct app_context *ctx, struct buffer *buffer) static void init_buffers (struct app_context *ctx) { - // At the moment we have only two global everpresent buffers struct buffer *global = ctx->global_buffer = buffer_new (); - struct buffer *server = ctx->server.buffer = buffer_new (); - global->type = BUFFER_GLOBAL; global->name = xstrdup (PROGRAM_NAME); - server->type = BUFFER_SERVER; - server->name = xstrdup ("server"); - server->server = &ctx->server; - buffer_add (ctx, global); - buffer_add (ctx, server); } // --- Users, channels --------------------------------------------------------- @@ -2811,12 +2819,12 @@ irc_initialize_ssl_ctx (struct server *s, struct error **e) { // XXX: maybe we should call SSL_CTX_set_options() for some workarounds - bool verify = get_config_boolean (s->ctx, "server.ssl_verify"); + bool verify = get_config_boolean (s->config, "ssl_verify"); if (!verify) SSL_CTX_set_verify (s->ssl_ctx, SSL_VERIFY_NONE, NULL); - const char *ca_file = get_config_string (s->ctx, "server.ca_file"); - const char *ca_path = get_config_string (s->ctx, "server.ca_path"); + const char *ca_file = get_config_string (s->config, "ssl_ca_file"); + const char *ca_path = get_config_string (s->config, "ssl_ca_path"); struct error *error = NULL; if (ca_file || ca_path) @@ -2866,7 +2874,7 @@ irc_initialize_ssl (struct server *s, struct error **e) if (!s->ssl) goto error_ssl_2; - const char *ssl_cert = get_config_string (s->ctx, "server.ssl_cert"); + const char *ssl_cert = get_config_string (s->config, "ssl_cert"); if (ssl_cert) { char *path = resolve_config_filename (ssl_cert); @@ -3011,8 +3019,19 @@ irc_destroy_connector (struct server *s) static void try_finish_quit (struct app_context *ctx) { - // TODO: multiserver - if (ctx->quitting && !irc_is_connected (&ctx->server)) + if (!ctx->quitting) + return; + + struct str_map_iter iter; + str_map_iter_init (&iter, &ctx->servers); + + bool disconnected_all = true; + struct server *s; + while ((s = str_map_iter_next (&iter))) + if (irc_is_connected (s)) + disconnected_all = false; + + if (disconnected_all) ctx->polling = false; } @@ -3025,13 +3044,18 @@ initiate_quit (struct app_context *ctx) buffer_send_status (ctx, ctx->global_buffer, "Shutting down"); // Initiate a connection close - // TODO: multiserver - struct server *s = &ctx->server; - if (irc_is_connected (s)) - // XXX: when we go async, we'll have to flush output buffers first - irc_shutdown (s); - else if (s->state == IRC_CONNECTING) - irc_destroy_connector (s); + struct str_map_iter iter; + str_map_iter_init (&iter, &ctx->servers); + + struct server *s; + while ((s = str_map_iter_next (&iter))) + { + if (irc_is_connected (s)) + // XXX: when we go async, we'll have to flush output buffers first + irc_shutdown (s); + else if (s->state == IRC_CONNECTING) + irc_destroy_connector (s); + } ctx->quitting = true; try_finish_quit (ctx); @@ -3248,9 +3272,9 @@ end: static void irc_register (struct server *s) { - const char *nickname = get_config_string (s->ctx, "server.nickname"); - const char *username = get_config_string (s->ctx, "server.username"); - const char *realname = get_config_string (s->ctx, "server.realname"); + const char *nickname = get_config_string (s->config, "nickname"); + const char *username = get_config_string (s->config, "username"); + const char *realname = get_config_string (s->config, "realname"); // These are filled automatically if needed hard_assert (nickname && username && realname); @@ -3268,7 +3292,7 @@ irc_finish_connection (struct server *s, int socket) s->socket = socket; struct error *e = NULL; - bool use_ssl = get_config_boolean (ctx, "server.ssl"); + bool use_ssl = get_config_boolean (s->config, "ssl"); if (use_ssl && !irc_initialize_ssl (s, &e)) { buffer_send_error (ctx, s->buffer, "Connection failed: %s", e->message); @@ -3378,13 +3402,13 @@ irc_initiate_connect_socks (struct server *s, { struct app_context *ctx = s->ctx; - const char *socks_host = get_config_string (ctx, "server.socks_host"); - int64_t socks_port_int = get_config_integer (ctx, "server.socks_port"); + const char *socks_host = get_config_string (s->config, "socks_host"); + int64_t socks_port_int = get_config_integer (s->config, "socks_port"); const char *socks_username = - get_config_string (ctx, "server.socks_username"); + get_config_string (s->config, "socks_username"); const char *socks_password = - get_config_string (ctx, "server.socks_password"); + get_config_string (s->config, "socks_password"); if (!socks_host) return false; @@ -3427,7 +3451,7 @@ irc_initiate_connect (struct server *s) hard_assert (s->state == IRC_DISCONNECTED); struct app_context *ctx = s->ctx; - const char *addresses = get_config_string (ctx, "server.addresses"); + const char *addresses = get_config_string (s->config, "addresses"); if (!addresses || !addresses[strspn (addresses, ",")]) { // No sense in trying to reconnect @@ -4231,7 +4255,7 @@ irc_on_registered (struct server *s, const char *nickname) // we can also use WHOIS if it's not supported (optional by RFC 2812) irc_send (s, "USERHOST %s", s->irc_user->nickname); - const char *autojoin = get_config_string (s->ctx, "server.autojoin"); + const char *autojoin = get_config_string (s->config, "autojoin"); if (autojoin) irc_send (s, "JOIN :%s", autojoin); @@ -5098,10 +5122,16 @@ handle_command_me (struct app_context *ctx, char *arguments) static bool handle_command_quit (struct app_context *ctx, char *arguments) { - // TODO: multiserver - struct server *s = &ctx->server; - if (irc_is_connected (s)) - irc_initiate_disconnect (s, *arguments ? arguments : NULL); + struct str_map_iter iter; + str_map_iter_init (&iter, &ctx->servers); + + struct server *s; + while ((s = str_map_iter_next (&iter))) + { + if (irc_is_connected (s)) + irc_initiate_disconnect (s, *arguments ? arguments : NULL); + } + initiate_quit (ctx); return true; } @@ -5159,8 +5189,24 @@ handle_command_part (struct app_context *ctx, char *arguments) static bool handle_command_connect (struct app_context *ctx, char *arguments) { - // TODO: multiserver - struct server *s = &ctx->server; + struct server *s = NULL; + if (*arguments) + { + char *name = cut_word (&arguments); + if (!(s = str_map_find (&ctx->servers, name))) + buffer_send_error (ctx, ctx->global_buffer, + "%s: %s: %s", "Can't connect", "no such server", name); + } + else if (ctx->current_buffer->type == BUFFER_GLOBAL) + buffer_send_error (ctx, ctx->current_buffer, + "%s: %s", "Can't connect", + "no server name given and this buffer is global"); + else + s = ctx->current_buffer->server; + + if (!s) + return true; + if (irc_is_connected (s)) { buffer_send_error (ctx, s->buffer, "Already connected"); @@ -5177,8 +5223,17 @@ handle_command_connect (struct app_context *ctx, char *arguments) static bool handle_command_disconnect (struct app_context *ctx, char *arguments) { - // TODO: multiserver - struct server *s = &ctx->server; + // TODO: try to take server name from arguments + struct server *s = NULL; + if (ctx->current_buffer->type == BUFFER_GLOBAL) + buffer_send_error (ctx, ctx->current_buffer, + "%s: %s", "Can't disconnect", "this buffer is global"); + else + s = ctx->current_buffer->server; + + if (!s) + return true; + if (s->state == IRC_CONNECTING) { buffer_send_status (ctx, s->buffer, "Connecting aborted"); @@ -5292,7 +5347,7 @@ g_command_handlers[] = NOT_IMPLEMENTED (invite) { "connect", "Connect to the server", - NULL, + "[server]", handle_command_connect }, { "disconnect", "Disconnect from the server", "[reason]", @@ -6264,11 +6319,11 @@ app_editline_init (struct input *self) // --- Configuration loading --------------------------------------------------- static bool -autofill_user_info (struct app_context *ctx, struct error **e) +autofill_user_info (struct server *s, struct error **e) { - const char *nickname = get_config_string (ctx, "server.nickname"); - const char *username = get_config_string (ctx, "server.username"); - const char *realname = get_config_string (ctx, "server.realname"); + const char *nickname = get_config_string (s->config, "nickname"); + const char *username = get_config_string (s->config, "username"); + const char *realname = get_config_string (s->config, "realname"); if (nickname && username && realname) return true; @@ -6280,9 +6335,9 @@ autofill_user_info (struct app_context *ctx, struct error **e) // FIXME: set_config_strings() writes errors on its own if (!nickname) - set_config_string (ctx, "server.nickname", pwd->pw_name); + set_config_string (s->config, "nickname", pwd->pw_name); if (!username) - set_config_string (ctx, "server.username", pwd->pw_name); + set_config_string (s->config, "username", pwd->pw_name); // Not all systems have the GECOS field but the vast majority does if (!realname) @@ -6294,7 +6349,7 @@ autofill_user_info (struct app_context *ctx, struct error **e) if (comma) *comma = '\0'; - set_config_string (ctx, "server.realname", gecos); + set_config_string (s->config, "realname", gecos); } return true; @@ -6371,18 +6426,9 @@ load_configuration (struct app_context *ctx) } config_load (&ctx->config, root ? root : config_item_object ()); - if (!autofill_user_info (ctx, &e)) - { - print_error ("%s: %s", "failed to fill in user details", e->message); - error_free (e); - } ctx->isolate_buffers = - get_config_boolean (ctx, "behaviour.isolate_buffers"); - ctx->server.reconnect = - get_config_boolean (ctx, "server.reconnect"); - ctx->server.reconnect_delay = - get_config_integer (ctx, "server.reconnect_delay"); + get_config_boolean (ctx->config.root, "behaviour.isolate_buffers"); } // --- Signals ----------------------------------------------------------------- @@ -6453,6 +6499,26 @@ setup_signal_handlers (void) // --- I/O event handlers ------------------------------------------------------ +// FIXME: merge this with initiate_quit() +static void +preinitiate_quit (struct app_context *ctx) +{ + struct str_map_iter iter; + str_map_iter_init (&iter, &ctx->servers); + + struct server *s; + while ((s = str_map_iter_next (&iter))) + { + // There may be a timer set to reconnect to the server + // TODO: a faster timer for quitting + // XXX: why do we do this? Just to reset the reconnect timer? + irc_reset_connection_timeouts (s); + + if (irc_is_connected (s)) + irc_initiate_disconnect (s, NULL); + } +} + static void on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx) { @@ -6461,15 +6527,7 @@ on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx) if (g_termination_requested && !ctx->quitting) { - // There may be a timer set to reconnect to the server - // TODO: multiserver - struct server *s = &ctx->server; - // TODO: a faster timer for quitting - // XXX: why do we do this? Just to reset the reconnect timer? - irc_reset_connection_timeouts (s); - - if (irc_is_connected (s)) - irc_initiate_disconnect (s, NULL); + preinitiate_quit (ctx); initiate_quit (ctx); } @@ -6588,6 +6646,43 @@ display_logo (void) str_vector_free (&v); } +static void +create_server (struct app_context *ctx) +{ + struct server *s = xmalloc (sizeof *s); + server_init (s, &ctx->poller); + + s->ctx = ctx; + s->name = xstrdup ("server"); + str_map_set (&ctx->servers, s->name, s); + + // Load configuration + s->config = config_item_get (ctx->config.root, "server", NULL); + hard_assert (s->config != NULL); + + s->reconnect = get_config_boolean (s->config, "reconnect"); + s->reconnect_delay = get_config_integer (s->config, "reconnect_delay"); + + struct error *e = NULL; + if (!autofill_user_info (s, &e)) + { + print_error ("%s: %s", "failed to fill in user details", e->message); + error_free (e); + } + + // Add a buffer and activate it + struct buffer *buffer = s->buffer = buffer_new (); + buffer->type = BUFFER_SERVER; + buffer->name = xstrdup (s->name); + buffer->server = s; + + buffer_add (ctx, buffer); + buffer_activate (ctx, buffer); + + // Connect to the server ASAP + poller_timer_set (&s->reconnect_tmr, 0); +} + int main (int argc, char *argv[]) { @@ -6650,10 +6745,9 @@ main (int argc, char *argv[]) refresh_prompt (&ctx); input_start (&ctx.input, argv[0]); - buffer_activate (&ctx, ctx.server.buffer); - // Connect to the server ASAP - poller_timer_set (&ctx.server.reconnect_tmr, 0); + // TODO: finish multi-server + create_server (&ctx); ctx.polling = true; while (ctx.polling)