aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/LocalManga.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-02-12 15:42:16 +0100
committerdec05eba <dec05eba@protonmail.com>2022-02-12 16:23:09 +0100
commit2f9ae9e9462a5a366461f20b4d0c2f4b80ef1b68 (patch)
tree165a71177182aec90f8d8fe888ffdbb7d801abcf /src/plugins/LocalManga.cpp
parenta8d597d59e347a80b109060ec8c7a88827487f57 (diff)
Local-manga: improve loading of page when using slow medium
Especially when using NFS. Only get the latest chapter when needed and cache link to the cover page.
Diffstat (limited to 'src/plugins/LocalManga.cpp')
-rw-r--r--src/plugins/LocalManga.cpp118
1 files changed, 100 insertions, 18 deletions
diff --git a/src/plugins/LocalManga.cpp b/src/plugins/LocalManga.cpp
index 8344527..dc98639 100644
--- a/src/plugins/LocalManga.cpp
+++ b/src/plugins/LocalManga.cpp
@@ -7,15 +7,75 @@
#include "../../external/cppcodec/base64_url.hpp"
#include "../../include/QuickMedia.hpp"
#include <json/value.h>
+#include <dirent.h>
namespace QuickMedia {
+ // This is needed because the manga may be stored on NFS.
+ // TODO: Remove once body items can async load when visible on screen
+ class CoverPageLinkCache {
+ public:
+ static CoverPageLinkCache& get_instance() {
+ static CoverPageLinkCache instance;
+ Path dir = get_cache_dir().join("thumbnail-link");
+ if(create_directory_recursive(dir) != 0) {
+ show_notification("QuickMedia", "Failed to create directory: " + dir.data, Urgency::CRITICAL);
+ abort();
+ }
+
+ instance.cache_filepath = dir.join("local-manga");
+ std::string file_content;
+ if(get_file_type(instance.cache_filepath) == FileType::REGULAR && file_get_content(instance.cache_filepath, file_content) != 0) {
+ show_notification("QuickMedia", "Failed to load local manga thumbnail link cache", Urgency::CRITICAL);
+ abort();
+ }
+
+ std::unordered_map<std::string, std::string> manga_map;
+ string_split(file_content, '\n', [&manga_map](const char *str_part, size_t size) {
+ const void *space_p = memchr(str_part, ' ', size);
+ if(!space_p)
+ return true;
+
+ std::string manga_name_base64(str_part, (const char*)space_p - str_part);
+ std::string cover_filepath((const char*)space_p + 1, str_part + size - ((const char*)space_p + 1));
+ manga_map[std::move(manga_name_base64)] = std::move(cover_filepath);
+ return true;
+ });
+
+ instance.manga_name_base64_to_cover_filepath_map = std::move(manga_map);
+ return instance;
+ }
+
+ std::string get_cached_cover_page_link_for_manga(const std::string &manga_name) {
+ std::string manga_name_base64_url = cppcodec::base64_url::encode<std::string>(manga_name);
+ auto it = manga_name_base64_to_cover_filepath_map.find(manga_name_base64_url);
+ if(it == manga_name_base64_to_cover_filepath_map.end())
+ return "";
+ return it->second;
+ }
+
+ void add_manga_to_thumbnail_link_cache(const std::string &manga_name, const std::string &cover_filepath) {
+ std::string manga_name_base64_url = cppcodec::base64_url::encode<std::string>(manga_name);
+ manga_name_base64_to_cover_filepath_map[manga_name_base64_url] = cover_filepath;
+
+ FILE *file = fopen(cache_filepath.data.c_str(), "ab");
+ if(file) {
+ std::string line = manga_name_base64_url + " " + cover_filepath + "\n";
+ fwrite(line.data(), 1, line.size(), file);
+ fclose(file);
+ }
+ }
+ private:
+ std::unordered_map<std::string, std::string> manga_name_base64_to_cover_filepath_map;
+ Path cache_filepath;
+ };
+
static const mgl::Color finished_reading_color = mgl::Color(43, 255, 47);
// 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) -> bool {
- if(get_file_type(filepath) != FileType::REGULAR)
+ for_files_in_dir(directory, [&page_list](const Path &filepath, FileType file_type) -> bool {
+ if(file_type != FileType::REGULAR)
return true;
std::string filname_no_ext = filepath.filename_no_ext();
@@ -36,20 +96,33 @@ namespace QuickMedia {
return page_list;
}
- static std::vector<LocalMangaChapter> get_chapters_in_manga(const Path &directory) {
+ 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](const Path &filepath) -> bool {
- if(get_file_type(filepath) != FileType::DIRECTORY)
+ auto callback = [&chapter_list, &manga_name, only_include_latest, include_pages, only_get_coverpage](const Path &filepath, FileType file_type) -> bool {
+ if(file_type != FileType::DIRECTORY)
return true;
LocalMangaChapter local_manga_chapter;
local_manga_chapter.name = filepath.filename();
- local_manga_chapter.pages = get_images_in_manga(filepath);
- if(local_manga_chapter.pages.empty() || !file_get_last_modified_time_seconds(filepath.data.c_str(), &local_manga_chapter.modified_time_seconds))
- return true;
+ if(include_pages) {
+ local_manga_chapter.pages = get_images_in_manga(filepath);
+ if(local_manga_chapter.pages.empty())
+ return true;
+ } else if(only_get_coverpage) {
+ std::string cover_page = CoverPageLinkCache::get_instance().get_cached_cover_page_link_for_manga(manga_name);
+ if(!cover_page.empty()) {
+ local_manga_chapter.pages.push_back({ cover_page, 1 });
+ } else {
+ local_manga_chapter.pages = get_images_in_manga(filepath);
+ if(local_manga_chapter.pages.empty())
+ return true;
+
+ CoverPageLinkCache::get_instance().add_manga_to_thumbnail_link_cache(manga_name, local_manga_chapter.pages.front().path.data);
+ }
+ }
chapter_list.push_back(std::move(local_manga_chapter));
- return true;
+ return only_include_latest ? false : true;
};
if(get_config().local_manga_sort_chapters_by_name)
@@ -60,15 +133,15 @@ namespace QuickMedia {
return chapter_list;
}
- static std::vector<LocalManga> get_manga_in_directory(const Path &directory) {
+ static std::vector<LocalManga> get_manga_in_directory(const Path &directory, bool only_get_coverpage) {
std::vector<LocalManga> manga_list;
- auto callback = [&manga_list](const Path &filepath) -> bool {
- if(get_file_type(filepath) != FileType::DIRECTORY)
+ auto callback = [&manga_list, only_get_coverpage](const Path &filepath, FileType file_type) -> bool {
+ if(file_type != FileType::DIRECTORY)
return true;
LocalManga local_manga;
local_manga.name = filepath.filename();
- local_manga.chapters = get_chapters_in_manga(filepath);
+ 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))
return true;
@@ -102,7 +175,7 @@ namespace QuickMedia {
}
Path manga_url = Path(get_config().local_manga_directory).join(manga_name);
- std::vector<LocalMangaChapter> chapters = get_chapters_in_manga(manga_url);
+ std::vector<LocalMangaChapter> chapters = get_chapters_in_manga(manga_name, manga_url, true, true);
if(chapters.empty() || chapters.front().pages.empty())
return false;
@@ -229,7 +302,8 @@ namespace QuickMedia {
body_item->url = local_manga.name;
body_item->set_description("Latest chapter: " + local_manga.chapters.front().name + "\nUpdated " + seconds_to_relative_time_str(time_now - local_manga.modified_time_seconds));
body_item->set_description_color(get_theme().faded_text_color);
- body_item->thumbnail_url = local_manga.chapters.back().pages.front().path.data;
+ if(!local_manga.chapters.back().pages.empty())
+ body_item->thumbnail_url = local_manga.chapters.back().pages.front().path.data;
body_item->thumbnail_is_local = true;
body_item->thumbnail_size = {101, 141};
return body_item;
@@ -258,7 +332,15 @@ namespace QuickMedia {
}
Path manga_url = Path(get_config().local_manga_directory).join(args.url);
- std::vector<LocalMangaChapter> chapters = get_chapters_in_manga(manga_url);
+ std::vector<LocalMangaChapter> chapters = get_chapters_in_manga(args.url, manga_url, false, false);
+
+ auto manga_it = std::find_if(manga_list.begin(), manga_list.end(), [&args](const LocalManga &local_manga) {
+ return local_manga.name == args.url;
+ });
+ if(manga_it == manga_list.end()) {
+ show_notification("QuickMedia", "The selected manga seems to have been removed?", Urgency::CRITICAL);
+ return PluginResult::OK;
+ }
const time_t time_now = time(nullptr);
BodyItems chapters_items;
@@ -266,7 +348,7 @@ namespace QuickMedia {
for(const LocalMangaChapter &local_manga_chapter : chapters) {
auto body_item = BodyItem::create(local_manga_chapter.name);
body_item->url = local_manga_chapter.name;
- body_item->set_description("Updated " + seconds_to_relative_time_str(time_now - local_manga_chapter.modified_time_seconds));
+ body_item->set_description("Updated " + seconds_to_relative_time_str(time_now - manga_it->modified_time_seconds));
body_item->set_description_color(get_theme().faded_text_color);
chapters_items.push_back(std::move(body_item));
}
@@ -293,7 +375,7 @@ namespace QuickMedia {
return PluginResult::OK;
}
- manga_list = get_manga_in_directory(get_config().local_manga_directory);
+ manga_list = get_manga_in_directory(get_config().local_manga_directory, true);
if(standalone)
finished_reading_manga = get_manga_finished_reading(manga_list);