diff options
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | src/fileutils.c | 20 | ||||
-rw-r--r-- | src/html.c | 17 | ||||
-rw-r--r-- | src/main.c | 156 | ||||
-rw-r--r-- | src/rss.c | 334 | ||||
-rw-r--r-- | src/rss.h | 9 | ||||
-rw-r--r-- | src/rss_html_common.c | 34 | ||||
-rw-r--r-- | src/rss_html_common.h | 7 | ||||
-rw-r--r-- | src/transmission.c | 97 | ||||
-rw-r--r-- | src/transmission.h | 3 |
10 files changed, 577 insertions, 103 deletions
@@ -9,6 +9,7 @@ Run automedia without any options to see all options. ## TODO 1. Periodically check and remove in_progress files and their directories. This can happen if the computer crashes while adding rss. 2. Automatically remove torrents that have finished seeding, to reduce memory usage and startup time of transmission. +3. Cache string lengths (too many strcat). # Requirements ## System transmission-cli, notify-send (optional) @@ -19,4 +20,4 @@ feedparser, transmissionrpc, lxml, requests, urllib, pure_protobuf (optional, us rofi, sxiv # Important Do not move files inside the download directory. If you want to move them, move the whole download directory -when automedia is not running and then set the download directory to the new location when using sync command. +when automedia is not running and then set the download directory to the new location when using sync command.
\ No newline at end of file diff --git a/src/fileutils.c b/src/fileutils.c index 64c7a48..4fba0d0 100644 --- a/src/fileutils.c +++ b/src/fileutils.c @@ -11,7 +11,7 @@ #include <fcntl.h> #include <sys/stat.h> -const char* get_home_dir() { +const char* get_home_dir(void) { const char *home_dir = getenv("HOME"); if(!home_dir) { struct passwd *pw = getpwuid(getuid()); @@ -38,12 +38,13 @@ int file_get_content(const char *filepath, char **data, long *size) { } fseek(file, 0, SEEK_SET); - *data = alloc_or_crash(*size); + *data = alloc_or_crash(*size + 1); if((long)fread(*data, 1, *size, file) != *size) { fprintf(stderr, "Failed to read all bytes in file %s\n", filepath); result = -1; goto cleanup; } + (*data)[*size] = '\0'; cleanup: fclose(file); @@ -87,10 +88,9 @@ int file_exists(const char *path) { } int create_lock_file(const char *path) { - int fd = open(path, O_CREAT | O_EXCL); + int fd = open(path, O_CREAT | O_EXCL | O_SYNC, 0666); if(fd == -1) return errno; - fsync(fd); return close(fd); } @@ -117,7 +117,17 @@ int file_overwrite_in_dir(const char *dir, const char *filename, const char *dat char filepath[PATH_MAX]; const char *filepath_components[] = { dir, filename }; path_join(filepath, filepath_components, 2); - return file_overwrite(filepath, data, size); + + char tmp_filepath[PATH_MAX]; + strcpy(tmp_filepath, filepath); + strcat(tmp_filepath, ".tmp"); + + int result = file_overwrite(tmp_filepath, data, size); + if(result != 0) + return result; + + /* Rename is atomic! */ + return rename(tmp_filepath, filepath); } void path_join(char *output, const char **components, int num_components) { @@ -11,6 +11,7 @@ #include <libgen.h> #include <signal.h> #include <time.h> +#include <assert.h> static int str_starts_with(const char *str, int len, const char *substr, int substr_len) { return len >= substr_len && memcmp(str, substr, substr_len) == 0; @@ -47,16 +48,6 @@ static int url_extract_domain(const char *url, char *domain, int domain_len) { return 0; } -static 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) { - if(strcmp(obj_element->name->string, name) == 0) - return obj_element->value; - obj_element = obj_element->next; - } - return NULL; -} - 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; @@ -80,8 +71,6 @@ static struct json_value_s* plugin_list(char *plugin_filepath, const char *url, goto err_cleanup; } - /*fprintf(stderr, "plugin list: %s\n", (char*)buffer.data);*/ - struct json_value_s *json_root = json_parse(buffer.data, buffer.size); if(!json_root) { fprintf(stderr, "Failed to load plugin %s list output as json\n", basename(plugin_filepath)); @@ -103,6 +92,9 @@ static struct json_value_s* plugin_list(char *plugin_filepath, const char *url, struct json_value_s *name_json = json_object_get_field_by_name(array_element_value, "name"); struct json_value_s *url_json = json_object_get_field_by_name(array_element_value, "url"); + if(!name_json || !url_json) + continue; + struct json_string_s *name_json_str = json_value_as_string(name_json); struct json_string_s *url_json_str = json_value_as_string(url_json); if(!name_json_str || !url_json_str) @@ -241,6 +233,7 @@ int add_html(const char *name, const char *url, char *html_config_dir, char *pro } char updated[32]; + assert(sizeof(time_t) == 4); sprintf(updated, "%ld", time(NULL)); result = file_overwrite_in_dir(html_tracked_dir, "updated", updated, strlen(updated)); if(result != 0) { @@ -4,6 +4,7 @@ #include "fileutils.h" #include "stringutils.h" #include "rss.h" +#include "rss_html_common.h" #include "html.h" #include "json.h" @@ -55,16 +56,6 @@ static void usage_sync(void) { exit(1); } -static 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) { - if(strcmp(obj_element->name->string, name) == 0) - return obj_element->value; - obj_element = obj_element->next; - } - return NULL; -} - typedef void (*DownloadedListCallback)(const char *title, double timestamp, void *userdata); static void data_file_get_downloaded(const char *dir_name, const char *data_filepath, int is_html, DownloadedListCallback callback, void *userdata) { @@ -163,21 +154,23 @@ static void downloaded_list_callback(const char *title, double timestamp, void * static void get_downloaded_items(const char *tracked_dir, int is_html, void *userdata) { struct dirent *dir; DIR *d = opendir(tracked_dir); - if(!d) + if(!d) { + fprintf(stderr, "Failed to open directory: %s\n", tracked_dir); return; + } - char data_filepath[PATH_MAX] = {0}; - strcat(data_filepath, tracked_dir); + char data_filepath[PATH_MAX]; + strcpy(data_filepath, tracked_dir); strcat(data_filepath, "/"); int data_filepath_length = strlen(data_filepath); while((dir = readdir(d)) != NULL) { - /* We dont want hidden files (and . ..) */ - if(dir->d_name[0] == '.') + int filename_len = strlen(dir->d_name); + if((filename_len == 1 && dir->d_name[0] == '.') || (filename_len == 2 && dir->d_name[0] == '.' && dir->d_name[1] == '.')) continue; strcpy(data_filepath + data_filepath_length, dir->d_name); - strcpy(data_filepath + data_filepath_length + strlen(dir->d_name), "/data"); + strcpy(data_filepath + data_filepath_length + filename_len, "/data"); data_file_get_downloaded(dir->d_name, data_filepath, is_html, downloaded_list_callback, userdata); } @@ -265,11 +258,98 @@ static void command_add(int argc, char **argv, char *rss_config_dir, char *html_ } } +sig_atomic_t running = 0; +static void automedia_pid_signal_handler(int signum) { + (void)signum; + running = 0; +} + +static void sync_tracked_rss(char *rss_config_dir) { + char rss_tracked_dir[PATH_MAX]; + strcpy(rss_tracked_dir, rss_config_dir); + strcat(rss_tracked_dir, "/tracked"); + + struct dirent *dir; + DIR *d = opendir(rss_tracked_dir); + if(!d) { + fprintf(stderr, "Failed to open directory: %s\n", rss_tracked_dir); + return; + } + + char *item_filepath = rss_tracked_dir; + strcat(item_filepath, "/"); + int item_filepath_len = strlen(item_filepath); + + while((dir = readdir(d)) != NULL && 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; + + strcpy(item_filepath + item_filepath_len, dir->d_name); + + strcpy(item_filepath + item_filepath_len + title_len, "/.in_progress"); + if(file_exists(item_filepath) == 0) { + fprintf(stderr, "Skipping in-progress rss %s\n", dir->d_name); + continue; + } + + strcpy(item_filepath + item_filepath_len + title_len, "/link"); + char *link_file_content = NULL; + 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); + + 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; + } + + struct json_value_s *json_data = json_parse(data_file_content, data_file_size); + free(data_file_content); + 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; + } + + 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(&tracked_rss, rss_config_dir) != 0) + fprintf(stderr, "Failed to sync %s\n", dir->d_name); + + free(link_file_content); + free(json_data); + } + + closedir(d); +} + static void sync_rss_html(char *rss_config_dir, char *html_config_dir, const char *download_dir, int sync_rate_sec) { - (void)rss_config_dir; (void)html_config_dir; - (void)download_dir; - (void)sync_rate_sec; + + if(transmission_is_daemon_running() != 0) { + if(transmission_start_daemon(download_dir) != 0) { + fprintf(stderr, "Failed to start torrent 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); + if(running) + sleep(sync_rate_sec); + } } static int cmdline_contains_str(const char *cmdline, const char *str) { @@ -285,24 +365,27 @@ static int cmdline_contains_str(const char *cmdline, const char *str) { } } -static void automedia_pid_signal_handler(int signum) { - (void)signum; - unlink("/tmp/automedia.pid"); - exit(1); -} - static void command_sync(int argc, char **argv, char *rss_config_dir, char *html_config_dir) { if(argc < 1) usage_sync(); - const char *download_dir = argv[0]; + char *download_dir = argv[0]; const char automedia_pid_path[] = "/tmp/automedia.pid"; + + if(create_directory_recursive(download_dir) != 0) { + fprintf(stderr, "Failed to create download directory %s\n", download_dir); + exit(1); + } - int pid_file = open(automedia_pid_path, O_CREAT | O_EXCL | O_RDWR); + int pid_file = open(automedia_pid_path, O_CREAT | O_EXCL | O_SYNC | O_RDWR, 0666); if(pid_file == -1 && errno == EEXIST) { char *running_automedia_pid; long running_automedia_pid_size; if(file_get_content(automedia_pid_path, &running_automedia_pid, &running_automedia_pid_size) == 0) { + /* + We have to check the cmdline because another process could theoretically receive the pid + that an old automedia process had + */ char cmdline_file_path[128]; sprintf(cmdline_file_path, "/proc/%s/cmdline", running_automedia_pid); free(running_automedia_pid); @@ -318,11 +401,14 @@ static void command_sync(int argc, char **argv, char *rss_config_dir, char *html } free(cmdline); } else { + fprintf(stderr, "Failed to get content of %s\n", automedia_pid_path); free(running_automedia_pid); + exit(1); } + fprintf(stderr, "Overwriting existing %s\n", automedia_pid_path); remove(automedia_pid_path); - pid_file = open(automedia_pid_path, O_CREAT | O_EXCL | O_RDWR); + pid_file = open(automedia_pid_path, O_CREAT | O_EXCL | O_SYNC | O_RDWR, 0666); } if(pid_file == -1 && errno != EEXIST) { @@ -337,14 +423,14 @@ static void command_sync(int argc, char **argv, char *rss_config_dir, char *html int process_pid_str_len = strlen(process_pid_str); if(write(pid_file, process_pid_str, process_pid_str_len) != process_pid_str_len) { fprintf(stderr, "Failed to write pid to %s\n", automedia_pid_path); - unlink(automedia_pid_path); + remove(automedia_pid_path); exit(1); } 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); - unlink(automedia_pid_path); + remove(automedia_pid_path); } static void command_downloaded(const char *rss_config_dir, const char *html_config_dir) { @@ -372,12 +458,7 @@ static void command_downloaded(const char *rss_config_dir, const char *html_conf buffer_deinit(&downloaded_items); } -/* -static void torrent_list_callback(int id, float percentage_finished, const char *name, void *userdata) { - (void)userdata; - fprintf(stderr, "id: |%d|, done: |%g|, name: |%s|\n", id, percentage_finished, name); -} -*/ + int main(int argc, char **argv) { if(argc < 2) usage(); @@ -404,8 +485,5 @@ int main(int argc, char **argv) { usage(); } - /*transmission_get_all_torrents(torrent_list_callback, NULL); - printf("is transmission daemon running? %s\n", transmission_is_daemon_running() == 0 ? "yes" : "no");*/ - return 0; } @@ -1,14 +1,17 @@ #include "rss.h" #include "download.h" +#include "transmission.h" #include "stringutils.h" #include "fileutils.h" #include "buffer.h" #include "rss_html_common.h" +#include "json.h" #include <string.h> #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <time.h> +#include <assert.h> static int is_alpha_lowercase(char c) { return c >= 'a' && c <= 'z'; @@ -29,7 +32,8 @@ static char* get_amp_end(char *str) { return str; } -static void xml_unescape(char *str, char *result, int result_length) { +static void xml_unescape(char *str) { + char *result = str; int index = 0; for(;;) { char c = *str; @@ -39,6 +43,7 @@ static void xml_unescape(char *str, char *result, int result_length) { *amp_end = '\0'; if(str[1] == '#') { + /* TODO: Also support non-ascii numbers */ result[index++] = atoi(str + 2); } else { if(strcmp(str + 1, "amp") == 0) @@ -62,11 +67,6 @@ static void xml_unescape(char *str, char *result, int result_length) { result[index++] = c; ++str; } - - if(index == result_length - 1) { - result[index] = '\0'; - break; - } } } @@ -79,7 +79,9 @@ static char* string_substr_before_tag_end(char *str, const char *tag) { typedef int (*RssParseCallback)(char *title, char *link, void *userdata); -static int parse_rss(char *str, char *rss_title_str, int rss_title_str_size, RssParseCallback parse_callback, void *userdata) { +static int parse_rss(char *str, char **rss_title_str, RssParseCallback parse_callback, void *userdata) { + *rss_title_str = NULL; + char *channel_start = strstr(str, "<channel>"); if(!channel_start) return 1; @@ -91,21 +93,18 @@ static int parse_rss(char *str, char *rss_title_str, int rss_title_str_size, Rss if(!first_item) { rss_title += 7; string_substr_before_tag_end(rss_title, "</title>"); - xml_unescape(rss_title, rss_title_str, rss_title_str_size); + xml_unescape(rss_title); + *rss_title_str = rss_title; return 0; } if(rss_title < first_item) { rss_title += 7; string_substr_before_tag_end(rss_title, "</title>"); - xml_unescape(rss_title, rss_title_str, rss_title_str_size); - } else { - rss_title_str[0] = '\0'; + xml_unescape(rss_title); + *rss_title_str = rss_title; } - char title_str[256]; - char link_str[2084]; - char *item = first_item; for(;;) { char *after_first_item = item + 6; @@ -136,9 +135,11 @@ static int parse_rss(char *str, char *rss_title_str, int rss_title_str_size, Rss item_link += 6; string_substr_before_tag_end(item_link, "</link>"); - xml_unescape(item_title, title_str, sizeof(title_str)); - xml_unescape(item_link, link_str, sizeof(link_str)); - if(parse_callback(title_str, link_str, userdata) != 0) + xml_unescape(item_title); + xml_unescape(item_link); + string_replace(item_title, '/', '_'); + char *stripped_title_str = strip(item_title); + if(parse_callback(stripped_title_str, item_link, userdata) != 0) return 0; item = strstr(item_end + 7, "<item>"); @@ -153,7 +154,7 @@ typedef struct { const char *start_after_url; } RssParseUserdata; -static int rss_parse_callback(char *title, char *link, void *userdata) { +static int rss_parse_add_callback(char *title, char *link, void *userdata) { RssParseUserdata *rss_parse_userdata = userdata; if(rss_parse_userdata->start_after && strcmp(rss_parse_userdata->start_after, title) == 0) { rss_parse_userdata->found_start_after = 1; @@ -179,9 +180,8 @@ int add_rss(const char *name, const char *url, char *rss_config_dir, const char rss_parse_userdata.found_start_after = 0; rss_parse_userdata.start_after_url = NULL; - /* TODO: What if rss title is longer than this? */ - char rss_title[250]; - result = parse_rss(buffer.data, rss_title, sizeof(rss_title), rss_parse_callback, &rss_parse_userdata); + char *rss_title = NULL; + result = parse_rss(buffer.data, &rss_title, rss_parse_add_callback, &rss_parse_userdata); if(result != 0) { fprintf(stderr, "Failed to parse rss for url: %s\n", url); goto cleanup; @@ -196,7 +196,7 @@ int add_rss(const char *name, const char *url, char *rss_config_dir, const char /* TODO: Add (add rss <episode name>) here */ if(!name) { - if(rss_title[0] == '\0') { + if(!rss_title) { fprintf(stderr, "Failed to find rss title and --name was not provided\n"); result = -1; goto cleanup; @@ -265,3 +265,293 @@ int add_rss(const char *name, const char *url, char *rss_config_dir, const char buffer_deinit(&buffer); return result; } + +static int is_item_already_downloaded(const char *title, const char *link, TrackedRss *tracked_rss) { + /* TODO: Optimize this... */ + struct json_value_s *downloaded_json = json_object_get_field_by_name(tracked_rss->json_data, "downloaded"); + 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 *downloaded_item = downloaded_json_array->start; + for(; downloaded_item; downloaded_item = downloaded_item->next) { + struct json_object_s *downloaded_obj = json_value_as_object(downloaded_item->value); + if(!downloaded_obj) + continue; + + struct json_value_s *download_title_value = json_object_get_field_by_name(downloaded_obj, "title"); + struct json_value_s *download_url_value = json_object_get_field_by_name(downloaded_obj, "url"); + + struct json_string_s *download_title_str = NULL; + struct json_string_s *download_url_str = NULL; + + if(download_title_value) + download_title_str = json_value_as_string(download_title_value); + + if(download_url_value) + download_url_str = json_value_as_string(download_url_value); + + if((download_title_str && strcmp(download_title_str->string, title) == 0) || (download_url_str && strcmp(download_url_str->string, link) == 0)) + return 1; + } + } + return 0; +} + +typedef struct { + const char *title; + const char *link; +} DownloadItemsData; + +typedef struct { + TrackedRss *tracked_rss; + Buffer *download_items_buffer; +} RssParseSyncData; + +static int rss_parse_sync_callback(char *title, char *link, void *userdata) { + RssParseSyncData *rss_parse_sync_data = userdata; + if(is_item_already_downloaded(title, link, rss_parse_sync_data->tracked_rss)) + return 1; + + DownloadItemsData download_items_data; + download_items_data.title = title; + download_items_data.link = link; + buffer_append(rss_parse_sync_data->download_items_buffer, &download_items_data, sizeof(download_items_data)); + 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); + int result = 0; + + 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, "title", 5); + + 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; + + 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(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) { + 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) { + fprintf(stderr, "Failed to update rss tracked data for %s\n", download_items_it->title); + return 1; + } + + /* Show notification that download has started? */ + } + return 0; +} + +int sync_rss(TrackedRss *tracked_rss, char *rss_config_dir) { + /* TODO: This can be cached */ + int rss_config_dir_len = strlen(rss_config_dir); + + fprintf(stderr, "Syncing %s\n", tracked_rss->title); + + int result = 0; + Buffer download_items_buffer; + buffer_init(&download_items_buffer); + + Buffer rss_data_buffer; + buffer_init(&rss_data_buffer); + result = download_to_buffer(tracked_rss->link, &rss_data_buffer); + if(result != 0) { + fprintf(stderr, "Failed to download rss: %s\n", tracked_rss->link); + goto cleanup; + } + + RssParseSyncData rss_parse_sync_data; + rss_parse_sync_data.tracked_rss = tracked_rss; + rss_parse_sync_data.download_items_buffer = &download_items_buffer; + char *rss_title = NULL; + result = parse_rss(rss_data_buffer.data, &rss_title, rss_parse_sync_callback, &rss_parse_sync_data); + if(result != 0) { + fprintf(stderr, "Failed to parse rss for url: %s\n", tracked_rss->link); + goto cleanup; + } + + 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); + if(result != 0) { + fprintf(stderr, "Failed while adding torrents for url: %s\n", tracked_rss->link); + goto cleanup; + } + + char updated[32]; + sprintf(updated, "%ld", time(NULL)); + strcat(rss_tracked_dir, tracked_rss->title); + result = file_overwrite_in_dir(rss_tracked_dir, "synced", updated, strlen(updated)); + if(result != 0) { + fprintf(stderr, "Failed to update %s/synced\n", rss_tracked_dir); + goto cleanup; + } + + cleanup: + rss_config_dir[rss_config_dir_len] = '\0'; + buffer_deinit(&rss_data_buffer); + buffer_deinit(&download_items_buffer); + return result; +} @@ -1,6 +1,15 @@ #ifndef RSS_H #define RSS_H +struct json_object_s; + +typedef struct { + char *title; + char *link; + struct json_object_s *json_data; +} 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); #endif diff --git a/src/rss_html_common.c b/src/rss_html_common.c index 56e1ccb..f1aa7ad 100644 --- a/src/rss_html_common.c +++ b/src/rss_html_common.c @@ -4,16 +4,6 @@ #include <string.h> #include <stdio.h> -static void create_json_string(struct json_string_s *json_result, const char *str, int len) { - json_result->string = str; - json_result->string_size = len; -} - -static void init_json_value_str(struct json_value_s *json_value, struct json_string_s *json_str) { - json_value->payload = json_str; - json_value->type = json_type_string; -} - 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; create_json_string(&title_json_key, "title", 5); @@ -144,7 +134,7 @@ int write_plugin_json_to_file(const char *dir, const char *filename, const char 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 || json_body_size == 0) { + if(!json_body_str) { fprintf(stderr, "Failed to write json data to file %s/%s\n", dir, filename); return -1; } @@ -155,4 +145,24 @@ int write_plugin_json_to_file(const char *dir, const char *filename, const char int result = file_overwrite_in_dir(dir, filename, json_body_str, json_body_size); free(json_body_str); return result; -}
\ No newline at end of file +} + +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) { + if(strcmp(obj_element->name->string, name) == 0) + return obj_element->value; + obj_element = obj_element->next; + } + return NULL; +} + +void create_json_string(struct json_string_s *json_result, const char *str, int len) { + json_result->string = str; + json_result->string_size = len; +} + +void init_json_value_str(struct json_value_s *json_value, struct json_string_s *json_str) { + json_value->payload = json_str; + json_value->type = json_type_string; +} diff --git a/src/rss_html_common.h b/src/rss_html_common.h index 99f7778..472f090 100644 --- a/src/rss_html_common.h +++ b/src/rss_html_common.h @@ -1,6 +1,13 @@ #ifndef RSS_HTML_COMMON_H #define RSS_HTML_COMMON_H +struct json_value_s; +struct json_object_s; +struct json_string_s; + 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_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); #endif
\ No newline at end of file diff --git a/src/transmission.c b/src/transmission.c index 0acb5a5..b9cb123 100644 --- a/src/transmission.c +++ b/src/transmission.c @@ -8,23 +8,32 @@ #define NUM_COLUMNS 10 -int transmission_is_daemon_running() { +int transmission_is_daemon_running(void) { const char *args[] = { "transmission-remote", "-si", NULL }; return program_exec(args, NULL, NULL); } -int transmission_start_daemon() { +int transmission_start_daemon(const char *download_dir) { /* TODO: Make seed ratio configurable */ - const char *args[] = { "transmission-daemon", "--global-seedratio", "2.0", "--download-dir", NULL }; + 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"); - while(transmission_is_daemon_running()) { - const useconds_t one_hundred_ms = 1000 * 1000 * 100; - usleep(one_hundred_ms); + 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; } @@ -46,13 +55,14 @@ int transmission_get_all_torrents(TorrentListCallback callback, void *userdata) result = exec_res; goto cleanup; } - buffer_append(&buffer, "0", 1); + 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]; @@ -67,10 +77,12 @@ int transmission_get_all_torrents(TorrentListCallback callback, void *userdata) 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 %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; - /*printf("id: %s, done: %s, have: %s, format: %s, eta: %s, up: %s, down: %s, ratio: %s, status: %s, name: %s\n", id, done, have, 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; } @@ -79,3 +91,66 @@ int transmission_get_all_torrents(TorrentListCallback callback, void *userdata) buffer_deinit(&buffer); return result; } + +static int find_start_of_line(const char *str, int offset) { + for(int i = offset; i >= 0; --i) { + if(*str == '\n') + return i + 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; + } + return 0; +} + +int transmission_get_last_added_torrent(int *id_result, float *percentage_finished_result, char *title_result) { + 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; + } + + 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; + goto cleanup; + } + } + + *id_result = atoi(id); + *percentage_finished_result = atof(done); + strcpy(title_result, name); + + cleanup: + buffer_deinit(&buffer); + return result; +} diff --git a/src/transmission.h b/src/transmission.h index c37ca3a..90a3594 100644 --- a/src/transmission.h +++ b/src/transmission.h @@ -6,9 +6,10 @@ typedef void (*TorrentListCallback)(int id, float percentage_finished, const cha /* Returns 0 if the daemon is running, otherwise returns an error value */ int transmission_is_daemon_running(); -int transmission_start_daemon(); +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); #endif |