/* channels-query.c : irssi Copyright (C) 1999-2000 Timo Sirainen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* How the thing works: - After channel is joined and NAMES list is got, send "channel joined" signal - "channel joined" : add channel to server->queries lists loop: - Wait for NAMES list from all channels before doing anything else.. - After got the last NAMES list, start sending the queries .. - find the query to send, check where server->queries list isn't NULL (mode, who, banlist, ban exceptions, invite list) - if not found anything -> all channels are synced - send "command #chan1,#chan2,#chan3,.." command to server - wait for reply from server, then check if it was last query to be sent to channel. If it was, send "channel sync" signal - check if the reply was for last channel in the command list. If so, goto loop */ #include "module.h" #include "misc.h" #include "signals.h" #include "settings.h" #include "modes.h" #include "mode-lists.h" #include "nicklist.h" #include "irc-servers.h" #include "irc-channels.h" #include "servers-redirect.h" enum { CHANNEL_QUERY_MODE, CHANNEL_QUERY_WHO, CHANNEL_QUERY_BMODE, CHANNEL_QUERIES }; #define CHANNEL_IS_MODE_QUERY(a) ((a) != CHANNEL_QUERY_WHO) typedef struct { int current_query_type; /* query type that is currently being asked */ GSList *current_queries; /* All channels that are currently being queried */ GSList *queries[CHANNEL_QUERIES]; /* All queries that need to be asked from server */ } SERVER_QUERY_REC; static void sig_connected(IRC_SERVER_REC *server) { SERVER_QUERY_REC *rec; g_return_if_fail(server != NULL); if (!IS_IRC_SERVER(server)) return; rec = g_new0(SERVER_QUERY_REC, 1); server->chanqueries = rec; } static void sig_disconnected(IRC_SERVER_REC *server) { SERVER_QUERY_REC *rec; int n; g_return_if_fail(server != NULL); if (!IS_IRC_SERVER(server)) return; rec = server->chanqueries; g_return_if_fail(rec != NULL); for (n = 0; n < CHANNEL_QUERIES; n++) g_slist_free(rec->queries[n]); g_slist_free(rec->current_queries); g_free(rec); server->chanqueries = NULL; } /* Add channel to query list */ static void query_add_channel(IRC_CHANNEL_REC *channel, int query_type) { SERVER_QUERY_REC *rec; g_return_if_fail(channel != NULL); rec = channel->server->chanqueries; rec->queries[query_type] = g_slist_append(rec->queries[query_type], channel); } static void query_check(IRC_SERVER_REC *server); static void query_remove_all(IRC_CHANNEL_REC *channel) { SERVER_QUERY_REC *rec; int n; rec = channel->server->chanqueries; /* remove channel from query lists */ for (n = 0; n < CHANNEL_QUERIES; n++) rec->queries[n] = g_slist_remove(rec->queries[n], channel); rec->current_queries = g_slist_remove(rec->current_queries, channel); query_check(channel->server); } static void sig_channel_destroyed(IRC_CHANNEL_REC *channel) { g_return_if_fail(channel != NULL); if (IS_IRC_CHANNEL(channel) && !channel->server->disconnected && !channel->synced) query_remove_all(channel); } static int channels_have_all_names(IRC_SERVER_REC *server) { GSList *tmp; for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { IRC_CHANNEL_REC *rec = tmp->data; if (IS_IRC_CHANNEL(rec) && !rec->names_got) return 0; } return 1; } static int query_find_next(SERVER_QUERY_REC *server) { int n; for (n = 0; n < CHANNEL_QUERIES; n++) { if (server->queries[n] != NULL) return n; } return -1; } static void query_send(IRC_SERVER_REC *server, int query) { SERVER_QUERY_REC *rec; IRC_CHANNEL_REC *chanrec; GSList *chans; char *cmd, *chanstr_commas, *chanstr; int onlyone, count; rec = server->chanqueries; /* get the list of channels to query */ onlyone = (server->no_multi_who && query == CHANNEL_QUERY_WHO) || (server->no_multi_mode && CHANNEL_IS_MODE_QUERY(query)); if (onlyone) { chans = rec->queries[query]; rec->queries[query] = g_slist_remove_link(rec->queries[query], chans); chanrec = chans->data; chanstr_commas = g_strdup(chanrec->name); chanstr = g_strdup(chanrec->name); count = 1; } else { char *chanstr_spaces; chans = rec->queries[query]; count = g_slist_length(chans); if (count > server->max_query_chans) { GSList *lastchan; lastchan = g_slist_nth(rec->queries[query], server->max_query_chans-1); count = server->max_query_chans; rec->queries[query] = lastchan->next; lastchan->next = NULL; } else { rec->queries[query] = NULL; } chanstr_commas = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), ","); chanstr_spaces = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), " "); chanstr = g_strconcat(chanstr_commas, " ", chanstr_spaces, NULL); g_free(chanstr_spaces); } rec->current_query_type = query; rec->current_queries = chans; switch (query) { case CHANNEL_QUERY_MODE: cmd = g_strdup_printf("MODE %s", chanstr_commas); /* the stop-event is received once for each channel, and we want to print 329 event (channel created). */ server_redirect_event(server, "mode channel", count, chanstr, -1, "chanquery abort", "event 324", "chanquery mode", "event 329", "event 329", "", "chanquery abort", NULL); break; case CHANNEL_QUERY_WHO: cmd = g_strdup_printf("WHO %s", chanstr_commas); server_redirect_event(server, "who", server->one_endofwho ? 1 : count, chanstr, -1, "chanquery abort", "event 315", "chanquery who end", "event 352", "silent event who", "", "chanquery abort", NULL); break; case CHANNEL_QUERY_BMODE: cmd = g_strdup_printf("MODE %s b", chanstr_commas); /* check all the multichannel problems with all mode requests - if channels are joined manually irssi could ask modes separately but afterwards join the two b/e/I modes together */ server_redirect_event(server, "mode b", count, chanstr, -1, "chanquery abort", "event 367", "chanquery ban", "event 368", "chanquery ban end", "", "chanquery abort", NULL); break; default: cmd = NULL; } irc_send_cmd(server, cmd); g_free(chanstr); g_free(chanstr_commas); g_free(cmd); } static void query_check(IRC_SERVER_REC *server) { SERVER_QUERY_REC *rec; int query; g_return_if_fail(server != NULL); rec = server->chanqueries; if (rec->current_queries != NULL) return; /* old queries haven't been answered yet */ if (server->max_query_chans > 1 && !server->no_multi_who && !server->no_multi_mode && !channels_have_all_names(server)) { /* all channels haven't sent /NAMES list yet */ /* only do this if there would be a benefit in combining * queries -- jilles */ return; } query = query_find_next(rec); if (query == -1) { /* no queries left */ return; } query_send(server, query); } /* if there's no more queries in queries in buffer, send the sync signal */ static void channel_checksync(IRC_CHANNEL_REC *channel) { SERVER_QUERY_REC *rec; int n; g_return_if_fail(channel != NULL); if (channel->synced) return; /* already synced */ rec = channel->server->chanqueries; for (n = 0; n < CHANNEL_QUERIES; n++) { if (g_slist_find(rec->queries[n], channel)) return; } channel->synced = TRUE; signal_emit("channel sync", 1, channel); } /* Error occured when trying to execute query - abort and try again. */ static void query_current_error(IRC_SERVER_REC *server) { SERVER_QUERY_REC *rec; GSList *tmp; int query, abort_query; rec = server->chanqueries; /* fix the thing that went wrong - or if it was already fixed, then all we can do is abort. */ abort_query = FALSE; query = rec->current_query_type; if (query == CHANNEL_QUERY_WHO) { if (server->no_multi_who) abort_query = TRUE; else server->no_multi_who = TRUE; } else { if (server->no_multi_mode) abort_query = TRUE; else server->no_multi_mode = TRUE; } if (!abort_query) { /* move all currently queried channels to main query lists */ for (tmp = rec->current_queries; tmp != NULL; tmp = tmp->next) { rec->queries[query] = g_slist_append(rec->queries[query], tmp->data); } } else { /* check if failed channels are synced after this error */ g_slist_foreach(rec->current_queries, (GFunc) channel_checksync, NULL); } g_slist_free(rec->current_queries); rec->current_queries = NULL; query_check(server); } static void sig_channel_joined(IRC_CHANNEL_REC *channel) { if (!IS_IRC_CHANNEL(channel)) return; if (!settings_get_bool("channel_sync")) return; /* Add channel to query lists */ if (!channel->no_modes) query_add_channel(channel, CHANNEL_QUERY_MODE); if (g_hash_table_size(channel->nicks) < settings_get_int("channel_max_who_sync")) query_add_channel(channel, CHANNEL_QUERY_WHO); if (!channel->no_modes) query_add_channel(channel, CHANNEL_QUERY_BMODE); query_check(channel->server); } static void channel_got_query(IRC_CHANNEL_REC *chanrec, int query_type) { SERVER_QUERY_REC *rec; g_return_if_fail(chanrec != NULL); rec = chanrec->server->chanqueries; if (query_type != rec->current_query_type) return; /* shouldn't happen */ /* got the query for channel.. */ rec->current_queries = g_slist_remove(rec->current_queries, chanrec); channel_checksync(chanrec); /* check if we need to send another query.. */ query_check(chanrec->server); } static void event_channel_mode(IRC_SERVER_REC *server, const char *data, const char *nick) { IRC_CHANNEL_REC *chanrec; char *params, *channel, *mode; g_return_if_fail(data != NULL); params = event_get_params(data, 3 | PARAM_FLAG_GETREST, NULL, &channel, &mode); chanrec = irc_channel_find(server, channel); if (chanrec != NULL) { if (chanrec->key != NULL && strchr(mode, 'k') == NULL) { /* we joined the channel with a key, but it didn't have +k mode.. */ parse_channel_modes(chanrec, NULL, "-k", TRUE); } parse_channel_modes(chanrec, nick, mode, FALSE); channel_got_query(chanrec, CHANNEL_QUERY_MODE); } g_free(params); } static void event_end_of_who(IRC_SERVER_REC *server, const char *data) { SERVER_QUERY_REC *rec; GSList *tmp, *next; char *params, *channel, **channels; int failed, multiple; g_return_if_fail(data != NULL); params = event_get_params(data, 2, NULL, &channel); multiple = strchr(channel, ',') != NULL; channels = g_strsplit(channel, ",", -1); failed = FALSE; rec = server->chanqueries; for (tmp = rec->current_queries; tmp != NULL; tmp = next) { IRC_CHANNEL_REC *chanrec = tmp->data; next = tmp->next; if (strarray_find(channels, chanrec->name) == -1) continue; if (chanrec->ownnick->host == NULL && multiple && !server->one_endofwho) { /* we should receive our own host for each channel. However, some servers really are stupid enough not to reply anything to /WHO requests.. */ failed = TRUE; } else { chanrec->wholist = TRUE; signal_emit("channel wholist", 1, chanrec); channel_got_query(chanrec, CHANNEL_QUERY_WHO); } } g_strfreev(channels); if (multiple) server->one_endofwho = TRUE; if (failed) { /* server didn't understand multiple WHO replies, send them again separately */ query_current_error(server); } g_free(params); } static void event_end_of_banlist(IRC_SERVER_REC *server, const char *data) { IRC_CHANNEL_REC *chanrec; char *params, *channel; g_return_if_fail(data != NULL); params = event_get_params(data, 2, NULL, &channel); chanrec = irc_channel_find(server, channel); if (chanrec != NULL) channel_got_query(chanrec, CHANNEL_QUERY_BMODE); g_free(params); } void channels_query_init(void) { settings_add_bool("misc", "channel_sync", TRUE); settings_add_int("misc", "channel_max_who_sync", 1000); signal_add("server connected", (SIGNAL_FUNC) sig_connected); signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined); signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); signal_add("chanquery mode", (SIGNAL_FUNC) event_channel_mode); signal_add("chanquery who end", (SIGNAL_FUNC) event_end_of_who); signal_add("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist); signal_add("chanquery abort", (SIGNAL_FUNC) query_current_error); } void channels_query_deinit(void) { signal_remove("server connected", (SIGNAL_FUNC) sig_connected); signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined); signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); signal_remove("chanquery mode", (SIGNAL_FUNC) event_channel_mode); signal_remove("chanquery who end", (SIGNAL_FUNC) event_end_of_who); signal_remove("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist); signal_remove("chanquery abort", (SIGNAL_FUNC) query_current_error); }