tildefriends/src/ssb.connections.c

182 lines
5.7 KiB
C

#include "ssb.connections.h"
#include "log.h"
#include "mem.h"
#include "ssb.h"
#include <uv.h>
#include <sqlite3.h>
#include <string.h>
#if !defined(_countof)
#define _countof(a) ((int)(sizeof((a)) / sizeof(*(a))))
#endif
typedef struct _tf_ssb_connections_t
{
tf_ssb_t* ssb;
sqlite3* db;
uv_timer_t timer;
} tf_ssb_connections_t;
static void _tf_ssb_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data)
{
tf_ssb_connections_t* connections = user_data;
switch (change)
{
case k_tf_ssb_change_create:
{
char key[k_id_base64_len];
if (tf_ssb_connection_get_host(connection) &&
*tf_ssb_connection_get_host(connection) &&
tf_ssb_connection_get_port(connection) &&
tf_ssb_connection_get_id(connection, key, sizeof(key)))
{
tf_ssb_connections_set_attempted(connections, tf_ssb_connection_get_host(connection), tf_ssb_connection_get_port(connection), key);
}
}
break;
case k_tf_ssb_change_connect:
{
char key[k_id_base64_len];
if (tf_ssb_connection_get_id(connection, key, sizeof(key)))
{
tf_ssb_connections_set_succeeded(connections, tf_ssb_connection_get_host(connection), tf_ssb_connection_get_port(connection), key);
}
}
break;
case k_tf_ssb_change_remove:
break;
}
}
static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connections, char* host, size_t host_size, int* port, char* key, size_t key_size)
{
bool result = false;
sqlite3_stmt* statement;
if (sqlite3_prepare(connections->db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > $1) ORDER BY last_attempt LIMIT 1", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_int(statement, 1, 60000) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
{
snprintf(host, host_size, "%s", sqlite3_column_text(statement, 0));
*port = sqlite3_column_int(statement, 1);
snprintf(key, key_size, "%s", sqlite3_column_text(statement, 2));
result = true;
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare: %s\n", sqlite3_errmsg(connections->db));
}
return result;
}
static void _tf_ssb_connections_timer(uv_timer_t* timer)
{
tf_ssb_connections_t* connections = timer->data;
tf_ssb_connection_t* active[4];
int count = tf_ssb_get_connections(connections->ssb, active, _countof(active));
if (count < (int)_countof(active))
{
char host[256];
int port;
char key[k_id_base64_len];
if (_tf_ssb_connections_get_next_connection(connections, host, sizeof(host), &port, key, sizeof(key)))
{
uint8_t key_bin[k_id_bin_len];
if (tf_ssb_id_str_to_bin(key_bin, key))
{
tf_ssb_connect(connections->ssb, host, port, key_bin);
}
}
}
}
tf_ssb_connections_t* tf_ssb_connections_create(tf_ssb_t* ssb)
{
tf_ssb_connections_t* connections = tf_malloc(sizeof(tf_ssb_connections_t));
memset(connections, 0, sizeof(*connections));
connections->ssb = ssb;
connections->db = tf_ssb_get_db(ssb);
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_connections_changed_callback, NULL, connections);
uv_loop_t* loop = tf_ssb_get_loop(ssb);
connections->timer.data = connections;
uv_timer_init(loop, &connections->timer);
uv_timer_start(&connections->timer, _tf_ssb_connections_timer, 2000, 2000);
uv_unref((uv_handle_t*)&connections->timer);
return connections;
}
static void _tf_ssb_connections_on_handle_close(uv_handle_t* handle)
{
tf_ssb_connections_t* connections = handle->data;
handle->data = NULL;
tf_free(connections);
}
void tf_ssb_connections_destroy(tf_ssb_connections_t* connections)
{
uv_close((uv_handle_t*)&connections->timer, _tf_ssb_connections_on_handle_close);
}
void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(connections->db, "INSERT INTO connections (host, port, key) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, port) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, key, -1, NULL) == SQLITE_OK)
{
int r = sqlite3_step(statement);
if (r != SQLITE_DONE)
{
tf_printf("tf_ssb_connections_store: %d, %s.\n", r, sqlite3_errmsg(connections->db));
}
}
sqlite3_finalize(statement);
}
}
void tf_ssb_connections_set_attempted(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(connections->db, "UPDATE connections SET last_attempt = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, port) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, key, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("tf_ssb_connections_set_attempted: %s.\n", sqlite3_errmsg(connections->db));
}
}
sqlite3_finalize(statement);
}
}
void tf_ssb_connections_set_succeeded(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{
sqlite3_stmt* statement;
if (sqlite3_prepare(connections->db, "UPDATE connections SET last_success = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 2, port) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, key, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("tf_ssb_connections_set_succeeded: %s.\n", sqlite3_errmsg(connections->db));
}
}
sqlite3_finalize(statement);
}
}