diff options
author | dec05eba <dec05eba@protonmail.com> | 2020-07-15 18:13:49 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2020-07-15 18:13:49 +0200 |
commit | 21208ecc1c6223cdfd2dbfeaff3bcfad8d0b8937 (patch) | |
tree | 67f151d022fdcd71bf2ddc5fff8046a4d3a8bfa1 | |
parent | 9946c0363648b44d396b07d8a1a4557c568edc88 (diff) |
Add torrent complete notification
-rw-r--r-- | src/alloc.c | 1 | ||||
-rw-r--r-- | src/alloc.h | 1 | ||||
-rw-r--r-- | src/buffer.c | 1 | ||||
-rw-r--r-- | src/html.c | 8 | ||||
-rw-r--r-- | src/main.c | 51 | ||||
-rw-r--r-- | src/rss.c | 4 | ||||
-rw-r--r-- | src/transmission.c | 141 | ||||
-rw-r--r-- | src/transmission.h | 11 |
8 files changed, 198 insertions, 20 deletions
diff --git a/src/alloc.c b/src/alloc.c index dca69b5..282119f 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -1,5 +1,6 @@ #include "alloc.h" #include <stdio.h> +#include <stdlib.h> void* alloc_or_crash(size_t size) { void *mem = malloc(size); diff --git a/src/alloc.h b/src/alloc.h index e8c6c85..9670ee7 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -2,7 +2,6 @@ #define ALLOC_H #include <stddef.h> -#include <stdlib.h> void* alloc_or_crash(size_t size); void* realloc_or_crash(void *mem, size_t new_size); diff --git a/src/buffer.c b/src/buffer.c index f5e1d65..05f99f2 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -2,6 +2,7 @@ #include "alloc.h" #include <stdio.h> #include <string.h> +#include <stdlib.h> #include <assert.h> void buffer_init(Buffer *self) { @@ -349,8 +349,13 @@ static int download_html_items_in_reverse(const char *plugin_filepath, Buffer *d download_items_end--; int download_item_index = 0; for(; download_items_it != download_items_end && download_item_index < MAX_UPDATE_ITEMS && is_program_running(); --download_items_it) { + char notify_msg[PATH_MAX]; + const char *path_components[] = { tracked_html->title, download_items_it->title }; + path_join(notify_msg, path_components, 2); + item_dir[item_dir_len] = '/'; strcpy(item_dir + item_dir_len + 1, download_items_it->title); + fprintf(stderr, "Starting download of html item: %s (title: %s)\n", download_items_it->link, notify_msg); if(create_directory_recursive(item_dir) != 0) { fprintf(stderr, "Failed to create directory for html item: %s\n", download_items_it->title); result = -1; @@ -363,9 +368,6 @@ static int download_html_items_in_reverse(const char *plugin_filepath, Buffer *d if(result != 0) fprintf(stderr, "Failed while downloading html, url: %s\n", download_items_it->link); - char notify_msg[PATH_MAX]; - const char *path_components[] = { tracked_html->title, download_items_it->title }; - path_join(notify_msg, path_components, 2); const char *notify_args[] = { "notify-send", "-u", result == 0 ? "normal" : "critical", "--", result == 0 ? "Download finished" : "Download failed", notify_msg, NULL }; program_exec(notify_args, NULL, NULL); @@ -3,10 +3,12 @@ #include "transmission.h" #include "fileutils.h" #include "stringutils.h" +#include "program.h" #include "rss.h" #include "rss_html_common.h" #include "html.h" #include "json.h" +#include "alloc.h" #include <stdio.h> #include <stdlib.h> @@ -383,6 +385,37 @@ static void sync_tracked_html(char *html_config_dir, char *program_dir, const ch iterate_tracked_items(html_config_dir, iterate_tracked_item_html_callback, &iterate_html_item_userdata); } +typedef struct { + char *items; + int size; +} UnfinishedTorrents; + +static void torrent_list_check_new_downloads_callback(int id, const char *name, double percentage_done, void *userdata) { + /* Sanity check, random high number */ + if(id <= 0 || id >= 650000) { + fprintf(stderr, "Invalid torrent id: %d\n", id); + return; + } + + id--; + UnfinishedTorrents *unfinished_torrents = userdata; + + int is_finished = (percentage_done >= 0.9999); + if(is_finished) { + if(id < unfinished_torrents->size && unfinished_torrents->items[id] == 1) { + unfinished_torrents->items[id] = 0; + const char *notify_args[] = { "notify-send", "-u", "normal", "--", "Download finished", name, NULL }; + program_exec(notify_args, NULL, NULL); + } + } else { + if(id >= unfinished_torrents->size) { + unfinished_torrents->size = id + 128; + unfinished_torrents->items = realloc_or_crash(unfinished_torrents->items, unfinished_torrents->size); + } + unfinished_torrents->items[id] = 1; + } +} + static void sync_rss_html(char *rss_config_dir, char *html_config_dir, char *program_dir, const char *download_dir, int sync_rate_sec) { if(transmission_is_daemon_running() != 0) { if(transmission_start_daemon(download_dir) != 0) { @@ -397,16 +430,28 @@ static void sync_rss_html(char *rss_config_dir, char *html_config_dir, char *pro exit(2); } + /* Check for torrent progress every 15 seconds */ + int check_torrent_status_rate_sec = 15; + + UnfinishedTorrents unfinished_torrents; + unfinished_torrents.items = alloc_or_crash(1024); + unfinished_torrents.size = 1024; + automedia_running = 1; /* running is set to 0 in SIGINT signal handler (ctrl+c) */ while(automedia_running) { sync_tracked_rss(&transmission_session, rss_config_dir); sync_tracked_html(html_config_dir, program_dir, download_dir); - /* TODO: Show finished html/rss items */ - if(automedia_running) - sleep(sync_rate_sec); + int check_count = 0; + while(automedia_running && check_count < sync_rate_sec/check_torrent_status_rate_sec) { + transmission_list_torrents(&transmission_session, torrent_list_check_new_downloads_callback, &unfinished_torrents); + sleep(check_torrent_status_rate_sec); + ++check_count; + } } + + free(unfinished_torrents.items); } static int cmdline_contains_str(const char *cmdline, const char *str) { @@ -328,14 +328,14 @@ static int add_torrents_in_reverse(TransmissionSession *transmission_session, Bu download_items_end--; int torrent_name_index = 0; for(; download_items_it != download_items_end && torrent_name_index < MAX_UPDATE_ITEMS; --download_items_it) { - if(transmission_add_torrent(transmission_session, download_items_it->link, &torrent_names[torrent_name_index]) != 0) { + fprintf(stderr, "Starting download of torrent: %s (title: %s)\n", download_items_it->link, download_items_it->title); + if(transmission_add_torrent(transmission_session, download_items_it->link, NULL, &torrent_names[torrent_name_index]) != 0) { fprintf(stderr, "Failed to add torrent: %s\n", download_items_it->link); result = -1; break; } added_download_items[torrent_name_index] = download_items_it; ++torrent_name_index; - fprintf(stderr, "Starting download of torrent: %s (title: %s)\n", download_items_it->link, download_items_it->title); /* Show notification that download has started? */ } diff --git a/src/transmission.c b/src/transmission.c index d789053..39b355f 100644 --- a/src/transmission.c +++ b/src/transmission.c @@ -90,6 +90,13 @@ static struct json_string_s* json_object_get_child_string_by_name(struct json_ob 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) { @@ -100,7 +107,7 @@ static int transmission_response_is_success(struct json_object_s *json_root) { return strcmp(result_field->string, "success"); } -static int transmission_add_torrent_response_get_torrent_name(struct json_object_s *json_root, const char **torrent_name) { +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"); @@ -116,17 +123,33 @@ static int transmission_add_torrent_response_get_torrent_name(struct json_object 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; + 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; } - *torrent_name = torrent_name_field->string; return 0; } -int transmission_add_torrent(TransmissionSession *session, const char *url, char **torrent_name) { +int transmission_add_torrent(TransmissionSession *session, const char *url, int *torrent_id, char **torrent_name) { int result = 0; Buffer buffer; buffer_init(&buffer); @@ -174,12 +197,112 @@ int transmission_add_torrent(TransmissionSession *session, const char *url, char goto cleanup; } + int response_torrent_id; const char *response_torrent_name; - result = transmission_add_torrent_response_get_torrent_name(json_response_obj, &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; - *torrent_name = strdup(response_torrent_name); + 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); diff --git a/src/transmission.h b/src/transmission.h index cbb6c7c..c15d42b 100644 --- a/src/transmission.h +++ b/src/transmission.h @@ -6,13 +6,20 @@ struct TransmissionSession { char session_header[128]; }; +typedef void (*TorrentListCallback)(int id, const char *name, double percentage_done, void *userdata); + 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); -/* The torrent name will be stored in @torrent_name, malloc'ed */ -int transmission_add_torrent(TransmissionSession *session, const char *url, char **torrent_name); +/* + The torrent id will be stored in @torrent_id, malloc'ed and + the torrent name will be stored in @torrent_name, malloc'ed. +*/ +int transmission_add_torrent(TransmissionSession *session, const char *url, int *torrent_id, char **torrent_name); + +int transmission_list_torrents(TransmissionSession *session, TorrentListCallback callback, void *userdata); #endif |