diff options
Diffstat (limited to 'src')
-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 |
9 files changed, 575 insertions, 102 deletions
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 |