From 74b98bed98aad3e70e8abe51292767ea8a7d109a Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 12 Feb 2022 04:31:44 +0100 Subject: Local-manga: show if the latest chapter of a manga has been read --- src/plugins/LocalManga.cpp | 86 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 8 deletions(-) (limited to 'src/plugins/LocalManga.cpp') diff --git a/src/plugins/LocalManga.cpp b/src/plugins/LocalManga.cpp index c39752b..1807ca7 100644 --- a/src/plugins/LocalManga.cpp +++ b/src/plugins/LocalManga.cpp @@ -4,7 +4,8 @@ #include "../../include/Theme.hpp" #include "../../include/StringUtils.hpp" #include "../../include/Storage.hpp" -#include +#include "../../external/cppcodec/base64_url.hpp" +#include namespace QuickMedia { // Pages are sorted from 1.png to n.png @@ -80,8 +81,60 @@ namespace QuickMedia { return manga_list; } - static std::shared_ptr local_manga_to_body_item(const LocalManga &local_manga, time_t time_now) { - auto body_item = BodyItem::create(local_manga.name); + static bool has_finished_reading_latest_chapter(const LocalManga &manga, const Json::Value &chapters_json) { + if(manga.chapters.empty()) + return false; + + const Json::Value &chapter_json = chapters_json[manga.chapters.front().name]; + if(!chapter_json.isObject()) + return false; + + const Json::Value ¤t_json = chapter_json["current"]; + const Json::Value &total_json = chapter_json["total"]; + if(!current_json.isInt() || !total_json.isInt()) + return false; + + return current_json.asInt() >= total_json.asInt(); + } + + // TODO: Check if this is too slow with a lot of manga. + // In that case, only save latest read chapter (or newest chapter read) + // into one file with a list of all manga. + static std::unordered_set get_manga_finished_reading(const std::vector &manga_list) { + Path local_manga_config_dir = get_storage_dir().join("local-manga"); + std::unordered_set finished_reading; + + for(const LocalManga &local_manga : manga_list) { + std::string manga_name_base64_url = cppcodec::base64_url::encode(local_manga.name); + Path manga_progress_filepath = local_manga_config_dir; + manga_progress_filepath.join(manga_name_base64_url); + + Json::Value json_root; + if(!read_file_as_json(manga_progress_filepath, json_root)) + continue; + + if(!json_root.isObject()) + continue; + + const Json::Value &chapters_json = json_root["chapters"]; + if(!chapters_json.isObject()) + continue; + + if(has_finished_reading_latest_chapter(local_manga, chapters_json)) + finished_reading.insert(local_manga.name); + } + return finished_reading; + } + + static std::shared_ptr local_manga_to_body_item(const LocalManga &local_manga, time_t time_now, bool has_finished_reading) { + std::string title; + if(has_finished_reading) + title = "[Finished reading] "; + title += local_manga.name; + + auto body_item = BodyItem::create(std::move(title)); + if(has_finished_reading) + body_item->set_title_color(mgl::Color(43, 255, 47)); 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); @@ -94,8 +147,10 @@ namespace QuickMedia { SearchResult LocalMangaSearchPage::search(const std::string &str, BodyItems &result_items) { time_t time_now = time(nullptr); for(const LocalManga &local_manga : manga_list) { - if(string_find_fuzzy_case_insensitive(local_manga.name, str)) - result_items.push_back(local_manga_to_body_item(local_manga, time_now)); + if(string_find_fuzzy_case_insensitive(local_manga.name, str)) { + const bool has_finished_reading = finished_reading_manga.find(local_manga.name) != finished_reading_manga.end(); + result_items.push_back(local_manga_to_body_item(local_manga, time_now, has_finished_reading)); + } } return SearchResult::OK; } @@ -128,13 +183,14 @@ namespace QuickMedia { auto chapters_body = create_body(); chapters_body->set_items(std::move(chapters_items)); - auto chapters_page = std::make_unique(program, args.title, args.url, args.thumbnail_url); + auto chapters_page = std::make_unique(program, args.url, args.url, args.thumbnail_url); result_tabs.push_back(Tab{std::move(chapters_body), std::move(chapters_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); return PluginResult::OK; } PluginResult LocalMangaSearchPage::lazy_fetch(BodyItems &result_items) { manga_list.clear(); + finished_reading_manga.clear(); if(get_config().local_manga_directory.empty()) { show_notification("QuickMedia", "local_manga_directory config is not set", Urgency::CRITICAL); @@ -148,14 +204,28 @@ namespace QuickMedia { manga_list = get_manga_in_directory(get_config().local_manga_directory); + if(standalone) + finished_reading_manga = get_manga_finished_reading(manga_list); + const time_t time_now = time(nullptr); for(const LocalManga &local_manga : manga_list) { - result_items.push_back(local_manga_to_body_item(local_manga, time_now)); + const bool has_finished_reading = finished_reading_manga.find(local_manga.name) != finished_reading_manga.end(); + result_items.push_back(local_manga_to_body_item(local_manga, time_now, has_finished_reading)); } return PluginResult::OK; } + std::shared_ptr LocalMangaSearchPage::get_bookmark_body_item(BodyItem *selected_item) { + if(!selected_item) + return nullptr; + + auto body_item = BodyItem::create(selected_item->url); + body_item->url = selected_item->url; + body_item->thumbnail_url = selected_item->thumbnail_url; + return body_item; + } + static std::unordered_set get_lines_in_file(const Path &filepath) { std::unordered_set lines; @@ -208,7 +278,7 @@ namespace QuickMedia { return PluginResult::OK; } - result_tabs.push_back(Tab{nullptr, std::make_unique(program, content_title, args.title, args.url, thumbnail_url), nullptr}); + result_tabs.push_back(Tab{nullptr, std::make_unique(program, content_title, args.url, args.url, thumbnail_url), nullptr}); if(is_program_executable_by_name("automedia")) append_seen_manga_to_automedia_seen(content_url + "/" + args.url); -- cgit v1.2.3