diff --git a/Makefile b/Makefile index 673fbf8..076b7fe 100644 --- a/Makefile +++ b/Makefile @@ -6,4 +6,4 @@ LDFLAGS := $(shell pkg-config --libs ${LIBS}) CC_COMMAND := ${CC} ${INCDIR} ${CFLAGS} all: build build: - ${CC_COMMAND} readmng.c manga.c main.c -o main ${LDFLAGS} -ggdb + ${CC_COMMAND} src/manga.c src/backend/readmng.c manga.c main.c -o main ${LDFLAGS} -ggdb diff --git a/include/manga.h b/include/manga.h index b0e6c6d..da8b553 100644 --- a/include/manga.h +++ b/include/manga.h @@ -1,6 +1,4 @@ -#ifndef MANGA -#define MANGA - +#pragma once #include #include #include @@ -59,4 +57,3 @@ loop_search_class (const xmlNodePtr node, xmlNodePtr *nodes, const char * class, size_t *len); char * copy_binary_data (const char *input, size_t size); -#endif diff --git a/include/openmg/backend/readmng.h b/include/openmg/backend/readmng.h new file mode 100644 index 0000000..093b428 --- /dev/null +++ b/include/openmg/backend/readmng.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +G_BEGIN_DECLS; + +/* + * Type declaration + */ +#define MG_TYPE_BACKEND_READMNG mg_backend_readmng_get_type () +G_DECLARE_FINAL_TYPE (MgBackendReadmng, mg_backend_readmng, MG, BACKEND_READMNG, GObject) + +void +mg_backend_readmng_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec); +void +mg_backend_readmng_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec); +/* + * Method definitions. + */ +MgBackendReadmng * +mg_backend_readmng_new (void); + +MgManga ** +mg_backend_readmng_get_featured_manga (MgBackendReadmng *self, size_t *len); + +G_END_DECLS diff --git a/include/openmg/manga.h b/include/openmg/manga.h new file mode 100644 index 0000000..4196b48 --- /dev/null +++ b/include/openmg/manga.h @@ -0,0 +1,20 @@ +#pragma once +#include + +G_BEGIN_DECLS; + +/* + * Type declaration + */ +#define MG_TYPE_MANGA mg_manga_get_type() +G_DECLARE_FINAL_TYPE (MgManga, mg_manga, MG, MANGA, GObject) + +/* + * Method definitions. + */ +char *mg_manga_get_image_url(MgManga *mg_manga); +char *mg_manga_get_title(MgManga *mg_manga); + +MgManga *mg_manga_new (const char *const image_url, const char *const title); + +G_END_DECLS diff --git a/main.c b/main.c index b9ba9e8..7ad057b 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,9 @@ #include #include +#include +#include + #include #include @@ -10,11 +13,13 @@ GtkBox * create_main_box (AdwApplicationWindow *window); GtkBox * create_manga_container (); -AdwCarousel * -create_adw_caroulsel (GtkBox *box); +GtkListBox * +create_list_box (GtkBox *box); +void +g_object_set_property_int(GObject *object, char *property_key, int value); void -fill_carousel_of_mangas (AdwCarousel *carousel); +fill_list_of_mangas (GtkListBox *list); void activate (AdwApplication *app, @@ -25,23 +30,26 @@ activate (AdwApplication *app, GtkBox *box = create_main_box( ADW_APPLICATION_WINDOW (window)); - AdwCarousel *carousel; + GtkListBox *list; create_headerbar (box); - carousel = create_adw_caroulsel (box); - fill_carousel_of_mangas (carousel); + list = create_list_box (box); + gtk_widget_set_vexpand (GTK_WIDGET (list), 1); + fill_list_of_mangas (list); gtk_widget_show (window); } void -fill_carousel_of_mangas (AdwCarousel *carousel) { - struct Manga *mangas; - struct Manga *manga; - GtkBox *manga_container; +fill_list_of_mangas (GtkListBox *list) { + MgManga **mangas; + MgManga *manga; + GtkWidget *row; size_t len_mangas = 0; - mangas = retrieve_readmng_title_mangas (&len_mangas); + + MgBackendReadmng *readmng = mg_backend_readmng_new (); + mangas = mg_backend_readmng_get_featured_manga (readmng, &len_mangas); for (int i = 0; iimage_url, &size_downloaded_image); + manga = mangas[i]; + + downloaded_image = get_request (mg_manga_get_image_url(manga), &size_downloaded_image); tmp_image = g_file_new_tmp ("mangareadertmpfileXXXXXX", &iostream, &error @@ -65,16 +72,27 @@ fill_carousel_of_mangas (AdwCarousel *carousel) { } error = NULL; g_output_stream_write (g_io_stream_get_output_stream (G_IO_STREAM (iostream)), - downloaded_image, size_downloaded_image, NULL, &error); + downloaded_image, size_downloaded_image, NULL, &error); if (error) { fprintf (stderr, "Unable to write file: %s\n", error->message); return; } picture = gtk_picture_new_for_file (tmp_image); - gtk_box_append (manga_container, picture); + g_object_set_property_int (G_OBJECT(picture), "height-request", 200); + row = gtk_list_box_row_new (); + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), picture); + gtk_list_box_append (list, row); } } +void +g_object_set_property_int(GObject *object, char *property_key, int value) { + GValue property = G_VALUE_INIT; + g_value_init (&property, G_TYPE_INT); + g_value_set_int (&property, value); + g_object_set_property (object, property_key, &property); +} + GtkBox * create_manga_container () { GtkBox *manga_container; @@ -95,11 +113,11 @@ create_main_box (AdwApplicationWindow *window) { return GTK_BOX (box); } -AdwCarousel * -create_adw_caroulsel (GtkBox *box) { - GtkWidget *carousel = adw_carousel_new (); - gtk_box_append (box, carousel); - return ADW_CAROUSEL (carousel); +GtkListBox * +create_list_box (GtkBox *box) { + GtkWidget *list = gtk_list_box_new (); + gtk_box_append (box, list); + return GTK_LIST_BOX (list); } AdwHeaderBar * @@ -117,7 +135,7 @@ create_headerbar (GtkBox *box) { return ADW_HEADER_BAR (header); } - int +int main (int argc, char **argv) { diff --git a/readmng.c b/readmng.c deleted file mode 100644 index 17a6222..0000000 --- a/readmng.c +++ /dev/null @@ -1,153 +0,0 @@ -#include -#include - -const char *readmng_url = "https://www.readmng.com/"; - -struct Manga * -parse_readmng_title_page (const xmlDocPtr html_document, - size_t *const len); -xmlNodePtr -retrieve_slides (const xmlDocPtr html_document); -xmlNodePtr -retrieve_ul_slides (xmlNodePtr const slides); -xmlNodePtr * -retrieve_li_slides (xmlNodePtr const slides, size_t *li_len); -xmlNodePtr -retrieve_img_from_thumnail (xmlNodePtr thumbnail); -xmlNodePtr -retrieve_thumbnail_from_li (xmlNodePtr current_li); -xmlNodePtr -retrieve_title_from_li (xmlNodePtr li); -struct Manga * -extract_manga_info_from_current_li (struct Manga *mangas, - xmlNodePtr current_li, size_t *len); - -struct Manga * -retrieve_readmng_title_mangas (size_t *const len) { - xmlDocPtr html_response; - gsize size_response_text; - struct Manga *mangas; - char *response_text = get_request (readmng_url, - &size_response_text); - html_response = htmlReadMemory (response_text, - size_response_text, - NULL, - NULL, - HTML_PARSE_RECOVER | HTML_PARSE_NODEFDTD - | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING - ); - mangas = parse_readmng_title_page (html_response, len); - xmlFreeDoc (html_response); - g_free (response_text); - return mangas; -} - -struct Manga * -parse_readmng_title_page (const xmlDocPtr html_document, - size_t *const len) { - struct Manga *mangas = NULL; - xmlNodePtr slides = retrieve_slides (html_document); - *len = 0; - size_t li_len = 0; - xmlNodePtr *li = retrieve_li_slides (slides, &li_len); - for (int i = 0; iimage_url = get_attr (img, "src"); - manga->title = (char *) xmlNodeGetContent (title); - } - return mangas; -} - -xmlNodePtr -retrieve_title_from_li (xmlNodePtr li) { - size_t title_len = 0; - xmlNodePtr *title = find_class (li, "title", &title_len, NULL, 1); - if (title_len) return title[0]; - return NULL; -} - -xmlNodePtr -retrieve_img_from_thumnail (xmlNodePtr thumbnail) { - for (xmlNodePtr child = thumbnail->children; child; child=child->next) { - if (!strcmp((char *)child->name, "img")) { - return child; - } - } - return NULL; -} - -xmlNodePtr -retrieve_thumbnail_from_li (xmlNodePtr current_li) { - size_t thumbnail_len = 0; - xmlNodePtr *thumbnail = find_class (current_li, "thumbnail", - &thumbnail_len, NULL, 1); - if (thumbnail_len) return thumbnail[0]; - return NULL; -} - -xmlNodePtr * -retrieve_li_slides (xmlNodePtr const slides, size_t *li_len) { - xmlNodePtr ul_slides = retrieve_ul_slides (slides); - xmlNodePtr *li = NULL; - for (xmlNodePtr child = ul_slides->children; child; child=child->next) { - (*li_len)++; - li = g_realloc(li, sizeof *li * *li_len); - li[*li_len-1] = xmlCopyNode(child, XML_COPY_NODE_RECURSIVE); - } - return li; -} - -xmlNodePtr -retrieve_ul_slides (xmlNodePtr const slides) { - for (xmlNodePtr child = slides->children; child; child = child->next) { - if (!strcmp((char *) child->name, "ul")) { - return child; - } - } - return NULL; -} - -xmlNodePtr -retrieve_slides (const xmlDocPtr html_document) { - xmlNodePtr *nodes = NULL; - xmlXPathObjectPtr xpath_result = NULL; - xpath_result = get_nodes_xpath_expression (html_document, - "//div[@class]"); - xmlNodePtr slides = NULL; - xmlNodeSetPtr node_set = NULL; - size_t matching_classes_len = 0; - - node_set = xpath_result->nodesetval; - if (!node_set) { - fprintf(stderr, "No match\n"); - return NULL; - } - for (int i = 0; i < node_set->nodeNr; i++) { - xmlNodePtr node = node_set->nodeTab[i]; - nodes = loop_search_class (node, nodes, "slides", &matching_classes_len); - } - if (nodes) { - slides = nodes[0]; - } - if (xpath_result) { - xmlXPathFreeObject(xpath_result); - } - return slides; -} diff --git a/src/backend/readmng.c b/src/backend/readmng.c new file mode 100644 index 0000000..235323e --- /dev/null +++ b/src/backend/readmng.c @@ -0,0 +1,253 @@ +#include + +#include +#include + +#include + +typedef enum { + MG_BACKEND_READMNG_BASE_URL = 1, + MG_BACKEND_READMNG_N_PROPERTIES +} MgBackendReadmngProperties; + +struct _MgBackendReadmng { + GObject parent_instance; + char *base_url; + size_t main_page_html_len; + char *main_page_html; + MgManga *(*get_featured_manga) (); +}; + +G_DEFINE_TYPE (MgBackendReadmng, mg_backend_readmng, G_TYPE_OBJECT) + +static GParamSpec *mg_backend_readmng_properties[MG_BACKEND_READMNG_N_PROPERTIES] = { NULL, }; + +static void +mg_backend_readmng_class_init (MgBackendReadmngClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS (class); + object_class->set_property = mg_backend_readmng_set_property; + object_class->get_property = mg_backend_readmng_get_property; + + mg_backend_readmng_properties[MG_BACKEND_READMNG_BASE_URL] = g_param_spec_string ("base_url", + "BaseURL", + "Url of the backend.", + NULL, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, + MG_BACKEND_READMNG_N_PROPERTIES, + mg_backend_readmng_properties); +} + +static xmlNodePtr +mg_backend_readmng_retrieve_img_from_thumbnail (MgBackendReadmng *self, xmlNodePtr thumbnail); +static xmlNodePtr +mg_backend_readmng_retrieve_ul_slides(MgBackendReadmng *self, xmlNodePtr slides) ; +static MgManga ** +mg_backend_readmng_extract_manga_info_from_current_li (MgBackendReadmng *self, + MgManga **mangas, xmlNodePtr current_li, size_t *len); +static xmlNodePtr * +mg_backend_readmng_retrieve_li_slides (MgBackendReadmng *self, const xmlNodePtr slides, size_t *li_len); +static xmlNodePtr +mg_backend_readmng_retrieve_slides (MgBackendReadmng *self, const xmlDocPtr html_document); +static const char * +mg_backend_readmng_get_main_page (MgBackendReadmng *self, size_t *len); +static MgManga ** +mg_backend_readmng_parse_main_page (MgBackendReadmng *self, size_t *len, const xmlDocPtr html_document); +static xmlDocPtr +mg_backend_readmng_fetch_xml_main_page (MgBackendReadmng *self); + +MgBackendReadmng * +mg_backend_readmng_new(void) { + return (MG_BACKEND_READMNG) (g_object_new (MG_TYPE_BACKEND_READMNG, NULL)); +} + +static void +mg_backend_readmng_init (MgBackendReadmng *self) { + if (!self->base_url) { + self->base_url = "https://www.readmng.com/"; + } +} + +const char * +mg_backend_readmng_get_base_url (MgBackendReadmng *self) { + return self->base_url; +} + +void +mg_backend_readmng_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { + MgBackendReadmng *self = MG_BACKEND_READMNG (object); + switch ((MgBackendReadmngProperties) property_id) { + case MG_BACKEND_READMNG_BASE_URL: + g_free (self->base_url); + self->base_url = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +mg_backend_readmng_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) { + MgBackendReadmng *self = MG_BACKEND_READMNG (object); + switch ((MgBackendReadmngProperties) property_id) { + case MG_BACKEND_READMNG_BASE_URL: + g_value_set_string (value, self->base_url); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +MgManga ** +mg_backend_readmng_get_featured_manga (MgBackendReadmng *self, size_t *len) { + MgManga **mangas; + xmlDocPtr html_document; + html_document = mg_backend_readmng_fetch_xml_main_page (self); + mangas = mg_backend_readmng_parse_main_page (self, len, html_document); + return mangas; + +} + +static xmlDocPtr +mg_backend_readmng_fetch_xml_main_page (MgBackendReadmng *self) { + size_t size_response_text = 0; + return htmlReadMemory (mg_backend_readmng_get_main_page (self, &size_response_text), + size_response_text, + NULL, + NULL, + HTML_PARSE_RECOVER | HTML_PARSE_NODEFDTD + | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING + ); +} + +static const char * +mg_backend_readmng_get_main_page (MgBackendReadmng *self, size_t *len) { + if (!self->main_page_html) { + self->main_page_html = get_request (self->base_url, + &self->main_page_html_len); + } + if (len) { + *len = self->main_page_html_len; + } + return self->main_page_html; +} + +static MgManga ** +mg_backend_readmng_parse_main_page (MgBackendReadmng *self, size_t *len, const xmlDocPtr html_document) { + MgManga **mangas = NULL; + xmlNodePtr *li; + + *len = 0; + xmlNodePtr slides = mg_backend_readmng_retrieve_slides (self, html_document); + + size_t li_len = 0; + li = mg_backend_readmng_retrieve_li_slides (self, slides, &li_len); + for (int i = 0; ichildren; child; child=child->next) { + (*li_len)++; + li = g_realloc(li, sizeof *li * *li_len); + li[*li_len-1] = xmlCopyNode(child, XML_COPY_NODE_RECURSIVE); + } + return li; +} + +static xmlNodePtr +mg_backend_readmng_retrieve_ul_slides(MgBackendReadmng *self, xmlNodePtr slides) { + for (xmlNodePtr child = slides->children; child; child = child->next) { + if (!strcmp((char *) child->name, "ul")) { + return child; + } + } + return NULL; +} + +static xmlNodePtr +mg_backend_readmng_retrieve_slides (MgBackendReadmng *self, const xmlDocPtr html_document) { + xmlNodePtr *nodes = NULL; + xmlXPathObjectPtr xpath_result = NULL; + xpath_result = get_nodes_xpath_expression (html_document, + "//div[@class]"); + xmlNodePtr slides = NULL; + xmlNodeSetPtr node_set = NULL; + size_t matching_classes_len = 0; + + node_set = xpath_result->nodesetval; + if (!node_set) { + fprintf(stderr, "No match\n"); + return NULL; + } + for (int i = 0; i < node_set->nodeNr; i++) { + xmlNodePtr node = node_set->nodeTab[i]; + nodes = loop_search_class (node, nodes, "slides", &matching_classes_len); + } + if (nodes) { + slides = nodes[0]; + } + if (xpath_result) { + xmlXPathFreeObject(xpath_result); + } + return slides; +} + +static xmlNodePtr +mg_backend_readmng_retrieve_thumbnail_from_li (MgBackendReadmng *self, xmlNodePtr current_li) { + size_t thumbnail_len = 0; + xmlNodePtr *thumbnail = find_class (current_li, "thumbnail", + &thumbnail_len, NULL, 1); + if (thumbnail_len) return thumbnail[0]; + return NULL; +} + +static xmlNodePtr +mg_backend_readmng_retrieve_title_from_li (MgBackendReadmng *self, xmlNodePtr li) { + size_t title_len = 0; + xmlNodePtr *title = find_class (li, "title", &title_len, NULL, 1); + if (title_len) return title[0]; + return NULL; +} + +static MgManga ** +mg_backend_readmng_extract_manga_info_from_current_li (MgBackendReadmng *self, + MgManga **mangas, xmlNodePtr current_li, size_t *len) { + + xmlNodePtr thumbnail = mg_backend_readmng_retrieve_thumbnail_from_li (self, current_li); + xmlNodePtr title = mg_backend_readmng_retrieve_title_from_li (self, current_li); + xmlNodePtr img; + + if (thumbnail && title && (img = mg_backend_readmng_retrieve_img_from_thumbnail (self, thumbnail))) { + (*len)++; + mangas = g_realloc(mangas, sizeof *mangas * *len); + mangas[*len-1] = mg_manga_new (get_attr (img, "src"), (char *)xmlNodeGetContent (title)); + } + return mangas; +} + +static xmlNodePtr +mg_backend_readmng_retrieve_img_from_thumbnail (MgBackendReadmng *self, xmlNodePtr thumbnail) { + for (xmlNodePtr child = thumbnail->children; child; child=child->next) { + if (!strcmp((char *)child->name, "img")) { + return child; + } + } + return NULL; +} diff --git a/src/manga.c b/src/manga.c new file mode 100644 index 0000000..ce7a7eb --- /dev/null +++ b/src/manga.c @@ -0,0 +1,128 @@ +#include + +#include +#include + +struct _MgManga { + GObject parent_instance; + char *image_url; + char *title; +}; + +G_DEFINE_TYPE (MgManga, mg_manga, G_TYPE_OBJECT) + +typedef enum { + MG_MANGA_IMAGE_URL = 1, + MG_MANGA_TITLE, + MG_MANGA_N_PROPERTIES +} MgMangaProperties; + +static GParamSpec *manga_properties[MG_MANGA_N_PROPERTIES] = { NULL, }; + +static void +mg_manga_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void +mg_manga_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void +mg_manga_class_init (MgMangaClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS (class); + object_class->set_property = mg_manga_set_property; + object_class->get_property = mg_manga_get_property; + + manga_properties[MG_MANGA_IMAGE_URL] = g_param_spec_string ("image_url", + "ImageURL", + "Url of the image.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + manga_properties[MG_MANGA_TITLE] = g_param_spec_string ("title", + "Title", + "Title of the manga.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, + MG_MANGA_N_PROPERTIES, + manga_properties); +} + +static void +mg_manga_init (MgManga *self) { +} + +char * +mg_manga_get_image_url(MgManga *self) { + GValue value = G_VALUE_INIT; + g_value_init (&value, G_TYPE_STRING); + g_object_get_property (G_OBJECT (self), + "image_url", + &value); + return g_value_dup_string (&value); +} + +char * +mg_manga_get_title(MgManga *self) { + GValue value = G_VALUE_INIT; + g_value_init (&value, G_TYPE_STRING); + g_object_get_property (G_OBJECT (self), + "title", + &value); + return g_value_dup_string (&value); +} + +static void +mg_manga_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { + MgManga *self = MG_MANGA (object); + switch ((MgMangaProperties) property_id) { + case MG_MANGA_IMAGE_URL: + g_free (self->image_url); + self->image_url = g_value_dup_string (value); + break; + case MG_MANGA_TITLE: + g_free (self->title); + self->title = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +mg_manga_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) { + MgManga *self = MG_MANGA (object); + switch ((MgMangaProperties) property_id) { + case MG_MANGA_IMAGE_URL: + g_value_set_string (value, self->image_url); + break; + case MG_MANGA_TITLE: + g_value_set_string (value, self->title); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +MgManga * +mg_manga_new (const char *const image_url, const char *const title) { + MgManga *self = NULL; + self = (MG_MANGA) (g_object_new (MG_TYPE_MANGA, NULL)); + self->image_url = alloc_string (strlen (image_url)); + self->title = alloc_string (strlen (title)); + copy_substring (image_url, self->image_url, strlen(image_url) + 1, 0, strlen (image_url)); + copy_substring (title, self->title, strlen(title) + 1, 0, strlen (title)); + return self; +}