From 73393bfab65515c68159a649c10856659b5ac016 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 15 Jul 2020 09:28:51 +0200 Subject: Use transmission rpc, fixes rss torrent name --- src/buffer.c | 5 ++ src/buffer.h | 1 + src/download.c | 6 +- src/main.c | 12 +++- src/rss.c | 22 +++--- src/rss.h | 3 +- src/transmission.c | 208 ++++++++++++++++++++++++++++++----------------------- src/transmission.h | 13 ++-- 8 files changed, 158 insertions(+), 112 deletions(-) diff --git a/src/buffer.c b/src/buffer.c index 5768b3a..f5e1d65 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -12,6 +12,7 @@ void buffer_init(Buffer *self) { void buffer_deinit(Buffer *self) { free(self->data); + self->data = NULL; self->size = 0; self->capacity = 0; } @@ -56,3 +57,7 @@ void* buffer_end(Buffer *self) { size_t buffer_get_size(Buffer *self, size_t type_size) { return self->size / type_size; } + +void buffer_clear(Buffer *self) { + self->size = 0; +} diff --git a/src/buffer.h b/src/buffer.h index a50cd85..f95e874 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -29,5 +29,6 @@ void* buffer_pop(Buffer *self, size_t size); void* buffer_begin(Buffer *self); void* buffer_end(Buffer *self); size_t buffer_get_size(Buffer *self, size_t type_size); +void buffer_clear(Buffer *self); #endif diff --git a/src/download.c b/src/download.c index 6c8e33e..74348e5 100644 --- a/src/download.c +++ b/src/download.c @@ -12,5 +12,9 @@ int download_to_buffer(const char *url, Buffer *buffer) { url, NULL }; - return program_exec(args, program_buffer_write_callback, buffer); + int result = program_exec(args, program_buffer_write_callback, buffer); + if(result != 0) + return result; + buffer_append(buffer, "\0", 1); + return result; } diff --git a/src/main.c b/src/main.c index eabd755..bb8114a 100644 --- a/src/main.c +++ b/src/main.c @@ -264,7 +264,7 @@ static void automedia_pid_signal_handler(int signum) { running = 0; } -static void sync_tracked_rss(char *rss_config_dir) { +static void sync_tracked_rss(TransmissionSession *transmission_session, char *rss_config_dir) { char rss_tracked_dir[PATH_MAX]; strcpy(rss_tracked_dir, rss_config_dir); strcat(rss_tracked_dir, "/tracked"); @@ -323,7 +323,7 @@ static void sync_tracked_rss(char *rss_config_dir) { tracked_rss.title = dir->d_name; tracked_rss.link = link_file_content; tracked_rss.json_data = json_value_as_object(json_data); - if(sync_rss(&tracked_rss, rss_config_dir) != 0) + if(sync_rss(transmission_session, &tracked_rss, rss_config_dir) != 0) fprintf(stderr, "Failed to sync %s\n", dir->d_name); free(link_file_content); @@ -343,10 +343,16 @@ static void sync_rss_html(char *rss_config_dir, char *html_config_dir, const cha } } + TransmissionSession transmission_session; + if(transmission_connect(&transmission_session) != 0) { + fprintf(stderr, "Failed to connect to the transmission daemon!\n"); + exit(2); + } + running = 1; /* running is set to 0 in SIGINT signal handler (ctrl+c) */ while(running) { - sync_tracked_rss(rss_config_dir); + sync_tracked_rss(&transmission_session, rss_config_dir); if(running) sleep(sync_rate_sec); } diff --git a/src/rss.c b/src/rss.c index ebbb1cf..005bd03 100644 --- a/src/rss.c +++ b/src/rss.c @@ -364,7 +364,7 @@ static int rss_update_latest(char *rss_tracked_dir, TrackedRss *tracked_rss, con init_json_value_str(&title_json_value, &title_json_value_str); struct json_string_s filename_json_key; - create_json_string(&filename_json_key, "title", 5); + create_json_string(&filename_json_key, "filename", 8); struct json_string_s filename_json_value_str; create_json_string(&filename_json_value_str, filename, strlen(filename)); @@ -473,37 +473,31 @@ static int rss_update_latest(char *rss_tracked_dir, TrackedRss *tracked_rss, con return result; } -static int add_torrents_in_reverse(Buffer *download_items_buffer, TrackedRss *tracked_rss, char *rss_tracked_dir) { +static int add_torrents_in_reverse(TransmissionSession *transmission_session, Buffer *download_items_buffer, TrackedRss *tracked_rss, char *rss_tracked_dir) { DownloadItemsData *download_items_it = buffer_end(download_items_buffer); DownloadItemsData *download_items_end = buffer_begin(download_items_buffer); download_items_it--; download_items_end--; for(; download_items_it != download_items_end; --download_items_it) { - if(transmission_add_torrent(download_items_it->link) != 0) { + char *torrent_name; + if(transmission_add_torrent(transmission_session, download_items_it->link, &torrent_name) != 0) { fprintf(stderr, "Failed to add torrent: %s\n", download_items_it->link); return 1; } - /* TODO: Verify if the last torrent is immediately accessible or if it gets an old torrent... */ - int id; - float percentage_done; - char torrent_name[256]; - if(transmission_get_last_added_torrent(&id, &percentage_done, torrent_name) != 0) { - fprintf(stderr, "Failed to get added torrent name for torrent: %s\n", download_items_it->link); - return 1; - } - if(rss_update_latest(rss_tracked_dir, tracked_rss, download_items_it->title, download_items_it->link, torrent_name) != 0) { + free(torrent_name); fprintf(stderr, "Failed to update rss tracked data for %s\n", download_items_it->title); return 1; } + free(torrent_name); /* Show notification that download has started? */ } return 0; } -int sync_rss(TrackedRss *tracked_rss, char *rss_config_dir) { +int sync_rss(TransmissionSession *transmission_session, TrackedRss *tracked_rss, char *rss_config_dir) { /* TODO: This can be cached */ int rss_config_dir_len = strlen(rss_config_dir); @@ -534,7 +528,7 @@ int sync_rss(TrackedRss *tracked_rss, char *rss_config_dir) { char *rss_tracked_dir = rss_config_dir; strcat(rss_tracked_dir, "/tracked/"); - result = add_torrents_in_reverse(&download_items_buffer, tracked_rss, rss_tracked_dir); + result = add_torrents_in_reverse(transmission_session, &download_items_buffer, tracked_rss, rss_tracked_dir); if(result != 0) { fprintf(stderr, "Failed while adding torrents for url: %s\n", tracked_rss->link); goto cleanup; diff --git a/src/rss.h b/src/rss.h index 0c3175c..d5fa548 100644 --- a/src/rss.h +++ b/src/rss.h @@ -2,6 +2,7 @@ #define RSS_H struct json_object_s; +struct TransmissionSession; typedef struct { char *title; @@ -10,6 +11,6 @@ typedef struct { } TrackedRss; int add_rss(const char *name, const char *url, char *rss_config_dir, const char *start_after); -int sync_rss(TrackedRss *tracked_rss, char *rss_config_dir); +int sync_rss(struct TransmissionSession *transmission_session, TrackedRss *tracked_rss, char *rss_config_dir); #endif diff --git a/src/transmission.c b/src/transmission.c index b9cb123..d789053 100644 --- a/src/transmission.c +++ b/src/transmission.c @@ -1,13 +1,51 @@ #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 */ + #define NUM_COLUMNS 10 +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); @@ -38,119 +76,113 @@ int transmission_start_daemon(const char *download_dir) { return 0; } -int transmission_add_torrent(const char *url) { - const char *args[] = { "transmission-remote", "-a", "--", url, NULL }; - return program_exec(args, NULL, NULL); +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; } -int transmission_get_all_torrents(TorrentListCallback callback, void *userdata) { - int result = 0; - - Buffer buffer; - buffer_init(&buffer); - - const char *args[] = { "transmission-remote", "--list", NULL }; - int exec_res = program_exec(args, program_buffer_write_callback, &buffer); - if(exec_res != 0) { - result = exec_res; - goto cleanup; - } - buffer_append(&buffer, "\0", 1); - - char id[6]; - char done[6]; - char have[13]; - char format[7]; - char eta[33]; - char eta2[13]; - char up[11]; - char down[11]; - char ratio[11]; - char status[33]; - char name[256]; - - char *end_of_first_line = strchr(buffer.data, '\n'); - if(!end_of_first_line) - goto cleanup; +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; +} - int num_bytes_read = 0; - size_t offset = end_of_first_line - (char*)buffer.data; - while(offset < buffer.size) { - /* ID, Done, Have (size, format), ETA, Up, Down, Ratio, Status, Name */ - int res = sscanf(buffer.data + offset, "%5s %5s %12s %6s %32s %12s %10s %10s %10s %32s %[^\n] %n", id, done, have, format, eta, eta2, up, down, ratio, status, name, &num_bytes_read); - if(res == EOF || res != NUM_COLUMNS + 1) { - int res = sscanf(buffer.data + offset, "%5s %5s %12s %6s %32s %10s %10s %10s %32s %[^\n] %n", id, done, have, format, eta, up, down, ratio, status, name, &num_bytes_read); - if(res == EOF || res != NUM_COLUMNS) - break; - } - callback(atoi(id), atof(done), name, userdata); - offset += num_bytes_read; +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; } - cleanup: - buffer_deinit(&buffer); - return result; + return strcmp(result_field->string, "success"); } -static int find_start_of_line(const char *str, int offset) { - for(int i = offset; i >= 0; --i) { - if(*str == '\n') - return i + 1; +static int transmission_add_torrent_response_get_torrent_name(struct json_object_s *json_root, 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; } - return 0; -} -static int find_end_of_previous_line(const char *str, int offset) { - for(int i = offset; i >= 0; --i) { - if(*str == '\n') - return i - 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; } + + 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_get_last_added_torrent(int *id_result, float *percentage_finished_result, char *title_result) { +int transmission_add_torrent(TransmissionSession *session, const char *url, char **torrent_name) { int result = 0; - Buffer buffer; buffer_init(&buffer); - - const char *args[] = { "transmission-remote", "--list", NULL }; - int exec_res = program_exec(args, program_buffer_write_callback, &buffer); - if(exec_res != 0) { - result = exec_res; + 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; } - char id[6]; - char done[6]; - char have[13]; - char format[7]; - char eta[33]; - char eta2[13]; - char up[11]; - char down[11]; - char ratio[11]; - char status[33]; - char name[256]; - - int end_of_second_last_line = find_end_of_previous_line(buffer.data, (int)buffer.size - 1); - int start_of_second_last_line = find_start_of_line(buffer.data, end_of_second_last_line); - - /* ID, Done, Have (size, format), ETA, Up, Down, Ratio, Status, Name */ - int res = sscanf(buffer.data + start_of_second_last_line, "%5s %5s %12s %6s %32s %12s %10s %10s %10s %32s %[^\n]", id, done, have, format, eta, eta2, up, down, ratio, status, name); - if(res == EOF || res != NUM_COLUMNS + 1) { - int res = sscanf(buffer.data + start_of_second_last_line, "%5s %5s %12s %6s %32s %10s %10s %10s %32s %[^\n]", id, done, have, format, eta, up, down, ratio, status, name); - if(res == EOF || res != NUM_COLUMNS) { - result = -1; + 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; } - *id_result = atoi(id); - *percentage_finished_result = atof(done); - strcpy(title_result, name); + 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; + } + + const char *response_torrent_name; + result = transmission_add_torrent_response_get_torrent_name(json_response_obj, &response_torrent_name); + if(result != 0) + goto cleanup; + + *torrent_name = strdup(response_torrent_name); cleanup: + free(json_response_val); buffer_deinit(&buffer); return result; } diff --git a/src/transmission.h b/src/transmission.h index 90a3594..cbb6c7c 100644 --- a/src/transmission.h +++ b/src/transmission.h @@ -1,15 +1,18 @@ #ifndef TRANSMISSION_H #define TRANSMISSION_H -/* @percentage_finished is a value between 0 and 100 [0.0, 100.0] */ -typedef void (*TorrentListCallback)(int id, float percentage_finished, const char *name, void *userdata); +typedef struct TransmissionSession TransmissionSession; +struct TransmissionSession { + char session_header[128]; +}; + +int transmission_connect(TransmissionSession *session); /* Returns 0 if the daemon is running, otherwise returns an error value */ int transmission_is_daemon_running(); int transmission_start_daemon(const char *download_dir); -int transmission_add_torrent(const char *url); -int transmission_get_all_torrents(TorrentListCallback callback, void *userdata); -int transmission_get_last_added_torrent(int *id, float *percentage_finished, char *title); +/* The torrent name will be stored in @torrent_name, malloc'ed */ +int transmission_add_torrent(TransmissionSession *session, const char *url, char **torrent_name); #endif -- cgit v1.2.3