/src/irssi/src/fe-common/irc/fe-netsplit.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | fe-netsplit.c : irssi |
3 | | |
4 | | Copyright (C) 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 | | #include "module.h" |
22 | | #include <irssi/src/fe-common/irc/module-formats.h> |
23 | | #include <irssi/src/core/signals.h> |
24 | | #include <irssi/src/core/levels.h> |
25 | | #include <irssi/src/core/settings.h> |
26 | | |
27 | | #include <irssi/src/irc/core/irc-servers.h> |
28 | | #include <irssi/src/irc/core/irc-commands.h> |
29 | | #include <irssi/src/core/ignore.h> |
30 | | #include <irssi/src/irc/core/netsplit.h> |
31 | | |
32 | | #include <irssi/src/fe-common/core/printtext.h> |
33 | | |
34 | 0 | #define SPLIT_WAIT_TIME 5 /* how many seconds to wait for the QUIT split messages to stop */ |
35 | | |
36 | | static int split_tag; |
37 | | static int netsplit_max_nicks, netsplit_nicks_hide_threshold; |
38 | | static int printing_splits; |
39 | | |
40 | | static int get_last_split(IRC_SERVER_REC *server) |
41 | 0 | { |
42 | 0 | GSList *tmp; |
43 | 0 | time_t last; |
44 | |
|
45 | 0 | last = 0; |
46 | 0 | for (tmp = server->split_servers; tmp != NULL; tmp = tmp->next) { |
47 | 0 | NETSPLIT_SERVER_REC *rec = tmp->data; |
48 | |
|
49 | 0 | if (rec->last > last) last = rec->last; |
50 | 0 | } |
51 | |
|
52 | 0 | return last; |
53 | 0 | } |
54 | | |
55 | | typedef struct { |
56 | | char *name; |
57 | | int nick_count, maxnickpos; |
58 | | GString *nicks; |
59 | | } TEMP_SPLIT_CHAN_REC; |
60 | | |
61 | | typedef struct { |
62 | | IRC_SERVER_REC *server_rec; |
63 | | GSList *servers; /* if many servers splitted from the same one */ |
64 | | GSList *channels; |
65 | | } TEMP_SPLIT_REC; |
66 | | |
67 | | static GSList *get_source_servers(const char *server, GSList **servers) |
68 | 0 | { |
69 | 0 | GSList *list, *next, *tmp; |
70 | |
|
71 | 0 | list = NULL; |
72 | 0 | for (tmp = *servers; tmp != NULL; tmp = next) { |
73 | 0 | NETSPLIT_SERVER_REC *rec = tmp->data; |
74 | 0 | next = tmp->next; |
75 | |
|
76 | 0 | if (g_ascii_strcasecmp(rec->server, server) == 0) { |
77 | 0 | rec->prints = 0; |
78 | 0 | list = g_slist_append(list, rec); |
79 | 0 | *servers = g_slist_remove(*servers, rec); |
80 | 0 | } |
81 | 0 | } |
82 | |
|
83 | 0 | return list; |
84 | 0 | } |
85 | | |
86 | | static TEMP_SPLIT_CHAN_REC *find_split_chan(TEMP_SPLIT_REC *rec, |
87 | | const char *name) |
88 | 0 | { |
89 | 0 | GSList *tmp; |
90 | |
|
91 | 0 | for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) { |
92 | 0 | TEMP_SPLIT_CHAN_REC *chanrec = tmp->data; |
93 | |
|
94 | 0 | if (g_ascii_strcasecmp(chanrec->name, name) == 0) |
95 | 0 | return chanrec; |
96 | 0 | } |
97 | | |
98 | 0 | return NULL; |
99 | 0 | } |
100 | | |
101 | | static void get_server_splits(void *key, NETSPLIT_REC *split, |
102 | | TEMP_SPLIT_REC *rec) |
103 | 0 | { |
104 | 0 | TEMP_SPLIT_CHAN_REC *chanrec; |
105 | 0 | GSList *tmp; |
106 | |
|
107 | 0 | if (split->printed || |
108 | 0 | g_slist_find(rec->servers, split->server) == NULL) |
109 | 0 | return; |
110 | | |
111 | 0 | split->printed = TRUE; |
112 | 0 | for (tmp = split->channels; tmp != NULL; tmp = tmp->next) { |
113 | 0 | NETSPLIT_CHAN_REC *splitchan = tmp->data; |
114 | |
|
115 | 0 | if (ignore_check(SERVER(rec->server_rec), split->nick, |
116 | 0 | split->address, splitchan->name, "", |
117 | 0 | MSGLEVEL_QUITS)) |
118 | 0 | continue; |
119 | | |
120 | 0 | chanrec = find_split_chan(rec, splitchan->name); |
121 | 0 | if (chanrec == NULL) { |
122 | 0 | chanrec = g_new0(TEMP_SPLIT_CHAN_REC, 1); |
123 | 0 | chanrec->name = splitchan->name; |
124 | 0 | chanrec->nicks = g_string_new(NULL); |
125 | |
|
126 | 0 | rec->channels = g_slist_append(rec->channels, chanrec); |
127 | 0 | } |
128 | |
|
129 | 0 | split->server->prints++; |
130 | 0 | chanrec->nick_count++; |
131 | 0 | if (netsplit_nicks_hide_threshold <= 0 || |
132 | 0 | chanrec->nick_count <= netsplit_nicks_hide_threshold) { |
133 | 0 | if (splitchan->op) |
134 | 0 | g_string_append_c(chanrec->nicks, '@'); |
135 | 0 | else if (splitchan->voice) |
136 | 0 | g_string_append_c(chanrec->nicks, '+'); |
137 | 0 | g_string_append_printf(chanrec->nicks, "%s, ", split->nick); |
138 | |
|
139 | 0 | if (chanrec->nick_count == netsplit_max_nicks) |
140 | 0 | chanrec->maxnickpos = chanrec->nicks->len; |
141 | 0 | } |
142 | 0 | } |
143 | 0 | } |
144 | | |
145 | | static void print_server_splits(IRC_SERVER_REC *server, TEMP_SPLIT_REC *rec, const char *filter_channel) |
146 | 0 | { |
147 | 0 | GString *destservers; |
148 | 0 | char *sourceserver; |
149 | 0 | GSList *tmp; |
150 | |
|
151 | 0 | g_return_if_fail(rec->servers != NULL); |
152 | | |
153 | 0 | destservers = g_string_new(NULL); |
154 | 0 | for (tmp = rec->servers; tmp != NULL; tmp = tmp->next) { |
155 | 0 | NETSPLIT_SERVER_REC *rec = tmp->data; |
156 | |
|
157 | 0 | if (rec->prints > 0) { |
158 | 0 | g_string_append_printf(destservers, "%s, ", |
159 | 0 | rec->destserver); |
160 | 0 | } |
161 | 0 | } |
162 | 0 | if (destservers->len == 0) { |
163 | | /* no nicks to print in this server */ |
164 | 0 | g_string_free(destservers, TRUE); |
165 | 0 | return; |
166 | 0 | } |
167 | 0 | g_string_truncate(destservers, destservers->len-2); |
168 | |
|
169 | 0 | sourceserver = ((NETSPLIT_SERVER_REC *) (rec->servers->data))->server; |
170 | 0 | for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) { |
171 | 0 | TEMP_SPLIT_CHAN_REC *chan = tmp->data; |
172 | |
|
173 | 0 | if (filter_channel != NULL && |
174 | 0 | strcasecmp(chan->name, filter_channel) != 0) |
175 | 0 | continue; |
176 | | |
177 | 0 | g_string_truncate(chan->nicks, chan->nicks->len-2); |
178 | |
|
179 | 0 | if (netsplit_max_nicks > 0 && |
180 | 0 | chan->nick_count > netsplit_max_nicks) { |
181 | 0 | g_string_truncate(chan->nicks, chan->maxnickpos); |
182 | 0 | printformat(server, chan->name, MSGLEVEL_QUITS, |
183 | 0 | IRCTXT_NETSPLIT_MORE, sourceserver, |
184 | 0 | destservers->str, chan->nicks->str, |
185 | 0 | chan->nick_count - netsplit_max_nicks); |
186 | 0 | } else { |
187 | 0 | printformat(server, chan->name, MSGLEVEL_QUITS, |
188 | 0 | IRCTXT_NETSPLIT, sourceserver, |
189 | 0 | destservers->str, chan->nicks->str); |
190 | 0 | } |
191 | 0 | } |
192 | |
|
193 | 0 | g_string_free(destservers, TRUE); |
194 | 0 | } |
195 | | |
196 | | static void temp_split_chan_free(TEMP_SPLIT_CHAN_REC *rec) |
197 | 0 | { |
198 | 0 | g_string_free(rec->nicks, TRUE); |
199 | 0 | g_free(rec); |
200 | 0 | } |
201 | | |
202 | | static void print_splits(IRC_SERVER_REC *server, const char *filter_channel) |
203 | 0 | { |
204 | 0 | TEMP_SPLIT_REC temp; |
205 | 0 | GSList *servers; |
206 | |
|
207 | 0 | printing_splits = TRUE; |
208 | |
|
209 | 0 | servers = g_slist_copy(server->split_servers); |
210 | 0 | while (servers != NULL) { |
211 | 0 | NETSPLIT_SERVER_REC *sserver = servers->data; |
212 | | |
213 | | /* get all the splitted servers that have the same |
214 | | source server */ |
215 | 0 | temp.servers = get_source_servers(sserver->server, &servers); |
216 | 0 | temp.server_rec = server; |
217 | 0 | temp.channels = NULL; |
218 | |
|
219 | 0 | g_hash_table_foreach(server->splits, |
220 | 0 | (GHFunc) get_server_splits, &temp); |
221 | 0 | print_server_splits(server, &temp, filter_channel); |
222 | |
|
223 | 0 | g_slist_foreach(temp.channels, |
224 | 0 | (GFunc) temp_split_chan_free, NULL); |
225 | 0 | g_slist_free(temp.servers); |
226 | 0 | g_slist_free(temp.channels); |
227 | 0 | } |
228 | |
|
229 | 0 | printing_splits = FALSE; |
230 | 0 | } |
231 | | |
232 | | static int check_server_splits(IRC_SERVER_REC *server) |
233 | 0 | { |
234 | 0 | time_t last; |
235 | |
|
236 | 0 | g_return_val_if_fail(IS_IRC_SERVER(server), FALSE); |
237 | | |
238 | 0 | last = get_last_split(server); |
239 | 0 | if (time(NULL)-last < SPLIT_WAIT_TIME) |
240 | 0 | return FALSE; |
241 | | |
242 | 0 | print_splits(server, NULL); |
243 | 0 | return TRUE; |
244 | 0 | } |
245 | | |
246 | | /* something is going to be printed to screen, print our current netsplit |
247 | | message before it. */ |
248 | | static void sig_print_starting(TEXT_DEST_REC *dest) |
249 | 0 | { |
250 | 0 | IRC_SERVER_REC *rec; |
251 | |
|
252 | 0 | if (printing_splits) |
253 | 0 | return; |
254 | | |
255 | 0 | if (!IS_IRC_SERVER(dest->server)) |
256 | 0 | return; |
257 | | |
258 | 0 | rec = IRC_SERVER(dest->server); |
259 | 0 | if (rec->split_servers != NULL) { |
260 | | /* if split_servers exists, the server rec should be |
261 | | still valid. otherwise, calling server->ischannel |
262 | | may not be safe. */ |
263 | 0 | if (dest->target != NULL && !server_ischannel((SERVER_REC *) rec, dest->target)) |
264 | 0 | return; |
265 | | |
266 | 0 | print_splits(rec, NULL); |
267 | 0 | } |
268 | 0 | } |
269 | | |
270 | | static int sig_check_splits(void) |
271 | 0 | { |
272 | 0 | GSList *tmp; |
273 | 0 | int stop; |
274 | |
|
275 | 0 | stop = TRUE; |
276 | 0 | for (tmp = servers; tmp != NULL; tmp = tmp->next) { |
277 | 0 | IRC_SERVER_REC *rec = tmp->data; |
278 | |
|
279 | 0 | if (!IS_IRC_SERVER(rec)) |
280 | 0 | continue; |
281 | | |
282 | 0 | if (rec->split_servers != NULL) { |
283 | 0 | if (!check_server_splits(rec)) |
284 | 0 | stop = FALSE; |
285 | 0 | } |
286 | 0 | } |
287 | |
|
288 | 0 | if (stop) { |
289 | 0 | g_source_remove(split_tag); |
290 | 0 | signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting); |
291 | 0 | split_tag = -1; |
292 | 0 | } |
293 | 0 | return 1; |
294 | 0 | } |
295 | | |
296 | | static void sig_netsplit_servers(void) |
297 | 0 | { |
298 | 0 | if (settings_get_bool("hide_netsplit_quits") && split_tag == -1) { |
299 | 0 | split_tag = g_timeout_add(1000, |
300 | 0 | (GSourceFunc) sig_check_splits, |
301 | 0 | NULL); |
302 | 0 | signal_add("print starting", (SIGNAL_FUNC) sig_print_starting); |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | | static int split_equal(NETSPLIT_REC *n1, NETSPLIT_REC *n2) |
307 | 0 | { |
308 | 0 | return g_ascii_strcasecmp(n1->nick, n2->nick); |
309 | 0 | } |
310 | | |
311 | | static void split_get(void *key, NETSPLIT_REC *rec, GSList **list) |
312 | 0 | { |
313 | 0 | *list = g_slist_insert_sorted(*list, rec, |
314 | 0 | (GCompareFunc) split_equal); |
315 | 0 | } |
316 | | |
317 | | static void split_print(NETSPLIT_REC *rec, SERVER_REC *server) |
318 | 0 | { |
319 | 0 | NETSPLIT_CHAN_REC *chan; |
320 | 0 | char *chanstr; |
321 | |
|
322 | 0 | chan = rec->channels->data; |
323 | 0 | chanstr = chan == NULL ? |
324 | 0 | g_strdup("") : |
325 | 0 | g_strconcat(chan->op ? "@" : (chan->voice ? "+" : ""), chan->name, NULL); |
326 | |
|
327 | 0 | printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_LINE, |
328 | 0 | rec->nick, chanstr, rec->server->server, |
329 | 0 | rec->server->destserver); |
330 | |
|
331 | 0 | g_free(chanstr); |
332 | 0 | } |
333 | | |
334 | | /* SYNTAX: NETSPLIT */ |
335 | | static void cmd_netsplit(const char *data, IRC_SERVER_REC *server) |
336 | 0 | { |
337 | 0 | GSList *list; |
338 | |
|
339 | 0 | CMD_IRC_SERVER(server); |
340 | | |
341 | 0 | if (server->split_servers == NULL) { |
342 | 0 | printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, |
343 | 0 | IRCTXT_NO_NETSPLITS); |
344 | 0 | return; |
345 | 0 | } |
346 | | |
347 | 0 | printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_HEADER); |
348 | |
|
349 | 0 | list = NULL; |
350 | 0 | g_hash_table_foreach(server->splits, (GHFunc) split_get, &list); |
351 | 0 | g_slist_foreach(list, (GFunc) split_print, server); |
352 | 0 | g_slist_free(list); |
353 | |
|
354 | 0 | printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_FOOTER); |
355 | 0 | } |
356 | | |
357 | | static void read_settings(void) |
358 | 2 | { |
359 | 2 | netsplit_max_nicks = settings_get_int("netsplit_max_nicks"); |
360 | 2 | netsplit_nicks_hide_threshold = |
361 | 2 | settings_get_int("netsplit_nicks_hide_threshold"); |
362 | 2 | if (netsplit_nicks_hide_threshold < netsplit_max_nicks) |
363 | 0 | netsplit_max_nicks = netsplit_nicks_hide_threshold; |
364 | 2 | } |
365 | | |
366 | | void fe_netsplit_init(void) |
367 | 2 | { |
368 | 2 | settings_add_int("misc", "netsplit_max_nicks", 10); |
369 | 2 | settings_add_int("misc", "netsplit_nicks_hide_threshold", 15); |
370 | 2 | split_tag = -1; |
371 | 2 | printing_splits = FALSE; |
372 | | |
373 | 2 | read_settings(); |
374 | 2 | signal_add("netsplit new", (SIGNAL_FUNC) sig_netsplit_servers); |
375 | 2 | signal_add("setup changed", (SIGNAL_FUNC) read_settings); |
376 | 2 | command_bind_irc("netsplit", NULL, (SIGNAL_FUNC) cmd_netsplit); |
377 | 2 | } |
378 | | |
379 | | void fe_netsplit_deinit(void) |
380 | 0 | { |
381 | 0 | if (split_tag != -1) { |
382 | 0 | g_source_remove(split_tag); |
383 | 0 | signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting); |
384 | 0 | } |
385 | |
|
386 | 0 | signal_remove("netsplit new", (SIGNAL_FUNC) sig_netsplit_servers); |
387 | 0 | signal_remove("setup changed", (SIGNAL_FUNC) read_settings); |
388 | 0 | command_unbind("netsplit", (SIGNAL_FUNC) cmd_netsplit); |
389 | 0 | } |