#include "program.h" #include "buffer.h" #include "fileutils.h" #include "json.h" #include #include #include #include #include #include /* static int program_buffer_write_callback(char *data, int size, void *userdata) { Buffer *buffer = userdata; buffer_append(buffer, data, size); return 0; } */ static void usage(void) { fprintf(stderr, "usage: automedia COMMAND\n"); fprintf(stderr, "\n"); fprintf(stderr, "COMMANDS\n"); fprintf(stderr, " add Add media to track\n"); fprintf(stderr, " sync Start syncing tracked media\n"); fprintf(stderr, " downloaded List downloaded media\n"); exit(1); } static void usage_add(void) { fprintf(stderr, "usage: automedia add [--name name] [--start-after start_after]\n"); fprintf(stderr, "OPTIONS\n"); fprintf(stderr, " type The type should be either rss or html\n"); fprintf(stderr, " url The url to the rss or html\n"); fprintf(stderr, " filename The filename of an episode of an existing serie to start track. Currently only works with rss on https://nyaa.si\n"); fprintf(stderr, " --name The display name to be used for the media. Optional for rss, in which case the name will be retries from rss TITLE, required for html\n"); fprintf(stderr, " --start-after The sync should start downloading media after this item. This --start-after value should be the title of the episode/chapter (Optional, default is to start from the first item)\n"); fprintf(stderr, "EXAMPLES\n"); fprintf(stderr, " automedia add rss 'https://nyaa.si/?page=rss&q=Tejina-senpai+1080p&c=0_0&f=0&u=HorribleSubs'\n"); fprintf(stderr, " automedia add html 'https://manganelo.com/manga/read_naruto_manga_online_free3' --name Naruto\n"); fprintf(stderr, " automedia add rss '[Erai-raws] Saiki Kusuo no Psi Nan - Kanketsu-hen - 01 [1080p][Multiple Subtitle].mkv'\n"); exit(1); } static void usage_sync(void) { fprintf(stderr, "usage: automedia sync \n"); fprintf(stderr, "OPTIONS\n"); fprintf(stderr, " download_dir The path where media should be downloaded to\n"); fprintf(stderr, "EXAMPLES\n"); fprintf(stderr, " automedia sync /home/adam/Downloads/automedia\n"); 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, bool is_html, DownloadedListCallback callback, void *userdata) { char *file_data; long file_size; if(file_get_content(data_filepath, &file_data, &file_size) != 0) { fprintf(stderr, "Failed to read the content of file %s\n", data_filepath); return; } struct json_value_s *json_root = json_parse(file_data, file_size); if(!json_root) { fprintf(stderr, "Failed to parse file %s as json\n", data_filepath); goto cleanup; } struct json_object_s *json_root_obj = json_value_as_object(json_root); if(!json_root_obj) { fprintf(stderr, "File %s contains malformed json. Expected json root element to be an object\n", data_filepath); goto cleanup; } struct json_value_s *downloaded_json = json_object_get_field_by_name(json_root_obj, "downloaded"); if(!downloaded_json) { fprintf(stderr, "File %s contains malformed json. Expected json to contain \"downloaded\"\n", data_filepath); goto cleanup; } struct json_array_s *downloaded_json_arr = json_value_as_array(downloaded_json); if(!downloaded_json_arr) { fprintf(stderr, "File %s contains malformed json. Expected \"downloaded\" to be an array\n", data_filepath); goto cleanup; } size_t dir_name_len = strlen(dir_name); struct json_array_element_s *downloaded_item = downloaded_json_arr->start; for(; downloaded_item; downloaded_item = downloaded_item->next) { struct json_object_s *downloaded_item_obj = json_value_as_object(downloaded_item->value); if(!downloaded_item_obj) continue; struct json_value_s *time_json = json_object_get_field_by_name(downloaded_item_obj, "time"); if(!time_json || time_json->type != json_type_string) continue; struct json_string_s *time_json_str = json_value_as_string(time_json); struct json_value_s *title_json = json_object_get_field_by_name(downloaded_item_obj, "title"); struct json_string_s *title_str = NULL; if(title_json && title_json->type == json_type_string) title_str = json_value_as_string(title_json); struct json_value_s *filename_json = json_object_get_field_by_name(downloaded_item_obj, "filename"); struct json_string_s *filename_str = NULL; if(filename_json && filename_json->type == json_type_string) filename_str = json_value_as_string(filename_json); /* Filename limit is 256, so this should be safe... */ char title[256]; if(filename_str) { strcpy(title, filename_str->string); } else if(title_str) { if(is_html) { strcpy(title, dir_name); title[dir_name_len] = '/'; strcpy(title + dir_name_len + 1, title_str->string); } else { strcpy(title, title_str->string); } } else { continue; } callback(title, atof(time_json_str->string), userdata); } cleanup: if(json_root) free(json_root); free(file_data); } typedef struct { char *title; double timestamp; } DownloadedListData; static void downloaded_list_callback(const char *title, double timestamp, void *userdata) { DownloadedListData list_data; list_data.title = strdup(title); list_data.timestamp = timestamp; Buffer *buffer = userdata; buffer_append(buffer, &list_data, sizeof(list_data)); } static void get_downloaded_items(const char *tracked_dir, bool is_html, void *userdata) { struct dirent *dir; DIR *d = opendir(tracked_dir); if(!d) return; char data_filepath[PATH_MAX] = {0}; strcat(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] == '.') continue; strcpy(data_filepath + data_filepath_length, dir->d_name); strcpy(data_filepath + data_filepath_length + strlen(dir->d_name), "/data"); data_file_get_downloaded(dir->d_name, data_filepath, is_html, downloaded_list_callback, userdata); } closedir(d); } static int compare_downloaded_item(const void *a, const void *b) { const DownloadedListData *list_data_a = a; const DownloadedListData *list_data_b = b; return list_data_a->timestamp - list_data_b->timestamp; } static void command_downloaded(const char *rss_config_dir, const char *html_config_dir) { char rss_tracked_dir[PATH_MAX]; strcpy(rss_tracked_dir, rss_config_dir); strcat(rss_tracked_dir, "/tracked"); char html_tracked_dir[PATH_MAX]; strcpy(html_tracked_dir, html_config_dir); strcat(html_tracked_dir, "/tracked"); Buffer downloaded_items; buffer_init(&downloaded_items); get_downloaded_items(rss_tracked_dir, false, &downloaded_items); get_downloaded_items(html_tracked_dir, true, &downloaded_items); qsort(downloaded_items.data, buffer_get_size(&downloaded_items, sizeof(DownloadedListData)), sizeof(DownloadedListData), compare_downloaded_item); DownloadedListData *list_it = buffer_begin(&downloaded_items); DownloadedListData *list_end = buffer_end(&downloaded_items); for(; list_it != list_end; ++list_it) { puts(list_it->title); free(list_it->title); } buffer_deinit(&downloaded_items); } int main(int argc, char **argv) { if(argc < 2) usage(); const char *home_dir = get_home_dir(); char rss_config_dir[PATH_MAX];; strcpy(rss_config_dir, home_dir); strcat(rss_config_dir, "/.config/automedia/rss"); char html_config_dir[PATH_MAX]; strcpy(html_config_dir, home_dir); strcat(html_config_dir, "/.config/automedia/html"); const char *command = argv[1]; if(strcmp(command, "add") == 0) { usage_add(); } else if(strcmp(command, "sync") == 0) { usage_sync(); } else if(strcmp(command, "downloaded") == 0) { command_downloaded(rss_config_dir, html_config_dir); } else { fprintf(stderr, "Error: Invalid command %s\n", command); usage(); } /* Buffer buffer; buffer_init(&buffer); const char *args[] = { "curl", "-s", "-L", "-f", "https://google.com", NULL }; program_exec(args, program_buffer_write_callback, &buffer); printf("program output: %.*s\n", (int)buffer.size, (char*)buffer.data); buffer_deinit(&buffer); */ return 0; }