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/QuickMedia.cpp | 38 +++++++++++++++----- src/plugins/LocalManga.cpp | 86 +++++++++++++++++++++++++++++++++++++++++----- src/plugins/Manga.cpp | 2 +- 3 files changed, 109 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index cfe4e15..656e196 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1951,6 +1951,8 @@ namespace QuickMedia { if(tabs[i].page->is_lazy_fetch_page() && static_cast(tabs[i].page.get())->reload_on_page_change()) { tab_associated_data[i].lazy_fetch_finished = false; tab_associated_data[i].fetched_page = 0; + const BodyItem *selected_item = tabs[i].body->get_selected(); + tab_associated_data[i].body_item_url_before_refresh = selected_item ? selected_item->url : ""; tabs[i].body->clear_items(); } } @@ -2165,9 +2167,11 @@ namespace QuickMedia { }); } } else if(event.key.code == mgl::Keyboard::B && event.key.control) { - auto bookmark_item = tabs[selected_tab].page->get_bookmark_body_item(); + auto bookmark_item = tabs[selected_tab].page->get_bookmark_body_item(tabs[selected_tab].body->get_selected()); + if(!bookmark_item) bookmark_item = tabs[selected_tab].body->get_selected_shared(); + if(bookmark_item) { const char *bookmark_name = tabs[selected_tab].page->get_bookmark_name(); if(bookmark_name) { @@ -2359,18 +2363,36 @@ namespace QuickMedia { } if(associated_data.fetch_status == FetchStatus::LOADING && associated_data.fetch_type == FetchType::LAZY && associated_data.fetch_future.ready()) { + LazyFetchPage *lazy_fetch_page = static_cast(tabs[i].page.get()); + associated_data.lazy_fetch_finished = true; FetchResult fetch_result = associated_data.fetch_future.get(); tabs[i].body->set_items(std::move(fetch_result.body_items)); - if(tabs[i].search_bar && tabs[i].page->search_is_filter()) tabs[i].body->filter_search_fuzzy(tabs[i].search_bar->get_text()); - if(tabs[i].body->attach_side == AttachSide::TOP) { - tabs[i].body->select_first_item(); + + if(tabs[i].search_bar && tabs[i].page->search_is_filter()) { + tabs[i].body->filter_search_fuzzy(tabs[i].search_bar->get_text()); } - if(tabs[i].body->attach_side == AttachSide::BOTTOM) { - tabs[i].body->reverse_items(); - tabs[i].body->select_last_item(); + + if(lazy_fetch_page->reseek_to_body_item_by_url()) { + const auto &tab_ass = tab_associated_data[i]; + const int item_index = tabs[i].body->find_item_index([&tab_ass](const std::shared_ptr &item) { + return item->visible && item->url == tab_ass.body_item_url_before_refresh; + }); + + if(item_index != -1) + tabs[i].body->set_selected_item(item_index); + } else { + if(tabs[i].body->attach_side == AttachSide::TOP) { + tabs[i].body->select_first_item(); + } + if(tabs[i].body->attach_side == AttachSide::BOTTOM) { + tabs[i].body->reverse_items(); + tabs[i].body->select_last_item(); + } } - LazyFetchPage *lazy_fetch_page = static_cast(tabs[i].page.get()); + + tab_associated_data[i].body_item_url_before_refresh.clear(); + if(fetch_result.result != PluginResult::OK) associated_data.search_result_text.set_string("Failed to fetch page!"); else if(tabs[i].body->get_num_items() == 0 && !lazy_fetch_page->lazy_fetch_is_loader()) 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); diff --git a/src/plugins/Manga.cpp b/src/plugins/Manga.cpp index 4401974..e4269fe 100644 --- a/src/plugins/Manga.cpp +++ b/src/plugins/Manga.cpp @@ -21,7 +21,7 @@ namespace QuickMedia { load_manga_content_storage(get_service_name(), content_title, content_url, manga_id); } - std::shared_ptr MangaChaptersPage::get_bookmark_body_item() { + std::shared_ptr MangaChaptersPage::get_bookmark_body_item(BodyItem*) { auto body_item = BodyItem::create(content_title); body_item->url = content_url; body_item->thumbnail_url = thumbnail_url; -- cgit v1.2.3