evince/evince-libarchive-gnome-3-24.patch

1442 lines
42 KiB
Diff
Raw Normal View History

From b3ee96e514a2f633ed6cd5db30263edaf5edf0f5 Mon Sep 17 00:00:00 2001
From: Bastien Nocera <hadess@hadess.net>
Date: Thu, 29 Jan 2015 16:54:21 +0100
Subject: [PATCH] comics: Port to using libarchive for unarchiving
v8:
- Fix double-free in error path when decompressing images
v7:
- Bump buffer size in ev-archive, good performance increase for
local files
v6:
- Fix 2 pretty big memory leaks
- Remove unneeded archive_read_data_skip() calls
- Optimise the "no rotation" case (could also be done for gnome-3-24)
- Use render_pixbuf_size_prepared_cb()
- Add debug to "next header" archive calls
v5:
- Remove unused members of ComicsDocument struct
- Split archive handling into an EvArchive object
- Fix copy/paste error in configure.ac
v4:
- Fix crash caused by a bug in comics_document_list()
(the array was not NULL terminated)
- Remove duplicate "!cb_files" check
- Use "size-prepared" instead of "area-prepared" to get the doc size
- Fix link to libarchive bug in code, not working yet :/
v3:
- Rebase against latest evince, making sure to bring back:
- Use Unicode in translatable strings
- Sort pages in natural order
- Fix mime-type comparisons
- https://github.com/libarchive/libarchive/issues/373 looks
like it's fixed!
v2:
- Rebase against latest evince
- Update libarchive bug URL
https://bugzilla.gnome.org/show_bug.cgi?id=720742
---
backend/comics/Makefile.am | 6 +-
backend/comics/comics-document.c | 807 +++++++++++----------------------------
backend/comics/ev-archive.c | 277 ++++++++++++++
backend/comics/ev-archive.h | 47 +++
configure.ac | 12 +-
5 files changed, 562 insertions(+), 587 deletions(-)
create mode 100644 backend/comics/ev-archive.c
create mode 100644 backend/comics/ev-archive.h
diff --git a/backend/comics/Makefile.am b/backend/comics/Makefile.am
index b047ad36..856f469c 100644
--- a/backend/comics/Makefile.am
+++ b/backend/comics/Makefile.am
@@ -2,7 +2,9 @@ backend_LTLIBRARIES = libcomicsdocument.la
libcomicsdocument_la_SOURCES = \
comics-document.c \
- comics-document.h
+ comics-document.h \
+ ev-archive.c \
+ ev-archive.h
libcomicsdocument_la_CPPFLAGS = \
-I$(top_srcdir) \
@@ -13,12 +15,14 @@ libcomicsdocument_la_CPPFLAGS = \
libcomicsdocument_la_CFLAGS = \
$(BACKEND_CFLAGS) \
+ $(LIBARCHIVE_CFLAGS) \
$(LIB_CFLAGS) \
$(AM_CFLAGS)
libcomicsdocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS)
libcomicsdocument_la_LIBADD = \
$(top_builddir)/libdocument/libevdocument3.la \
+ $(LIBARCHIVE_LIBS) \
$(BACKEND_LIBS) \
$(LIB_LIBS)
diff --git a/backend/comics/comics-document.c b/backend/comics/comics-document.c
index 641d7856..b55c25b3 100644
--- a/backend/comics/comics-document.c
+++ b/backend/comics/comics-document.c
@@ -2,6 +2,7 @@
/*
* Copyright (C) 2009-2010 Juanjo Marín <juanj.marin@juntadeandalucia.es>
* Copyright (C) 2005, Teemu Tervo <teemu.tervo@gmx.net>
+ * Copyright (C) 2016-2017, Bastien Nocera <hadess@hadess.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,34 +31,12 @@
#include <glib/gstdio.h>
#include <gio/gio.h>
-#ifdef G_OS_WIN32
-# define WIFEXITED(x) ((x) != 3)
-# define WEXITSTATUS(x) (x)
-#else
-# include <sys/wait.h>
-#endif
-
#include "comics-document.h"
#include "ev-document-misc.h"
#include "ev-file-helpers.h"
+#include "ev-archive.h"
-#ifdef G_OS_WIN32
-/* On windows g_spawn_command_line_sync reads stdout in O_BINARY mode, not in O_TEXT mode.
- * As a consequence, newlines are in a platform dependent representation (\r\n). This
- * might be considered a bug in glib.
- */
-#define EV_EOL "\r\n"
-#else
-#define EV_EOL "\n"
-#endif
-
-typedef enum
-{
- RARLABS,
- GNAUNRAR,
- UNZIP,
- P7ZIP
-} ComicBookDecompressType;
+#define BLOCK_SIZE 10240
typedef struct _ComicsDocumentClass ComicsDocumentClass;
@@ -68,346 +47,98 @@ struct _ComicsDocumentClass
struct _ComicsDocument
{
- EvDocument parent_instance;
-
- gchar *archive, *dir;
- GPtrArray *page_names;
- gchar *selected_command, *alternative_command;
- gchar *extract_command, *list_command, *decompress_tmp;
- gboolean regex_arg;
- gint offset;
- ComicBookDecompressType command_usage;
-};
-
-#define OFFSET_7Z 53
-#define OFFSET_ZIP 2
-#define NO_OFFSET 0
-
-/* For perfomance reasons of 7z* we've choosen to decompress on the temporary
- * directory instead of decompressing on the stdout */
-
-/**
- * @extract: command line arguments to pass to extract a file from the archive
- * to stdout.
- * @list: command line arguments to list the archive contents
- * @decompress_tmp: command line arguments to pass to extract the archive
- * into a directory.
- * @regex_arg: whether the command can accept regex expressions
- * @offset: the position offset of the filename on each line in the output of
- * running the @list command
- */
-typedef struct {
- char *extract;
- char *list;
- char *decompress_tmp;
- gboolean regex_arg;
- gint offset;
-} ComicBookDecompressCommand;
-
-static const ComicBookDecompressCommand command_usage_def[] = {
- /* RARLABS unrar */
- {"%s p -c- -ierr --", "%s vb -c- -- %s", NULL , FALSE, NO_OFFSET},
-
- /* GNA! unrar */
- {NULL , "%s t %s" , "%s -xf %s %s" , FALSE, NO_OFFSET},
-
- /* unzip */
- {"%s -p -C --" , "%s %s" , NULL , TRUE , OFFSET_ZIP},
-
- /* 7zip */
- {NULL , "%s l -- %s" , "%s x -y %s -o%s", FALSE, OFFSET_7Z},
+ EvDocument parent_instance;
+ EvArchive *a;
+ EvArchiveType a_type;
+ gchar *archive_path;
+ gchar *archive_uri;
+ GPtrArray *page_names;
};
static GSList* get_supported_image_extensions (void);
-static void get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
- gpointer data);
+static void get_page_size_prepared_cb (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ gpointer data);
static void render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
gint width,
gint height,
EvRenderContext *rc);
-static char** extract_argv (EvDocument *document,
- gint page);
-
EV_BACKEND_REGISTER (ComicsDocument, comics_document)
-/**
- * comics_regex_quote:
- * @unquoted_string: a literal string
- *
- * Quotes a string so unzip will not interpret the regex expressions of
- * @unquoted_string. Basically, this functions uses [] to disable regex
- * expressions. The return value must be freed with * g_free()
- *
- * Return value: quoted and disabled-regex string
- **/
-static gchar *
-comics_regex_quote (const gchar *unquoted_string)
+static void
+comics_document_reset_archive (ComicsDocument *comics_document)
{
- const gchar *p;
- GString *dest;
-
- dest = g_string_new ("'");
-
- p = unquoted_string;
-
- while (*p) {
- switch (*p) {
- /* * matches a sequence of 0 or more characters */
- case ('*'):
- /* ? matches exactly 1 charactere */
- case ('?'):
- /* [...] matches any single character found inside
- * the brackets. Disabling the first bracket is enough.
- */
- case ('['):
- g_string_append (dest, "[");
- g_string_append_c (dest, *p);
- g_string_append (dest, "]");
- break;
- /* Because \ escapes regex expressions that we are
- * disabling for unzip, we need to disable \ too */
- case ('\\'):
- g_string_append (dest, "[\\\\]");
- break;
- /* Escape single quote inside the string */
- case ('\''):
- g_string_append (dest, "'\\''");
- break;
- default:
- g_string_append_c (dest, *p);
- break;
- }
- ++p;
- }
- g_string_append_c (dest, '\'');
- return g_string_free (dest, FALSE);
+ g_clear_object (&comics_document->a);
+ comics_document->a = ev_archive_new ();
+ ev_archive_set_archive_type (comics_document->a, comics_document->a_type);
}
-
-/* This function manages the command for decompressing a comic book */
-static gboolean
-comics_decompress_temp_dir (const gchar *command_decompress_tmp,
- const gchar *command,
- GError **error)
+static char **
+comics_document_list (ComicsDocument *comics_document)
{
- gboolean success;
- gchar *std_out, *basename;
- GError *err = NULL;
- gint retval;
-
- success = g_spawn_command_line_sync (command_decompress_tmp, &std_out,
- NULL, &retval, &err);
- basename = g_path_get_basename (command);
- if (!success) {
- g_set_error (error,
- EV_DOCUMENT_ERROR,
- EV_DOCUMENT_ERROR_INVALID,
- _("Error launching the command “%s” in order to "
- "decompress the comic book: %s"),
- basename,
- err->message);
- g_error_free (err);
- } else if (WIFEXITED (retval)) {
- if (WEXITSTATUS (retval) == EXIT_SUCCESS) {
- g_free (std_out);
- g_free (basename);
- return TRUE;
- } else {
- g_set_error (error,
- EV_DOCUMENT_ERROR,
- EV_DOCUMENT_ERROR_INVALID,
- _("The command “%s” failed at "
- "decompressing the comic book."),
- basename);
- g_free (std_out);
+ char **ret = NULL;
+ GPtrArray *array;
+
+ if (!ev_archive_open_filename (comics_document->a, comics_document->archive_path, NULL))
+ goto out;
+
+ array = g_ptr_array_new ();
+
+ while (1) {
+ const char *name;
+ GError *error = NULL;
+
+ if (!ev_archive_read_next_header (comics_document->a, &error)) {
+ if (error != NULL) {
+ g_warning ("Fatal error handling archive: %s", error->message);
+ g_error_free (error);
+ }
+ break;
}
- } else {
- g_set_error (error,
- EV_DOCUMENT_ERROR,
- EV_DOCUMENT_ERROR_INVALID,
- _("The command “%s” did not end normally."),
- basename);
- g_free (std_out);
- }
- g_free (basename);
- return FALSE;
-}
-/* This function shows how to use the chosen command for decompressing a
- * comic book file. It modifies fields of the ComicsDocument struct with
- * this information */
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat-nonliteral"
-static gboolean
-comics_generate_command_lines (ComicsDocument *comics_document,
- GError **error)
-{
- gchar *quoted_file, *quoted_file_aux;
- gchar *quoted_command;
- ComicBookDecompressType type;
-
- type = comics_document->command_usage;
- comics_document->regex_arg = command_usage_def[type].regex_arg;
- quoted_command = g_shell_quote (comics_document->selected_command);
- if (comics_document->regex_arg) {
- quoted_file = comics_regex_quote (comics_document->archive);
- quoted_file_aux = g_shell_quote (comics_document->archive);
- comics_document->list_command =
- g_strdup_printf (command_usage_def[type].list,
- comics_document->alternative_command,
- quoted_file_aux);
- g_free (quoted_file_aux);
- } else {
- quoted_file = g_shell_quote (comics_document->archive);
- comics_document->list_command =
- g_strdup_printf (command_usage_def[type].list,
- quoted_command, quoted_file);
+ name = ev_archive_get_entry_pathname (comics_document->a);
+
+ g_debug ("Adding '%s' to the list of files in the comics", name);
+ g_ptr_array_add (array, g_strdup (name));
}
- comics_document->extract_command =
- g_strdup_printf (command_usage_def[type].extract,
- quoted_command);
- comics_document->offset = command_usage_def[type].offset;
- if (command_usage_def[type].decompress_tmp) {
- comics_document->dir = ev_mkdtemp ("evince-comics-XXXXXX", error);
- if (comics_document->dir == NULL)
- return FALSE;
-
- /* unrar-free can't create directories, but ev_mkdtemp already created the dir */
-
- comics_document->decompress_tmp =
- g_strdup_printf (command_usage_def[type].decompress_tmp,
- quoted_command, quoted_file,
- comics_document->dir);
- g_free (quoted_file);
- g_free (quoted_command);
-
- if (!comics_decompress_temp_dir (comics_document->decompress_tmp,
- comics_document->selected_command, error))
- return FALSE;
- else
- return TRUE;
+
+ if (array->len == 0) {
+ g_ptr_array_free (array, TRUE);
} else {
- g_free (quoted_file);
- g_free (quoted_command);
- return TRUE;
+ g_ptr_array_add (array, NULL);
+ ret = (char **) g_ptr_array_free (array, FALSE);
}
+out:
+ comics_document_reset_archive (comics_document);
+ return ret;
}
-#pragma GCC diagnostic pop
-/* This function chooses an external command for decompressing a comic
- * book based on its mime tipe. */
-static gboolean
-comics_check_decompress_command (gchar *mime_type,
+/* This function chooses the archive decompression support
+ * book based on its mime type. */
+static gboolean
+comics_check_decompress_support (gchar *mime_type,
ComicsDocument *comics_document,
GError **error)
{
- gboolean success;
- gchar *std_out, *std_err;
- gint retval;
- GError *err = NULL;
-
- /* FIXME, use proper cbr/cbz mime types once they're
- * included in shared-mime-info */
-
if (g_content_type_is_a (mime_type, "application/x-cbr") ||
g_content_type_is_a (mime_type, "application/x-rar")) {
- /* The RARLAB provides a no-charge proprietary (freeware)
- * decompress-only client for Linux called unrar. Another
- * option is a GPLv2-licensed command-line tool developed by
- * the Gna! project. Confusingly enough, the free software RAR
- * decoder is also named unrar. For this reason we need to add
- * some lines for disambiguation. Sorry for the added the
- * complexity but it's life :)
- * Finally, some distributions, like Debian, rename this free
- * option as unrar-free.
- * */
- comics_document->selected_command =
- g_find_program_in_path ("unrar");
- if (comics_document->selected_command) {
- /* We only use std_err to avoid printing useless error
- * messages on the terminal */
- success =
- g_spawn_command_line_sync (
- comics_document->selected_command,
- &std_out, &std_err,
- &retval, &err);
- if (!success) {
- g_propagate_error (error, err);
- g_error_free (err);
- return FALSE;
- /* I don't check retval status because RARLAB unrar
- * doesn't have a way to return 0 without involving an
- * operation with a file*/
- } else if (WIFEXITED (retval)) {
- if (g_strrstr (std_out,"freeware") != NULL)
- /* The RARLAB freeware client */
- comics_document->command_usage = RARLABS;
- else
- /* The Gna! free software client */
- comics_document->command_usage = GNAUNRAR;
-
- g_free (std_out);
- g_free (std_err);
- return TRUE;
- }
- }
- /* The Gna! free software client with Debian naming convention */
- comics_document->selected_command =
- g_find_program_in_path ("unrar-free");
- if (comics_document->selected_command) {
- comics_document->command_usage = GNAUNRAR;
+ if (ev_archive_set_archive_type (comics_document->a, EV_ARCHIVE_TYPE_RAR))
return TRUE;
- }
} else if (g_content_type_is_a (mime_type, "application/x-cbz") ||
g_content_type_is_a (mime_type, "application/zip")) {
- /* InfoZIP's unzip program */
- comics_document->selected_command =
- g_find_program_in_path ("unzip");
- comics_document->alternative_command =
- g_find_program_in_path ("zipnote");
- if (comics_document->selected_command &&
- comics_document->alternative_command) {
- comics_document->command_usage = UNZIP;
- return TRUE;
- }
- /* fallback mode using 7za and 7z from p7zip project */
- comics_document->selected_command =
- g_find_program_in_path ("7za");
- if (comics_document->selected_command) {
- comics_document->command_usage = P7ZIP;
- return TRUE;
- }
- comics_document->selected_command =
- g_find_program_in_path ("7z");
- if (comics_document->selected_command) {
- comics_document->command_usage = P7ZIP;
+ if (ev_archive_set_archive_type (comics_document->a, EV_ARCHIVE_TYPE_ZIP))
return TRUE;
- }
-
} else if (g_content_type_is_a (mime_type, "application/x-cb7") ||
g_content_type_is_a (mime_type, "application/x-7z-compressed")) {
- /* 7zr, 7za and 7z are the commands from the p7zip project able
- * to decompress .7z files */
- comics_document->selected_command =
- g_find_program_in_path ("7zr");
- if (comics_document->selected_command) {
- comics_document->command_usage = P7ZIP;
- return TRUE;
- }
- comics_document->selected_command =
- g_find_program_in_path ("7za");
- if (comics_document->selected_command) {
- comics_document->command_usage = P7ZIP;
+ if (ev_archive_set_archive_type (comics_document->a, EV_ARCHIVE_TYPE_7Z))
return TRUE;
- }
- comics_document->selected_command =
- g_find_program_in_path ("7z");
- if (comics_document->selected_command) {
- comics_document->command_usage = P7ZIP;
+ } else if (g_content_type_is_a (mime_type, "application/x-cbt") ||
+ g_content_type_is_a (mime_type, "application/x-tar")) {
+ if (ev_archive_set_archive_type (comics_document->a, EV_ARCHIVE_TYPE_TAR))
return TRUE;
- }
} else {
g_set_error (error,
EV_DOCUMENT_ERROR,
@@ -419,8 +150,8 @@ comics_check_decompress_command (gchar *mime_type,
g_set_error_literal (error,
EV_DOCUMENT_ERROR,
EV_DOCUMENT_ERROR_INVALID,
- _("Cant find an appropriate command to "
- "decompress this type of comic book"));
+ _("libarchive lacks support for this comic books "
+ "compression, please contact your distributor"));
return FALSE;
}
@@ -449,56 +180,45 @@ comics_document_load (EvDocument *document,
{
ComicsDocument *comics_document = COMICS_DOCUMENT (document);
GSList *supported_extensions;
- gchar *std_out;
gchar *mime_type;
gchar **cb_files, *cb_file;
- gboolean success;
- int i, retval;
+ int i;
GError *err = NULL;
+ GFile *file;
- comics_document->archive = g_filename_from_uri (uri, NULL, error);
- if (!comics_document->archive)
+ file = g_file_new_for_uri (uri);
+ comics_document->archive_path = g_file_get_path (file);
+ g_object_unref (file);
+
+ if (!comics_document->archive_path) {
+ g_set_error_literal (error,
+ EV_DOCUMENT_ERROR,
+ EV_DOCUMENT_ERROR_INVALID,
+ _("Can not get local path for archive"));
return FALSE;
+ }
+
+ comics_document->archive_uri = g_strdup (uri);
mime_type = ev_file_get_mime_type (uri, FALSE, &err);
if (mime_type == NULL)
return FALSE;
-
- if (!comics_check_decompress_command (mime_type, comics_document,
- error)) {
+
+ if (!comics_check_decompress_support (mime_type, comics_document, error)) {
g_free (mime_type);
return FALSE;
- } else if (!comics_generate_command_lines (comics_document, error)) {
- g_free (mime_type);
- return FALSE;
}
+ comics_document->a_type = ev_archive_get_archive_type (comics_document->a);
g_free (mime_type);
/* Get list of files in archive */
- success = g_spawn_command_line_sync (comics_document->list_command,
- &std_out, NULL, &retval, error);
-
- if (!success) {
- return FALSE;
- } else if (!WIFEXITED(retval) || WEXITSTATUS(retval) != EXIT_SUCCESS) {
+ cb_files = comics_document_list (comics_document);
+ if (!cb_files) {
g_set_error_literal (error,
EV_DOCUMENT_ERROR,
EV_DOCUMENT_ERROR_INVALID,
- _("File corrupted"));
- return FALSE;
- }
-
- /* FIXME: is this safe against filenames containing \n in the archive ? */
- cb_files = g_strsplit (std_out, EV_EOL, 0);
-
- g_free (std_out);
-
- if (!cb_files) {
- g_set_error_literal (error,
- EV_DOCUMENT_ERROR,
- EV_DOCUMENT_ERROR_INVALID,
- _("No files in archive"));
+ _("File corrupted or no files in archive"));
return FALSE;
}
@@ -506,18 +226,7 @@ comics_document_load (EvDocument *document,
supported_extensions = get_supported_image_extensions ();
for (i = 0; cb_files[i] != NULL; i++) {
- if (comics_document->offset != NO_OFFSET) {
- if (g_utf8_strlen (cb_files[i],-1) >
- comics_document->offset) {
- cb_file =
- g_utf8_offset_to_pointer (cb_files[i],
- comics_document->offset);
- } else {
- continue;
- }
- } else {
- cb_file = cb_files[i];
- }
+ cb_file = cb_files[i];
gchar *suffix = g_strrstr (cb_file, ".");
if (!suffix)
continue;
@@ -548,7 +257,6 @@ comics_document_load (EvDocument *document,
return TRUE;
}
-
static gboolean
comics_document_save (EvDocument *document,
const char *uri,
@@ -556,7 +264,7 @@ comics_document_save (EvDocument *document,
{
ComicsDocument *comics_document = COMICS_DOCUMENT (document);
- return ev_xfer_uri_simple (comics_document->archive, uri, error);
+ return ev_xfer_uri_simple (comics_document->archive_uri, uri, error);
}
static int
@@ -570,6 +278,12 @@ comics_document_get_n_pages (EvDocument *document)
return comics_document->page_names->len;
}
+typedef struct {
+ gboolean got_info;
+ int height;
+ int width;
+} pixbuf_info;
+
static void
comics_document_get_page_size (EvDocument *document,
EvPage *page,
@@ -577,74 +291,79 @@ comics_document_get_page_size (EvDocument *document,
double *height)
{
GdkPixbufLoader *loader;
- char **argv;
- guchar buf[1024];
- gboolean success, got_size = FALSE;
- gint outpipe = -1;
- GPid child_pid;
- gssize bytes;
- GdkPixbuf *pixbuf;
- gchar *filename;
ComicsDocument *comics_document = COMICS_DOCUMENT (document);
-
- if (!comics_document->decompress_tmp) {
- argv = extract_argv (document, page->index);
- success = g_spawn_async_with_pipes (NULL, argv, NULL,
- G_SPAWN_SEARCH_PATH |
- G_SPAWN_STDERR_TO_DEV_NULL,
- NULL, NULL,
- &child_pid,
- NULL, &outpipe, NULL, NULL);
- g_strfreev (argv);
- g_return_if_fail (success == TRUE);
-
- loader = gdk_pixbuf_loader_new ();
- g_signal_connect (loader, "area-prepared",
- G_CALLBACK (get_page_size_area_prepared_cb),
- &got_size);
-
- while (outpipe >= 0) {
- bytes = read (outpipe, buf, 1024);
-
- if (bytes > 0)
- gdk_pixbuf_loader_write (loader, buf, bytes, NULL);
- if (bytes <= 0 || got_size) {
- close (outpipe);
- outpipe = -1;
- gdk_pixbuf_loader_close (loader, NULL);
+ const char *page_path;
+ pixbuf_info info;
+ GError *error = NULL;
+
+ if (!ev_archive_open_filename (comics_document->a, comics_document->archive_path, &error)) {
+ g_warning ("Fatal error opening archive: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ loader = gdk_pixbuf_loader_new ();
+ info.got_info = FALSE;
+ g_signal_connect (loader, "size-prepared",
+ G_CALLBACK (get_page_size_prepared_cb),
+ &info);
+
+ page_path = g_ptr_array_index (comics_document->page_names, page->index);
+
+ while (1) {
+ const char *name;
+ GError *error = NULL;
+
+ if (!ev_archive_read_next_header (comics_document->a, &error)) {
+ if (error != NULL) {
+ g_warning ("Fatal error handling archive: %s", error->message);
+ g_error_free (error);
}
+ break;
}
- pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
- if (pixbuf) {
- if (width)
- *width = gdk_pixbuf_get_width (pixbuf);
- if (height)
- *height = gdk_pixbuf_get_height (pixbuf);
- }
- g_spawn_close_pid (child_pid);
- g_object_unref (loader);
- } else {
- filename = g_build_filename (comics_document->dir,
- (char *) comics_document->page_names->pdata[page->index],
- NULL);
- pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
- if (pixbuf) {
- if (width)
- *width = gdk_pixbuf_get_width (pixbuf);
- if (height)
- *height = gdk_pixbuf_get_height (pixbuf);
- g_object_unref (pixbuf);
+
+ name = ev_archive_get_entry_pathname (comics_document->a);
+ if (g_strcmp0 (name, page_path) == 0) {
+ char buf[BLOCK_SIZE];
+ gssize read;
+
+ read = ev_archive_read_data (comics_document->a, buf, sizeof(buf), &error);
+ while (read > 0 && !info.got_info) {
+ gdk_pixbuf_loader_write (loader, (guchar *) buf, read, NULL);
+ read = ev_archive_read_data (comics_document->a, buf, BLOCK_SIZE, &error);
+ }
+ if (read < 0) {
+ g_warning ("Fatal error reading '%s' in archive: %s", name, error->message);
+ g_error_free (error);
+ }
+ break;
}
- g_free (filename);
}
+
+ gdk_pixbuf_loader_close (loader, NULL);
+ g_object_unref (loader);
+
+ if (info.got_info) {
+ if (width)
+ *width = info.width;
+ if (height)
+ *height = info.height;
+ }
+
+out:
+ comics_document_reset_archive (comics_document);
}
static void
-get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
- gpointer data)
+get_page_size_prepared_cb (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ gpointer data)
{
- gboolean *got_size = data;
- *got_size = TRUE;
+ pixbuf_info *info = data;
+ info->got_info = TRUE;
+ info->height = height;
+ info->width = width;
}
static GdkPixbuf *
@@ -652,73 +371,73 @@ comics_document_render_pixbuf (EvDocument *document,
EvRenderContext *rc)
{
GdkPixbufLoader *loader;
- GdkPixbuf *rotated_pixbuf, *tmp_pixbuf;
- char **argv;
- guchar buf[4096];
- gboolean success;
- gint outpipe = -1;
- GPid child_pid;
- gssize bytes;
- gint width, height;
- gchar *filename;
+ GdkPixbuf *tmp_pixbuf;
+ GdkPixbuf *rotated_pixbuf;
ComicsDocument *comics_document = COMICS_DOCUMENT (document);
-
- if (!comics_document->decompress_tmp) {
- argv = extract_argv (document, rc->page->index);
- success = g_spawn_async_with_pipes (NULL, argv, NULL,
- G_SPAWN_SEARCH_PATH |
- G_SPAWN_STDERR_TO_DEV_NULL,
- NULL, NULL,
- &child_pid,
- NULL, &outpipe, NULL, NULL);
- g_strfreev (argv);
- g_return_val_if_fail (success == TRUE, NULL);
-
- loader = gdk_pixbuf_loader_new ();
- g_signal_connect (loader, "size-prepared",
- G_CALLBACK (render_pixbuf_size_prepared_cb),
- rc);
-
- while (outpipe >= 0) {
- bytes = read (outpipe, buf, 4096);
-
- if (bytes > 0) {
- gdk_pixbuf_loader_write (loader, buf, bytes,
- NULL);
- } else if (bytes <= 0) {
- close (outpipe);
- gdk_pixbuf_loader_close (loader, NULL);
- outpipe = -1;
+ const char *page_path;
+ GError *error = NULL;
+
+ if (!ev_archive_open_filename (comics_document->a, comics_document->archive_path, &error)) {
+ g_warning ("Fatal error opening archive: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ loader = gdk_pixbuf_loader_new ();
+ g_signal_connect (loader, "size-prepared",
+ G_CALLBACK (render_pixbuf_size_prepared_cb),
+ rc);
+
+ page_path = g_ptr_array_index (comics_document->page_names, rc->page->index);
+
+ while (1) {
+ const char *name;
+
+ if (!ev_archive_read_next_header (comics_document->a, &error)) {
+ if (error != NULL) {
+ g_warning ("Fatal error handling archive: %s", error->message);
+ g_error_free (error);
}
+ break;
+ }
+
+ name = ev_archive_get_entry_pathname (comics_document->a);
+ if (g_strcmp0 (name, page_path) == 0) {
+ size_t size = ev_archive_get_entry_size (comics_document->a);
+ char *buf;
+ ssize_t read;
+
+ buf = g_malloc (size);
+ read = ev_archive_read_data (comics_document->a, buf, size, &error);
+ if (read <= 0) {
+ if (read < 0) {
+ g_warning ("Fatal error reading '%s' in archive: %s", name, error->message);
+ g_error_free (error);
+ } else {
+ g_warning ("Read an empty file from the archive");
+ }
+ } else {
+ gdk_pixbuf_loader_write (loader, (guchar *) buf, size, NULL);
+ }
+ g_free (buf);
+ gdk_pixbuf_loader_close (loader, NULL);
+ break;
}
- tmp_pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
- rotated_pixbuf =
- gdk_pixbuf_rotate_simple (tmp_pixbuf,
- 360 - rc->rotation);
- g_spawn_close_pid (child_pid);
- g_object_unref (loader);
- } else {
- int scaled_width, scaled_height;
-
- filename =
- g_build_filename (comics_document->dir,
- (char *) comics_document->page_names->pdata[rc->page->index],
- NULL);
-
- gdk_pixbuf_get_file_info (filename, &width, &height);
-
- ev_render_context_compute_scaled_size (rc, width, height,
- &scaled_width, &scaled_height);
-
- tmp_pixbuf =
- gdk_pixbuf_new_from_file_at_size (
- filename, scaled_width, scaled_height, NULL);
- rotated_pixbuf =
- gdk_pixbuf_rotate_simple (tmp_pixbuf,
- 360 - rc->rotation);
- g_free (filename);
- g_object_unref (tmp_pixbuf);
}
+
+ tmp_pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+ rotated_pixbuf = NULL;
+ if (tmp_pixbuf) {
+ if ((rc->rotation % 360) == 0)
+ rotated_pixbuf = g_object_ref (tmp_pixbuf);
+ else
+ rotated_pixbuf = gdk_pixbuf_rotate_simple (tmp_pixbuf,
+ 360 - rc->rotation);
+ }
+ g_object_unref (loader);
+
+out:
+ comics_document_reset_archive (comics_document);
return rotated_pixbuf;
}
@@ -732,7 +451,7 @@ comics_document_render (EvDocument *document,
pixbuf = comics_document_render_pixbuf (document, rc);
surface = ev_document_misc_surface_from_pixbuf (pixbuf);
g_object_unref (pixbuf);
-
+
return surface;
}
@@ -748,60 +467,19 @@ render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
gdk_pixbuf_loader_set_size (loader, scaled_width, scaled_height);
}
-/**
- * comics_remove_dir: Removes a directory recursively.
- * Returns:
- * 0 if it was successfully deleted,
- * -1 if an error occurred
- */
-static int
-comics_remove_dir (gchar *path_name)
-{
- GDir *content_dir;
- const gchar *filename;
- gchar *filename_with_path;
-
- if (g_file_test (path_name, G_FILE_TEST_IS_DIR)) {
- content_dir = g_dir_open (path_name, 0, NULL);
- filename = g_dir_read_name (content_dir);
- while (filename) {
- filename_with_path =
- g_build_filename (path_name,
- filename, NULL);
- comics_remove_dir (filename_with_path);
- g_free (filename_with_path);
- filename = g_dir_read_name (content_dir);
- }
- g_dir_close (content_dir);
- }
- /* Note from g_remove() documentation: on Windows, it is in general not
- * possible to remove a file that is open to some process, or mapped
- * into memory.*/
- return (g_remove (path_name));
-}
-
static void
comics_document_finalize (GObject *object)
{
ComicsDocument *comics_document = COMICS_DOCUMENT (object);
-
- if (comics_document->decompress_tmp) {
- if (comics_remove_dir (comics_document->dir) == -1)
- g_warning (_("There was an error deleting “%s”."),
- comics_document->dir);
- g_free (comics_document->dir);
- }
-
+
if (comics_document->page_names) {
g_ptr_array_foreach (comics_document->page_names, (GFunc) g_free, NULL);
g_ptr_array_free (comics_document->page_names, TRUE);
}
- g_free (comics_document->archive);
- g_free (comics_document->selected_command);
- g_free (comics_document->alternative_command);
- g_free (comics_document->extract_command);
- g_free (comics_document->list_command);
+ g_clear_object (&comics_document->a);
+ g_free (comics_document->archive_path);
+ g_free (comics_document->archive_uri);
G_OBJECT_CLASS (comics_document_parent_class)->finalize (object);
}
@@ -824,9 +502,8 @@ comics_document_class_init (ComicsDocumentClass *klass)
static void
comics_document_init (ComicsDocument *comics_document)
{
- comics_document->archive = NULL;
+ comics_document->a = ev_archive_new ();
comics_document->page_names = NULL;
- comics_document->extract_command = NULL;
}
/* Returns a list of file extensions supported by gdk-pixbuf */
@@ -852,41 +529,3 @@ get_supported_image_extensions(void)
g_slist_free (formats);
return extensions;
}
-
-static char**
-extract_argv (EvDocument *document, gint page)
-{
- ComicsDocument *comics_document = COMICS_DOCUMENT (document);
- char **argv;
- char *command_line, *quoted_archive, *quoted_filename;
- GError *err = NULL;
-
- if (page >= comics_document->page_names->len)
- return NULL;
-
- if (comics_document->regex_arg) {
- quoted_archive = g_shell_quote (comics_document->archive);
- quoted_filename =
- comics_regex_quote (comics_document->page_names->pdata[page]);
- } else {
- quoted_archive = g_shell_quote (comics_document->archive);
- quoted_filename = g_shell_quote (comics_document->page_names->pdata[page]);
- }
-
- command_line = g_strdup_printf ("%s %s %s",
- comics_document->extract_command,
- quoted_archive,
- quoted_filename);
- g_shell_parse_argv (command_line, NULL, &argv, &err);
-
- if (err) {
- g_warning (_("Error %s"), err->message);
- g_error_free (err);
- return NULL;
- }
-
- g_free (command_line);
- g_free (quoted_archive);
- g_free (quoted_filename);
- return argv;
-}
diff --git a/backend/comics/ev-archive.c b/backend/comics/ev-archive.c
new file mode 100644
index 00000000..9efa69f8
--- /dev/null
+++ b/backend/comics/ev-archive.c
@@ -0,0 +1,277 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
+/*
+ * Copyright (C) 2017, Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "ev-archive.h"
+
+#include <archive.h>
+#include <archive_entry.h>
+#include <gio/gio.h>
+
+#define BUFFER_SIZE (64 * 1024)
+
+struct _EvArchive {
+ GObject parent_instance;
+ EvArchiveType type;
+
+ /* libarchive */
+ struct archive *libar;
+ struct archive_entry *libar_entry;
+};
+
+G_DEFINE_TYPE(EvArchive, ev_archive, G_TYPE_OBJECT);
+
+static void
+ev_archive_finalize (GObject *object)
+{
+ EvArchive *archive = EV_ARCHIVE (object);
+
+ switch (archive->type) {
+ case EV_ARCHIVE_TYPE_RAR:
+ break;
+ case EV_ARCHIVE_TYPE_ZIP:
+ case EV_ARCHIVE_TYPE_7Z:
+ case EV_ARCHIVE_TYPE_TAR:
+ g_clear_pointer (&archive->libar, archive_free);
+ break;
+ default:
+ break;
+ }
+
+ G_OBJECT_CLASS (ev_archive_parent_class)->finalize (object);
+}
+
+static void
+ev_archive_class_init (EvArchiveClass *class)
+{
+ GObjectClass *object_class = (GObjectClass *) class;
+ object_class->finalize = ev_archive_finalize;
+}
+
+EvArchive *
+ev_archive_new (void)
+{
+ return g_object_new (EV_TYPE_ARCHIVE, NULL);
+}
+
+static void
+libarchive_set_archive_type (EvArchive *archive,
+ EvArchiveType archive_type)
+{
+ archive->type = archive_type;
+ archive->libar = archive_read_new ();
+
+ if (archive_type == EV_ARCHIVE_TYPE_ZIP)
+ archive_read_support_format_zip (archive->libar);
+ else if (archive_type == EV_ARCHIVE_TYPE_7Z)
+ archive_read_support_format_7zip (archive->libar);
+ else if (archive_type == EV_ARCHIVE_TYPE_TAR)
+ archive_read_support_format_tar (archive->libar);
+}
+
+EvArchiveType
+ev_archive_get_archive_type (EvArchive *archive)
+{
+ g_return_val_if_fail (EV_IS_ARCHIVE (archive), EV_ARCHIVE_TYPE_NONE);
+ return archive->type;
+}
+
+gboolean
+ev_archive_set_archive_type (EvArchive *archive,
+ EvArchiveType archive_type)
+{
+ g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
+ g_return_val_if_fail (archive->type == EV_ARCHIVE_TYPE_NONE, FALSE);
+
+ switch (archive_type) {
+ case EV_ARCHIVE_TYPE_RAR:
+ /* Disabled until this is fixed:
+ * https://github.com/libarchive/libarchive/issues/373 */
+ return FALSE;
+ case EV_ARCHIVE_TYPE_ZIP:
+ case EV_ARCHIVE_TYPE_7Z:
+ case EV_ARCHIVE_TYPE_TAR:
+ libarchive_set_archive_type (archive, archive_type);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+gboolean
+ev_archive_open_filename (EvArchive *archive,
+ const char *path,
+ GError **error)
+{
+ int r;
+
+ g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
+ g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);
+ g_return_val_if_fail (path != NULL, FALSE);
+
+ switch (archive->type) {
+ case EV_ARCHIVE_TYPE_NONE:
+ g_assert_not_reached ();
+ case EV_ARCHIVE_TYPE_RAR:
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Archive type 'RAR' not supported");
+ return FALSE;
+ case EV_ARCHIVE_TYPE_ZIP:
+ case EV_ARCHIVE_TYPE_7Z:
+ case EV_ARCHIVE_TYPE_TAR:
+ r = archive_read_open_filename (archive->libar, path, BUFFER_SIZE);
+ if (r != ARCHIVE_OK) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error opening archive: %s", archive_error_string (archive->libar));
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+libarchive_read_next_header (EvArchive *archive,
+ GError **error)
+{
+ while (1) {
+ int r;
+
+ r = archive_read_next_header (archive->libar, &archive->libar_entry);
+ if (r != ARCHIVE_OK) {
+ if (r != ARCHIVE_EOF)
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error reading archive: %s", archive_error_string (archive->libar));
+ return FALSE;
+ }
+
+ if (archive_entry_filetype (archive->libar_entry) != AE_IFREG) {
+ g_debug ("Skipping '%s' as it's not a regular file",
+ archive_entry_pathname (archive->libar_entry));
+ continue;
+ }
+
+ g_debug ("At header for file '%s'", archive_entry_pathname (archive->libar_entry));
+
+ break;
+ }
+
+ return TRUE;
+}
+
+gboolean
+ev_archive_read_next_header (EvArchive *archive,
+ GError **error)
+{
+ g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE);
+ g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE);
+
+ switch (archive->type) {
+ case EV_ARCHIVE_TYPE_NONE:
+ case EV_ARCHIVE_TYPE_RAR:
+ g_assert_not_reached ();
+ case EV_ARCHIVE_TYPE_ZIP:
+ case EV_ARCHIVE_TYPE_7Z:
+ case EV_ARCHIVE_TYPE_TAR:
+ return libarchive_read_next_header (archive, error);
+ }
+
+ return FALSE;
+}
+
+const char *
+ev_archive_get_entry_pathname (EvArchive *archive)
+{
+ g_return_val_if_fail (EV_IS_ARCHIVE (archive), NULL);
+ g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, NULL);
+ g_return_val_if_fail (archive->libar_entry != NULL, NULL);
+
+ switch (archive->type) {
+ case EV_ARCHIVE_TYPE_NONE:
+ g_assert_not_reached ();
+ case EV_ARCHIVE_TYPE_RAR:
+ return NULL;
+ case EV_ARCHIVE_TYPE_ZIP:
+ case EV_ARCHIVE_TYPE_7Z:
+ case EV_ARCHIVE_TYPE_TAR:
+ return archive_entry_pathname (archive->libar_entry);
+ }
+
+ return NULL;
+}
+
+gint64
+ev_archive_get_entry_size (EvArchive *archive)
+{
+ gint64 r;
+
+ g_return_val_if_fail (EV_IS_ARCHIVE (archive), -1);
+ g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, -1);
+ g_return_val_if_fail (archive->libar_entry != NULL, -1);
+
+ switch (archive->type) {
+ case EV_ARCHIVE_TYPE_RAR:
+ case EV_ARCHIVE_TYPE_NONE:
+ g_assert_not_reached ();
+ case EV_ARCHIVE_TYPE_ZIP:
+ case EV_ARCHIVE_TYPE_7Z:
+ case EV_ARCHIVE_TYPE_TAR:
+ r = archive_entry_size (archive->libar_entry);
+ break;
+ }
+
+ return r;
+}
+
+gssize
+ev_archive_read_data (EvArchive *archive,
+ void *buf,
+ gsize count,
+ GError **error)
+{
+ gssize r = -1;
+
+ g_return_val_if_fail (EV_IS_ARCHIVE (archive), -1);
+ g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, -1);
+ g_return_val_if_fail (archive->libar_entry != NULL, -1);
+
+ switch (archive->type) {
+ case EV_ARCHIVE_TYPE_RAR:
+ case EV_ARCHIVE_TYPE_NONE:
+ g_assert_not_reached ();
+ case EV_ARCHIVE_TYPE_ZIP:
+ case EV_ARCHIVE_TYPE_7Z:
+ case EV_ARCHIVE_TYPE_TAR:
+ r = archive_read_data (archive->libar, buf, count);
+ if (r < 0) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Failed to decompress data: %s", archive_error_string (archive->libar));
+ }
+ break;
+ }
+
+ return r;
+}
+
+static void
+ev_archive_init (EvArchive *archive)
+{
+}
diff --git a/backend/comics/ev-archive.h b/backend/comics/ev-archive.h
new file mode 100644
index 00000000..38d47d79
--- /dev/null
+++ b/backend/comics/ev-archive.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
+/*
+ * Copyright (C) 2017, Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <glib-object.h>
+
+#define EV_TYPE_ARCHIVE ev_archive_get_type ()
+G_DECLARE_FINAL_TYPE (EvArchive, ev_archive, EV, ARCHIVE, GObject)
+
+typedef enum {
+ EV_ARCHIVE_TYPE_NONE = 0,
+ EV_ARCHIVE_TYPE_RAR,
+ EV_ARCHIVE_TYPE_ZIP,
+ EV_ARCHIVE_TYPE_7Z,
+ EV_ARCHIVE_TYPE_TAR
+} EvArchiveType;
+
+EvArchive *ev_archive_new (void);
+gboolean ev_archive_set_archive_type (EvArchive *archive,
+ EvArchiveType archive_type);
+EvArchiveType ev_archive_get_archive_type (EvArchive *archive);
+gboolean ev_archive_open_filename (EvArchive *archive,
+ const char *path,
+ GError **error);
+gboolean ev_archive_read_next_header (EvArchive *archive,
+ GError **error);
+const char *ev_archive_get_entry_pathname (EvArchive *archive);
+gint64 ev_archive_get_entry_size (EvArchive *archive);
+gssize ev_archive_read_data (EvArchive *archive,
+ void *buf,
+ gsize count,
+ GError **error);
diff --git a/configure.ac b/configure.ac
index 24d537d9..26863ede 100644
--- a/configure.ac
+++ b/configure.ac
@@ -688,9 +688,17 @@ AC_ARG_ENABLE(comics,
[Compile with support for comic book archives])],
[enable_comics=$enableval],
[enable_comics=yes])
-
+
if test "x$enable_comics" = "xyes"; then
- AC_DEFINE([ENABLE_COMICS], [1], [Enable support for comics.])
+ LIBARCHIVE_REQUIRED=3.1.2
+ PKG_CHECK_MODULES(LIBARCHIVE, libarchive >= $LIBARCHIVE_REQUIRED,enable_comics=yes,enable_comics=no)
+
+ if test "x$enable_comics" = "xyes"; then
+ AC_DEFINE([ENABLE_COMICS], [1], [Enable support for comics.])
+ else
+ enable_comics="no"
+ AC_MSG_WARN([Comics support is disabled since libarchive (version >= $LIBARCHIVE_REQUIRED) is needed])
+ fi
fi
AM_CONDITIONAL(ENABLE_COMICS, test x$enable_comics = xyes)
--
2.14.1