#include "common.h" #include #include #include #include #include #include #include #include "customcellrendererstate.h" #include "customcellrendererprogress.h" #include "customcellrendererrate.h" #include "customcellrendererleft.h" #ifndef G_VALUE_INIT # define G_VALUE_INIT { 0 } #endif static const guint UPDATE_INTERVAL_MS = 3500; typedef enum { COLUMN_STATE, COLUMN_TITLE, COLUMN_DOWNLOADED, COLUMN_SEEDED, COLUMN_UP, COLUMN_DOWN, COLUMN_LEFT, COLUMN_PROGRESSSORT, COLUMN_HASH, } column_t; 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 */ } torrent_data_t; typedef struct { GtkListStore* store; GtkTreeIter iter; } torrent_t; static void torrent_update(const gchar* hash, 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 { GAsyncQueue* queue; gboolean connected; GtkListStore* liststore; GtkTreeSelection* listselection; GtkWidget* top; GtkAction* connectaction, * disconnectaction; GtkAction* startaction, * stopaction, * rehashaction; GtkWidget* connectdlg; GtkWidget* connectdlg_url; GtkWidget* connectdlg_user; GtkWidget* connectdlg_pwd; GtkWidget* statusbar; guint status_contextid; GKeyFile* config; gchar* configfile; guint config_timer; guint config_try; GHashTable* torrents; guint64 last_sync_time; guint last_sync_count; guint sync_timeout; const gchar* item_lock; } master_t; typedef struct { GAsyncQueue* queue; master_t* master; guint64 ratio_min, ratio_max, ratio_upload; } 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, /* Worker responding to an MSG_CONNECT */ MSG_CONNECTRESULT, /* Tell worker to disconnect. No response. */ MSG_DISCONNECT, /* Tell worker to QUIT. No response. */ MSG_QUIT, /* Worker reporting a fatal error to main. Main should just quit. */ MSG_ERROR, /* Worker reporting a status message to main. Can be generated at any time * for any reason */ MSG_STATUS, /* Tell worker to update the list of torrents. * Will generate a MSG_SYNCLIST response */ MSG_UPDATELIST, /* Worker responding to a MSG_UPDATELIST with added and removed torrents */ MSG_SYNCLIST, /* Tell worker to update a torrent. * Will generate a MSG_SYNC response */ MSG_UPDATE, /* Worker responding to a MSG_UPDATE with uptodate torrent data */ MSG_SYNC, /* Tell worker to send a start command for torrent */ MSG_START, /* Tell worker to send a stop command for torrent */ MSG_STOP, /* Tell worker to send a rehash command for torrent */ MSG_REHASH, } msg_type_t; typedef struct { msg_type_t type; master_t* master; } msg_t; typedef struct { msg_t base; gchar* url; gchar* auth_username, * auth_password; } msg_connect_t; typedef struct { msg_t base; gboolean success; version_t version; gchar* errmsg; } msg_connectresult_t; typedef struct { msg_t base; gchar* msg; } msg_error_t; typedef struct { msg_t base; gchar* msg; } msg_status_t; typedef struct { msg_t base; } msg_quit_t; 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; const gchar* hash; torrent_t torrent; torrent_data_t data; } msg_sync_t; typedef struct { msg_t base; gchar* hash; torrent_t torrent; } msg_start_t; typedef struct { msg_t base; gchar* hash; torrent_t torrent; } msg_stop_t; typedef struct { msg_t base; gchar* hash; torrent_t torrent; } msg_rehash_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_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(const gchar* hash, torrent_t* torrent, torrent_data_t* data); static msg_t* msg_start(const gchar* hash, torrent_t* torrent); static msg_t* msg_stop(const gchar* hash, torrent_t* torrent); static msg_t* msg_rehash(const gchar* hash, torrent_t* torrent); 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(GtkAction* action, gpointer data); static void do_disconnect(GtkAction* action, gpointer data); static void do_start(GtkAction* action, gpointer data); static void do_stop(GtkAction* action, gpointer data); static void do_rehash(GtkAction* action, gpointer data); static gpointer worker_main(gpointer data); static gboolean incoming_msg(gpointer data); 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); static void listselection_changed(GtkTreeSelection* selection, gpointer user_data); static void liststore_sort_column_changed(GtkTreeSortable* sortable, gpointer user_data); static gint liststore_default_compare_func(GtkTreeModel* model, GtkTreeIter* a, GtkTreeIter* b, gpointer user_data); int main(int argc, char** argv) { const gchar* gladefile; GtkBuilder* builder; GError* error = NULL; guint ret; GtkWidget* pwddlg; GtkCellRenderer* cell; GtkTreeViewColumn* column; GThread* worker; worker_data_t worker_data; master_t master; memset(&master, 0, sizeof(master)); memset(&worker_data, 0, sizeof(worker_data)); g_thread_init(NULL); gtk_init(&argc, &argv); worker_data.queue = g_async_queue_new(); master.queue = worker_data.queue; worker_data.master = &master; worker = g_thread_create(worker_main, &worker_data, TRUE, NULL); if (worker == NULL) { fprintf(stderr, "Unable to create thread\n"); return EXIT_FAILURE; } builder = gtk_builder_new(); gladefile = DATAROOTDIR "viewtorrents/viewtorrents.glade"; ret = gtk_builder_add_from_file(builder, gladefile, &error); #ifdef DEBUG if (ret == 0) { g_clear_error(&error); gladefile = "gui/viewtorrents.glade"; ret = gtk_builder_add_from_file(builder, gladefile, &error); } #endif if (ret == 0) { fprintf(stderr, "Unable to load %s: %s\n", gladefile, error->message); g_clear_error(&error); 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", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(gtk_builder_get_object(builder, "quitmenuitem"), "activate", G_CALLBACK(gtk_main_quit), NULL); master.connectaction = GTK_ACTION(gtk_builder_get_object(builder, "connectaction")); g_signal_connect(master.connectaction, "activate", G_CALLBACK(do_connect), &master); master.disconnectaction = GTK_ACTION(gtk_builder_get_object(builder, "disconnectaction")); g_signal_connect(master.disconnectaction, "activate", G_CALLBACK(do_disconnect), &master); master.startaction = GTK_ACTION(gtk_builder_get_object(builder, "startaction")); g_signal_connect(master.startaction, "activate", G_CALLBACK(do_start), &master); master.stopaction = GTK_ACTION(gtk_builder_get_object(builder, "stopaction")); g_signal_connect(master.stopaction, "activate", G_CALLBACK(do_stop), &master); master.rehashaction = GTK_ACTION(gtk_builder_get_object(builder, "rehashaction")); g_signal_connect(master.rehashaction, "activate", G_CALLBACK(do_rehash), &master); gtk_action_set_sensitive(master.connectaction, TRUE); gtk_action_set_sensitive(master.disconnectaction, FALSE); gtk_action_set_sensitive(master.startaction, FALSE); gtk_action_set_sensitive(master.stopaction, FALSE); gtk_action_set_sensitive(master.rehashaction, FALSE); pwddlg = GTK_WIDGET(gtk_builder_get_object(builder, "pwddlg")); gtk_dialog_set_default_response(GTK_DIALOG(pwddlg), GTK_RESPONSE_OK); master.liststore = GTK_LIST_STORE(gtk_builder_get_object(builder, "liststore")); g_signal_connect(master.liststore, "sort-column-changed", G_CALLBACK(liststore_sort_column_changed), &master); gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(master.liststore), liststore_default_compare_func, &master, NULL); master.listselection = gtk_tree_view_get_selection( GTK_TREE_VIEW(gtk_builder_get_object(builder, "treeview"))); gtk_tree_selection_set_mode(master.listselection, GTK_SELECTION_SINGLE); g_signal_connect(master.listselection, "changed", G_CALLBACK(listselection_changed), &master); cell = custom_cell_renderer_state_new(); column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object(builder, "statecolumn")); gtk_tree_view_column_pack_start(column, cell, TRUE); gtk_tree_view_column_add_attribute(column, cell, "state", COLUMN_STATE); cell = custom_cell_renderer_progress_new(); column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object(builder, "progresscolumn")); gtk_tree_view_column_pack_start(column, cell, TRUE); gtk_tree_view_column_add_attribute(column, cell, "downloaded", COLUMN_DOWNLOADED); gtk_tree_view_column_add_attribute(column, cell, "seeded", COLUMN_SEEDED); cell = custom_cell_renderer_rate_new(); column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object(builder, "upcolumn")); gtk_tree_view_column_pack_start(column, cell, TRUE); gtk_tree_view_column_add_attribute(column, cell, "rate", COLUMN_UP); column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object(builder,"downcolumn")); gtk_tree_view_column_pack_start(column, cell, TRUE); gtk_tree_view_column_add_attribute(column, cell, "rate", COLUMN_DOWN); cell = custom_cell_renderer_left_new(); column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object(builder,"leftcolumn")); gtk_tree_view_column_pack_start(column, cell, TRUE); gtk_tree_view_column_add_attribute(column, cell, "left", COLUMN_LEFT); master.statusbar = GTK_WIDGET(gtk_builder_get_object(builder, "statusbar")); master.status_contextid = gtk_statusbar_get_context_id(GTK_STATUSBAR(master.statusbar), "main"); master.config = g_key_file_new(); master.configfile = g_build_filename(g_get_user_config_dir(), "viewtorrent.ini", NULL); g_key_file_load_from_file(master.config, master.configfile, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL); { gint column = g_key_file_get_integer(master.config, "view", "sort_column", NULL); gboolean desc = g_key_file_get_boolean(master.config, "view", "sort_desc", NULL); if (column > 0) { gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE(master.liststore), column, desc ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING); } else { gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE(master.liststore), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING); } } gtk_widget_show_all(master.top); master.connectdlg = GTK_WIDGET(gtk_builder_get_object(builder, "connectdlg")); gtk_dialog_set_default_response(GTK_DIALOG(master.connectdlg), GTK_RESPONSE_OK); master.connectdlg_url = GTK_WIDGET(gtk_builder_get_object(builder, "connectdlg_url")); master.connectdlg_user = GTK_WIDGET(gtk_builder_get_object(builder, "connectdlg_user")); master.connectdlg_pwd = GTK_WIDGET(gtk_builder_get_object(builder, "connectdlg_pwd")); if (g_key_file_get_boolean(master.config, "connect", "auto", NULL)) { gchar* url = g_key_file_get_string(master.config, "connect", "url", NULL); gchar* user = g_key_file_get_string(master.config, "connect", "username", NULL); gchar* pass = NULL; if (url != NULL && user != NULL) { GtkWidget* label, * pwd; gchar* format, * text; label = GTK_WIDGET(gtk_builder_get_object(builder, "pwddlg_label")); pwd = GTK_WIDGET(gtk_builder_get_object(builder, "pwddlg_pwd")); format = g_strdup(gtk_label_get_text(GTK_LABEL(label))); text = g_strdup_printf(format, url); g_free(format); gtk_label_set_text(GTK_LABEL(label), text); g_free(text); switch (gtk_dialog_run(GTK_DIALOG(pwddlg))) { case GTK_RESPONSE_OK: pass = g_strdup(gtk_entry_get_text(GTK_ENTRY(pwd))); break; case GTK_RESPONSE_CANCEL: default: g_free(url); url = NULL; } gtk_widget_hide(pwddlg); } if (url != NULL) { g_async_queue_push(worker_data.queue, msg_connect(url, user, pass)); status(&master, "Autoconnecting to %s...", url); } g_free(url); g_free(user); g_free(pass); } gtk_main(); 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; sync_config(&master); } g_async_queue_push(worker_data.queue, msg_quit()); g_async_queue_unref(worker_data.queue); 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); return EXIT_SUCCESS; } gboolean sync_config(gpointer _data) { master_t* master = (master_t*)_data; gsize length; gchar *data = g_key_file_to_data(master->config, &length, NULL); GError* err = NULL; if (!g_file_set_contents(master->configfile, data, length, &err)) { g_free(data); status(master, "Error writing configfile: %s", err->message); g_error_free(err); master->config_try++; if (master->config_try == 2) { /* Wait longer */ master->config_timer = g_timeout_add_seconds(5 * 60, sync_config, master); return FALSE; } if (master->config_try == 10) { /* Just give up */ return FALSE; } /* Try again */ return TRUE; } g_free(data); master->config_timer = 0; master->config_try = 0; 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()); master->sync_timeout = 0; return FALSE; } gboolean incoming_msg(gpointer data) { msg_t* msg = data; switch (msg->type) { case MSG_CONNECTRESULT: { msg_connectresult_t* m = (msg_connectresult_t*)msg; if (m->success) { msg->master->connected = TRUE; gtk_action_set_sensitive(msg->master->connectaction, FALSE); gtk_action_set_sensitive(msg->master->disconnectaction, TRUE); listselection_changed(msg->master->listselection, msg->master); g_key_file_set_boolean(msg->master->config, "connect", "auto", TRUE); save_config(msg->master); 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 { GtkWidget* dlg; msg->master->connected = FALSE; gtk_action_set_sensitive(msg->master->connectaction, TRUE); gtk_action_set_sensitive(msg->master->disconnectaction, FALSE); gtk_list_store_clear(msg->master->liststore); listselection_changed(msg->master->listselection, msg->master); dlg = gtk_message_dialog_new(GTK_WINDOW(msg->master->top), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Failed to connect: %s", m->errmsg); gtk_dialog_run(GTK_DIALOG(dlg)); gtk_widget_destroy(dlg); } break; } case MSG_ERROR: { msg_error_t* m = (msg_error_t*)msg; GtkWidget* dlg; dlg = gtk_message_dialog_new(GTK_WINDOW(msg->master->top), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Fatal error: %s", m->msg); gtk_dialog_run(GTK_DIALOG(dlg)); gtk_widget_destroy(dlg); gtk_main_quit(); break; } case MSG_STATUS: { msg_status_t* m = (msg_status_t*)msg; 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) { if (msg->master->item_lock == *x) { msg->master->item_lock = NULL; listselection_changed(msg->master->listselection, msg->master); } 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->hash, &m->torrent, &m->data); if (msg->master->item_lock == m->hash) { msg->master->item_lock = NULL; listselection_changed(msg->master->listselection, msg->master); } 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: case MSG_START: case MSG_STOP: case MSG_REHASH: g_assert(FALSE); } msg_free(msg); return FALSE; } void do_connect(GtkAction* action, gpointer data) { master_t* master = data; gchar* url, * user, * pass = NULL; if (master->connected) { return; } url = g_key_file_get_string(master->config, "connect", "url", NULL); user = g_key_file_get_string(master->config, "connect", "username", NULL); gtk_entry_set_text(GTK_ENTRY(master->connectdlg_url), url ? url : ""); gtk_entry_set_text(GTK_ENTRY(master->connectdlg_user),user ? user : ""); gtk_entry_set_text(GTK_ENTRY(master->connectdlg_pwd), ""); switch (gtk_dialog_run(GTK_DIALOG(master->connectdlg))) { case GTK_RESPONSE_OK: g_free(url); g_free(user); url = g_strdup(gtk_entry_get_text(GTK_ENTRY(master->connectdlg_url))); user = g_strdup(gtk_entry_get_text(GTK_ENTRY(master->connectdlg_user))); pass = g_strdup(gtk_entry_get_text(GTK_ENTRY(master->connectdlg_pwd))); break; case GTK_RESPONSE_CANCEL: default: g_free(url); g_free(user); url = NULL; } gtk_widget_hide(master->connectdlg); if (url == NULL) { return; } if (strlen(user) == 0 && strlen(pass) == 0) { g_free(user); g_free(pass); user = NULL; pass = NULL; } gtk_action_set_sensitive(action, FALSE); status(master, "Connecting to %s...", url); g_key_file_set_string(master->config, "connect", "url", url); if (user != NULL) { g_key_file_set_string(master->config, "connect", "username", user); } else { g_key_file_remove_key(master->config, "connect", "username", NULL); } g_key_file_remove_key(master->config, "connect", "auto", NULL); save_config(master); g_async_queue_push(master->queue, msg_connect(url, user, pass)); g_free(url); g_free(user); g_free(pass); } void do_disconnect(GtkAction* action, gpointer data) { master_t* master = data; if (!master->connected) { return; } if (master->sync_timeout != 0) { g_source_remove(master->sync_timeout); master->sync_timeout = 0; } gtk_action_set_sensitive(action, FALSE); status(master, "Disconnecting..."); g_hash_table_remove_all(master->torrents); g_async_queue_push(master->queue, msg_disconnect()); master->connected = FALSE; listselection_changed(master->listselection, master); } static gboolean get_selected_torrent(master_t* master, const gchar** hash, torrent_t* torrent) { GValue value = G_VALUE_INIT; GtkTreeModel* model; if (!master->connected || master->item_lock) { return FALSE; } if (!gtk_tree_selection_get_selected(master->listselection, &model, &torrent->iter)) { return FALSE; } torrent->store = master->liststore; gtk_tree_model_get_value(model, &torrent->iter, COLUMN_HASH, &value); *hash = g_value_get_pointer(&value); g_value_unset(&value); return TRUE; } void do_start(GtkAction* action, gpointer data) { master_t* master = data; torrent_t torrent; const gchar* hash; if (!get_selected_torrent(master, &hash, &torrent)) { return; } g_async_queue_push(master->queue, msg_start(hash, &torrent)); master->item_lock = hash; gtk_action_set_sensitive(master->startaction, FALSE); gtk_action_set_sensitive(master->stopaction, FALSE); gtk_action_set_sensitive(master->rehashaction, FALSE); } void do_stop(GtkAction* action, gpointer data) { master_t* master = data; torrent_t torrent; const gchar* hash; if (!get_selected_torrent(master, &hash, &torrent)) { return; } g_async_queue_push(master->queue, msg_stop(hash, &torrent)); master->item_lock = hash; gtk_action_set_sensitive(master->startaction, FALSE); gtk_action_set_sensitive(master->stopaction, FALSE); gtk_action_set_sensitive(master->rehashaction, FALSE); } void do_rehash(GtkAction* action, gpointer data) { master_t* master = data; torrent_t torrent; const gchar* hash; if (!get_selected_torrent(master, &hash, &torrent)) { return; } g_async_queue_push(master->queue, msg_rehash(hash, &torrent)); master->item_lock = hash; gtk_action_set_sensitive(master->startaction, FALSE); gtk_action_set_sensitive(master->stopaction, FALSE); gtk_action_set_sensitive(master->rehashaction, FALSE); } void save_config(master_t* master) { if (master->config_timer == 0) { master->config_timer = g_timeout_add_seconds(5, sync_config, master); } } void status(master_t* master, const char* format, ...) { gchar* tmp; va_list args; gtk_statusbar_pop(GTK_STATUSBAR(master->statusbar), master->status_contextid); va_start(args, format); tmp = g_strdup_vprintf(format, args); va_end(args); gtk_statusbar_push(GTK_STATUSBAR(master->statusbar), master->status_contextid, tmp); g_free(tmp); } void torrent_update(const gchar* hash, torrent_t* torrent, torrent_data_t* data) { 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, data->downloaded < 100.0f ? data->downloaded : 1000.0f + data->seeded, COLUMN_HASH, hash, -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); static void sync_torrent_data(worker_data_t* data, xmlrpc_env* env, xmlrpc_client* client, xmlrpc_server_info* server, const gchar* hash, torrent_t* torrent); gpointer worker_main(gpointer _data) { worker_data_t* data = _data; xmlrpc_env env; xmlrpc_client* client; xmlrpc_server_info* server = NULL; gboolean quit = FALSE; version_t version; hashlist_t hashlist; hashlist_init(&hashlist); data->queue = g_async_queue_ref(data->queue); xmlrpc_env_init(&env); xmlrpc_client_setup_global_const(&env); xmlrpc_client_create(&env, XMLRPC_CLIENT_NO_FLAGS, "viewtorrents", "0.1", NULL, 0, &client); if (env.fault_occurred) { worker_respond(data, msg_error("Unable to create XMLRPC-C client (%d): %s", env.fault_code, env.fault_string)); quit = TRUE; } while (!quit) { msg_t* msg = g_async_queue_pop(data->queue); switch (msg->type) { case MSG_CONNECT: { 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) { xmlrpc_server_info_set_user(&env, server, m->auth_username, m->auth_password); xmlrpc_server_info_allow_auth_basic(&env, server); } params = xmlrpc_array_new(&env); xmlrpc_client_call2(&env, client, server, "system.client_version", params, &result); xmlrpc_DECREF(params); if (env.fault_occurred) { xmlrpc_server_info_free(server); msg_free(msg); server = NULL; worker_respond(data, msg_connectresult_failed( "Unable to get client version (%d): %s", env.fault_code, env.fault_string)); env.fault_occurred = FALSE; continue; } xmlrpc_read_string(&env, result, &tmp); xmlrpc_DECREF(result); if (env.fault_occurred) { xmlrpc_server_info_free(server); msg_free(msg); server = NULL; worker_respond(data, msg_connectresult_failed( "Unable to get client version (%d): %s", env.fault_code, env.fault_string)); env.fault_occurred = FALSE; continue; } 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; } case MSG_DISCONNECT: { xmlrpc_server_info_free(server); server = NULL; 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); data->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); data->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); data->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; sync_torrent_data(data, &env, client, server, m->hash, &m->torrent); msg_free(msg); break; } case MSG_QUIT: quit = TRUE; msg_free(msg); break; case MSG_START: { msg_start_t* m = (msg_start_t*)msg; xmlrpc_value* params, * item, * result = NULL; 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.resume", params, &result); xmlrpc_DECREF(params); if (!get_bool_xmlrpc(&env, result) || env.fault_occurred) { worker_respond(data, msg_status("Failed to resume torrent %s (%d): %s", m->hash, env.fault_code, env.fault_string)); env.fault_occurred = FALSE; } else { sync_torrent_data(data, &env, client, server, m->hash, &m->torrent); } msg_free(msg); break; } case MSG_STOP: { msg_stop_t* m = (msg_stop_t*)msg; xmlrpc_value* params, * item, * result = NULL; gboolean active; 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); active = get_bool_xmlrpc(&env, result); if (env.fault_occurred) { worker_respond(data, msg_status("Failed to get torrent status %s (%d): %s", m->hash, env.fault_code, env.fault_string)); env.fault_occurred = FALSE; } else { xmlrpc_client_call2(&env, client, server, active ? "d.pause" : "d.erase", params, &result); xmlrpc_DECREF(params); if (!get_bool_xmlrpc(&env, result) || env.fault_occurred) { worker_respond(data, msg_status("Failed to %s torrent %s (%d): %s", active ? "pause" : "remove", m->hash, env.fault_code, env.fault_string)); env.fault_occurred = FALSE; } else { sync_torrent_data(data, &env, client, server, m->hash, &m->torrent); } } msg_free(msg); break; } case MSG_REHASH: { msg_rehash_t* m = (msg_rehash_t*)msg; xmlrpc_value* params, * item, * result = NULL; 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.check_hash", params, &result); xmlrpc_DECREF(params); if (!get_bool_xmlrpc(&env, result) || env.fault_occurred) { worker_respond(data, msg_status("Failed to rehash torrent %s (%d): %s", m->hash, env.fault_code, env.fault_string)); env.fault_occurred = FALSE; } else { sync_torrent_data(data, &env, client, server, m->hash, &m->torrent); } 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); } xmlrpc_client_destroy(client); xmlrpc_client_teardown_global_const(); g_async_queue_unref(data->queue); return NULL; } void worker_respond(worker_data_t* data, msg_t* msg) { msg->master = data->master; g_main_context_invoke(NULL, incoming_msg, msg); } void msg_free(msg_t* msg) { switch (msg->type) { case MSG_CONNECT: { msg_connect_t* m = (msg_connect_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; 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; } 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; } case MSG_START: { msg_start_t* m = (msg_start_t*)msg; g_free(m->hash); g_slice_free(msg_start_t, m); break; } case MSG_STOP: { msg_stop_t* m = (msg_stop_t*)msg; g_free(m->hash); g_slice_free(msg_stop_t, m); break; } case MSG_REHASH: { msg_rehash_t* m = (msg_rehash_t*)msg; g_free(m->hash); g_slice_free(msg_rehash_t, m); break; } } } msg_t* msg_connect(const gchar* url, const gchar* username, const gchar* password) { 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); msg->auth_password = g_strdup(password); return &msg->base; } msg_t* msg_connectresult_success(version_t version) { msg_connectresult_t* msg = g_slice_new(msg_connectresult_t); msg->base.type = MSG_CONNECTRESULT; 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_slice_new(msg_error_t); va_list args; msg->base.type = MSG_ERROR; va_start(args, format); msg->msg = g_strdup_vprintf(format, args); va_end(args); return &msg->base; } msg_t* msg_status(const gchar* format, ...) { msg_status_t* msg = g_slice_new(msg_status_t); va_list args; msg->base.type = MSG_STATUS; va_start(args, format); msg->msg = g_strdup_vprintf(format, args); va_end(args); return &msg->base; } msg_t* msg_disconnect(void) { 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_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(const gchar* hash, torrent_t* torrent, torrent_data_t* data) { msg_sync_t* msg = g_slice_new(msg_sync_t); msg->base.type = MSG_SYNC; msg->hash = hash; msg->torrent = *torrent; msg->data = *data; return &msg->base; } msg_t* msg_start(const gchar* hash, torrent_t* torrent) { msg_start_t* msg = g_slice_new(msg_start_t); msg->base.type = MSG_START; msg->hash = g_strdup(hash); msg->torrent = *torrent; return &msg->base; } msg_t* msg_stop(const gchar* hash, torrent_t* torrent) { msg_stop_t* msg = g_slice_new(msg_stop_t); msg->base.type = MSG_STOP; msg->hash = g_strdup(hash); msg->torrent = *torrent; return &msg->base; } msg_t* msg_rehash(const gchar* hash, torrent_t* torrent) { msg_rehash_t* msg = g_slice_new(msg_rehash_t); msg->base.type = MSG_REHASH; msg->hash = g_strdup(hash); msg->torrent = *torrent; 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, G_GNUC_UNUSED 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(G_GNUC_UNUSED 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; } } } void liststore_sort_column_changed(GtkTreeSortable* sortable, gpointer user_data) { master_t* master = user_data; gint column; GtkSortType order; if (gtk_tree_sortable_get_sort_column_id(sortable, &column, &order)) { g_key_file_set_integer(master->config, "view", "sort_column", column + 1); g_key_file_set_boolean(master->config, "view", "sort_desc", order == GTK_SORT_DESCENDING); } else { if (!g_key_file_remove_key(master->config, "view", "sort_column", NULL) && !g_key_file_remove_key(master->config, "view", "sort_desc", NULL)) { return; } } save_config(master); } gint liststore_default_compare_func(GtkTreeModel* model, GtkTreeIter* a, GtkTreeIter* b, G_GNUC_UNUSED gpointer user_data) { GValue value = G_VALUE_INIT; gfloat af, bf; gint ai, bi; const gchar* as, * bs; gtk_tree_model_get_value(model, a, COLUMN_DOWN, &value); af = g_value_get_float(&value); g_value_unset(&value); gtk_tree_model_get_value(model, b, COLUMN_DOWN, &value); bf = g_value_get_float(&value); g_value_unset(&value); if (fabs(af - bf) > 1e-4) { return af > bf ? -1 : 1; } gtk_tree_model_get_value(model, a, COLUMN_UP, &value); af = g_value_get_float(&value); g_value_unset(&value); gtk_tree_model_get_value(model, b, COLUMN_UP, &value); bf = g_value_get_float(&value); g_value_unset(&value); if (fabs(af - bf) > 1e-4) { return af > bf ? -1 : 1; } gtk_tree_model_get_value(model, a, COLUMN_STATE, &value); ai = g_value_get_int(&value); g_value_unset(&value); gtk_tree_model_get_value(model, b, COLUMN_STATE, &value); bi = g_value_get_int(&value); g_value_unset(&value); if (ai != bi) { return ai > bi ? -1 : 1; } gtk_tree_model_get_value(model, a, COLUMN_PROGRESSSORT, &value); af = g_value_get_float(&value); g_value_unset(&value); gtk_tree_model_get_value(model, b, COLUMN_PROGRESSSORT, &value); bf = g_value_get_float(&value); g_value_unset(&value); if (fabs(af - bf) > 1e-3) { return af < bf ? -1 : 1; } gtk_tree_model_get_value(model, a, COLUMN_TITLE, &value); as = g_value_get_string(&value); { GValue value2 = G_VALUE_INIT; gint ret; gtk_tree_model_get_value(model, b, COLUMN_TITLE, &value2); bs = g_value_get_string(&value2); ret = strcmp(as, bs); g_value_unset(&value); g_value_unset(&value2); return ret; } } void sync_torrent_data(worker_data_t* data, xmlrpc_env* env, xmlrpc_client* client, xmlrpc_server_info* server, const gchar* hash, torrent_t* torrent) { 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, 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 (done >= torrent_data.size) { torrent_data.downloaded = 100.0f; } else if (torrent_data.size > 0) { torrent_data.downloaded = ((gfloat)done * 100.0f) / (gfloat)torrent_data.size; } 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 (data->ratio_max > 0) { if (ratio < data->ratio_max) { upload_left = (torrent_data.size * (data->ratio_max - ratio)) / 1000; } else { upload_left = 0; } } if (data->ratio_min > 0) { /* Inexact but good enough */ guint64 uploaded_bytes = (torrent_data.size * ratio) / 1000; if (uploaded_bytes >= data->ratio_upload) { if (ratio < data->ratio_min) { gint64 tmp = (torrent_data.size * (data->ratio_min - ratio)) / 1000; if (upload_left < 0 || tmp < upload_left) { upload_left = tmp; } } else { upload_left = 0; } } else { gint64 tmp = data->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", hash, env->fault_code, env->fault_string)); env->fault_occurred = FALSE; torrent_data.state = STATE_DEAD; } worker_respond(data, msg_sync(hash, torrent, &torrent_data)); } void listselection_changed(GtkTreeSelection* selection, gpointer user_data) { master_t* master = user_data; GtkTreeIter iter; GtkTreeModel* model; if (master->item_lock) { if (!master->connected) { master->item_lock = NULL; } } else if (master->connected && gtk_tree_selection_get_selected(selection, &model, &iter)) { GValue value = G_VALUE_INIT; state_t state; gtk_tree_model_get_value(model, &iter, COLUMN_STATE, &value); state = (state_t)g_value_get_int(&value); g_value_unset(&value); gtk_action_set_sensitive(master->startaction, state == STATE_DEAD || state == STATE_HASHFAILED); gtk_action_set_sensitive(master->stopaction, state != STATE_REHASH); gtk_action_set_sensitive(master->rehashaction, state != STATE_REHASH); return; } gtk_action_set_sensitive(master->startaction, FALSE); gtk_action_set_sensitive(master->stopaction, FALSE); gtk_action_set_sensitive(master->rehashaction, FALSE); }