~ubuntu-branches/ubuntu/intrepid/irssi/intrepid-updates

« back to all changes in this revision

Viewing changes to src/irc/core/channels-query.c

  • Committer: Bazaar Package Importer
  • Author(s): David Pashley
  • Date: 2005-12-10 21:25:51 UTC
  • Revision ID: james.westby@ubuntu.com-20051210212551-5qwm108g7inyu2f2
Tags: upstream-0.8.10
ImportĀ upstreamĀ versionĀ 0.8.10

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
17
    along with this program; if not, write to the Free Software
 
18
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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
 
 
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
 
 
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 */
 
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
 
 
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
 
 
123
        /* remove channel from query lists */
 
124
        for (n = 0; n < CHANNEL_QUERIES; n++)
 
125
                rec->queries[n] = g_slist_remove(rec->queries[n], channel);
 
126
        rec->current_queries = g_slist_remove(rec->current_queries, channel);
 
127
 
 
128
        query_check(channel->server);
 
129
}
 
130
 
 
131
static void sig_channel_destroyed(IRC_CHANNEL_REC *channel)
 
132
{
 
133
        g_return_if_fail(channel != NULL);
 
134
 
 
135
        if (IS_IRC_CHANNEL(channel) && !channel->server->disconnected &&
 
136
            !channel->synced)
 
137
                query_remove_all(channel);
 
138
}
 
139
 
 
140
static int channels_have_all_names(IRC_SERVER_REC *server)
 
141
{
 
142
        GSList *tmp;
 
143
 
 
144
        for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
 
145
                IRC_CHANNEL_REC *rec = tmp->data;
 
146
 
 
147
                if (IS_IRC_CHANNEL(rec) && !rec->names_got)
 
148
                        return 0;
 
149
        }
 
150
 
 
151
        return 1;
 
152
}
 
153
 
 
154
static int query_find_next(SERVER_QUERY_REC *server)
 
155
{
 
156
        int n;
 
157
 
 
158
        for (n = 0; n < CHANNEL_QUERIES; n++) {
 
159
                if (server->queries[n] != NULL)
 
160
                        return n;
 
161
        }
 
162
 
 
163
        return -1;
 
164
}
 
165
 
 
166
static void query_send(IRC_SERVER_REC *server, int query)
 
167
{
 
168
        SERVER_QUERY_REC *rec;
 
169
        IRC_CHANNEL_REC *chanrec;
 
170
        GSList *chans;
 
171
        char *cmd, *chanstr_commas, *chanstr;
 
172
        int onlyone, count;
 
173
 
 
174
        rec = server->chanqueries;
 
175
 
 
176
        /* get the list of channels to query */
 
177
        onlyone = (server->no_multi_who && query == CHANNEL_QUERY_WHO) ||
 
178
                (server->no_multi_mode && CHANNEL_IS_MODE_QUERY(query));
 
179
 
 
180
        if (onlyone) {
 
181
                chans = rec->queries[query];
 
182
                rec->queries[query] =
 
183
                        g_slist_remove_link(rec->queries[query], chans);
 
184
 
 
185
                chanrec = chans->data;
 
186
                chanstr_commas = g_strdup(chanrec->name);
 
187
                chanstr = g_strdup(chanrec->name);
 
188
                count = 1;
 
189
        } else {
 
190
                char *chanstr_spaces;
 
191
 
 
192
                chans = rec->queries[query];
 
193
                count = g_slist_length(chans);
 
194
 
 
195
                if (count > server->max_query_chans) {
 
196
                        GSList *lastchan;
 
197
 
 
198
                        lastchan = g_slist_nth(rec->queries[query],
 
199
                                               server->max_query_chans-1);
 
200
                        count = server->max_query_chans;
 
201
                        rec->queries[query] = lastchan->next;
 
202
                        lastchan->next = NULL;
 
203
                } else {
 
204
                        rec->queries[query] = NULL;
 
205
                }
 
206
 
 
207
                chanstr_commas = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), ",");
 
208
                chanstr_spaces = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), " ");
 
209
 
 
210
                chanstr = g_strconcat(chanstr_commas, " ", chanstr_spaces, NULL);
 
