source: irc_user.c @ 5c90890

Last change on this file since 5c90890 was 5c90890, checked in by dequis <dx@…>, at 2018-07-12T08:54:12Z

Stop using the irc->users linked list, use the hash table instead

irc_user_new() mentions that the reason this list is kept is for easy
iteration. Luckily, this is the future, and GHashTableIter exists now.

The main point of this is to get rid of the g_slist_insert_sorted() in
irc_user_set_nick() which is a particularly slow part of loading large
user lists, and scales poorly

In a test with discord, the GUILD_SYNC event is now 4 times faster, on
top of the improvements of the other bee_user hash tables patch.
Combining both patches it went from 136 to 6 seconds for 50k members.

  • Property mode set to 100644
File size: 6.4 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* Stuff to handle, save and search IRC buddies                         */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include "bitlbee.h"
27#include "ipc.h"
28
29irc_user_t *irc_user_new(irc_t *irc, const char *nick)
30{
31        irc_user_t *iu = g_new0(irc_user_t, 1);
32
33        iu->irc = irc;
34        iu->nick = g_strdup(nick);
35        iu->user = iu->host = iu->fullname = iu->nick;
36
37        iu->key = g_strdup(nick);
38        nick_lc(irc, iu->key);
39        g_hash_table_insert(irc->nick_user_hash, iu->key, iu);
40
41        return iu;
42}
43
44int irc_user_free(irc_t *irc, irc_user_t *iu)
45{
46        static struct im_connection *last_ic;
47        static char *msg;
48
49        if (!iu) {
50                return 0;
51        }
52
53        if (iu->bu &&
54            (iu->bu->ic->flags & OPT_LOGGING_OUT) &&
55            iu->bu->ic != last_ic) {
56                char host_prefix[] = "bitlbee.";
57                char *s;
58
59                /* Irssi recognises netsplits by quitmsgs with two
60                   hostnames, where a hostname is a "word" with one
61                   of more dots. Mangle no-dot hostnames a bit. */
62                if (strchr(irc->root->host, '.')) {
63                        *host_prefix = '\0';
64                }
65
66                last_ic = iu->bu->ic;
67                g_free(msg);
68                if (!set_getbool(&irc->b->set, "simulate_netsplit")) {
69                        msg = g_strdup("Account off-line");
70                } else if ((s = strchr(iu->bu->ic->acc->user, '@'))) {
71                        msg = g_strdup_printf("%s%s %s", host_prefix,
72                                              irc->root->host, s + 1);
73                } else {
74                        msg = g_strdup_printf("%s%s %s.%s",
75                                              host_prefix, irc->root->host,
76                                              iu->bu->ic->acc->prpl->name, irc->root->host);
77                }
78        } else if (!iu->bu || !(iu->bu->ic->flags & OPT_LOGGING_OUT)) {
79                g_free(msg);
80                msg = g_strdup("Removed");
81                last_ic = NULL;
82        }
83        irc_user_quit(iu, msg);
84
85        g_hash_table_remove(irc->nick_user_hash, iu->key);
86
87        g_free(iu->nick);
88        if (iu->nick != iu->user) {
89                g_free(iu->user);
90        }
91        if (iu->nick != iu->host) {
92                g_free(iu->host);
93        }
94        if (iu->nick != iu->fullname) {
95                g_free(iu->fullname);
96        }
97        g_free(iu->pastebuf);
98        if (iu->pastebuf_timer) {
99                b_event_remove(iu->pastebuf_timer);
100        }
101        g_free(iu->key);
102        g_free(iu);
103
104        return 1;
105}
106
107irc_user_t *irc_user_by_name(irc_t *irc, const char *nick)
108{
109        char key[strlen(nick) + 1];
110
111        strcpy(key, nick);
112        if (nick_lc(irc, key)) {
113                return g_hash_table_lookup(irc->nick_user_hash, key);
114        } else {
115                return NULL;
116        }
117}
118
119int irc_user_set_nick(irc_user_t *iu, const char *new)
120{
121        irc_t *irc = iu->irc;
122        irc_user_t *new_iu;
123        char key[strlen(new) + 1];
124        GSList *cl;
125
126        strcpy(key, new);
127        if (iu == NULL || !nick_lc(irc, key) ||
128            ((new_iu = irc_user_by_name(irc, new)) && new_iu != iu)) {
129                return 0;
130        }
131
132        for (cl = irc->channels; cl; cl = cl->next) {
133                irc_channel_t *ic = cl->data;
134
135                /* Send a NICK update if we're renaming our user, or someone
136                   who's in the same channel like our user. */
137                if (iu == irc->user ||
138                    ((ic->flags & IRC_CHANNEL_JOINED) &&
139                     irc_channel_has_user(ic, iu))) {
140                        irc_send_nick(iu, new);
141                        break;
142                }
143        }
144
145        g_hash_table_remove(irc->nick_user_hash, iu->key);
146
147        if (iu->nick == iu->user) {
148                iu->user = NULL;
149        }
150        if (iu->nick == iu->host) {
151                iu->host = NULL;
152        }
153        if (iu->nick == iu->fullname) {
154                iu->fullname = NULL;
155        }
156        g_free(iu->nick);
157        iu->nick = g_strdup(new);
158        if (iu->user == NULL) {
159                iu->user = g_strdup(iu->nick);
160        }
161        if (iu->host == NULL) {
162                iu->host = g_strdup(iu->nick);
163        }
164        if (iu->fullname == NULL) {
165                iu->fullname = g_strdup(iu->nick);
166        }
167
168        g_free(iu->key);
169        iu->key = g_strdup(key);
170        g_hash_table_insert(irc->nick_user_hash, iu->key, iu);
171
172        if (iu == irc->user) {
173                ipc_to_master_str("NICK :%s\r\n", new);
174        }
175
176        return 1;
177}
178
179gint irc_user_cmp(gconstpointer a_, gconstpointer b_)
180{
181        const irc_user_t *a = a_, *b = b_;
182
183        return strcmp(a->key, b->key);
184}
185
186const char *irc_user_get_away(irc_user_t *iu)
187{
188        irc_t *irc = iu->irc;
189        bee_user_t *bu = iu->bu;
190
191        if (iu == irc->user) {
192                return set_getstr(&irc->b->set, "away");
193        } else if (bu) {
194                if (!bu->flags & BEE_USER_ONLINE) {
195                        return "Offline";
196                } else if (bu->flags & BEE_USER_AWAY) {
197                        if (bu->status_msg) {
198                                static char ret[MAX_STRING];
199                                g_snprintf(ret, MAX_STRING - 1, "%s (%s)",
200                                           bu->status ? : "Away", bu->status_msg);
201                                return ret;
202                        } else {
203                                return bu->status ? : "Away";
204                        }
205                }
206        }
207
208        return NULL;
209}
210
211void irc_user_quit(irc_user_t *iu, const char *msg)
212{
213        GSList *l;
214        gboolean send_quit = FALSE;
215
216        if (!iu) {
217                return;
218        }
219
220        for (l = iu->irc->channels; l; l = l->next) {
221                irc_channel_t *ic = l->data;
222                send_quit |= irc_channel_del_user(ic, iu, IRC_CDU_SILENT, NULL) &&
223                             (ic->flags & IRC_CHANNEL_JOINED);
224        }
225
226        if (send_quit) {
227                irc_send_quit(iu, msg);
228        }
229}
230
231/* User-type dependent functions, for root/NickServ: */
232static gboolean root_privmsg(irc_user_t *iu, const char *msg)
233{
234        char cmd[strlen(msg) + 1];
235
236        strcpy(cmd, msg);
237        root_command_string(iu->irc, cmd);
238
239        return TRUE;
240}
241
242static gboolean root_ctcp(irc_user_t *iu, char * const *ctcp)
243{
244        if (g_strcasecmp(ctcp[0], "VERSION") == 0) {
245                irc_send_msg_f(iu, "NOTICE", iu->irc->user->nick, "\001%s %s\001",
246                               ctcp[0], PACKAGE " " BITLBEE_VERSION);
247        } else if (g_strcasecmp(ctcp[0], "PING") == 0) {
248                irc_send_msg_f(iu, "NOTICE", iu->irc->user->nick, "\001%s %s\001",
249                               ctcp[0], ctcp[1] ? : "");
250        }
251
252        return TRUE;
253}
254
255const struct irc_user_funcs irc_user_root_funcs = {
256        root_privmsg,
257        root_ctcp,
258};
259
260/* Echo to yourself: */
261static gboolean self_privmsg(irc_user_t *iu, const char *msg)
262{
263        irc_send_msg(iu, "PRIVMSG", iu->nick, msg, NULL);
264
265        return TRUE;
266}
267
268const struct irc_user_funcs irc_user_self_funcs = {
269        self_privmsg,
270};
Note: See TracBrowser for help on using the repository browser.