From 31850456aa4ef27162b6d749ab00ff2249873a72 Mon Sep 17 00:00:00 2001 From: sergiotarxz Date: Sun, 16 Jan 2022 01:32:46 +0100 Subject: [PATCH] Adding initial search view. --- include/openmg/backend/readmng.h | 4 + include/openmg/util/regex.h | 2 +- include/openmg/util/soup.h | 10 +++ include/openmg/view/controls.h | 3 + include/openmg/view/search.h | 6 ++ meson.build | 4 +- src/backend/readmng.c | 127 +++++++++++++++++++++++++++++-- src/util/regex.c | 2 +- src/util/soup.c | 99 ++++++++++++++++++++++++ src/view/main_view.c | 28 ++++++- src/view/search.c | 11 +++ 11 files changed, 286 insertions(+), 10 deletions(-) create mode 100644 include/openmg/view/search.h create mode 100644 src/view/search.c diff --git a/include/openmg/backend/readmng.h b/include/openmg/backend/readmng.h index ae44321..4b9862b 100644 --- a/include/openmg/backend/readmng.h +++ b/include/openmg/backend/readmng.h @@ -30,6 +30,10 @@ mg_backend_readmng_new (void); GListStore * mg_backend_readmng_get_featured_manga (MgBackendReadmng *self); +GListStore * +mg_backend_readmng_search (MgBackendReadmng *self, + const char *search_query); + void mg_backend_readmng_retrieve_manga_details (MgBackendReadmng *self, MgManga *manga); diff --git a/include/openmg/util/regex.h b/include/openmg/util/regex.h index 3d1dc7e..54c073c 100644 --- a/include/openmg/util/regex.h +++ b/include/openmg/util/regex.h @@ -27,6 +27,6 @@ mg_util_regex_splitted_string_free (MgUtilRegex *self, struct SplittedString *splitted_string); char * mg_util_regex_match_1 (MgUtilRegex *self, - char *re_str, char *subject); + const char *re_str, const char *subject); G_END_DECLS diff --git a/include/openmg/util/soup.h b/include/openmg/util/soup.h index 4d503c4..f7b9388 100644 --- a/include/openmg/util/soup.h +++ b/include/openmg/util/soup.h @@ -9,7 +9,17 @@ G_DECLARE_FINAL_TYPE (MgUtilSoup, mg_util_soup, MG, UTIL_SOUP, GObject) MgUtilSoup * mg_util_soup_new (); +typedef struct { + char *key; + char *value; +} SoupParam; + char * mg_util_soup_get_request (MgUtilSoup *self, const char *const url, gsize *size_response_text); +char * +mg_util_soup_post_request_url_encoded (MgUtilSoup *self, + const char *url, SoupParam *body, gsize body_len, + SoupParam *headers, gsize headers_len, + gsize *size_response_text); G_END_DECLS diff --git a/include/openmg/view/controls.h b/include/openmg/view/controls.h index 539334d..422f791 100644 --- a/include/openmg/view/controls.h +++ b/include/openmg/view/controls.h @@ -2,6 +2,9 @@ #include +#include +#include + typedef struct { AdwHeaderBar *header; AdwLeaflet *views_leaflet; diff --git a/include/openmg/view/search.h b/include/openmg/view/search.h new file mode 100644 index 0000000..6327aee --- /dev/null +++ b/include/openmg/view/search.h @@ -0,0 +1,6 @@ +#include + +#include + +GtkWidget * +create_search_view (ControlsAdwaita *controls); diff --git a/meson.build b/meson.build index 84c3e89..9753e8b 100644 --- a/meson.build +++ b/meson.build @@ -9,7 +9,8 @@ openmgdeps = [ dependency('libxml-2.0'), dependency('libpcre2-8'), dependency('gio-2.0'), - dependency('sqlite3') + dependency('sqlite3'), + dependency('json-glib-1.0'), ] sources = [ @@ -30,6 +31,7 @@ sources = [ 'src/database.c', 'src/database/statement.c', 'src/view/explore.c', + 'src/view/search.c', 'src/main.c', ] diff --git a/src/backend/readmng.c b/src/backend/readmng.c index 52a5337..74fdd96 100644 --- a/src/backend/readmng.c +++ b/src/backend/readmng.c @@ -1,4 +1,6 @@ #include +#include + #include #ifndef PCRE2_CODE_UNIT_WIDTH @@ -63,6 +65,8 @@ static MgMangaChapter * mg_backend_readmng_loop_li_chapter ( MgBackendReadmng *self, xmlNodePtr li); +static char * +mg_backend_readmng_fetch_search (MgBackendReadmng *self, const char *search_query); static GListModel * mg_backend_readmng_parse_page (MgBackendReadmng *self, xmlDocPtr html_document); @@ -92,6 +96,8 @@ static GListStore * mg_backend_readmng_parse_main_page (MgBackendReadmng *self, const xmlDocPtr html_document); static xmlDocPtr mg_backend_readmng_fetch_xml_main_page (MgBackendReadmng *self); +static char * +mg_backend_readmng_get_id_manga_link_from_string (MgBackendReadmng *self, const char *url); MgBackendReadmng * mg_backend_readmng_new(void) { @@ -226,6 +232,113 @@ mg_backend_readmng_fetch_page_url (MgBackendReadmng *self, return document; } +GListStore * +mg_backend_readmng_search (MgBackendReadmng *self, + const char *search_query) { + char *response = mg_backend_readmng_fetch_search (self, search_query); + JsonParser *parser = json_parser_new (); + GListStore *mangas = g_list_store_new(MG_TYPE_MANGA); + GError *error = NULL; + JsonNode *root = NULL; + JsonArray *mangas_json_array = NULL; + guint mangas_json_array_len = 0; + + json_parser_load_from_data (parser, response, -1, &error); + if (error) { + g_warning ("Unable to parse json: %s.", error->message); + g_clear_error (&error); + goto cleanup_mg_backend_readmng_search; + } + root = json_parser_get_root (parser); + if (json_node_get_node_type (root) != JSON_NODE_ARRAY) { + goto cleanup_mg_backend_readmng_search; + } + mangas_json_array = json_node_get_array (root); + mangas_json_array_len = json_array_get_length ( + mangas_json_array); + for (guint i = 0; i < mangas_json_array_len; i++) { + JsonObject *manga_json_object = + json_array_get_object_element (mangas_json_array, i); + char *id_manga = NULL; + const char *url = json_object_get_string_member + (manga_json_object, "url"); + const char *title = json_object_get_string_member + (manga_json_object, "title"); + const char *image = json_object_get_string_member + (manga_json_object, "image"); + + id_manga = mg_backend_readmng_get_id_manga_link_from_string (self, url); + g_list_store_append (mangas, mg_manga_new (image, title, id_manga)); + + pcre2_substring_free ((PCRE2_UCHAR8 *) id_manga); + } +cleanup_mg_backend_readmng_search: + g_clear_object (&parser); + g_free (response); + response = NULL; + return mangas; +} + +static char * +mg_backend_readmng_fetch_search (MgBackendReadmng *self, const char *search_query) { + MgUtilSoup *util_soup; + MgUtilString *string_util; + + char *request_url; + + size_t request_url_len; + size_t response_len = 0; + + util_soup = mg_util_soup_new (); + string_util = mg_util_string_new (); + request_url_len = snprintf ( NULL, 0, "%s/%s/", self->base_url, "service/search"); + request_url = mg_util_string_alloc_string (string_util, request_url_len); + snprintf ( request_url, request_url_len+1, "%s/%s/", self->base_url, "service/search"); + + SoupParam headers[] = { + { + .key = "Accept", + .value = "application/json, text/javascript, */*; q=0.01" + }, + { + .key = "Content-Type", + .value = "application/x-www-form-urlencoded; charset=UTF-8" + }, + { + .key = "X-Requested-With", + .value = "XMLHttpRequest" + } + }; + + char *phrase = g_malloc (strlen (search_query) + 1); + snprintf ( phrase, strlen (search_query) + 1, "%s", search_query); + + SoupParam body[] = { + { + .key = "dataType", + .value = "json" + }, + { + .key = "phrase", + .value = phrase + } + }; + + size_t headers_len = sizeof headers / sizeof *headers; + size_t body_len = sizeof body / sizeof *body; + + char *text_response = mg_util_soup_post_request_url_encoded (util_soup, + request_url, body, body_len, headers, headers_len, &response_len); + + g_free (request_url); + g_free (phrase); + request_url = NULL; + g_clear_object (&util_soup); + g_clear_object (&string_util); + + return text_response; +} + GListStore * mg_backend_readmng_get_featured_manga (MgBackendReadmng *self) { @@ -402,7 +515,6 @@ mg_backend_readmng_fetch_xml_details (MgBackendReadmng *self, size_t request_url_len; size_t response_len = 0; - util_soup = mg_util_soup_new (); string_util = mg_util_string_new (); manga_id = mg_manga_get_id (manga); @@ -573,13 +685,18 @@ mg_backend_readmng_find_a_link_chapter (MgBackendReadmng *self, static char * mg_backend_readmng_get_id_manga_link (MgBackendReadmng *self, xmlNodePtr a) { - char *re_str = "readmng\\.com/([^/]+)"; MgUtilXML *xml_utils = self->xml_utils; - MgUtilRegex *regex_util = mg_util_regex_new (); char *href = mg_util_xml_get_attr (xml_utils, a, "href"); - char *result = mg_util_regex_match_1 (regex_util, re_str, href); - + char *result = mg_backend_readmng_get_id_manga_link_from_string (self, href); g_free (href); + return result; +} + +static char * +mg_backend_readmng_get_id_manga_link_from_string (MgBackendReadmng *self, const char *url) { + MgUtilRegex *regex_util = mg_util_regex_new (); + char *re_str = "readmng\\.com/([^/]+)"; + char *result = mg_util_regex_match_1 (regex_util, re_str, url); g_clear_object (®ex_util); return result; } diff --git a/src/util/regex.c b/src/util/regex.c index 078ec7c..bab82ea 100644 --- a/src/util/regex.c +++ b/src/util/regex.c @@ -131,7 +131,7 @@ cleanup_iterate_string_to_split: char * mg_util_regex_match_1 (MgUtilRegex *self, - char *re_str, char *subject) { + const char *re_str, const char *subject) { pcre2_code *re; pcre2_match_data *match_data; diff --git a/src/util/soup.c b/src/util/soup.c index 429480c..bdc8342 100644 --- a/src/util/soup.c +++ b/src/util/soup.c @@ -1,3 +1,6 @@ +#include +#include + #include #include @@ -23,6 +26,7 @@ mg_util_soup_class_init (MgUtilSoupClass *class) { static void mg_util_soup_init (MgUtilSoup *self) { } + char * mg_util_soup_get_request (MgUtilSoup *self, const char *url, gsize *size_response_text) { SoupSession *soup_session; @@ -53,6 +57,101 @@ mg_util_soup_get_request (MgUtilSoup *self, const char *url, gsize *size_respons return return_value; } + +char * +mg_util_soup_post_request_url_encoded (MgUtilSoup *self, + const char *url, SoupParam *body, gsize body_len, + SoupParam *headers, gsize headers_len, + gsize *size_response_text) { + SoupSession *soup_session; + SoupMessage *msg; + SoupMessageBody *request_body; + SoupMessageHeaders *request_headers; + + GValue response = G_VALUE_INIT; + GValue request = G_VALUE_INIT; + GValue request_headers_value = G_VALUE_INIT; + + *size_response_text = 0; + + g_value_init (&response, G_TYPE_BYTES); + g_value_init (&request, SOUP_TYPE_MESSAGE_BODY); + g_value_init (&request_headers_value, + SOUP_TYPE_MESSAGE_HEADERS); + + soup_session = soup_session_new (); + msg = soup_message_new ("POST", url); + g_object_get_property ( + G_OBJECT (msg), + "request-body", + &request); + + g_object_get_property ( + G_OBJECT (msg), + "request-headers", + &request_headers_value); + + soup_message_set_request (msg, + "application/x-www-form-urlencoded; charset=UTF-8", + SOUP_MEMORY_COPY, "", 1); + + request_body = g_value_peek_pointer (&request); + request_headers = g_value_peek_pointer ( + &request_headers_value); + + for (int i = 0; i < body_len; i++) { + char *key = g_uri_escape_string (body[i].key, + NULL, false); + size_t key_len = strlen (key) + 1; + char *value = g_uri_escape_string (body[i].value, + NULL, false); + size_t value_len = strlen (value) + 1; + + if (body_len) { + soup_message_body_append (request_body, + SOUP_MEMORY_COPY, "&", 1); + } + + soup_message_body_append (request_body, + SOUP_MEMORY_COPY, key, key_len); + soup_message_body_append (request_body, + SOUP_MEMORY_COPY, "=", 1); + soup_message_body_append (request_body, + SOUP_MEMORY_COPY, value, value_len); + + g_free (key); + g_free (value); + } + soup_message_body_append (request_body, + SOUP_MEMORY_COPY, "", 1); + + for (int i = 0; i < headers_len; i++) { + soup_message_headers_append (request_headers, + headers[i].key, + headers[i].value); + } + + soup_session_send_message (soup_session, msg); + g_object_get_property( + G_OBJECT (msg), + "response-body-data", + &response); + + const char *html_response = g_bytes_get_data ((GBytes *) + g_value_peek_pointer (&response), + size_response_text); + + char *return_value = mg_util_soup_copy_binary_data(self, html_response, *size_response_text); + + g_value_unset (&response); + g_value_unset (&request); + + g_clear_object (&soup_session); + g_clear_object (&msg); + + return return_value; +} + static char * mg_util_soup_copy_binary_data (MgUtilSoup *self, const char *input, size_t size) { char *response = NULL; diff --git a/src/view/main_view.c b/src/view/main_view.c index a163cef..650abc2 100644 --- a/src/view/main_view.c +++ b/src/view/main_view.c @@ -5,6 +5,7 @@ #include #include +#include static AdwHeaderBar * create_headerbar (GtkBox *box, ControlsAdwaita *controls, GtkButton **out_previous); @@ -15,6 +16,8 @@ go_back_view (GtkButton *previous, gpointer user_data); typedef void (*swipe_back_t)(AdwLeaflet *, gboolean); static AdwLeaflet * create_explore_leaflet (ControlsAdwaita *controls, swipe_back_t swipe_back); +static AdwLeaflet * +create_search_leaflet (ControlsAdwaita *controls, swipe_back_t swipe_back); static void activate (AdwApplication *app, @@ -29,6 +32,7 @@ activate (AdwApplication *app, ControlsAdwaita *controls = g_malloc (sizeof *controls); GtkButton *previous = NULL; AdwLeaflet *views_leaflet_explore; + AdwLeaflet *views_leaflet_search; AdwHeaderBar *header_bar; swipe_back_t swipe_back = (swipe_back_t) dlsym @@ -45,6 +49,7 @@ activate (AdwApplication *app, views_leaflet_explore = create_explore_leaflet (controls, swipe_back); + views_leaflet_search = create_search_leaflet (controls, swipe_back); header_bar = create_headerbar (box, controls, &previous); controls->header = header_bar; controls->previous = previous; @@ -52,12 +57,32 @@ activate (AdwApplication *app, AdwViewStackPage *explore_page = adw_view_stack_add_titled (view_stack, GTK_WIDGET (views_leaflet_explore), "explore", "Explore"); + AdwViewStackPage *search_page = adw_view_stack_add_titled (view_stack, GTK_WIDGET (views_leaflet_search), + "search", + "Search"); + adw_view_stack_page_set_icon_name (explore_page, "view-list-symbolic"); + adw_view_stack_page_set_icon_name (search_page, "system-search-symbolic"); + gtk_box_append (box, GTK_WIDGET (view_stack)); gtk_widget_show (window); } +static AdwLeaflet * +create_search_leaflet (ControlsAdwaita *controls, swipe_back_t swipe_back) { + AdwLeaflet *views_leaflet = ADW_LEAFLET (adw_leaflet_new ()); + GtkWidget *search_view; + swipe_back (views_leaflet, 1); + search_view = create_search_view (controls); + + adw_leaflet_append (views_leaflet, search_view); + + adw_leaflet_set_can_unfold (views_leaflet, false); + + return views_leaflet; +} + static AdwLeaflet * create_explore_leaflet (ControlsAdwaita *controls, swipe_back_t swipe_back) { AdwLeaflet *views_leaflet_explore = ADW_LEAFLET (adw_leaflet_new ()); @@ -75,8 +100,7 @@ create_explore_leaflet (ControlsAdwaita *controls, swipe_back_t swipe_back) { static GtkBox * create_main_box (AdwApplicationWindow *window) { - GtkWidget *box = gtk_box_new( - GTK_ORIENTATION_VERTICAL, + GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); adw_application_window_set_content( window, diff --git a/src/view/search.c b/src/view/search.c new file mode 100644 index 0000000..011758b --- /dev/null +++ b/src/view/search.c @@ -0,0 +1,11 @@ +#include + +GtkWidget * +create_search_view (ControlsAdwaita *controls) { + GtkWidget *search_view = gtk_box_new ( + GTK_ORIENTATION_VERTICAL, 10); + + GtkWidget *search_entry = gtk_entry_new (); + gtk_box_append (GTK_BOX (search_view), search_entry);; + return search_view; +}