aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--TODO4
-rw-r--r--include/QuickMedia.hpp2
-rw-r--r--plugins/LocalManga.hpp1
-rw-r--r--plugins/Page.hpp2
-rw-r--r--src/QuickMedia.cpp2
-rw-r--r--src/plugins/LocalManga.cpp113
7 files changed, 122 insertions, 4 deletions
diff --git a/README.md b/README.md
index a1ff7c9..eaf0660 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,8 @@ Type text and then wait and QuickMedia will automatically search.\
`Ctrl+T`: Subscribe/Unsubscribe from the channel.
### Manga search/history/chapters page controls
`Ctrl+B`: Bookmark the selected manga. If the manga is already bookmarked then its removed from bookmarks.
+### Local manga search page controls
+`Ctrl+R`: Mark the manga as read/unread.
### Manga page view controls
`Arrow up`/`Ctrl+K`: Go to the next page (or chapter if the current page is the last one).\
`Arrow down`/`Ctrl+J`: Go to the previous page (or chapter if the current page is the first one).\
diff --git a/TODO b/TODO
index fc90b30..9599d31 100644
--- a/TODO
+++ b/TODO
@@ -176,7 +176,6 @@ Add throttling to youtube live stream (MediaProxy).
Use event timestamp to sort display name/room name (etc) events to apply them in order when fetching previous messages or additional messages.
Restart and resume youtube media download in downloader when media is throttled.
Try to reconnect media proxy on disconnect. The internet may be unstable (for example on mobile internet).
-Youtube is still getting throttled, but to 500kb/sec from 5mb/sec. How to detect this???.
Opening a media url should display it directly in quickmedia.
Automatically resize body item thumbnail if body is small, or move thumbnail above the text.
Support &nbsp?.
@@ -216,4 +215,5 @@ Add latest read chapter name to manga progress, to easily see from manga list pa
Allow changing manga sorting in gui (especially for local manga).
Allow using ~ and $HOME in config file.
Save manga "I" mode to file and load it on startup.
-Show in bookmarks and history page if a manga has been read (local-manga). \ No newline at end of file
+Show in bookmarks and history page if a manga has been read (local-manga).
+Add "finished reading" to online manga as well, for the manga sites that publish latest chapter in the search page. \ No newline at end of file
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index c3f06de..6856f05 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -99,6 +99,7 @@ namespace QuickMedia {
const char* get_plugin_name() const;
void manga_get_watch_history(const char *plugin_name, BodyItems &history_items, bool local_thumbnail);
void youtube_get_watch_history(BodyItems &history_items);
+ void update_manga_history(const std::string &manga_id, const std::string &thumbnail_url);
Json::Value load_history_json();
@@ -122,7 +123,6 @@ namespace QuickMedia {
bool video_download_if_non_streamable(std::string &video_url, std::string &audio_url, bool &is_audio_only, bool &has_embedded_audio, PageType previous_page);
int video_get_max_height();
void video_content_page(Page *parent_page, VideoPage *video_page, std::string video_title, bool download_if_streaming_fails, Body *parent_body, int play_index, int *parent_body_page = nullptr, const std::string &parent_page_search = "");
- void update_manga_history(const std::string &manga_id, const std::string &thumbnail_url);
void save_manga_progress(MangaImagesPage *images_page, Json::Value &json_chapters, Json::Value &json_chapter, int &latest_read);
// Returns -1 to go to previous chapter, 0 to stay on same chapter and 1 to go to next chapter
int image_page(MangaImagesPage *images_page, Body *chapters_body);
diff --git a/plugins/LocalManga.hpp b/plugins/LocalManga.hpp
index eca5f96..e85e08b 100644
--- a/plugins/LocalManga.hpp
+++ b/plugins/LocalManga.hpp
@@ -33,6 +33,7 @@ namespace QuickMedia {
bool reload_on_page_change() override { return true; }
bool reseek_to_body_item_by_url() override { return true; }
std::shared_ptr<BodyItem> get_bookmark_body_item(BodyItem *selected_item) override;
+ void toggle_read(BodyItem *selected_item) override;
private:
std::vector<LocalManga> manga_list;
std::unordered_set<std::string> finished_reading_manga;
diff --git a/plugins/Page.hpp b/plugins/Page.hpp
index 6864a5d..20d6000 100644
--- a/plugins/Page.hpp
+++ b/plugins/Page.hpp
@@ -71,6 +71,8 @@ namespace QuickMedia {
// |selected_item| may be nullptr.
virtual std::shared_ptr<BodyItem> get_bookmark_body_item(BodyItem *selected_item) { (void)selected_item; return nullptr; }
virtual bool is_bookmark_page() const { return false; }
+ // |selected_item| may be nullptr.
+ virtual void toggle_read(BodyItem *selected_item) { (void)selected_item; }
virtual bool is_lazy_fetch_page() const { return false; }
// Note: If submit is done without any selection, then the search term is sent as the |title| and |url|. Submit will only be sent if the input text is not empty or if an item is selected
virtual bool allow_submit_no_selection() const { return false; }
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 656e196..8174680 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -2183,6 +2183,8 @@ namespace QuickMedia {
}
}
}
+ } else if(event.key.code == mgl::Keyboard::R && event.key.control) {
+ tabs[selected_tab].page->toggle_read(tabs[selected_tab].body->get_selected());
} else if(event.key.code == mgl::Keyboard::C && event.key.control) {
BodyItem *selected_item = tabs[selected_tab].body->get_selected();
if(selected_item)
diff --git a/src/plugins/LocalManga.cpp b/src/plugins/LocalManga.cpp
index 1807ca7..8344527 100644
--- a/src/plugins/LocalManga.cpp
+++ b/src/plugins/LocalManga.cpp
@@ -5,9 +5,12 @@
#include "../../include/StringUtils.hpp"
#include "../../include/Storage.hpp"
#include "../../external/cppcodec/base64_url.hpp"
+#include "../../include/QuickMedia.hpp"
#include <json/value.h>
namespace QuickMedia {
+ static const mgl::Color finished_reading_color = mgl::Color(43, 255, 47);
+
// 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;
@@ -81,6 +84,94 @@ namespace QuickMedia {
return manga_list;
}
+ enum class ReadStatus {
+ READ,
+ UNREAD
+ };
+
+ // Returns the new read status
+ static bool toggle_read_save_to_file(Program *program, const std::string &manga_name, const std::string &thumbnail_url, ReadStatus &read_status) {
+ if(get_config().local_manga_directory.empty()) {
+ show_notification("QuickMedia", "local_manga_directory config is not set", Urgency::CRITICAL);
+ return false;
+ }
+
+ 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 false;
+ }
+
+ Path manga_url = Path(get_config().local_manga_directory).join(manga_name);
+ std::vector<LocalMangaChapter> chapters = get_chapters_in_manga(manga_url);
+ if(chapters.empty() || chapters.front().pages.empty())
+ return false;
+
+ Path content_storage_dir = get_storage_dir().join("local-manga");
+ if(create_directory_recursive(content_storage_dir) != 0) {
+ show_notification("QuickMedia", "Failed to create directory: " + content_storage_dir.data, Urgency::CRITICAL);
+ return false;
+ }
+
+ Path content_storage_file = content_storage_dir;
+ content_storage_file.join(cppcodec::base64_url::encode<std::string>(manga_name));
+ Json::Value content_storage_json;
+
+ bool result = true;
+ FileType file_type = get_file_type(content_storage_file);
+ if(file_type == FileType::REGULAR) {
+ result = read_file_as_json(content_storage_file, content_storage_json) && content_storage_json.isObject();
+ if(!result) {
+ show_notification("QuickMedia", "Failed to read " + content_storage_file.data, Urgency::CRITICAL);
+ return false;
+ }
+ } else {
+ result = true;
+ }
+
+ if(!content_storage_json.isObject())
+ content_storage_json = Json::Value(Json::objectValue);
+
+ content_storage_json["name"] = manga_name;
+ content_storage_json["url"] = manga_name;
+
+ Json::Value *chapters_json = &content_storage_json["chapters"];
+ if(!chapters_json->isObject()) {
+ content_storage_json["chapters"] = Json::Value(Json::objectValue);
+ chapters_json = &content_storage_json["chapters"];
+ }
+
+ const LocalMangaChapter &latest_chapter = chapters.front();
+ Json::Value *chapter_json = &(*chapters_json)[latest_chapter.name];
+ if(!chapter_json->isObject()) {
+ (*chapters_json)[latest_chapter.name] = Json::Value(Json::objectValue);
+ chapters_json = &(*chapters_json)[latest_chapter.name];
+ }
+
+ bool read = false;
+ const Json::Value &current_json = (*chapter_json)["current"];
+ const Json::Value &total_json = (*chapter_json)["total"];
+ if(current_json.isInt() && total_json.isInt() && current_json.asInt() >= total_json.asInt()) {
+ chapters_json->removeMember(latest_chapter.name);
+ read = true;
+ } else {
+ (*chapter_json)["current"] = (int)latest_chapter.pages.size();
+ (*chapter_json)["total"] = (int)latest_chapter.pages.size();
+ (*chapter_json)["url"] = latest_chapter.name;
+ read = false;
+ }
+
+ if(!save_json_to_file_atomic(content_storage_file, content_storage_json)) {
+ show_notification("QuickMedia", std::string("Failed to mark manga as ") + (read ? "unread" : "read"), Urgency::CRITICAL);
+ return false;
+ }
+
+ read_status = (read ? ReadStatus::UNREAD : ReadStatus::READ);
+ if(read_status == ReadStatus::READ)
+ program->update_manga_history(manga_name, thumbnail_url);
+
+ return true;
+ }
+
static bool has_finished_reading_latest_chapter(const LocalManga &manga, const Json::Value &chapters_json) {
if(manga.chapters.empty())
return false;
@@ -134,7 +225,7 @@ namespace QuickMedia {
auto body_item = BodyItem::create(std::move(title));
if(has_finished_reading)
- body_item->set_title_color(mgl::Color(43, 255, 47));
+ body_item->set_title_color(finished_reading_color);
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);
@@ -226,6 +317,26 @@ namespace QuickMedia {
return body_item;
}
+ void LocalMangaSearchPage::toggle_read(BodyItem *selected_item) {
+ if(!selected_item)
+ return;
+
+ ReadStatus read_status;
+ if(!toggle_read_save_to_file(program, selected_item->url, selected_item->thumbnail_url, read_status))
+ return;
+
+ mgl::Color color = get_theme().text_color;
+ std::string title;
+ if(read_status == ReadStatus::READ) {
+ title = "[Finished reading] ";
+ color = finished_reading_color;
+ }
+ title += selected_item->url;
+
+ selected_item->set_title(std::move(title));
+ selected_item->set_title_color(color);
+ }
+
static std::unordered_set<std::string> get_lines_in_file(const Path &filepath) {
std::unordered_set<std::string> lines;