#include "common.h" #include #include #include #include #include "customcellrendererstate.h" #include "customcellrendererprogress.h" #include "customcellrendererrate.h" #include "customcellrendererleft.h" typedef enum { COLUMN_STATE, COLUMN_TITLE, COLUMN_DOWNLOADED, COLUMN_SEEDED, COLUMN_UP, COLUMN_DOWN, COLUMN_LEFT, COLUMN_PROGRESSSORT, } 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_t; static void torrent_update(torrent_t* torrent, GtkListStore* store, GtkTreeIter* iter); typedef struct { GAsyncQueue* queue; } worker_data_t; typedef enum { MSG_CONNECT, /* main -> worker */ MSG_CONNECTRESULT, /* worker -> main */ MSG_DISCONNECT, /* main -> worker */ MSG_QUIT, /* main -> worker */ MSG_ERROR, /* worker -> main */ } msg_type_t; typedef struct { msg_type_t type; } msg_t; typedef struct { msg_t base; gchar* url; gchar* auth_username, * auth_password; } msg_connect_t; typedef struct { msg_t base; gboolean success; gchar* errmsg; } msg_connectresult_t; typedef struct { msg_t base; gchar* msg; } msg_error_t; typedef struct { msg_t base; } msg_quit_t; typedef struct { msg_t base; } msg_disconnect_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_error(const gchar* format, ...); static msg_t* msg_disconnect(void); static msg_t* msg_quit(void); static gpointer worker_main(gpointer data); int main(int argc, char** argv) { const gchar* gladefile; GtkBuilder* builder; GError* error = NULL; guint ret; GtkWidget* top; GtkWidget* listview; GtkListStore* liststore; GtkTreeIter iter; GtkCellRenderer* cell; GtkTreeViewColumn* column; GThread* worker; worker_data_t worker_data; g_thread_init(NULL); gtk_init(&argc, &argv); worker_data.queue = g_async_queue_new(); 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; } top = GTK_WIDGET(gtk_builder_get_object(builder, "main")); g_signal_connect(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); listview = GTK_WIDGET(gtk_builder_get_object(builder, "treeview")); liststore = GTK_LIST_STORE(gtk_builder_get_object(builder, "liststore")); 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); { torrent_t torrent; torrent.state = STATE_DEAD; torrent.title = "The.Tree.of.Life.2011.720p.BluRay.x264.DTS-HDxT"; torrent.seeded = 3.0; torrent.downloaded = 100.0; torrent.up = 0.0; torrent.down = 0.0; torrent.left = -1; gtk_list_store_append(liststore, &iter); torrent_update(&torrent, liststore, &iter); } { torrent_t torrent; torrent.state = STATE_ACTIVE; torrent.title = "Burn.Notice.S05E14.720p.HDTV.x264-AVS.mkv"; torrent.seeded = 2.79; torrent.downloaded = 100.0; torrent.up = 72.5; torrent.down = 0.0; torrent.left = -1; gtk_list_store_append(liststore, &iter); torrent_update(&torrent, liststore, &iter); } { torrent_t torrent; torrent.state = STATE_ACTIVE; torrent.title = "CSI.S12E07.720p.HDTV.X264-DIMENSION.mkv"; torrent.seeded = 1.5; torrent.downloaded = 73.45; torrent.up = 37.3; torrent.down = 994.8; torrent.left = 24340; gtk_list_store_append(liststore, &iter); torrent_update(&torrent, liststore, &iter); } gtk_widget_show_all(top); gtk_main(); g_async_queue_push(worker_data.queue, msg_quit()); g_async_queue_unref(worker_data.queue); return EXIT_SUCCESS; } void torrent_update(torrent_t* torrent, GtkListStore* store, GtkTreeIter* iter) { 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, COLUMN_PROGRESSSORT, torrent->downloaded < 100.0f ? torrent->downloaded : torrent->seeded + 1000.0f, -1); } gpointer worker_main(gpointer _data) { worker_data_t* data = _data; xmlrpc_env env; xmlrpc_client* client; xmlrpc_server_info* server = NULL; gboolean quit = FALSE; 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) { g_async_queue_push(data->queue, 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; 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); if (env.fault_occurred) { xmlrpc_DECREF(params); xmlrpc_server_info_free(server); msg_free(msg); server = NULL; g_async_queue_push(data->queue, msg_connectresult(FALSE, "Unable to get client version (%d): %s", env.fault_code, env.fault_string)); continue; } xmlrpc_DECREF(params); xmlrpc_read_string(&env, result, &tmp); if (env.fault_occurred) { xmlrpc_DECREF(result); xmlrpc_server_info_free(server); msg_free(msg); server = NULL; g_async_queue_push(data->queue, msg_connectresult(FALSE, "Unable to get client version (%d): %s", env.fault_code, env.fault_string)); continue; } printf("result: %s\n", tmp); xmlrpc_DECREF(result); g_async_queue_push(data->queue, msg_connectresult(TRUE, NULL)); msg_free(msg); break; } case MSG_DISCONNECT: { xmlrpc_server_info_free(server); server = NULL; break; } case MSG_QUIT: break; case MSG_CONNECTRESULT: case MSG_ERROR: g_assert(FALSE); break; } } 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 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); break; } case MSG_CONNECTRESULT: { msg_connectresult_t* m = (msg_connectresult_t*)msg; g_free(m->errmsg); break; } case MSG_ERROR: { msg_error_t* m = (msg_error_t*)msg; g_free(m->msg); break; } case MSG_DISCONNECT: case MSG_QUIT: break; } g_free(msg); } msg_t* msg_connect(const gchar* url, const gchar* username, const gchar* password) { msg_connect_t* msg = g_new0(msg_connect_t, 1); 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(gboolean success, const gchar* errfmt, ...) { msg_connectresult_t* msg = g_new0(msg_connectresult_t, 1); 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); } return &msg->base; } msg_t* msg_error(const gchar* format, ...) { msg_error_t* msg = g_new0(msg_error_t, 1); 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_disconnect(void) { msg_disconnect_t* msg = g_new0(msg_disconnect_t, 1); msg->base.type = MSG_DISCONNECT; return &msg->base; } msg_t* msg_quit(void) { msg_quit_t* msg = g_new0(msg_quit_t, 1); msg->base.type = MSG_QUIT; return &msg->base; }