diff --git a/src/fe-common/irc/Makefile.am b/src/fe-common/irc/Makefile.am index 182cb9b0..eeb27f50 100644 --- a/src/fe-common/irc/Makefile.am +++ b/src/fe-common/irc/Makefile.am @@ -20,6 +20,7 @@ libfe_common_irc_la_SOURCES = \ fe-events.c \ fe-events-numeric.c \ fe-ignore.c \ + fe-netjoin.c \ fe-netsplit.c \ fe-query.c \ fe-common-irc.c \ diff --git a/src/fe-common/irc/fe-common-irc.c b/src/fe-common/irc/fe-common-irc.c index 25f4886b..fb31a44c 100644 --- a/src/fe-common/irc/fe-common-irc.c +++ b/src/fe-common/irc/fe-common-irc.c @@ -67,6 +67,9 @@ void irc_completion_deinit(void); void fe_netsplit_init(void); void fe_netsplit_deinit(void); +void fe_netjoin_init(void); +void fe_netjoin_deinit(void); + void irc_hilight_text_init(void); void irc_hilight_text_deinit(void); @@ -116,6 +119,7 @@ void fe_common_irc_init(void) fe_events_numeric_init(); fe_ignore_init(); fe_netsplit_init(); + fe_netjoin_init(); fe_query_init(); irc_completion_init(); irc_hilight_text_init(); @@ -137,6 +141,7 @@ void fe_common_irc_deinit(void) fe_events_numeric_deinit(); fe_ignore_deinit(); fe_netsplit_deinit(); + fe_netjoin_deinit(); fe_query_deinit(); irc_completion_deinit(); irc_hilight_text_deinit(); diff --git a/src/fe-common/irc/fe-events.c b/src/fe-common/irc/fe-events.c index ee52c917..f54275bc 100644 --- a/src/fe-common/irc/fe-events.c +++ b/src/fe-common/irc/fe-events.c @@ -208,6 +208,10 @@ static void event_join(const char *data, IRC_SERVER_REC *server, const char *nic g_return_if_fail(data != NULL); + if (settings_get_bool("hide_netsplit_quits") && + netsplit_is_join(server, nick, addr)) + return; + params = event_get_params(data, 1, &channel); tmp = strchr(channel, 7); /* ^G does something weird.. */ if (tmp != NULL) *tmp = '\0'; diff --git a/src/fe-common/irc/fe-netjoin.c b/src/fe-common/irc/fe-netjoin.c new file mode 100644 index 00000000..ffb1d93e --- /dev/null +++ b/src/fe-common/irc/fe-netjoin.c @@ -0,0 +1,307 @@ +/* + fe-netjoin.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "levels.h" +#include "misc.h" +#include "settings.h" + +#include "irc.h" +#include "irc-server.h" +#include "ignore.h" +#include "netsplit.h" + +#define NETJOIN_WAIT_TIME 2 /* how many seconds to wait for the netsplitted JOIN messages to stop */ +#define NETJOIN_MAX_WAIT 30 /* how many seconds to wait for nick to join to the rest of the channels she was before the netsplit */ + +typedef struct { + char *nick; + GSList *old_channels; + GSList *now_channels; +} NETJOIN_REC; + +typedef struct { + IRC_SERVER_REC *server; + time_t last_netjoin; + + GSList *netjoins; +} NETJOIN_SERVER_REC; + +typedef struct { + int count; + GString *nicks; +} TEMP_PRINT_REC; + +static int join_tag; +static int netjoin_max_nicks, hide_netsplit_quits; +static GSList *joinservers; + +static NETJOIN_SERVER_REC *netjoin_find_server(IRC_SERVER_REC *server) +{ + GSList *tmp; + + g_return_val_if_fail(server != NULL, NULL); + + for (tmp = joinservers; tmp != NULL; tmp = tmp->next) { + NETJOIN_SERVER_REC *rec = tmp->data; + + if (rec->server == server) + return rec; + } + + return NULL; +} + +static NETJOIN_REC *netjoin_add(IRC_SERVER_REC *server, const char *nick, GSList *channels) +{ + NETJOIN_REC *rec; + NETJOIN_SERVER_REC *srec; + + g_return_val_if_fail(server != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + g_return_val_if_fail(channels != NULL, NULL); + + rec = g_new0(NETJOIN_REC, 1); + rec->nick = g_strdup(nick); + while (channels != NULL) { + NETSPLIT_CHAN_REC *channel = channels->data; + + rec->old_channels = g_slist_append(rec->old_channels, g_strdup(channel->name)); + channels = channels->next; + } + + srec = netjoin_find_server(server); + if (srec == NULL) { + srec = g_new0(NETJOIN_SERVER_REC, 1); + srec->server = server; + joinservers = g_slist_append(joinservers, srec); + } + + srec->last_netjoin = time(NULL); + srec->netjoins = g_slist_append(srec->netjoins, rec); + return rec; +} + +static NETJOIN_REC *netjoin_find(IRC_SERVER_REC *server, const char *nick) +{ + NETJOIN_SERVER_REC *srec; + GSList *tmp; + + g_return_val_if_fail(server != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + + srec = netjoin_find_server(server); + if (srec == NULL) return NULL; + + for (tmp = srec->netjoins; tmp != NULL; tmp = tmp->next) { + NETJOIN_REC *rec = tmp->data; + + if (g_strcasecmp(rec->nick, nick) == 0) + return rec; + } + + return NULL; +} + +int netsplit_is_join(IRC_SERVER_REC *server, const char *nick, const char *address) +{ + return netsplit_find(server, nick, address) || + netjoin_find(server, nick); +} + +static void netjoin_remove(NETJOIN_SERVER_REC *server, NETJOIN_REC *rec) +{ + server->netjoins = g_slist_remove(server->netjoins, rec); + + g_slist_foreach(rec->old_channels, (GFunc) g_free, NULL); + g_slist_foreach(rec->now_channels, (GFunc) g_free, NULL); + g_slist_free(rec->old_channels); + g_slist_free(rec->now_channels); + + g_free(rec->nick); + g_free(rec); +} + +static void netjoin_server_remove(NETJOIN_SERVER_REC *server) +{ + joinservers = g_slist_remove(joinservers, server); + + while (server->netjoins != NULL) + netjoin_remove(server, server->netjoins->data); + g_free(server); +} + +static void print_channel_netjoins(char *channel, TEMP_PRINT_REC *rec, NETJOIN_SERVER_REC *server) +{ + if (rec->nicks->len > 0) + g_string_truncate(rec->nicks, rec->nicks->len-2); + + printformat(server->server, channel, MSGLEVEL_JOINS, + rec->count > netjoin_max_nicks ? IRCTXT_NETSPLIT_JOIN_MORE : IRCTXT_NETSPLIT_JOIN, + rec->nicks->str, rec->count-netjoin_max_nicks); + + g_string_free(rec->nicks, TRUE); + g_free(rec); + g_free(channel); +} + +static void print_netjoins(NETJOIN_SERVER_REC *server) +{ + TEMP_PRINT_REC *temp; + GHashTable *channels; + GSList *tmp, *next, *old; + + g_return_if_fail(server != NULL); + + /* save nicks to string, clear now_channels and remove the same + channels from old_channels list */ + channels = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + for (tmp = server->netjoins; tmp != NULL; tmp = next) { + NETJOIN_REC *rec = tmp->data; + + next = tmp->next; + while (rec->now_channels != NULL) { + char *channel = rec->now_channels->data; + + temp = g_hash_table_lookup(channels, channel); + if (temp == NULL) { + temp = g_new0(TEMP_PRINT_REC, 1); + temp->nicks = g_string_new(NULL); + g_hash_table_insert(channels, g_strdup(channel), temp); + } + + temp->count++; + if (temp->count <= netjoin_max_nicks) + g_string_sprintfa(temp->nicks, "%s, ", rec->nick); + + /* remove the channel from old_channels too */ + old = gslist_find_icase_string(rec->old_channels, channel); + if (old != NULL) { + g_free(old->data); + rec->old_channels = g_slist_remove(rec->old_channels, old->data); + } + + g_free(channel); + rec->now_channels = g_slist_remove(rec->now_channels, channel); + } + + if (rec->old_channels == NULL) + netjoin_remove(server, rec); + } + + g_hash_table_foreach(channels, (GHFunc) print_channel_netjoins, server); + g_hash_table_destroy(channels); + + if (server->netjoins == NULL) + netjoin_server_remove(server); +} + +static int sig_check_netjoins(void) +{ + GSList *tmp, *next; + int diff; + + for (tmp = joinservers; tmp != NULL; tmp = next) { + NETJOIN_SERVER_REC *server = tmp->data; + + next = tmp->next; + diff = time(NULL)-server->last_netjoin; + if (diff <= NETJOIN_WAIT_TIME) { + /* wait for more JOINs */ + continue; + } + + if (server->netjoins != NULL) + print_netjoins(server); + else if (diff >= NETJOIN_MAX_WAIT) { + /* waited long enough, remove the netjoin */ + netjoin_server_remove(server); + } + } + + if (joinservers == NULL) { + g_source_remove(join_tag); + join_tag = -1; + } + return 1; +} + +static void event_join(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address) +{ + NETSPLIT_REC *split; + NETJOIN_REC *netjoin; + char *params, *channel, *tmp; + + g_return_if_fail(data != NULL); + + split = netsplit_find(server, nick, address); + netjoin = netjoin_find(server, nick); + if (split == NULL && netjoin == NULL) + return; + + params = event_get_params(data, 1, &channel); + tmp = strchr(channel, 7); /* ^G does something weird.. */ + if (tmp != NULL) *tmp = '\0'; + + if (!ignore_check(server, nick, address, channel, NULL, MSGLEVEL_JOINS)) { + if (join_tag == -1) + join_tag = g_timeout_add(1000, (GSourceFunc) sig_check_netjoins, NULL); + + if (netjoin == NULL) + netjoin = netjoin_add(server, nick, split->channels); + + netjoin->now_channels = g_slist_append(netjoin->now_channels, g_strdup(channel)); + } + g_free(params); +} + +static void read_settings(void) +{ + int old_hide; + + old_hide = hide_netsplit_quits; + hide_netsplit_quits = settings_get_bool("hide_netsplit_quits"); + netjoin_max_nicks = settings_get_int("netjoin_max_nicks"); + + if (old_hide && !hide_netsplit_quits) + signal_remove("event join", (SIGNAL_FUNC) event_join); + else if (!old_hide && hide_netsplit_quits) + signal_add("event join", (SIGNAL_FUNC) event_join); +} + +void fe_netjoin_init(void) +{ + settings_add_int("misc", "netjoin_max_nicks", 10); + join_tag = -1; + + read_settings(); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void fe_netjoin_deinit(void) +{ + while (joinservers != NULL) + netjoin_server_remove(joinservers->data); + if (join_tag != -1) g_source_remove(join_tag); + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/fe-common/irc/fe-netjoin.h b/src/fe-common/irc/fe-netjoin.h new file mode 100644 index 00000000..ef2cabd5 --- /dev/null +++ b/src/fe-common/irc/fe-netjoin.h @@ -0,0 +1,6 @@ +#ifndef __FE_NETJOIN_H +#define __FE_NETJOIN_H + +int netsplit_is_join(IRC_SERVER_REC *server, const char *nick, const char *address); + +#endif diff --git a/src/fe-common/irc/fe-netsplit.c b/src/fe-common/irc/fe-netsplit.c index 1992ec14..9411b2e9 100644 --- a/src/fe-common/irc/fe-netsplit.c +++ b/src/fe-common/irc/fe-netsplit.c @@ -25,6 +25,7 @@ #include "levels.h" #include "settings.h" +#include "irc.h" #include "irc-server.h" #include "ignore.h" #include "netsplit.h" @@ -57,7 +58,7 @@ typedef struct { typedef struct { IRC_SERVER_REC *server_rec; - NETSPLIT_SERVER_REC *server; + GSList *servers; /* if many servers splitted from the same one */ GSList *channels; } TEMP_SPLIT_REC; @@ -80,7 +81,7 @@ static void get_server_splits(void *key, NETSPLIT_REC *split, TEMP_SPLIT_REC *re TEMP_SPLIT_CHAN_REC *chanrec; GSList *tmp; - if (split->printed || split->server != rec->server) + if (split->printed || g_slist_find(rec->servers, split->server) == NULL) return; split->printed = TRUE; @@ -100,6 +101,7 @@ static void get_server_splits(void *key, NETSPLIT_REC *split, TEMP_SPLIT_REC *re rec->channels = g_slist_append(rec->channels, chanrec); } + split->server->prints++; chanrec->nick_count++; if (netsplit_max_nicks <= 0 || chanrec->nick_count < netsplit_max_nicks) { @@ -111,8 +113,25 @@ static void get_server_splits(void *key, NETSPLIT_REC *split, TEMP_SPLIT_REC *re static void print_splits(IRC_SERVER_REC *server, TEMP_SPLIT_REC *rec) { + GString *destservers; + char *sourceserver; GSList *tmp; + destservers = g_string_new(NULL); + for (tmp = rec->servers; tmp != NULL; tmp = tmp->next) { + NETSPLIT_SERVER_REC *rec = tmp->data; + + if (rec->prints > 0) + g_string_sprintfa(destservers, "%s, ", rec->destserver); + } + if (destservers->len == 0) { + /* no nicks to print in this server */ + g_string_free(destservers, TRUE); + return; + } + g_string_truncate(destservers, destservers->len-2); + + sourceserver = ((NETSPLIT_SERVER_REC *) (rec->servers->data))->server; for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) { TEMP_SPLIT_CHAN_REC *chan = tmp->data; @@ -120,13 +139,15 @@ static void print_splits(IRC_SERVER_REC *server, TEMP_SPLIT_REC *rec) if (netsplit_max_nicks > 0 && chan->nick_count > netsplit_max_nicks) { printformat(server, chan->name, MSGLEVEL_QUITS, IRCTXT_NETSPLIT_MORE, - rec->server->server, rec->server->destserver, chan->nicks->str, + sourceserver, destservers->str, chan->nicks->str, chan->nick_count - netsplit_max_nicks); } else { printformat(server, chan->name, MSGLEVEL_QUITS, IRCTXT_NETSPLIT, - rec->server->server, rec->server->destserver, chan->nicks->str); + sourceserver, destservers->str, chan->nicks->str); } } + + g_string_free(destservers, TRUE); } static void temp_split_chan_free(TEMP_SPLIT_CHAN_REC *rec) @@ -137,26 +158,41 @@ static void temp_split_chan_free(TEMP_SPLIT_CHAN_REC *rec) static int check_server_splits(IRC_SERVER_REC *server) { - TEMP_SPLIT_REC rec; - GSList *tmp; + TEMP_SPLIT_REC temp; + GSList *tmp, *next, *servers; time_t last; last = get_last_split(server); - if (last+SPLIT_WAIT_TIME >= time(NULL)) + if (time(NULL)-last < SPLIT_WAIT_TIME) return FALSE; - for (tmp = server->split_servers; tmp != NULL; tmp = tmp->next) { - NETSPLIT_SERVER_REC *sserver = tmp->data; + servers = g_slist_copy(server->split_servers); + while (servers != NULL) { + NETSPLIT_SERVER_REC *sserver = servers->data; - rec.server_rec = server; - rec.server = sserver; - rec.channels = NULL; + /* get all the splitted servers that have the same + source server */ + temp.servers = NULL; + for (tmp = servers; tmp != NULL; tmp = next) { + NETSPLIT_SERVER_REC *rec = tmp->data; - g_hash_table_foreach(server->splits, (GHFunc) get_server_splits, &rec); - print_splits(server, &rec); + next = tmp->next; + if (g_strcasecmp(rec->server, sserver->server) == 0) { + rec->prints = 0; + temp.servers = g_slist_append(temp.servers, rec); + servers = g_slist_remove(servers, rec); + } + } - g_slist_foreach(rec.channels, (GFunc) temp_split_chan_free, NULL); - g_slist_free(rec.channels); + temp.server_rec = server; + temp.channels = NULL; + + g_hash_table_foreach(server->splits, (GHFunc) get_server_splits, &temp); + print_splits(server, &temp); + + g_slist_foreach(temp.channels, (GFunc) temp_split_chan_free, NULL); + g_slist_free(temp.servers); + g_slist_free(temp.channels); } return TRUE; diff --git a/src/fe-common/irc/module-formats.c b/src/fe-common/irc/module-formats.c index 088e02e1..05f431a4 100644 --- a/src/fe-common/irc/module-formats.c +++ b/src/fe-common/irc/module-formats.c @@ -43,6 +43,8 @@ FORMAT_REC fecommon_irc_formats[] = { { "setupserver_footer", "", 0 }, { "netsplit", "%RNetsplit%n %_$0%_ %_$1%_ quits: $2", 3, { 0, 0, 0 } }, { "netsplit_more", "%RNetsplit%n %_$0%_ %_$1%_ quits: $2 (+$3 more, use /NETSPLIT to show all of them)", 4, { 0, 0, 0, 1 } }, + { "netsplit_join", "%CNetsplit%n over, joins: $0", 1, { 0 } }, + { "netsplit_join_more", "%CNetsplit%n over, joins: $0 (+$1 more)", 2, { 0, 1 } }, { "no_netsplits", "There are no net splits", 0 }, { "netsplits_header", "Nick Channel Server Splitted server", 0 }, { "netsplits_line", "$[9]0 $[10]1 $[20]2 $3", 4, { 0, 0, 0, 0 } }, diff --git a/src/fe-common/irc/module-formats.h b/src/fe-common/irc/module-formats.h index daf84a02..d9247d46 100644 --- a/src/fe-common/irc/module-formats.h +++ b/src/fe-common/irc/module-formats.h @@ -21,6 +21,8 @@ enum { IRCTXT_SETUPSERVER_FOOTER, IRCTXT_NETSPLIT, IRCTXT_NETSPLIT_MORE, + IRCTXT_NETSPLIT_JOIN, + IRCTXT_NETSPLIT_JOIN_MORE, IRCTXT_NO_NETSPLITS, IRCTXT_NETSPLITS_HEADER, IRCTXT_NETSPLITS_LINE, diff --git a/src/irc/core/netsplit.c b/src/irc/core/netsplit.c index 4ee80f0c..d4ea714e 100644 --- a/src/irc/core/netsplit.c +++ b/src/irc/core/netsplit.c @@ -243,9 +243,8 @@ static void event_join(const char *data, IRC_SERVER_REC *server, const char *nic /* check if split is over */ rec = g_hash_table_lookup(server->splits, nick); - if (rec == NULL) return; - if (g_strcasecmp(rec->address, address) == 0) { + if (rec != NULL && g_strcasecmp(rec->address, address) == 0) { /* yep, looks like it is. for same people that had the same splitted servers set the timeout to one minute. @@ -253,8 +252,17 @@ static void event_join(const char *data, IRC_SERVER_REC *server, const char *nic same nick (unless the server is broken) so don't bother checking that the nick's server matches the split. */ g_hash_table_foreach(server->splits, (GHFunc) split_set_timeout, rec); - } else { - /* back from different address.. just destroy it. */ + } +} + +/* remove the nick from netsplit, but do it last so that other "event join" + signal handlers can check if the join was a netjoin */ +static void event_join_last(const char *data, IRC_SERVER_REC *server, const char *nick, const char *address) +{ + NETSPLIT_REC *rec; + + rec = g_hash_table_lookup(server->splits, nick); + if (rec != NULL) { g_hash_table_remove(server->splits, rec->nick); netsplit_destroy(server, rec); } @@ -306,6 +314,7 @@ void netsplit_init(void) { split_tag = g_timeout_add(1000, (GSourceFunc) split_check_old, NULL); signal_add_first("event join", (SIGNAL_FUNC) event_join); + signal_add_last("event join", (SIGNAL_FUNC) event_join_last); signal_add_first("event quit", (SIGNAL_FUNC) event_quit); signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); } @@ -319,6 +328,7 @@ void netsplit_deinit(void) g_source_remove(split_tag); signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("event join", (SIGNAL_FUNC) event_join_last); signal_remove("event quit", (SIGNAL_FUNC) event_quit); signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); } diff --git a/src/irc/core/netsplit.h b/src/irc/core/netsplit.h index 6ea9cf3f..c801f168 100644 --- a/src/irc/core/netsplit.h +++ b/src/irc/core/netsplit.h @@ -7,6 +7,7 @@ typedef struct { char *server; char *destserver; int count; + int prints; /* temp variable */ time_t last; /* last time we received a QUIT msg here */ } NETSPLIT_SERVER_REC;