211
                g_free(chanstr_spaces);
 
212
        }
 
213
 
 
214
        rec->current_query_type = query;
 
215
        rec->current_queries = chans;
 
216
 
 
217
        switch (query) {
 
218
        case CHANNEL_QUERY_MODE:
 
219
                cmd = g_strdup_printf("MODE %s", chanstr_commas);
 
220
 
 
221
                /* the stop-event is received once for each channel,
 
222
                   and we want to print 329 event (channel created). */
 
223
                server_redirect_event(server, "mode channel", count,
 
224
                                      chanstr, -1, "chanquery abort",
 
225
                                      "event 324", "chanquery mode",
 
226
                                      "event 329", "event 329",
 
227
                                      "", "chanquery abort", NULL);
 
228
                break;
 
229
 
 
230
        case CHANNEL_QUERY_WHO:
 
231
                cmd = g_strdup_printf("WHO %s", chanstr_commas);
 
232
 
 
233
                server_redirect_event(server, "who",
 
234
                                      server->one_endofwho ? 1 : count,
 
235
                                      chanstr, -1,
 
236
                                      "chanquery abort",
 
237
                                      "event 315", "chanquery who end",
 
238
                                      "event 352", "silent event who",
 
239
                                      "", "chanquery abort", NULL);
 
240
                break;
 
241
 
 
242
        case CHANNEL_QUERY_BMODE:
 
243
                cmd = g_strdup_printf("MODE %s b", chanstr_commas);
 
244
                /* check all the multichannel problems with all
 
245
                   mode requests - if channels are joined manually
 
246
                   irssi could ask modes separately but afterwards
 
247
                   join the two b/e/I modes together */
 
248
                server_redirect_event(server, "mode b", count, chanstr, -1,
 
249
                                      "chanquery abort",
 
250
                                      "event 367", "chanquery ban",
 
251
                                      "event 368", "chanquery ban end",
 
252
                                      "", "chanquery abort", NULL);
 
253
                break;
 
254
 
 
255
        default:
 
256
                cmd = NULL;
 
257
        }
 
258
 
 
259
        irc_send_cmd(server, cmd);
 
260
 
 
261
        g_free(chanstr);
 
262
        g_free(chanstr_commas);
 
263
        g_free(cmd);
 
264
}
 
265
 
 
266
static void query_check(IRC_SERVER_REC *server)
 
267
{
 
268
        SERVER_QUERY_REC *rec;
 
269
        int query;
 
270
 
 
271
        g_return_if_fail(server != NULL);
 
272
 
 
273
        rec = server->chanqueries;
 
274
        if (rec->current_queries != NULL)
 
275
                return; /* old queries haven't been answered yet */
 
276
 
 
277
        if (!channels_have_all_names(server)) {
 
278
                /* all channels haven't sent /NAMES list yet */
 
279
                return;
 
280
        }
 
281
 
 
282
        query = query_find_next(rec);
 
283
        if (query == -1) {
 
284
                /* no queries left */
 
285
                return;
 
286
        }
 
287
 
 
288
        query_send(server, query);
 
289
}
 
290
 
 
291
/* if there's no more queries in queries in buffer, send the sync signal */
 
292
static void channel_checksync(IRC_CHANNEL_REC *channel)
 
293
{
 
294
        SERVER_QUERY_REC *rec;
 
295
        int n;
 
296
 
 
297
        g_return_if_fail(channel != NULL);
 
298
 
 
299
        if (channel->synced)
 
300
                return; /* already synced */
 
301
 
 
302
        rec = channel->server->chanqueries;
 
303
        for (n = 0; n < CHANNEL_QUERIES; n++) {
 
304
                if (g_slist_find(rec->queries[n], channel))
 
305
                        return;
 
306
        }
 
307
 
 
308
        channel->synced = TRUE;
 
309
        signal_emit("channel sync", 1, channel);
 
310
}
 
311
 
 
312
/* Error occured when trying to execute query - abort and try again. */
 
313
static void query_current_error(IRC_SERVER_REC *server)
 
