1 /*
2 channels-query.c : irssi
3
4 Copyright (C) 1999-2000 Timo Sirainen
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 /*
22
23 How the thing works:
24
25 - After channel is joined and NAMES list is got, send "channel joined" signal
26 - "channel joined" : add channel to server->queries lists
27
28 loop:
29 - Wait for NAMES list from all channels before doing anything else..
30 - After got the last NAMES list, start sending the queries ..
31 - find the query to send, check where server->queries list isn't NULL
32 (mode, who, banlist, ban exceptions, invite list)
33 - if not found anything -> all channels are synced
34 - send "command #chan1,#chan2,#chan3,.." command to server
35 - wait for reply from server, then check if it was last query to be sent to
36 channel. If it was, send "channel sync" signal
37 - check if the reply was for last channel in the command list. If so,
38 goto loop
39 */
40
41 #include "module.h"
42 #include "misc.h"
43 #include "signals.h"
44 #include "settings.h"
45
46 #include "modes.h"
47 #include "mode-lists.h"
48 #include "nicklist.h"
49 #include "irc-servers.h"
50 #include "irc-channels.h"
51 #include "servers-redirect.h"
52
53 enum {
54 CHANNEL_QUERY_MODE,
55 CHANNEL_QUERY_WHO,
56 CHANNEL_QUERY_BMODE,
57
58 CHANNEL_QUERIES
59 };
60
61 #define CHANNEL_IS_MODE_QUERY(a) ((a) != CHANNEL_QUERY_WHO)
62
63 typedef struct {
64 int current_query_type; /* query type that is currently being asked */
65 GSList *current_queries; /* All channels that are currently being queried */
66
67 GSList *queries[CHANNEL_QUERIES]; /* All queries that need to be asked from server */
68 } SERVER_QUERY_REC;
69
sig_connected(IRC_SERVER_REC * server)70 static void sig_connected(IRC_SERVER_REC *server)
71 {
72 SERVER_QUERY_REC *rec;
73
74 g_return_if_fail(server != NULL);
75 if (!IS_IRC_SERVER(server))
76 return;
77
78 rec = g_new0(SERVER_QUERY_REC, 1);
79 server->chanqueries = rec;
80 }
81
sig_disconnected(IRC_SERVER_REC * server)82 static void sig_disconnected(IRC_SERVER_REC *server)
83 {
84 SERVER_QUERY_REC *rec;
85 int n;
86
87 g_return_if_fail(server != NULL);
88 if (!IS_IRC_SERVER(server))
89 return;
90
91 rec = server->chanqueries;
92 g_return_if_fail(rec != NULL);
93
94 for (n = 0; n < CHANNEL_QUERIES; n++)
95 g_slist_free(rec->queries[n]);
96 g_slist_free(rec->current_queries);
97 g_free(rec);
98
99 server->chanqueries = NULL;
100 }
101
102 /* Add channel to query list */
query_add_channel(IRC_CHANNEL_REC * channel,int query_type)103 static void query_add_channel(IRC_CHANNEL_REC *channel, int query_type)
104 {
105 SERVER_QUERY_REC *rec;
106
107 g_return_if_fail(channel != NULL);
108
109 rec = channel->server->chanqueries;
110 rec->queries[query_type] =
111 g_slist_append(rec->queries[query_type], channel);
112 }
113
114 static void query_check(IRC_SERVER_REC *server);
115
query_remove_all(IRC_CHANNEL_REC * channel)116 static void query_remove_all(IRC_CHANNEL_REC *channel)
117 {
118 SERVER_QUERY_REC *rec;
119 int n;
120
121 rec = channel->server->chanqueries;
122 if (rec == NULL) return;
123
124 /* remove channel from query lists */
125 for (n = 0; n < CHANNEL_QUERIES; n++)
126 rec->queries[n] = g_slist_remove(rec->queries[n], channel);
127 rec->current_queries = g_slist_remove(rec->current_queries, channel);
128
129 if (!channel->server->disconnected)
130 query_check(channel->server);
131 }
132
sig_channel_destroyed(IRC_CHANNEL_REC * channel)133 static void sig_channel_destroyed(IRC_CHANNEL_REC *channel)
134 {
135 g_return_if_fail(channel != NULL);
136
137 if (IS_IRC_CHANNEL(channel))
138 query_remove_all(channel);
139 }
140
channels_have_all_names(IRC_SERVER_REC * server)141 static int channels_have_all_names(IRC_SERVER_REC *server)
142 {
143 GSList *tmp;
144
145 for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
146 IRC_CHANNEL_REC *rec = tmp->data;
147
148 if (IS_IRC_CHANNEL(rec) && !rec->names_got)
149 return 0;
150 }
151
152 return 1;
153 }
154
query_find_next(SERVER_QUERY_REC * server)155 static int query_find_next(SERVER_QUERY_REC *server)
156 {
157 int n;
158
159 for (n = 0; n < CHANNEL_QUERIES; n++) {
160 if (server->queries[n] != NULL)
161 return n;
162 }
163
164 return -1;
165 }
166
query_send(IRC_SERVER_REC * server,int query)167 static void query_send(IRC_SERVER_REC *server, int query)
168 {
169 SERVER_QUERY_REC *rec;
170 IRC_CHANNEL_REC *chanrec;
171 GSList *chans;
172 char *cmd, *chanstr_commas, *chanstr;
173 int onlyone, count;
174
175 rec = server->chanqueries;
176
177 /* get the list of channels to query */
178 onlyone = (server->no_multi_who && query == CHANNEL_QUERY_WHO) ||
179 (server->no_multi_mode && CHANNEL_IS_MODE_QUERY(query));
180
181 if (onlyone) {
182 chans = rec->queries[query];
183 rec->queries[query] =
184 g_slist_remove_link(rec->queries[query], chans);
185
186 chanrec = chans->data;
187 chanstr_commas = g_strdup(chanrec->name);
188 chanstr = g_strdup(chanrec->name);
189 count = 1;
190 } else {
191 char *chanstr_spaces;
192
193 chans = rec->queries[query];
194 count = g_slist_length(chans);
195
196 if (count > server->max_query_chans) {
197 GSList *lastchan;
198
199 lastchan = g_slist_nth(rec->queries[query],
200 server->max_query_chans-1);
201 count = server->max_query_chans;
202 rec->queries[query] = lastchan->next;
203 lastchan->next = NULL;
204 } else {
205 rec->queries[query] = NULL;
206 }
207
208 chanstr_commas = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), ",");
209 chanstr_spaces = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), " ");
210
211 chanstr = g_strconcat(chanstr_commas, " ", chanstr_spaces, NULL);
212 g_free(chanstr_spaces);
213 }
214
215 rec->current_query_type = query;
216 rec->current_queries = chans;
217
218 switch (query) {
219 case CHANNEL_QUERY_MODE:
220 cmd = g_strdup_printf("MODE %s", chanstr_commas);
221
222 /* the stop-event is received once for each channel,
223 and we want to print 329 event (channel created). */
224 server_redirect_event(server, "mode channel", count,
225 chanstr, -1, "chanquery abort",
226 "event 324", "chanquery mode",
227 "event 329", "event 329",
228 "", "chanquery abort", NULL);
229 break;
230
231 case CHANNEL_QUERY_WHO:
232 cmd = g_strdup_printf("WHO %s", chanstr_commas);
233
234 server_redirect_event(server, "who",
235 server->one_endofwho ? 1 : count,
236 chanstr, -1,
237 "chanquery abort",
238 "event 315", "chanquery who end",
239 "event 352", "silent event who",
240 "", "chanquery abort", NULL);
241 break;
242
243 case CHANNEL_QUERY_BMODE:
244 cmd = g_strdup_printf("MODE %s b", chanstr_commas);
245 /* check all the multichannel problems with all
246 mode requests - if channels are joined manually
247 irssi could ask modes separately but afterwards
248 join the two b/e/I modes together */
249 server_redirect_event(server, "mode b", count, chanstr, -1,
250 "chanquery abort",
251 "event 367", "chanquery ban",
252 "event 368", "chanquery ban end",
253 "", "chanquery abort", NULL);
254 break;
255
256 default:
257 cmd = NULL;
258 }
259
260 irc_send_cmd(server, cmd);
261
262 g_free(chanstr);
263 g_free(chanstr_commas);
264 g_free(cmd);
265 }
266
query_check(IRC_SERVER_REC * server)267 static void query_check(IRC_SERVER_REC *server)
268 {
269 SERVER_QUERY_REC *rec;
270 int query;
271
272 g_return_if_fail(server != NULL);
273
274 rec = server->chanqueries;
275 if (rec->current_queries != NULL)
276 return; /* old queries haven't been answered yet */
277
278 if (server->max_query_chans > 1 && !server->no_multi_who && !server->no_multi_mode && !channels_have_all_names(server)) {
279 /* all channels haven't sent /NAMES list yet */
280 /* only do this if there would be a benefit in combining
281 * queries -- jilles */
282 return;
283 }
284
285 query = query_find_next(rec);
286 if (query == -1) {
287 /* no queries left */
288 return;
289 }
290
291 query_send(server, query);
292 }
293
294 /* if there's no more queries in queries in buffer, send the sync signal */
channel_checksync(IRC_CHANNEL_REC * channel)295 static void channel_checksync(IRC_CHANNEL_REC *channel)
296 {
297 SERVER_QUERY_REC *rec;
298 int n;
299
300 g_return_if_fail(channel != NULL);
301
302 if (channel->synced)
303 return; /* already synced */
304
305 rec = channel->server->chanqueries;
306 for (n = 0; n < CHANNEL_QUERIES; n++) {
307 if (g_slist_find(rec->queries[n], channel))
308 return;
309 }
310
311 channel->synced = TRUE;
312 signal_emit("channel sync", 1, channel);
313 }
314
315 /* Error occurred when trying to execute query - abort and try again. */
query_current_error(IRC_SERVER_REC * server)316 static void query_current_error(IRC_SERVER_REC *server)
317 {
318 SERVER_QUERY_REC *rec;
319 GSList *tmp;
320 int query, abort_query;
321
322 rec = server->chanqueries;
323
324 /* fix the thing that went wrong - or if it was already fixed,
325 then all we can do is abort. */
326 abort_query = FALSE;
327
328 query = rec->current_query_type;
329 if (query == CHANNEL_QUERY_WHO) {
330 if (server->no_multi_who)
331 abort_query = TRUE;
332 else
333 server->no_multi_who = TRUE;
334 } else {
335 if (server->no_multi_mode)
336 abort_query = TRUE;
337 else
338 server->no_multi_mode = TRUE;
339 }
340
341 if (!abort_query) {
342 /* move all currently queried channels to main query lists */
343 for (tmp = rec->current_queries; tmp != NULL; tmp = tmp->next) {
344 rec->queries[query] =
345 g_slist_append(rec->queries[query], tmp->data);
346 }
347 } else {
348 /* check if failed channels are synced after this error */
349 g_slist_foreach(rec->current_queries,
350 (GFunc) channel_checksync, NULL);
351 }
352
353 g_slist_free(rec->current_queries);
354 rec->current_queries = NULL;
355
356 query_check(server);
357 }
358
sig_channel_joined(IRC_CHANNEL_REC * channel)359 static void sig_channel_joined(IRC_CHANNEL_REC *channel)
360 {
361 if (!IS_IRC_CHANNEL(channel))
362 return;
363
364 if (!settings_get_bool("channel_sync"))
365 return;
366
367 /* Add channel to query lists */
368 if (!channel->no_modes)
369 query_add_channel(channel, CHANNEL_QUERY_MODE);
370 if (g_hash_table_size(channel->nicks) <
371 settings_get_int("channel_max_who_sync"))
372 query_add_channel(channel, CHANNEL_QUERY_WHO);
373 if (!channel->no_modes)
374 query_add_channel(channel, CHANNEL_QUERY_BMODE);
375
376 query_check(channel->server);
377 }
378
channel_got_query(IRC_CHANNEL_REC * chanrec,int query_type)379 static void channel_got_query(IRC_CHANNEL_REC *chanrec, int query_type)
380 {
381 SERVER_QUERY_REC *rec;
382
383 g_return_if_fail(chanrec != NULL);
384
385 rec = chanrec->server->chanqueries;
386 if (query_type != rec->current_query_type)
387 return; /* shouldn't happen */
388
389 /* got the query for channel.. */
390 rec->current_queries =
391 g_slist_remove(rec->current_queries, chanrec);
392 channel_checksync(chanrec);
393
394 /* check if we need to send another query.. */
395 query_check(chanrec->server);
396 }
397
event_channel_mode(IRC_SERVER_REC * server,const char * data,const char * nick)398 static void event_channel_mode(IRC_SERVER_REC *server, const char *data,
399 const char *nick)
400 {
401 IRC_CHANNEL_REC *chanrec;
402 char *params, *channel, *mode;
403
404 g_return_if_fail(data != NULL);
405
406 params = event_get_params(data, 3 | PARAM_FLAG_GETREST,
407 NULL, &channel, &mode);
408 chanrec = irc_channel_find(server, channel);
409 if (chanrec != NULL) {
410 if (chanrec->key != NULL && strchr(mode, 'k') == NULL) {
411 /* we joined the channel with a key,
412 but it didn't have +k mode.. */
413 parse_channel_modes(chanrec, NULL, "-k", TRUE);
414 }
415 parse_channel_modes(chanrec, nick, mode, FALSE);
416 channel_got_query(chanrec, CHANNEL_QUERY_MODE);
417 }
418
419 g_free(params);
420 }
421
event_end_of_who(IRC_SERVER_REC * server,const char * data)422 static void event_end_of_who(IRC_SERVER_REC *server, const char *data)
423 {
424 SERVER_QUERY_REC *rec;
425 GSList *tmp, *next;
426 char *params, *channel, **channels;
427 int failed, multiple;
428
429 g_return_if_fail(data != NULL);
430
431 params = event_get_params(data, 2, NULL, &channel);
432 multiple = strchr(channel, ',') != NULL;
433 channels = g_strsplit(channel, ",", -1);
434
435 failed = FALSE;
436 rec = server->chanqueries;
437 for (tmp = rec->current_queries; tmp != NULL; tmp = next) {
438 IRC_CHANNEL_REC *chanrec = tmp->data;
439
440 next = tmp->next;
441 if (strarray_find(channels, chanrec->name) == -1)
442 continue;
443
444 if (chanrec->ownnick->host == NULL && multiple &&
445 !server->one_endofwho) {
446 /* we should receive our own host for each channel.
447 However, some servers really are stupid enough
448 not to reply anything to /WHO requests.. */
449 failed = TRUE;
450 } else {
451 chanrec->wholist = TRUE;
452 signal_emit("channel wholist", 1, chanrec);
453 channel_got_query(chanrec, CHANNEL_QUERY_WHO);
454 }
455 }
456
457 g_strfreev(channels);
458 if (multiple)
459 server->one_endofwho = TRUE;
460
461 if (failed) {
462 /* server didn't understand multiple WHO replies,
463 send them again separately */
464 query_current_error(server);
465 }
466
467 g_free(params);
468 }
469
event_end_of_banlist(IRC_SERVER_REC * server,const char * data)470 static void event_end_of_banlist(IRC_SERVER_REC *server, const char *data)
471 {
472 IRC_CHANNEL_REC *chanrec;
473 char *params, *channel;
474
475 g_return_if_fail(data != NULL);
476
477 params = event_get_params(data, 2, NULL, &channel);
478 chanrec = irc_channel_find(server, channel);
479
480 if (chanrec != NULL)
481 channel_got_query(chanrec, CHANNEL_QUERY_BMODE);
482
483 g_free(params);
484 }
485
channels_query_init(void)486 void channels_query_init(void)
487 {
488 settings_add_bool("misc", "channel_sync", TRUE);
489 settings_add_int("misc", "channel_max_who_sync", 1000);
490
491 signal_add("server connected", (SIGNAL_FUNC) sig_connected);
492 signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
493 signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined);
494 signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
495
496 signal_add("chanquery mode", (SIGNAL_FUNC) event_channel_mode);
497 signal_add("chanquery who end", (SIGNAL_FUNC) event_end_of_who);
498
499 signal_add("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist);
500 signal_add("chanquery abort", (SIGNAL_FUNC) query_current_error);
501 }
502
channels_query_deinit(void)503 void channels_query_deinit(void)
504 {
505 signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
506 signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
507 signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined);
508 signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
509
510 signal_remove("chanquery mode", (SIGNAL_FUNC) event_channel_mode);
511 signal_remove("chanquery who end", (SIGNAL_FUNC) event_end_of_who);
512
513 signal_remove("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist);
514 signal_remove("chanquery abort", (SIGNAL_FUNC) query_current_error);
515 }
516