aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2019-08-05 18:17:00 +0200
committerdec05eba <dec05eba@protonmail.com>2019-08-05 18:17:04 +0200
commit657edb8eb9ab2fdef60d9c5d23a4c3093a64d859 (patch)
treea7055146e30551104480efd5f80898e21ae6aa5c /src
parent7e8a2f23a40e6374ddfb551920257846021e86fa (diff)
Add manga chapter viewing
Diffstat (limited to 'src')
-rw-r--r--src/Program.c10
-rw-r--r--src/QuickMedia.cpp254
-rw-r--r--src/plugins/Manganelo.cpp (renamed from src/Manganelo.cpp)88
-rw-r--r--src/plugins/Youtube.cpp (renamed from src/Youtube.cpp)6
4 files changed, 259 insertions, 99 deletions
diff --git a/src/Program.c b/src/Program.c
index 39957ed..38a602f 100644
--- a/src/Program.c
+++ b/src/Program.c
@@ -66,7 +66,15 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void
int exit_status = WEXITSTATUS(status);
if(exit_status != 0) {
- fprintf(stderr, "Failed to execute program, exit status %d\n", exit_status);
+ fprintf(stderr, "Failed to execute program (");
+ const char **arg = args;
+ while(*arg) {
+ if(arg != args)
+ fputc(' ', stderr);
+ fprintf(stderr, "'%s'", *arg);
+ ++arg;
+ }
+ fprintf(stderr, "), exit status %d\n", exit_status);
result = -exit_status;
goto cleanup;
}
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index c7d89e2..e6c7077 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -173,7 +173,8 @@ namespace QuickMedia {
window_size(800, 600),
body(nullptr),
current_plugin(nullptr),
- current_page(Page::SEARCH_SUGGESTION)
+ current_page(Page::SEARCH_SUGGESTION),
+ image_index(0)
{
window.setVerticalSyncEnabled(true);
if(!font.loadFromFile("fonts/Lato-Regular.ttf")) {
@@ -181,8 +182,8 @@ namespace QuickMedia {
abort();
}
body = new Body(font);
- //current_plugin = new Manganelo();
- current_plugin = new Youtube();
+ current_plugin = new Manganelo();
+ //current_plugin = new Youtube();
search_bar = std::make_unique<SearchBar>(font);
}
@@ -191,14 +192,15 @@ namespace QuickMedia {
delete current_plugin;
}
- static SearchResult search_selected_suggestion(Body *body, Plugin *plugin) {
+ static SearchResult search_selected_suggestion(Body *body, Plugin *plugin, Page &next_page) {
BodyItem *selected_item = body->get_selected();
if(!selected_item)
return SearchResult::ERR;
std::string selected_item_title = selected_item->title;
+ std::string selected_item_url = selected_item->url;
body->clear_items();
- SearchResult search_result = plugin->search(selected_item_title, body->items);
+ SearchResult search_result = plugin->search(!selected_item_url.empty() ? selected_item_url : selected_item_title, body->items, next_page);
body->reset_selected();
return search_result;
}
@@ -208,7 +210,6 @@ namespace QuickMedia {
if(text.isEmpty())
return;
- body->items.push_back(std::make_unique<BodyItem>(text));
SuggestionResult suggestion_result = plugin->update_search_suggestions(text, body->items);
body->clamp_selection();
}
@@ -217,7 +218,8 @@ namespace QuickMedia {
while(window.isOpen()) {
switch(current_page) {
case Page::EXIT:
- return;
+ window.close();
+ break;
case Page::SEARCH_SUGGESTION:
search_suggestion_page();
break;
@@ -227,14 +229,40 @@ namespace QuickMedia {
case Page::VIDEO_CONTENT:
video_content_page();
break;
+ case Page::EPISODE_LIST:
+ episode_list_page();
+ break;
+ case Page::IMAGES:
+ image_page();
+ break;
default:
return;
}
}
}
- void Program::base_event_handler(sf::Event &event) {
-
+ void Program::base_event_handler(sf::Event &event, Page previous_page) {
+ if (event.type == sf::Event::Closed) {
+ current_page = Page::EXIT;
+ } else 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) {
+ if(event.key.code == sf::Keyboard::Up) {
+ body->select_previous_item();
+ } else if(event.key.code == sf::Keyboard::Down) {
+ body->select_next_item();
+ } else if(event.key.code == sf::Keyboard::Escape) {
+ current_page = previous_page;
+ body->clear_items();
+ body->reset_selected();
+ search_bar->clear();
+ }
+ } else if(event.type == sf::Event::TextEntered) {
+ search_bar->onTextEntered(event.text.unicode);
+ }
}
void Program::search_suggestion_page() {
@@ -243,8 +271,9 @@ namespace QuickMedia {
};
search_bar->onTextSubmitCallback = [this](const std::string &text) {
- if(search_selected_suggestion(body, current_plugin) == SearchResult::OK)
- current_page = Page::SEARCH_RESULT;
+ Page next_page;
+ if(search_selected_suggestion(body, current_plugin, next_page) == SearchResult::OK)
+ current_page = next_page;
};
sf::Vector2f body_pos;
@@ -252,29 +281,11 @@ namespace QuickMedia {
bool resized = true;
sf::Event event;
- while (window.isOpen() && current_page == Page::SEARCH_SUGGESTION) {
+ while (current_page == Page::SEARCH_SUGGESTION) {
while (window.pollEvent(event)) {
- if (event.type == sf::Event::Closed) {
- window.close();
- current_page = Page::EXIT;
- } else 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));
+ base_event_handler(event, Page::EXIT);
+ if(event.type == sf::Event::Resized)
resized = true;
- } else if(event.type == sf::Event::KeyPressed) {
- if(event.key.code == sf::Keyboard::Up) {
- body->select_previous_item();
- } else if(event.key.code == sf::Keyboard::Down) {
- body->select_next_item();
- } else if(event.key.code == sf::Keyboard::Escape) {
- current_page = Page::EXIT;
- window.close();
- }
- } else if(event.type == sf::Event::TextEntered) {
- search_bar->onTextEntered(event.text.unicode);
- }
}
if(resized) {
@@ -321,31 +332,9 @@ namespace QuickMedia {
bool resized = true;
sf::Event event;
- while (window.isOpen() && current_page == Page::SEARCH_RESULT) {
+ while (current_page == Page::SEARCH_RESULT) {
while (window.pollEvent(event)) {
- if (event.type == sf::Event::Closed) {
- window.close();
- current_page = Page::EXIT;
- } else 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));
- resized = true;
- } else if(event.type == sf::Event::KeyPressed) {
- if(event.key.code == sf::Keyboard::Up) {
- body->select_previous_item();
- } else if(event.key.code == sf::Keyboard::Down) {
- body->select_next_item();
- } else if(event.key.code == sf::Keyboard::Escape) {
- current_page = Page::SEARCH_SUGGESTION;
- body->clear_items();
- body->reset_selected();
- search_bar->clear();
- }
- } else if(event.type == sf::Event::TextEntered) {
- search_bar->onTextEntered(event.text.unicode);
- }
+ base_event_handler(event, Page::SEARCH_SUGGESTION);
}
if(resized) {
@@ -400,31 +389,160 @@ namespace QuickMedia {
sf::Clock resize_timer;
sf::Event event;
- while (window.isOpen() && current_page == Page::VIDEO_CONTENT) {
+ while (current_page == Page::VIDEO_CONTENT) {
+ while (window.pollEvent(event)) {
+ base_event_handler(event, Page::SEARCH_SUGGESTION);
+ if(event.type == sf::Event::Resized) {
+ if(video_player)
+ video_player->resize(sf::Vector2i(window_size.x, window_size.y));
+ }
+ }
+
+ window.clear();
+ if(video_player)
+ video_player->draw(window);
+ window.display();
+ }
+ }
+
+ void Program::episode_list_page() {
+ search_bar->onTextUpdateCallback = [this](const std::string &text) {
+ body->filter_search_fuzzy(text);
+ body->clamp_selection();
+ };
+
+ search_bar->onTextSubmitCallback = [this](const std::string &text) {
+ BodyItem *selected_item = body->get_selected();
+ if(!selected_item)
+ return;
+
+ images_url = selected_item->url;
+ image_index = 0;
+ current_page = Page::IMAGES;
+ };
+
+ sf::Vector2f body_pos;
+ sf::Vector2f body_size;
+ bool resized = true;
+ sf::Event event;
+
+ while (current_page == Page::EPISODE_LIST) {
+ while (window.pollEvent(event)) {
+ base_event_handler(event, Page::SEARCH_SUGGESTION);
+ if(event.type == sf::Event::Resized)
+ resized = true;
+ }
+
+ if(resized) {
+ search_bar->onWindowResize(window_size);
+
+ float body_padding_horizontal = 50.0f;
+ float body_padding_vertical = 50.0f;
+ float body_width = window_size.x - body_padding_horizontal * 2.0f;
+ if(body_width < 400) {
+ body_width = window_size.x;
+ body_padding_horizontal = 0.0f;
+ }
+
+ float search_bottom = search_bar->getBottom();
+ body_pos = sf::Vector2f(body_padding_horizontal, search_bottom + body_padding_vertical);
+ body_size = sf::Vector2f(body_width, window_size.y);
+ }
+
+ search_bar->update();
+
+ window.clear(back_color);
+ body->draw(window, body_pos, body_size);
+ search_bar->draw(window);
+ window.display();
+ }
+ }
+
+ void Program::image_page() {
+ search_bar->onTextUpdateCallback = nullptr;
+ search_bar->onTextSubmitCallback = nullptr;
+
+ sf::Texture image_texture;
+ sf::Sprite image;
+ sf::Text error_message("", font, 30);
+ error_message.setFillColor(sf::Color::White);
+
+ Manganelo *image_plugin = static_cast<Manganelo*>(current_plugin);
+ std::string image_data;
+
+ // TODO: Optimize this somehow. One image alone uses more than 20mb ram! Total ram usage for viewing one image
+ // becomes 40mb (private memory, almost 100mb in total!) Unacceptable!
+ ImageResult image_result = image_plugin->get_image_by_index(images_url, image_index, image_data);
+ if(image_result == ImageResult::OK) {
+ if(image_texture.loadFromMemory(image_data.data(), image_data.size())) {
+ image_texture.setSmooth(true);
+ image.setTexture(image_texture, true);
+ } else {
+ error_message.setString(std::string("Failed to load image for page ") + std::to_string(image_index));
+ }
+ } else if(image_result == ImageResult::END) {
+ // TODO: Better error message, with chapter name
+ error_message.setString("End of chapter");
+ } else {
+ // TODO: Convert ImageResult error to a string and show to user
+ error_message.setString(std::string("Network error, failed to get image for page ") + std::to_string(image_index));
+ }
+ image_data.resize(0);
+
+ bool error = !error_message.getString().isEmpty();
+ bool resized = true;
+ sf::Event event;
+
+ // TODO: Show current page / number of pages.
+ // TODO: Show to user if a certain page is missing (by checking page name (number) and checking if some is skipped)
+ while (current_page == Page::IMAGES) {
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
- window.close();
current_page = Page::EXIT;
} else 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));
- if(video_player)
- video_player->resize(sf::Vector2i(window_size.x, window_size.y));
+ resized = true;
} else if(event.type == sf::Event::KeyPressed) {
- if(event.key.code == sf::Keyboard::Escape) {
- current_page = Page::SEARCH_SUGGESTION;
- body->clear_items();
- body->reset_selected();
- search_bar->clear();
+ if(event.key.code == sf::Keyboard::Up) {
+ if(image_index > 0) {
+ --image_index;
+ return;
+ }
+ } else if(event.key.code == sf::Keyboard::Down) {
+ if(!error) {
+ ++image_index;
+ return;
+ }
+ } else if(event.key.code == sf::Keyboard::Escape) {
+ current_page = Page::EPISODE_LIST;
}
}
}
- window.clear();
- if(video_player)
- video_player->draw(window);
+ if(resized) {
+ if(error) {
+ auto bounds = error_message.getLocalBounds();
+ error_message.setPosition(window_size.x * 0.5f - bounds.width * 0.5f, window_size.y * 0.5f - bounds.height);
+ } else {
+ auto texture_size = image.getTexture()->getSize();
+ auto image_scale = image.getScale();
+ auto image_size = sf::Vector2f(texture_size.x, texture_size.y);
+ image_size.x *= image_scale.x;
+ image_size.y *= image_scale.y;
+
+ image.setPosition(window_size.x * 0.5f - image_size.x * 0.5f, window_size.y * 0.5f - image_size.y * 0.5f);
+ }
+ }
+
+ window.clear(back_color);
+ if(error) {
+ window.draw(error_message);
+ } else {
+ window.draw(image);
+ }
window.display();
}
}
diff --git a/src/Manganelo.cpp b/src/plugins/Manganelo.cpp
index 1d3929e..bc99387 100644
--- a/src/Manganelo.cpp
+++ b/src/plugins/Manganelo.cpp
@@ -1,51 +1,31 @@
-#include "../plugins/Manganelo.hpp"
+#include "../../plugins/Manganelo.hpp"
#include <quickmedia/HtmlSearch.h>
#include <json/reader.h>
namespace QuickMedia {
- SearchResult Manganelo::search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) {
- std::string url = "https://manganelo.com/search/";
- url += url_param_encode(text);
+ SearchResult Manganelo::search(const std::string &url, std::vector<std::unique_ptr<BodyItem>> &result_items, Page &next_page) {
+ next_page = Page::EPISODE_LIST;
std::string website_data;
if(download_to_string(url, website_data) != DownloadResult::OK)
return SearchResult::NET_ERR;
- struct ItemData {
- std::vector<std::unique_ptr<BodyItem>> &result_items;
- size_t item_index;
- };
-
- ItemData item_data = { result_items, 0 };
-
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, "//h3[class=\"story_name\"]/a",
+ result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='chapter-list']/div[class='row']//a",
[](QuickMediaHtmlNode *node, void *userdata) {
- ItemData *item_data = (ItemData*)userdata;
+ auto *item_data = (std::vector<std::unique_ptr<BodyItem>>*)userdata;
const char *href = quickmedia_html_node_get_attribute_value(node, "href");
const char *text = quickmedia_html_node_get_text(node);
if(href && text) {
auto item = std::make_unique<BodyItem>(text);
item->url = href;
- item_data->result_items.push_back(std::move(item));
- }
- }, &item_data);
- if (result != 0)
- goto cleanup;
-
- result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class=\"story_item\"]//img",
- [](QuickMediaHtmlNode *node, void *userdata) {
- ItemData *item_data = (ItemData*)userdata;
- const char *src = quickmedia_html_node_get_attribute_value(node, "src");
- if(src && item_data->item_index < item_data->result_items.size()) {
- item_data->result_items[item_data->item_index]->thumbnail_url = src;
- ++item_data->item_index;
+ item_data->push_back(std::move(item));
}
- }, &item_data);
+ }, &result_items);
cleanup:
quickmedia_html_search_deinit(&html_search);
@@ -98,11 +78,13 @@ namespace QuickMedia {
for(const Json::Value &child : json_root) {
if(child.isObject()) {
Json::Value name = child.get("name", "");
- if(name.isString() && name.asCString()[0] != '\0') {
+ Json::Value nameunsigned = child.get("nameunsigned", "");
+ if(name.isString() && name.asCString()[0] != '\0' && nameunsigned.isString() && nameunsigned.asCString()[0] != '\0') {
std::string name_str = name.asString();
while(remove_html_span(name_str)) {}
if(name_str != text) {
auto item = std::make_unique<BodyItem>(name_str);
+ item->url = "https://manganelo.com/manga/" + url_param_encode(nameunsigned.asString());
result_items.push_back(std::move(item));
}
}
@@ -111,4 +93,54 @@ namespace QuickMedia {
}
return SuggestionResult::OK;
}
+
+ ImageResult Manganelo::get_image_by_index(const std::string &url, int index, std::string &image_data) {
+ if(url != last_chapter_url) {
+ printf("Get list of image urls for chapter: %s\n", url.c_str());
+ last_chapter_image_urls.clear();
+ ImageResult image_result = get_image_urls_for_chapter(url, last_chapter_image_urls);
+ if(image_result != ImageResult::OK)
+ return image_result;
+ last_chapter_url = url;
+ }
+
+ int num_images = last_chapter_image_urls.size();
+ if(index < 0 || index >= num_images)
+ return ImageResult::END;
+
+ // TODO: Cache image in file/memory
+ switch(download_to_string(last_chapter_image_urls[index], image_data)) {
+ case DownloadResult::OK:
+ return ImageResult::OK;
+ case DownloadResult::ERR:
+ return ImageResult::ERR;
+ case DownloadResult::NET_ERR:
+ return ImageResult::NET_ERR;
+ default:
+ return ImageResult::ERR;
+ }
+ }
+
+ ImageResult Manganelo::get_image_urls_for_chapter(const std::string &url, std::vector<std::string> &urls) {
+ std::string website_data;
+ if(download_to_string(url, website_data) != DownloadResult::OK)
+ return ImageResult::NET_ERR;
+
+ 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='vungdoc']/img",
+ [](QuickMediaHtmlNode *node, void *userdata) {
+ auto *urls = (std::vector<std::string>*)userdata;
+ const char *src = quickmedia_html_node_get_attribute_value(node, "src");
+ if(src)
+ urls->push_back(src);
+ }, &urls);
+
+ cleanup:
+ quickmedia_html_search_deinit(&html_search);
+ return result == 0 ? ImageResult::OK : ImageResult::ERR;
+ }
} \ No newline at end of file
diff --git a/src/Youtube.cpp b/src/plugins/Youtube.cpp
index 780244c..6cc4ac6 100644
--- a/src/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -1,4 +1,4 @@
-#include "../plugins/Youtube.hpp"
+#include "../../plugins/Youtube.hpp"
#include <quickmedia/HtmlSearch.h>
#include <json/reader.h>
#include <string.h>
@@ -8,7 +8,8 @@ namespace QuickMedia {
return strncmp(str, begin_with, strlen(begin_with)) == 0;
}
- SearchResult Youtube::search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) {
+ SearchResult Youtube::search(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items, Page &next_page) {
+ next_page = Page::SEARCH_RESULT;
std::string url = "https://youtube.com/results?search_query=";
url += url_param_encode(text);
@@ -54,6 +55,7 @@ namespace QuickMedia {
}
SuggestionResult Youtube::update_search_suggestions(const std::string &text, std::vector<std::unique_ptr<BodyItem>> &result_items) {
+ result_items.push_back(std::make_unique<BodyItem>(text));
std::string url = "https://clients1.google.com/complete/search?client=youtube&hl=en&gl=us&q=";
url += url_param_encode(text);