314
{
 
315
        SERVER_QUERY_REC *rec;
 
316
        GSList *tmp;
 
317
        int query, abort_query;
 
318
 
 
319
        rec = server->chanqueries;
 
320
 
 
321
        /* fix the thing that went wrong - or if it was already fixed,
 
322
           then all we can do is abort. */
 
323
        abort_query = FALSE;
 
324
 
 
325
        query = rec->current_query_type;
 
326
        if (query == CHANNEL_QUERY_WHO) {
 
327
                if (server->no_multi_who)
 
328
                        abort_query = TRUE;
 
329
                else
 
330
                        server->no_multi_who = TRUE;
 
331
        } else {
 
332
                if (server->no_multi_mode)
 
333
                        abort_query = TRUE;
 
334
                else
 
335
                        server->no_multi_mode = TRUE;
 
336
        }
 
337
 
 
338
        if (!abort_query) {
 
339
                /* move all currently queried channels to main query lists */
 
340
                for (tmp = rec->current_queries; tmp != NULL; tmp = tmp->next) {
 
341
                        rec->queries[query] =
 
342
                                g_slist_append(rec->queries[query], tmp->data);
 
343
                }
 
344
        } else {
 
345
                /* check if failed channels are synced after this error */
 
346
                g_slist_foreach(rec->current_queries,
 
347
                                (GFunc) channel_checksync, NULL);
 
348
        }
 
349
 
 
350
        g_slist_free(rec->current_queries);
 
351
        rec->current_queries = NULL;
 
352
 
 
353
        query_check(server);
 
354
}
 
355
 
 
356
static void sig_channel_joined(IRC_CHANNEL_REC *channel)
 
357
{
 
358
        if (!IS_IRC_CHANNEL(channel))
 
359
                return;
 
360
 
 
361
        if (!settings_get_bool("channel_sync"))
 
362
                return;
 
363
 
 
364
        /* Add channel to query lists */
 
365
        if (!channel->no_modes)
 
366
                query_add_channel(channel, CHANNEL_QUERY_MODE);
 
367
        if (g_hash_table_size(channel->nicks) <
 
368
            settings_get_int("channel_max_who_sync"))
 
369
                query_add_channel(channel, CHANNEL_QUERY_WHO);
 
370
        if (!channel->no_modes)
 
371
                query_add_channel(channel, CHANNEL_QUERY_BMODE);
 
372
 
 
373
        query_check(channel->server);
 
374
}
 
375
 
 
376
static void channel_got_query(IRC_CHANNEL_REC *chanrec, int query_type)
 
377
{
 
378
        SERVER_QUERY_REC *rec;
 
379
 
 
380
        g_return_if_fail(chanrec != NULL);
 
381
 
 
382
        rec = chanrec->server->chanqueries;
 
383
        if (query_type != rec->current_query_type)
 
384
                return; /* shouldn't happen */
 
385
 
 
386
        /* got the query for channel.. */
 
387
        rec->current_queries =
 
388
                g_slist_remove(rec->current_queries, chanrec);
 
389
        channel_checksync(chanrec);
 
390
 
 
391
        /* check if we need to send another query.. */
 
392
        query_check(chanrec->server);
 
393
}
 
394
 
 
395
static void event_channel_mode(IRC_SERVER_REC *server, const char *data,
 
396
                               const char *nick)
 
397
{
 
398
        IRC_CHANNEL_REC *chanrec;
 
399
        char *params, *channel, *mode;
 
400
 
 
401
        g_return_if_fail(data != NULL);
 
402
 
 
403
        params = event_get_params(data, 3 | PARAM_FLAG_GETREST,
 
404
                                  NULL, &channel, &mode);
 
405
        chanrec = irc_channel_find(server, channel);
 
406
        if (chanrec != NULL) {
 
407
                if (chanrec->key != NULL && strchr(mode, 'k') == NULL) {
 
408
                        /* we joined the channel with a key,
 
409
                           but it didn't have +k mode.. */
 
410
                        parse_channel_modes(chanrec, NULL, "-k", TRUE);
 
411
                }
 
412
                parse_channel_modes(chanrec, nick, mode, FALSE);
 
413
                channel_got_query(chanrec, CHANNEL_QUERY_MODE);
 
414
        }
 
415
 
 
416
        g_free(params);
 
417
}
 
