From ef193c1fbc29b0ebd7842866d606782fc3208d15 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Wed, 16 Nov 2011 23:35:33 +0100 Subject: Starts to display useful information now --- Makefile | 3 + configure.sh | 2 +- gui/viewtorrents.glade | 7 +- src/customcellrendererrate.c | 3 +- src/customcellrendererstate.c | 3 + src/customcellrendererstate.h | 1 + src/main.c | 989 +++++++++++++++++++++++++++++++++++++++--- 7 files changed, 944 insertions(+), 64 deletions(-) diff --git a/Makefile b/Makefile index e2ad0f5..f0e2ef0 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ all: $(MAKE) -C src all +check: + $(MAKE) -C test check + clean: $(MAKE) -C src clean diff --git a/configure.sh b/configure.sh index 0dd7b57..b4dbe28 100644 --- a/configure.sh +++ b/configure.sh @@ -1,2 +1,2 @@ -glib >= 2.6 +glib >= 2.28 xmlrpc-c >= 1.05 \ No newline at end of file diff --git a/gui/viewtorrents.glade b/gui/viewtorrents.glade index db2283f..db80a9c 100644 --- a/gui/viewtorrents.glade +++ b/gui/viewtorrents.glade @@ -42,6 +42,7 @@ True True True + True True False True @@ -303,6 +304,7 @@ If authentication is needed, enter username and password as well. True 1 True + 1 fixed @@ -343,7 +345,7 @@ If authentication is needed, enter username and password as well. fixed - 60 + 70 Up True True @@ -354,7 +356,7 @@ If authentication is needed, enter username and password as well. fixed - 60 + 70 Down True True @@ -434,6 +436,7 @@ If authentication is needed, enter username and password as well. True True True + True True False True diff --git a/src/customcellrendererrate.c b/src/customcellrendererrate.c index 4b7b9bf..f86c76d 100644 --- a/src/customcellrendererrate.c +++ b/src/customcellrendererrate.c @@ -22,6 +22,7 @@ G_DEFINE_TYPE(CustomCellRendererRate, custom_cell_renderer_rate, GTK_TYPE_CELL_R static void custom_cell_renderer_rate_init(CustomCellRendererRate* cell_rate) { + g_object_set(cell_rate, "xalign", 1.0f, NULL); convert_rate_to_string(cell_rate->buffer, sizeof(cell_rate->buffer), cell_rate->rate); g_object_set(cell_rate, "text", cell_rate->buffer, NULL); @@ -112,7 +113,7 @@ void convert_rate_to_string(gchar* str, guint size, gfloat rate) } else if (rate < 0.9f) { - g_snprintf(str, size, "%3fB", rate * 1000.0f); + g_snprintf(str, size, "%3.0fB", rate * 1000.0f); } else if (rate <= 900.0f) { diff --git a/src/customcellrendererstate.c b/src/customcellrendererstate.c index 1f0a7f8..70d5d29 100644 --- a/src/customcellrendererstate.c +++ b/src/customcellrendererstate.c @@ -113,6 +113,9 @@ void update_state(CustomCellRendererState* cell_state, gint state) case STATE_REHASH: g_object_set(cell_state, "stock-id", GTK_STOCK_REFRESH, NULL); break; + case STATE_HASHFAILED: + g_object_set(cell_state, "stock-id", GTK_STOCK_NO, NULL); + break; default: g_assert(FALSE); break; diff --git a/src/customcellrendererstate.h b/src/customcellrendererstate.h index 3ad16e2..2d163f3 100644 --- a/src/customcellrendererstate.h +++ b/src/customcellrendererstate.h @@ -8,6 +8,7 @@ typedef enum STATE_DEAD, /* Not active (downloaded >= 100.0 ? closed : paused) */ STATE_ACTIVE, /* Active (downloaded >= 100.0 ? seeding : downloading) */ STATE_REHASH, /* Hashing */ + STATE_HASHFAILED, /* Hashing failed */ } state_t; G_BEGIN_DECLS 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 #include #include +#include #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; + } + } +} -- cgit v1.2.3-70-g09d2