aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-02-12 04:31:44 +0100
committerdec05eba <dec05eba@protonmail.com>2022-02-12 04:33:00 +0100
commit74b98bed98aad3e70e8abe51292767ea8a7d109a (patch)
treeea1558431137f8a1e52f4d550c0438e68e676f6f /src
parentcc445c60d4806fb462a3efc27bf8d727176f77da (diff)
Local-manga: show if the latest chapter of a manga has been read
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp38
-rw-r--r--src/plugins/LocalManga.cpp86
-rw-r--r--src/plugins/Manga.cpp2
3 files changed, 109 insertions, 17 deletions
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<LazyFetchPage*>(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<LazyFetchPage*>(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<BodyItem> &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<LazyFetchPage*>(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 <unordered_set>
+#include "../../external/cppcodec/base64_url.hpp"
+#include <json/value.h>
namespace QuickMedia {
// Pages are sorted from 1.png to n.png
@@ -80,8 +81,60 @@ namespace QuickMedia {
return manga_list;
}
- static std::shared_ptr<BodyItem> 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 &current_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<std::string> get_manga_finished_reading(const std::vector<LocalManga> &manga_list) {
+ Path local_manga_config_dir = get_storage_dir().join("local-manga");
+ std::unordered_set<std::string> finished_reading;
+
+ for(const LocalManga &local_manga : manga_list) {
+ std::string manga_name_base64_url = cppcodec::base64_url::encode<std::string>(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<BodyItem> 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<LocalMangaChaptersPage>(program, args.title, args.url, args.thumbnail_url);
+ auto chapters_page = std::make_unique<LocalMangaChaptersPage>(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<BodyItem> 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<std::string> get_lines_in_file(const Path &filepath) {
std::unordered_set<std::string> lines;
@@ -208,7 +278,7 @@ namespace QuickMedia {
return PluginResult::OK;
}
- result_tabs.push_back(Tab{nullptr, std::make_unique<LocalMangaImagesPage>(program, content_title, args.title, args.url, thumbnail_url), nullptr});
+ result_tabs.push_back(Tab{nullptr, std::make_unique<LocalMangaImagesPage>(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<BodyItem> MangaChaptersPage::get_bookmark_body_item() {
+ std::shared_ptr<BodyItem> MangaChaptersPage::get_bookmark_body_item(BodyItem*) {
auto body_item = BodyItem::create(content_title);
body_item->url = content_url;
body_item->thumbnail_url = thumbnail_url;