2017-03-02 14:37:32 +00:00
|
|
|
using Gee;
|
|
|
|
|
|
|
|
using Xmpp;
|
|
|
|
using Dino.Entities;
|
|
|
|
|
|
|
|
namespace Dino {
|
|
|
|
|
|
|
|
public class ConnectionManager {
|
|
|
|
|
|
|
|
public signal void stream_opened(Account account, Core.XmppStream stream);
|
|
|
|
public signal void connection_state_changed(Account account, ConnectionState state);
|
2017-04-08 09:53:10 +00:00
|
|
|
public signal void connection_error(Account account, ConnectionError error);
|
2017-03-02 14:37:32 +00:00
|
|
|
|
|
|
|
public enum ConnectionState {
|
|
|
|
CONNECTED,
|
|
|
|
CONNECTING,
|
|
|
|
DISCONNECTED
|
|
|
|
}
|
|
|
|
|
|
|
|
private ArrayList<Account> connection_todo = new ArrayList<Account>(Account.equals_func);
|
2017-04-08 09:53:10 +00:00
|
|
|
private HashMap<Account, Connection> connections = new HashMap<Account, Connection>(Account.hash_func, Account.equals_func);
|
|
|
|
private HashMap<Account, ConnectionError> connection_errors = new HashMap<Account, ConnectionError>(Account.hash_func, Account.equals_func);
|
2017-08-24 21:24:04 +00:00
|
|
|
private HashMap<Account, RecMutexWrap> connection_mutexes = new HashMap<Account, RecMutexWrap>(Account.hash_func, Account.equals_func);
|
2017-04-08 09:53:10 +00:00
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
private NetworkManager? network_manager;
|
|
|
|
private Login1Manager? login1;
|
2017-08-24 21:24:04 +00:00
|
|
|
private NetworkManagerDBusProperties? dbus_properties;
|
2017-03-02 14:37:32 +00:00
|
|
|
private ModuleManager module_manager;
|
2017-04-03 13:09:30 +00:00
|
|
|
public string? log_options;
|
2017-03-02 14:37:32 +00:00
|
|
|
|
2017-04-08 09:53:10 +00:00
|
|
|
public class ConnectionError {
|
|
|
|
|
|
|
|
public enum Source {
|
|
|
|
CONNECTION,
|
|
|
|
SASL,
|
|
|
|
STREAM_ERROR
|
|
|
|
}
|
|
|
|
|
|
|
|
public Source source;
|
|
|
|
public string? identifier;
|
|
|
|
public StreamError.Flag? flag;
|
|
|
|
|
|
|
|
public ConnectionError(Source source, string? identifier) {
|
|
|
|
this.source = source;
|
|
|
|
this.identifier = identifier;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
private class Connection {
|
|
|
|
public Core.XmppStream stream { get; set; }
|
|
|
|
public ConnectionState connection_state { get; set; default = ConnectionState.DISCONNECTED; }
|
|
|
|
public DateTime established { get; set; }
|
2017-08-24 21:24:04 +00:00
|
|
|
public DateTime last_activity { get; set; }
|
2017-03-02 14:37:32 +00:00
|
|
|
public class Connection(Core.XmppStream stream, DateTime established) {
|
|
|
|
this.stream = stream;
|
|
|
|
this.established = established;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 21:24:04 +00:00
|
|
|
private class RecMutexWrap {
|
|
|
|
public RecMutex mutex = new RecMutex();
|
|
|
|
public void lock() { mutex.lock(); }
|
|
|
|
public void unlock() { mutex.unlock(); }
|
|
|
|
public bool trylock() { return mutex.trylock(); }
|
|
|
|
}
|
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
public ConnectionManager(ModuleManager module_manager) {
|
|
|
|
this.module_manager = module_manager;
|
|
|
|
network_manager = get_network_manager();
|
|
|
|
if (network_manager != null) {
|
|
|
|
network_manager.StateChanged.connect(on_nm_state_changed);
|
|
|
|
}
|
|
|
|
login1 = get_login1();
|
|
|
|
if (login1 != null) {
|
|
|
|
login1.PrepareForSleep.connect(on_prepare_for_sleep);
|
|
|
|
}
|
2017-08-24 21:24:04 +00:00
|
|
|
dbus_properties = get_dbus_properties();
|
|
|
|
if (dbus_properties != null) {
|
|
|
|
dbus_properties.properties_changed.connect((s, sv, sa) => {
|
|
|
|
foreach (string key in sv.get_keys()) {
|
|
|
|
if (key == "PrimaryConnection") {
|
|
|
|
print("primary connection changed\n");
|
|
|
|
check_reconnects();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
Timeout.add_seconds(60, () => {
|
|
|
|
foreach (Account account in connection_todo) {
|
|
|
|
if (connections[account].last_activity.compare(new DateTime.now_utc().add_minutes(-1)) < 0) {
|
|
|
|
check_reconnect(account);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
});
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public Core.XmppStream? get_stream(Account account) {
|
2017-04-08 09:53:10 +00:00
|
|
|
if (get_state(account) == ConnectionState.CONNECTED) {
|
|
|
|
return connections[account].stream;
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-04-08 09:53:10 +00:00
|
|
|
public ConnectionState get_state(Account account) {
|
|
|
|
if (connections.has_key(account)){
|
|
|
|
return connections[account].connection_state;
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
return ConnectionState.DISCONNECTED;
|
|
|
|
}
|
|
|
|
|
2017-04-08 09:53:10 +00:00
|
|
|
public ConnectionError? get_error(Account account) {
|
|
|
|
if (connection_errors.has_key(account)) {
|
|
|
|
return connection_errors[account];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
public ArrayList<Account> get_managed_accounts() {
|
|
|
|
return connection_todo;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Core.XmppStream? connect(Account account) {
|
2017-08-24 21:24:04 +00:00
|
|
|
if (!connection_mutexes.contains(account)) connection_mutexes[account] = new RecMutexWrap();
|
2017-03-02 14:37:32 +00:00
|
|
|
if (!connection_todo.contains(account)) connection_todo.add(account);
|
2017-04-08 09:53:10 +00:00
|
|
|
if (!connections.has_key(account)) {
|
2017-03-02 14:37:32 +00:00
|
|
|
return connect_(account);
|
|
|
|
} else {
|
|
|
|
check_reconnect(account);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void disconnect(Account account) {
|
|
|
|
change_connection_state(account, ConnectionState.DISCONNECTED);
|
2017-03-13 14:52:54 +00:00
|
|
|
connection_todo.remove(account);
|
2017-04-08 09:53:10 +00:00
|
|
|
if (connections.has_key(account)) {
|
2017-03-02 14:37:32 +00:00
|
|
|
try {
|
2017-04-08 09:53:10 +00:00
|
|
|
connections[account].stream.disconnect();
|
|
|
|
connections.unset(account);
|
2017-03-02 14:37:32 +00:00
|
|
|
} catch (Error e) { }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Core.XmppStream? connect_(Account account, string? resource = null) {
|
2017-08-24 21:24:04 +00:00
|
|
|
if (!connection_mutexes[account].trylock()) return null;
|
|
|
|
|
2017-04-08 09:53:10 +00:00
|
|
|
if (connections.has_key(account)) connections[account].stream.remove_modules();
|
|
|
|
connection_errors.unset(account);
|
2017-03-02 14:37:32 +00:00
|
|
|
if (resource == null) resource = account.resourcepart;
|
|
|
|
|
|
|
|
Core.XmppStream stream = new Core.XmppStream();
|
|
|
|
foreach (Core.XmppStreamModule module in module_manager.get_modules(account, resource)) {
|
|
|
|
stream.add_module(module);
|
|
|
|
}
|
2017-04-03 13:09:30 +00:00
|
|
|
stream.log = new Core.XmppLog(account.bare_jid.to_string(), log_options);
|
2017-03-02 14:37:32 +00:00
|
|
|
|
|
|
|
Connection connection = new Connection(stream, new DateTime.now_local());
|
2017-04-08 09:53:10 +00:00
|
|
|
connections[account] = connection;
|
2017-03-02 14:37:32 +00:00
|
|
|
change_connection_state(account, ConnectionState.CONNECTING);
|
2017-08-12 21:14:50 +00:00
|
|
|
stream.attached_modules.connect((stream) => {
|
2017-03-02 14:37:32 +00:00
|
|
|
change_connection_state(account, ConnectionState.CONNECTED);
|
|
|
|
});
|
2017-04-08 09:53:10 +00:00
|
|
|
stream.get_module(PlainSasl.Module.IDENTITY).received_auth_failure.connect((stream, node) => {
|
|
|
|
set_connection_error(account, ConnectionError.Source.SASL, null);
|
|
|
|
change_connection_state(account, ConnectionState.DISCONNECTED);
|
|
|
|
});
|
2017-08-24 21:24:04 +00:00
|
|
|
stream.received_node.connect(() => {
|
|
|
|
connections[account].last_activity = new DateTime.now_utc();
|
|
|
|
});
|
2017-03-02 14:37:32 +00:00
|
|
|
new Thread<void*> (null, () => {
|
|
|
|
try {
|
|
|
|
stream.connect(account.domainpart);
|
|
|
|
} catch (Error e) {
|
|
|
|
stderr.printf("Stream Error: %s\n", e.message);
|
|
|
|
change_connection_state(account, ConnectionState.DISCONNECTED);
|
2017-03-13 14:52:54 +00:00
|
|
|
if (!connection_todo.contains(account)) {
|
2017-04-08 09:53:10 +00:00
|
|
|
connections.unset(account);
|
2017-08-24 21:24:04 +00:00
|
|
|
return null;
|
2017-03-13 14:52:54 +00:00
|
|
|
}
|
2017-08-24 21:24:04 +00:00
|
|
|
StreamError.Flag? flag = stream.get_flag(StreamError.Flag.IDENTITY);
|
|
|
|
if (flag != null) {
|
|
|
|
set_connection_error(account, ConnectionError.Source.STREAM_ERROR, flag.error_type);
|
|
|
|
}
|
|
|
|
interpret_connection_error(account);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
2017-08-24 21:24:04 +00:00
|
|
|
connection_mutexes[account].unlock();
|
2017-03-02 14:37:32 +00:00
|
|
|
return null;
|
|
|
|
});
|
|
|
|
stream_opened(account, stream);
|
|
|
|
|
2017-08-24 21:24:04 +00:00
|
|
|
connection_mutexes[account].unlock();
|
2017-03-02 14:37:32 +00:00
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
2017-04-08 09:53:10 +00:00
|
|
|
private void interpret_connection_error(Account account) {
|
|
|
|
ConnectionError? error = connection_errors[account];
|
|
|
|
int wait_sec = 5;
|
|
|
|
if (error == null) {
|
|
|
|
wait_sec = 3;
|
|
|
|
} else if (error.source == ConnectionError.Source.STREAM_ERROR && error.flag != null) {
|
|
|
|
if (error.flag.resource_rejected) {
|
|
|
|
connect_(account, account.resourcepart + "-" + random_uuid());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (error.flag.reconnection_recomendation) {
|
|
|
|
case StreamError.Flag.Reconnect.NOW:
|
|
|
|
wait_sec = 5; break;
|
|
|
|
case StreamError.Flag.Reconnect.LATER:
|
|
|
|
wait_sec = 60; break;
|
|
|
|
case StreamError.Flag.Reconnect.NEVER:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (error.source == ConnectionError.Source.SASL) {
|
|
|
|
return;
|
|
|
|
}
|
2017-03-02 14:37:32 +00:00
|
|
|
if (network_manager != null && network_manager.State != NetworkManager.CONNECTED_GLOBAL) {
|
2017-06-13 16:14:59 +00:00
|
|
|
wait_sec = 30;
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
print(@"recovering in $wait_sec\n");
|
|
|
|
Timeout.add_seconds(wait_sec, () => {
|
2017-08-24 21:24:04 +00:00
|
|
|
check_reconnect(account);
|
2017-03-02 14:37:32 +00:00
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private void check_reconnects() {
|
|
|
|
foreach (Account account in connection_todo) {
|
|
|
|
check_reconnect(account);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void check_reconnect(Account account) {
|
2017-08-24 21:24:04 +00:00
|
|
|
if (!connection_mutexes[account].trylock()) return;
|
2017-06-13 16:14:59 +00:00
|
|
|
bool acked = false;
|
|
|
|
|
2017-04-08 09:53:10 +00:00
|
|
|
Core.XmppStream stream = connections[account].stream;
|
2017-06-13 16:14:59 +00:00
|
|
|
stream.get_module(Xep.Ping.Module.IDENTITY).send_ping(stream, account.domainpart, (stream) => {
|
|
|
|
acked = true;
|
|
|
|
change_connection_state(account, ConnectionState.CONNECTED);
|
|
|
|
});
|
2017-03-02 14:37:32 +00:00
|
|
|
|
|
|
|
Timeout.add_seconds(5, () => {
|
2017-04-08 09:53:10 +00:00
|
|
|
if (connections[account].stream != stream) return false;
|
2017-06-13 16:14:59 +00:00
|
|
|
if (acked) return false;
|
2017-03-02 14:37:32 +00:00
|
|
|
|
|
|
|
change_connection_state(account, ConnectionState.DISCONNECTED);
|
|
|
|
try {
|
2017-04-08 09:53:10 +00:00
|
|
|
connections[account].stream.disconnect();
|
2017-03-02 14:37:32 +00:00
|
|
|
} catch (Error e) { }
|
2017-08-24 21:24:04 +00:00
|
|
|
connect_(account);
|
|
|
|
connection_mutexes[account].unlock();
|
2017-03-02 14:37:32 +00:00
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private void on_nm_state_changed(uint32 state) {
|
|
|
|
print("nm " + state.to_string() + "\n");
|
|
|
|
if (state == NetworkManager.CONNECTED_GLOBAL) {
|
|
|
|
check_reconnects();
|
|
|
|
} else {
|
|
|
|
foreach (Account account in connection_todo) {
|
|
|
|
change_connection_state(account, ConnectionState.DISCONNECTED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void on_prepare_for_sleep(bool suspend) {
|
|
|
|
foreach (Account account in connection_todo) {
|
|
|
|
change_connection_state(account, ConnectionState.DISCONNECTED);
|
|
|
|
}
|
|
|
|
if (suspend) {
|
|
|
|
print("suspend\n");
|
|
|
|
foreach (Account account in connection_todo) {
|
|
|
|
Xmpp.Presence.Stanza presence = new Xmpp.Presence.Stanza();
|
|
|
|
presence.type_ = Xmpp.Presence.Stanza.TYPE_UNAVAILABLE;
|
|
|
|
try {
|
2017-04-08 09:53:10 +00:00
|
|
|
connections[account].stream.get_module(Presence.Module.IDENTITY).send_presence(connections[account].stream, presence);
|
|
|
|
connections[account].stream.disconnect();
|
2017-03-02 14:37:32 +00:00
|
|
|
} catch (Error e) { print(@"on_prepare_for_sleep error $(e.message)\n"); }
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
print("un-suspend\n");
|
|
|
|
check_reconnects();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void change_connection_state(Account account, ConnectionState state) {
|
2017-04-08 09:53:10 +00:00
|
|
|
if (connections.has_key(account)) {
|
|
|
|
connections[account].connection_state = state;
|
|
|
|
connection_state_changed(account, state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void set_connection_error(Account account, ConnectionError.Source source, string? id) {
|
|
|
|
ConnectionError error = new ConnectionError(source, id);
|
|
|
|
connection_errors[account] = error;
|
|
|
|
connection_error(account, error);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|