aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-02-11 00:42:21 +0100
committerdec05eba <dec05eba@protonmail.com>2022-02-11 00:42:21 +0100
commit1f74222bf4cfadead768b095c6b3f8d422ebf84c (patch)
tree39035288edb79852cef6237f0d7ab8ea146cf218 /src/plugins
parent404ac476a213164a041f0f53be30855df815aa6a (diff)
Add local-manga plugin to read local manga
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/LocalManga.cpp195
-rw-r--r--src/plugins/MangaCombined.cpp15
-rw-r--r--src/plugins/Page.cpp1
3 files changed, 210 insertions, 1 deletions
diff --git a/src/plugins/LocalManga.cpp b/src/plugins/LocalManga.cpp
new file mode 100644
index 0000000..06b5cf0
--- /dev/null
+++ b/src/plugins/LocalManga.cpp
@@ -0,0 +1,195 @@
+#include "../../plugins/LocalManga.hpp"
+#include "../../include/Notification.hpp"
+#include "../../include/Config.hpp"
+#include "../../include/Theme.hpp"
+#include "../../include/StringUtils.hpp"
+#include "../../include/Storage.hpp"
+
+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) -> bool {
+ if(get_file_type(filepath) != FileType::REGULAR)
+ return true;
+
+ std::string filname_no_ext = filepath.filename_no_ext();
+ int page_number = 0;
+ if(!to_num(filname_no_ext.c_str(), filname_no_ext.size(), page_number) || filepath.ext()[0] == '\0')
+ return true;
+
+ LocalMangaPage local_manga_page;
+ local_manga_page.path = filepath;
+ local_manga_page.number = page_number;
+ page_list.push_back(std::move(local_manga_page));
+ return true;
+ });
+
+ std::sort(page_list.begin(), page_list.end(), [](const LocalMangaPage &manga_page1, const LocalMangaPage &manga_page2) {
+ return manga_page1.number < manga_page2.number;
+ });
+ return page_list;
+ }
+
+ static std::vector<LocalMangaChapter> get_chapters_in_manga(const Path &directory) {
+ std::vector<LocalMangaChapter> chapter_list;
+ for_files_in_dir_sort_last_modified(directory, [&chapter_list](const Path &filepath) -> bool {
+ if(get_file_type(filepath) != 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;
+
+ chapter_list.push_back(std::move(local_manga_chapter));
+ return true;
+ });
+ return chapter_list;
+ }
+
+ static std::vector<LocalManga> get_manga_in_directory(const Path &directory) {
+ std::vector<LocalManga> manga_list;
+ for_files_in_dir_sort_last_modified(directory, [&manga_list](const Path &filepath) -> bool {
+ if(get_file_type(filepath) != FileType::DIRECTORY)
+ return true;
+
+ LocalManga local_manga;
+ local_manga.name = filepath.filename();
+ local_manga.chapters = get_chapters_in_manga(filepath);
+ if(local_manga.chapters.empty() || !file_get_last_modified_time_seconds(filepath.data.c_str(), &local_manga.modified_time_seconds))
+ return true;
+
+ manga_list.push_back(std::move(local_manga));
+ return true;
+ });
+ 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);
+ 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;
+ body_item->thumbnail_is_local = true;
+ body_item->thumbnail_size = {101, 141};
+ return body_item;
+ }
+
+ 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));
+ }
+ return SearchResult::OK;
+ }
+
+ PluginResult LocalMangaSearchPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) {
+ if(get_config().local_manga_directory.empty()) {
+ show_notification("QuickMedia", "local_manga_directory config is not set", Urgency::CRITICAL);
+ return PluginResult::OK;
+ }
+
+ if(get_file_type(get_config().local_manga_directory) != FileType::DIRECTORY) {
+ show_notification("QuickMedia", "local_manga_directory config is not set to a valid directory", Urgency::CRITICAL);
+ return PluginResult::OK;
+ }
+
+ Path manga_url = Path(get_config().local_manga_directory).join(args.url);
+ std::vector<LocalMangaChapter> chapters = get_chapters_in_manga(manga_url);
+
+ const time_t time_now = time(nullptr);
+ BodyItems chapters_items;
+
+ 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_color(get_theme().faded_text_color);
+ chapters_items.push_back(std::move(body_item));
+ }
+
+ 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);
+ 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();
+
+ if(get_config().local_manga_directory.empty()) {
+ show_notification("QuickMedia", "local_manga_directory config is not set", Urgency::CRITICAL);
+ return PluginResult::OK;
+ }
+
+ if(get_file_type(get_config().local_manga_directory) != FileType::DIRECTORY) {
+ show_notification("QuickMedia", "local_manga_directory config is not set to a valid directory", Urgency::CRITICAL);
+ return PluginResult::OK;
+ }
+
+ manga_list = get_manga_in_directory(get_config().local_manga_directory);
+
+ 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));
+ }
+
+ return PluginResult::OK;
+ }
+
+ PluginResult LocalMangaChaptersPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) {
+ if(get_config().local_manga_directory.empty()) {
+ show_notification("QuickMedia", "local_manga_directory config is not set", Urgency::CRITICAL);
+ return PluginResult::OK;
+ }
+
+ if(get_file_type(get_config().local_manga_directory) != FileType::DIRECTORY) {
+ show_notification("QuickMedia", "local_manga_directory config is not set to a valid directory", Urgency::CRITICAL);
+ return PluginResult::OK;
+ }
+
+ Path chapter_url = Path(get_config().local_manga_directory).join(content_url).join(args.url);
+ std::vector<LocalMangaPage> pages = get_images_in_manga(chapter_url);
+ result_tabs.push_back(Tab{nullptr, std::make_unique<LocalMangaImagesPage>(program, content_title, args.title, args.url, thumbnail_url, std::move(pages)), nullptr});
+ return PluginResult::OK;
+ }
+
+ bool LocalMangaChaptersPage::extract_id_from_url(const std::string &url, std::string &manga_id) const {
+ manga_id = url;
+ return true;
+ }
+
+ ImageResult LocalMangaImagesPage::get_number_of_images(int &num_images) {
+ num_images = 0;
+ ImageResult image_result = get_image_urls_for_chapter(url);
+ if(image_result != ImageResult::OK) return image_result;
+ num_images = chapter_image_urls.size();
+ return ImageResult::OK;
+ }
+
+ ImageResult LocalMangaImagesPage::for_each_page_in_chapter(PageCallback callback) {
+ ImageResult image_result = get_image_urls_for_chapter(url);
+ if(image_result != ImageResult::OK) return image_result;
+ for(const std::string &url : chapter_image_urls) {
+ if(!callback(url))
+ break;
+ }
+ return ImageResult::OK;
+ }
+
+ ImageResult LocalMangaImagesPage::get_image_urls_for_chapter(const std::string&) {
+ if(!chapter_image_urls.empty())
+ return ImageResult::OK;
+
+ for(const LocalMangaPage &local_manga_page : pages) {
+ chapter_image_urls.push_back(local_manga_page.path.data);
+ }
+ return ImageResult::OK;
+ }
+} \ No newline at end of file
diff --git a/src/plugins/MangaCombined.cpp b/src/plugins/MangaCombined.cpp
index bc4043f..fca5705 100644
--- a/src/plugins/MangaCombined.cpp
+++ b/src/plugins/MangaCombined.cpp
@@ -4,7 +4,7 @@ namespace QuickMedia {
static const int SEARCH_TIMEOUT_MILLISECONDS = 5000;
MangaCombinedSearchPage::MangaCombinedSearchPage(Program *program, std::vector<MangaPlugin> search_pages) :
- Page(program), search_pages(std::move(search_pages))
+ LazyFetchPage(program), search_pages(std::move(search_pages))
{
}
@@ -125,6 +125,19 @@ namespace QuickMedia {
return page->submit(args, result_tabs);
}
+ PluginResult MangaCombinedSearchPage::lazy_fetch(BodyItems&) {
+ for(MangaPlugin &manga_plugin : search_pages) {
+ if(manga_plugin.local_manga) {
+ LazyFetchPage *lazy_fetch_page = dynamic_cast<LazyFetchPage*>(manga_plugin.page.get());
+ if(lazy_fetch_page) {
+ BodyItems dummy_body_items;
+ lazy_fetch_page->lazy_fetch(dummy_body_items);
+ }
+ }
+ }
+ return PluginResult::OK;
+ }
+
void MangaCombinedSearchPage::cancel_operation() {
for(auto &search_thread : search_threads) {
search_thread.second.cancel();
diff --git a/src/plugins/Page.cpp b/src/plugins/Page.cpp
index 26c795c..c2e8060 100644
--- a/src/plugins/Page.cpp
+++ b/src/plugins/Page.cpp
@@ -124,6 +124,7 @@ namespace QuickMedia {
if(thumbnail_url_json.isString()) {
body_item->thumbnail_url = thumbnail_url_json.asString();
body_item->thumbnail_size = {101, 141};
+ body_item->thumbnail_is_local = local_thumbnail;
}
if(timestamp_json.isInt64()) {