diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/fileutils.c | 9 | ||||
-rw-r--r-- | src/fileutils.h | 3 | ||||
-rw-r--r-- | src/html.c | 234 | ||||
-rw-r--r-- | src/html.h | 8 | ||||
-rw-r--r-- | src/main.c | 119 | ||||
-rw-r--r-- | src/main.h | 6 | ||||
-rw-r--r-- | src/rss.c | 198 | ||||
-rw-r--r-- | src/rss.h | 2 | ||||
-rw-r--r-- | src/rss_html_common.c | 196 | ||||
-rw-r--r-- | src/rss_html_common.h | 23 |
10 files changed, 564 insertions, 234 deletions
diff --git a/src/fileutils.c b/src/fileutils.c index 4fba0d0..524a603 100644 --- a/src/fileutils.c +++ b/src/fileutils.c @@ -25,7 +25,7 @@ int file_get_content(const char *filepath, char **data, long *size) { FILE *file = fopen(filepath, "rb"); if(!file) { int err = -errno; - perror(filepath); + /*perror(filepath);*/ return err; } @@ -116,11 +116,11 @@ int file_overwrite(const char *filepath, const char *data, size_t size) { int file_overwrite_in_dir(const char *dir, const char *filename, const char *data, size_t size) { char filepath[PATH_MAX]; const char *filepath_components[] = { dir, filename }; - path_join(filepath, filepath_components, 2); + int filepath_len = path_join(filepath, filepath_components, 2); char tmp_filepath[PATH_MAX]; strcpy(tmp_filepath, filepath); - strcat(tmp_filepath, ".tmp"); + strcpy(tmp_filepath + filepath_len, ".tmp"); int result = file_overwrite(tmp_filepath, data, size); if(result != 0) @@ -130,7 +130,7 @@ int file_overwrite_in_dir(const char *dir, const char *filename, const char *dat return rename(tmp_filepath, filepath); } -void path_join(char *output, const char **components, int num_components) { +int path_join(char *output, const char **components, int num_components) { int offset = 0; for(int i = 0; i < num_components; ++i) { if(i > 0) { @@ -143,4 +143,5 @@ void path_join(char *output, const char **components, int num_components) { offset += component_len; } output[offset] = '\0'; + return offset; } diff --git a/src/fileutils.h b/src/fileutils.h index 2b9906c..9f8f4fe 100644 --- a/src/fileutils.h +++ b/src/fileutils.h @@ -15,6 +15,7 @@ int create_lock_file(const char *path); int file_overwrite(const char *filepath, const char *data, size_t size); int file_overwrite_in_dir(const char *dir, const char *filename, const char *data, size_t size); -void path_join(char *output, const char **components, int num_components); +/* Returns the length of the new path (the length of @output) */ +int path_join(char *output, const char **components, int num_components); #endif @@ -4,6 +4,7 @@ #include "buffer.h" #include "stringutils.h" #include "rss_html_common.h" +#include "main.h" #include "json.h" #include <limits.h> #include <string.h> @@ -11,6 +12,8 @@ #include <libgen.h> #include <signal.h> #include <time.h> +#include <dirent.h> +#include <unistd.h> #include <assert.h> static int str_starts_with(const char *str, int len, const char *substr, int substr_len) { @@ -48,9 +51,38 @@ static int url_extract_domain(const char *url, char *domain, int domain_len) { return 0; } +/* + The plugin should print the names and urls of each item (chapter for manga) and the output list should stop when an + item matches any item in the input. The output should be sorted from newest to oldest. + + The input should be in this format: + [ + { + "title": "Example name", + "url": "https://example.com" + }, + { + "title": "Another item", + "url": "https://another.url.com" + } + ] + + And the output should be in this format: + [ + { + "name": "Example name", + "url": "https://example.com" + }, + { + "name": "Another item", + "url": "https://another.url.com" + } + ] + + TODO: Rename input "title" to "url", to make input and output match (easier to test with). +*/ typedef int (*PluginListCallback)(const char *name, const char *url, void *userdata); -static struct json_value_s* plugin_list(char *plugin_filepath, const char *url, const char *latest, PluginListCallback callback, void *userdata) { - (void)latest; +static struct json_value_s* plugin_list(char *plugin_filepath, const char *url, struct json_array_s *downloaded_items, PluginListCallback callback, void *userdata) { int result; Buffer buffer; buffer_init(&buffer); @@ -65,6 +97,34 @@ static struct json_value_s* plugin_list(char *plugin_filepath, const char *url, goto err_cleanup; } + if(downloaded_items) { + struct json_value_s downloaded_items_value; + downloaded_items_value.payload = downloaded_items; + downloaded_items_value.type = json_type_array; + size_t json_output_len = 0; + void *json_body_str = json_write_minified(&downloaded_items_value, &json_output_len); + if(!json_body_str) { + fprintf(stderr, "Failed to convert downloaded items to json\n"); + if(process_id != -1) + kill(process_id, SIGKILL); + close(stdin_file); + close(stdout_file); + goto err_cleanup; + } + + /* This is a bug in the json library */ + json_output_len = strlen(json_body_str); + if(write(stdin_file, json_body_str, json_output_len) != (ssize_t)json_output_len) { + fprintf(stderr, "Failed to write all bytes to plugin list\n"); + if(process_id != -1) + kill(process_id, SIGKILL); + close(stdin_file); + close(stdout_file); + goto err_cleanup; + } + free(json_body_str); + } + result = program_wait_until_exit(process_id, stdin_file, stdout_file, program_buffer_write_callback, &buffer); if(result != 0) { fprintf(stderr, "Failed to launch plugin list for plugin %s\n", basename(plugin_filepath)); @@ -76,6 +136,7 @@ static struct json_value_s* plugin_list(char *plugin_filepath, const char *url, fprintf(stderr, "Failed to load plugin %s list output as json\n", basename(plugin_filepath)); goto err_cleanup; } + buffer_deinit(&buffer); struct json_array_s *json_root_array = json_value_as_array(json_root); if(!json_root_array) { @@ -108,7 +169,6 @@ static struct json_value_s* plugin_list(char *plugin_filepath, const char *url, break; } - buffer_deinit(&buffer); return json_root; err_cleanup: @@ -123,7 +183,6 @@ typedef struct { } PluginListUserdata; static int plugin_list_callback(const char *name, const char *url, void *userdata) { - /*fprintf(stderr, "name: |%s|, url: |%s|\n", name, url);*/ PluginListUserdata *plugin_list_userdata = userdata; if(plugin_list_userdata->start_after && strcmp(plugin_list_userdata->start_after, name) == 0) { plugin_list_userdata->found_start_after = 1; @@ -133,6 +192,31 @@ static int plugin_list_callback(const char *name, const char *url, void *userdat return 0; } +/* Store result in @plugin_filepath */ +static int get_plugin_filepath(const char *program_dir, const char *plugin_name, char *plugin_filepath) { + const char *path_components[] = { program_dir, "plugins" }; + path_join(plugin_filepath, path_components, 2); + if(file_exists(plugin_filepath) != 0) { + strcpy(plugin_filepath, "/usr/share/automedia/plugins"); + if(file_exists(plugin_filepath) != 0) { + fprintf(stderr, "Failed to find plugins directory\n"); + return -1; + } + } + + strcat(plugin_filepath, "/"); + strcat(plugin_filepath, plugin_name); + if(file_exists(plugin_filepath) != 0) { + strcat(plugin_filepath, ".py"); + if(file_exists(plugin_filepath) != 0) { + fprintf(stderr, "Plugin doesn't exist: %s\n", plugin_name); + return -1; + } + } + + return 0; +} + int add_html(const char *name, const char *url, char *html_config_dir, char *program_dir, const char *start_after) { int result = 0; @@ -152,26 +236,9 @@ int add_html(const char *name, const char *url, char *html_config_dir, char *pro return -1; } - const char *path_components[] = { program_dir, "plugins" }; char domain_plugin_path[PATH_MAX]; - path_join(domain_plugin_path, path_components, 2); - if(file_exists(domain_plugin_path) != 0) { - strcpy(domain_plugin_path, "/usr/share/automedia/plugins"); - if(file_exists(domain_plugin_path) != 0) { - fprintf(stderr, "Failed to find plugins directory\n"); - return -1; - } - } - - strcat(domain_plugin_path, "/"); - strcat(domain_plugin_path, domain); - if(file_exists(domain_plugin_path) != 0) { - strcat(domain_plugin_path, ".py"); - if(file_exists(domain_plugin_path) != 0) { - fprintf(stderr, "Plugin doesn't exist: %s\n", domain); - return -1; - } - } + if(get_plugin_filepath(program_dir, domain, domain_plugin_path) != 0) + return -1; PluginListUserdata plugin_list_userdata; plugin_list_userdata.start_after = start_after; @@ -255,3 +322,124 @@ int add_html(const char *name, const char *url, char *html_config_dir, char *pro free(json_root); return result; } + +static int plugin_list_sync_callback(const char *name, const char *url, void *userdata) { + Buffer *download_items_buffer = userdata; + DownloadItemsData download_items_data; + download_items_data.title = name; + download_items_data.link = url; + buffer_append(download_items_buffer, &download_items_data, sizeof(download_items_data)); + return 0; +} + +static int download_html_items_in_reverse(const char *plugin_filepath, Buffer *download_items_buffer, TrackedHtml *tracked_html, char *html_tracked_dir, const char *download_dir) { + int result = 0; + DownloadItemsData *added_download_items[MAX_UPDATE_ITEMS]; + + char item_dir[PATH_MAX]; + const char *path_components[] = { download_dir, tracked_html->title }; + int item_dir_len = path_join(item_dir, path_components, 2); + + Buffer json_element_buffer; + buffer_init(&json_element_buffer); + + DownloadItemsData *download_items_it = buffer_end(download_items_buffer); + DownloadItemsData *download_items_end = buffer_begin(download_items_buffer); + download_items_it--; + 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) { + item_dir[item_dir_len] = '/'; + strcpy(item_dir + item_dir_len + 1, download_items_it->title); + if(create_directory_recursive(item_dir) != 0) { + fprintf(stderr, "Failed to create directory for html item: %s\n", download_items_it->title); + result = -1; + break; + } + + /* TODO: Make asynchronous */ + const char *args[] = { plugin_filepath, "download", download_items_it->link, NULL }; + result = program_exec(args, NULL, NULL); + 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); + + if(result != 0) + break; + + added_download_items[download_item_index] = download_items_it; + ++download_item_index; + } + + TrackedItem tracked_item; + tracked_item.title = tracked_html->title; + tracked_item.link = tracked_html->link; + tracked_item.json_data = tracked_html->json_data; + result = tracked_item_update_latest(&tracked_item, html_tracked_dir, added_download_items, NULL, download_item_index); + + buffer_deinit(&json_element_buffer); + return result; +} + +/* TODO: Make asynchronous. Right now this will only complete when the whole chapter download completes */ +int sync_html(TrackedHtml *tracked_html, char *program_dir, const char *download_dir, char *html_config_dir) { + /* TODO: This can be cached */ + int html_config_dir_len = strlen(html_config_dir); + + fprintf(stderr, "Syncing %s\n", tracked_html->title); + + char plugin_filepath[PATH_MAX]; + /* This will check with ${tracked_html->plugin}.py as well, but that is fine */ + if(get_plugin_filepath(program_dir, tracked_html->plugin, plugin_filepath) != 0) + return -1; + + struct json_value_s *downloaded_items = json_object_get_field_by_name(tracked_html->json_data, "downloaded"); + struct json_array_s *downloaded_items_array = NULL; + if(downloaded_items) { + downloaded_items_array = json_value_as_array(downloaded_items); + if(!downloaded_items_array) { + fprintf(stderr, "Corrupt json for html item: %s\n", tracked_html->title); + return -1; + } + } + + Buffer download_items_buffer; + buffer_init(&download_items_buffer); + + int result = 0; + + struct json_value_s *json_root = plugin_list(plugin_filepath, tracked_html->link, downloaded_items_array, plugin_list_sync_callback, &download_items_buffer); + if(!json_root) { + result = -1; + goto cleanup; + } + + char *html_tracked_dir = html_config_dir; + strcat(html_tracked_dir, "/tracked/"); + + result = download_html_items_in_reverse(plugin_filepath, &download_items_buffer, tracked_html, html_tracked_dir, download_dir); + if(result != 0) { + fprintf(stderr, "Failed while download html item for url: %s\n", tracked_html->link); + goto cleanup; + } + + char updated[32]; + sprintf(updated, "%ld", time(NULL)); + strcat(html_tracked_dir, tracked_html->title); + result = file_overwrite_in_dir(html_tracked_dir, "synced", updated, strlen(updated)); + if(result != 0) { + fprintf(stderr, "Failed to update %s/synced\n", html_tracked_dir); + goto cleanup; + } + + cleanup: + free(json_root); + buffer_deinit(&download_items_buffer); + html_config_dir[html_config_dir_len] = '\0'; + return result; +} @@ -1,6 +1,14 @@ #ifndef HTML_H #define HTML_H +typedef struct { + char *plugin; + char *title; + char *link; + struct json_object_s *json_data; +} TrackedHtml; + int add_html(const char *name, const char *url, char *html_config_dir, char *program_dir, const char *start_after); +int sync_html(TrackedHtml *tracked_html, char *program_dir, const char *download_dir, char *html_config_dir); #endif @@ -258,29 +258,35 @@ static void command_add(int argc, char **argv, char *rss_config_dir, char *html_ } } -sig_atomic_t running = 0; +sig_atomic_t automedia_running = 0; static void automedia_pid_signal_handler(int signum) { (void)signum; - running = 0; + automedia_running = 0; } -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"); +int is_program_running() { + return automedia_running; +} + +/* plugin is NULL for rss */ +typedef int (*IterateTrackedItemCallback)(char *title, char *link, char *plugin, char *config_dir, struct json_object_s *json_data, void *userdata); +static void iterate_tracked_items(char *config_dir, IterateTrackedItemCallback iterate_callback, void *userdata) { + char tracked_dir[PATH_MAX]; + strcpy(tracked_dir, config_dir); + strcat(tracked_dir, "/tracked"); struct dirent *dir; - DIR *d = opendir(rss_tracked_dir); + DIR *d = opendir(tracked_dir); if(!d) { - fprintf(stderr, "Failed to open directory: %s\n", rss_tracked_dir); + fprintf(stderr, "Failed to open directory: %s\n", tracked_dir); return; } - char *item_filepath = rss_tracked_dir; + char *item_filepath = tracked_dir; strcat(item_filepath, "/"); int item_filepath_len = strlen(item_filepath); - while((dir = readdir(d)) != NULL && running) { + while((dir = readdir(d)) != NULL && automedia_running) { int title_len = strlen(dir->d_name); if((title_len == 1 && dir->d_name[0] == '.') || (title_len == 2 && dir->d_name[0] == '.' && dir->d_name[1] == '.')) continue; @@ -293,49 +299,91 @@ static void sync_tracked_rss(TransmissionSession *transmission_session, char *rs continue; } - strcpy(item_filepath + item_filepath_len + title_len, "/link"); char *link_file_content = NULL; + char *data_file_content = NULL; + char *plugin_file_content = NULL; + struct json_value_s *json_data = NULL; + + strcpy(item_filepath + item_filepath_len + title_len, "/link"); long link_file_size = 0; int has_link = file_get_content(item_filepath, &link_file_content, &link_file_size); strcpy(item_filepath + item_filepath_len + title_len, "/data"); - char *data_file_content = NULL; long data_file_size = 0; int has_data = file_get_content(item_filepath, &data_file_content, &data_file_size); + strcpy(item_filepath + item_filepath_len + title_len, "/plugin"); + long plugin_file_size = 0; + file_get_content(item_filepath, &plugin_file_content, &plugin_file_size); + if(has_link != 0 || has_data != 0) { - free(link_file_content); - free(data_file_content); fprintf(stderr, "Rss corrupt, link or data missing for rss %s\n", dir->d_name); - continue; + goto cleanup_item; } - struct json_value_s *json_data = json_parse(data_file_content, data_file_size); - free(data_file_content); + json_data = json_parse(data_file_content, data_file_size); if(!json_data || json_data->type != json_type_object) { - free(link_file_content); - free(json_data); fprintf(stderr, "Rss corrupt for %s\n", dir->d_name); - continue; + goto cleanup_item; } + free(data_file_content); + data_file_content = NULL; - TrackedRss tracked_rss; - 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(transmission_session, &tracked_rss, rss_config_dir) != 0) + if(iterate_callback(dir->d_name, link_file_content, plugin_file_content, config_dir, json_value_as_object(json_data), userdata) != 0) fprintf(stderr, "Failed to sync %s\n", dir->d_name); - free(link_file_content); + cleanup_item: free(json_data); + free(plugin_file_content); + free(data_file_content); + free(link_file_content); } closedir(d); } -static void sync_rss_html(char *rss_config_dir, char *html_config_dir, const char *download_dir, int sync_rate_sec) { - (void)html_config_dir; +static int iterate_tracked_item_rss_callback(char *title, char *link, char *plugin, char *config_dir, struct json_object_s *json_data, void *userdata) { + (void)plugin; + TransmissionSession *transmission_session = userdata; + TrackedRss tracked_rss; + tracked_rss.title = title; + tracked_rss.link = link; + tracked_rss.json_data = json_data; + return sync_rss(&tracked_rss, transmission_session, config_dir); +} + +typedef struct { + char *program_dir; + const char *download_dir; +} IterateHtmlItemUserdata; + +static int iterate_tracked_item_html_callback(char *title, char *link, char *plugin, char *config_dir, struct json_object_s *json_data, void *userdata) { + if(!plugin) { + fprintf(stderr, "Missing plugin name for html item: %s\n", title); + return -1; + } + + IterateHtmlItemUserdata *iterate_html_item_userdata = userdata; + TrackedHtml tracked_html; + tracked_html.plugin = plugin; + tracked_html.title = title; + tracked_html.link = link; + tracked_html.json_data = json_data; + return sync_html(&tracked_html, iterate_html_item_userdata->program_dir, iterate_html_item_userdata->download_dir, config_dir); +} + +static void sync_tracked_rss(TransmissionSession *transmission_session, char *rss_config_dir) { + iterate_tracked_items(rss_config_dir, iterate_tracked_item_rss_callback, transmission_session); +} + +static void sync_tracked_html(char *html_config_dir, char *program_dir, const char *download_dir) { + IterateHtmlItemUserdata iterate_html_item_userdata; + iterate_html_item_userdata.program_dir = program_dir; + iterate_html_item_userdata.download_dir = download_dir; + iterate_tracked_items(html_config_dir, iterate_tracked_item_html_callback, &iterate_html_item_userdata); +} +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) { fprintf(stderr, "Failed to start torrent daemon\n"); @@ -349,11 +397,14 @@ static void sync_rss_html(char *rss_config_dir, char *html_config_dir, const cha exit(2); } - running = 1; + automedia_running = 1; /* running is set to 0 in SIGINT signal handler (ctrl+c) */ - while(running) { + while(automedia_running) { sync_tracked_rss(&transmission_session, rss_config_dir); - if(running) + sync_tracked_html(html_config_dir, program_dir, download_dir); + + /* TODO: Show finished html/rss items */ + if(automedia_running) sleep(sync_rate_sec); } } @@ -371,7 +422,7 @@ static int cmdline_contains_str(const char *cmdline, const char *str) { } } -static void command_sync(int argc, char **argv, char *rss_config_dir, char *html_config_dir) { +static void command_sync(int argc, char **argv, char *rss_config_dir, char *html_config_dir, char *program_dir) { if(argc < 1) usage_sync(); @@ -435,7 +486,7 @@ static void command_sync(int argc, char **argv, char *rss_config_dir, char *html close(pid_file); const int sync_rate_sec = 15 * 60; /* every 15 min */ - sync_rss_html(rss_config_dir, html_config_dir, download_dir, sync_rate_sec); + sync_rss_html(rss_config_dir, html_config_dir, program_dir, download_dir, sync_rate_sec); remove(automedia_pid_path); } @@ -483,7 +534,7 @@ int main(int argc, char **argv) { if(strcmp(command, "add") == 0) { command_add(argc - 2, argv + 2, rss_config_dir, html_config_dir, dirname(argv[0])); } else if(strcmp(command, "sync") == 0) { - command_sync(argc - 2, argv + 2, rss_config_dir, html_config_dir); + command_sync(argc - 2, argv + 2, rss_config_dir, html_config_dir, dirname(argv[0])); } else if(strcmp(command, "downloaded") == 0) { command_downloaded(rss_config_dir, html_config_dir); } else { diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..5312063 --- /dev/null +++ b/src/main.h @@ -0,0 +1,6 @@ +#ifndef MAIN_H +#define MAIN_H + +int is_program_running(); + +#endif @@ -6,6 +6,7 @@ #include "buffer.h" #include "rss_html_common.h" #include "json.h" +#include "alloc.h" #include <string.h> #include <stdio.h> #include <stdlib.h> @@ -297,11 +298,6 @@ static int is_item_already_downloaded(const char *title, const char *link, Track } typedef struct { - const char *title; - const char *link; -} DownloadItemsData; - -typedef struct { TrackedRss *tracked_rss; Buffer *download_items_buffer; } RssParseSyncData; @@ -318,186 +314,46 @@ static int rss_parse_sync_callback(char *title, char *link, void *userdata) { return 0; } -static struct json_array_element_s* get_last_element_in_json_array(struct json_array_s *json_array) { - struct json_array_element_s *json_element = json_array->start; - while(json_element) { - struct json_array_element_s *next_json_element = json_element->next; - if(next_json_element) - json_element = next_json_element; - else - return json_element; - } - return NULL; -} - -/* TODO: If this fails in the middle, recover and update this next time somehow */ -static int rss_update_latest(char *rss_tracked_dir, TrackedRss *tracked_rss, const char *latest_title, const char *url, const char *filename) { - int rss_tracked_dir_len = strlen(rss_tracked_dir); +static int add_torrents_in_reverse(TransmissionSession *transmission_session, Buffer *download_items_buffer, TrackedRss *tracked_rss, char *rss_tracked_dir) { int result = 0; + char *torrent_names[MAX_UPDATE_ITEMS]; + DownloadItemsData *added_download_items[MAX_UPDATE_ITEMS]; - char *item_filepath = rss_tracked_dir; - strcat(item_filepath, tracked_rss->title); - - char updated[32]; - assert(sizeof(time_t) == sizeof(long)); - sprintf(updated, "%ld", time(NULL)); - int updated_len = strlen(updated); - result = file_overwrite_in_dir(item_filepath, "updated", updated, updated_len); - if(result != 0) { - fprintf(stderr, "Failed to update %s/updated\n", item_filepath); - goto cleanup; - } - - struct json_string_s *updated_json = json_value_as_string(json_object_get_field_by_name(tracked_rss->json_data, "updated")); - updated_json->string = updated; - updated_json->string_size = updated_len; - - struct json_value_s *downloaded_json = json_object_get_field_by_name(tracked_rss->json_data, "downloaded"); - /* TODO:; WHAT IF DJSONWLOADING JSON DOENS*T SHIT */ - - struct json_string_s title_json_key; - create_json_string(&title_json_key, "title", 5); - - struct json_string_s title_json_value_str; - create_json_string(&title_json_value_str, latest_title, strlen(latest_title)); - struct json_value_s title_json_value; - init_json_value_str(&title_json_value, &title_json_value_str); - - struct json_string_s filename_json_key; - 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)); - struct json_value_s filename_json_value; - init_json_value_str(&filename_json_value, &filename_json_value_str); - - struct json_string_s time_json_key; - create_json_string(&time_json_key, "time", 4); - - struct json_string_s time_value_str; - create_json_string(&time_value_str, updated, updated_len); - struct json_value_s time_json_value; - init_json_value_str(&time_json_value, &time_value_str); - - struct json_string_s url_json_key; - create_json_string(&url_json_key, "url", 3); - - struct json_string_s url_value_str; - create_json_string(&url_value_str, url, strlen(url)); - struct json_value_s url_json_value; - init_json_value_str(&url_json_value, &url_value_str); - - struct json_object_element_s downloaded_title_element; - downloaded_title_element.name = &title_json_key; - downloaded_title_element.value = &title_json_value; - - struct json_object_element_s downloaded_filename_element; - downloaded_filename_element.name = &filename_json_key; - downloaded_filename_element.value = &filename_json_value; - - struct json_object_element_s downloaded_time_element; - downloaded_time_element.name = &time_json_key; - downloaded_time_element.value = &time_json_value; - - struct json_object_element_s downloaded_url_element; - downloaded_url_element.name = &url_json_key; - downloaded_url_element.value = &url_json_value; - - downloaded_title_element.next = &downloaded_filename_element; - downloaded_filename_element.next = &downloaded_time_element; - downloaded_time_element.next = &downloaded_url_element; - downloaded_url_element.next = NULL; - - struct json_object_s new_downloaded_json_obj; - new_downloaded_json_obj.length = 4; - new_downloaded_json_obj.start = &downloaded_title_element; - - struct json_value_s new_downloaded_json_val; - new_downloaded_json_val.payload = &new_downloaded_json_obj; - new_downloaded_json_val.type = json_type_object; - - struct json_array_element_s new_downloaded_item_element; - new_downloaded_item_element.value = &new_downloaded_json_val; - new_downloaded_item_element.next = NULL; + Buffer json_element_buffer; + buffer_init(&json_element_buffer); - struct json_array_s new_downloaded_array; - struct json_value_s new_downloaded_array_val; - new_downloaded_array_val.payload = &new_downloaded_array; - new_downloaded_array_val.type = json_type_array; - - struct json_string_s downloaded_json_key; - create_json_string(&downloaded_json_key, "downloaded", 10); - - struct json_object_element_s new_downloaded_array_obj_el; - new_downloaded_array_obj_el.name = &downloaded_json_key; - new_downloaded_array_obj_el.value = &new_downloaded_array_val; - - if(downloaded_json && downloaded_json->type == json_type_array) { - struct json_array_s *downloaded_json_array = json_value_as_array(downloaded_json); - struct json_array_element_s *last_downloaded_element = get_last_element_in_json_array(downloaded_json_array); - if(last_downloaded_element) - last_downloaded_element->next = &new_downloaded_item_element; - else - downloaded_json_array->start = &new_downloaded_item_element; - downloaded_json_array->length++; - } else { - new_downloaded_array.start = &new_downloaded_item_element; - new_downloaded_array.length = 1; - - struct json_object_element_s *prev_start = tracked_rss->json_data->start; - tracked_rss->json_data->start = &new_downloaded_array_obj_el; - new_downloaded_array_obj_el.next = prev_start; - tracked_rss->json_data->length++; - } - - struct json_value_s json_root_value; - json_root_value.payload = tracked_rss->json_data; - json_root_value.type = json_type_object; - - size_t json_body_size = 0; - char *json_body_str = json_write_pretty(&json_root_value, " ", "\n", &json_body_size); - if(!json_body_str) { - fprintf(stderr, "Failed to write json data to file %s/data\n", item_filepath); - result = -1; - goto cleanup; - } - - /* Workaround json bug (?) */ - json_body_size = strlen(json_body_str); - - result = file_overwrite_in_dir(item_filepath, "data", json_body_str, json_body_size); - free(json_body_str); - - cleanup: - rss_tracked_dir[rss_tracked_dir_len] = '\0'; - return result; -} - -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) { - char *torrent_name; - if(transmission_add_torrent(transmission_session, download_items_it->link, &torrent_name) != 0) { + 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, "Failed to add torrent: %s\n", download_items_it->link); - return 1; + 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? */ + } - 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; - } + TrackedItem tracked_item; + tracked_item.title = tracked_rss->title; + tracked_item.link = tracked_rss->link; + tracked_item.json_data = tracked_rss->json_data; + result = tracked_item_update_latest(&tracked_item, rss_tracked_dir, added_download_items, torrent_names, torrent_name_index); - free(torrent_name); - /* Show notification that download has started? */ + for(int i = 0; i < torrent_name_index; ++i) { + free(torrent_names[torrent_name_index]); } - return 0; + + buffer_deinit(&json_element_buffer); + return result; } -int sync_rss(TransmissionSession *transmission_session, TrackedRss *tracked_rss, char *rss_config_dir) { +int sync_rss(TrackedRss *tracked_rss, TransmissionSession *transmission_session, char *rss_config_dir) { /* TODO: This can be cached */ int rss_config_dir_len = strlen(rss_config_dir); @@ -11,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(struct TransmissionSession *transmission_session, TrackedRss *tracked_rss, char *rss_config_dir); +int sync_rss(TrackedRss *tracked_rss, struct TransmissionSession *transmission_session, char *rss_config_dir); #endif diff --git a/src/rss_html_common.c b/src/rss_html_common.c index f1aa7ad..ba4682b 100644 --- a/src/rss_html_common.c +++ b/src/rss_html_common.c @@ -3,6 +3,8 @@ #include "fileutils.h" #include <string.h> #include <stdio.h> +#include <time.h> +#include <assert.h> int write_plugin_json_to_file(const char *dir, const char *filename, const char *url, const char *updated, const char *start_after, const char *start_after_url, const char *plugin_name) { struct json_string_s title_json_key; @@ -147,6 +149,200 @@ int write_plugin_json_to_file(const char *dir, const char *filename, const char return result; } +static struct json_array_element_s* get_last_element_in_json_array(struct json_array_s *json_array) { + struct json_array_element_s *json_element = json_array->start; + while(json_element) { + struct json_array_element_s *next_json_element = json_element->next; + if(next_json_element) + json_element = next_json_element; + else + return json_element; + } + return NULL; +} + +/* TODO: If this fails in the middle, recover and update the next time somehow */ +int tracked_item_update_latest(TrackedItem *tracked_item, char *tracked_dir, DownloadItemsData **download_items, char **filenames, int num_download_items) { + assert(num_download_items <= MAX_UPDATE_ITEMS); + int tracked_dir_len = strlen(tracked_dir); + int result = 0; + + char *item_filepath = tracked_dir; + strcat(item_filepath, tracked_item->title); + + char updated[32]; + assert(sizeof(time_t) == sizeof(long)); + sprintf(updated, "%ld", time(NULL)); + int updated_len = strlen(updated); + result = file_overwrite_in_dir(item_filepath, "updated", updated, updated_len); + if(result != 0) { + fprintf(stderr, "Failed to update %s/updated\n", item_filepath); + goto cleanup; + } + + struct json_value_s *updated_field = json_object_get_field_by_name(tracked_item->json_data, "updated"); + if(updated_field) { + struct json_string_s *updated_json = json_value_as_string(updated_field); + updated_json->string = updated; + updated_json->string_size = updated_len; + } else { + fprintf(stderr, "Corrupt json for rss item: %s\n", item_filepath); + } + + struct json_value_s *downloaded_json = json_object_get_field_by_name(tracked_item->json_data, "downloaded"); + struct json_array_s *downloaded_json_array = NULL; + + struct json_array_s new_downloaded_array; + new_downloaded_array.length = 1; + new_downloaded_array.start = NULL; + + struct json_value_s new_downloaded_array_val; + new_downloaded_array_val.payload = &new_downloaded_array; + new_downloaded_array_val.type = json_type_array; + + struct json_string_s downloaded_json_key; + create_json_string(&downloaded_json_key, "downloaded", 10); + + struct json_object_element_s new_downloaded_array_obj_el; + new_downloaded_array_obj_el.name = &downloaded_json_key; + new_downloaded_array_obj_el.value = &new_downloaded_array_val; + + if(downloaded_json) { + downloaded_json_array = json_value_as_array(downloaded_json); + if(!downloaded_json_array) { + fprintf(stderr, "Corrupt json for rss item: %s\n", item_filepath); + result = -1; + goto cleanup; + } + } else { + downloaded_json_array = &new_downloaded_array; + } + + struct json_string_s title_json_key; + create_json_string(&title_json_key, "title", 5); + + struct json_string_s filename_json_key; + create_json_string(&filename_json_key, "filename", 8); + + struct json_string_s time_json_key; + create_json_string(&time_json_key, "time", 4); + + struct json_string_s url_json_key; + create_json_string(&url_json_key, "url", 3); + + struct json_string_s title_json_value_str[MAX_UPDATE_ITEMS]; + struct json_value_s title_json_value[MAX_UPDATE_ITEMS]; + struct json_string_s filename_json_value_str[MAX_UPDATE_ITEMS]; + struct json_value_s filename_json_value[MAX_UPDATE_ITEMS]; + struct json_string_s time_value_str[MAX_UPDATE_ITEMS]; + struct json_value_s time_json_value[MAX_UPDATE_ITEMS]; + struct json_string_s url_value_str[MAX_UPDATE_ITEMS]; + struct json_value_s url_json_value[MAX_UPDATE_ITEMS]; + + struct json_object_element_s downloaded_title_element[MAX_UPDATE_ITEMS]; + struct json_object_element_s downloaded_filename_element[MAX_UPDATE_ITEMS]; + struct json_object_element_s downloaded_time_element[MAX_UPDATE_ITEMS]; + struct json_object_element_s downloaded_url_element[MAX_UPDATE_ITEMS]; + + struct json_object_s new_downloaded_json_obj[MAX_UPDATE_ITEMS]; + struct json_value_s new_downloaded_json_val[MAX_UPDATE_ITEMS]; + struct json_array_element_s new_downloaded_item_element[MAX_UPDATE_ITEMS]; + + struct json_array_element_s *last_downloaded_element = NULL; + for(int i = 0; i < num_download_items; ++i) { + create_json_string(&title_json_value_str[i], download_items[i]->title, strlen(download_items[i]->title)); + init_json_value_str(&title_json_value[i], &title_json_value_str[i]); + + if(filenames) { + create_json_string(&filename_json_value_str[i], filenames[i], strlen(filenames[i])); + init_json_value_str(&filename_json_value[i], &filename_json_value_str[i]); + } + + create_json_string(&time_value_str[i], updated, updated_len); + init_json_value_str(&time_json_value[i], &time_value_str[i]); + + create_json_string(&url_value_str[i], download_items[i]->link, strlen(download_items[i]->link)); + init_json_value_str(&url_json_value[i], &url_value_str[i]); + + downloaded_title_element[i].name = &title_json_key; + downloaded_title_element[i].value = &title_json_value[i]; + + if(filenames) { + downloaded_filename_element[i].name = &filename_json_key; + downloaded_filename_element[i].value = &filename_json_value[i]; + } + + downloaded_time_element[i].name = &time_json_key; + downloaded_time_element[i].value = &time_json_value[i]; + + downloaded_url_element[i].name = &url_json_key; + downloaded_url_element[i].value = &url_json_value[i]; + + downloaded_title_element[i].next = &downloaded_time_element[i]; + downloaded_time_element[i].next = &downloaded_url_element[i]; + downloaded_url_element[i].next = NULL; + if(filenames) { + downloaded_url_element[i].next = &downloaded_filename_element[i]; + downloaded_filename_element[i].next = NULL; + new_downloaded_json_obj[i].length = 4; + } else { + new_downloaded_json_obj[i].length = 3; + } + + new_downloaded_json_obj[i].start = &downloaded_title_element[i]; + + new_downloaded_json_val[i].payload = &new_downloaded_json_obj[i]; + new_downloaded_json_val[i].type = json_type_object; + + new_downloaded_item_element[i].value = &new_downloaded_json_val[i]; + new_downloaded_item_element[i].next = NULL; + + if(downloaded_json_array->length > 0) { + if(!last_downloaded_element) + last_downloaded_element = get_last_element_in_json_array(downloaded_json_array); + + if(last_downloaded_element) + last_downloaded_element->next = &new_downloaded_item_element[i]; + else + downloaded_json_array->start = &new_downloaded_item_element[i]; + + last_downloaded_element = &new_downloaded_item_element[i]; + downloaded_json_array->length++; + } else { + downloaded_json_array->start = &new_downloaded_item_element[i]; + downloaded_json_array->length = 1; + last_downloaded_element = &new_downloaded_item_element[i]; + + struct json_object_element_s *prev_start = tracked_item->json_data->start; + tracked_item->json_data->start = &new_downloaded_array_obj_el; + new_downloaded_array_obj_el.next = prev_start; + tracked_item->json_data->length++; + } + } + + struct json_value_s json_root_value; + json_root_value.payload = tracked_item->json_data; + json_root_value.type = json_type_object; + + size_t json_body_size = 0; + char *json_body_str = json_write_pretty(&json_root_value, " ", "\n", &json_body_size); + if(!json_body_str) { + fprintf(stderr, "Failed to write json data to file %s/data\n", item_filepath); + result = -1; + goto cleanup; + } + + /* Workaround json bug (?) */ + json_body_size = strlen(json_body_str); + + result = file_overwrite_in_dir(item_filepath, "data", json_body_str, json_body_size); + free(json_body_str); + + cleanup: + tracked_dir[tracked_dir_len] = '\0'; + return result; +} + struct json_value_s* json_object_get_field_by_name(struct json_object_s *json_obj, const char *name) { struct json_object_element_s *obj_element = json_obj->start; while(obj_element) { diff --git a/src/rss_html_common.h b/src/rss_html_common.h index 472f090..3ae9d77 100644 --- a/src/rss_html_common.h +++ b/src/rss_html_common.h @@ -1,11 +1,34 @@ #ifndef RSS_HTML_COMMON_H #define RSS_HTML_COMMON_H +#define MAX_UPDATE_ITEMS 10 + struct json_value_s; struct json_object_s; struct json_string_s; +typedef struct { + const char *title; + const char *link; +} DownloadItemsData; + +typedef struct { + char *title; + char *link; + struct json_object_s *json_data; +} TrackedItem; + int write_plugin_json_to_file(const char *dir, const char *filename, const char *url, const char *updated, const char *start_after, const char *start_after_url, const char *plugin_name); + +/* + @num_download_items can't be more than MAX_UPDATE_ITEMS. + @filenames can be NULL, in which case filenames are not stored for items. This is the case for html items. +*/ +/* + Note: tracked_item.json_data becomes invalid after this call. + @tracked_dir is also modified and then restored at the end. +*/ +int tracked_item_update_latest(TrackedItem *tracked_item, char *tracked_dir, DownloadItemsData **download_items, char **filenames, int num_download_items); struct json_value_s* json_object_get_field_by_name(struct json_object_s *json_obj, const char *name); void create_json_string(struct json_string_s *json_result, const char *str, int len); void init_json_value_str(struct json_value_s *json_value, struct json_string_s *json_str); |