diff options
-rw-r--r-- | TODO | 3 | ||||
-rw-r--r-- | example-config.json | 7 | ||||
-rw-r--r-- | include/Config.hpp | 1 | ||||
-rw-r--r-- | include/Storage.hpp | 6 | ||||
-rw-r--r-- | src/Config.cpp | 1 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 8 | ||||
-rw-r--r-- | src/Storage.cpp | 32 | ||||
-rw-r--r-- | src/plugins/LocalAnime.cpp | 55 | ||||
-rw-r--r-- | src/plugins/LocalManga.cpp | 19 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 2 |
10 files changed, 81 insertions, 53 deletions
@@ -296,4 +296,5 @@ Youtube community tab. v0.m3u8 doesn't work for some lbry videos (such as https://odysee.com/@MoneroMagazine:9/Privacy-101-w-Luke-Smith:2). Fallback to v1.m3u8 (next track in the master.m3u8 file) in such cases. Use DPMSInfoNotify. Use stb_image_resize2.h -Youtube audio only download if audio stream not available, also for youtube-dl fallback.
\ No newline at end of file +Youtube audio only download if audio stream not available, also for youtube-dl fallback. +Make history (local-manga and others) use relative path to the downloads directory for thumbnails. Otherwise the thumbnails wont show when moving the download directory.
\ No newline at end of file diff --git a/example-config.json b/example-config.json index d5e6555..83858c8 100644 --- a/example-config.json +++ b/example-config.json @@ -43,7 +43,12 @@ "sort_episodes_by_name": true, // If false, anime is displayed in the way they appear in the directory. // If true, QuickMedia tries to group anime episodes into a folder (visually) with the name of the anime - "auto_group_episodes": true + "auto_group_episodes": true, + // If anime should be searched for recursively, where directories become anime and subdirectories become seasons. + // If false, only take the episodes directly in the "directory" path, useful on a slow harddrive when sort_episodes_by_name is set to true + // and the directory is for directly downloaded episodes without a structure. + // Setting this to false is ideal when the directory is the download directory for AutoMedia. + "recursive": true }, "youtube": { // If true, resume playback where you left off in youtube videos diff --git a/include/Config.hpp b/include/Config.hpp index 68e4462..e737fee 100644 --- a/include/Config.hpp +++ b/include/Config.hpp @@ -48,6 +48,7 @@ namespace QuickMedia { bool sort_by_name = false; // Series bool sort_episodes_by_name = true; bool auto_group_episodes = true; + bool recursive = true; }; struct YoutubeSponsorblock { diff --git a/include/Storage.hpp b/include/Storage.hpp index 19dd345..60c15f6 100644 --- a/include/Storage.hpp +++ b/include/Storage.hpp @@ -20,8 +20,8 @@ namespace QuickMedia { DESC }; - // Return false to stop the iterator - using FileIteratorCallback = std::function<bool(const Path &filepath, FileType file_type)>; + // Return false to stop the iterator. + using FileIteratorCallback = std::function<bool(const Path &filepath, FileType file_type, time_t last_modified_seconds)>; Path get_home_dir(); Path get_storage_dir(); @@ -34,8 +34,10 @@ namespace QuickMedia { bool file_get_last_modified_time_seconds(const char *path, time_t *result); int file_overwrite(const Path &path, const std::string &data); int file_overwrite_atomic(const Path &path, const std::string &data); + // The callback is called with 0 as the argument (last_modified_seconds) void for_files_in_dir(const Path &path, FileIteratorCallback callback); void for_files_in_dir_sort_last_modified(const Path &path, FileIteratorCallback callback, FileSortDirection sort_dir = FileSortDirection::ASC); + // The callback is called with 0 as the argument (last_modified_seconds) void for_files_in_dir_sort_name(const Path &path, FileIteratorCallback callback, FileSortDirection sort_dir = FileSortDirection::ASC); bool read_file_as_json(const Path &filepath, Json::Value &result); diff --git a/src/Config.cpp b/src/Config.cpp index 5d16083..ecc1fce 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -275,6 +275,7 @@ namespace QuickMedia { get_json_value(local_anime_json, "sort_by_name", config->local_anime.sort_by_name); get_json_value(local_anime_json, "sort_episodes_by_name", config->local_anime.sort_episodes_by_name); get_json_value(local_anime_json, "auto_group_episodes", config->local_anime.auto_group_episodes); + get_json_value(local_anime_json, "recursive", config->local_anime.recursive); } const Json::Value &youtube_json = json_root["youtube"]; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 332308b..d18ad4e 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1790,7 +1790,7 @@ namespace QuickMedia { // TODO: Remove this once manga history file has been in use for a few months and is filled with history time_t now = time(NULL); - for_files_in_dir_sort_last_modified(content_storage_dir, [&](const Path &filepath, FileType) { + for_files_in_dir_sort_last_modified(content_storage_dir, [&](const Path &filepath, FileType, time_t last_modified_seconds) { // This can happen when QuickMedia crashes/is killed while writing to storage. // In that case, the storage wont be corrupt but there will be .tmp files. // TODO: Remove these .tmp files if they exist during startup @@ -1812,12 +1812,12 @@ namespace QuickMedia { if(!manga_name.isString()) return true; - time_t last_modified_time = 0; - file_get_last_modified_time_seconds(filepath.data.c_str(), &last_modified_time); + if(last_modified_seconds == 0) + file_get_last_modified_time_seconds(filepath.data.c_str(), &last_modified_seconds); // TODO: Add thumbnail auto body_item = BodyItem::create(manga_name.asString()); - body_item->set_description("Last read " + seconds_to_relative_time_str(now - last_modified_time)); + body_item->set_description("Last read " + seconds_to_relative_time_str(now - last_modified_seconds)); body_item->set_description_color(get_theme().faded_text_color); auto thumbnail_it = manga_id_to_thumbnail_url_map.find(manga_id); diff --git a/src/Storage.cpp b/src/Storage.cpp index dc6b6a2..7bb3be6 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -249,7 +249,7 @@ namespace QuickMedia { for(auto &p : std::filesystem::directory_iterator(path.data)) { std::error_code ec; const FileType file_type = p.is_directory(ec) ? FileType::DIRECTORY : FileType::REGULAR; - if(!callback(p.path().string(), file_type)) + if(!callback(p.path().string(), file_type, 0)) break; } } catch(const std::filesystem::filesystem_error &err) { @@ -258,19 +258,13 @@ namespace QuickMedia { } } - static std::filesystem::file_time_type file_get_filetime_or(const std::filesystem::directory_entry &path, std::filesystem::file_time_type default_value) { - try { - return path.last_write_time(); - } catch(const std::filesystem::filesystem_error &err) { - return default_value; - } - } - void for_files_in_dir_sort_last_modified(const Path &path, FileIteratorCallback callback, FileSortDirection sort_dir) { - std::vector<std::filesystem::directory_entry> paths; + std::vector<std::pair<std::filesystem::directory_entry, time_t>> paths_with_last_modified; try { for(auto &p : std::filesystem::directory_iterator(path.data)) { - paths.push_back(p); + time_t last_modified = 0; + file_get_last_modified_time_seconds(p.path().c_str(), &last_modified); + paths_with_last_modified.push_back(std::make_pair(p, last_modified)); } } catch(const std::filesystem::filesystem_error &err) { fprintf(stderr, "Failed to list files in directory %s, error: %s\n", path.data.c_str(), err.what()); @@ -278,19 +272,19 @@ namespace QuickMedia { } if(sort_dir == FileSortDirection::ASC) { - std::sort(paths.begin(), paths.end(), [](const std::filesystem::directory_entry &path1, std::filesystem::directory_entry &path2) { - return file_get_filetime_or(path1, std::filesystem::file_time_type::min()) > file_get_filetime_or(path2, std::filesystem::file_time_type::min()); + std::sort(paths_with_last_modified.begin(), paths_with_last_modified.end(), [](const auto &path1, const auto &path2) { + return path1.second > path2.second; }); } else { - std::sort(paths.begin(), paths.end(), [](const std::filesystem::directory_entry &path1, std::filesystem::directory_entry &path2) { - return file_get_filetime_or(path1, std::filesystem::file_time_type::min()) < file_get_filetime_or(path2, std::filesystem::file_time_type::min()); + std::sort(paths_with_last_modified.begin(), paths_with_last_modified.end(), [](const auto &path1, const auto &path2) { + return path1.second < path2.second; }); } - for(auto &p : paths) { + for(auto &p : paths_with_last_modified) { std::error_code ec; - const FileType file_type = p.is_directory(ec) ? FileType::DIRECTORY : FileType::REGULAR; - if(!callback(p.path().string(), file_type)) + const FileType file_type = p.first.is_directory(ec) ? FileType::DIRECTORY : FileType::REGULAR; + if(!callback(p.first.path().string(), file_type, p.second)) break; } } @@ -319,7 +313,7 @@ namespace QuickMedia { for(auto &p : paths) { std::error_code ec; const FileType file_type = p.is_directory(ec) ? FileType::DIRECTORY : FileType::REGULAR; - if(!callback(p.path().string(), file_type)) + if(!callback(p.path().string(), file_type, 0)) break; } } diff --git a/src/plugins/LocalAnime.cpp b/src/plugins/LocalAnime.cpp index c906b9b..59e52ad 100644 --- a/src/plugins/LocalAnime.cpp +++ b/src/plugins/LocalAnime.cpp @@ -190,7 +190,7 @@ namespace QuickMedia { static std::vector<LocalAnimeItem> get_episodes_in_directory(const Path &directory) { std::vector<LocalAnimeItem> episodes; - for_files_in_dir_sort_name(directory, [&episodes](const Path &filepath, FileType file_type) -> bool { + for_files_in_dir_sort_name(directory, [&episodes](const Path &filepath, FileType file_type, time_t) -> bool { if(file_type != FileType::REGULAR || !is_video_ext(filepath.ext())) return true; @@ -206,21 +206,28 @@ namespace QuickMedia { static std::vector<LocalAnimeItem> get_episodes_or_seasons_in_directory(const Path &directory) { std::vector<LocalAnimeItem> anime_items; - auto callback = [&](const Path &filepath, FileType file_type) -> bool { - time_t modified_time_seconds; - if(!file_get_last_modified_time_seconds(filepath.data.c_str(), &modified_time_seconds)) - return true; - + auto callback = [&](const Path &filepath, FileType file_type, time_t last_modified_seconds) -> bool { if(file_type == FileType::REGULAR) { - if(is_video_ext(filepath.ext())) - anime_items.push_back(LocalAnimeEpisode{ filepath, modified_time_seconds }); + if(is_video_ext(filepath.ext())) { + if(last_modified_seconds == 0) { + if(!file_get_last_modified_time_seconds(filepath.data.c_str(), &last_modified_seconds)) + return true; + } + + anime_items.push_back(LocalAnimeEpisode{ filepath, last_modified_seconds }); + } return true; } + if(last_modified_seconds == 0) { + if(!file_get_last_modified_time_seconds(filepath.data.c_str(), &last_modified_seconds)) + return true; + } + LocalAnimeSeason season; season.name = filepath.filename(); season.episodes = get_episodes_in_directory(filepath); - season.modified_time_seconds = modified_time_seconds; + season.modified_time_seconds = last_modified_seconds; if(season.episodes.empty()) return true; @@ -236,23 +243,33 @@ namespace QuickMedia { return anime_items; } - std::vector<LocalAnimeItem> get_anime_in_directory(const Path &directory) { + std::vector<LocalAnimeItem> get_anime_in_directory(const Path &directory, bool recursive) { std::vector<LocalAnimeItem> anime_items; - auto callback = [&anime_items](const Path &filepath, FileType file_type) -> bool { - time_t modified_time_seconds; - if(!file_get_last_modified_time_seconds(filepath.data.c_str(), &modified_time_seconds)) - return true; - + auto callback = [&anime_items, recursive](const Path &filepath, FileType file_type, time_t last_modified_seconds) -> bool { if(file_type == FileType::REGULAR) { - if(is_video_ext(filepath.ext())) - anime_items.push_back(LocalAnimeEpisode{ filepath, modified_time_seconds }); + if(is_video_ext(filepath.ext())) { + if(last_modified_seconds == 0) { + if(!file_get_last_modified_time_seconds(filepath.data.c_str(), &last_modified_seconds)) + return true; + } + + anime_items.push_back(LocalAnimeEpisode{ filepath, last_modified_seconds }); + } return true; } + + if(!recursive) + return true; + if(last_modified_seconds == 0) { + if(!file_get_last_modified_time_seconds(filepath.data.c_str(), &last_modified_seconds)) + return true; + } + LocalAnime anime; anime.name = filepath.filename(); anime.items = get_episodes_or_seasons_in_directory(filepath); - anime.modified_time_seconds = modified_time_seconds; + anime.modified_time_seconds = last_modified_seconds; if(anime.items.empty()) return true; @@ -352,7 +369,7 @@ namespace QuickMedia { PluginResult LocalAnimeSearchPage::lazy_fetch(BodyItems &result_items) { if(fetch_home_page) { fetch_home_page = false; - anime_items = get_anime_in_directory(get_config().local_anime.directory); + anime_items = get_anime_in_directory(get_config().local_anime.directory, get_config().local_anime.recursive); } std::unordered_map<std::string, WatchProgress> watch_progress = get_watch_progress_for_plugin("local-anime"); diff --git a/src/plugins/LocalManga.cpp b/src/plugins/LocalManga.cpp index fa0df27..21e8780 100644 --- a/src/plugins/LocalManga.cpp +++ b/src/plugins/LocalManga.cpp @@ -83,7 +83,7 @@ namespace QuickMedia { // Pages are sorted from 1.png to n.png static std::vector<LocalMangaPage> get_images_in_manga(const Path &directory) { std::vector<LocalMangaPage> page_list; - for_files_in_dir(directory, [&page_list](const Path &filepath, FileType file_type) -> bool { + for_files_in_dir(directory, [&page_list](const Path &filepath, FileType file_type, time_t) -> bool { if(file_type != FileType::REGULAR) return true; @@ -107,7 +107,7 @@ namespace QuickMedia { static std::vector<LocalMangaChapter> get_chapters_in_manga(std::string manga_name, const Path &directory, bool only_include_latest, bool include_pages, bool only_get_coverpage = false) { std::vector<LocalMangaChapter> chapter_list; - auto callback = [&chapter_list, &manga_name, only_include_latest, include_pages, only_get_coverpage](const Path &filepath, FileType file_type) -> bool { + auto callback = [&chapter_list, &manga_name, only_include_latest, include_pages, only_get_coverpage](const Path &filepath, FileType file_type, time_t last_modified_seconds) -> bool { if(file_type != FileType::DIRECTORY) return true; @@ -130,8 +130,11 @@ namespace QuickMedia { } } - if(!only_get_coverpage) - file_get_last_modified_time_seconds(filepath.data.c_str(), &local_manga_chapter.modified_time_seconds); + if(!only_get_coverpage) { + if(last_modified_seconds == 0) + file_get_last_modified_time_seconds(filepath.data.c_str(), &last_modified_seconds); + local_manga_chapter.modified_time_seconds = last_modified_seconds; + } chapter_list.push_back(std::move(local_manga_chapter)); return only_include_latest ? false : true; @@ -147,16 +150,20 @@ namespace QuickMedia { static std::vector<LocalManga> get_manga_in_directory(const Path &directory, bool only_get_coverpage) { std::vector<LocalManga> manga_list; - auto callback = [&manga_list, only_get_coverpage](const Path &filepath, FileType file_type) -> bool { + auto callback = [&manga_list, only_get_coverpage](const Path &filepath, FileType file_type, time_t last_modified_seconds) -> bool { if(file_type != FileType::DIRECTORY) return true; LocalManga local_manga; local_manga.name = filepath.filename(); local_manga.chapters = get_chapters_in_manga(local_manga.name, filepath, true, false, only_get_coverpage); - if(local_manga.chapters.empty() || !file_get_last_modified_time_seconds(filepath.data.c_str(), &local_manga.modified_time_seconds)) + if(local_manga.chapters.empty()) return true; + if(last_modified_seconds == 0) + file_get_last_modified_time_seconds(filepath.data.c_str(), &last_modified_seconds); + local_manga.modified_time_seconds = last_modified_seconds; + manga_list.push_back(std::move(local_manga)); return true; }; diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index c4aa076..1b43b2d 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -5102,7 +5102,7 @@ namespace QuickMedia { //Path filter_cache_path = get_storage_dir().join("matrix").join("filter"); //remove(filter_cache_path.data.c_str()); - for_files_in_dir(get_cache_dir().join("matrix").join("events"), [](const Path &filepath, FileType) { + for_files_in_dir(get_cache_dir().join("matrix").join("events"), [](const Path &filepath, FileType, time_t) { remove(filepath.data.c_str()); return true; }); |