diff --git a/src/command/command.c b/src/command/command.c index 91c25131..36579990 100644 --- a/src/command/command.c +++ b/src/command/command.c @@ -1490,15 +1490,17 @@ static struct cmd_t command_defs[] = }, { "/autoping", - cmd_autoping, parse_args, 1, 1, &cons_autoping_setting, + cmd_autoping, parse_args, 2, 2, &cons_autoping_setting, CMD_TAGS( CMD_TAG_CONNECTION) CMD_SYN( - "/autoping ") + "/autoping set ", + "/autoping timeout ") CMD_DESC( "Set the interval between sending ping requests to the server to ensure the connection is kept alive.") CMD_ARGS( - { "", "Number of seconds between sending pings, a value of 0 disables autoping." }) + { "set ", "Number of seconds between sending pings, a value of 0 disables autoping." }, + { "timeout ", "Seconds to wait for autoping responses, after which the connection is considered broken." }) CMD_NOEXAMPLES }, @@ -1908,6 +1910,7 @@ static Autocomplete script_ac; static Autocomplete script_show_ac; static Autocomplete console_ac; static Autocomplete console_muc_ac; +static Autocomplete autoping_ac; /* * Initialise command autocompleter and history @@ -2401,6 +2404,10 @@ cmd_init(void) autocomplete_add(console_muc_ac, "all"); autocomplete_add(console_muc_ac, "first"); autocomplete_add(console_muc_ac, "none"); + + autoping_ac = autocomplete_new(); + autocomplete_add(autoping_ac, "set"); + autocomplete_add(autoping_ac, "timeout"); } void @@ -2480,6 +2487,7 @@ cmd_uninit(void) autocomplete_free(script_show_ac); autocomplete_free(console_ac); autocomplete_free(console_muc_ac); + autocomplete_free(autoping_ac); } gboolean @@ -2674,6 +2682,7 @@ cmd_reset_autocomplete(ProfWin *window) autocomplete_reset(tls_certpath_ac); autocomplete_reset(console_ac); autocomplete_reset(console_muc_ac); + autocomplete_reset(autoping_ac); autocomplete_reset(script_ac); if (script_show_ac) { autocomplete_free(script_show_ac); @@ -2892,8 +2901,8 @@ _cmd_complete_parameters(ProfWin *window, const char *const input) } } - gchar *cmds[] = { "/prefs", "/disco", "/close", "/room" }; - Autocomplete completers[] = { prefs_ac, disco_ac, close_ac, room_ac }; + gchar *cmds[] = { "/prefs", "/disco", "/close", "/room", "/autoping" }; + Autocomplete completers[] = { prefs_ac, disco_ac, close_ac, room_ac, autoping_ac }; for (i = 0; i < ARRAY_SIZE(cmds); i++) { result = autocomplete_param_with_ac(input, cmds[i], completers[i], TRUE); diff --git a/src/command/commands.c b/src/command/commands.c index 404db139..192f241d 100644 --- a/src/command/commands.c +++ b/src/command/commands.c @@ -4788,23 +4788,46 @@ cmd_reconnect(ProfWin *window, const char *const command, gchar **args) gboolean cmd_autoping(ProfWin *window, const char *const command, gchar **args) { - char *value = args[0]; + char *cmd = args[0]; + char *value = args[1]; - int intval = 0; - char *err_msg = NULL; - gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg); - if (res) { - prefs_set_autoping(intval); - iq_set_autoping(intval); - if (intval == 0) { - cons_show("Autoping disabled.", intval); + if (g_strcmp0(cmd, "set") == 0) { + int intval = 0; + char *err_msg = NULL; + gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg); + if (res) { + prefs_set_autoping(intval); + iq_set_autoping(intval); + if (intval == 0) { + cons_show("Autoping disabled."); + } else { + cons_show("Autoping interval set to %d seconds.", intval); + } } else { - cons_show("Autoping interval set to %d seconds.", intval); + cons_show(err_msg); + cons_bad_cmd_usage(command); + free(err_msg); } + + } else if (g_strcmp0(cmd, "timeout") == 0) { + int intval = 0; + char *err_msg = NULL; + gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg); + if (res) { + prefs_set_autoping_timeout(intval); + if (intval == 0) { + cons_show("Autoping timeout disabled."); + } else { + cons_show("Autoping timeout set to %d seconds.", intval); + } + } else { + cons_show(err_msg); + cons_bad_cmd_usage(command); + free(err_msg); + } + } else { - cons_show(err_msg); cons_bad_cmd_usage(command); - free(err_msg); } return TRUE; diff --git a/src/config/preferences.c b/src/config/preferences.c index bf576864..8624921c 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -513,6 +513,23 @@ prefs_set_autoping(gint value) _save_prefs(); } +gint +prefs_get_autoping_timeout(void) +{ + if (!g_key_file_has_key(prefs, PREF_GROUP_CONNECTION, "autoping.timeout", NULL)) { + return 5; + } else { + return g_key_file_get_integer(prefs, PREF_GROUP_CONNECTION, "autoping.timeout", NULL); + } +} + +void +prefs_set_autoping_timeout(gint value) +{ + g_key_file_set_integer(prefs, PREF_GROUP_CONNECTION, "autoping.timeout", value); + _save_prefs(); +} + gint prefs_get_autoaway_time(void) { diff --git a/src/config/preferences.h b/src/config/preferences.h index 03d59e76..80ed2696 100644 --- a/src/config/preferences.h +++ b/src/config/preferences.h @@ -156,6 +156,8 @@ void prefs_set_reconnect(gint value); gint prefs_get_reconnect(void); void prefs_set_autoping(gint value); gint prefs_get_autoping(void); +void prefs_set_autoping_timeout(gint value); +gint prefs_get_autoping_timeout(void); gint prefs_get_inpblock(void); void prefs_set_inpblock(gint value); diff --git a/src/profanity.c b/src/profanity.c index 6e973a72..51609582 100644 --- a/src/profanity.c +++ b/src/profanity.c @@ -124,6 +124,7 @@ prof_run(char *log_level, char *account_name) #endif notify_remind(); jabber_process_events(10); + iq_autoping_check(); ui_update(); } } diff --git a/src/ui/console.c b/src/ui/console.c index 14487b7c..315e933c 100644 --- a/src/ui/console.c +++ b/src/ui/console.c @@ -1695,6 +1695,15 @@ cons_autoping_setting(void) } else { cons_show("Autoping interval (/autoping) : %d seconds", autoping_interval); } + + gint autoping_timeout = prefs_get_autoping_timeout(); + if (autoping_timeout == 0) { + cons_show("Autoping timeout (/autoping) : OFF"); + } else if (autoping_timeout == 1) { + cons_show("Autoping timeout (/autoping) : 1 second"); + } else { + cons_show("Autoping timeout (/autoping) : %d seconds", autoping_timeout); + } } void diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c index 42293c18..d72d10ca 100644 --- a/src/xmpp/connection.c +++ b/src/xmpp/connection.c @@ -104,7 +104,7 @@ static jabber_conn_status_t _jabber_connect(const char *const fulljid, const cha const char *const altdomain, int port, const char *const tls_policy); static void _jabber_reconnect(void); - +static void _jabber_lost_connection(void); static void _connection_handler(xmpp_conn_t *const conn, const xmpp_conn_event_t status, const int error, xmpp_stream_error_t *const stream_error, void *const userdata); @@ -201,6 +201,35 @@ jabber_connect_with_details(const char *const jid, const char *const passwd, con saved_details.tls_policy); } +void +jabber_autoping_fail(void) +{ + if (jabber_conn.conn_status == JABBER_CONNECTED) { + log_info("Closing connection"); + accounts_set_last_activity(jabber_get_account_name()); + jabber_conn.conn_status = JABBER_DISCONNECTING; + xmpp_disconnect(jabber_conn.conn); + + while (jabber_get_connection_status() == JABBER_DISCONNECTING) { + jabber_process_events(10); + } + if (jabber_conn.conn) { + xmpp_conn_release(jabber_conn.conn); + jabber_conn.conn = NULL; + } + if (jabber_conn.ctx) { + xmpp_ctx_free(jabber_conn.ctx); + jabber_conn.ctx = NULL; + } + } + + FREE_SET_NULL(jabber_conn.presence_message); + FREE_SET_NULL(jabber_conn.domain); + + jabber_conn.conn_status = JABBER_DISCONNECTED; + _jabber_lost_connection(); +} + void jabber_disconnect(void) { @@ -281,6 +310,12 @@ jabber_get_connection_status(void) return (jabber_conn.conn_status); } +void +jabber_set_connection_status(jabber_conn_status_t status) +{ + jabber_conn.conn_status = status; +} + xmpp_conn_t* connection_get_conn(void) { @@ -542,6 +577,20 @@ _jabber_reconnect(void) } } +static void +_jabber_lost_connection(void) +{ + sv_ev_lost_connection(); + if (prefs_get_reconnect() != 0) { + assert(reconnect_timer == NULL); + reconnect_timer = g_timer_new(); + } else { + _connection_free_saved_account(); + _connection_free_saved_details(); + } + _connection_free_session_data(); +} + static void _connection_handler(xmpp_conn_t *const conn, const xmpp_conn_event_t status, const int error, xmpp_stream_error_t *const stream_error, void *const userdata) @@ -600,17 +649,7 @@ _connection_handler(xmpp_conn_t *const conn, const xmpp_conn_event_t status, con // lost connection for unknown reason if (jabber_conn.conn_status == JABBER_CONNECTED) { log_debug("Connection handler: Lost connection for unknown reason"); - sv_ev_lost_connection(); - if (prefs_get_reconnect() != 0) { - assert(reconnect_timer == NULL); - reconnect_timer = g_timer_new(); - // free resources but leave saved_user untouched - _connection_free_session_data(); - } else { - _connection_free_saved_account(); - _connection_free_saved_details(); - _connection_free_session_data(); - } + _jabber_lost_connection(); // login attempt failed } else if (jabber_conn.conn_status != JABBER_DISCONNECTING) { diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c index dc1bbc4a..6ccce236 100644 --- a/src/xmpp/iq.c +++ b/src/xmpp/iq.c @@ -99,6 +99,7 @@ static int _caps_response_handler_for_jid(xmpp_conn_t *const conn, xmpp_stanza_t static int _caps_response_handler_legacy(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata); static gboolean autoping_wait = FALSE; +static GTimer *autoping_time; void iq_add_handlers(void) @@ -125,6 +126,29 @@ iq_add_handlers(void) } } +void +iq_autoping_check(void) +{ + if (jabber_get_connection_status() != JABBER_CONNECTED) { + return; + } + + if (autoping_wait == FALSE) { + return; + } + + gdouble elapsed = g_timer_elapsed(autoping_time, NULL); + unsigned long seconds_elapsed = elapsed * 1.0; + gint timeout = prefs_get_autoping_timeout(); + if (timeout > 0 && seconds_elapsed >= timeout) { + cons_show("Autoping response timed out afer %u seconds.", timeout); + log_debug("Autoping check: timed out afer %u seconds, disconnecting", timeout); + jabber_autoping_fail(); + autoping_wait = FALSE; + g_timer_destroy(autoping_time); + } +} + void iq_set_autoping(const int seconds) { @@ -529,6 +553,7 @@ static int _auto_pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata) { autoping_wait = FALSE; + g_timer_destroy(autoping_time); char *id = xmpp_stanza_get_id(stanza); if (id == NULL) { @@ -870,6 +895,7 @@ _autoping_timed_handler(xmpp_conn_t *const conn, void *const userdata) xmpp_send(conn, iq); xmpp_stanza_release(iq); autoping_wait = TRUE; + autoping_time = g_timer_new(); return 1; } diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h index f52603eb..7e5e430e 100644 --- a/src/xmpp/xmpp.h +++ b/src/xmpp/xmpp.h @@ -144,10 +144,12 @@ jabber_conn_status_t jabber_connect_with_details(const char *const jid, const ch jabber_conn_status_t jabber_connect_with_account(const ProfAccount *const account); void jabber_disconnect(void); void jabber_shutdown(void); +void jabber_autoping_fail(void); void jabber_process_events(int millis); const char* jabber_get_fulljid(void); const char* jabber_get_domain(void); jabber_conn_status_t jabber_get_connection_status(void); +void jabber_set_connection_status(jabber_conn_status_t status); char* jabber_get_presence_message(void); char* jabber_get_account_name(void); GList* jabber_get_available_resources(void); @@ -212,6 +214,7 @@ void iq_room_affiliation_set(const char *const room, const char *const jid, char void iq_room_kick_occupant(const char *const room, const char *const nick, const char *const reason); void iq_room_role_set(const char *const room, const char *const nick, char *role, const char *const reason); void iq_room_role_list(const char * const room, char *role); +void iq_autoping_check(void); // caps functions Capabilities* caps_lookup(const char *const jid); diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c index 6abec1bf..dc630198 100644 --- a/tests/unittests/xmpp/stub_xmpp.c +++ b/tests/unittests/xmpp/stub_xmpp.c @@ -173,6 +173,7 @@ void iq_room_role_set(const char * const room, const char * const nick, char *ro const char * const reason) {} void iq_room_role_list(const char * const room, char *role) {} void iq_last_activity_request(gchar *jid) {} +void iq_autoping_check(void) {} // caps functions Capabilities* caps_lookup(const char * const jid)