Compare commits

...

38 Commits
main ... main

Author SHA1 Message Date
sergiotarxz e7ff014da8 Adding github sponsor button. 2022-06-03 00:25:46 +02:00
sergiotarxz 8da7632ddd Adding complete RELEASE.PL 2022-05-09 00:09:29 +02:00
sergiotarxz 37c4c00240 Removing unused functions. 2022-05-06 18:56:53 +02:00
sergiotarxz 14fc2c48a3 Adding better instructions. 2022-05-06 05:06:53 +02:00
sergiotarxz 33a688bc75 Adding dont theme my app. 2022-05-06 04:56:28 +02:00
sergiotarxz b77d055f35 Adding again support for reading chapters. 2022-05-05 06:21:56 +02:00
sergiotarxz b01ac9df45 Adding support for details and search. 2022-05-05 05:14:41 +02:00
sergiotarxz 1c5037e46b Fixing main page retrieval. 2022-05-05 01:31:03 +02:00
sergiotarxz 8fa232580b Adding a stub release script. 2022-04-26 23:49:01 +02:00
sergiotarxz 16b67a4df8 Adding adjustement to default to up right on new page. 2022-04-26 23:20:16 +02:00
sergiotarxz 3982cb2845 Adding keyboard page passing. 2022-04-26 17:01:54 +02:00
sergiotarxz 405d91ff00 Adding some keybindings to ease navigation. 2022-04-26 02:15:58 +02:00
sergiotarxz 1ca46abad9 Adding a bigger default size. 2022-04-12 05:41:28 +02:00
sergiotarxz 170a27abe6 Fixing icon not being exported by default while using flatpak. 2022-04-12 04:13:57 +02:00
sergiotarxz 0dc8040a16 Merge pull request 'redesigned the icon of openmg' (#9) from germedeb/mangareader:main into main
Reviewed-on: https://gitea.sergiotarxz.freemyip.com/sergiotarxz/mangareader/pulls/9
2022-03-30 23:14:20 +02:00
germedeb 597b0e4011 redesigned the icon of openmg
this makes the icon more attractive, and it is now pixel perfect on 32x32 and looks better in all resolutions
2022-03-30 17:44:09 -03:00
sergiotarxz 49d41b5a43 Adding some arbitrary limits to the zoom out/in. 2022-03-06 19:16:22 +01:00
sergiotarxz 60e3291190 Adding a demostration gif to openmg's README.md 2022-03-06 18:37:39 +01:00
sergiotarxz 71f5db627f Adding a demostration gif. 2022-03-06 18:33:29 +01:00
sergiotarxz 6892ddcddc Avoiding the app to crash when no internet conection is found. 2022-02-18 15:08:24 +01:00
sergiotarxz 02048bf41d Adding desktop icon. 2022-02-10 19:54:32 +01:00
sergiotarxz 8f9be2a656 Merge pull request 'added a .desktop file' (#7) from germedeb/mangareader:main into main
Reviewed-on: https://gitea.sergiotarxz.freemyip.com/sergiotarxz/mangareader/pulls/7
2022-02-10 19:21:24 +01:00
germedeb 5c00c7e09c Merge branch 'main' into main 2022-02-10 18:27:06 +01:00
sergiotarxz b3cf226554 Adding a donations bitcoin address. 2022-02-01 19:52:58 +01:00
sergiotarxz c54fa2b2e4 Adding me.sergiotarxz.openmg.json 2022-01-25 01:45:27 +01:00
sergiotarxz 52378d5623 Limiting search to 20 mangas to improve performance. 2022-01-23 15:43:50 +01:00
sergiotarxz d78d687f6d Fixing unblocking the image download after setting the model. 2022-01-23 15:27:37 +01:00
sergiotarxz 81a266fe3a Remooving debug statement. 2022-01-23 11:42:08 +01:00
sergiotarxz e0c837642b Better yet image download. 2022-01-23 11:35:31 +01:00
sergiotarxz 84c47861ad Adding g_task_set_return_on_cancel. 2022-01-23 11:03:40 +01:00
sergiotarxz 2c2e1d550e Improving the image logic. 2022-01-23 10:53:46 +01:00
sergiotarxz a17f9c1f25 Fix to endes patch (Unitialized html_document.) and default to build flatpak
without images to work better on low resources computers.
2022-01-23 09:58:01 +01:00
sergiotarxz 5b49e51642 Merge pull request 'Mainly some memory leaks fixes' (#8) from endes/mangareader:broken-patch-1 into main
Reviewed-on: https://gitea.sergiotarxz.freemyip.com/sergiotarxz/mangareader/pulls/8
2022-01-23 09:45:01 +01:00
endes 44689c9561 corrected 'AUTHORS.md' 2022-01-20 17:45:50 +01:00
endes 14c671c78b updated AUTHORS.md 2022-01-20 17:42:40 +01:00
broken a49e12154f Added valgrind supression list 2022-01-20 14:22:47 +01:00
broken eb19a7bd0b fixed memory leaks 2022-01-20 14:17:33 +01:00
germedeb fdcc909f4b added a .desktop file
This is necessary to get openmg in a menu like in kde or gnome
2022-01-17 18:20:12 -03:00
29 changed files with 911 additions and 463 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: sergiotarxz

View File

@ -1,2 +1,5 @@
Sergiotarxz sergiotarxz@posteo.net Project leader and developer [Gitea](https://gitea.sergiotarxz.freemyip.com/sergiotarxz) Sergiotarxz sergiotarxz@posteo.net Project leader and developer. [Gitea](https://gitea.sergiotarxz.freemyip.com/sergiotarxz)
Germe.db FOSSgerme.deb@tuta.io Author of the icon openmg.svg. [sr.ht](https://sr.ht/~germe-fur/)
Germe.db FOSSgerme.deb@tuta.io Designer and icons creator. [sr.ht](https://sr.ht/~germe-fur/)
Endes endes@disroot.org Developer. [Github](https://github.com/endes0)

View File

@ -1,17 +1,79 @@
# mangareader # OpenMG
OpenMG is a GTK4 + Libadwaita manga reader written in C which uses `readmng` as its backend.
[![Please do not theme this app](https://stopthemingmy.app/badge.svg)](https://stopthemingmy.app)
## Demostration
![Demostration vídeo of the manga reader.](https://gitea.sergiotarxz.freemyip.com/sergiotarxz/mangareader/raw/branch/main/demostration.gif)
## Installing the app. ## Installing the app.
These are the installation methods supported currently.
### Flatpak
Download from https://gitea.sergiotarxz.freemyip.com/sergiotarxz/mangareader/releases the latest `openmg-x86_64-(version).flatpak` and run:
```shell
flatpak install openmg-x86_64-(version).flatpak
```
Beware that not being in Flathub yet you will have to come here again
to get updates.
### Gentoo
```shell
sudo eselect repository enable sergiotarxz
echo 'app-misc/openmg ~amd64' | sudo tee -a /etc/portage/package.accept_keywords/zz-autounmask
sudo emerge -a openmg --autounmask
```
If the installation ask you for a package masked for ~amd64 you can run
`sudo etc-update`, upgrade the `package.accept_keywords` config file
and try again the latest command of the installation instructions.
## Build from source
### Flatpak
First fine tune the options in `me.sergiotarxz.openmg.json` for
meson you want to have, for example preview images, complete list is
on `meson_options.txt`
```shell ```shell
flatpak --user remote-add --if-not-exists gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo flatpak --user remote-add --if-not-exists gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo
flatpak install org.gnome.Sdk//master flatpak --user install org.gnome.Sdk//master
flatpak install org.gnome.Platform//master flatpak --user install org.gnome.Platform//master
flatpak-builder --install --user build me.sergiotarxz.openmg.yml me.sergiotarxz.openmg flatpak-builder --install --user build me.sergiotarxz.openmg.json me.sergiotarxz.openmg
``` ```
### Native
```shell
meson build
meson compile -C build
sudo meson install -C build
```
## Running the app ## Running the app
If using flatpak:
```shell ```shell
flatpak run me.sergiotarxz.openmg flatpak run me.sergiotarxz.openmg
``` ```
If native installated:
```shell
openmg
```
## Donations welcome:
btc: `bc1q0apxdedrm5vjn3zr0hxswnruk2x2uecwqrusmj`

92
RELEASE.PL Normal file
View File

@ -0,0 +1,92 @@
use v5.30.0;
use strict;
use warnings;
use Const::Fast;
use Path::Tiny qw/path/;
use JSON;
use File::pushd;
use Mojo::URL;
use Mojo::UserAgent;
my $tag = $ARGV[0];
my $title = $ARGV[1] // "Automated release without title $tag";
my $description = $ARGV[2] // '';
const my $config_file => "$ENV{HOME}/.config/openmg_releaser.json";
if ( !-f $config_file ) {
die "No credentials in $config_file.";
}
my $config_file_contents = path($config_file)->slurp_utf8;
my $config = decode_json($config_file_contents);
my $username = $config->{username} // die "No user in config.";
my $token = $config->{token} // die "No token in config.";
my $host = $config->{host} // die "No host in config.";
my $commit = `git rev-parse HEAD`;
my $clone_path = Path::Tiny->tempdir;
system qw/git clone/,
'https://gitea.sergiotarxz.freemyip.com/sergiotarxz/mangareader/',
$clone_path;
my @subs = (compile('x86_64'), compile('aarch64'));
my $arch = shift;
my $release = request(
POST => '/repos/sergiotarxz/mangareader/releases' => json => {
body => $description,
name => $title,
tag_name => $tag,
target_commitish => 'main',
}
);
print Data::Dumper::Dumper $release;
my $release_id = $release->{id};
for my $sub (@subs) {
$sub->() if ref $sub eq 'CODE';
}
sub compile {
my $arch = shift;
my $flatpak_builder_file = 'me.sergiotarxz.openmg.json';
my $app_id = 'me.sergiotarxz.openmg';
my $app_output_name = path("openmg-$arch-$tag.flatpak")->absolute;
system 'cp', '/usr/bin/qemu-aarch64',
"$ENV{HOME}/.local/share/flatpak/runtime/org.gnome.Sdk/aarch64/master/active/files/bin/";
my $push = pushd $clone_path;
system 'flatpak-builder', '--force-clean', '--arch', $arch, '--install', '--user',
'build', $flatpak_builder_file, $app_id
and return 1;
system 'flatpak', 'build-bundle', '--arch', $arch,
path( $ENV{HOME} )->child('.local/share/flatpak/repo/'),
$app_output_name, $app_id
and return 1;
return sub {
print Data::Dumper::Dumper request( POST =>
"/repos/sergiotarxz/mangareader/releases/$release_id/assets?name=@{[$app_output_name->basename]}"
=> form => { attachment => { file => "" . $app_output_name } } );
}
}
sub request {
my $method = shift // die "No method passed.";
my $endpoint = shift // die "No endpoint passed.";
my $body_type = shift // die "No body type passed.";
my $body = shift // die "No body passed.";
my $ua = Mojo::UserAgent->new();
my $url = Mojo::URL->new("https://$host/api/v1/$endpoint");
$url->query( token => $token );
say $url;
my $tx = $ua->build_tx( $method => $url => {} => $body_type => $body );
$ua->start($tx);
my $response = $tx->result;
say $response->code;
say $response->message;
return decode_json( $response->body );
}

BIN
demostration.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

View File

@ -2,4 +2,6 @@
#include <glib-object.h> #include <glib-object.h>
void void
g_object_set_property_int(GObject *object, char *property_key, int value); g_object_set_property_int(GObject *object, char *property_key, gint value);
void
g_object_set_property_double(GObject *object, char *property_key, gdouble value);

View File

@ -8,10 +8,12 @@ G_DECLARE_FINAL_TYPE (MgUtilString, mg_util_string, MG, UTIL_STRING, GObject)
MgUtilString *mg_util_string_new (); MgUtilString *mg_util_string_new ();
char * char *
mg_util_string_alloc_string(MgUtilString *self, size_t len); mg_util_string_alloc_string (MgUtilString *self, size_t len);
void void
mg_util_string_copy_substring(MgUtilString *self, mg_util_string_copy_substring (MgUtilString *self,
const char *origin, char *dest, size_t dest_len, size_t start, const char *origin, char *dest, size_t dest_len, size_t start,
size_t len); size_t len);
int
g_asprintf (char **strp, const char *format, ...);
G_END_DECLS G_END_DECLS

View File

@ -27,11 +27,15 @@ mg_util_xml_loop_search_class (MgUtilXML *self, const xmlNodePtr node, xmlNodePt
const char * class, size_t *len); const char * class, size_t *len);
xmlXPathObjectPtr xmlXPathObjectPtr
mg_util_xml_get_nodes_xpath_expression (MgUtilXML *self, mg_util_xml_get_nodes_xpath_expression (MgUtilXML *self,
const xmlDocPtr document, char *xpath); const xmlDocPtr document, const xmlNodePtr node, char *xpath);
int int
mg_util_xml_has_class (MgUtilXML *self, mg_util_xml_has_class (MgUtilXML *self,
const char *class_attribute, const char *class_to_check); const char *class_attribute, const char *class_to_check);
char * char *
mg_util_xml_get_title_text (MgUtilXML *self, mg_util_xml_get_title_text (MgUtilXML *self,
const char *const text); const char *const text);
void
mg_util_xml_print_debug_nodes (MgUtilXML *self,
const xmlDocPtr html_document, xmlNodePtr *nodes,
size_t nodes_len);
G_END_DECLS G_END_DECLS

View File

@ -9,6 +9,9 @@ typedef struct {
AdwHeaderBar *header; AdwHeaderBar *header;
AdwLeaflet *views_leaflet; AdwLeaflet *views_leaflet;
AdwViewStack *view_stack; AdwViewStack *view_stack;
GCancellable **image_threads;
size_t image_threads_len;
bool avoid_list_image_downloads;
GtkButton *previous; GtkButton *previous;
gboolean is_set_previous; gboolean is_set_previous;
} ControlsAdwaita; } ControlsAdwaita;

View File

@ -3,4 +3,4 @@
GtkPicture * GtkPicture *
create_picture_from_url (const char *const url, gint picture_size, create_picture_from_url (const char *const url, gint picture_size,
GAsyncReadyCallback ready, gpointer source_object, GAsyncReadyCallback ready, gpointer source_object,
gpointer callback_data); gpointer callback_data, bool do_not_download);

View File

@ -0,0 +1,11 @@
[Desktop Entry]
Name=OpenMG
GenericName=Manga Reader
Comment=A manga reader
Comment[es]=A manga reader
Exec=openmg
Icon=me.sergiotarxz.openmg
Type=Application
Terminal=false
StartupWMClass=openmg
Keywords=manga;reader;book;story;visor;

View File

@ -0,0 +1,30 @@
{
"app-id": "me.sergiotarxz.openmg",
"runtime": "org.gnome.Platform",
"runtime-version": "master",
"sdk": "org.gnome.Sdk",
"command": "openmg",
"finish-args": [
"--share=ipc",
"--socket=x11",
"--socket=wayland",
"--socket=session-bus",
"--share=network",
"--device=dri"
],
"modules": [
{
"name": "openmg",
"buildsystem": "meson",
"config-opts": [
"-Dimages=true"
],
"sources": [
{
"type": "dir",
"path": "."
}
]
}
]
}

View File

@ -2,11 +2,11 @@
<!-- Created with Inkscape (http://www.inkscape.org/) --> <!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg <svg
width="18.000843" width="32"
height="18.000355" height="32"
id="svg2" id="svg2"
version="1.1" version="1.1"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="openmg.svg" sodipodi:docname="openmg.svg"
inkscape:export-filename="/home/rober/Software/Fdroid Icon Pack/twelf_icon_template.png" inkscape:export-filename="/home/rober/Software/Fdroid Icon Pack/twelf_icon_template.png"
inkscape:export-xdpi="640" inkscape:export-xdpi="640"
@ -23,17 +23,45 @@
id="title3766">Twelf Icon Template</title> id="title3766">Twelf Icon Template</title>
<defs <defs
id="defs4"> id="defs4">
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect15270"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<linearGradient <linearGradient
inkscape:collect="always" inkscape:collect="always"
id="linearGradient3989"> id="linearGradient13844">
<stop
style="stop-color:#9211e0;stop-opacity:1"
offset="0"
id="stop3985" />
<stop <stop
style="stop-color:#2edc7c;stop-opacity:1" style="stop-color:#2edc7c;stop-opacity:1"
offset="0"
id="stop13842" />
<stop
style="stop-color:#9211e0;stop-opacity:1"
offset="1" offset="1"
id="stop3987" /> id="stop13840" />
</linearGradient>
<linearGradient
id="linearGradient8471">
<stop
id="stop8467"
offset="0"
style="stop-color:#2aa9c7;stop-opacity:1" />
<stop
id="stop8469"
offset="1"
style="stop-color:#297fcc;stop-opacity:1" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="linearGradient5306"> id="linearGradient5306">
@ -1293,17 +1321,17 @@
inkscape:collect="always" /> inkscape:collect="always" />
<inkscape:perspective <inkscape:perspective
sodipodi:type="inkscape:persp3d" sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : -10.21298 : 1" inkscape:vp_x="0 : 3.7866652 : 1"
inkscape:vp_y="0 : 1066.6667 : 0" inkscape:vp_y="0 : 1066.6667 : 0"
inkscape:vp_z="1.0666667 : -10.21298 : 1" inkscape:vp_z="1.0666667 : 3.7866652 : 1"
inkscape:persp3d-origin="0.53333333 : -10.390757 : 1" inkscape:persp3d-origin="0.53333333 : 3.6088882 : 1"
id="perspective3852" /> id="perspective3852" />
<inkscape:perspective <inkscape:perspective
id="perspective3924" id="perspective3924"
inkscape:persp3d-origin="396.85039 : 363.4269 : 1" inkscape:persp3d-origin="396.85039 : 377.42655 : 1"
inkscape:vp_z="793.70078 : 550.51351 : 1" inkscape:vp_z="793.70078 : 564.51316 : 1"
inkscape:vp_y="0 : 1066.6667 : 0" inkscape:vp_y="0 : 1066.6667 : 0"
inkscape:vp_x="0 : 550.51351 : 1" inkscape:vp_x="0 : 564.51316 : 1"
sodipodi:type="inkscape:persp3d" /> sodipodi:type="inkscape:persp3d" />
<linearGradient <linearGradient
id="linearGradient3689-2"> id="linearGradient3689-2">
@ -1504,17 +1532,17 @@
inkscape:collect="always" /> inkscape:collect="always" />
<inkscape:perspective <inkscape:perspective
sodipodi:type="inkscape:persp3d" sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : -10.21298 : 1" inkscape:vp_x="0 : 3.7866652 : 1"
inkscape:vp_y="0 : 1066.6667 : 0" inkscape:vp_y="0 : 1066.6667 : 0"
inkscape:vp_z="1.0666667 : -10.21298 : 1" inkscape:vp_z="1.0666667 : 3.7866652 : 1"
inkscape:persp3d-origin="0.53333333 : -10.390757 : 1" inkscape:persp3d-origin="0.53333333 : 3.6088882 : 1"
id="perspective3852-5" /> id="perspective3852-5" />
<inkscape:perspective <inkscape:perspective
id="perspective3924-4" id="perspective3924-4"
inkscape:persp3d-origin="396.85039 : 363.4269 : 1" inkscape:persp3d-origin="396.85039 : 377.42655 : 1"
inkscape:vp_z="793.70078 : 550.51351 : 1" inkscape:vp_z="793.70078 : 564.51316 : 1"
inkscape:vp_y="0 : 1066.6667 : 0" inkscape:vp_y="0 : 1066.6667 : 0"
inkscape:vp_x="0 : 550.51351 : 1" inkscape:vp_x="0 : 564.51316 : 1"
sodipodi:type="inkscape:persp3d" /> sodipodi:type="inkscape:persp3d" />
<linearGradient <linearGradient
id="linearGradient2460" id="linearGradient2460"
@ -1840,12 +1868,12 @@
</linearGradient> </linearGradient>
<linearGradient <linearGradient
inkscape:collect="always" inkscape:collect="always"
xlink:href="#linearGradient3989" xlink:href="#linearGradient13844"
id="linearGradient3991" id="linearGradient8465"
x1="12.923307" x1="18.999157"
y1="1109.7729" y1="1094.7725"
x2="10.240108" x2="23.999157"
y2="1092.5032" y2="1123.7725"
gradientUnits="userSpaceOnUse" /> gradientUnits="userSpaceOnUse" />
</defs> </defs>
<sodipodi:namedview <sodipodi:namedview
@ -1855,16 +1883,16 @@
borderopacity="1.0" borderopacity="1.0"
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="64" inkscape:zoom="16.3125"
inkscape:cx="10.445313" inkscape:cx="16"
inkscape:cy="9.9765625" inkscape:cy="16"
inkscape:document-units="pt" inkscape:document-units="pt"
inkscape:current-layer="layer1" inkscape:current-layer="layer5"
showgrid="false" showgrid="false"
inkscape:window-width="1920" inkscape:window-width="1366"
inkscape:window-height="1017" inkscape:window-height="707"
inkscape:window-x="-8" inkscape:window-x="0"
inkscape:window-y="-8" inkscape:window-y="0"
inkscape:window-maximized="1" inkscape:window-maximized="1"
units="px" units="px"
showguides="false" showguides="false"
@ -1895,13 +1923,15 @@
visible="true" visible="true"
enabled="true" enabled="true"
snapvisiblegridlinesonly="true" snapvisiblegridlinesonly="true"
spacingx="0.5" spacingx="1"
spacingy="0.5" spacingy="1"
units="px" units="px"
originx="9.0008429" originx="0"
originy="9.0005" originy="0"
color="#5200ff" color="#5600f7"
opacity="0.0627451" /> opacity="0.04705882"
empcolor="#6008ff"
empopacity="0.19215686" />
</sodipodi:namedview> </sodipodi:namedview>
<metadata <metadata
id="metadata7"> id="metadata7">
@ -1918,10 +1948,6 @@
id="layer1" id="layer1"
transform="translate(1.0008429,-1092.7725)" transform="translate(1.0008429,-1092.7725)"
style="display:inline"> style="display:inline">
<path
id="rect4731-1"
style="display:inline;opacity:1;fill:url(#linearGradient3991);stroke-width:0.0468842;stroke-linecap:round;stroke-linejoin:round;fill-opacity:1;stroke:none;stroke-opacity:1"
d="M 2.0000006,1093.774 H 13.99893 c 1.107948,0 1.999906,0.892 1.999906,2 v 11.9989 c 0,1.1079 -0.891958,1.9999 -1.999906,1.9999 H 2.0000006 c -1.1079475,0 -1.999905232568,-0.892 -1.999905232568,-1.9999 v -11.9989 c 0,-1.108 0.891957732568,-2 1.999905232568,-2 z" />
<g <g
id="g4337" id="g4337"
transform="translate(-22.320454,1056.4942)"> transform="translate(-22.320454,1056.4942)">
@ -1942,59 +1968,27 @@
<g <g
id="g6215" id="g6215"
transform="matrix(0.25650448,0,0,0.25650448,1.5030103,1094.2087)" /> transform="matrix(0.25650448,0,0,0.25650448,1.5030103,1094.2087)" />
<path
id="rect2559"
style="display:inline;opacity:0.3;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0468876;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="M 3.0019531,1 C 1.893925,1 0.99813395,1.8919735 1,3 l 8.429e-4,0.5005 c 0,-1.1080281 0.8919719,-2 2,-2 H 15.000843 c 1.108028,0 2,0.8919719 2,2 L 17,3 C 16.998134,1.8919735 16.108028,1 15,1 Z"
transform="translate(-1.0008429,1092.7725)"
sodipodi:nodetypes="sscsscsss" />
<path
id="path2777"
style="display:inline;opacity:0.3;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0468876;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 13.998047,1109.773 c 1.108028,0 2.001953,-0.892 2.001953,-2 v -0.5 c 0,1.108 -0.891972,2 -2,2 H 2 c -1.108028,0 -2,-0.892 -2,-2 l 5.000001e-8,0.5 c 1.1080002e-7,1.108 0.89197199999999,2 2.00000004999999,2 z"
sodipodi:nodetypes="sscsscsss" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="icon"
transform="translate(1.0008429,1.0005)"
style="display:inline">
<rect <rect
style="fill:#ffffff;stroke-width:0.047;stroke-linejoin:round;paint-order:markers stroke fill" style="opacity:1;fill:url(#linearGradient8465);fill-opacity:1;stroke:none;stroke-width:2;paint-order:markers stroke fill"
id="rect1296" id="rect7796"
width="1" width="28"
height="10" height="28.000038"
x="3.5000002" x="0.99915707"
y="3.0000002" /> y="1094.7725"
ry="4.0000391" />
<path <path
id="rect1440" id="rect7982"
style="fill:#ffffff;stroke-width:0.047;stroke-linejoin:round;paint-order:markers stroke fill" style="opacity:0.3;fill:#ffffff;fill-opacity:1;stroke-width:2;paint-order:markers stroke fill"
d="M 6 4 L 6 14 L 13.5 14 L 13.5 4 L 6 4 z M 6.5 4.5 L 13 4.5 L 13 13.5 L 6.5 13.5 L 6.5 4.5 z " d="M 6,2 C 3.7839783,2 2,3.7839783 2,6 V 7 C 2,4.7839783 3.7839783,3 6,3 h 20 c 2.216022,0 4,1.7839783 4,4 V 6 C 30,3.7839783 28.216022,2 26,2 Z"
transform="translate(-1.0008429,-1.0005)" /> transform="translate(-1.0008429,1092.7725)"
sodipodi:nodetypes="sscsscsss"
inkscape:transform-center-y="-11.5" />
<path <path
style="fill:none;stroke:#ffffff;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" id="path8382"
d="M 8.06773,3.1798504 9.0000001,7" style="opacity:0.3;fill:#000000;fill-opacity:1;stroke-width:2;paint-order:markers stroke fill"
id="path1532" d="m 4.9991571,1122.7725 c -2.2160217,0 -4,-1.784 -4,-4 v -1 c 0,2.216 1.7839783,4 4,4 H 24.999157 c 2.216022,0 4,-1.784 4,-4 v 1 c 0,2.216 -1.783978,4 -4,4 z"
sodipodi:nodetypes="cc" /> sodipodi:nodetypes="sscsscsss"
<path inkscape:transform-center-y="11.5" />
style="fill:none;stroke:#ffffff;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
d="M 12.301065,6.52159 5.2594625,7.4856312"
id="path1849"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#ffffff;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
d="M 8.4732114,7.0644481 8.0489561,12.757055"
id="path2055" />
<path
style="fill:none;stroke:#ffffff;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
d="m 8.2340194,10.621912 3.9353726,0.256162"
id="path2057"
sodipodi:nodetypes="cc" />
<path
style="fill:#2aa9c7;stroke:#ffffff;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
d="M 8.4017913,8.2957811 12.18977,9.205978"
id="path2605" />
</g> </g>
<g <g
inkscape:groupmode="layer" inkscape:groupmode="layer"
@ -2004,19 +1998,49 @@
transform="translate(1.0008429,1.0005)" transform="translate(1.0008429,1.0005)"
sodipodi:insensitive="true"> sodipodi:insensitive="true">
<rect <rect
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#003966;stroke-width:0.05;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" style="opacity:0.2;fill:#000000;stroke-width:2;paint-order:markers stroke fill"
id="rect3862" id="rect6364"
width="10" width="16"
height="8" height="16"
x="3" x="6.999157"
y="4" /> y="6.9994998"
ry="0" />
</g>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="foreground 1"
style="opacity:1">
<path
id="rect14239"
style="fill:#ffffff;stroke:none;stroke-width:0.2;paint-order:markers stroke fill"
d="m 9,9 v 14 a 1,1 45 0 0 1,1 h 1 V 8 H 10 A 1,1 135 0 0 9,9 Z m 3,-1 v 16 h 10 a 1,1 135 0 0 1,-1 V 9 A 1,1 45 0 0 22,8 Z m 1,1 h 9 v 14 h -9 z"
inkscape:path-effect="#path-effect15270"
inkscape:original-d="m 9,8 v 16 h 2 V 8 Z m 3,0 V 24 H 23 V 8 Z m 1,1 h 9 v 14 h -9 z" />
<path
style="fill:#ffffff;stroke:#ffffff;stroke-width:1px;stroke-linecap:square;stroke-linejoin:miter;stroke-opacity:1"
d="m 16.430957,8.9918773 1.237872,4.9468637"
id="path14345"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 22.119982,13.428408 -9.476619,2.181628"
id="path14850" />
<path
style="fill:none;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 17.462363,14.570037 -1.071749,8.952352"
id="path14885" />
<path
style="fill:none;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 17.130427,18.389722 5.211403,1.235203"
id="path14920"
sodipodi:nodetypes="cc" />
<rect <rect
style="display:inline;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#006601;stroke-width:0.05;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" style="opacity:0.5;fill:#ffffff;stroke:none;stroke-width:0.2;stroke-linecap:square;paint-order:markers stroke fill"
id="rect3862-6" id="rect15163"
width="10" width="3"
height="8" height="16"
x="3" x="10"
y="-12" y="8" />
transform="rotate(90)" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -1,20 +0,0 @@
app-id: me.sergiotarxz.openmg
runtime: org.gnome.Platform
runtime-version: master
sdk: org.gnome.Sdk
sdk-version: master
command: openmg
finish-args:
- "--share=ipc"
- "--socket=x11"
- "--socket=wayland"
- "--socket=session-bus"
- "--share=network"
- "--device=dri"
modules:
- name: openmg
buildsystem: meson
sources:
- type: dir
path: .

View File

@ -1,4 +1,4 @@
project('openmg', 'c') project('me.sergiotarxz.openmg', 'c')
inc = include_directories('include') inc = include_directories('include')
@ -36,16 +36,42 @@ sources = [
] ]
link_arguments = [ link_arguments = [
'-ldl',
'-lm' '-lm'
] ]
images_on_lists = get_option('images') images_on_lists = get_option('images')
is_windows = get_option('windows')
cArgs = '' if not is_windows
if images_on_lists link_arguments += ['-ldl']
cArgs = cArgs + '-DLIST_IMAGES'
endif endif
cArgs = []
if images_on_lists
cArgs += ['-DLIST_IMAGES']
endif
# Install our icons in all the required sizes
icon_sizes = ['16', '24', '32', '48', '64', '128']
foreach i : icon_sizes
install_data(
'me.sergiotarxz.openmg.svg',
install_dir: get_option('datadir') / 'icons' / 'hicolor' / i + 'x' + i / 'apps',
rename: meson.project_name() + '.svg'
)
install_data(
'me.sergiotarxz.openmg.svg',
install_dir: get_option('datadir') / 'icons' / 'hicolor' / i + 'x' + i + '@2' / 'apps',
rename: meson.project_name() + '.svg'
)
endforeach
install_data(
'me.sergiotarxz.openmg.desktop',
install_dir: get_option('datadir') / 'applications',
)
executable('openmg', executable('openmg',
sources, sources,
dependencies : openmgdeps, dependencies : openmgdeps,

View File

@ -1 +1,2 @@
option('images', type : 'boolean', value : true) option('images', type : 'boolean', value : true)
option('windows', type : 'boolean', value : false)

36
openmg.supp Normal file
View File

@ -0,0 +1,36 @@
#
# Valgrind suppression file for mangareader.
# Warning: This file is very generic, so a real memory leak might be suppressed.
# Also, some false positives are still captured
#
# Format specification:
# http://valgrind.org/docs/manual/manual-core.html#manual-core.suppress
#
{
gtk4
Memcheck:Leak
...
obj:/usr/lib/x86_64-linux-gnu/libgtk-4.so.1.600.0
...
}
{
glib
Memcheck:Leak
...
obj:/usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.7100.0
...
}
{
fontconfig
Memcheck:Leak
...
obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0
...
}
{
gallium
Memcheck:Leak
...
obj:/usr/lib/x86_64-linux-gnu/GL/default/lib/dri/libgallium_dri.so
...
}

1
sqlite

@ -1 +0,0 @@
Subproject commit 855a165fecc25da50cb20525ddf5b9e60a67d18f

View File

@ -57,14 +57,6 @@ mg_backend_readmng_class_init (MgBackendReadmngClass *class) {
mg_backend_readmng_properties); mg_backend_readmng_properties);
} }
static xmlNodePtr
mg_backend_readmng_get_a_for_chapter (
MgBackendReadmng *self,
xmlNodePtr li);
static MgMangaChapter *
mg_backend_readmng_loop_li_chapter (
MgBackendReadmng *self,
xmlNodePtr li);
static char * static char *
mg_backend_readmng_fetch_search (MgBackendReadmng *self, mg_backend_readmng_fetch_search (MgBackendReadmng *self,
const char *search_query, size_t *response_len); const char *search_query, size_t *response_len);
@ -76,21 +68,10 @@ mg_backend_readmng_fetch_page_url (MgBackendReadmng *self,
MgMangaChapter *chapter); MgMangaChapter *chapter);
static GListStore * static GListStore *
mg_backend_readmng_recover_chapter_list (MgBackendReadmng *self, mg_backend_readmng_recover_chapter_list (MgBackendReadmng *self,
xmlDocPtr html_document_details); xmlDocPtr html_document_details, MgManga *manga);
static xmlDocPtr static xmlDocPtr
mg_backend_readmng_fetch_xml_details (MgBackendReadmng *self, mg_backend_readmng_fetch_xml_details (MgBackendReadmng *self,
MgManga *manga); MgManga *manga);
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 void
mg_backend_readmng_extract_manga_info_from_current_li (MgBackendReadmng *self,
GListStore *mangas, xmlNodePtr current_li);
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 * static const char *
mg_backend_readmng_get_main_page (MgBackendReadmng *self, size_t *len); mg_backend_readmng_get_main_page (MgBackendReadmng *self, size_t *len);
static GListStore * static GListStore *
@ -99,6 +80,21 @@ static xmlDocPtr
mg_backend_readmng_fetch_xml_main_page (MgBackendReadmng *self); mg_backend_readmng_fetch_xml_main_page (MgBackendReadmng *self);
static char * static char *
mg_backend_readmng_get_id_manga_link_from_string (MgBackendReadmng *self, const char *url); mg_backend_readmng_get_id_manga_link_from_string (MgBackendReadmng *self, const char *url);
static char *
mg_backend_readmng_get_manga_id_main_page (MgBackendReadmng *self,
xmlDocPtr html_document, xmlNodePtr manga_slider_card);
static char *
mg_backend_readmng_get_manga_title_main_page (MgBackendReadmng *self,
xmlDocPtr html_document, xmlNodePtr manga_slider_card);
static char *
mg_backend_readmng_get_manga_image_main_page (MgBackendReadmng *self,
xmlDocPtr html_document, xmlNodePtr manga_slider_card);
static MgManga *
mg_backend_readmng_extract_from_manga_slider_card (MgBackendReadmng *self,
xmlDocPtr html_document, xmlNodePtr node);
static MgMangaChapter *
mg_backend_readmng_get_data_from_check_box_card (MgBackendReadmng *self,
xmlDocPtr html_document, xmlNodePtr check_box_card, MgManga *manga);
MgBackendReadmng * MgBackendReadmng *
mg_backend_readmng_new(void) { mg_backend_readmng_new(void) {
@ -108,7 +104,7 @@ mg_backend_readmng_new(void) {
static void static void
mg_backend_readmng_init (MgBackendReadmng *self) { mg_backend_readmng_init (MgBackendReadmng *self) {
if (!self->base_url) { if (!self->base_url) {
self->base_url = "https://www.readmng.com/"; self->base_url = "https://www.readmng.com";
} }
self->xml_utils = mg_util_xml_new (); self->xml_utils = mg_util_xml_new ();
} }
@ -180,28 +176,216 @@ mg_backend_readmng_get_chapter_images (MgBackendReadmng *self, MgMangaChapter *c
static GListModel * static GListModel *
mg_backend_readmng_parse_page (MgBackendReadmng *self, mg_backend_readmng_parse_page (MgBackendReadmng *self,
xmlDocPtr html_document) { xmlDocPtr html_document) {
GListModel *images = G_LIST_MODEL GListModel *images = G_LIST_MODEL (gtk_string_list_new (NULL));
(gtk_string_list_new (NULL)); MgUtilXML *xml_utils = self->xml_utils;
MgUtilRegex *regex_util = mg_util_regex_new ();
MgUtilXML *xml_utils = self->xml_utils; xmlNodeSetPtr node_set = NULL;
xmlXPathObjectPtr xpath_result = NULL; xmlXPathObjectPtr xpath_result = NULL;
xmlNodeSetPtr node_set = NULL; xmlNodePtr script = NULL;
JsonParser *parser = json_parser_new ();
JsonNode *root = NULL;
JsonObject *root_object = NULL;
JsonArray *sources = NULL;
JsonArray *images_json_object = NULL;
JsonObject *source = NULL;
guint sources_len;
GError *error = NULL;
char *ts_reader_run = NULL;
char *ts_reader_run_json = NULL;
xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils, xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils,
html_document, "//img[@class]"); html_document, NULL, "//script[contains(., 'ts_reader')]");
if (!xpath_result) {
fprintf(stderr, "No match for images.\n");
}
node_set = xpath_result->nodesetval;
if (!node_set) {
fprintf(stderr, "No match for images.\n");
goto cleanup_mg_backend_readmng_parse_page;
}
script = node_set->nodeTab[0];
ts_reader_run = (char *)xmlNodeGetContent (script);
ts_reader_run_json = mg_util_regex_match_1 (regex_util,
"^\\s+ts_reader\\.run\\(((?:.|\\r|\\n)+)\\);", ts_reader_run);
json_parser_load_from_data (parser, ts_reader_run_json, -1,
&error);
if (error) {
g_warning ("Unable to parse json: %s.", error->message);
g_clear_error (&error);
goto cleanup_mg_backend_readmng_parse_page;
}
root = json_parser_get_root (parser);
if (json_node_get_node_type (root) != JSON_NODE_OBJECT) {
fprintf(stderr, "Expected object as JSON root.\n");
goto cleanup_mg_backend_readmng_parse_page;
}
root_object = json_node_get_object (root);
sources = json_object_get_array_member (root_object, "sources");
if (!sources) {
fprintf(stderr, "No source in JSON.\n");
goto cleanup_mg_backend_readmng_parse_page;
}
sources_len = json_array_get_length (sources);
if (!sources_len) {
fprintf(stderr, "No source element in JSON.\n");
goto cleanup_mg_backend_readmng_parse_page;
}
source = json_array_get_object_element (sources, 0);
images_json_object = json_object_get_array_member (source, "images");
if (!images_json_object) {
fprintf(stderr, "No images in JSON.\n");
goto cleanup_mg_backend_readmng_parse_page;
}
for (int i = 0; i < json_array_get_length(images_json_object); i++) {
gtk_string_list_append (GTK_STRING_LIST (images),
json_array_get_string_element (images_json_object, i));
}
cleanup_mg_backend_readmng_parse_page:
if (ts_reader_run) {
g_free (ts_reader_run);
}
if (ts_reader_run_json) {
pcre2_substring_free ((PCRE2_UCHAR8 *)ts_reader_run_json);
}
if (xpath_result) {
xmlXPathFreeObject(xpath_result);
}
if (parser) {
g_clear_object (&parser);
}
return images;
}
static MgManga *
mg_backend_readmng_extract_from_manga_slider_card (MgBackendReadmng *self,
xmlDocPtr html_document, xmlNodePtr node) {
MgManga *manga = NULL;
char *image = NULL;
char *title = NULL;
char *id = NULL;
image = mg_backend_readmng_get_manga_image_main_page (self, html_document,
node);
title = mg_backend_readmng_get_manga_title_main_page (self, html_document,
node);
id = mg_backend_readmng_get_manga_id_main_page (self, html_document,
node);
if (!image) {
fprintf (stderr, "Failed to find image\n");
goto cleanup_mg_backend_readmng_extract_from_manga_slider_card;
}
if (!title) {
fprintf (stderr, "Failed to find title\n");
goto cleanup_mg_backend_readmng_extract_from_manga_slider_card;
}
if (!id) {
fprintf (stderr, "Failed to find id\n");
goto cleanup_mg_backend_readmng_extract_from_manga_slider_card;
}
manga = mg_manga_new (image, title, id);
cleanup_mg_backend_readmng_extract_from_manga_slider_card:
if (image) {
g_free (image);
}
if (title) {
g_free (title);
}
if (id) {
pcre2_substring_free ((PCRE2_UCHAR8 *)id);
}
return manga;
}
static char *
mg_backend_readmng_get_manga_id_main_page (MgBackendReadmng *self,
xmlDocPtr html_document, xmlNodePtr manga_slider_card) {
MgUtilXML *xml_utils = self->xml_utils;
MgUtilRegex *regex_util = mg_util_regex_new ();
xmlXPathObjectPtr xpath_result = NULL;
char *id = NULL;
char *new_id = NULL;
xmlNodeSetPtr node_set = NULL;
xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils, html_document,
manga_slider_card, "./a");
if (!xpath_result) {
fprintf (stderr, "No matching id.\n");
goto cleanup_mg_backend_readmng_get_manga_id_main_page;
}
node_set = xpath_result->nodesetval; node_set = xpath_result->nodesetval;
if (!node_set) { if (!node_set) {
fprintf(stderr, "No match for images.\n"); fprintf (stderr, "No matching id node set.\n");
goto cleanup_mg_backend_readmng_parse_page; goto cleanup_mg_backend_readmng_get_manga_id_main_page;
} }
for (int i = 0; i < node_set->nodeNr; i++) { xmlNodePtr a = node_set->nodeTab[0];
xmlNodePtr node = node_set->nodeTab[i]; id = mg_util_xml_get_attr (xml_utils, a, "href");
char *image_url = mg_util_xml_get_attr (xml_utils, node, "src"); if (id) {
gtk_string_list_append (GTK_STRING_LIST (images), image_url); new_id = mg_util_regex_match_1 (regex_util, "^/([^/]+)", id);
g_free (image_url); g_free (id);
id = new_id;
} }
cleanup_mg_backend_readmng_parse_page:
xmlXPathFreeObject(xpath_result); cleanup_mg_backend_readmng_get_manga_id_main_page:
return images; if (xpath_result) {
xmlXPathFreeObject (xpath_result);
}
g_clear_object (&regex_util);
return id;
}
static char *
mg_backend_readmng_get_manga_title_main_page (MgBackendReadmng *self,
xmlDocPtr html_document, xmlNodePtr manga_slider_card) {
MgUtilXML *xml_utils = self->xml_utils;
xmlXPathObjectPtr xpath_result = NULL;
char *title = NULL;
xmlNodeSetPtr node_set = NULL;
xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils, html_document,
manga_slider_card, ".//div[@class='postDetail']//h2");
if (!xpath_result) {
fprintf (stderr, "No matching title.\n");
goto cleanup_mg_backend_readmng_get_manga_title_main_page;
}
node_set = xpath_result->nodesetval;
if (!node_set) {
fprintf (stderr, "No matching title node set.\n");
goto cleanup_mg_backend_readmng_get_manga_title_main_page;
}
xmlNodePtr h2 = node_set->nodeTab[0];
title = (char *)xmlNodeGetContent (h2);
cleanup_mg_backend_readmng_get_manga_title_main_page:
if (xpath_result) {
xmlXPathFreeObject (xpath_result);
}
return title;
}
static char *
mg_backend_readmng_get_manga_image_main_page (MgBackendReadmng *self,
xmlDocPtr html_document, xmlNodePtr manga_slider_card) {
MgUtilXML *xml_utils = self->xml_utils;
xmlXPathObjectPtr xpath_result = NULL;
char *image = NULL;
xmlNodeSetPtr node_set = NULL;
xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils, html_document,
manga_slider_card, ".//div[@class='sliderImg']//img");
if (!xpath_result) {
fprintf (stderr, "No matching image.\n");
goto cleanup_mg_backend_readmng_get_manga_image_main_page;
}
node_set = xpath_result->nodesetval;
if (!node_set) {
fprintf (stderr, "No matching image node set.\n");
goto cleanup_mg_backend_readmng_get_manga_image_main_page;
}
xmlNodePtr img = node_set->nodeTab[0];
image = mg_util_xml_get_attr (xml_utils, img, "src");
cleanup_mg_backend_readmng_get_manga_image_main_page:
if (xpath_result) {
xmlXPathFreeObject (xpath_result);
}
return image;
} }
static xmlDocPtr static xmlDocPtr
@ -239,12 +423,13 @@ mg_backend_readmng_search (MgBackendReadmng *self,
size_t response_len = 0; size_t response_len = 0;
char *response = mg_backend_readmng_fetch_search (self, search_query, char *response = mg_backend_readmng_fetch_search (self, search_query,
&response_len); &response_len);
JsonParser *parser = json_parser_new (); JsonParser *parser = json_parser_new ();
GListStore *mangas = g_list_store_new (MG_TYPE_MANGA); GListStore *mangas = g_list_store_new (MG_TYPE_MANGA);
GError *error = NULL; GError *error = NULL;
JsonNode *root = NULL; JsonNode *root = NULL;
JsonArray *mangas_json_array = NULL; JsonArray *mangas_json_array = NULL;
guint mangas_json_array_len = 0; JsonObject *root_object = NULL;
guint mangas_json_array_len = 0;
if (!response) { if (!response) {
g_warning ("Json search response is null."); g_warning ("Json search response is null.");
@ -257,13 +442,13 @@ mg_backend_readmng_search (MgBackendReadmng *self,
goto cleanup_mg_backend_readmng_search; goto cleanup_mg_backend_readmng_search;
} }
root = json_parser_get_root (parser); root = json_parser_get_root (parser);
if (json_node_get_node_type (root) != JSON_NODE_ARRAY) { if (json_node_get_node_type (root) != JSON_NODE_OBJECT) {
goto cleanup_mg_backend_readmng_search; goto cleanup_mg_backend_readmng_search;
} }
mangas_json_array = json_node_get_array (root); root_object = json_node_get_object (root);
mangas_json_array_len = json_array_get_length ( mangas_json_array = json_object_get_array_member (root_object, "manga");
mangas_json_array); mangas_json_array_len = json_array_get_length (mangas_json_array);
for (guint i = 0; i < mangas_json_array_len; i++) { for (guint i = 0; i < mangas_json_array_len && i < 5; i++) {
JsonObject *manga_json_object = JsonObject *manga_json_object =
json_array_get_object_element (mangas_json_array, i); json_array_get_object_element (mangas_json_array, i);
char *id_manga = NULL; char *id_manga = NULL;
@ -294,13 +479,9 @@ mg_backend_readmng_fetch_search (MgBackendReadmng *self,
char *request_url; char *request_url;
size_t request_url_len;
util_soup = mg_util_soup_new (); util_soup = mg_util_soup_new ();
string_util = mg_util_string_new (); string_util = mg_util_string_new ();
request_url_len = snprintf ( NULL, 0, "%s/%s/", self->base_url, "service/search"); g_asprintf ( &request_url, "%s/%s/", self->base_url, "search/live");
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[] = { SoupParam headers[] = {
{ {
@ -352,6 +533,9 @@ mg_backend_readmng_get_featured_manga (MgBackendReadmng *self) {
GListStore *mangas; GListStore *mangas;
xmlDocPtr html_document; xmlDocPtr html_document;
html_document = mg_backend_readmng_fetch_xml_main_page (self); html_document = mg_backend_readmng_fetch_xml_main_page (self);
if (!html_document) {
return NULL;
}
mangas = mg_backend_readmng_parse_main_page (self, html_document); mangas = mg_backend_readmng_parse_main_page (self, html_document);
xmlFreeDoc (html_document); xmlFreeDoc (html_document);
@ -364,7 +548,7 @@ mg_backend_readmng_retrieve_manga_details (MgBackendReadmng *self,
MgManga *manga) { MgManga *manga) {
MgUtilXML *xml_utils; MgUtilXML *xml_utils;
xmlDocPtr html_document; xmlDocPtr html_document = NULL;
xmlNodePtr *movie_detail = NULL; xmlNodePtr *movie_detail = NULL;
xmlXPathObjectPtr xpath_result = NULL; xmlXPathObjectPtr xpath_result = NULL;
xmlNodeSetPtr node_set = NULL; xmlNodeSetPtr node_set = NULL;
@ -380,23 +564,17 @@ mg_backend_readmng_retrieve_manga_details (MgBackendReadmng *self,
html_document = mg_backend_readmng_fetch_xml_details (self, html_document = mg_backend_readmng_fetch_xml_details (self,
manga); manga);
xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils, xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils,
html_document, "//li[@class]"); html_document, NULL, "//div[@class='infox']//div[@class='wd-full'][2]");
node_set = xpath_result->nodesetval; node_set = xpath_result->nodesetval;
if (!node_set) { if (!node_set) {
fprintf(stderr, "No match\n"); fprintf(stderr, "No match\n");
goto cleanup_mg_backend_readmng_retrieve_manga_details; goto cleanup_mg_backend_readmng_retrieve_manga_details;
} }
for (int i = 0; i < node_set->nodeNr; i++) { xmlNodePtr description_node = node_set->nodeTab[0];
xmlNodePtr node = node_set->nodeTab[i]; char *description = (char *) xmlNodeGetContent (description_node);
movie_detail = mg_util_xml_loop_search_class (xml_utils, mg_manga_set_description (manga, description);
node, movie_detail, "movie-detail", &movie_detail_len); g_free (description);
} manga_chapters = mg_backend_readmng_recover_chapter_list (self, html_document, manga);
if (movie_detail) {
char *description = (char *) xmlNodeGetContent (movie_detail[0]);
mg_manga_set_description (manga, description);
g_free (description);
}
manga_chapters = mg_backend_readmng_recover_chapter_list (self, html_document);
mg_manga_set_chapter_list (manga, manga_chapters); mg_manga_set_chapter_list (manga, manga_chapters);
mg_manga_details_recovered (manga); mg_manga_details_recovered (manga);
cleanup_mg_backend_readmng_retrieve_manga_details: cleanup_mg_backend_readmng_retrieve_manga_details:
@ -409,105 +587,81 @@ cleanup_mg_backend_readmng_retrieve_manga_details:
if (movie_detail) { if (movie_detail) {
g_free (movie_detail); g_free (movie_detail);
} }
if (html_document) {
xmlFreeDoc(html_document);
}
} }
static GListStore * static GListStore *
mg_backend_readmng_recover_chapter_list (MgBackendReadmng *self, mg_backend_readmng_recover_chapter_list (MgBackendReadmng *self,
xmlDocPtr html_document_details) { xmlDocPtr html_document_details, MgManga *manga) {
MgUtilXML *xml_utils = self->xml_utils; MgUtilXML *xml_utils = self->xml_utils;
xmlXPathObjectPtr xpath_result = NULL; xmlXPathObjectPtr xpath_result = NULL;
xmlNodeSetPtr node_set = NULL; xmlNodeSetPtr node_set = NULL;
xmlNodePtr *uls = NULL; GListStore *return_value = g_list_store_new (
xmlNodePtr ul;
GListStore *return_value = g_list_store_new (
MG_TYPE_MANGA_CHAPTER); MG_TYPE_MANGA_CHAPTER);
size_t ul_len = 0;
xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils, xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils,
html_document_details, "//ul[@class]"); html_document_details, NULL, "//div[@class='checkBoxCard']");
node_set = xpath_result->nodesetval; node_set = xpath_result->nodesetval;
if (!node_set) { if (!node_set) {
fprintf(stderr, "No matching ul\n"); fprintf(stderr, "No matching chapter\n");
goto cleanup_mg_backend_readmng_recover_chapter_list; goto cleanup_mg_backend_readmng_recover_chapter_list;
} }
for (int i = 0; i < node_set->nodeNr; i++) { for (int i = 0; i < node_set->nodeNr; i++) {
xmlNodePtr node = node_set->nodeTab[i]; xmlNodePtr check_box_card = node_set->nodeTab[i];
uls = mg_util_xml_loop_search_class (xml_utils, MgMangaChapter *chapter = mg_backend_readmng_get_data_from_check_box_card
node, uls, "chp_lst", &ul_len); (self, html_document_details, check_box_card, manga);
} if (chapter) {
if (!ul_len) { g_list_store_append (return_value, chapter);
fprintf(stderr, "No matching chp_lst\n"); }
goto cleanup_mg_backend_readmng_recover_chapter_list;
}
ul = uls[0];
for (xmlNodePtr li = ul->children; li; li = li->next) {
if (!strcmp ((char *) li->name, "li")) {
MgMangaChapter *chapter = mg_backend_readmng_loop_li_chapter (self, li);
if (chapter) {
g_list_store_append (return_value, chapter);
}
}
} }
cleanup_mg_backend_readmng_recover_chapter_list: cleanup_mg_backend_readmng_recover_chapter_list:
if (xpath_result) { if (xpath_result) {
xmlXPathFreeObject(xpath_result); xmlXPathFreeObject(xpath_result);
} }
if (uls) {
g_free (uls);
}
return return_value; return return_value;
} }
static MgMangaChapter * static MgMangaChapter *
mg_backend_readmng_loop_li_chapter ( mg_backend_readmng_get_data_from_check_box_card (MgBackendReadmng *self,
MgBackendReadmng *self, xmlDocPtr html_document, xmlNodePtr check_box_card, MgManga *manga) {
xmlNodePtr li) { xmlXPathObjectPtr xpath_result = NULL;
MgUtilXML *xml_utils = self->xml_utils; xmlNodeSetPtr node_set = NULL;
MgMangaChapter *chapter = NULL; MgMangaChapter *chapter = NULL;
xmlNodePtr a = mg_backend_readmng_get_a_for_chapter ( MgUtilXML *xml_utils = self->xml_utils;
self, li); char *chapter_id = NULL;
if (!a) return NULL; char *title = NULL;
char *url = NULL;
char *url = mg_util_xml_get_attr (xml_utils, a, "href"); char *manga_id = mg_manga_get_id (manga);
size_t val_len = 0; xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils,
size_t dte_len = 0; html_document, check_box_card, ".//label[@data-chapter-id]");
if (!xpath_result) {
xmlNodePtr *val = mg_util_xml_find_class (xml_utils, a, "val", &val_len, NULL, 1); fprintf(stderr, "Unable to parse chapter, xpath failed.\n");
xmlNodePtr *dte = mg_util_xml_find_class (xml_utils, a, "dte", &dte_len, NULL, 1); goto cleanup_mg_backend_readmng_get_data_from_check_box_card;
if (val_len && dte_len) { }
char *val_str = (char *) xmlNodeGetContent (val[0]); node_set = xpath_result->nodesetval;
char *dte_str = (char *) xmlNodeGetContent (dte[0]); if (!node_set) {
fprintf(stderr, "Unable to parse chapter, no nodeset.\n");
chapter = mg_manga_chapter_new (val_str, dte_str, url); goto cleanup_mg_backend_readmng_get_data_from_check_box_card;
}
g_free (val_str); xmlNodePtr chapter_node = node_set->nodeTab[0];
g_free (dte_str); chapter_id = mg_util_xml_get_attr (xml_utils, chapter_node, "data-chapter-id");
}
if (url) { g_asprintf (&title, "Chapter %s", chapter_id);
g_free (url); g_asprintf (&url, "%s/%s/%s", self->base_url, manga_id, chapter_id);
} chapter = mg_manga_chapter_new (title, "", url);
if (val) {
g_free (val); cleanup_mg_backend_readmng_get_data_from_check_box_card:
val = NULL; if (xpath_result) {
} xmlXPathFreeObject (xpath_result);
if (dte) { }
g_free (dte); if (chapter_id) {
dte = NULL; g_free (chapter_id);
} }
return chapter;
return chapter;
}
static xmlNodePtr
mg_backend_readmng_get_a_for_chapter (
MgBackendReadmng *self,
xmlNodePtr li) {
for (xmlNodePtr child = li->children; child; child = child->next) {
if (!strcmp((char *) child->name, "a")) {
return child;
}
}
return NULL;
} }
static xmlDocPtr static xmlDocPtr
@ -519,15 +673,12 @@ mg_backend_readmng_fetch_xml_details (MgBackendReadmng *self,
char *request_url; char *request_url;
char *manga_id; char *manga_id;
size_t request_url_len;
size_t response_len = 0; size_t response_len = 0;
util_soup = mg_util_soup_new (); util_soup = mg_util_soup_new ();
string_util = mg_util_string_new (); string_util = mg_util_string_new ();
manga_id = mg_manga_get_id (manga); manga_id = mg_manga_get_id (manga);
request_url_len = snprintf ( NULL, 0, "%s/%s/", self->base_url, manga_id); g_asprintf ( &request_url, "%s/%s", self->base_url, manga_id);
request_url = mg_util_string_alloc_string (string_util, request_url_len);
snprintf ( request_url, request_url_len+1, "%s/%s/", self->base_url, manga_id);
g_free (manga_id); g_free (manga_id);
char *html_response = mg_util_soup_get_request (util_soup, char *html_response = mg_util_soup_get_request (util_soup,
@ -574,170 +725,39 @@ mg_backend_readmng_get_main_page (MgBackendReadmng *self, size_t *len) {
static GListStore * static GListStore *
mg_backend_readmng_parse_main_page (MgBackendReadmng *self, const xmlDocPtr html_document) { mg_backend_readmng_parse_main_page (MgBackendReadmng *self, const xmlDocPtr html_document) {
GListStore *mangas = g_list_store_new(MG_TYPE_MANGA); GListStore *mangas = g_list_store_new (MG_TYPE_MANGA);
xmlNodePtr *li;
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; i<li_len; i++) {
xmlNodePtr current_li = li[i];
mg_backend_readmng_extract_manga_info_from_current_li (self,
mangas, current_li);
xmlFreeNode (current_li);
li[i] = NULL;
}
g_free (li);
return mangas;
}
static xmlNodePtr *
mg_backend_readmng_retrieve_li_slides (MgBackendReadmng *self, const xmlNodePtr slides, size_t *li_len) {
xmlNodePtr ul_slides = mg_backend_readmng_retrieve_ul_slides (self, 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;
}
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;
MgUtilXML *xml_utils = self->xml_utils; MgUtilXML *xml_utils = self->xml_utils;
xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils, xmlXPathObjectPtr xpath_result = NULL;
html_document, "//div[@class]");
xmlNodePtr slides = NULL;
xmlNodeSetPtr node_set = NULL; xmlNodeSetPtr node_set = NULL;
size_t matching_classes_len = 0; xpath_result = mg_util_xml_get_nodes_xpath_expression (xml_utils,
html_document, NULL, "//div[@class='mangaSliderCard']");
node_set = xpath_result->nodesetval; node_set = xpath_result->nodesetval;
if (!node_set) { if (!node_set) {
fprintf(stderr, "No match\n"); fprintf(stderr, "No match for mangas.\n");
goto cleanup_mg_backend_readmng_retrieve_slides; goto cleanup_mg_backend_readmng_parse_main_page;
} }
for (int i = 0; i < node_set->nodeNr; i++) { for (int i = 0; i < node_set->nodeNr; i++) {
xmlNodePtr node = node_set->nodeTab[i]; xmlNodePtr node = node_set->nodeTab[i];
nodes = mg_util_xml_loop_search_class (xml_utils, node, nodes, MgManga *manga = mg_backend_readmng_extract_from_manga_slider_card (self,
"slides", &matching_classes_len); html_document, node);
} if (!manga) {
if (nodes) { continue;
slides = nodes[0];
}
cleanup_mg_backend_readmng_retrieve_slides:
if (xpath_result) {
xmlXPathFreeObject(xpath_result);
}
if (nodes) {
g_free (nodes);
}
return slides;
}
static xmlNodePtr
mg_backend_readmng_retrieve_thumbnail_from_li (MgBackendReadmng *self, xmlNodePtr current_li) {
size_t thumbnail_len = 0;
MgUtilXML *xml_utils = self->xml_utils;
xmlNodePtr return_value = NULL;
xmlNodePtr *thumbnail = mg_util_xml_find_class (xml_utils, current_li, "thumbnail",
&thumbnail_len, NULL, 1);
if (!thumbnail_len) goto cleanup_mg_backend_retrieve_thumbnail_from_li;
return_value = thumbnail[0];
cleanup_mg_backend_retrieve_thumbnail_from_li:
if (thumbnail) {
g_free (thumbnail);
}
return return_value;
}
static xmlNodePtr
mg_backend_readmng_retrieve_title_from_li (MgBackendReadmng *self, xmlNodePtr li) {
size_t title_len = 0;
MgUtilXML *xml_utils = self->xml_utils;
xmlNodePtr return_value = NULL;
xmlNodePtr *title = mg_util_xml_find_class (xml_utils, li, "title", &title_len, NULL, 1);
if (title_len) {
return_value = title[0];
}
if (title) {
g_free (title);
}
return return_value;
}
static xmlNodePtr
mg_backend_readmng_find_a_link_chapter (MgBackendReadmng *self,
xmlNodePtr current_li) {
for (xmlNodePtr child = current_li->children; child; child = child->next) {
if (!strcmp((char *)child->name, "a")) {
return child;
} }
g_list_store_append (mangas, manga);
} }
return NULL; cleanup_mg_backend_readmng_parse_main_page:
} if (xpath_result) {
xmlXPathFreeObject (xpath_result);
static char * }
mg_backend_readmng_get_id_manga_link (MgBackendReadmng *self, xmlNodePtr a) { return mangas;
MgUtilXML *xml_utils = self->xml_utils;
char *href = mg_util_xml_get_attr (xml_utils, a, "href");
char *result = mg_backend_readmng_get_id_manga_link_from_string (self, href);
g_free (href);
return result;
} }
static char * static char *
mg_backend_readmng_get_id_manga_link_from_string (MgBackendReadmng *self, const char *url) { mg_backend_readmng_get_id_manga_link_from_string (MgBackendReadmng *self, const char *url) {
MgUtilRegex *regex_util = mg_util_regex_new (); MgUtilRegex *regex_util = mg_util_regex_new ();
char *re_str = "readmng\\.com/([^/]+)"; char *re_str = "/([^/]+)";
char *result = mg_util_regex_match_1 (regex_util, re_str, url); char *result = mg_util_regex_match_1 (regex_util, re_str, url);
g_clear_object (&regex_util); g_clear_object (&regex_util);
return result; return result;
} }
static void
mg_backend_readmng_extract_manga_info_from_current_li (MgBackendReadmng *self,
GListStore *mangas, xmlNodePtr current_li) {
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 a = mg_backend_readmng_find_a_link_chapter (self, current_li);
xmlNodePtr img;
MgUtilXML *xml_utils = self->xml_utils;
char *id_manga = NULL;
if (thumbnail && title && (img = mg_backend_readmng_retrieve_img_from_thumbnail (self, thumbnail))
&& a && (id_manga = mg_backend_readmng_get_id_manga_link (self, a))) {
char *src = mg_util_xml_get_attr (xml_utils, img, "src");
char *title_string = (char *)xmlNodeGetContent (title);
g_list_store_append (mangas, mg_manga_new (src, title_string, id_manga));
g_free (src);
g_free (title_string);
pcre2_substring_free ((PCRE2_UCHAR8 *) id_manga);
}
}
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;
}

View File

@ -1,7 +1,7 @@
#include <glib-object.h> #include <glib-object.h>
void void
g_object_set_property_int(GObject *object, char *property_key, int value) { g_object_set_property_int(GObject *object, char *property_key, gint value) {
GValue property = G_VALUE_INIT; GValue property = G_VALUE_INIT;
g_value_init (&property, G_TYPE_INT); g_value_init (&property, G_TYPE_INT);
g_value_set_int (&property, value); g_value_set_int (&property, value);
@ -9,3 +9,10 @@ g_object_set_property_int(GObject *object, char *property_key, int value) {
} }
void
g_object_set_property_double(GObject *object, char *property_key, gdouble value) {
GValue property = G_VALUE_INIT;
g_value_init (&property, G_TYPE_DOUBLE);
g_value_set_double (&property, value);
g_object_set_property (object, property_key, &property);
}

View File

@ -54,7 +54,6 @@ mg_util_soup_get_request (MgUtilSoup *self, const char *url, gsize *size_respons
g_value_unset (&response); g_value_unset (&response);
g_clear_object (&soup_session); g_clear_object (&soup_session);
g_clear_object (&msg); g_clear_object (&msg);
return return_value; return return_value;
} }

View File

@ -1,5 +1,6 @@
#include <stdio.h> #include <stdio.h>
#include <glib/gprintf.h>
#include <glib-object.h> #include <glib-object.h>
#include <openmg/util/string.h> #include <openmg/util/string.h>
@ -46,3 +47,11 @@ mg_util_string_new () {
self = MG_UTIL_STRING ((g_object_new (MG_TYPE_UTIL_STRING, NULL))); self = MG_UTIL_STRING ((g_object_new (MG_TYPE_UTIL_STRING, NULL)));
return self; return self;
} }
int
g_asprintf (char **strp, const char *format, ...) {
va_list ap;
va_start (ap, format);
int retval = g_vasprintf (strp, format, ap);
va_end (ap);
return retval;
}

View File

@ -143,11 +143,14 @@ mg_util_xml_loop_search_class (MgUtilXML *self, const xmlNodePtr node, xmlNodePt
xmlXPathObjectPtr xmlXPathObjectPtr
mg_util_xml_get_nodes_xpath_expression (MgUtilXML *self, mg_util_xml_get_nodes_xpath_expression (MgUtilXML *self,
const xmlDocPtr document, char *xpath) { const xmlDocPtr document, const xmlNodePtr node, char *xpath) {
xmlXPathContextPtr context; xmlXPathContextPtr context;
xmlXPathObjectPtr result; xmlXPathObjectPtr result;
context = xmlXPathNewContext (document); context = xmlXPathNewContext (document);
if (node) {
context->node = node;
}
result = xmlXPathEvalExpression ((const xmlChar *)xpath, context); result = xmlXPathEvalExpression ((const xmlChar *)xpath, context);
xmlXPathFreeContext (context); xmlXPathFreeContext (context);

View File

@ -10,6 +10,11 @@
#include <openmg/util/string.h> #include <openmg/util/string.h>
#include <openmg/backend/readmng.h> #include <openmg/backend/readmng.h>
#define KEY_LEFT 65361
#define KEY_RIGHT 65363
#define KEY_UP 65362
#define KEY_DOWN 65364
static void static void
fire_zoom (GtkGestureZoom *zoom, fire_zoom (GtkGestureZoom *zoom,
gdouble scale, gdouble scale,
@ -53,7 +58,10 @@ static void
picture_ready_manga_page (GObject *source_object, picture_ready_manga_page (GObject *source_object,
GAsyncResult *res, GAsyncResult *res,
gpointer user_data); gpointer user_data);
static void
zoomable_container_keybinding_handle (GtkEventControllerKey *self,
guint keyval, guint keycode, GdkModifierType state, gpointer user_data);
static void static void
image_page_show (GtkWidget *picture, gpointer user_data) { image_page_show (GtkWidget *picture, gpointer user_data) {
ChapterVisorData *chapter_visor_data = (ChapterVisorData *) user_data; ChapterVisorData *chapter_visor_data = (ChapterVisorData *) user_data;
@ -70,9 +78,19 @@ set_image_dimensions (GtkWidget *picture,
GdkPaintable *paintable = gtk_picture_get_paintable (GTK_PICTURE (picture)); GdkPaintable *paintable = gtk_picture_get_paintable (GTK_PICTURE (picture));
GtkWidget *views_leaflet = GTK_WIDGET (chapter_visor_data->views_leaflet); GtkWidget *views_leaflet = GTK_WIDGET (chapter_visor_data->views_leaflet);
gdouble scale_factor = log (scale) / 20 + log (chapter_visor_data->zoom); gdouble scale_factor = log (scale) / 20 + log (chapter_visor_data->zoom);
chapter_visor_data->zoom = pow (M_E, scale_factor); gdouble final_zoom = pow (M_E, scale_factor);
guint width = gtk_widget_get_allocated_width guint views_leaflet_width = gtk_widget_get_allocated_width (views_leaflet);
(views_leaflet) * chapter_visor_data->zoom; if (final_zoom > 3) {
final_zoom = 3;
}
if (final_zoom < 1/3) {
final_zoom = 1/3;
}
chapter_visor_data->zoom = final_zoom;
if (views_leaflet_width > 600) {
views_leaflet_width = 300;
}
guint width = views_leaflet_width * chapter_visor_data->zoom;
gdk_paintable_compute_concrete_size ( gdk_paintable_compute_concrete_size (
paintable, paintable,
width, width,
@ -81,7 +99,7 @@ set_image_dimensions (GtkWidget *picture,
gdk_paintable_get_intrinsic_height (paintable), gdk_paintable_get_intrinsic_height (paintable),
&final_width, &final_width,
&final_height &final_height
); );
g_object_set_property_int (G_OBJECT (picture), g_object_set_property_int (G_OBJECT (picture),
"width-request", (int) final_width); "width-request", (int) final_width);
g_object_set_property_int (G_OBJECT (picture), g_object_set_property_int (G_OBJECT (picture),
@ -100,13 +118,15 @@ setup_chapter_view (MgMangaChapter *chapter, AdwLeaflet *views_leaflet) {
GtkOverlay *overlay = GTK_OVERLAY (gtk_overlay_new ()); GtkOverlay *overlay = GTK_OVERLAY (gtk_overlay_new ());
GListModel *pages = mg_backend_readmng_get_chapter_images (readmng, chapter); GListModel *pages = mg_backend_readmng_get_chapter_images (readmng, chapter);
ChapterVisorData *chapter_visor_data = g_malloc (sizeof *chapter_visor_data); ChapterVisorData *chapter_visor_data = g_malloc (sizeof *chapter_visor_data);
chapter_visor_data->current_page = 0; chapter_visor_data->current_page = 0;
chapter_visor_data->pages = pages; chapter_visor_data->pages = pages;
chapter_visor_data->views_leaflet = views_leaflet; chapter_visor_data->views_leaflet = views_leaflet;
chapter_visor_data->zoom = 1; chapter_visor_data->zoom = 1;
chapter_visor_data->zoomable_picture_container = zoomable_picture_container; chapter_visor_data->zoomable_picture_container = zoomable_picture_container;
set_zoomable_picture_container_properties (zoomable_picture_container, set_zoomable_picture_container_properties (zoomable_picture_container,
chapter_visor_data); chapter_visor_data);
set_image_zoomable_picture_container (chapter_visor_data); set_image_zoomable_picture_container (chapter_visor_data);
gtk_overlay_set_child (overlay, GTK_WIDGET (zoomable_picture_container)); gtk_overlay_set_child (overlay, GTK_WIDGET (zoomable_picture_container));
@ -123,6 +143,8 @@ add_controls_overlay (GtkOverlay *overlay, ChapterVisorData *chapter_visor_data)
("go-next-symbolic")); ("go-next-symbolic"));
GtkButton *previous_button = GTK_BUTTON (gtk_button_new_from_icon_name GtkButton *previous_button = GTK_BUTTON (gtk_button_new_from_icon_name
("go-previous-symbolic")); ("go-previous-symbolic"));
gtk_widget_set_focusable (GTK_WIDGET (next_button), false);
gtk_widget_set_focusable (GTK_WIDGET (previous_button), false);
g_signal_connect (G_OBJECT (next_button), "clicked", G_CALLBACK (go_next), chapter_visor_data); g_signal_connect (G_OBJECT (next_button), "clicked", G_CALLBACK (go_next), chapter_visor_data);
g_signal_connect (G_OBJECT (previous_button), "clicked", G_CALLBACK (go_prev), chapter_visor_data); g_signal_connect (G_OBJECT (previous_button), "clicked", G_CALLBACK (go_prev), chapter_visor_data);
gtk_widget_set_valign (GTK_WIDGET (next_button), GTK_ALIGN_CENTER); gtk_widget_set_valign (GTK_WIDGET (next_button), GTK_ALIGN_CENTER);
@ -132,6 +154,19 @@ add_controls_overlay (GtkOverlay *overlay, ChapterVisorData *chapter_visor_data)
gtk_overlay_add_overlay (overlay, GTK_WIDGET (next_button)); gtk_overlay_add_overlay (overlay, GTK_WIDGET (next_button));
gtk_overlay_add_overlay (overlay, GTK_WIDGET (previous_button)); gtk_overlay_add_overlay (overlay, GTK_WIDGET (previous_button));
} }
static void
configure_zoomable_for_new_page (ChapterVisorData *chapter_visor_data) {
GtkScrolledWindow *zoomable_picture_container =
chapter_visor_data->zoomable_picture_container;
GtkAdjustment *hadjustment = gtk_scrolled_window_get_hadjustment (zoomable_picture_container);
GtkAdjustment *vadjustment = gtk_scrolled_window_get_vadjustment (zoomable_picture_container);
gtk_widget_grab_focus (GTK_WIDGET (chapter_visor_data->zoomable_picture_container));
set_image_zoomable_picture_container (chapter_visor_data);
g_object_set_property_double (G_OBJECT (vadjustment), "value", 0);
g_object_set_property_double (G_OBJECT (hadjustment), "value", 999);
}
static void static void
go_next (GtkButton *next, go_next (GtkButton *next,
gpointer user_data) { gpointer user_data) {
@ -139,7 +174,7 @@ go_next (GtkButton *next,
GListModel *pages = chapter_visor_data->pages; GListModel *pages = chapter_visor_data->pages;
if (chapter_visor_data->current_page < g_list_model_get_n_items (pages) -1) { if (chapter_visor_data->current_page < g_list_model_get_n_items (pages) -1) {
chapter_visor_data->current_page = chapter_visor_data->current_page + 1; chapter_visor_data->current_page = chapter_visor_data->current_page + 1;
set_image_zoomable_picture_container (chapter_visor_data); configure_zoomable_for_new_page (chapter_visor_data);
} }
} }
static void static void
@ -148,7 +183,7 @@ go_prev (GtkButton *prev,
ChapterVisorData *chapter_visor_data = (ChapterVisorData *) user_data; ChapterVisorData *chapter_visor_data = (ChapterVisorData *) user_data;
if (chapter_visor_data->current_page > 0) { if (chapter_visor_data->current_page > 0) {
chapter_visor_data->current_page = chapter_visor_data->current_page - 1; chapter_visor_data->current_page = chapter_visor_data->current_page - 1;
set_image_zoomable_picture_container (chapter_visor_data); configure_zoomable_for_new_page (chapter_visor_data);
} }
} }
@ -168,7 +203,7 @@ set_image_zoomable_picture_container (ChapterVisorData *chapter_visor_data) {
strlen (url_image_not_owned)); strlen (url_image_not_owned));
GtkPicture *picture = create_picture_from_url (url_image, 0, picture_ready_manga_page, GtkPicture *picture = create_picture_from_url (url_image, 0, picture_ready_manga_page,
zoomable_picture_container, chapter_visor_data); zoomable_picture_container, chapter_visor_data, false);
if (picture) { if (picture) {
chapter_visor_data->current_picture = GTK_PICTURE (picture); chapter_visor_data->current_picture = GTK_PICTURE (picture);
g_signal_connect (G_OBJECT (picture), "map", g_signal_connect (G_OBJECT (picture), "map",
@ -200,6 +235,13 @@ set_zoomable_picture_container_properties (
GtkScrolledWindow *zoomable_picture_container, GtkScrolledWindow *zoomable_picture_container,
ChapterVisorData *chapter_visor_data) { ChapterVisorData *chapter_visor_data) {
GtkGesture *zoom_controller = gtk_gesture_zoom_new (); GtkGesture *zoom_controller = gtk_gesture_zoom_new ();
GtkEventController *key_controller = gtk_event_controller_key_new ();
g_signal_connect (G_OBJECT (key_controller), "key-pressed", G_CALLBACK (zoomable_container_keybinding_handle),
chapter_visor_data);
gtk_widget_add_controller (GTK_WIDGET (zoomable_picture_container),
key_controller);
g_object_set_property_int (G_OBJECT (zoomable_picture_container), "hexpand", 1); g_object_set_property_int (G_OBJECT (zoomable_picture_container), "hexpand", 1);
g_object_set_property_int (G_OBJECT (zoomable_picture_container), "vexpand", 1); g_object_set_property_int (G_OBJECT (zoomable_picture_container), "vexpand", 1);
gtk_widget_add_controller (GTK_WIDGET (zoomable_picture_container), gtk_widget_add_controller (GTK_WIDGET (zoomable_picture_container),
@ -208,6 +250,56 @@ set_zoomable_picture_container_properties (
g_signal_connect (G_OBJECT (zoom_controller), "end", G_CALLBACK (zoom_end), chapter_visor_data); g_signal_connect (G_OBJECT (zoom_controller), "end", G_CALLBACK (zoom_end), chapter_visor_data);
} }
static void
zoomable_container_keybinding_handle (GtkEventControllerKey *self,
guint keyval, guint keycode, GdkModifierType state, gpointer user_data) {
ChapterVisorData *chapter_visor_data = (ChapterVisorData *) user_data;
GtkScrolledWindow *zoomable_picture_container = chapter_visor_data->zoomable_picture_container;
GtkAdjustment *vadjustment = gtk_scrolled_window_get_vadjustment (zoomable_picture_container);
GtkAdjustment *hadjustment = gtk_scrolled_window_get_hadjustment (zoomable_picture_container);
GValue adjustment = G_VALUE_INIT;
gdouble current_adjustment;
gdouble change_rate_key_movement = 50;
if (state & GDK_CONTROL_MASK ) {
if ( keyval == '+' ) {
set_image_dimensions (GTK_WIDGET (chapter_visor_data->current_picture),
chapter_visor_data, 2);
}
if ( keyval == '-' ) {
set_image_dimensions (GTK_WIDGET (chapter_visor_data->current_picture),
chapter_visor_data, 0.5);
}
}
if (state & GDK_SHIFT_MASK ) {
g_object_get_property (G_OBJECT (hadjustment), "value", &adjustment);
current_adjustment = g_value_get_double (&adjustment);
if (keyval == KEY_LEFT) {
g_object_set_property_double (G_OBJECT (hadjustment), "value", current_adjustment - change_rate_key_movement);
}
if (keyval == KEY_RIGHT) {
g_object_set_property_double (G_OBJECT (hadjustment), "value", current_adjustment + change_rate_key_movement);
}
g_object_get_property (G_OBJECT (vadjustment), "value", &adjustment);
current_adjustment = g_value_get_double (&adjustment);
if (keyval == KEY_UP) {
// UP
g_object_set_property_double (G_OBJECT (vadjustment), "value", current_adjustment - change_rate_key_movement);
}
if (keyval == KEY_DOWN) {
// UP
g_object_set_property_double (G_OBJECT (vadjustment), "value", current_adjustment + change_rate_key_movement);
}
} else {
if (keyval == KEY_LEFT) {
go_prev (NULL, chapter_visor_data);
}
if (keyval == KEY_RIGHT) {
go_next (NULL, chapter_visor_data);
}
}
}
static void static void
append_chapter_view_leaflet (AdwLeaflet *views_leaflet, append_chapter_view_leaflet (AdwLeaflet *views_leaflet,

View File

@ -90,7 +90,7 @@ create_detail_view (MgManga *manga, ControlsAdwaita *controls) {
GtkListView *chapter_list = NULL; GtkListView *chapter_list = NULL;
char *url_image = mg_manga_get_image_url(manga); char *url_image = mg_manga_get_image_url(manga);
GtkPicture *picture = create_picture_from_url (url_image, 200, GtkPicture *picture = create_picture_from_url (url_image, 200,
picture_ready_manga_detail, avatar_title_box, NULL); picture_ready_manga_detail, avatar_title_box, NULL, false);
char *manga_title_text = mg_manga_get_title (manga); char *manga_title_text = mg_manga_get_title (manga);
char *title_text = mg_util_xml_get_title_text ( char *title_text = mg_util_xml_get_title_text (
xml_util, manga_title_text); xml_util, manga_title_text);

View File

@ -26,6 +26,15 @@ manga_selected (GtkListView *list_view,
guint position, guint position,
gpointer user_data) { gpointer user_data) {
ControlsAdwaita *controls = (ControlsAdwaita *) user_data; ControlsAdwaita *controls = (ControlsAdwaita *) user_data;
controls->avoid_list_image_downloads = true;;
for (size_t i = 0; i < controls->image_threads_len; i++) {
g_cancellable_cancel (controls->image_threads[i]);
}
if (controls->image_threads) {
g_free (controls->image_threads);
}
controls->image_threads = NULL;
controls->image_threads_len = 0;
AdwLeaflet *views_leaflet = controls->views_leaflet; AdwLeaflet *views_leaflet = controls->views_leaflet;
GtkSingleSelection *selection = GTK_SINGLE_SELECTION GtkSingleSelection *selection = GTK_SINGLE_SELECTION
(gtk_list_view_get_model (list_view)); (gtk_list_view_get_model (list_view));
@ -44,6 +53,7 @@ manga_selected (GtkListView *list_view,
GtkBox *detail_view = create_detail_view (manga, controls); GtkBox *detail_view = create_detail_view (manga, controls);
adw_leaflet_append (views_leaflet, GTK_WIDGET (detail_view)); adw_leaflet_append (views_leaflet, GTK_WIDGET (detail_view));
adw_leaflet_navigate (views_leaflet, ADW_NAVIGATION_DIRECTION_FORWARD); adw_leaflet_navigate (views_leaflet, ADW_NAVIGATION_DIRECTION_FORWARD);
controls->avoid_list_image_downloads = false;
} }
#ifdef LIST_IMAGES #ifdef LIST_IMAGES
@ -65,15 +75,23 @@ static void
setup_list_view_mangas (GtkSignalListItemFactory *factory, setup_list_view_mangas (GtkSignalListItemFactory *factory,
GtkListItem *list_item, GtkListItem *list_item,
gpointer user_data) { gpointer user_data) {
ControlsAdwaita *controls = (ControlsAdwaita *) user_data;
MgManga *manga = gtk_list_item_get_item (list_item); MgManga *manga = gtk_list_item_get_item (list_item);
GtkBox *box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0)); GtkBox *box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
char *manga_title = mg_manga_get_title (manga); char *manga_title = mg_manga_get_title (manga);
char *image_url = mg_manga_get_image_url (manga); char *image_url = mg_manga_get_image_url (manga);
GtkWidget *label = gtk_label_new (manga_title); GtkWidget *label = gtk_label_new (manga_title);
GtkPicture *picture = NULL;
#ifdef LIST_IMAGES #ifdef LIST_IMAGES
GtkPicture *picture = create_picture_from_url (image_url, 100, GCancellable *cancellable = g_cancellable_new ();
picture_ready_manga_preview, box, NULL); picture = create_picture_from_url (image_url, 100,
picture_ready_manga_preview, box, cancellable,
controls->avoid_list_image_downloads);
controls->image_threads_len++;
controls->image_threads = g_realloc (controls->image_threads,
controls->image_threads_len * sizeof *(controls->image_threads));
controls->image_threads[controls->image_threads_len-1] = cancellable;
#endif #endif
g_object_set_property_int (G_OBJECT(box), "height-request", 100); g_object_set_property_int (G_OBJECT(box), "height-request", 100);
@ -91,14 +109,13 @@ setup_list_view_mangas (GtkSignalListItemFactory *factory,
GtkListView * GtkListView *
create_list_view_mangas (GListStore *mangas, ControlsAdwaita *controls) { create_list_view_mangas (GListStore *mangas, ControlsAdwaita *controls) {
AdwLeaflet *views_leaflet = controls->views_leaflet;
GtkSingleSelection *selection = gtk_single_selection_new (G_LIST_MODEL (mangas)); GtkSingleSelection *selection = gtk_single_selection_new (G_LIST_MODEL (mangas));
GtkListItemFactory *factory = gtk_signal_list_item_factory_new (); GtkListItemFactory *factory = gtk_signal_list_item_factory_new ();
GtkListView *list_view_manga = NULL; GtkListView *list_view_manga = NULL;
g_signal_connect (G_OBJECT (factory), "bind", g_signal_connect (G_OBJECT (factory), "bind",
G_CALLBACK (setup_list_view_mangas), G_CALLBACK (setup_list_view_mangas),
views_leaflet); controls);
list_view_manga = GTK_LIST_VIEW (gtk_list_view_new (GTK_SELECTION_MODEL (selection), list_view_manga = GTK_LIST_VIEW (gtk_list_view_new (GTK_SELECTION_MODEL (selection),
factory)); factory));

View File

@ -38,6 +38,8 @@ activate (AdwApplication *app,
AdwLeaflet *views_leaflet_explore; AdwLeaflet *views_leaflet_explore;
AdwLeaflet *views_leaflet_search; AdwLeaflet *views_leaflet_search;
AdwHeaderBar *header_bar; AdwHeaderBar *header_bar;
gtk_window_set_default_size (GTK_WINDOW (window), 3000, 3000);
#ifndef _WIN32 #ifndef _WIN32
swipe_back_t swipe_back = (swipe_back_t) dlsym swipe_back_t swipe_back = (swipe_back_t) dlsym
@ -54,7 +56,9 @@ activate (AdwApplication *app,
controls->is_set_previous = 0; controls->is_set_previous = 0;
controls->header = NULL; controls->header = NULL;
controls->view_stack = view_stack; controls->view_stack = view_stack;
controls->image_threads_len = 0;
controls->image_threads = NULL;
controls->avoid_list_image_downloads = false;
views_leaflet_explore = create_explore_leaflet (controls, swipe_back); views_leaflet_explore = create_explore_leaflet (controls, swipe_back);
views_leaflet_search = create_search_leaflet (controls, swipe_back); views_leaflet_search = create_search_leaflet (controls, swipe_back);

View File

@ -45,7 +45,7 @@ threaded_picture_recover (GTask *task, gpointer source_object,
static GMutex mutex; static GMutex mutex;
g_mutex_lock (&mutex); g_mutex_lock (&mutex);
if (!g_file_query_exists (image, NULL)) { if (!g_file_query_exists (image, NULL)) {
g_warning ("Storing %s", url); g_info ("Storing %s", url);
iostream = g_file_create_readwrite (image, G_FILE_CREATE_NONE, iostream = g_file_create_readwrite (image, G_FILE_CREATE_NONE,
NULL, &error); NULL, &error);
if (error) { if (error) {
@ -96,7 +96,7 @@ free_picture_thread_attributes (gpointer user_data) {
GtkPicture * GtkPicture *
create_picture_from_url (const char *const url, gint picture_size, create_picture_from_url (const char *const url, gint picture_size,
GAsyncReadyCallback ready, gpointer source_object, GAsyncReadyCallback ready, gpointer source_object,
gpointer callback_data) { gpointer callback_data, bool do_not_download) {
GtkPicture *picture = NULL; GtkPicture *picture = NULL;
GFile *image = NULL; GFile *image = NULL;
GdkTexture *texture = NULL; GdkTexture *texture = NULL;
@ -118,7 +118,7 @@ create_picture_from_url (const char *const url, gint picture_size,
g_object_set_property_int (G_OBJECT(picture), "width-request", picture_size); g_object_set_property_int (G_OBJECT(picture), "width-request", picture_size);
} }
} else { } else if (!do_not_download) {
GTask *task = g_task_new (source_object, NULL, ready, callback_data); GTask *task = g_task_new (source_object, NULL, ready, callback_data);
PictureThreadAttributes *attrs = g_malloc (sizeof *attrs); PictureThreadAttributes *attrs = g_malloc (sizeof *attrs);
attrs->url = g_malloc (url_len * sizeof *url); attrs->url = g_malloc (url_len * sizeof *url);
@ -126,6 +126,7 @@ create_picture_from_url (const char *const url, gint picture_size,
attrs->image = image; attrs->image = image;
attrs->picture_size = picture_size; attrs->picture_size = picture_size;
g_task_set_task_data (task, attrs, free_picture_thread_attributes); g_task_set_task_data (task, attrs, free_picture_thread_attributes);
g_task_set_return_on_cancel (task, true);
g_task_run_in_thread (task, threaded_picture_recover); g_task_run_in_thread (task, threaded_picture_recover);
} }
cleanup_create_picture_from_url: cleanup_create_picture_from_url:

View File

@ -9,6 +9,11 @@ static void
search_text_changed (GtkEntry *entry, search_text_changed (GtkEntry *entry,
gpointer user_data); gpointer user_data);
typedef struct {
GtkListView *list_view_mangas;
ControlsAdwaita *controls;
} SearchTextData;
GtkWidget * GtkWidget *
create_search_view (ControlsAdwaita *controls) { create_search_view (ControlsAdwaita *controls) {
GtkWidget *search_view = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); GtkWidget *search_view = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
@ -17,12 +22,15 @@ create_search_view (ControlsAdwaita *controls) {
GtkWidget *scroll = gtk_scrolled_window_new (); GtkWidget *scroll = gtk_scrolled_window_new ();
GListStore *mangas = g_list_store_new(MG_TYPE_MANGA); GListStore *mangas = g_list_store_new(MG_TYPE_MANGA);
GtkListView *list_view_mangas; GtkListView *list_view_mangas;
SearchTextData *search_text_data = g_malloc (sizeof *search_text_data);
gtk_box_append (GTK_BOX (search_view), search_entry); gtk_box_append (GTK_BOX (search_view), search_entry);
list_view_mangas = create_list_view_mangas (mangas, controls); list_view_mangas = create_list_view_mangas (mangas, controls);
search_text_data->list_view_mangas = list_view_mangas;
search_text_data ->controls = controls;
g_signal_connect (search_entry, "activate", g_signal_connect (search_entry, "activate",
G_CALLBACK (search_text_changed), list_view_mangas); G_CALLBACK (search_text_changed), search_text_data);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scroll), gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scroll),
GTK_WIDGET (list_view_mangas)); GTK_WIDGET (list_view_mangas));
@ -36,14 +44,26 @@ create_search_view (ControlsAdwaita *controls) {
static void static void
search_text_changed (GtkEntry *entry, search_text_changed (GtkEntry *entry,
gpointer user_data) { gpointer user_data) {
GtkListView *list_view_mangas = GTK_LIST_VIEW (user_data); SearchTextData *search_text_data = (SearchTextData *) user_data;
ControlsAdwaita *controls = search_text_data->controls;
GtkListView *list_view_mangas = search_text_data->list_view_mangas;
GtkEntryBuffer *buffer = gtk_entry_get_buffer (entry); GtkEntryBuffer *buffer = gtk_entry_get_buffer (entry);
MgBackendReadmng *readmng = mg_backend_readmng_new (); MgBackendReadmng *readmng = mg_backend_readmng_new ();
const char *search_string = gtk_entry_buffer_get_text (buffer); const char *search_string = gtk_entry_buffer_get_text (buffer);
GListStore *mangas = mg_backend_readmng_search (readmng, search_string); GListStore *mangas = mg_backend_readmng_search (readmng, search_string);
for (size_t i = 0; i < controls->image_threads_len; i++) {
g_cancellable_cancel (controls->image_threads[i]);
}
if (controls->image_threads) {
g_free (controls->image_threads);
}
controls->image_threads = NULL;
controls->image_threads_len = 0;
controls->avoid_list_image_downloads = true;
if (!mangas) return; if (!mangas) return;
GtkSingleSelection *selection = GTK_SINGLE_SELECTION ( GtkSingleSelection *selection = GTK_SINGLE_SELECTION (
gtk_list_view_get_model (list_view_mangas)); gtk_list_view_get_model (list_view_mangas));
controls->avoid_list_image_downloads = false;
gtk_single_selection_set_model (selection, gtk_single_selection_set_model (selection,
G_LIST_MODEL (mangas)); G_LIST_MODEL (mangas));
} }