From ab36fbffef977b99cc03d0b77ac37bdc1b5705dc Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 14 Apr 2021 19:52:21 +0200 Subject: Rework manga plugins downloading.. preparing for parallel downloads --- include/AsyncTask.hpp | 22 ++--- include/ImageViewer.hpp | 4 +- include/QuickMedia.hpp | 4 +- plugins/Manga.hpp | 9 +- plugins/Mangadex.hpp | 7 +- plugins/Manganelo.hpp | 8 +- plugins/Manganelos.hpp | 7 +- plugins/Mangatown.hpp | 7 +- src/DownloadUtils.cpp | 6 ++ src/ImageViewer.cpp | 10 +-- src/Program.cpp | 2 +- src/QuickMedia.cpp | 218 ++++++++++++++++++++++++--------------------- src/SearchBar.cpp | 1 - src/plugins/Mangadex.cpp | 48 +++------- src/plugins/Manganelo.cpp | 41 ++------- src/plugins/Manganelos.cpp | 41 ++------- src/plugins/Mangatown.cpp | 104 +++++++++------------ 17 files changed, 207 insertions(+), 332 deletions(-) diff --git a/include/AsyncTask.hpp b/include/AsyncTask.hpp index abdc0f8..6148923 100644 --- a/include/AsyncTask.hpp +++ b/include/AsyncTask.hpp @@ -5,25 +5,17 @@ #include namespace QuickMedia { - template + template class AsyncTask { public: - using CallbackFunc = std::function; + using CallbackFunc = std::function; AsyncTask() = default; - AsyncTask(CallbackFunc callback_func) { + AsyncTask(CallbackFunc callback_func, Args&&... args) { std::promise promise; future = promise.get_future(); - thread = std::thread(&AsyncTask::thread_handler, this, std::move(promise), std::move(callback_func)); - } - - AsyncTask& operator=(CallbackFunc callback_func) { - cancel(); - std::promise promise; - future = promise.get_future(); - thread = std::thread(&AsyncTask::thread_handler, this, std::move(promise), std::move(callback_func)); - return *this; + thread = std::thread(&AsyncTask::thread_handler, this, std::move(promise), std::move(callback_func), std::forward(args)...); } AsyncTask(AsyncTask &&other) { @@ -63,12 +55,12 @@ namespace QuickMedia { } } private: - void thread_handler(std::promise &&promise, CallbackFunc callback_func) { + void thread_handler(std::promise &&promise, CallbackFunc callback_func, Args&&... args) { if constexpr(std::is_same::value) { - callback_func(); + callback_func(std::forward(args)...); promise.set_value(); } else { - promise.set_value(callback_func()); + promise.set_value(callback_func(std::forward(args)...)); } } private: diff --git a/include/ImageViewer.hpp b/include/ImageViewer.hpp index cd1bc8f..c29ea13 100644 --- a/include/ImageViewer.hpp +++ b/include/ImageViewer.hpp @@ -15,8 +15,6 @@ namespace sf { } namespace QuickMedia { - class MangaImagesPage; - enum class ImageStatus { WAITING, LOADING, @@ -46,7 +44,7 @@ namespace QuickMedia { class ImageViewer { public: - ImageViewer(sf::RenderWindow *window, MangaImagesPage *manga_images_page, const std::string &content_title, const std::string &chapter_title, int current_page, const Path &chapter_cache_dir); + ImageViewer(sf::RenderWindow *window, int num_pages, const std::string &content_title, const std::string &chapter_title, int current_page, const Path &chapter_cache_dir); ~ImageViewer(); ImageViewerAction draw(); // Returns page as 1 indexed diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 2877e0c..b387968 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -160,12 +160,14 @@ namespace QuickMedia { std::unordered_set watched_videos; AsyncTask search_suggestion_future; AsyncTask autocomplete_future; - AsyncTask image_download_future; + AsyncTask> image_download_future; std::thread image_upscale_thead; MessageQueue images_to_upscale_queue; std::vector image_upscale_status; std::string downloading_chapter_url; bool image_download_cancel = false; + std::future num_manga_pages_future; + int num_manga_pages = 0; int exit_code = 0; std::string resources_root; sf::Shader circle_mask_shader; diff --git a/plugins/Manga.hpp b/plugins/Manga.hpp index 23e8dd0..60e739b 100644 --- a/plugins/Manga.hpp +++ b/plugins/Manga.hpp @@ -19,15 +19,11 @@ namespace QuickMedia { const char* get_title() const override { return chapter_name.c_str(); } PageTypez get_type() const override { return PageTypez::MANGA_IMAGES; } - virtual ImageResult get_number_of_images(int &num_images) = 0; - virtual ImageResult for_each_page_in_chapter(PageCallback callback) = 0; + virtual ImageResult get_page_image_urls(std::vector &urls) = 0; virtual void change_chapter(std::string new_chapter_name, std::string new_url) { chapter_name = std::move(new_chapter_name); - if(url != new_url) { - url = std::move(new_url); - chapter_image_urls.clear(); - } + url = std::move(new_url); } const std::string& get_chapter_name() const { return chapter_name; } @@ -39,7 +35,6 @@ namespace QuickMedia { protected: std::string chapter_name; std::string url; - std::vector chapter_image_urls; }; class MangaChaptersPage : public TrackablePage { diff --git a/plugins/Mangadex.hpp b/plugins/Mangadex.hpp index 00da59c..e4a64d8 100644 --- a/plugins/Mangadex.hpp +++ b/plugins/Mangadex.hpp @@ -31,14 +31,9 @@ namespace QuickMedia { class MangadexImagesPage : public MangaImagesPage { public: MangadexImagesPage(Program *program, std::string manga_name, std::string chapter_name, std::string url) : MangaImagesPage(program, std::move(manga_name), std::move(chapter_name), std::move(url)) {} - - ImageResult get_number_of_images(int &num_images) override; - ImageResult for_each_page_in_chapter(PageCallback callback) override; - + ImageResult get_page_image_urls(std::vector &urls) override; const char* get_service_name() const override { return "mangadex"; } private: - // Cached - ImageResult get_image_urls_for_chapter(const std::string &url); bool save_mangadex_cookies(const std::string &url, const std::string &cookie_filepath); }; } \ No newline at end of file diff --git a/plugins/Manganelo.hpp b/plugins/Manganelo.hpp index e2b602f..4a7bfc3 100644 --- a/plugins/Manganelo.hpp +++ b/plugins/Manganelo.hpp @@ -37,13 +37,7 @@ namespace QuickMedia { class ManganeloImagesPage : public MangaImagesPage { public: ManganeloImagesPage(Program *program, std::string manga_name, std::string chapter_name, std::string url) : MangaImagesPage(program, std::move(manga_name), std::move(chapter_name), std::move(url)) {} - - ImageResult get_number_of_images(int &num_images) override; - ImageResult for_each_page_in_chapter(PageCallback callback) override; - + ImageResult get_page_image_urls(std::vector &urls) override; const char* get_service_name() const override { return "manganelo"; } - private: - // Cached - ImageResult get_image_urls_for_chapter(const std::string &url); }; } \ No newline at end of file diff --git a/plugins/Manganelos.hpp b/plugins/Manganelos.hpp index ede2712..4df0c3f 100644 --- a/plugins/Manganelos.hpp +++ b/plugins/Manganelos.hpp @@ -27,12 +27,7 @@ namespace QuickMedia { class ManganelosImagesPage : public MangaImagesPage { public: ManganelosImagesPage(Program *program, std::string manga_name, std::string chapter_name, std::string url) : MangaImagesPage(program, std::move(manga_name), std::move(chapter_name), std::move(url)) {} - - ImageResult get_number_of_images(int &num_images) override; - ImageResult for_each_page_in_chapter(PageCallback callback) override; - + ImageResult get_page_image_urls(std::vector &urls) override; const char* get_service_name() const override { return "manganelos"; } - private: - ImageResult get_image_urls_for_chapter(const std::string &url); }; } \ No newline at end of file diff --git a/plugins/Mangatown.hpp b/plugins/Mangatown.hpp index 66d9d2a..0b6c1c4 100644 --- a/plugins/Mangatown.hpp +++ b/plugins/Mangatown.hpp @@ -27,12 +27,7 @@ namespace QuickMedia { class MangatownImagesPage : public MangaImagesPage { public: MangatownImagesPage(Program *program, std::string manga_name, std::string chapter_name, std::string url) : MangaImagesPage(program, std::move(manga_name), std::move(chapter_name), std::move(url)) {} - - ImageResult get_number_of_images(int &num_images) override; - ImageResult for_each_page_in_chapter(PageCallback callback) override; - + ImageResult get_page_image_urls(std::vector &urls) override; const char* get_service_name() const override { return "mangatown"; } - private: - ImageResult get_image_urls_for_chapter(const std::string &url); }; } \ No newline at end of file diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp index 4a35640..336c154 100644 --- a/src/DownloadUtils.cpp +++ b/src/DownloadUtils.cpp @@ -142,6 +142,12 @@ namespace QuickMedia { return DownloadResult::NET_ERR; FILE *file = fdopen(read_program.read_fd, "rb"); + if(!file) { + program_clear_current_thread(); + wait_program(read_program.pid); + return DownloadResult::ERR; + } + char read_buffer[8192]; rapidjson::FileReadStream is(file, read_buffer, sizeof(read_buffer)); rapidjson::ParseResult parse_result = result.ParseStream(is); diff --git a/src/ImageViewer.cpp b/src/ImageViewer.cpp index 85db360..271d69f 100644 --- a/src/ImageViewer.cpp +++ b/src/ImageViewer.cpp @@ -3,7 +3,6 @@ #include "../include/Storage.hpp" #include "../include/SfmlFixes.hpp" #include "../include/ResourceLoader.hpp" -#include "../plugins/Manga.hpp" #include #include #include @@ -11,10 +10,10 @@ #include namespace QuickMedia { - ImageViewer::ImageViewer(sf::RenderWindow *window, MangaImagesPage *manga_images_page, const std::string &content_title, const std::string &chapter_title, int current_page, const Path &chapter_cache_dir) : + ImageViewer::ImageViewer(sf::RenderWindow *window, int num_pages, const std::string &content_title, const std::string &chapter_title, int current_page, const Path &chapter_cache_dir) : window(window), current_page(current_page), - num_pages(0), + num_pages(num_pages), content_title(content_title), chapter_title(chapter_title), chapter_cache_dir(chapter_cache_dir), @@ -22,10 +21,6 @@ namespace QuickMedia { font(FontLoader::get_font(FontLoader::FontType::LATIN)), page_text("", *FontLoader::get_font(FontLoader::FontType::LATIN), 14) { - if(manga_images_page->get_number_of_images(num_pages) != ImageResult::OK) { - show_notification("QuickMedia", "Failed to get number of images", Urgency::CRITICAL); - return; - } current_page = std::min(current_page, num_pages); image_data.resize(num_pages); page_size.resize(num_pages); @@ -148,6 +143,7 @@ namespace QuickMedia { Path image_path = chapter_cache_dir; image_path.join(page_str); + // TODO: Add delay before checking again? if(get_file_type(image_path) == FileType::REGULAR) { fprintf(stderr, "ImageViewer: Loaded page %d\n", 1 + page); diff --git a/src/Program.cpp b/src/Program.cpp index dc92f16..feb543f 100644 --- a/src/Program.cpp +++ b/src/Program.cpp @@ -153,7 +153,6 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void break; } } - program_clear_current_thread(); if(result != 0) kill(read_program.pid, SIGTERM); @@ -184,6 +183,7 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void } cleanup: + program_clear_current_thread(); close(read_program.read_fd); return result; } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 053c97f..5bdc67e 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1265,7 +1265,8 @@ namespace QuickMedia { MangaImagesPage *manga_images_page = static_cast(new_tabs[0].page.get()); window.setKeyRepeatEnabled(false); downloading_chapter_url.clear(); - while(true) { + + while(window.isOpen() && (current_page == PageType::IMAGES || current_page == PageType::IMAGES_CONTINUOUS)) { if(current_page == PageType::IMAGES) { window.setFramerateLimit(20); while(current_page == PageType::IMAGES) { @@ -1287,10 +1288,14 @@ namespace QuickMedia { idle = true; } else if(current_page == PageType::IMAGES_CONTINUOUS) { image_continuous_page(manga_images_page); - } else { - break; } } + + image_download_cancel = true; + image_download_future.cancel(); + image_download_cancel = false; + images_to_upscale_queue.clear(); + num_manga_pages = 0; window.setKeyRepeatEnabled(true); malloc_trim(0); } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::IMAGE_BOARD_THREAD) { @@ -1436,12 +1441,12 @@ namespace QuickMedia { int next_page = tab_associated_data[selected_tab].fetched_page + 1; Page *page = tabs[selected_tab].page.get(); std::string update_search_text = tab_associated_data[selected_tab].update_search_text; - tab_associated_data[selected_tab].next_page_future = [update_search_text, next_page, page]() { + tab_associated_data[selected_tab].next_page_future = AsyncTask([update_search_text, next_page, page]() { BodyItems result_items; if(page->get_page(update_search_text, next_page, result_items) != PluginResult::OK) fprintf(stderr, "Failed to get next page (page %d)\n", next_page); return result_items; - }; + }); } } else if(event.key.code == sf::Keyboard::Up || (event.key.control && event.key.code == sf::Keyboard::K)) { tabs[selected_tab].body->select_previous_item(); @@ -1528,11 +1533,11 @@ namespace QuickMedia { tab_associated_data[selected_tab].fetch_type = FetchType::LAZY; tab_associated_data[selected_tab].search_result_text.setString("Fetching page..."); LazyFetchPage *lazy_fetch_page = static_cast(tabs[selected_tab].page.get()); - tab_associated_data[selected_tab].fetch_future = [lazy_fetch_page]() { + tab_associated_data[selected_tab].fetch_future = AsyncTask([lazy_fetch_page]() { FetchResult fetch_result; fetch_result.result = lazy_fetch_page->lazy_fetch(fetch_result.body_items); return fetch_result; - }; + }); } for(size_t i = 0; i < tabs.size(); ++i) { @@ -1558,11 +1563,11 @@ namespace QuickMedia { associated_data.fetch_type = FetchType::SEARCH; associated_data.search_result_text.setString("Searching..."); Page *page = tabs[i].page.get(); - associated_data.fetch_future = [update_search_text, page]() { + associated_data.fetch_future = AsyncTask([update_search_text, page]() { FetchResult fetch_result; fetch_result.result = search_result_to_plugin_result(page->search(update_search_text, fetch_result.body_items)); return fetch_result; - }; + }); } if(associated_data.fetch_status == FetchStatus::LOADING && associated_data.fetch_type == FetchType::SEARCH && associated_data.fetch_future.ready()) { @@ -2276,7 +2281,7 @@ namespace QuickMedia { if(upscale_image_action != UpscaleImageAction::NO) { Path image_filepath_upcaled = image_path; image_filepath_upcaled.append(".upscaled"); - if(get_file_type(image_filepath_upcaled) == FileType::FILE_NOT_FOUND && image_upscale_status[image_index] == 0) + if(get_file_type(image_filepath_upcaled) == FileType::FILE_NOT_FOUND && image_index < (int)image_upscale_status.size() && image_upscale_status[image_index] == 0) upscaled_ok = false; } @@ -2311,13 +2316,29 @@ namespace QuickMedia { image_download_future.cancel(); image_download_cancel = false; + std::promise num_manga_pages_promise; + num_manga_pages_future = num_manga_pages_promise.get_future(); + Path content_cache_dir_ = content_cache_dir; - image_download_future = [images_page, content_cache_dir_, this]() { + image_download_future = AsyncTask>([images_page, content_cache_dir_, this](std::promise num_manga_pages_promise) { + std::vector page_image_urls; + if(images_page->get_page_image_urls(page_image_urls) != ImageResult::OK) { + num_manga_pages_promise.set_value(0); + if(!image_download_cancel) show_notification("QuickMedia", "Failed to fetch page images", Urgency::CRITICAL); + return; + } else { + num_manga_pages_promise.set_value(page_image_urls.size()); + image_upscale_status.resize(page_image_urls.size(), 0); + } + + if(page_image_urls.empty()) + return; + // TODO: Download images in parallel int page = 1; - images_page->for_each_page_in_chapter([content_cache_dir_, &page, images_page, this](const std::string &url) { + for(const std::string &url : page_image_urls) { if(image_download_cancel) - return false; + return; int image_index = page - 1; @@ -2334,7 +2355,7 @@ namespace QuickMedia { } if(get_file_type(image_filepath) != FileType::FILE_NOT_FOUND && upscaled_ok) - return true; + continue; std::vector extra_args; const bool is_manganelo = (strcmp(images_page->get_service_name(), "manganelo") == 0); @@ -2355,7 +2376,7 @@ namespace QuickMedia { size_t file_size = 0; if(download_to_file(url, image_filepath_tmp.data, extra_args, true) != DownloadResult::OK || (is_manganelo && file_get_size(image_filepath_tmp, &file_size) == 0 && file_size < 255)) { if(!image_download_cancel) show_notification("QuickMedia", "Failed to download image: " + url, Urgency::CRITICAL); - return true; + continue; } bool rename_immediately = true; @@ -2391,26 +2412,45 @@ namespace QuickMedia { if(rename(image_filepath_tmp.data.c_str(), image_filepath.data.c_str()) != 0) { perror(image_filepath_tmp.data.c_str()); show_notification("QuickMedia", "Failed to save image to file: " + image_filepath.data, Urgency::CRITICAL); - return true; + continue; } } + } + }, std::move(num_manga_pages_promise)); - return true; - }); - }; + sf::Event event; + PageType current_manga_page = current_page; + + while (current_page == current_manga_page && window.isOpen()) { + while(window.pollEvent(event)) { + if(event.type == sf::Event::Resized) { + window_size.x = event.size.width; + window_size.y = event.size.height; + sf::FloatRect visible_area(0, 0, window_size.x, window_size.y); + window.setView(sf::View(visible_area)); + } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) { + current_page = pop_page_stack(); + } + } + handle_window_close(); + + if(num_manga_pages_future.valid() && num_manga_pages_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + num_manga_pages = num_manga_pages_future.get(); + break; + } + + window.clear(back_color); + load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f); + load_sprite.setRotation(load_sprite_timer.getElapsedTime().asSeconds() * 400.0); + window.draw(load_sprite); + window.display(); + } } int Program::image_page(MangaImagesPage *images_page, Body *chapters_body) { int page_navigation = 0; image_download_cancel = false; - sf::Texture image_texture; - sf::Sprite image; - sf::Text error_message("", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(30 * get_ui_scale())); - error_message.setFillColor(sf::Color::White); - - bool download_in_progress = false; - content_cache_dir = get_cache_dir().join(images_page->get_service_name()).join(manga_id_base64).join(base64_encode(images_page->get_chapter_name())); if(create_directory_recursive(content_cache_dir) != 0) { show_notification("QuickMedia", "Failed to create directory: " + content_cache_dir.data, Urgency::CRITICAL); @@ -2418,20 +2458,21 @@ namespace QuickMedia { return 0; } - int num_images = 0; - if(images_page->get_number_of_images(num_images) != ImageResult::OK) { - show_notification("QuickMedia", "Failed to get number of images", Urgency::CRITICAL); - current_page = pop_page_stack(); - return 0; - } - image_index = std::min(image_index, num_images); - - if(num_images != (int)image_upscale_status.size()) - image_upscale_status.resize(num_images); + sf::Texture image_texture; + sf::Sprite image; + sf::Text error_message("", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(30 * get_ui_scale())); + error_message.setFillColor(sf::Color::White); + + bool download_in_progress = false; + sf::Event event; download_chapter_images_if_needed(images_page); + if(current_page != PageType::IMAGES || !window.isOpen()) + return 0; + + image_index = std::min(image_index, num_manga_pages); - if(image_index < num_images) { + if(image_index < num_manga_pages) { sf::String error_msg; LoadImageResult load_image_result = load_image_by_index(image_index, image_texture, error_msg); if(load_image_result == LoadImageResult::OK) @@ -2439,7 +2480,7 @@ namespace QuickMedia { else if(load_image_result == LoadImageResult::DOWNLOAD_IN_PROGRESS) download_in_progress = true; error_message.setString(error_msg); - } else if(image_index == num_images) { + } else if(image_index == num_manga_pages) { error_message.setString("End of " + images_page->get_chapter_name()); } @@ -2460,8 +2501,8 @@ namespace QuickMedia { json_chapters = Json::Value(Json::objectValue); json_chapter = Json::Value(Json::objectValue); } - json_chapter["current"] = std::min(latest_read, num_images); - json_chapter["total"] = num_images; + json_chapter["current"] = std::min(latest_read, num_manga_pages); + json_chapter["total"] = num_manga_pages; json_chapters[images_page->get_chapter_name()] = json_chapter; if(!save_json_to_file_atomic(content_storage_file, content_storage_json)) { show_notification("QuickMedia", "Failed to save manga progress", Urgency::CRITICAL); @@ -2470,8 +2511,8 @@ namespace QuickMedia { bool error = !error_message.getString().isEmpty(); bool redraw = true; - sf::Text chapter_text(images_page->manga_name + " | " + images_page->get_chapter_name() + " | Page " + std::to_string(image_index + 1) + "/" + std::to_string(num_images), *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(14 * get_ui_scale())); - if(image_index == num_images) + sf::Text chapter_text(images_page->manga_name + " | " + images_page->get_chapter_name() + " | Page " + std::to_string(image_index + 1) + "/" + std::to_string(num_manga_pages), *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(14 * get_ui_scale())); + if(image_index == num_manga_pages) chapter_text.setString(images_page->manga_name + " | " + images_page->get_chapter_name() + " | End"); chapter_text.setFillColor(sf::Color::White); sf::RectangleShape chapter_text_background; @@ -2487,10 +2528,6 @@ namespace QuickMedia { sf::Clock check_downloaded_timer; const sf::Int32 check_downloaded_timeout_ms = 500; - sf::Event event; - // Consume events sent during above call to get_number_of_images which sends a request to server which may take a while. We dont want pages to be skipped when pressing arrow up/down - while(window.pollEvent(event)) {} - malloc_trim(0); // TODO: Show to user if a certain page is missing (by checking page name (number) and checking if some are skipped) @@ -2514,10 +2551,10 @@ namespace QuickMedia { goto end_of_images_page; } } else if(event.key.code == sf::Keyboard::Down || (event.key.control && event.key.code == sf::Keyboard::J)) { - if(image_index < num_images) { + if(image_index < num_manga_pages) { ++image_index; goto end_of_images_page; - } else if(image_index == num_images && chapters_body->get_selected_item() > 0) { + } else if(image_index == num_manga_pages && chapters_body->get_selected_item() > 0) { page_navigation = 1; goto end_of_images_page; } @@ -2601,13 +2638,6 @@ namespace QuickMedia { } end_of_images_page: - if(current_page != PageType::IMAGES && current_page != PageType::IMAGES_CONTINUOUS) { - image_download_cancel = true; - image_download_future.cancel(); - image_download_cancel = false; - images_to_upscale_queue.clear(); - image_upscale_status.clear(); - } return page_navigation; } @@ -2621,17 +2651,9 @@ namespace QuickMedia { return; } - int num_images = 0; - if(images_page->get_number_of_images(num_images) != ImageResult::OK) { - show_notification("QuickMedia", "Failed to get number of images", Urgency::CRITICAL); - current_page = pop_page_stack(); - return; - } - - if(num_images != (int)image_upscale_status.size()) - image_upscale_status.resize(num_images); - download_chapter_images_if_needed(images_page); + if(current_page != PageType::IMAGES_CONTINUOUS || !window.isOpen()) + return; Json::Value &json_chapters = content_storage_json["chapters"]; Json::Value json_chapter; @@ -2650,7 +2672,7 @@ namespace QuickMedia { json_chapter = Json::Value(Json::objectValue); } - ImageViewer image_viewer(&window, images_page, images_page->manga_name, images_page->get_chapter_name(), image_index, content_cache_dir); + ImageViewer image_viewer(&window, num_manga_pages, images_page->manga_name, images_page->get_chapter_name(), image_index, content_cache_dir); json_chapter["current"] = std::min(latest_read, image_viewer.get_num_pages()); json_chapter["total"] = image_viewer.get_num_pages(); @@ -2690,14 +2712,6 @@ namespace QuickMedia { } } - if(current_page != PageType::IMAGES && current_page != PageType::IMAGES_CONTINUOUS) { - image_download_cancel = true; - image_download_future.cancel(); - image_download_cancel = false; - images_to_upscale_queue.clear(); - image_upscale_status.clear(); - } - window_size.x = window.getSize().x; window_size.y = window.getSize().y; @@ -2844,17 +2858,17 @@ namespace QuickMedia { comment_to_post = std::move(text); if(!captcha_post_id.empty() && captcha_solved_time.getElapsedTime().asSeconds() < 120) { - post_comment_future = [&post_comment]() -> bool { + post_comment_future = AsyncTask([&post_comment]() -> bool { post_comment(); return true; - }; + }); } else if(thread_page->get_pass_id().empty()) { request_new_google_captcha_challenge(); } else if(!thread_page->get_pass_id().empty()) { - post_comment_future = [&post_comment]() -> bool { + post_comment_future = AsyncTask([&post_comment]() -> bool { post_comment(); return true; - }; + }); } return true; }; @@ -2940,7 +2954,7 @@ namespace QuickMedia { load_image_future.cancel(); downloading_image = true; navigation_stage = NavigationStage::VIEWING_ATTACHED_IMAGE; - load_image_future = [&thread_body]() { + load_image_future = AsyncTask([&thread_body]() { std::string image_data; BodyItem *selected_item = thread_body->get_selected(); if(!selected_item || selected_item->attached_content_url.empty()) { @@ -2951,7 +2965,7 @@ namespace QuickMedia { image_data.clear(); } return image_data; - }; + }); } } } @@ -4025,9 +4039,9 @@ namespace QuickMedia { body_item->embedded_item_status = FetchStatus::LOADING; fetch_message_tab = MESSAGES_TAB_INDEX; // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? - fetch_message_future = [this, ¤t_room, message_event_id]() { + fetch_message_future = AsyncTask([this, ¤t_room, message_event_id]() { return FetchMessageResult{FetchMessageType::MESSAGE, matrix->get_message_by_id(current_room, message_event_id)}; - }; + }); return; } @@ -4056,9 +4070,9 @@ namespace QuickMedia { event_data->status = FetchStatus::LOADING; fetch_message_tab = PINNED_TAB_INDEX; // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? - fetch_message_future = [this, ¤t_room, message_event_id]() { + fetch_message_future = AsyncTask([this, ¤t_room, message_event_id]() { return FetchMessageResult{FetchMessageType::MESSAGE, matrix->get_message_by_id(current_room, message_event_id)}; - }; + }); }; // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. @@ -4080,11 +4094,11 @@ namespace QuickMedia { RoomData *room = current_room; std::string event_id = read_message->event_id; int64_t event_timestamp = read_message->timestamp; - set_read_marker_future = [this, room, event_id, event_timestamp]() mutable { + set_read_marker_future = AsyncTask([this, room, event_id, event_timestamp]() mutable { if(matrix->set_read_marker(room, event_id, event_timestamp) != PluginResult::OK) { fprintf(stderr, "Warning: failed to set read marker to %s\n", event_id.c_str()); } - }; + }); } } @@ -4093,10 +4107,10 @@ namespace QuickMedia { fetch_message = message; message->user->resolve_state = UserResolveState::RESOLVING; std::string user_id = message->user->user_id; - fetch_message_future = [this, ¤t_room, user_id]() { + fetch_message_future = AsyncTask([this, ¤t_room, user_id]() { matrix->update_user_with_latest_state(current_room, user_id); return FetchMessageResult{FetchMessageType::USER_UPDATE, nullptr}; - }; + }); return; } else if(message->user->resolve_state == UserResolveState::RESOLVING) { return; @@ -4119,9 +4133,9 @@ namespace QuickMedia { body_item->embedded_item_status = FetchStatus::LOADING; fetch_message_tab = MESSAGES_TAB_INDEX; // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? - fetch_message_future = [this, ¤t_room, message_event_id]() { + fetch_message_future = AsyncTask([this, ¤t_room, message_event_id]() { return FetchMessageResult{FetchMessageType::MESSAGE, matrix->get_message_by_id(current_room, message_event_id)}; - }; + }); }; tabs[MESSAGES_TAB_INDEX].body->body_item_merge_handler = [](BodyItem *prev_item, BodyItem *this_item) { @@ -4164,23 +4178,23 @@ namespace QuickMedia { auto fetch_more_previous_messages_if_needed = [this, &tabs, ¤t_room, &fetched_enough_messages, &previous_messages_future]() { if(!fetched_enough_messages && !previous_messages_future.valid()) { if(tabs[MESSAGES_TAB_INDEX].body->items.size() < 30) { - previous_messages_future = [this, ¤t_room]() { + previous_messages_future = AsyncTask([this, ¤t_room]() { Messages messages; if(matrix->get_previous_room_messages(current_room, messages) != PluginResult::OK) fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", current_room->id.c_str()); return messages; - }; + }); } } }; if(!matrix->is_initial_sync_finished()) { - previous_messages_future = [this, ¤t_room]() { + previous_messages_future = AsyncTask([this, ¤t_room]() { Messages messages; if(matrix->get_previous_room_messages(current_room, messages, true) != PluginResult::OK) fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", current_room->id.c_str()); return messages; - }; + }); } sf::RectangleShape more_messages_below_rect; @@ -4428,10 +4442,10 @@ namespace QuickMedia { //update_ } else { // TODO: Race condition? maybe use matrix /members instead which has a since parameter to make the members list match current sync - fetch_users_future = [this, ¤t_room]() { + fetch_users_future = AsyncTask([this, ¤t_room]() { matrix->update_room_users(current_room); return true; - }; + }); } float tab_shade_height = 0.0f; @@ -4531,12 +4545,12 @@ namespace QuickMedia { } if(hit_top && !previous_messages_future.valid() && selected_tab == MESSAGES_TAB_INDEX && current_room) { gradient_inc = 0; - previous_messages_future = [this, ¤t_room]() { + previous_messages_future = AsyncTask([this, ¤t_room]() { Messages messages; if(matrix->get_previous_room_messages(current_room, messages) != PluginResult::OK) fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", current_room->id.c_str()); return messages; - }; + }); } } else if(event.key.code == sf::Keyboard::Down || (event.key.control && event.key.code == sf::Keyboard::J)) { tabs[selected_tab].body.get()->select_next_item(); @@ -5057,7 +5071,9 @@ namespace QuickMedia { const float margin = 5.0f; const float replying_to_text_height = replying_to_text.getLocalBounds().height + margin; - const float item_height = std::min(body_size.y - replying_to_text_height - margin, tabs[MESSAGES_TAB_INDEX].body->get_item_height(currently_operating_on_item.get(), body_size.x) + margin); + float item_height = std::min(body_size.y - replying_to_text_height - margin, tabs[MESSAGES_TAB_INDEX].body->get_item_height(currently_operating_on_item.get(), body_size.x) + margin); + if(item_height < 0.0f) + item_height = 0.0f; sf::RectangleShape overlay(sf::Vector2f(window_size.x, window_size.y - chat_input_height_full)); overlay.setFillColor(sf::Color(0, 0, 0, 240)); @@ -5066,8 +5082,8 @@ namespace QuickMedia { sf::Vector2f body_item_pos(body_pos.x, window_size.y - chat_input_height_full - item_height); sf::Vector2f body_item_size(body_size.x, item_height); - sf::RectangleShape item_background(sf::Vector2f(window_size.x, body_item_size.y + replying_to_text_height + margin + body_item_size.y)); - item_background.setPosition(sf::Vector2f(0.0f, body_item_pos.y - replying_to_text_height - margin)); + sf::RectangleShape item_background(sf::Vector2f(window_size.x, body_item_size.y + chat_input_height_full + replying_to_text_height + margin)); + item_background.setPosition(sf::Vector2f(0.0f, window_size.y - (body_item_size.y + chat_input_height_full + replying_to_text_height + margin))); item_background.setFillColor(back_color); window.draw(item_background); diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index 7c20568..b20c3b0 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -130,7 +130,6 @@ namespace QuickMedia { if(backspace_pressed) timeout = 750; if(updated_search && elapsed_time >= timeout) { - fprintf(stderr, "update search!\n"); updated_search = false; auto u8 = text.getString().toUtf8(); std::string *u8_str = (std::string*)&u8; diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp index 59126cb..44c9762 100644 --- a/src/plugins/Mangadex.cpp +++ b/src/plugins/Mangadex.cpp @@ -219,29 +219,7 @@ namespace QuickMedia { return true; } - ImageResult MangadexImagesPage::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; - } - - bool MangadexImagesPage::save_mangadex_cookies(const std::string &url, const std::string &cookie_filepath) { - CommandArg cookie_arg = { "-c", cookie_filepath }; - std::string server_response; - if(download_to_string(url, server_response, {std::move(cookie_arg)}, true) != DownloadResult::OK) - return false; - - return true; - } - - ImageResult MangadexImagesPage::get_image_urls_for_chapter(const std::string &url) { - if(!chapter_image_urls.empty()) - return ImageResult::OK; - + ImageResult MangadexImagesPage::get_page_image_urls(std::vector &urls) { std::string cookie_filepath; if(!get_cookie_filepath(cookie_filepath)) return ImageResult::ERR; @@ -283,28 +261,22 @@ namespace QuickMedia { continue; std::string image_url = server + chapter_hash_str + "/" + image_name.asCString(); - chapter_image_urls.push_back(std::move(image_url)); + urls.push_back(std::move(image_url)); } } - if(chapter_image_urls.empty()) + if(urls.empty()) return ImageResult::ERR; + return ImageResult::OK; } - ImageResult MangadexImagesPage::for_each_page_in_chapter(PageCallback callback) { - std::vector image_urls; - ImageResult image_result = get_image_urls_for_chapter(url); - if(image_result != ImageResult::OK) - return image_result; - - image_urls = chapter_image_urls; - - for(const std::string &url : image_urls) { - if(!callback(url)) - break; - } + bool MangadexImagesPage::save_mangadex_cookies(const std::string &url, const std::string &cookie_filepath) { + CommandArg cookie_arg = { "-c", cookie_filepath }; + std::string server_response; + if(download_to_string(url, server_response, {std::move(cookie_arg)}, true) != DownloadResult::OK) + return false; - return ImageResult::OK; + return true; } } diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp index 8ce9e01..b67acb2 100644 --- a/src/plugins/Manganelo.cpp +++ b/src/plugins/Manganelo.cpp @@ -226,36 +226,7 @@ namespace QuickMedia { return PluginResult::OK; } - ImageResult ManganeloImagesPage::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 ManganeloImagesPage::for_each_page_in_chapter(PageCallback callback) { - std::vector image_urls; - ImageResult image_result = get_image_urls_for_chapter(url); - if(image_result != ImageResult::OK) - return image_result; - - image_urls = chapter_image_urls; - - for(const std::string &url : image_urls) { - if(!callback(url)) - break; - } - - return ImageResult::OK; - } - - ImageResult ManganeloImagesPage::get_image_urls_for_chapter(const std::string &url) { - if(!chapter_image_urls.empty()) - return ImageResult::OK; - + ImageResult ManganeloImagesPage::get_page_image_urls(std::vector &urls) { std::string website_data; if(download_to_string(url, website_data, {}, true) != DownloadResult::OK) return ImageResult::NET_ERR; @@ -273,16 +244,16 @@ namespace QuickMedia { std::string image_url = strip(src); urls->push_back(std::move(image_url)); } - }, &chapter_image_urls); + }, &urls); cleanup: quickmedia_html_search_deinit(&html_search); - if(result != 0) { - chapter_image_urls.clear(); + if(result != 0) return ImageResult::ERR; - } - if(chapter_image_urls.empty()) + + if(urls.empty()) return ImageResult::ERR; + return ImageResult::OK; } } \ No newline at end of file diff --git a/src/plugins/Manganelos.cpp b/src/plugins/Manganelos.cpp index 9ab0915..04d9ca2 100644 --- a/src/plugins/Manganelos.cpp +++ b/src/plugins/Manganelos.cpp @@ -117,36 +117,7 @@ namespace QuickMedia { return true; } - ImageResult ManganelosImagesPage::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 ManganelosImagesPage::for_each_page_in_chapter(PageCallback callback) { - std::vector image_urls; - ImageResult image_result = get_image_urls_for_chapter(url); - if(image_result != ImageResult::OK) - return image_result; - - image_urls = chapter_image_urls; - - for(const std::string &url : image_urls) { - if(!callback(url)) - break; - } - - return ImageResult::OK; - } - - ImageResult ManganelosImagesPage::get_image_urls_for_chapter(const std::string &url) { - if(!chapter_image_urls.empty()) - return ImageResult::OK; - + ImageResult ManganelosImagesPage::get_page_image_urls(std::vector &urls) { std::string website_data; if(download_to_string(url, website_data, {}, true) != DownloadResult::OK) return ImageResult::NET_ERR; @@ -168,16 +139,16 @@ namespace QuickMedia { return true; }); } - }, &chapter_image_urls); + }, &urls); cleanup: quickmedia_html_search_deinit(&html_search); - if(result != 0) { - chapter_image_urls.clear(); + if(result != 0) return ImageResult::ERR; - } - if(chapter_image_urls.empty()) + + if(urls.empty()) return ImageResult::ERR; + return ImageResult::OK; } } \ No newline at end of file diff --git a/src/plugins/Mangatown.cpp b/src/plugins/Mangatown.cpp index 04a1049..4db1ebc 100644 --- a/src/plugins/Mangatown.cpp +++ b/src/plugins/Mangatown.cpp @@ -139,65 +139,30 @@ namespace QuickMedia { return true; } - ImageResult MangatownImagesPage::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 MangatownImagesPage::for_each_page_in_chapter(PageCallback callback) { - int num_pages; - ImageResult image_result = get_number_of_images(num_pages); - if(image_result != ImageResult::OK) - return image_result; - - for(const std::string &full_url : chapter_image_urls) { - std::string image_src; - std::string website_data; - if(download_to_string_cache(full_url, website_data, {}, true) != DownloadResult::OK) - break; - - QuickMediaHtmlSearch html_search; - int result = quickmedia_html_search_init(&html_search, website_data.c_str()); - if(result != 0) - goto cleanup; - - result = quickmedia_html_find_nodes_xpath(&html_search, "//div[id='viewer']//img", - [](QuickMediaHtmlNode *node, void *userdata) { - std::string *image_src = (std::string*)userdata; - const char *src = quickmedia_html_node_get_attribute_value(node, "src"); - if(src && strstr(src, "/store/manga/")) { - if(strncmp(src, "//", 2) == 0) - *image_src = strip(src + 2); - else - *image_src = strip(src); - } - }, &image_src); - - cleanup: - quickmedia_html_search_deinit(&html_search); - - if(result != 0) - return ImageResult::ERR; - - if(image_src.empty()) - break; - - if(!callback(image_src)) - break; - } - - return ImageResult::OK; + // First page = 0 + static std::string first_image_url_to_page_image_url(const std::string &image_url, int page) { + size_t slash_index = image_url.rfind('/'); + if(slash_index == std::string::npos) + return ""; + + size_t dot_index = image_url.rfind('.'); + if(dot_index == std::string::npos || dot_index <= slash_index) + return ""; + + std::string filename_without_ext = image_url.substr(slash_index + 1, dot_index - (slash_index + 1)); + if(!filename_without_ext.empty() && filename_without_ext.back() == '1') + ++page; + + std::string page_str = std::to_string(page); + if(page_str.size() > filename_without_ext.size()) + return ""; + + filename_without_ext.replace(filename_without_ext.size() - page_str.size(), page_str.size(), page_str); + return image_url.substr(0, slash_index) + '/' + filename_without_ext + image_url.substr(dot_index); } - ImageResult MangatownImagesPage::get_image_urls_for_chapter(const std::string &url) { - if(!chapter_image_urls.empty()) - return ImageResult::OK; - + ImageResult MangatownImagesPage::get_page_image_urls(std::vector &urls) { + std::string image_src; int num_pages = 0; std::string website_data; @@ -209,6 +174,21 @@ namespace QuickMedia { if(result != 0) goto cleanup; + result = quickmedia_html_find_nodes_xpath(&html_search, "//div[id='viewer']//img", + [](QuickMediaHtmlNode *node, void *userdata) { + std::string *image_src = (std::string*)userdata; + const char *src = quickmedia_html_node_get_attribute_value(node, "src"); + if(src && strstr(src, "/store/manga/")) { + if(strncmp(src, "//", 2) == 0) + *image_src = strip(src + 2); + else + *image_src = strip(src); + } + }, &image_src); + + if(result != 0 || image_src.empty()) + goto cleanup; + result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='page_select']//option", [](QuickMediaHtmlNode *node, void *userdata) { int *last_num_pages = (int*)userdata; @@ -225,15 +205,13 @@ namespace QuickMedia { cleanup: quickmedia_html_search_deinit(&html_search); - if(result != 0) { - chapter_image_urls.clear(); - return ImageResult::ERR; - } - if(num_pages == 0) + if(result != 0 || image_src.empty() || num_pages == 0) return ImageResult::ERR; + for(int i = 0; i < num_pages; ++i) { - chapter_image_urls.push_back(url + std::to_string(1 + i) + ".html"); + urls.push_back(first_image_url_to_page_image_url(image_src, i)); } + return ImageResult::OK; } } \ No newline at end of file -- cgit v1.2.3