#include "transmission.h" #include "program.h" #include "buffer.h" #include "json.h" #include "rss_html_common.h" #include #include #include #include /* TODO: Make port configurable */ int transmission_connect(TransmissionSession *session) { int result = 0; Buffer buffer; buffer_init(&buffer); const char *args[] = { "curl", "-s", "-I", "http://127.0.0.1:9091/transmission/rpc", NULL }; result = program_exec(args, program_buffer_write_callback, &buffer); if(result != 0) { fprintf(stderr, "Failed to retrieve transmission session id\n"); goto cleanup; } char *session_id_start = strstr(buffer.data, "X-Transmission-Session-Id: "); if(!session_id_start) { fprintf(stderr, "Failed to find session id in transmission response\n"); result = -1; goto cleanup; } char *session_id_end = strchr(session_id_start + 27, '\r'); if(!session_id_start) { fprintf(stderr, "Failed to find session id in transmission response\n"); result = -1; goto cleanup; } memcpy(session->session_header, session_id_start, session_id_end - session_id_start); session->session_header[session_id_end - session_id_start] = '\0'; cleanup: buffer_deinit(&buffer); return result; } int transmission_is_daemon_running(void) { const char *args[] = { "transmission-remote", "-si", NULL }; return program_exec(args, NULL, NULL); } int transmission_start_daemon(const char *download_dir) { /* TODO: Make seed ratio configurable */ const char *args[] = { "transmission-daemon", "--global-seedratio", "2.0", "--download-dir", download_dir, NULL }; int res = program_exec(args, NULL, NULL); if(res != 0) return res; fprintf(stderr, "Waiting for the transmission daemon to startup...\n"); int num_tries = 0; const int max_tries = 7; /* 7 seconds */ while(transmission_is_daemon_running() != 0 && num_tries < max_tries) { sleep(1); ++num_tries; } if(num_tries == max_tries) { fprintf(stderr, "Failed to launch transmission daemon in 7 seconds\n"); return -1; } fprintf(stderr, "The transmission daemon is now running!\n"); return 0; } static struct json_object_s* json_object_get_child_object_by_name(struct json_object_s *json_obj, const char *field_name) { struct json_value_s *json_child_obj = json_object_get_field_by_name(json_obj, field_name); if(!json_child_obj || json_child_obj->type != json_type_object) return NULL; return (struct json_object_s*)json_child_obj->payload; } static struct json_string_s* json_object_get_child_string_by_name(struct json_object_s *json_obj, const char *field_name) { struct json_value_s *json_child_str = json_object_get_field_by_name(json_obj, field_name); if(!json_child_str || json_child_str->type != json_type_string) return NULL; return (struct json_string_s*)json_child_str->payload; } static struct json_number_s* json_object_get_child_number_by_name(struct json_object_s *json_obj, const char *field_name) { struct json_value_s *json_number_str = json_object_get_field_by_name(json_obj, field_name); if(!json_number_str || json_number_str->type != json_type_number) return NULL; return (struct json_number_s*)json_number_str->payload; } static int transmission_response_is_success(struct json_object_s *json_root) { struct json_string_s *result_field = json_object_get_child_string_by_name(json_root, "result"); if(!result_field) { fprintf(stderr, "Error: Transmission response is missing result field\n"); return -1; } return strcmp(result_field->string, "success"); } static int transmission_add_torrent_response_get_torrent_name(struct json_object_s *json_root, int *torrent_id, const char **torrent_name) { struct json_object_s *arguments_obj = json_object_get_child_object_by_name(json_root, "arguments"); if(!arguments_obj) { fprintf(stderr, "Error: transmission add torrent response is missing arguments field or its not an object\n"); return -1; } struct json_object_s *torrent_added_obj = json_object_get_child_object_by_name(arguments_obj, "torrent-added"); if(!torrent_added_obj) torrent_added_obj = json_object_get_child_object_by_name(arguments_obj, "torrent-duplicate"); if(!torrent_added_obj) { fprintf(stderr, "Error: transmission add torrent response is missing both torrent-added and torrent-duplicate argument\n"); return -1; } if(torrent_id) { struct json_number_s *torrent_id_field = json_object_get_child_number_by_name(torrent_added_obj, "id"); if(!torrent_id_field) { fprintf(stderr, "Error: transmission add torrent response is missing torrent id\n"); return -1; } *torrent_id = atoi(torrent_id_field->number); if(*torrent_id == 0) { fprintf(stderr, "Error: transmission add torrent response has invalid torrent id\n"); return -1; } } if(torrent_name) { struct json_string_s *torrent_name_field = json_object_get_child_string_by_name(torrent_added_obj, "name"); if(!torrent_name_field) { fprintf(stderr, "Error: transmission add torrent response is missing torrent name\n"); return -1; } *torrent_name = torrent_name_field->string; } return 0; } int transmission_add_torrent(TransmissionSession *session, const char *url, int *torrent_id, char **torrent_name) { int result = 0; Buffer buffer; buffer_init(&buffer); struct json_value_s *json_response_val = NULL; /* TODO: json escape url */ char request[4096]; int written_bytes = snprintf(request, sizeof(request), "{ \"arguments\": { \"filename\": \"%s\" }, \"method\": \"torrent-add\" }", url); if(written_bytes >= (int)sizeof(request)) { fprintf(stderr, "Failed to write all bytes to request (this is a bug)\n"); result = -1; goto cleanup; } const char *args[] = { "curl", "-s", "-f", "-d", request, "-H", session->session_header, "--", "http://127.0.0.1:9091/transmission/rpc", NULL }; result = program_exec(args, program_buffer_write_callback, &buffer); if(result != 0) { /* Maybe the request failed because the session is no longer valid? retry once with new session id */ if(transmission_connect(session) != 0) { fprintf(stderr, "Failed to add torrent: %s\n", url); goto cleanup; } buffer_clear(&buffer); const char *new_args[] = { "curl", "-s", "-f", "-d", request, "-H", session->session_header, "--", "http://127.0.0.1:9091/transmission/rpc", NULL }; result = program_exec(new_args, program_buffer_write_callback, &buffer); if(result != 0) { fprintf(stderr, "Failed to add torrent: %s\n", url); goto cleanup; } } json_response_val = json_parse(buffer.data, buffer.size); if(!json_response_val || json_response_val->type != json_type_object) { fprintf(stderr, "Failed to parse torrent add response: %.*s as json\n", (int)buffer.size, (char*)buffer.data); result = -1; goto cleanup; } struct json_object_s *json_response_obj = json_value_as_object(json_response_val); if(transmission_response_is_success(json_response_obj) != 0) { fprintf(stderr, "Error: transmission torrent add request failed, response: %.*s\n", (int)buffer.size, (char*)buffer.data); result = -1; goto cleanup; } int response_torrent_id; const char *response_torrent_name; result = transmission_add_torrent_response_get_torrent_name(json_response_obj, torrent_id ? &response_torrent_id : NULL, torrent_name ? &response_torrent_name : NULL); if(result != 0) goto cleanup; if(torrent_id) *torrent_id = response_torrent_id; if(torrent_name) *torrent_name = strdup(response_torrent_name); cleanup: free(json_response_val); buffer_deinit(&buffer); return result; } int transmission_list_torrents(TransmissionSession *session, TorrentListCallback callback, void *userdata) { int result = 0; Buffer buffer; buffer_init(&buffer); struct json_value_s *json_response_val = NULL; char request[100]; int written_bytes = snprintf(request, sizeof(request), "{ \"arguments\": { \"fields\": [ \"id\", \"name\", \"percentDone\" ] }, \"method\": \"torrent-get\" }"); if(written_bytes >= (int)sizeof(request)) { fprintf(stderr, "Failed to write all bytes to request (this is a bug)\n"); result = -1; goto cleanup; } const char *args[] = { "curl", "-s", "-f", "-d", request, "-H", session->session_header, "--", "http://127.0.0.1:9091/transmission/rpc", NULL }; result = program_exec(args, program_buffer_write_callback, &buffer); if(result != 0) { /* Maybe the request failed because the session is no longer valid? retry once with new session id */ if(transmission_connect(session) != 0) { fprintf(stderr, "Failed to list torrents\n"); goto cleanup; } buffer_clear(&buffer); const char *new_args[] = { "curl", "-s", "-f", "-d", request, "-H", session->session_header, "--", "http://127.0.0.1:9091/transmission/rpc", NULL }; result = program_exec(new_args, program_buffer_write_callback, &buffer); if(result != 0) { fprintf(stderr, "Failed to list torrents\n"); goto cleanup; } } json_response_val = json_parse(buffer.data, buffer.size); if(!json_response_val || json_response_val->type != json_type_object) { fprintf(stderr, "Failed to parse torrent list response: %.*s as json\n", (int)buffer.size, (char*)buffer.data); result = -1; goto cleanup; } struct json_object_s *json_response_obj = json_value_as_object(json_response_val); if(transmission_response_is_success(json_response_obj) != 0) { fprintf(stderr, "Error: transmission torrent list request failed, response: %.*s\n", (int)buffer.size, (char*)buffer.data); result = -1; goto cleanup; } struct json_value_s *arguments_field = json_object_get_field_by_name(json_response_obj, "arguments"); if(!arguments_field || arguments_field->type != json_type_object) { fprintf(stderr, "Error: transmission torrent list response is missing arguments or its not an object\n"); result = -1; goto cleanup; } struct json_object_s *arguments_field_obj = json_value_as_object(arguments_field); struct json_value_s *torrents_field = json_object_get_field_by_name(arguments_field_obj, "torrents"); if(!torrents_field || torrents_field->type != json_type_array) { fprintf(stderr, "Error: transmission torrent list response is missing torrents or its not an array\n"); result = -1; goto cleanup; } struct json_array_element_s *torrent_item = json_value_as_array(torrents_field)->start; while(torrent_item) { struct json_object_s *torrent_item_obj = json_value_as_object(torrent_item->value); if(!torrent_item_obj) continue; struct json_value_s *id_field = json_object_get_field_by_name(torrent_item_obj, "id"); struct json_value_s *name_field = json_object_get_field_by_name(torrent_item_obj, "name"); struct json_value_s *percent_done_field = json_object_get_field_by_name(torrent_item_obj, "percentDone"); if(!id_field || id_field->type != json_type_number || !name_field || name_field->type != json_type_string || !percent_done_field || percent_done_field->type != json_type_number) { continue; } callback(atoi(json_value_as_number(id_field)->number), json_value_as_string(name_field)->string, atof(json_value_as_number(percent_done_field)->number), userdata); torrent_item = torrent_item->next; } cleanup: free(json_response_val); buffer_deinit(&buffer); return result; }