aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Body.cpp1
-rw-r--r--src/QuickMedia.cpp165
-rw-r--r--src/Storage.cpp3
-rw-r--r--src/StringUtils.cpp24
-rw-r--r--src/plugins/Page.cpp42
-rw-r--r--src/plugins/Youtube.cpp24
6 files changed, 198 insertions, 61 deletions
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<PipePage>(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<ManganeloSearchPage>(this), create_search_bar("Search...", 400)});
+ auto search_page = std::make_unique<ManganeloSearchPage>(this);
+
+ tabs.push_back(Tab{create_body(), std::make_unique<BookmarksPage>(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<HistoryPage>(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<MangaGenericSearchPage>(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<BookmarksPage>(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<HistoryPage>(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<MangaGenericSearchPage>(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<BookmarksPage>(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<HistoryPage>(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<MangaGenericSearchPage>(this, plugin_name, "https://mangakatana.com/", false);
add_mangakatana_handlers(search_page.get());
+
+ tabs.push_back(Tab{create_body(), std::make_unique<BookmarksPage>(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<HistoryPage>(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<MangadexSearchPage>(this), create_search_bar("Search...", 400)});
- upgrade_legacy_mangadex_ids(this, tabs.back().page.get());
+ auto search_page = std::make_unique<MangadexSearchPage>(this);
+ upgrade_legacy_mangadex_ids(this, search_page.get());
+
+ tabs.push_back(Tab{create_body(), std::make_unique<BookmarksPage>(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<HistoryPage>(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<MangaGenericSearchPage>(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<BookmarksPage>(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<HistoryPage>(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<MangaGenericSearchPage>(this, plugin_name, "https://onimanga.com/");
add_onimanga_handlers(search_page.get());
+
+ tabs.push_back(Tab{create_body(), std::make_unique<BookmarksPage>(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<HistoryPage>(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<MangadexSearchPage>(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<Tab> &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<std::string> 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 <json/reader.h>
@@ -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<Tab> &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 &timestamp_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;