summaryrefslogtreecommitdiff
path: root/src/main.c
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2011-11-16 23:35:33 +0100
committerJoel Klinghed <the_jk@yahoo.com>2011-11-16 23:35:33 +0100
commitef193c1fbc29b0ebd7842866d606782fc3208d15 (patch)
tree0d7d81e67c9ee55d6119a2f79908f6aa09984841 /src/main.c
parent29ef95680bcbb6cacefbd9f26cb04ab2ed12e1ca (diff)
Starts to display useful information now
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c989
1 files changed, 929 insertions, 60 deletions
diff --git a/src/main.c b/src/main.c
index 1197054..636c72e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -4,12 +4,15 @@
#include <xmlrpc-c/base.h>
#include <xmlrpc-c/client.h>
#include <string.h>
+#include <errno.h>
#include "customcellrendererstate.h"
#include "customcellrendererprogress.h"
#include "customcellrendererrate.h"
#include "customcellrendererleft.h"
+static const guint UPDATE_INTERVAL_MS = 3500;
+
typedef enum
{
COLUMN_STATE,
@@ -27,14 +30,21 @@ typedef struct
state_t state;
gchar* title;
gfloat downloaded; /* percent */
- gfloat seeded; /* ratio */
- gfloat down, up; /* kilo (1000) byte per second */
- guint64 size; /* bytes */
- gint64 left; /* seconds left until done, < 0 means unknown */
+ gfloat seeded; /* ratio */
+ gfloat down, up; /* kilo (1000) byte per second */
+ guint64 size; /* bytes */
+ gint64 left; /* seconds left until done, < 0 means unknown */
+} torrent_data_t;
+
+typedef struct
+{
+ GtkListStore* store;
+ GtkTreeIter iter;
} torrent_t;
-static void torrent_update(torrent_t* torrent,
- GtkListStore* store, GtkTreeIter* iter);
+static void torrent_update(torrent_t* torrent, torrent_data_t* data);
+static void torrent_data_init(torrent_data_t* data);
+static void torrent_data_free(torrent_data_t* data);
typedef struct
{
@@ -57,6 +67,11 @@ typedef struct
gchar* configfile;
guint config_timer;
guint config_try;
+
+ GHashTable* torrents;
+ guint64 last_sync_time;
+ guint last_sync_count;
+ guint sync_timeout;
} master_t;
typedef struct
@@ -65,14 +80,38 @@ typedef struct
master_t* master;
} worker_data_t;
+typedef struct
+{
+ int major;
+ int minor;
+ int patch;
+} version_t;
+
typedef enum
{
+ /* Tell worker to connect. Will generate a MSG_CONNECTRESULT as response. */
MSG_CONNECT, /* main -> worker */
+ /* Worker responding to an MSG_CONNECT */
MSG_CONNECTRESULT, /* worker -> main */
+ /* Tell worker to disconnect. No response. */
MSG_DISCONNECT, /* main -> worker */
+ /* Tell worker to QUIT. No response. */
MSG_QUIT, /* main -> worker */
+ /* Worker reporting a fatal error to main. Main should just quit. */
MSG_ERROR, /* worker -> main */
+ /* Worker reporting a status message to main. Can be generated at any time
+ * for any reason */
MSG_STATUS, /* worker -> main */
+ /* Tell worker to update the list of torrents.
+ * Will generate a MSG_SYNCLIST response */
+ MSG_UPDATELIST, /* main -> worker */
+ /* Worker responding to a MSG_UPDATELIST with added and removed torrents */
+ MSG_SYNCLIST, /* worker -> main */
+ /* Tell worker to update a torrent.
+ * Will generate a MSG_SYNC response */
+ MSG_UPDATE, /* main -> worker */
+ /* Worker responding to a MSG_UPDATE with uptodate torrent data */
+ MSG_SYNC, /* worker -> main */
} msg_type_t;
typedef struct
@@ -92,6 +131,7 @@ typedef struct
{
msg_t base;
gboolean success;
+ version_t version;
gchar* errmsg;
} msg_connectresult_t;
@@ -117,15 +157,62 @@ typedef struct
msg_t base;
} msg_disconnect_t;
+typedef struct
+{
+ msg_t base;
+} msg_updatelist_t;
+
+typedef struct
+{
+ msg_t base;
+ const gchar** added; /* NULL terminated list */
+ const gchar** removed; /* NULL terminated list */
+} msg_synclist_t;
+
+typedef struct
+{
+ msg_t base;
+ const gchar* hash;
+ torrent_t torrent;
+} msg_update_t;
+
+typedef struct
+{
+ msg_t base;
+ torrent_t torrent;
+ torrent_data_t data;
+} msg_sync_t;
+
+typedef struct _hashlist_t
+{
+ gchar** data;
+ gboolean* mark;
+ gsize size, fill;
+
+ const gchar** added;
+ const gchar** removed;
+} hashlist_t;
+
static void msg_free(msg_t* msg);
static msg_t* msg_connect(const gchar* url, const gchar* username,
const gchar* password);
-static msg_t* msg_connectresult(gboolean success, const gchar* errfmt, ...);
+static msg_t* msg_connectresult_success(version_t version);
+static msg_t* msg_connectresult_failed(const gchar* errfmt, ...);
static msg_t* msg_error(const gchar* format, ...);
static msg_t* msg_status(const gchar* format, ...);
static msg_t* msg_disconnect(void);
static msg_t* msg_quit(void);
+static msg_t* msg_updatelist(void);
+static msg_t* msg_synclist(const gchar** added, const gchar** removed);
+static msg_t* msg_update(const gchar* hash, torrent_t* torrent);
+static msg_t* msg_sync(torrent_t* torrent, torrent_data_t* data);
+
+static void hashlist_init(hashlist_t* hlist);
+static void hashlist_clear(hashlist_t* hlist);
+static void hashlist_free(hashlist_t* hlist);
+static gboolean hashlist_sync(hashlist_t* hlist, xmlrpc_env * const env,
+ const xmlrpc_value * const result, gsize count);
static void do_connect(GtkMenuItem* menuitem, gpointer data);
static void do_disconnect(GtkMenuItem* menuitem, gpointer data);
@@ -136,13 +223,16 @@ static gboolean sync_config(gpointer data);
static void save_config(master_t* master);
static void status(master_t* master, const char* format, ...);
+static void noop_destroy(gpointer value);
+static void torrent_destroy(gpointer value);
+
int main(int argc, char** argv)
{
const gchar* gladefile;
GtkBuilder* builder;
GError* error = NULL;
guint ret;
- GtkWidget* listview;
+ GtkWidget* listview, * pwddlg;
GtkCellRenderer* cell;
GtkTreeViewColumn* column;
GThread* worker;
@@ -183,6 +273,9 @@ int main(int argc, char** argv)
return EXIT_FAILURE;
}
+ master.torrents = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+ noop_destroy, torrent_destroy);
+
master.top = GTK_WIDGET(gtk_builder_get_object(builder, "main"));
g_signal_connect(master.top, "delete-event",
@@ -200,6 +293,8 @@ int main(int argc, char** argv)
gtk_widget_set_sensitive(master.connectmenuitem, TRUE);
gtk_widget_set_sensitive(master.disconnectmenuitem, FALSE);
+ pwddlg = GTK_WIDGET(gtk_builder_get_object(builder, "pwddlg"));
+
listview = GTK_WIDGET(gtk_builder_get_object(builder, "treeview"));
master.liststore = GTK_LIST_STORE(gtk_builder_get_object(builder,
"liststore"));
@@ -257,9 +352,8 @@ int main(int argc, char** argv)
gchar* pass = NULL;
if (url != NULL && user != NULL)
{
- GtkWidget* dlg, * label, * pwd;
+ GtkWidget* label, * pwd;
gchar* format, * text;
- dlg = GTK_WIDGET(gtk_builder_get_object(builder, "pwddlg"));
label = GTK_WIDGET(gtk_builder_get_object(builder, "pwddlg_label"));
pwd = GTK_WIDGET(gtk_builder_get_object(builder, "pwddlg_pwd"));
@@ -269,7 +363,7 @@ int main(int argc, char** argv)
gtk_label_set_text(GTK_LABEL(label), text);
g_free(text);
- switch (gtk_dialog_run(GTK_DIALOG(dlg)))
+ switch (gtk_dialog_run(GTK_DIALOG(pwddlg)))
{
case GTK_RESPONSE_OK:
pass = g_strdup(gtk_entry_get_text(GTK_ENTRY(pwd)));
@@ -279,7 +373,7 @@ int main(int argc, char** argv)
g_free(url);
url = NULL;
}
- gtk_widget_hide(dlg);
+ gtk_widget_hide(pwddlg);
}
if (url != NULL)
{
@@ -293,7 +387,12 @@ int main(int argc, char** argv)
gtk_main();
- if (master.config_timer > 0)
+ if (master.sync_timeout != 0)
+ {
+ g_source_remove(master.sync_timeout);
+ master.sync_timeout = 0;
+ }
+ if (master.config_timer != 0)
{
g_source_remove(master.config_timer);
master.config_timer = 0;
@@ -305,6 +404,12 @@ int main(int argc, char** argv)
g_thread_join(worker);
+ g_hash_table_destroy(master.torrents);
+ gtk_widget_destroy(master.top);
+ gtk_widget_destroy(pwddlg);
+ gtk_widget_destroy(master.connectdlg);
+ g_object_unref(builder);
+
g_key_file_free(master.config);
g_free(master.configfile);
@@ -344,6 +449,22 @@ gboolean sync_config(gpointer _data)
return FALSE;
}
+static void update_torrent(gpointer _hash, gpointer _torrent, gpointer _master)
+{
+ master_t* master = _master;
+ const gchar* hash = _hash;
+ torrent_t* torrent = _torrent;
+
+ g_async_queue_push(master->queue, msg_update(hash, torrent));
+}
+
+static gboolean update_list(gpointer _master)
+{
+ master_t* master = _master;
+ g_async_queue_push(master->queue, msg_updatelist());
+ return FALSE;
+}
+
gboolean incoming_msg(gpointer data)
{
msg_t* msg = data;
@@ -362,7 +483,10 @@ gboolean incoming_msg(gpointer data)
"auto", TRUE);
save_config(msg->master);
- status(master, "Connected.");
+ status(msg->master, "Connected to rTorrent %d.%d.%d.",
+ m->version.major, m->version.minor, m->version.patch);
+
+ g_async_queue_push(msg->master->queue, msg_updatelist());
}
else
{
@@ -404,9 +528,71 @@ gboolean incoming_msg(gpointer data)
status(msg->master, m->msg);
break;
}
+ case MSG_SYNCLIST:
+ {
+ msg_synclist_t* m = (msg_synclist_t*)msg;
+ const gchar** x = m->removed;
+ for (; *x != NULL; ++x)
+ {
+ g_hash_table_remove(msg->master->torrents, *x);
+ }
+ x = m->added;
+ for (; *x != NULL; ++x)
+ {
+ torrent_t* t = g_slice_new(torrent_t);
+ t->store = msg->master->liststore;
+ gtk_list_store_append(t->store, &t->iter);
+ g_hash_table_insert(msg->master->torrents, (gpointer)*x, t);
+ }
+ msg->master->last_sync_time = g_get_monotonic_time();
+ msg->master->last_sync_count =
+ g_hash_table_size(msg->master->torrents);
+ g_hash_table_foreach(msg->master->torrents,
+ update_torrent, msg->master);
+ break;
+ }
+ case MSG_SYNC:
+ {
+ msg_sync_t* m = (msg_sync_t*)msg;
+ g_assert(msg->master->last_sync_count > 0);
+
+ torrent_update(&m->torrent, &m->data);
+
+ if (--msg->master->last_sync_count == 0)
+ {
+ if (msg->master->sync_timeout == 0)
+ {
+ guint64 now = g_get_monotonic_time();
+ msg->master->last_sync_time += UPDATE_INTERVAL_MS * 1000;
+ if (now >= msg->master->last_sync_time)
+ {
+ g_async_queue_push(msg->master->queue, msg_updatelist());
+ }
+ else
+ {
+ now = msg->master->last_sync_time - now;
+ if (now >= UPDATE_INTERVAL_MS * 1000)
+ {
+ g_async_queue_push(msg->master->queue,
+ msg_updatelist());
+ }
+ else
+ {
+ msg->master->sync_timeout =
+ g_timeout_add((guint)(now / 1000),
+ update_list,
+ msg->master);
+ }
+ }
+ }
+ }
+ break;
+ }
case MSG_CONNECT:
case MSG_DISCONNECT:
case MSG_QUIT:
+ case MSG_UPDATE:
+ case MSG_UPDATELIST:
g_assert(FALSE);
}
msg_free(msg);
@@ -448,7 +634,7 @@ void do_connect(GtkMenuItem* menuitem, gpointer data)
if (url == NULL)
{
- return FALSE;
+ return;
}
if (strlen(user) == 0 && strlen(pass) == 0)
@@ -485,9 +671,16 @@ void do_disconnect(GtkMenuItem* menuitem, gpointer data)
{
return;
}
+ if (master->sync_timeout != 0)
+ {
+ g_source_remove(master->sync_timeout);
+ master->sync_timeout = 0;
+ }
gtk_widget_set_sensitive(master->disconnectmenuitem, FALSE);
status(master, "Disconnecting...");
+ g_hash_table_remove_all(master->torrents);
g_async_queue_push(master->queue, msg_disconnect());
+ master->connected = FALSE;
}
void save_config(master_t* master)
@@ -512,24 +705,27 @@ void status(master_t* master, const char* format, ...)
g_free(tmp);
}
-void torrent_update(torrent_t* torrent, GtkListStore* store, GtkTreeIter* iter)
+void torrent_update(torrent_t* torrent, torrent_data_t* data)
{
- gtk_list_store_set(store, iter,
- COLUMN_STATE, torrent->state,
- COLUMN_TITLE, torrent->title,
- COLUMN_DOWNLOADED, torrent->downloaded,
- COLUMN_SEEDED, torrent->seeded,
- COLUMN_UP, torrent->up,
- COLUMN_DOWN, torrent->down,
- COLUMN_LEFT, torrent->left,
+ gtk_list_store_set(torrent->store, &torrent->iter,
+ COLUMN_STATE, data->state,
+ COLUMN_TITLE, data->title,
+ COLUMN_DOWNLOADED, data->downloaded,
+ COLUMN_SEEDED, data->seeded,
+ COLUMN_UP, data->up,
+ COLUMN_DOWN, data->down,
+ COLUMN_LEFT, data->left,
COLUMN_PROGRESSSORT,
- torrent->downloaded < 100.0f ? torrent->downloaded :
- torrent->seeded + 1000.0f,
+ data->downloaded < 100.0f ? data->downloaded :
+ data->seeded + 1000.0f,
-1);
}
static void worker_respond(worker_data_t* data, msg_t* msg);
+static gint64 get_i64_xmlrpc(xmlrpc_env* env, xmlrpc_value* value);
+static gboolean get_bool_xmlrpc(xmlrpc_env* env, xmlrpc_value* value);
+
gpointer worker_main(gpointer _data)
{
worker_data_t* data = _data;
@@ -537,6 +733,11 @@ gpointer worker_main(gpointer _data)
xmlrpc_client* client;
xmlrpc_server_info* server = NULL;
gboolean quit = FALSE;
+ version_t version;
+ hashlist_t hashlist;
+ guint64 ratio_min = 0, ratio_max = 0, ratio_upload = 0;
+
+ hashlist_init(&hashlist);
data->queue = g_async_queue_ref(data->queue);
@@ -562,6 +763,8 @@ gpointer worker_main(gpointer _data)
const gchar* tmp;
xmlrpc_value* params, * result;
msg_connect_t* m = (msg_connect_t*)msg;
+ char* end;
+ long value;
server = xmlrpc_server_info_new(&env, m->url);
if (m->auth_username != NULL)
@@ -575,36 +778,61 @@ gpointer worker_main(gpointer _data)
params = xmlrpc_array_new(&env);
xmlrpc_client_call2(&env, client, server, "system.client_version",
params, &result);
+ xmlrpc_DECREF(params);
if (env.fault_occurred)
{
- xmlrpc_DECREF(params);
xmlrpc_server_info_free(server);
msg_free(msg);
server = NULL;
worker_respond(data,
- msg_connectresult(FALSE,
- "Unable to get client version (%d): %s",
- env.fault_code, env.fault_string));
+ msg_connectresult_failed(
+ "Unable to get client version (%d): %s",
+ env.fault_code, env.fault_string));
+ env.fault_occurred = FALSE;
continue;
}
- xmlrpc_DECREF(params);
xmlrpc_read_string(&env, result, &tmp);
+ xmlrpc_DECREF(result);
if (env.fault_occurred)
{
- xmlrpc_DECREF(result);
xmlrpc_server_info_free(server);
msg_free(msg);
server = NULL;
worker_respond(data,
- msg_connectresult(FALSE,
- "Unable to get client version (%d): %s",
- env.fault_code, env.fault_string));
+ msg_connectresult_failed(
+ "Unable to get client version (%d): %s",
+ env.fault_code, env.fault_string));
+ env.fault_occurred = FALSE;
continue;
}
- printf("result: %s\n", tmp);
- xmlrpc_DECREF(result);
- worker_respond(data,
- msg_connectresult(TRUE, NULL));
+ memset(&version, 0, sizeof(version));
+ errno = 0;
+ value = strtol(tmp, &end, 10);
+ if (errno == 0 && value >= 0 && value < INT_MAX)
+ {
+ version.major = (int)value;
+ if (end && *end == '.')
+ {
+ char* last = end + 1;
+ value = strtol(last, &end, 10);
+ if (errno == 0 && value >= 0 && value < INT_MAX)
+ {
+ version.minor = (int)value;
+ if (end && *end == '.')
+ {
+ last = end + 1;
+ value = strtol(last, &end, 10);
+ if (errno == 0 && value >= 0 && value < INT_MAX)
+ {
+ version.patch = (int)value;
+ }
+ }
+ }
+ }
+ }
+ free((void*)tmp);
+ worker_respond(data, msg_connectresult_success(version));
+ hashlist_clear(&hashlist);
msg_free(msg);
break;
}
@@ -615,17 +843,311 @@ gpointer worker_main(gpointer _data)
worker_respond(data, msg_status("Disconnected."));
break;
}
+ case MSG_UPDATELIST:
+ {
+ xmlrpc_value* params, * result = NULL;
+ int count;
+ params = xmlrpc_array_new(&env);
+ xmlrpc_client_call2(&env, client, server, "download_list",
+ params, &result);
+ if (!env.fault_occurred)
+ {
+ count = xmlrpc_array_size(&env, result);
+ }
+ if (env.fault_occurred)
+ {
+ xmlrpc_DECREF(params);
+ if (result) xmlrpc_DECREF(result);
+ worker_respond(data,
+ msg_status("Failed to update list (%d): %s",
+ env.fault_code, env.fault_string));
+ env.fault_occurred = FALSE;
+ msg_free(msg);
+ continue;
+ }
+ if (!hashlist_sync(&hashlist, &env,
+ result, count > 0 ? (gsize)count : 0))
+ {
+ xmlrpc_DECREF(params);
+ xmlrpc_DECREF(result);
+ if (env.fault_occurred)
+ {
+ worker_respond(data,
+ msg_status("Failed to update list (%d): %s",
+ env.fault_code, env.fault_string));
+ }
+ else
+ {
+ worker_respond(data,
+ msg_status("Failed to update list: Out of memory"));
+ }
+ env.fault_occurred = FALSE;
+ msg_free(msg);
+ continue;
+ }
+ xmlrpc_DECREF(result);
+ worker_respond(data, msg_synclist(hashlist.added,
+ hashlist.removed));
+
+ xmlrpc_client_call2(&env, client, server, "ratio.min",
+ params, &result);
+ ratio_min = get_i64_xmlrpc(&env, result) * 10;
+ if (env.fault_occurred)
+ {
+ env.fault_occurred = FALSE;
+ }
+ xmlrpc_client_call2(&env, client, server, "ratio.max",
+ params, &result);
+ ratio_max = get_i64_xmlrpc(&env, result) * 10;
+ if (env.fault_occurred)
+ {
+ env.fault_occurred = FALSE;
+ }
+ xmlrpc_client_call2(&env, client, server, "ratio.upload",
+ params, &result);
+ ratio_upload = get_i64_xmlrpc(&env, result);
+ if (env.fault_occurred)
+ {
+ env.fault_occurred = FALSE;
+ }
+
+ xmlrpc_DECREF(params);
+ msg_free(msg);
+ break;
+ }
+ case MSG_UPDATE:
+ {
+ msg_update_t* m = (msg_update_t*)msg;
+ xmlrpc_value* params;
+ torrent_data_t torrent_data;
+ do
+ {
+ xmlrpc_value* result = NULL, * item;
+ const gchar* tmpstr;
+ gint64 tmpi64;
+ gboolean tmpb;
+ guint64 done, up_rate, down_rate, ratio;
+ torrent_data_init(&torrent_data);
+ params = xmlrpc_array_new(&env);
+ item = xmlrpc_string_new(&env, m->hash);
+ xmlrpc_array_append_item(&env, params, item);
+ xmlrpc_DECREF(item);
+
+ xmlrpc_client_call2(&env, client, server, "d.is_active",
+ params, &result);
+ tmpb = get_bool_xmlrpc(&env, result);
+ if (env.fault_occurred)
+ {
+ break;
+ }
+ if (!tmpb)
+ {
+ xmlrpc_client_call2(&env, client, server,
+ "d.hashing_failed", params, &result);
+ tmpb = get_bool_xmlrpc(&env, result);
+ if (env.fault_occurred)
+ {
+ break;
+ }
+ if (tmpb)
+ {
+ torrent_data.state = STATE_HASHFAILED;
+ }
+ else
+ {
+ torrent_data.state = STATE_DEAD;
+ }
+ }
+ else
+ {
+ xmlrpc_client_call2(&env, client, server,
+ "d.is_hash_checking", params, &result);
+ tmpb = get_bool_xmlrpc(&env, result);
+ if (env.fault_occurred)
+ {
+ break;
+ }
+ if (tmpb)
+ {
+ torrent_data.state = STATE_REHASH;
+ }
+ else
+ {
+ torrent_data.state = STATE_ACTIVE;
+ }
+ }
+
+ xmlrpc_client_call2(&env, client, server, "d.name",
+ params, &result);
+ if (!env.fault_occurred)
+ {
+ xmlrpc_read_string(&env, result, &tmpstr);
+ }
+ if (result) xmlrpc_DECREF(result);
+ if (env.fault_occurred)
+ {
+ break;
+ }
+ torrent_data.title = (gchar*)tmpstr;
+
+ xmlrpc_client_call2(&env, client, server, "d.size_bytes",
+ params, &result);
+ tmpi64 = get_i64_xmlrpc(&env, result);
+ if (env.fault_occurred)
+ {
+ break;
+ }
+ torrent_data.size = (guint64)tmpi64;
+
+ xmlrpc_client_call2(&env, client, server, "d.bytes_done",
+ params, &result);
+ tmpi64 = get_i64_xmlrpc(&env, result);
+ if (env.fault_occurred)
+ {
+ break;
+ }
+ done = (guint64)tmpi64;
+ if (torrent_data.size > 0)
+ {
+ torrent_data.downloaded = ((gfloat)done * 100.0f) /
+ (gfloat)torrent_data.size;
+ if (torrent_data.downloaded > 100.0f)
+ {
+ torrent_data.downloaded = 100.0f;
+ }
+ }
+ else
+ {
+ torrent_data.downloaded = 0.0f;
+ }
+
+ xmlrpc_client_call2(&env, client, server, "d.ratio",
+ params, &result);
+ tmpi64 = get_i64_xmlrpc(&env, result);
+ if (env.fault_occurred)
+ {
+ break;
+ }
+ ratio = (guint64)tmpi64;
+ torrent_data.seeded = (gfloat)ratio / 1000.0f;
+
+ xmlrpc_client_call2(&env, client, server, "d.down.rate",
+ params, &result);
+ tmpi64 = get_i64_xmlrpc(&env, result);
+ if (env.fault_occurred)
+ {
+ break;
+ }
+ down_rate = (guint64)tmpi64;
+ torrent_data.down = (gfloat)down_rate / 1000.0f;
+
+ xmlrpc_client_call2(&env, client, server, "d.up.rate",
+ params, &result);
+ tmpi64 = get_i64_xmlrpc(&env, result);
+ if (env.fault_occurred)
+ {
+ break;
+ }
+ up_rate = (guint64)tmpi64;
+ torrent_data.up = (gfloat)up_rate / 1000.0f;
+
+ if (torrent_data.downloaded < 100.0f)
+ {
+ if (down_rate >= 512)
+ {
+ torrent_data.left = (torrent_data.size - done) /
+ (down_rate & ~(guint32)511);
+ }
+ }
+ else
+ {
+ gint64 upload_left = -1;
+ if (ratio_max > 0)
+ {
+ if (ratio < ratio_max)
+ {
+ upload_left = (torrent_data.size *
+ (ratio_max - ratio)) / 1000;
+ }
+ else
+ {
+ upload_left = 0;
+ }
+ }
+ if (ratio_min > 0)
+ {
+ /* Inexact but good enough */
+ guint64 uploaded_bytes =
+ (torrent_data.size * ratio) / 1000;
+ if (uploaded_bytes >= ratio_upload)
+ {
+ if (ratio < ratio_min)
+ {
+ gint64 tmp = (torrent_data.size *
+ (ratio_min - ratio)) / 1000;
+ if (upload_left < 0 || tmp < upload_left)
+ {
+ upload_left = tmp;
+ }
+ }
+ else
+ {
+ upload_left = 0;
+ }
+ }
+ else
+ {
+ gint64 tmp = ratio_upload - uploaded_bytes;
+ if (upload_left < 0 || tmp < upload_left)
+ {
+ upload_left = tmp;
+ }
+ }
+ }
+ if (upload_left >= 0)
+ {
+ if (up_rate >= 512)
+ {
+ torrent_data.left = (guint64)upload_left /
+ (up_rate & ~(guint32)511);
+ }
+ }
+ }
+ }
+ while (FALSE);
+ xmlrpc_DECREF(params);
+ if (env.fault_occurred)
+ {
+ worker_respond(data,
+ msg_status("Failed to update torrent %s (%d): %s",
+ m->hash, env.fault_code, env.fault_string));
+ env.fault_occurred = FALSE;
+ torrent_data.state = STATE_DEAD;
+ worker_respond(data, msg_sync(&m->torrent, &torrent_data));
+ msg_free(msg);
+ continue;
+ }
+
+ worker_respond(data, msg_sync(&m->torrent, &torrent_data));
+ msg_free(msg);
+ break;
+ }
case MSG_QUIT:
quit = TRUE;
+ msg_free(msg);
break;
case MSG_CONNECTRESULT:
case MSG_ERROR:
case MSG_STATUS:
+ case MSG_SYNCLIST:
+ case MSG_SYNC:
g_assert(FALSE);
break;
}
}
+ hashlist_free(&hashlist);
+
if (server != NULL)
{
xmlrpc_server_info_free(server);
@@ -652,37 +1174,77 @@ void msg_free(msg_t* msg)
g_free(m->url);
g_free(m->auth_username);
g_free(m->auth_password);
+ g_slice_free(msg_connect_t, m);
break;
}
case MSG_CONNECTRESULT:
{
msg_connectresult_t* m = (msg_connectresult_t*)msg;
- g_free(m->errmsg);
+ if (!m->success)
+ {
+ g_free(m->errmsg);
+ }
+ g_slice_free(msg_connectresult_t, m);
break;
}
case MSG_ERROR:
{
msg_error_t* m = (msg_error_t*)msg;
g_free(m->msg);
+ g_slice_free(msg_error_t, m);
break;
}
case MSG_STATUS:
{
msg_status_t* m = (msg_status_t*)msg;
g_free(m->msg);
+ g_slice_free(msg_status_t, m);
break;
}
case MSG_DISCONNECT:
+ {
+ msg_disconnect_t* m = (msg_disconnect_t*)msg;
+ g_slice_free(msg_disconnect_t, m);
+ break;
+ }
case MSG_QUIT:
+ {
+ msg_quit_t* m = (msg_quit_t*)msg;
+ g_slice_free(msg_quit_t, m);
+ break;
+ }
+ case MSG_UPDATELIST:
+ {
+ msg_updatelist_t* m = (msg_updatelist_t*)msg;
+ g_slice_free(msg_updatelist_t, m);
break;
}
- g_free(msg);
+ case MSG_SYNCLIST:
+ {
+ msg_synclist_t* m = (msg_synclist_t*)msg;
+ g_slice_free(msg_synclist_t, m);
+ break;
+ }
+ case MSG_UPDATE:
+ {
+ msg_update_t* m = (msg_update_t*)msg;
+ g_slice_free(msg_update_t, m);
+ break;
+ }
+ case MSG_SYNC:
+ {
+ msg_sync_t* m = (msg_sync_t*)msg;
+ torrent_data_free(&m->data);
+ g_slice_free(msg_sync_t, m);
+ break;
+ }
+ }
}
msg_t* msg_connect(const gchar* url, const gchar* username,
const gchar* password)
{
- msg_connect_t* msg = g_new0(msg_connect_t, 1);
+ msg_connect_t* msg = g_slice_new(msg_connect_t);
msg->base.type = MSG_CONNECT;
msg->url = strdup(url);
msg->auth_username = g_strdup(username);
@@ -690,28 +1252,30 @@ msg_t* msg_connect(const gchar* url, const gchar* username,
return &msg->base;
}
-msg_t* msg_connectresult(gboolean success, const gchar* errfmt, ...)
+msg_t* msg_connectresult_success(version_t version)
{
- msg_connectresult_t* msg = g_new0(msg_connectresult_t, 1);
+ msg_connectresult_t* msg = g_slice_new(msg_connectresult_t);
msg->base.type = MSG_CONNECTRESULT;
- if (success)
- {
- msg->success = TRUE;
- }
- else
- {
- va_list args;
- msg->success = FALSE;
- va_start(args, errfmt);
- msg->errmsg = g_strdup_vprintf(errfmt, args);
- va_end(args);
- }
+ msg->success = TRUE;
+ msg->version = version;
+ return &msg->base;
+}
+
+msg_t* msg_connectresult_failed(const gchar* errfmt, ...)
+{
+ msg_connectresult_t* msg = g_slice_new(msg_connectresult_t);
+ va_list args;
+ msg->base.type = MSG_CONNECTRESULT;
+ msg->success = FALSE;
+ va_start(args, errfmt);
+ msg->errmsg = g_strdup_vprintf(errfmt, args);
+ va_end(args);
return &msg->base;
}
msg_t* msg_error(const gchar* format, ...)
{
- msg_error_t* msg = g_new0(msg_error_t, 1);
+ msg_error_t* msg = g_slice_new(msg_error_t);
va_list args;
msg->base.type = MSG_ERROR;
va_start(args, format);
@@ -722,7 +1286,7 @@ msg_t* msg_error(const gchar* format, ...)
msg_t* msg_status(const gchar* format, ...)
{
- msg_status_t* msg = g_new0(msg_status_t, 1);
+ msg_status_t* msg = g_slice_new(msg_status_t);
va_list args;
msg->base.type = MSG_STATUS;
va_start(args, format);
@@ -733,14 +1297,319 @@ msg_t* msg_status(const gchar* format, ...)
msg_t* msg_disconnect(void)
{
- msg_disconnect_t* msg = g_new0(msg_disconnect_t, 1);
+ msg_disconnect_t* msg = g_slice_new(msg_disconnect_t);
msg->base.type = MSG_DISCONNECT;
return &msg->base;
}
msg_t* msg_quit(void)
{
- msg_quit_t* msg = g_new0(msg_quit_t, 1);
+ msg_quit_t* msg = g_slice_new(msg_quit_t);
msg->base.type = MSG_QUIT;
return &msg->base;
}
+
+msg_t* msg_updatelist(void)
+{
+ msg_updatelist_t* msg = g_slice_new(msg_updatelist_t);
+ msg->base.type = MSG_UPDATELIST;
+ return &msg->base;
+}
+
+msg_t* msg_synclist(const gchar** added, const gchar** removed)
+{
+ msg_synclist_t* msg = g_slice_new(msg_synclist_t);
+ msg->base.type = MSG_SYNCLIST;
+ msg->added = added;
+ msg->removed = removed;
+ return &msg->base;
+}
+
+msg_t* msg_update(const gchar* hash, torrent_t* torrent)
+{
+ msg_update_t* msg = g_slice_new(msg_update_t);
+ msg->base.type = MSG_UPDATE;
+ msg->hash = hash;
+ msg->torrent = *torrent;
+ return &msg->base;
+}
+
+msg_t* msg_sync(torrent_t* torrent, torrent_data_t* data)
+{
+ msg_sync_t* msg = g_slice_new(msg_sync_t);
+ msg->base.type = MSG_SYNC;
+ msg->torrent = *torrent;
+ msg->data = *data;
+ return &msg->base;
+}
+
+void hashlist_init(hashlist_t* hlist)
+{
+ hlist->fill = 0;
+ hlist->size = 10;
+ hlist->data = g_new0(gchar*, hlist->size);
+ hlist->mark = g_new0(gboolean, hlist->size);
+ hlist->added = (const gchar**)hlist->data;
+ hlist->removed = (const gchar**)hlist->data + hlist->size - 1;
+}
+
+void hashlist_clear(hashlist_t* hlist)
+{
+ gsize i, a, r;
+ for (a = hlist->added - (const gchar**)hlist->data; hlist->data[a]; ++a);
+ r = hlist->removed - (const gchar**)hlist->data;
+ for (i = 0; i < a; ++i)
+ {
+ g_free(hlist->data[i]);
+ }
+ for (i = r; hlist->data[i]; ++i)
+ {
+ g_free(hlist->data[i]);
+ }
+ hlist->data[0] = NULL;
+ hlist->fill = 0;
+ hlist->added = (const gchar**)hlist->data;
+ hlist->removed = (const gchar**)hlist->data + hlist->size - 1;
+}
+
+void hashlist_free(hashlist_t* hlist)
+{
+ hashlist_clear(hlist);
+ g_free(hlist->data);
+ g_free(hlist->mark);
+}
+
+gboolean hashlist_resize(hashlist_t* hlist, gsize* a, gsize* r)
+{
+ gsize ns = hlist->size * 2;
+ gchar** tmp1;
+ gboolean* tmp2;
+ g_assert(*r == hlist->size - 1);
+ if (ns < 10) ns = 10;
+ tmp1 = realloc(hlist->data, ns * sizeof(gchar**));
+ tmp2 = realloc(hlist->mark, ns * sizeof(gboolean));
+ if (tmp1 == NULL || tmp2 == NULL)
+ {
+ if (tmp1) hlist->data = tmp1;
+ if (tmp2) hlist->mark = tmp2;
+ hlist->added = (const gchar**)hlist->data + hlist->fill;
+ hlist->removed = (const gchar**)hlist->data + *r;
+ return FALSE;
+ }
+ memset(tmp1 + hlist->size, 0, (ns - hlist->size) * sizeof(gchar*));
+ hlist->data = tmp1;
+ hlist->mark = tmp2;
+ hlist->size = ns;
+ *r = hlist->size - 1;
+ return TRUE;
+}
+
+gboolean hashlist_sync(hashlist_t* hlist, xmlrpc_env * const env,
+ const xmlrpc_value * const result, gsize count)
+{
+ gsize i, j, a, r;
+ gsize found = 0;
+ g_assert((const gchar**)hlist->data + hlist->fill == hlist->added);
+ for (a = hlist->added - (const gchar**)hlist->data; hlist->data[a]; ++a);
+ hlist->fill = a;
+ hlist->added = (const gchar**)hlist->data + a;
+ for (r = hlist->removed - (const gchar**)hlist->data; hlist->data[r]; ++r)
+ {
+ g_free(hlist->data[r]);
+ }
+ g_assert(r == hlist->size - 1);
+ if (a + 5 > r)
+ {
+ gsize ns = a + 10;
+ gchar** tmp1 = realloc(hlist->data, ns * sizeof(gchar*));
+ if (tmp1 != NULL)
+ {
+ gboolean* tmp2 = realloc(hlist->mark, ns * sizeof(gboolean));
+ hlist->data = tmp1;
+ if (tmp2 != NULL)
+ {
+ hlist->mark = tmp2;
+ memset(hlist->data + hlist->size, 0,
+ (ns - hlist->size) * sizeof(gchar*));
+ hlist->size = ns;
+ r = hlist->size - 1;
+ }
+ }
+ }
+ memset(hlist->mark, 0, hlist->fill * sizeof(gboolean));
+ for (i = 0; i < count; ++i)
+ {
+ xmlrpc_value* item;
+ const gchar* hash;
+ xmlrpc_array_read_item(env, result, i, &item);
+ xmlrpc_read_string(env, item, &hash);
+ xmlrpc_DECREF(item);
+ if (env->fault_occurred)
+ {
+ hlist->added = (const gchar**)hlist->data + hlist->fill;
+ hlist->removed = (const gchar**)hlist->data + r;
+ return FALSE;
+ }
+ if (found == hlist->fill)
+ {
+ if (a + 1 == r &&
+ !hashlist_resize(hlist, &a, &r))
+ {
+ free((void*)hash);
+ return FALSE;
+ }
+ hlist->data[a++] = (gchar*)hash;
+ continue;
+ }
+ /* Quick path */
+ if (i < hlist->fill && !hlist->mark[i] &&
+ strcmp(hlist->data[i], hash) == 0)
+ {
+ hlist->mark[i] = TRUE;
+ found++;
+ free((void*)hash);
+ continue;
+ }
+ for (j = i + 1; j != i; ++j)
+ {
+ if (j >= hlist->fill)
+ {
+ j = 0;
+ if (i == 0)
+ {
+ break;
+ }
+ }
+ if (hlist->mark[j])
+ {
+ continue;
+ }
+ if (strcmp(hlist->data[j], hash) == 0)
+ {
+ break;
+ }
+ }
+ if (j != i)
+ {
+ hlist->mark[j] = TRUE;
+ found++;
+ free((void*)hash);
+ }
+ else
+ {
+ /* New */
+ if (a + 1 == r &&
+ !hashlist_resize(hlist, &a, &r))
+ {
+ free((void*)hash);
+ return FALSE;
+ }
+ hlist->data[a++] = (gchar*)hash;
+ }
+ }
+ if (found < hlist->fill)
+ {
+ gsize n = hlist->fill - found;
+ for (j = hlist->fill - 1; n > 0; --j)
+ {
+ if (!hlist->mark[j])
+ {
+ hlist->data[--r] = hlist->data[j];
+ a--;
+ hlist->fill--;
+ memmove(hlist->data + j, hlist->data + j + 1,
+ (a - j) * sizeof(gchar*));
+ hlist->data[a] = NULL;
+ n--;
+ }
+ }
+ }
+ hlist->added = (const gchar**)hlist->data + hlist->fill;
+ hlist->removed = (const gchar**)hlist->data + r;
+ return TRUE;
+}
+
+void noop_destroy(gpointer key)
+{
+}
+
+void torrent_destroy(gpointer value)
+{
+ torrent_t* torrent = value;
+ gtk_list_store_remove(torrent->store, &torrent->iter);
+ g_slice_free(torrent_t, torrent);
+}
+
+void torrent_data_init(torrent_data_t* data)
+{
+ memset(data, 0, sizeof(torrent_data_t));
+ data->left = -1;
+}
+
+void torrent_data_free(torrent_data_t* data)
+{
+ g_free(data->title);
+}
+
+gint64 get_i64_xmlrpc(xmlrpc_env* env, xmlrpc_value* value)
+{
+ if (env->fault_occurred || !value)
+ {
+ return 0;
+ }
+ switch (xmlrpc_value_type(value))
+ {
+#if XMLRPC_HAVE_I8
+ case XMLRPC_TYPE_I8:
+ {
+ xmlrpc_int64 tmp;
+ xmlrpc_read_i8(env, value, &tmp);
+ xmlrpc_DECREF(value);
+ return tmp;
+ }
+#endif
+ case XMLRPC_TYPE_INT:
+ default:
+ {
+ xmlrpc_int tmp;
+ xmlrpc_read_int(env, value, &tmp);
+ xmlrpc_DECREF(value);
+ return tmp;
+ }
+ }
+}
+
+gboolean get_bool_xmlrpc(xmlrpc_env* env, xmlrpc_value* value)
+{
+ if (env->fault_occurred || !value)
+ {
+ return 0;
+ }
+ switch (xmlrpc_value_type(value))
+ {
+#if XMLRPC_HAVE_I8
+ case XMLRPC_TYPE_I8:
+ {
+ xmlrpc_int64 tmp;
+ xmlrpc_read_i8(env, value, &tmp);
+ xmlrpc_DECREF(value);
+ return tmp != 0;
+ }
+#endif
+ case XMLRPC_TYPE_INT:
+ {
+ xmlrpc_int tmp;
+ xmlrpc_read_int(env, value, &tmp);
+ xmlrpc_DECREF(value);
+ return tmp != 0;
+ }
+ case XMLRPC_TYPE_BOOL:
+ default:
+ {
+ xmlrpc_bool tmp;
+ xmlrpc_read_bool(env, value, &tmp);
+ xmlrpc_DECREF(value);
+ return tmp;
+ }
+ }
+}