degesch: more multiserver preparations

Almost done now.
This commit is contained in:
Přemysl Eric Janouch 2015-05-16 12:33:59 +02:00
parent bedbadd396
commit ca8540e217
1 changed files with 191 additions and 97 deletions

288
degesch.c
View File

@ -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)