aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-07-15 06:09:50 +0200
committerdec05eba <dec05eba@protonmail.com>2020-07-15 07:46:57 +0200
commit35aca1f0582c43b5f6818c8fc00b924247e45881 (patch)
tree66d5e8f7954481863ba6d79db22a6df32f78af69 /src
parent5b20475c7faf89bbabc9eab43c7e5622317a18fc (diff)
Implement rss sync
Diffstat (limited to 'src')
-rw-r--r--src/fileutils.c20
-rw-r--r--src/html.c17
-rw-r--r--src/main.c156
-rw-r--r--src/rss.c334
-rw-r--r--src/rss.h9
-rw-r--r--src/rss_html_common.c34
-rw-r--r--src/rss_html_common.h7
-rw-r--r--src/transmission.c97
-rw-r--r--src/transmission.h3
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) {
diff --git a/src/html.c b/src/html.c
index de888a0..02982d6 100644
--- a/src/html.c
+++ b/src/html.c
@@ -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) {
diff --git a/src/main.c b/src/main.c
index 11fdc54..eabd755 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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;
}
diff --git a/src/rss.c b/src/rss.c
index 84fa345..ebbb1cf 100644
--- a/src/rss.c
+++ b/src/rss.c
@@ -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;
+}
diff --git a/src/rss.h b/src/rss.h
index b33231d..0c3175c 100644
--- a/src/rss.h
+++ b/src/rss.h
@@ -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