418
 
 
419
static void event_end_of_who(IRC_SERVER_REC *server, const char *data)
 
420
{
 
421
        SERVER_QUERY_REC *rec;
 
422
        GSList *tmp, *next;
 
423
        char *params, *channel, **channels;
 
424
        int failed, multiple;
 
425
 
 
426
        g_return_if_fail(data != NULL);
 
427
 
 
428
        params = event_get_params(data, 2, NULL, &channel);
 
429
        multiple = strchr(channel, ',') != NULL;
 
430
        channels = g_strsplit(channel, ",", -1);
 
431
 
 
432
        failed = FALSE;
 
433
        rec = server->chanqueries;
 
434
        for (tmp = rec->current_queries; tmp != NULL; tmp = next) {
 
435
                IRC_CHANNEL_REC *chanrec = tmp->data;
 
436
 
 
437
                next = tmp->next;
 
438
                if (strarray_find(channels, chanrec->name) == -1)
 
439
                        continue;
 
440
 
 
441
                if (chanrec->ownnick->host == NULL && multiple &&
 
442
                    !server->one_endofwho) {
 
443
                        /* we should receive our own host for each channel.
 
444
                           However, some servers really are stupid enough
 
445
                           not to reply anything to /WHO requests.. */
 
446
                        failed = TRUE;
 
447
                } else {
 
448
                        chanrec->wholist = TRUE;
 
449
                        signal_emit("channel wholist", 1, chanrec);
 
450
                        channel_got_query(chanrec, CHANNEL_QUERY_WHO);
 
451
                }
 
452
        }
 
453
 
 
454
        g_strfreev(channels);
 
455
        if (multiple)
 
456
                server->one_endofwho = TRUE;
 
457
 
 
458
        if (failed) {
 
459
                /* server didn't understand multiple WHO replies,
 
460
                   send them again separately */
 
461
                query_current_error(server);
 
462
        }
 
463
 
 
464
        g_free(params);
 
465
}
 
466
 
 
467
static void event_end_of_banlist(IRC_SERVER_REC *server, const char *data)
 
468
{
 
469
        IRC_CHANNEL_REC *chanrec;
 
470
        char *params, *channel;
 
471
 
 
472
        g_return_if_fail(data != NULL);
 
473
 
 
474
        params = event_get_params(data, 2, NULL, &channel);
 
475
        chanrec = irc_channel_find(server, channel);
 
476
 
 
477
        if (chanrec != NULL)
 
478
                channel_got_query(chanrec, CHANNEL_QUERY_BMODE);
 
479
 
 
480
        g_free(params);
 
481
}
 
482
 
 
483
void channels_query_init(void)
 
484
{
 
485
        settings_add_bool("misc", "channel_sync", TRUE);
 
486
        settings_add_int("misc", "channel_max_who_sync", 1000);
 
487
 
 
488
        signal_add("server connected", (SIGNAL_FUNC) sig_connected);
 
489
        signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
 
490
        signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined);
 
491
        signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
 
492
 
 
493
        signal_add("chanquery mode", (SIGNAL_FUNC) event_channel_mode);
 
494
        signal_add("chanquery who end", (SIGNAL_FUNC) event_end_of_who);
 
495
 
 
496
        signal_add("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist);
 
497
        signal_add("chanquery abort", (SIGNAL_FUNC) query_current_error);
 
498
}
 
499
 
 
500
void channels_query_deinit(void)
 
501
{
 
502
        signal_remove("server connected", (SIGNAL_FUNC) sig_connected);
 
503
        signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
 
504
        signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined);
 
505
        signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
 
506
 
 
507
        signal_remove("chanquery mode", (SIGNAL_FUNC) event_channel_mode);
 
508
        signal_remove("chanquery who end", (SIGNAL_FUNC) event_end_of_who);
 
509
 
 
510
        signal_remove("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist);
 
511
        signal_remove("chanquery abort", (SIGNAL_FUNC) query_current_error);
 
512
}