From aa8e9dad472cbadc96719a8f521768aeeb0913f0 Mon Sep 17 00:00:00 2001 From: lukefromdc Date: Mon, 25 Dec 2023 15:11:04 -0500 Subject: comics: Use libarchive to unpack documents This commit eliminates the use of external commands for opening comic documents, and uses libarchive instead. --- backend/comics/Makefile.am | 5 +- backend/comics/comics-document.c | 1143 ++++++++++++++------------------------ backend/comics/comics-document.h | 7 +- backend/comics/ev-archive.c | 323 +++++++++++ backend/comics/ev-archive.h | 56 ++ configure.ac | 3 + libdocument/ev-document.h | 1 + 7 files changed, 799 insertions(+), 739 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 b27f9b85..77f3dedb 100644 --- a/backend/comics/Makefile.am +++ b/backend/comics/Makefile.am @@ -12,12 +12,15 @@ backend_LTLIBRARIES = libcomicsdocument.la libcomicsdocument_la_SOURCES = \ comics-document.c \ - comics-document.h + comics-document.h \ + ev-archive.c \ + ev-archive.h libcomicsdocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS) libcomicsdocument_la_LIBADD = \ $(top_builddir)/libdocument/libatrildocument.la \ $(BACKEND_LIBS) \ + $(COMICS_LIBS) \ $(LIB_LIBS) backend_in_files = comicsdocument.atril-backend.desktop.in diff --git a/backend/comics/comics-document.c b/backend/comics/comics-document.c index ed02a35c..1da1eee2 100644 --- a/backend/comics/comics-document.c +++ b/backend/comics/comics-document.c @@ -30,24 +30,17 @@ #include #include -#include - #include "comics-document.h" #include "ev-document-misc.h" #include "ev-document-thumbnails.h" #include "ev-file-helpers.h" +#include "ev-archive.h" +#include +#include #define EV_EOL "\n" -typedef enum -{ - RARLABS, - GNAUNRAR, - UNZIP, - P7ZIP, - TAR, - UNARCHIVER -} ComicBookDecompressType; +#define BLOCK_SIZE 10240 typedef struct _ComicsDocumentClass ComicsDocumentClass; @@ -58,398 +51,269 @@ 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; + EvDocument parent_instance; + EvArchive *archive; + gchar *archive_path; + gchar *archive_uri; + GPtrArray *page_names; /* elem: char * */ + GHashTable *page_positions; /* key: char *, value: uint + 1 */ + }; -#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 void +comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); +EV_BACKEND_REGISTER_WITH_CODE (ComicsDocument, comics_document, + { + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, + comics_document_document_thumbnails_iface_init); + } ); + +#define FORMAT_UNKNOWN 0 +#define FORMAT_SUPPORTED 1 +#define FORMAT_UNSUPPORTED 2 + +/* Returns a GHashTable of: + * : file extensions + * : degree of support in gdk-pixbuf */ +static GHashTable * +get_image_extensions(void) +{ + GHashTable *extensions; + GSList *formats = gdk_pixbuf_get_formats (); + GSList *l; + guint i; + const char *known_image_formats[] = { + "png", + "jpg", + "jpeg", + "webp" + }; + + extensions = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + for (l = formats; l != NULL; l = l->next) { + int i; + gchar **ext = gdk_pixbuf_format_get_extensions (l->data); + + for (i = 0; ext[i] != NULL; i++) { + g_hash_table_insert (extensions, + g_strdup (ext[i]), + GINT_TO_POINTER (FORMAT_SUPPORTED)); + } -static const ComicBookDecompressCommand command_usage_def[] = { - /* RARLABS unrar */ - {"%s p -c- -ierr --", "%s vb -c- -- %s", NULL , FALSE, NO_OFFSET}, + g_strfreev (ext); + } + g_slist_free (formats); - /* GNA! unrar */ - {NULL , "%s t %s" , "%s -xf %s %s" , FALSE, NO_OFFSET}, + /* Add known image formats that aren't supported by gdk-pixbuf */ + for (i = 0; i < G_N_ELEMENTS (known_image_formats); i++) { + if (!g_hash_table_lookup (extensions, known_image_formats[i])) { + g_hash_table_insert (extensions, + g_strdup (known_image_formats[i]), + GINT_TO_POINTER (FORMAT_UNSUPPORTED)); + } + } - /* unzip */ - {"%s -p -C --" , "%s %s" , NULL , TRUE , OFFSET_ZIP}, + return extensions; +} - /* 7zip */ - {NULL , "%s l -- %s" , "%s x -y %s -o%s", FALSE, OFFSET_7Z}, +static int +has_supported_extension (const char *name, + GHashTable *supported_extensions) +{ + gboolean ret = FALSE; + gchar *suffix; + suffix = g_strrstr (name, "."); + if (!suffix) + return ret; - /* tar */ - {"%s -xOf" , "%s -tf %s" , NULL , FALSE, NO_OFFSET}, + suffix = g_ascii_strdown (suffix + 1, -1); + ret = GPOINTER_TO_INT (g_hash_table_lookup (supported_extensions, suffix)); + g_free (suffix); - /* UNARCHIVER */ - {"unar -o -" , "%s %s" , NULL , FALSE, NO_OFFSET} -}; + return ret; +} -static void comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); +#define APPLE_DOUBLE_PREFIX "._" +static gboolean +is_apple_double (const char *name) +{ +char *basename; + gboolean ret = FALSE; -static GSList* get_supported_image_extensions (void); -static void get_page_size_area_prepared_cb (GdkPixbufLoader *loader, - gpointer data); -static void render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader, - gint width, - gint height, - gpointer data); -static char** extract_argv (EvDocument *document, - gint page); + basename = g_path_get_basename (name); + if (basename == NULL) { + g_debug ("Filename '%s' doesn't have a basename?", name); + return ret; + } + ret = g_str_has_prefix (basename, APPLE_DOUBLE_PREFIX); + g_free (basename); -EV_BACKEND_REGISTER_WITH_CODE (ComicsDocument, comics_document, - { - EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, - comics_document_document_thumbnails_iface_init); - } ); - -/** - * 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) + return ret; +} + +static gboolean +archive_reopen_if_needed (ComicsDocument *comics_document, + const char *page_wanted, + GError **error) { - 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; + const char *current_page; + guint current_page_idx, page_wanted_idx; + + if (ev_archive_at_entry (comics_document->archive)) { + current_page = ev_archive_get_entry_pathname (comics_document->archive); + if (current_page) { + current_page_idx = GPOINTER_TO_UINT (g_hash_table_lookup (comics_document->page_positions, current_page)); + page_wanted_idx = GPOINTER_TO_UINT (g_hash_table_lookup (comics_document->page_positions, page_wanted)); + + if (current_page_idx != 0 && + page_wanted_idx != 0 && + page_wanted_idx > current_page_idx) + return TRUE; } - ++p; + + ev_archive_reset (comics_document->archive); } - g_string_append_c (dest, '\''); - return g_string_free (dest, FALSE); +return ev_archive_open_filename (comics_document->archive, comics_document->archive_path, error); } -/* 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 GPtrArray * +comics_document_list (ComicsDocument *comics_document, + GError **error) { - 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, +GPtrArray *array = NULL; + gboolean has_encrypted_files, has_unsupported_images, has_archive_errors; + GHashTable *supported_extensions = NULL; + + if (!ev_archive_open_filename (comics_document->archive, comics_document->archive_path, error)) { + if (*error != NULL) { + g_warning ("Fatal error handling archive (%s): %s", G_STRFUNC, (*error)->message); + g_clear_error (error); + } + + g_set_error_literal (error, EV_DOCUMENT_ERROR, EV_DOCUMENT_ERROR_INVALID, - _("The command “%s” failed at " - "decompressing the comic book."), - basename); - g_free (std_out); + _("File is corrupted")); + goto out; + } + + supported_extensions = get_image_extensions (); + + has_encrypted_files = FALSE; + has_unsupported_images = FALSE; + has_archive_errors = FALSE; + array = g_ptr_array_sized_new (64); + + while (1) { + const char *name; + int supported; + + if (!ev_archive_read_next_header (comics_document->archive, error)) { + if (*error != NULL) { + g_debug ("Fatal error handling archive (%s): %s", G_STRFUNC, (*error)->message); + g_clear_error (error); + has_archive_errors = TRUE; + goto out; + } + 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); + + name = ev_archive_get_entry_pathname (comics_document->archive); + /* Ignore https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats */ + if (is_apple_double (name)) { + g_debug ("Not adding AppleDouble file '%s' to the list of files in the comics", name); + continue; + } + + supported = has_supported_extension (name, supported_extensions); + if (supported == FORMAT_UNKNOWN) { + g_debug ("Not adding unsupported file '%s' to the list of files in the comics", name); + continue; + } else if (supported == FORMAT_UNSUPPORTED) { + g_debug ("Not adding unsupported image '%s' to the list of files in the comics", name); + has_unsupported_images = TRUE; + continue; + } + + if (ev_archive_get_entry_is_encrypted (comics_document->archive)) { + g_debug ("Not adding encrypted file '%s' to the list of files in the comics", name); + has_encrypted_files = TRUE; + continue; + } + + g_debug ("Adding '%s' to the list of files in the comics", name); + g_ptr_array_add (array, g_strdup (name)); } - g_free (basename); - return FALSE; +out: + if (array->len == 0) { + g_ptr_array_free (array, TRUE); + array = NULL; + + if (has_encrypted_files) { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_ENCRYPTED, + _("Archive is encrypted")); + } else if (has_unsupported_images) { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_UNSUPPORTED_CONTENT, + _("No supported images in archive")); + } else if (has_archive_errors) { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("File is corrupted")); + } else { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("No files in archive")); + } + } + + if (supported_extensions) + g_hash_table_destroy (supported_extensions); + ev_archive_reset (comics_document->archive); + return array; } -/* This function shows how to use the choosen command for decompressing a - * comic book file. It modifies fields of the ComicsDocument struct with - * this information */ -static gboolean -comics_generate_command_lines (ComicsDocument *comics_document, - GError **error) +static GHashTable * +save_positions (GPtrArray *page_names) { - 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); - } - 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 ("atril-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; - } else { - g_free (quoted_file); - g_free (quoted_command); - return TRUE; - } + guint i; + GHashTable *ht; + ht = g_hash_table_new (g_str_hash, g_str_equal); + for (i = 0; i < page_names->len; i++) + g_hash_table_insert (ht, page_names->pdata[i], GUINT_TO_POINTER(i + 1)); + return ht; } -/* This function chooses an external command for decompressing a comic - * book based on its mime tipe. */ +/*This function chooses the archive decompression support + * book based on its mime type. */ static gboolean -comics_check_decompress_command (gchar *mime_type, +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; - return TRUE; - } - comics_document->selected_command = - g_find_program_in_path ("lsar"); - if (comics_document->selected_command) { - comics_document->command_usage = UNARCHIVER; - return TRUE; - } - comics_document->selected_command = - g_find_program_in_path ("bsdtar"); - if (comics_document->selected_command) { - comics_document->command_usage = TAR; + if (ev_archive_set_archive_type (comics_document->archive, 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; + if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_ZIP)) return TRUE; - } - comics_document->selected_command = - g_find_program_in_path ("bsdtar"); - if (comics_document->selected_command) { - comics_document->command_usage = TAR; - return TRUE; - } - comics_document->selected_command = - g_find_program_in_path ("lsar"); - if (comics_document->selected_command) { - comics_document->command_usage = UNARCHIVER; - 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; + if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_7Z)) return TRUE; - } - 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; - return TRUE; - } - comics_document->selected_command = - g_find_program_in_path ("bsdtar"); - if (comics_document->selected_command) { - comics_document->command_usage = TAR; - return TRUE; - } - comics_document->selected_command = - g_find_program_in_path ("lsar"); - if (comics_document->selected_command) { - comics_document->command_usage = UNARCHIVER; - return TRUE; - } } else if (g_content_type_is_a (mime_type, "application/x-cbt") || g_content_type_is_a (mime_type, "application/x-tar")) { - /* tar utility (Tape ARchive) */ - comics_document->selected_command = - g_find_program_in_path ("tar"); - if (comics_document->selected_command) { - comics_document->command_usage = TAR; + if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_TAR)) return TRUE; - } - comics_document->selected_command = - g_find_program_in_path ("bsdtar"); - if (comics_document->selected_command) { - comics_document->command_usage = TAR; - return TRUE; - } - comics_document->selected_command = - g_find_program_in_path ("lsar"); - if (comics_document->selected_command) { - comics_document->command_usage = UNARCHIVER; - return TRUE; - } } else { g_set_error (error, EV_DOCUMENT_ERROR, @@ -461,8 +325,9 @@ comics_check_decompress_command (gchar *mime_type, g_set_error_literal (error, EV_DOCUMENT_ERROR, EV_DOCUMENT_ERROR_INVALID, - _("Can't find an appropriate command to " - "decompress this type of comic book")); + _("libarchive lacks support for this comic book’s " + "compression, please contact your distributor")); + return FALSE; } @@ -470,43 +335,15 @@ static int sort_page_names (gconstpointer a, gconstpointer b) { - const char *name_1, *name_2; - gchar *key_1, *key_2; - gboolean sort_last_1, sort_last_2; - int compare; - - name_1 = * (const char **) a; - name_2 = * (const char **) b; - - #define SORT_LAST_CHAR1 '.' - #define SORT_LAST_CHAR2 '#' - - sort_last_1 = name_1[0] == SORT_LAST_CHAR1 || name_1[0] == SORT_LAST_CHAR2; - sort_last_2 = name_2[0] == SORT_LAST_CHAR1 || name_2[0] == SORT_LAST_CHAR2; - - #undef SORT_LAST_CHAR1 - #undef SORT_LAST_CHAR2 - - if (sort_last_1 && !sort_last_2) - { - compare = +1; - } - else if (!sort_last_1 && sort_last_2) - { - compare = -1; - } - else - { - key_1 = g_utf8_collate_key_for_filename (name_1, -1); - key_2 = g_utf8_collate_key_for_filename (name_2, -1); - - compare = strcmp (key_1, key_2); - - g_free (key_1); - g_free (key_2); - } - - return compare; + gchar *temp1, *temp2; + gint ret; + temp1 = g_utf8_collate_key_for_filename (* (const char **) a, -1); + temp2 = g_utf8_collate_key_for_filename (* (const char **) b, -1); + ret = strcmp (temp1, temp2); + + g_free (temp1); + g_free (temp2); + return ret; } static gboolean @@ -515,50 +352,13 @@ comics_document_load (EvDocument *document, GError **error) { 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; - GError *err = NULL; - - comics_document->archive = g_filename_from_uri (uri, NULL, error); - if (!comics_document->archive) - return FALSE; - - mime_type = ev_file_get_mime_type (uri, FALSE, &err); - if (!mime_type) { - if (err) { - g_propagate_error (error, err); - } else { - g_set_error_literal (error, - EV_DOCUMENT_ERROR, - EV_DOCUMENT_ERROR_INVALID, - _("Unknown MIME Type")); - } - - return FALSE; - } - - if (!comics_check_decompress_command (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; - } + GFile *file; + file = g_file_new_for_uri (uri); + comics_document->archive_path = g_file_get_path (file); + g_object_unref (file); - 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) { + if (!comics_document->archive_path) { g_set_error_literal (error, EV_DOCUMENT_ERROR, EV_DOCUMENT_ERROR_INVALID, @@ -566,58 +366,26 @@ comics_document_load (EvDocument *document, return FALSE; } - /* FIXME: is this safe against filenames containing \n in the archive ? */ - cb_files = g_strsplit (std_out, EV_EOL, 0); + comics_document->archive_uri = g_strdup (uri); + mime_type = ev_file_get_mime_type (uri, FALSE, error); - g_free (std_out); + if (mime_type == NULL) + return FALSE; - if (!cb_files) { - g_set_error_literal (error, - EV_DOCUMENT_ERROR, - EV_DOCUMENT_ERROR_INVALID, - _("No files in archive")); + if (!comics_check_decompress_support (mime_type, comics_document, error)) { + g_free (mime_type); return FALSE; } - comics_document->page_names = g_ptr_array_sized_new (64); - - 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]; - } - gchar *suffix = g_strrstr (cb_file, "."); - if (!suffix) - continue; - suffix = g_ascii_strdown (suffix + 1, -1); - if (g_slist_find_custom (supported_extensions, suffix, - (GCompareFunc) strcmp) != NULL) { - g_ptr_array_add (comics_document->page_names, - g_strstrip (g_strdup (cb_file))); - } - g_free (suffix); - } - g_strfreev (cb_files); - g_slist_foreach (supported_extensions, (GFunc) g_free, NULL); - g_slist_free (supported_extensions); + g_free (mime_type); - if (comics_document->page_names->len == 0) { - g_set_error (error, - EV_DOCUMENT_ERROR, - EV_DOCUMENT_ERROR_INVALID, - _("No images found in archive %s"), - uri); + /* Get list of files in archive */ + comics_document->page_names = comics_document_list (comics_document, error); + if (!comics_document->page_names) return FALSE; - } + + /* Keep an index */ + comics_document->page_positions = save_positions (comics_document->page_names); /* Now sort the pages */ g_ptr_array_sort (comics_document->page_names, sort_page_names); @@ -632,7 +400,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 @@ -646,6 +414,23 @@ comics_document_get_n_pages (EvDocument *document) return comics_document->page_names->len; } +typedef struct { + gboolean got_info; + int height; + int width; +} PixbufInfo; + +static void +get_page_size_prepared_cb (GdkPixbufLoader *loader, + int width, + int height, + PixbufInfo *info) +{ + info->got_info = TRUE; + info->height = height; + info->width = width; +} + static void comics_document_get_page_size (EvDocument *document, EvPage *page, @@ -653,74 +438,89 @@ 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; + PixbufInfo info; + GError *error = NULL; + + page_path = g_ptr_array_index (comics_document->page_names, page->index); + + if (!archive_reopen_if_needed (comics_document, page_path, &error)) { + g_warning ("Fatal error opening archive: %s", error->message); + g_error_free (error); + return; + } + + loader = gdk_pixbuf_loader_new (); + info.got_info = FALSE; + g_signal_connect (loader, "size-prepared", + G_CALLBACK (get_page_size_prepared_cb), + &info); + + while (1) { + const char *name; + GError *error = NULL; + + if (!ev_archive_read_next_header (comics_document->archive, &error)) { + if (error != NULL) { + g_warning ("Fatal error handling archive (%s): %s", G_STRFUNC, 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->archive); + if (g_strcmp0 (name, page_path) == 0) { + char buf[BLOCK_SIZE]; + gssize read; + gint64 left; + + left = ev_archive_get_entry_size (comics_document->archive); + read = ev_archive_read_data (comics_document->archive, buf, + MIN(BLOCK_SIZE, left), &error); + while (read > 0 && !info.got_info) { + if (!gdk_pixbuf_loader_write (loader, (guchar *) buf, read, &error)) { + read = -1; + break; + } + left -= read; + read = ev_archive_read_data (comics_document->archive, buf, + MIN(BLOCK_SIZE, left), &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; } } static void -get_page_size_area_prepared_cb (GdkPixbufLoader *loader, - gpointer data) +render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader, + gint width, + gint height, + EvRenderContext *rc) { - gboolean *got_size = data; - *got_size = TRUE; + // int scaled_width, scaled_height; + double scale = rc->scale; + int w = (width * scale + 0.5); + int h = (height * scale + 0.5); + + // ev_render_context_compute_scaled_size (rc, width, height, &scaled_width, &scaled_height); + gdk_pixbuf_loader_set_size (loader, w, h); } static GdkPixbuf * @@ -728,69 +528,68 @@ 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 = NULL; ComicsDocument *comics_document = COMICS_DOCUMENT (document); + const char *page_path; + GError *error = NULL; + + page_path = g_ptr_array_index (comics_document->page_names, rc->page->index); + + if (!archive_reopen_if_needed (comics_document, page_path, &error)) { + g_warning ("Fatal error opening archive: %s", error->message); + g_error_free (error); + return NULL; + } - 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->scale); - - while (outpipe >= 0) { - bytes = read (outpipe, buf, 4096); - - if (bytes > 0) { - gdk_pixbuf_loader_write (loader, buf, bytes, - NULL); + loader = gdk_pixbuf_loader_new (); + g_signal_connect (loader, "size-prepared", + G_CALLBACK (render_pixbuf_size_prepared_cb), + rc); + + while (1) { + const char *name; + + if (!ev_archive_read_next_header (comics_document->archive, &error)) { + if (error != NULL) { + g_warning ("Fatal error handling archive (%s): %s", G_STRFUNC, error->message); + g_error_free (error); + } + break; + } + + name = ev_archive_get_entry_pathname (comics_document->archive); + if (g_strcmp0 (name, page_path) == 0) { + size_t size = ev_archive_get_entry_size (comics_document->archive); + char *buf; + ssize_t read; + + buf = g_malloc (size); + read = ev_archive_read_data (comics_document->archive, 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 { - close (outpipe); - gdk_pixbuf_loader_close (loader, NULL); - outpipe = -1; + 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 { - 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); - - tmp_pixbuf = - gdk_pixbuf_new_from_file_at_size ( - filename, width * (rc->scale) + 0.5, - height * (rc->scale) + 0.5, 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); + 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); return rotated_pixbuf; } @@ -802,79 +601,26 @@ comics_document_render (EvDocument *document, cairo_surface_t *surface; pixbuf = comics_document_render_pixbuf (document, rc); + if (!pixbuf) + return NULL; surface = ev_document_misc_surface_from_pixbuf (pixbuf); - g_object_unref (pixbuf); - + g_clear_object (&pixbuf); return surface; } -static void -render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader, - gint width, - gint height, - gpointer data) -{ - double *scale = data; - int w = (width * (*scale) + 0.5); - int h = (height * (*scale) + 0.5); - - gdk_pixbuf_loader_set_size (loader, w, h); -} - -/** - * 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_pointer (&comics_document->page_positions, g_hash_table_destroy); + g_clear_object (&comics_document->archive); + g_free (comics_document->archive_path); + g_free (comics_document->archive_uri); G_OBJECT_CLASS (comics_document_parent_class)->finalize (object); } @@ -897,33 +643,7 @@ comics_document_class_init (ComicsDocumentClass *klass) static void comics_document_init (ComicsDocument *comics_document) { - comics_document->archive = NULL; - comics_document->page_names = NULL; - comics_document->extract_command = NULL; -} - -/* Returns a list of file extensions supported by gdk-pixbuf */ -static GSList* -get_supported_image_extensions(void) -{ - GSList *extensions = NULL; - GSList *formats = gdk_pixbuf_get_formats (); - GSList *l; - - for (l = formats; l != NULL; l = l->next) { - int i; - gchar **ext = gdk_pixbuf_format_get_extensions (l->data); - - for (i = 0; ext[i] != NULL; i++) { - extensions = g_slist_append (extensions, - g_strdup (ext[i])); - } - - g_strfreev (ext); - } - - g_slist_free (formats); - return extensions; + comics_document->archive = ev_archive_new (); } static GdkPixbuf * @@ -971,48 +691,3 @@ comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *i iface->get_thumbnail = comics_document_thumbnails_get_thumbnail; iface->get_dimensions = comics_document_thumbnails_get_dimensions; } - -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 (g_strrstr (comics_document->page_names->pdata[page], "--checkpoint-action=")) - { - g_warning ("File unsupported\n"); - gtk_main_quit (); - } - - 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_free (quoted_archive); - g_free (quoted_filename); - - g_shell_parse_argv (command_line, NULL, &argv, &err); - g_free (command_line); - - if (err) { - g_warning (_("Error %s"), err->message); - g_error_free (err); - return NULL; - } - - return argv; -} diff --git a/backend/comics/comics-document.h b/backend/comics/comics-document.h index f6a4b440..4417a69f 100644 --- a/backend/comics/comics-document.h +++ b/backend/comics/comics-document.h @@ -16,9 +16,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __COMICS_DOCUMENT_H__ -#define __COMICS_DOCUMENT_H__ +#pragma once +#include "ev-macros.h" #include "ev-document.h" G_BEGIN_DECLS @@ -30,9 +30,8 @@ G_BEGIN_DECLS typedef struct _ComicsDocument ComicsDocument; GType comics_document_get_type (void) G_GNUC_CONST; +GType register_atril_backend (GTypeModule *module); -G_MODULE_EXPORT GType register_atril_backend (GTypeModule *module); G_END_DECLS -#endif /* __COMICS_DOCUMENT_H__ */ diff --git a/backend/comics/ev-archive.c b/backend/comics/ev-archive.c new file mode 100644 index 00000000..568e1621 --- /dev/null +++ b/backend/comics/ev-archive.c @@ -0,0 +1,323 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* + * Copyright (C) 2017, Bastien Nocera + * + * 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 "config.h" +#include "ev-archive.h" + +#include +#include +#include + +#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: + 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 *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + 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); + else if (archive_type == EV_ARCHIVE_TYPE_RAR) { + archive_read_support_format_rar (archive->libar); + archive_read_support_format_rar5 (archive->libar); + } else + g_assert_not_reached (); +} + +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: + 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: + 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: + g_assert_not_reached (); + case EV_ARCHIVE_TYPE_RAR: + case EV_ARCHIVE_TYPE_ZIP: + case EV_ARCHIVE_TYPE_7Z: + case EV_ARCHIVE_TYPE_TAR: + return libarchive_read_next_header (archive, error); + } + + return FALSE; +} + +gboolean +ev_archive_at_entry (EvArchive *archive) +{ + g_return_val_if_fail (EV_IS_ARCHIVE (archive), FALSE); + g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, FALSE); + + return (archive->libar_entry != NULL); +} + +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); + + switch (archive->type) { + case EV_ARCHIVE_TYPE_NONE: + g_assert_not_reached (); + case EV_ARCHIVE_TYPE_RAR: + case EV_ARCHIVE_TYPE_ZIP: + case EV_ARCHIVE_TYPE_7Z: + case EV_ARCHIVE_TYPE_TAR: + g_return_val_if_fail (archive->libar_entry != NULL, NULL); + return archive_entry_pathname (archive->libar_entry); + } + + return NULL; +} + +gint64 +ev_archive_get_entry_size (EvArchive *archive) +{ + g_return_val_if_fail (EV_IS_ARCHIVE (archive), -1); + g_return_val_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE, -1); + + switch (archive->type) { + case EV_ARCHIVE_TYPE_NONE: + g_assert_not_reached (); + case EV_ARCHIVE_TYPE_RAR: + case EV_ARCHIVE_TYPE_ZIP: + case EV_ARCHIVE_TYPE_7Z: + case EV_ARCHIVE_TYPE_TAR: + g_return_val_if_fail (archive->libar_entry != NULL, -1); + return archive_entry_size (archive->libar_entry); + } + + return -1; +} + +gboolean +ev_archive_get_entry_is_encrypted (EvArchive *archive) +{ + 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: + g_assert_not_reached (); + case EV_ARCHIVE_TYPE_RAR: + case EV_ARCHIVE_TYPE_ZIP: + case EV_ARCHIVE_TYPE_7Z: + case EV_ARCHIVE_TYPE_TAR: + g_return_val_if_fail (archive->libar_entry != NULL, -1); + return archive_entry_is_encrypted (archive->libar_entry); + } + + return FALSE; +} + +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); + + switch (archive->type) { + case EV_ARCHIVE_TYPE_NONE: + g_assert_not_reached (); + case EV_ARCHIVE_TYPE_RAR: + case EV_ARCHIVE_TYPE_ZIP: + case EV_ARCHIVE_TYPE_7Z: + case EV_ARCHIVE_TYPE_TAR: + g_return_val_if_fail (archive->libar_entry != NULL, -1); + 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; +} + +void +ev_archive_reset (EvArchive *archive) +{ + g_return_if_fail (EV_IS_ARCHIVE (archive)); + g_return_if_fail (archive->type != EV_ARCHIVE_TYPE_NONE); + + switch (archive->type) { + case EV_ARCHIVE_TYPE_RAR: + case EV_ARCHIVE_TYPE_ZIP: + case EV_ARCHIVE_TYPE_7Z: + case EV_ARCHIVE_TYPE_TAR: + g_clear_pointer (&archive->libar, archive_free); + libarchive_set_archive_type (archive, archive->type); + archive->libar_entry = NULL; + break; + default: + g_assert_not_reached (); + } +} + +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..b4e1399c --- /dev/null +++ b/backend/comics/ev-archive.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* + * Copyright (C) 2017, Bastien Nocera + * + * 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. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#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); +gboolean ev_archive_at_entry (EvArchive *archive); +const char *ev_archive_get_entry_pathname (EvArchive *archive); +gint64 ev_archive_get_entry_size (EvArchive *archive); +gboolean ev_archive_get_entry_is_encrypted (EvArchive *archive); +gssize ev_archive_read_data (EvArchive *archive, + void *buf, + gsize count, + GError **error); +void ev_archive_reset (EvArchive *archive); + +G_END_DECLS diff --git a/configure.ac b/configure.ac index e25de054..d5830716 100644 --- a/configure.ac +++ b/configure.ac @@ -580,8 +580,11 @@ AC_ARG_ENABLE(comics, [enable_comics=$enableval], [enable_comics=yes]) +COMICS_DEPS="libarchive" if test "x$enable_comics" = "xyes"; then AC_DEFINE([ENABLE_COMICS], [1], [Enable support for comics.]) + PKG_CHECK_MODULES([COMICS], [$COMICS_DEPS]) + AC_SUBST(COMICS_LIBS) fi AM_CONDITIONAL(ENABLE_COMICS, test x$enable_comics = xyes) diff --git a/libdocument/ev-document.h b/libdocument/ev-document.h index 67f53abb..8f54e6b5 100644 --- a/libdocument/ev-document.h +++ b/libdocument/ev-document.h @@ -59,6 +59,7 @@ typedef struct _EvDocumentPrivate EvDocumentPrivate; typedef enum { EV_DOCUMENT_ERROR_INVALID, + EV_DOCUMENT_ERROR_UNSUPPORTED_CONTENT, EV_DOCUMENT_ERROR_ENCRYPTED } EvDocumentError; -- cgit v1.2.1