Added SILC Thread Queue API
[runtime.git] / apps / irssi / src / core / nicklist.c
1 /*
2  nicklist.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 #include "module.h"
22 #include "signals.h"
23 #include "misc.h"
24
25 #include "servers.h"
26 #include "channels.h"
27 #include "nicklist.h"
28 #include "masks.h"
29
30 #define isalnumhigh(a) \
31         (i_isalnum(a) || (unsigned char) (a) >= 128)
32
33 static void nick_hash_add(CHANNEL_REC *channel, NICK_REC *nick)
34 {
35         NICK_REC *list;
36
37         nick->next = NULL;
38
39         list = g_hash_table_lookup(channel->nicks, nick->nick);
40         if (list == NULL)
41                 g_hash_table_insert(channel->nicks, nick->nick, nick);
42         else {
43                 /* multiple nicks with same name */
44                 while (list->next != NULL)
45                         list = list->next;
46                 list->next = nick;
47         }
48
49         if (nick == channel->ownnick) {
50                 /* move our own nick to beginning of the nick list.. */
51                 nicklist_set_own(channel, nick);
52         }
53 }
54
55 static void nick_hash_remove(CHANNEL_REC *channel, NICK_REC *nick)
56 {
57         NICK_REC *list;
58
59         list = g_hash_table_lookup(channel->nicks, nick->nick);
60         if (list == NULL)
61                 return;
62
63         if (list == nick || list->next == NULL) {
64                 g_hash_table_remove(channel->nicks, nick->nick);
65                 if (list->next != NULL) {
66                         g_hash_table_insert(channel->nicks, nick->next->nick,
67                                             nick->next);
68                 }
69         } else {
70                 while (list->next != nick)
71                         list = list->next;
72                 list->next = nick->next;
73         }
74 }
75
76 /* Add new nick to list */
77 void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick)
78 {
79         /*MODULE_DATA_INIT(nick);*/
80
81         nick->type = module_get_uniq_id("NICK", 0);
82         nick->chat_type = channel->chat_type;
83
84         nick_hash_add(channel, nick);
85         signal_emit("nicklist new", 2, channel, nick);
86 }
87
88 /* Set host address for nick */
89 void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host)
90 {
91         g_return_if_fail(channel != NULL);
92         g_return_if_fail(nick != NULL);
93         g_return_if_fail(host != NULL);
94
95         g_free_not_null(nick->host);
96         nick->host = g_strdup(host);
97
98         signal_emit("nicklist host changed", 2, channel, nick);
99 }
100
101 static void nicklist_destroy(CHANNEL_REC *channel, NICK_REC *nick)
102 {
103         signal_emit("nicklist remove", 2, channel, nick);
104
105         if (channel->ownnick == nick)
106                 channel->ownnick = NULL;
107
108         /*MODULE_DATA_DEINIT(nick);*/
109         g_free(nick->nick);
110         g_free_not_null(nick->realname);
111         g_free_not_null(nick->host);
112         g_free(nick);
113 }
114
115 /* Remove nick from list */
116 void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
117 {
118         g_return_if_fail(IS_CHANNEL(channel));
119         g_return_if_fail(nick != NULL);
120
121         nick_hash_remove(channel, nick);
122         nicklist_destroy(channel, nick);
123 }
124
125 static void nicklist_rename_list(SERVER_REC *server, void *new_nick_id,
126                                  const char *old_nick, const char *new_nick,
127                                  GSList *nicks)
128 {
129         CHANNEL_REC *channel;
130         NICK_REC *nickrec;
131         GSList *tmp;
132
133         for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
134                 channel = tmp->data;
135                 nickrec = tmp->next->data;
136
137                 /* remove old nick from hash table */
138                 nick_hash_remove(channel, nickrec);
139
140                 if (new_nick_id != NULL)
141                         nickrec->unique_id = new_nick_id;
142
143                 g_free(nickrec->nick);
144                 nickrec->nick = g_strdup(new_nick);
145
146                 /* add new nick to hash table */
147                 nick_hash_add(channel, nickrec);
148
149                 signal_emit("nicklist changed", 3, channel, nickrec, old_nick);
150         }
151         g_slist_free(nicks);
152 }
153
154 void nicklist_rename(SERVER_REC *server, const char *old_nick,
155                      const char *new_nick)
156 {
157         nicklist_rename_list(server, NULL, old_nick, new_nick,
158                              nicklist_get_same(server, old_nick));
159 }
160
161 void nicklist_rename_unique(SERVER_REC *server,
162                             void *old_nick_id, const char *old_nick,
163                             void *new_nick_id, const char *new_nick)
164 {
165         nicklist_rename_list(server, new_nick_id, old_nick, new_nick,
166                              nicklist_get_same_unique(server, old_nick_id));
167 }
168
169 static NICK_REC *nicklist_find_wildcards(CHANNEL_REC *channel,
170                                          const char *mask)
171 {
172         GSList *nicks, *tmp;
173         NICK_REC *nick;
174
175         nicks = nicklist_getnicks(channel);
176         nick = NULL;
177         for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
178                 nick = tmp->data;
179
180                 if (mask_match_address(channel->server, mask,
181                                        nick->nick, nick->host))
182                         break;
183         }
184         g_slist_free(nicks);
185         return tmp == NULL ? NULL : nick;
186 }
187
188 GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask)
189 {
190         GSList *nicks, *tmp, *next;
191
192         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
193         g_return_val_if_fail(mask != NULL, NULL);
194
195         nicks = nicklist_getnicks(channel);
196         for (tmp = nicks; tmp != NULL; tmp = next) {
197                 NICK_REC *nick = tmp->data;
198
199                 next = tmp->next;
200                 if (!mask_match_address(channel->server, mask,
201                                         nick->nick, nick->host))
202                         nicks = g_slist_remove(nicks, tmp->data);
203         }
204
205         return nicks;
206 }
207
208 /* Find nick */
209 NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick)
210 {
211         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
212         g_return_val_if_fail(nick != NULL, NULL);
213
214         return g_hash_table_lookup(channel->nicks, nick);
215 }
216
217 NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick,
218                                void *id)
219 {
220         NICK_REC *rec;
221
222         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
223         g_return_val_if_fail(nick != NULL, NULL);
224
225         rec = g_hash_table_lookup(channel->nicks, nick);
226         while (rec != NULL && rec->unique_id != id)
227                 rec = rec->next;
228
229         return rec;
230 }
231
232 /* Find nick mask, wildcards allowed */
233 NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask)
234 {
235         NICK_REC *nickrec;
236         char *nick, *host;
237
238         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
239         g_return_val_if_fail(mask != NULL, NULL);
240
241         nick = g_strdup(mask);
242         host = strchr(nick, '!');
243         if (host != NULL) *host++ = '\0';
244
245         if (strchr(nick, '*') || strchr(nick, '?')) {
246                 g_free(nick);
247                 return nicklist_find_wildcards(channel, mask);
248         }
249
250         nickrec = g_hash_table_lookup(channel->nicks, nick);
251
252         if (host != NULL) {
253                 while (nickrec != NULL) {
254                         if (nickrec->host != NULL &&
255                             match_wildcards(host, nickrec->host))
256                                 break; /* match */
257                         nickrec = nickrec->next;
258                 }
259         }
260         g_free(nick);
261         return nickrec;
262 }
263
264 static void get_nicks_hash(gpointer key, NICK_REC *rec, GSList **list)
265 {
266         while (rec != NULL) {
267                 *list = g_slist_append(*list, rec);
268                 rec = rec->next;
269         }
270 }
271
272 /* Get list of nicks */
273 GSList *nicklist_getnicks(CHANNEL_REC *channel)
274 {
275         GSList *list;
276
277         g_return_val_if_fail(IS_CHANNEL(channel), NULL);
278
279         list = NULL;
280         g_hash_table_foreach(channel->nicks, (GHFunc) get_nicks_hash, &list);
281         return list;
282 }
283
284 typedef struct {
285         CHANNEL_REC *channel;
286         const char *nick;
287         GSList *list;
288 } NICKLIST_GET_SAME_REC;
289
290 static void get_nicks_same_hash(gpointer key, NICK_REC *nick,
291                                 NICKLIST_GET_SAME_REC *rec)
292 {
293         while (nick != NULL) {
294                 if (g_strcasecmp(nick->nick, rec->nick) == 0) {
295                         rec->list = g_slist_append(rec->list, rec->channel);
296                         rec->list = g_slist_append(rec->list, nick);
297                 }
298
299                 nick = nick->next;
300         }
301 }
302
303 GSList *nicklist_get_same(SERVER_REC *server, const char *nick)
304 {
305         NICKLIST_GET_SAME_REC rec;
306         GSList *tmp;
307
308         g_return_val_if_fail(IS_SERVER(server), NULL);
309
310         rec.nick = nick;
311         rec.list = NULL;
312         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
313                 rec.channel = tmp->data;
314                 g_hash_table_foreach(rec.channel->nicks,
315                                      (GHFunc) get_nicks_same_hash, &rec);
316         }
317         return rec.list;
318 }
319
320 typedef struct {
321         CHANNEL_REC *channel;
322         void *id;
323         GSList *list;
324 } NICKLIST_GET_SAME_UNIQUE_REC;
325
326 static void get_nicks_same_hash_unique(gpointer key, NICK_REC *nick,
327                                        NICKLIST_GET_SAME_UNIQUE_REC *rec)
328 {
329         while (nick != NULL) {
330                 if (nick->unique_id == rec->id) {
331                         rec->list = g_slist_append(rec->list, rec->channel);
332                         rec->list = g_slist_append(rec->list, nick);
333                         break;
334                 }
335
336                 nick = nick->next;
337         }
338 }
339
340 GSList *nicklist_get_same_unique(SERVER_REC *server, void *id)
341 {
342         NICKLIST_GET_SAME_UNIQUE_REC rec;
343         GSList *tmp;
344
345         g_return_val_if_fail(IS_SERVER(server), NULL);
346         g_return_val_if_fail(id != NULL, NULL);
347
348         rec.id = id;
349         rec.list = NULL;
350         for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
351                 rec.channel = tmp->data;
352                 g_hash_table_foreach(rec.channel->nicks,
353                                      (GHFunc) get_nicks_same_hash_unique,
354                                      &rec);
355         }
356         return rec.list;
357 }
358
359 #if GLIB_MAJOR_VERSION < 2
360 /* glib1 doesn't have g_slist_sort_with_data, so non-standard prefixes won't be sorted correctly */
361 int nicklist_compare_glib1(NICK_REC *p1, NICK_REC *p2)
362 {
363         return nicklist_compare(p1, p2, NULL);
364 }
365 #endif
366
367 /* nick record comparision for sort functions */
368 int nicklist_compare(NICK_REC *p1, NICK_REC *p2, const char *nick_prefix)
369 {
370         int status1, status2;
371         
372         if (p1 == NULL) return -1;
373         if (p2 == NULL) return 1;
374
375         /* we assign each status (op, halfop, voice, normal) a number
376          * and compare them. this is easier than 100,000 if's and
377          * returns :-)
378          * -- yath */
379
380         if (p1->other) {
381                 const char *other = (nick_prefix == NULL) ? NULL : strchr(nick_prefix, p1->other);
382                 status1 = (other == NULL) ? 5 : 1000 - (other - nick_prefix);
383         } else if (p1->op)
384                 status1 = 4;
385         else if (p1->halfop)
386                 status1 = 3;
387         else if (p1->voice)
388                 status1 = 2;
389         else
390                 status1 = 1;
391
392         if (p2->other) {
393                 const char *other = (nick_prefix == NULL) ? NULL : strchr(nick_prefix, p2->other);
394                 status2 = (other == NULL) ? 5 : 1000 - (other - nick_prefix);
395         } else if (p2->op)
396                 status2 = 4;
397         else if (p2->halfop)
398                 status2 = 3;
399         else if (p2->voice)
400                 status2 = 2;
401         else
402                 status2 = 1;
403         
404         if (status1 < status2)
405                 return 1;
406         else if (status1 > status2)
407                 return -1;
408         
409         return g_strcasecmp(p1->nick, p2->nick);
410 }
411
412 static void nicklist_update_flags_list(SERVER_REC *server, int gone,
413                                        int serverop, GSList *nicks)
414 {
415         GSList *tmp;
416         CHANNEL_REC *channel;
417         NICK_REC *rec;
418
419         g_return_if_fail(IS_SERVER(server));
420
421         for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
422                 channel = tmp->data;
423                 rec = tmp->next->data;
424
425                 rec->last_check = time(NULL);
426
427                 if (gone != -1 && (int)rec->gone != gone) {
428                         rec->gone = gone;
429                         signal_emit("nicklist gone changed", 2, channel, rec);
430                 }
431
432                 if (serverop != -1 && (int)rec->serverop != serverop) {
433                         rec->serverop = serverop;
434                         signal_emit("nicklist serverop changed", 2, channel, rec);
435                 }
436         }
437         g_slist_free(nicks);
438 }
439
440 void nicklist_update_flags(SERVER_REC *server, const char *nick,
441                            int gone, int serverop)
442 {
443         nicklist_update_flags_list(server, gone, serverop,
444                                    nicklist_get_same(server, nick));
445 }
446
447 void nicklist_update_flags_unique(SERVER_REC *server, void *id,
448                                   int gone, int serverop)
449 {
450         nicklist_update_flags_list(server, gone, serverop,
451                                    nicklist_get_same_unique(server, id));
452 }
453
454 /* Specify which nick in channel is ours */
455 void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick)
456 {
457         NICK_REC *first, *next;
458
459         channel->ownnick = nick;
460
461         /* move our nick in the list to first, makes some things easier
462            (like handling multiple identical nicks in fe-messages.c) */
463         first = g_hash_table_lookup(channel->nicks, nick->nick);
464         if (first->next == NULL)
465                 return;
466
467         next = nick->next;
468         nick->next = first;
469
470         while (first->next != nick)
471                 first = first->next;
472         first->next = next;
473
474         g_hash_table_insert(channel->nicks, nick->nick, nick);
475 }
476
477 static void sig_channel_created(CHANNEL_REC *channel)
478 {
479         g_return_if_fail(IS_CHANNEL(channel));
480
481         channel->nicks = g_hash_table_new((GHashFunc) g_istr_hash,
482                                           (GCompareFunc) g_istr_equal);
483 }
484
485 static void nicklist_remove_hash(gpointer key, NICK_REC *nick,
486                                  CHANNEL_REC *channel)
487 {
488         NICK_REC *next;
489
490         while (nick != NULL) {
491                 next = nick->next;
492                 nicklist_destroy(channel, nick);
493                 nick = next;
494         }
495 }
496
497 static void sig_channel_destroyed(CHANNEL_REC *channel)
498 {
499         g_return_if_fail(IS_CHANNEL(channel));
500
501         g_hash_table_foreach(channel->nicks,
502                              (GHFunc) nicklist_remove_hash, channel);
503         g_hash_table_destroy(channel->nicks);
504 }
505
506 static NICK_REC *nick_nfind(CHANNEL_REC *channel, const char *nick, int len)
507 {
508         NICK_REC *rec;
509         char *tmpnick;
510
511         tmpnick = g_strndup(nick, len);
512         rec = g_hash_table_lookup(channel->nicks, tmpnick);
513
514         if (rec != NULL) {
515                 /* if there's multiple, get the one with identical case */
516                 while (rec->next != NULL) {
517                         if (strcmp(rec->nick, tmpnick) == 0)
518                                 break;
519                         rec = rec->next;
520                 }
521         }
522
523         g_free(tmpnick);
524         return rec;
525 }
526
527 /* Check is `msg' is meant for `nick'. */
528 int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick)
529 {
530         const char *msgstart, *orignick;
531         int len, fullmatch;
532
533         g_return_val_if_fail(nick != NULL, FALSE);
534         g_return_val_if_fail(msg != NULL, FALSE);
535
536         if (channel != NULL && channel->server->nick_match_msg != NULL)
537                 return channel->server->nick_match_msg(msg, nick);
538
539         /* first check for identical match */
540         len = strlen(nick);
541         if (g_strncasecmp(msg, nick, len) == 0 && !isalnumhigh((int) msg[len]))
542                 return TRUE;
543
544         orignick = nick;
545         for (;;) {
546                 nick = orignick;
547                 msgstart = msg;
548                 fullmatch = TRUE;
549
550                 /* check if it matches for alphanumeric parts of nick */
551                 while (*nick != '\0' && *msg != '\0') {
552                         if (i_toupper(*nick) == i_toupper(*msg)) {
553                                 /* total match */
554                                 msg++;
555                         } else if (i_isalnum(*msg) && !i_isalnum(*nick)) {
556                                 /* some strange char in your nick, pass it */
557                                 fullmatch = FALSE;
558                         } else
559                                 break;
560
561                         nick++;
562                 }
563
564                 if (msg != msgstart && !isalnumhigh(*msg)) {
565                         /* at least some of the chars in line matched the
566                            nick, and msg continue with non-alphanum character,
567                            this might be for us.. */
568                         if (*nick != '\0') {
569                                 /* remove the rest of the non-alphanum chars
570                                    from nick and check if it then matches. */
571                                 fullmatch = FALSE;
572                                 while (*nick != '\0' && !i_isalnum(*nick))
573                                         nick++;
574                         }
575
576                         if (*nick == '\0') {
577                                 /* yes, match! */
578                                 break;
579                         }
580                 }
581
582                 /* no match. check if this is a message to multiple people
583                    (like nick1,nick2: text) */
584                 while (*msg != '\0' && *msg != ' ' && *msg != ',') msg++;
585
586                 if (*msg != ',') {
587                         nick = orignick;
588                         break;
589                 }
590
591                 msg++;
592         }
593
594         if (*nick != '\0')
595                 return FALSE; /* didn't match */
596
597         if (fullmatch)
598                 return TRUE; /* matched without fuzzyness */
599
600         /* matched with some fuzzyness .. check if there's an exact match
601            for some other nick in the same channel. */
602         return nick_nfind(channel, msgstart, (int) (msg-msgstart)) == NULL;
603 }
604
605 void nicklist_init(void)
606 {
607         signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created);
608         signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
609 }
610
611 void nicklist_deinit(void)
612 {
613         signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created);
614         signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
615
616         module_uniq_destroy("NICK");
617 }