From 65cf7681a04f2511db8c7829e9828b53a6676c88 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 2 Aug 2019 20:08:27 +0200 Subject: Convert to sfml, starting on manganelo and youtube --- .gitignore | 3 + depends/html-search | 2 +- fonts/Lato-Regular.ttf | Bin 0 -> 656568 bytes include/Program.h | 10 ++- plugins/Manganelo.hpp | 10 +++ plugins/Plugin.hpp | 38 ++++++++++ plugins/Youtube.hpp | 10 +++ project.conf | 2 +- src/Manganelo.cpp | 51 +++++++++++++ src/Plugin.cpp | 17 +++++ src/Program.c | 38 ++++++---- src/Youtube.cpp | 34 +++++++++ src/main.c | 147 ------------------------------------ src/main.cpp | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/main.c | 45 ----------- 15 files changed, 399 insertions(+), 209 deletions(-) create mode 100644 fonts/Lato-Regular.ttf create mode 100644 plugins/Manganelo.hpp create mode 100644 plugins/Plugin.hpp create mode 100644 plugins/Youtube.hpp create mode 100644 src/Manganelo.cpp create mode 100644 src/Plugin.cpp create mode 100644 src/Youtube.cpp delete mode 100644 src/main.c create mode 100644 src/main.cpp delete mode 100644 tests/main.c diff --git a/.gitignore b/.gitignore index 636c6b9..3d24e52 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ sibs-build/ compile_commands.json tests/sibs-build/ tests/compile_commands.json + +.vscode/ +.gdb_history diff --git a/depends/html-search b/depends/html-search index c7500d3..b2fd63a 160000 --- a/depends/html-search +++ b/depends/html-search @@ -1 +1 @@ -Subproject commit c7500d39c6bae0084196ca7a7395ac85fff09928 +Subproject commit b2fd63ad96469b450eb98a8034073f5a8c1cb95c diff --git a/fonts/Lato-Regular.ttf b/fonts/Lato-Regular.ttf new file mode 100644 index 0000000..0f3d0f8 Binary files /dev/null and b/fonts/Lato-Regular.ttf differ diff --git a/include/Program.h b/include/Program.h index 5f986a4..f891d8e 100644 --- a/include/Program.h +++ b/include/Program.h @@ -1,6 +1,10 @@ #ifndef QUICKMEDIA_PROGRAM_H #define QUICKMEDIA_PROGRAM_H +#ifdef __cplusplus +extern "C" { +#endif + /* Return 0 if you want to continue reading */ typedef int (*ProgramOutputCallback)(char *data, int size, void *userdata); @@ -8,6 +12,10 @@ typedef int (*ProgramOutputCallback)(char *data, int size, void *userdata); @args need to have at least 2 arguments. The first which is the program name and the last which is NULL, which indicates end of args */ -int exec_program(char **args, ProgramOutputCallback output_callback, void *userdata); +int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata); + +#ifdef __cplusplus +} +#endif #endif diff --git a/plugins/Manganelo.hpp b/plugins/Manganelo.hpp new file mode 100644 index 0000000..fd56b85 --- /dev/null +++ b/plugins/Manganelo.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "Plugin.hpp" + +namespace QuickMedia { + class Manganelo : public Plugin { + public: + SearchResult search(const std::string &text, std::vector> &result_items) override; + }; +} \ No newline at end of file diff --git a/plugins/Plugin.hpp b/plugins/Plugin.hpp new file mode 100644 index 0000000..bc518a8 --- /dev/null +++ b/plugins/Plugin.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +namespace QuickMedia { + class BodyItem { + public: + BodyItem(const std::string &_title): title(_title) { + + } + + std::string title; + std::string cover_url; + }; + + enum class SearchResult { + OK, + ERR, + NET_ERR + }; + + enum class DownloadResult { + OK, + ERR, + NET_ERR + }; + + class Plugin { + public: + virtual ~Plugin() = default; + + virtual SearchResult search(const std::string &text, std::vector> &result_items) = 0; + protected: + DownloadResult download_to_string(const std::string &url, std::string &result); + }; +} \ No newline at end of file diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp new file mode 100644 index 0000000..ea2918d --- /dev/null +++ b/plugins/Youtube.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "Plugin.hpp" + +namespace QuickMedia { + class Youtube : public Plugin { + public: + SearchResult search(const std::string &text, std::vector> &result_items) override; + }; +} \ No newline at end of file diff --git a/project.conf b/project.conf index c35ebe5..c0fdf68 100644 --- a/project.conf +++ b/project.conf @@ -5,4 +5,4 @@ version = "0.1.0" platforms = ["posix"] [dependencies] -gtk+-3.0 = "3" \ No newline at end of file +sfml-graphics = "2" \ No newline at end of file diff --git a/src/Manganelo.cpp b/src/Manganelo.cpp new file mode 100644 index 0000000..56a6d50 --- /dev/null +++ b/src/Manganelo.cpp @@ -0,0 +1,51 @@ +#include "../plugins/Manganelo.hpp" +#include + +namespace QuickMedia { + SearchResult Manganelo::search(const std::string &text, std::vector> &result_items) { + std::string url = "https://manganelo.com/search/"; + // TODO: Escape @text. Need to replace space with underscore for example. + url += text; + + std::string website_data; + if(download_to_string(url, website_data) != DownloadResult::OK) + return SearchResult::NET_ERR; + + struct ItemData { + std::vector> &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", + [](QuickMediaHtmlNode *node, void *userdata) { + ItemData *item_data = (ItemData*)userdata; + const char *href = quickmedia_html_node_get_attribute_value(node, "href"); + const char *text = quickmedia_html_node_get_text(node); + auto item = std::make_unique(text); + 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(item_data->item_index < item_data->result_items.size()) { + item_data->result_items[item_data->item_index]->cover_url = src; + ++item_data->item_index; + } + }, &item_data); + + cleanup: + quickmedia_html_search_deinit(&html_search); + return result == 0 ? SearchResult::OK : SearchResult::ERR; + } +} \ No newline at end of file diff --git a/src/Plugin.cpp b/src/Plugin.cpp new file mode 100644 index 0000000..d2fe925 --- /dev/null +++ b/src/Plugin.cpp @@ -0,0 +1,17 @@ +#include "../plugins/Plugin.hpp" +#include "../include/Program.h" + +static int accumulate_string(char *data, int size, void *userdata) { + std::string *str = (std::string*)userdata; + str->append(data, size); + return 0; +} + +namespace QuickMedia { + DownloadResult Plugin::download_to_string(const std::string &url, std::string &result) { + const char *args[] = { "curl", "-H", "Accept-Language: en-US,en;q=0.5", "--compressed", "-s", "-L", url.c_str(), nullptr }; + if(exec_program(args, accumulate_string, &result) != 0) + return DownloadResult::NET_ERR; + return DownloadResult::OK; + } +} \ No newline at end of file diff --git a/src/Program.c b/src/Program.c index 526f64b..39957ed 100644 --- a/src/Program.c +++ b/src/Program.c @@ -8,7 +8,7 @@ #define READ_END 0 #define WRITE_END 1 -int exec_program(char **args, ProgramOutputCallback output_callback, void *userdata) { +int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata) { /* 1 arguments */ if(args[0] == NULL) return -1; @@ -28,24 +28,15 @@ int exec_program(char **args, ProgramOutputCallback output_callback, void *userd close(fd[READ_END]); close(fd[WRITE_END]); - execv(args[0], args); + execvp(args[0], args); return 0; } else { /* parent */ close(fd[WRITE_END]); + int result = 0; int status; - waitpid(pid, &status, 0); - if(!WIFEXITED(status)) - return -4; - - int exit_status = WEXITSTATUS(status); - if(exit_status != 0) { - fprintf(stderr, "Failed to execute program, exit status %d\n", exit_status); - return -exit_status; - } char buffer[2048]; - int result = 0; for(;;) { ssize_t bytes_read = read(fd[READ_END], buffer, sizeof(buffer)); @@ -55,14 +46,33 @@ int exec_program(char **args, ProgramOutputCallback output_callback, void *userd int err = errno; fprintf(stderr, "Failed to read from pipe to program %s, error: %s\n", args[0], strerror(err)); result = -err; - break; + goto cleanup; } if(output_callback(buffer, bytes_read, userdata) != 0) break; } + if(waitpid(pid, &status, WUNTRACED) == -1) { + perror("waitpid failed"); + result = -5; + goto cleanup; + } + + if(!WIFEXITED(status)) { + result = -4; + goto cleanup; + } + + int exit_status = WEXITSTATUS(status); + if(exit_status != 0) { + fprintf(stderr, "Failed to execute program, exit status %d\n", exit_status); + result = -exit_status; + goto cleanup; + } + + cleanup: close(fd[READ_END]); return result; } -} \ No newline at end of file +} diff --git a/src/Youtube.cpp b/src/Youtube.cpp new file mode 100644 index 0000000..d8b046f --- /dev/null +++ b/src/Youtube.cpp @@ -0,0 +1,34 @@ +#include "../plugins/Youtube.hpp" +#include + +namespace QuickMedia { + SearchResult Youtube::search(const std::string &text, std::vector> &result_items) { + std::string url = "https://youtube.com/results?search_query="; + // TODO: Escape @text + url += text; + + std::string website_data; + if(download_to_string(url, website_data) != DownloadResult::OK) + return SearchResult::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, "//h3[class=\"yt-lockup-title\"]/a", + [](QuickMediaHtmlNode *node, void *userdata) { + auto *result_items = (std::vector>*)userdata; + const char *href = quickmedia_html_node_get_attribute_value(node, "href"); + const char *title = quickmedia_html_node_get_attribute_value(node, "title"); + printf("a href: %s, title: %s\n", href, title); + + auto item = std::make_unique(title); + result_items->push_back(std::move(item)); + }, &result_items); + + cleanup: + quickmedia_html_search_deinit(&html_search); + return result == 0 ? SearchResult::OK : SearchResult::ERR; + } +} \ No newline at end of file diff --git a/src/main.c b/src/main.c deleted file mode 100644 index a909d28..0000000 --- a/src/main.c +++ /dev/null @@ -1,147 +0,0 @@ -#include -#include -#include -#include -#include - -static GtkWidget *window = NULL; -static GtkWidget *search_entry = NULL; -static GtkWidget *list = NULL; -/* TODO: Optimize this. There shouldn't be a need to copy the whole buffer everytime it's modified */ -static gchar *search_text = NULL; - -typedef struct { - const char *data; - size_t size; -} StringView; - -typedef enum { - DATA_INVALID, - DATA_TITLE, - DATA_DESCRIPTION, - DATA_IMAGE -} DataType; - -static GtkLabel* get_list_item_title(GtkListBoxRow *row) { - GtkWidget *child_widget = GTK_WIDGET(gtk_container_get_children(GTK_CONTAINER(row))->data); - return GTK_LABEL(gtk_grid_get_child_at(GTK_GRID(child_widget), 0, 0)); -} - -static gboolean focus_out_callback(GtkWidget *widget, GdkEvent *event, gpointer userdata) { - gtk_widget_destroy(window); - return FALSE; -} - -static gboolean filter_func(GtkListBoxRow *row, gpointer userdata) { - GtkLabel *row_title_label = get_list_item_title(row); - gboolean show = !search_text || strlen(search_text) == 0 || strstr(gtk_label_get_text(row_title_label), search_text) != NULL; - if(!show && gtk_list_box_get_selected_row(GTK_LIST_BOX(list)) == row) { - gtk_list_box_unselect_row(GTK_LIST_BOX(list), row); - } - return show; -} - -static void list_move_select(GtkListBox *list, gint direction) { - GtkListBoxRow *row = gtk_list_box_get_selected_row(GTK_LIST_BOX(list)); - if(!row) - return; - - gint current_row_index = gtk_list_box_row_get_index(row); - GtkListBoxRow *new_row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(list), current_row_index + direction); - if(new_row) - gtk_list_box_select_row(GTK_LIST_BOX(list), new_row); -} - -static gboolean keypress_callback(GtkWidget *widget, GdkEventKey *event, gpointer userdata) { - if(event->keyval == GDK_KEY_BackSpace) { - gtk_editable_delete_text(GTK_EDITABLE(search_entry), 0, -1); - } else if(event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) { - GtkListBoxRow *row = gtk_list_box_get_selected_row(GTK_LIST_BOX(list)); - if(row) { - if(gtk_widget_is_visible(GTK_WIDGET(row))) { - GtkLabel *row_title_label = get_list_item_title(row); - puts(gtk_label_get_text(row_title_label)); - } else { - puts(gtk_entry_get_text(GTK_ENTRY(search_entry))); - } - } - gtk_widget_destroy(window); - return FALSE; - } else if(event->keyval == GDK_KEY_Escape) { - gtk_widget_destroy(window); - return FALSE; - } else if(event->keyval == GDK_KEY_Up) { - list_move_select(GTK_LIST_BOX(list), -1); - return FALSE; - } else if(event->keyval == GDK_KEY_Down) { - list_move_select(GTK_LIST_BOX(list), 1); - return FALSE; - } else { - gint position = -1; - gtk_editable_insert_text(GTK_EDITABLE(search_entry), event->string, -1, &position); - //printf("key press %d\n", event->keyval); - } - - g_free(search_text); - search_text = gtk_editable_get_chars(GTK_EDITABLE(search_entry), 0, -1); - gtk_list_box_invalidate_filter(GTK_LIST_BOX(list)); - return TRUE; -} - -static GtkWidget* create_entry(const char *text, const char *description) { - GtkWidget *entry = gtk_grid_new(); - GtkWidget *label_widget = gtk_label_new(text); - gtk_grid_attach(GTK_GRID(entry), label_widget, 0, 0, 1, 1); - GtkWidget *description_widget = gtk_label_new(description); - gtk_grid_attach(GTK_GRID(entry), description_widget, 1, 0, 1, 1); - return entry; -} - -static void activate(GtkApplication *app, gpointer userdata) { - window = gtk_application_window_new(app); - gtk_window_set_title(GTK_WINDOW(window), "QuickMedia"); - gtk_window_set_default_size(GTK_WINDOW(window), 600, 300); - - GtkWidget *grid = gtk_grid_new(); - gtk_container_add(GTK_CONTAINER(window), grid); - - search_entry = gtk_search_entry_new(); - gtk_widget_set_sensitive(search_entry, FALSE); - GdkRGBA text_color; - gdk_rgba_parse(&text_color, "black"); - gtk_widget_override_color(search_entry, GTK_STATE_FLAG_INSENSITIVE, &text_color); - gtk_widget_set_hexpand(search_entry, TRUE); - gtk_grid_attach(GTK_GRID(grid), search_entry, 0, 0, 1, 1); - - GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); - gtk_grid_attach(GTK_GRID(grid), scrolled_window, 0, 1, 1, 1); - - list = gtk_list_box_new(); - gtk_widget_set_hexpand(list, TRUE); - gtk_widget_set_vexpand(list, TRUE); - gtk_list_box_set_filter_func(GTK_LIST_BOX(list), filter_func, NULL, NULL); - gtk_container_add(GTK_CONTAINER(scrolled_window), list); - - for(int i = 0; i < 100; ++i) { - GtkWidget *item = create_entry("hello, world!", "description"); - gtk_list_box_insert(GTK_LIST_BOX(list), item, i); - } - - gtk_window_set_resizable(GTK_WINDOW(window), FALSE); - gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_SPLASHSCREEN); - gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS); - gtk_window_set_decorated(GTK_WINDOW(window), FALSE); - gtk_widget_show_all(window); - - gtk_widget_add_events(window, GDK_KEY_PRESS_MASK | GDK_FOCUS_CHANGE_MASK); - g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(keypress_callback), NULL); - g_signal_connect(G_OBJECT(window), "focus-out-event", G_CALLBACK(focus_out_callback), NULL); -} - -int main(int argc, char **argv) { - GtkApplication *app = gtk_application_new("org.dec05eba.quickmedia", G_APPLICATION_FLAGS_NONE); - g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); - int status = g_application_run(G_APPLICATION(app), argc, argv); - g_object_unref(app); - return status; -} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..4689bc5 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,201 @@ +#include "../include/Program.h" +#include "../plugins/Manganelo.hpp" +#include "../plugins/Youtube.hpp" +#include +#include +#include + +const sf::Color front_color(43, 45, 47); +const sf::Color back_color(33, 35, 37); + +namespace QuickMedia { + class Body { + public: + Body(sf::Font &font) : title_text("", font, 14), selected_item(0) { + title_text.setFillColor(sf::Color::White); + } + + void add_item(std::unique_ptr item) { + items.push_back(std::move(item)); + } + + void select_previous_item() { + selected_item = std::max(0, selected_item - 1); + } + + void select_next_item() { + const int last_item = std::max(0, (int)items.size() - 1); + selected_item = std::min(last_item, selected_item + 1); + } + + void reset_selected() { + selected_item = 0; + } + + void clear_items() { + items.clear(); + } + + void draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size) { + const float font_height = title_text.getCharacterSize() + 8.0f; + + sf::RectangleShape image(sf::Vector2f(50, 50)); + image.setFillColor(sf::Color::White); + + sf::RectangleShape item_background; + item_background.setFillColor(front_color); + item_background.setOutlineThickness(1.0f); + item_background.setOutlineColor(sf::Color(63, 65, 67)); + + sf::RectangleShape selected_border(sf::Vector2f(5.0f, 50)); + selected_border.setFillColor(sf::Color::Red); + + int i = 0; + for(const auto &item : items) { + sf::Vector2f item_pos = pos; + if(i == selected_item) { + selected_border.setPosition(pos); + window.draw(selected_border); + item_pos.x += selected_border.getSize().x; + } + + item_background.setPosition(item_pos); + item_background.setSize(sf::Vector2f(size.x, 50)); + window.draw(item_background); + + image.setPosition(item_pos); + window.draw(image); + + title_text.setString(item->title); + title_text.setPosition(item_pos.x + 50 + 10, item_pos.y); + window.draw(title_text); + + pos.y += 50 + 10; + ++i; + } + } + + sf::Text title_text; + int selected_item; + std::vector> items; + }; +} + +static void search(sf::String text, QuickMedia::Body *body, QuickMedia::Plugin *plugin) { + body->clear_items(); + QuickMedia::SearchResult search_result = plugin->search(text, body->items); + fprintf(stderr, "Search result: %d\n", search_result); +} + +int main() { + const float padding_horizontal = 10.0f; + const float padding_vertical = 10.0f; + + sf::RenderWindow window(sf::VideoMode(800, 800), "SFML works!"); + window.setVerticalSyncEnabled(true); + + sf::Font font; + if(!font.loadFromFile("fonts/Lato-Regular.ttf")) { + fprintf(stderr, "Failed to load font!\n"); + abort(); + } + + bool show_placeholder = true; + sf::Color text_placeholder_color(255, 255, 255, 100); + sf::Text search_text("Search...", font, 18); + search_text.setFillColor(text_placeholder_color); + + bool resized = true; + sf::Vector2f window_size(window.getSize().x, window.getSize().y); + + sf::RectangleShape search_background; + search_background.setFillColor(front_color); + search_background.setPosition(padding_horizontal, padding_vertical); + const float search_background_margin_horizontal = 8.0f; + const float search_background_margin_vertical = 4.0f; + + sf::RectangleShape body_background; + body_background.setFillColor(front_color); + + QuickMedia::Body body(font); + QuickMedia::Manganelo manganelo_plugin; + QuickMedia::Youtube youtube_plugin; + QuickMedia::Plugin *plugin = &youtube_plugin; + + while (window.isOpen()) { + sf::Event event; + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed) + window.close(); + 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.type == sf::Event::TextEntered) { + if(event.text.unicode == 8 && !show_placeholder) { // Backspace + sf::String str = search_text.getString(); + if(str.getSize() > 0) { + str.erase(str.getSize() - 1, 1); + search_text.setString(str); + if(str.getSize() == 0) { + show_placeholder = true; + search_text.setString("Search..."); + search_text.setFillColor(text_placeholder_color); + } + } + } else if(event.text.unicode == 13 && !show_placeholder) { // Return + body.reset_selected(); + search(search_text.getString(), &body, plugin); + show_placeholder = true; + search_text.setString("Search..."); + search_text.setFillColor(text_placeholder_color); + } else if(event.text.unicode > 31) { // Non-control character + if(show_placeholder) { + show_placeholder = false; + search_text.setString(""); + search_text.setFillColor(sf::Color::White); + } + sf::String str = search_text.getString(); + str += event.text.unicode; + search_text.setString(str); + } + } + } + + if(resized) { + resized = false; + + float font_height = search_text.getCharacterSize() + 8.0f; + float rect_height = font_height + search_background_margin_vertical * 2.0f; + search_background.setSize(sf::Vector2f(window_size.x - padding_horizontal * 2.0f, rect_height)); + search_text.setPosition(padding_horizontal + search_background_margin_horizontal, padding_vertical + search_background_margin_vertical); + + 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; + } + body_background.setPosition(body_padding_horizontal, search_background.getPosition().y + search_background.getSize().y + body_padding_vertical); + body_background.setSize(sf::Vector2f(body_width, window_size.y)); + } + + window.clear(back_color); + body.draw(window, body_background.getPosition(), body_background.getSize()); + window.draw(search_background); + window.draw(search_text); + window.display(); + } + + return 0; +} + diff --git a/tests/main.c b/tests/main.c deleted file mode 100644 index 3ba930b..0000000 --- a/tests/main.c +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include "../include/Program.h" -#include -#include -#include - -typedef struct { - char *data; - size_t size; -} Buffer; - -static int program_output_callback(char *data, int size, void *userdata) { - Buffer *buf = userdata; - size_t new_size = buf->size + size; - buf->data = realloc(buf->data, new_size + 1); - buf->data[new_size] = '\0'; - memcpy(buf->data + buf->size, data, size); - buf->size = new_size; - return 0; -} - -static void html_search_callback(QuickMediaHtmlNode *node, void *userdata) { - const char *href = quickmedia_html_node_get_attribute_value(node, "href"); - QuickMediaStringView text = quickmedia_html_node_get_text(node); - printf("a href: %s, text: %.*s\n", href, text.size, text.data); -} - -int main(int argc, char **argv) { - Buffer buf; - buf.data = NULL; - buf.size = 0; - char *args[] = { "/usr/bin/curl", "-s", "-L", "https://manganelo.com/search/naruto", NULL }; - exec_program(args, program_output_callback, &buf); - /*printf("%s\n", buf.data);*/ - - QuickMediaHtmlSearch html_search; - if(quickmedia_html_search_init(&html_search, buf.data) != 0) - return -1; - if(quickmedia_html_find_nodes_xpath(&html_search, "//h3[class=\"story_name\"]//a", html_search_callback, NULL) != 0) - return -1; - quickmedia_html_search_deinit(&html_search); - - free(buf.data); - return 0; -} -- cgit v1.2.3