From e62b707603ec00fc5192bf702b4bca0ed77501e6 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 26 Aug 2021 01:10:40 +0200 Subject: Add ctrl+b to bookmark manga --- src/Body.cpp | 1 + src/QuickMedia.cpp | 165 +++++++++++++++++++++++++++++++++++++----------- src/Storage.cpp | 3 + src/StringUtils.cpp | 24 +++++++ src/plugins/Page.cpp | 42 ++++++++++++ src/plugins/Youtube.cpp | 24 ------- 6 files changed, 198 insertions(+), 61 deletions(-) (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp index f9a3717..10f0d86 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -378,6 +378,7 @@ namespace QuickMedia { for(auto it = items.begin(), end = items.end(); it != end; ++it) { if(callback(*it)) { items.erase(it); + clamp_selection(); return true; } } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 1bc260c..acc1770 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -293,6 +293,7 @@ namespace QuickMedia { return PluginResult::OK; } bool reload_on_page_change() override { return true; } + const char* get_bookmark_name() const override { return search_page->get_bookmark_name(); } private: Page *search_page; HistoryType history_type; @@ -1118,51 +1119,81 @@ namespace QuickMedia { }); tabs.push_back(Tab{std::move(pipe_body), std::make_unique(this, "Select plugin to launch"), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "manganelo") == 0) { - tabs.push_back(Tab{create_body(false, true), std::make_unique(this), create_search_bar("Search...", 400)}); + auto search_page = std::make_unique(this); + + tabs.push_back(Tab{create_body(), std::make_unique(this, search_page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + start_tab_index = 1; } else if(strcmp(plugin_name, "manganelos") == 0) { auto search_page = std::make_unique(this, plugin_name, "http://manganelos.com/"); add_manganelos_handlers(search_page.get()); - tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 400)}); + + tabs.push_back(Tab{create_body(), std::make_unique(this, search_page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + start_tab_index = 1; } else if(strcmp(plugin_name, "mangatown") == 0) { auto search_page = std::make_unique(this, plugin_name, "https://www.mangatown.com/"); add_mangatown_handlers(search_page.get()); - tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 400)}); + + tabs.push_back(Tab{create_body(), std::make_unique(this, search_page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + start_tab_index = 1; } else if(strcmp(plugin_name, "mangakatana") == 0) { auto search_page = std::make_unique(this, plugin_name, "https://mangakatana.com/", false); add_mangakatana_handlers(search_page.get()); + + tabs.push_back(Tab{create_body(), std::make_unique(this, search_page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + start_tab_index = 1; } else if(strcmp(plugin_name, "mangadex") == 0) { - tabs.push_back(Tab{create_body(), std::make_unique(this), create_search_bar("Search...", 400)}); - upgrade_legacy_mangadex_ids(this, tabs.back().page.get()); + auto search_page = std::make_unique(this); + upgrade_legacy_mangadex_ids(this, search_page.get()); + + tabs.push_back(Tab{create_body(), std::make_unique(this, search_page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + start_tab_index = 1; } else if(strcmp(plugin_name, "readm") == 0) { auto search_page = std::make_unique(this, plugin_name, "https://readm.org/"); add_readm_handlers(search_page.get()); - tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 400)}); + + tabs.push_back(Tab{create_body(), std::make_unique(this, search_page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + start_tab_index = 1; } else if(strcmp(plugin_name, "onimanga") == 0) { auto search_page = std::make_unique(this, plugin_name, "https://onimanga.com/"); add_onimanga_handlers(search_page.get()); + + tabs.push_back(Tab{create_body(), std::make_unique(this, search_page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); auto history_page = std::make_unique(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + + start_tab_index = 1; } else if(strcmp(plugin_name, "manga") == 0) { auto mangadex = std::make_unique(this); upgrade_legacy_mangadex_ids(this, mangadex.get()); @@ -1349,35 +1380,9 @@ namespace QuickMedia { LOGIN }; - // Returns relative time as a string (approximation) - static std::string seconds_to_relative_time_str(time_t seconds) { - seconds = std::max(0L, seconds); - - time_t minutes = seconds / 60; - time_t hours = minutes / 60; - time_t days = hours / 24; - time_t months = days / 30; - time_t years = days / 365; - - if(years >= 1) - return std::to_string(years) + " year" + (years == 1 ? "" : "s") + " ago"; - else if(months >= 1) - return std::to_string(months) + " month" + (months == 1 ? "" : "s") + " ago"; - else if(days >= 1) - return std::to_string(days) + " day" + (days == 1 ? "" : "s") + " ago"; - else if(hours >= 1) - return std::to_string(hours) + " hour" + (hours == 1 ? "" : "s") + " ago"; - else if(minutes >= 1) - return std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s") + " ago"; - else - return std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s") + " ago"; - } - static void fill_history_items_from_json(const Json::Value &history_json, BodyItems &history_items) { assert(history_json.isArray()); - BodyItems body_items; - time_t time_now = time(NULL); for(const Json::Value &item : history_json) { if(!item.isObject()) @@ -1403,12 +1408,10 @@ namespace QuickMedia { body_item->set_description("Watched " + seconds_to_relative_time_str(time_now - timestamp.asInt64())); body_item->set_description_color(get_current_theme().faded_text_color); body_item->thumbnail_size = sf::Vector2i(192, 108); - body_items.push_back(std::move(body_item)); + history_items.push_back(std::move(body_item)); } - for(auto it = body_items.rbegin(), end = body_items.rend(); it != end; ++it) { - history_items.push_back(std::move(*it)); - } + std::reverse(history_items.begin(), history_items.end()); } static Path get_video_history_filepath(const char *plugin_name) { @@ -1626,6 +1629,81 @@ namespace QuickMedia { return true; } + // Returns -1 if not found + int bookmark_find_item(Json::Value &root, const std::string &title, const std::string &author, const std::string &url) { + if(!root.isArray()) + return false; + + int index = -1; + for(const Json::Value &item_json : root) { + ++index; + if(!item_json.isObject()) + continue; + + const Json::Value &title_json = item_json["title"]; + const Json::Value &author_json = item_json["author"]; + const Json::Value &url_json = item_json["url"]; + if((!title.empty() && title_json.isString() && strcmp(title.c_str(), title_json.asCString()) == 0) + || (!author.empty() && author_json.isString() && strcmp(author.c_str(), author_json.asCString()) == 0) + || (!url.empty() && url_json.isString() && strcmp(url.c_str(), url_json.asCString()) == 0)) + { + return index; + } + } + + return -1; + } + + bool Program::toggle_bookmark(BodyItem *body_item, const char *bookmark_name) { + assert(bookmark_name); + Path bookmark_path = get_storage_dir().join("bookmarks"); + if(create_directory_recursive(bookmark_path) != 0) { + show_notification("QuickMedia", "Failed to update bookmark", Urgency::CRITICAL); + return false; + } + + bookmark_path.join(bookmark_name); + Json::Value json_root; + if(!read_file_as_json(bookmark_path, json_root) || !json_root.isArray()) + json_root = Json::Value(Json::arrayValue); + + const int existing_index = bookmark_find_item(json_root, body_item->get_title(), body_item->get_author(), body_item->url); + if(existing_index != -1) { + Json::Value removed; + json_root.removeIndex(existing_index, &removed); + if(!save_json_to_file_atomic(bookmark_path, json_root)) { + show_notification("QuickMedia", "Failed to update bookmark", Urgency::CRITICAL); + return false; + } + + std::string bookmark_title = body_item->get_title(); + if(bookmark_title.empty()) bookmark_title = body_item->get_author(); + show_notification("QuickMedia", "Removed " + bookmark_title + " from bookmarks"); + removed = true; + return true; + } + + Json::Value new_item(Json::objectValue); + if(!body_item->get_title().empty()) + new_item["title"] = body_item->get_title(); + if(!body_item->get_author().empty()) + new_item["author"] = body_item->get_author(); + if(!body_item->url.empty()) + new_item["url"] = body_item->url; + new_item["timestamp"] = (int64_t)time(nullptr); + + json_root.append(std::move(new_item)); + if(!save_json_to_file_atomic(bookmark_path, json_root)) { + show_notification("QuickMedia", "Failed to update bookmark", Urgency::CRITICAL); + return false; + } + + std::string bookmark_title = body_item->get_title(); + if(bookmark_title.empty()) bookmark_title = body_item->get_author(); + show_notification("QuickMedia", "Added " + bookmark_title + " to bookmarks"); + return true; + } + void Program::page_loop_render(sf::RenderWindow &window, std::vector &tabs, int selected_tab, TabAssociatedData &tab_associated_data, const Json::Value *json_chapters, Tabs &ui_tabs) { if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->draw(window, window_size, true); @@ -2053,6 +2131,19 @@ namespace QuickMedia { return trackable_page->track(selected_item->get_title()) == TrackResult::OK; }); } + } else if(event.key.code == sf::Keyboard::B && event.key.control) { + BodyItem *selected_item = tabs[selected_tab].body->get_selected(); + if(selected_item) { + const char *bookmark_name = tabs[selected_tab].page->get_bookmark_name(); + if(bookmark_name) { + if(toggle_bookmark(selected_item, bookmark_name)) { + for(Tab &tab : tabs) { + if(tab.page && tab.page->is_bookmark_page()) + tab.page->needs_refresh = true; + } + } + } + } } else if(event.key.code == sf::Keyboard::C && event.key.control) { BodyItem *selected_item = tabs[selected_tab].body->get_selected(); if(selected_item) { @@ -7422,7 +7513,7 @@ namespace QuickMedia { redraw = true; } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Tab) { file_name_entry.set_editable(!file_name_entry.is_editable()); - search_bar->set_editable(!file_name_entry.is_editable()); + search_bar->set_editable(!search_bar->is_editable()); } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Enter && event.key.control) { std::string save_path = save_file(); if(!save_path.empty()) diff --git a/src/Storage.cpp b/src/Storage.cpp index 4baca49..1b5435f 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -333,6 +333,9 @@ namespace QuickMedia { bool is_program_executable_by_name(const char *name) { // TODO: Implement for Windows. Windows also uses semicolon instead of colon as a separator char *env = getenv("PATH"); + if(!env) + return false; + std::unordered_set paths; string_split(env, ':', [&paths](const char *str, size_t size) { if(size > 0) diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp index 8a3a0ef..6a8d3f2 100644 --- a/src/StringUtils.cpp +++ b/src/StringUtils.cpp @@ -170,4 +170,28 @@ namespace QuickMedia { return true; } + + // Returns relative time as a string (approximation) + std::string seconds_to_relative_time_str(time_t seconds) { + seconds = std::max(0L, seconds); + + time_t minutes = seconds / 60; + time_t hours = minutes / 60; + time_t days = hours / 24; + time_t months = days / 30; + time_t years = days / 365; + + if(years >= 1) + return std::to_string(years) + " year" + (years == 1 ? "" : "s") + " ago"; + else if(months >= 1) + return std::to_string(months) + " month" + (months == 1 ? "" : "s") + " ago"; + else if(days >= 1) + return std::to_string(days) + " day" + (days == 1 ? "" : "s") + " ago"; + else if(hours >= 1) + return std::to_string(hours) + " hour" + (hours == 1 ? "" : "s") + " ago"; + else if(minutes >= 1) + return std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s") + " ago"; + else + return std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s") + " ago"; + } } \ No newline at end of file diff --git a/src/plugins/Page.cpp b/src/plugins/Page.cpp index 8605d82..5c64183 100644 --- a/src/plugins/Page.cpp +++ b/src/plugins/Page.cpp @@ -1,4 +1,6 @@ #include "../../plugins/Page.hpp" +#include "../../include/StringUtils.hpp" +#include "../../include/Theme.hpp" #include "../../include/QuickMedia.hpp" #include @@ -40,4 +42,44 @@ namespace QuickMedia { bool Page::load_manga_content_storage(const char *service_name, const std::string &manga_title, const std::string &manga_id) { return program->load_manga_content_storage(service_name, manga_title, manga_id); } + + PluginResult BookmarksPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { + return redirect_page->submit(title, url, result_tabs); + } + + PluginResult BookmarksPage::lazy_fetch(BodyItems &result_items) { + const char *bookmark_name = redirect_page->get_bookmark_name(); + if(!bookmark_name) + return PluginResult::ERR; + + Path bookmark_path = get_storage_dir().join("bookmarks").join(bookmark_name); + Json::Value json_root; + if(!read_file_as_json(bookmark_path, json_root) || !json_root.isArray()) + return PluginResult::OK; + + const time_t time_now = time(nullptr); + for(const Json::Value &item_json : json_root) { + if(!item_json.isObject()) + continue; + + const Json::Value &title_json = item_json["title"]; + const Json::Value &author_json = item_json["author"]; + const Json::Value &url_json = item_json["url"]; + const Json::Value ×tamp_json = item_json["timestamp"]; + + auto body_item = BodyItem::create(title_json.isString() ? title_json.asString() : ""); + if(author_json.isString()) + body_item->set_author(author_json.asString()); + if(url_json.isString()) + body_item->url = url_json.asString(); + if(timestamp_json.isInt64()) { + body_item->set_description("Bookmarked " + seconds_to_relative_time_str(time_now - timestamp_json.asInt64())); + body_item->set_description_color(get_current_theme().faded_text_color); + } + result_items.push_back(std::move(body_item)); + } + + std::reverse(result_items.begin(), result_items.end()); + return PluginResult::OK; + } } \ No newline at end of file diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index b490e97..6c080c5 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -1660,30 +1660,6 @@ namespace QuickMedia { return self->size == sub_len && memcmp(self->data, sub, sub_len) == 0; } - // Returns relative time as a string (approximation) - static std::string seconds_to_relative_time_str(time_t seconds) { - seconds = std::max(0L, seconds); - - time_t minutes = seconds / 60; - time_t hours = minutes / 60; - time_t days = hours / 24; - time_t months = days / 30; - time_t years = days / 365; - - if(years >= 1) - return std::to_string(years) + " year" + (years == 1 ? "" : "s") + " ago"; - else if(months >= 1) - return std::to_string(months) + " month" + (months == 1 ? "" : "s") + " ago"; - else if(days >= 1) - return std::to_string(days) + " day" + (days == 1 ? "" : "s") + " ago"; - else if(hours >= 1) - return std::to_string(hours) + " hour" + (hours == 1 ? "" : "s") + " ago"; - else if(minutes >= 1) - return std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s") + " ago"; - else - return std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s") + " ago"; - } - PluginResult YoutubeSubscriptionsPage::lazy_fetch(BodyItems &result_items) { Path subscriptions_path = get_storage_dir().join("subscriptions").join("youtube.txt"); std::string subscriptions_str; -- cgit v1.2.3