diff --git a/backend/comics/comics-document.c b/backend/comics/comics-document.c index 99967f09..17c76b07 100644 --- a/backend/comics/comics-document.c +++ b/backend/comics/comics-document.c @@ -2,6 +2,7 @@ /* * Copyright (C) 2009-2010 Juanjo Marín * Copyright (C) 2005, Teemu Tervo + * Copyright (C) 2016-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 @@ -30,24 +31,13 @@ #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" -#define EV_EOL "\n" - -typedef enum -{ - RARLABS, - GNAUNRAR, - UNZIP, - P7ZIP, - TAR, - UNARCHIVER -} ComicBookDecompressType; +#define BLOCK_SIZE 10240 typedef struct _ComicsDocumentClass ComicsDocumentClass; @@ -58,400 +48,273 @@ 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); -static const ComicBookDecompressCommand command_usage_def[] = { - /* RARLABS unrar */ - {"%s p -c- -ierr --", "%s vb -c- -- %s", NULL , FALSE, NO_OFFSET}, +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); - /* GNA! unrar */ - {NULL , "%s t %s" , "%s -xf %s %s" , FALSE, NO_OFFSET}, + for (i = 0; ext[i] != NULL; i++) { + g_hash_table_insert (extensions, + g_strdup (ext[i]), + GINT_TO_POINTER (FORMAT_SUPPORTED)); + } + + g_strfreev (ext); + } - /* unzip */ - {"%s -p -C --" , "%s %s" , NULL , TRUE , OFFSET_ZIP}, + g_slist_free (formats); - /* 7zip */ - {NULL , "%s l -- %s" , "%s x -y %s -o%s", FALSE, OFFSET_7Z}, + /* 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)); + } + } - /* tar */ - {"%s -xOf" , "%s -tf %s" , NULL , FALSE, NO_OFFSET}, + return extensions; +} - /* UNARCHIVER */ - {"unar -o -" , "%s %s" , NULL , FALSE, NO_OFFSET} -}; +static int +has_supported_extension (const char *name, + GHashTable *supported_extensions) +{ + gboolean ret = FALSE; + gchar *suffix; -static void comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); + suffix = g_strrstr (name, "."); + if (!suffix) + return ret; -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); + suffix = g_ascii_strdown (suffix + 1, -1); + ret = GPOINTER_TO_INT (g_hash_table_lookup (supported_extensions, suffix)); + g_free (suffix); + return ret; +} -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) +#define APPLE_DOUBLE_PREFIX "._" +static gboolean +is_apple_double (const char *name) { - 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; + char *basename; + gboolean ret = FALSE; + + basename = g_path_get_basename (name); + if (basename == NULL) { + g_debug ("Filename '%s' doesn't have a basename?", name); + return ret; } - g_string_append_c (dest, '\''); - return g_string_free (dest, FALSE); + + ret = g_str_has_prefix (basename, APPLE_DOUBLE_PREFIX); + g_free (basename); + + return ret; } +static gboolean +archive_reopen_if_needed (ComicsDocument *comics_document, + const char *page_wanted, + GError **error) +{ + 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; + } + + ev_archive_reset (comics_document->archive); + } -/* 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) + return ev_archive_open_filename (comics_document->archive, comics_document->archive_path, 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 ("xreader-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. */ -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->archive, EV_ARCHIVE_TYPE_RAR)) 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; - 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; - } - 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_ZIP)) 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; - 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; + if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_7Z)) 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; - 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; + if (ev_archive_set_archive_type (comics_document->archive, EV_ARCHIVE_TYPE_TAR)) return TRUE; - } } else { g_set_error (error, EV_DOCUMENT_ERROR, @@ -463,8 +326,8 @@ 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; } @@ -472,43 +335,18 @@ 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; + gchar *temp1, *temp2; + gint ret; - #define SORT_LAST_CHAR1 '.' - #define SORT_LAST_CHAR2 '#' + temp1 = g_utf8_collate_key_for_filename (* (const char **) a, -1); + temp2 = g_utf8_collate_key_for_filename (* (const char **) b, -1); - 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; + ret = strcmp (temp1, temp2); - #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); - } + g_free (temp1); + g_free (temp2); - return compare; + return ret; } static gboolean @@ -517,109 +355,41 @@ 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; + GFile *file; - comics_document->archive = g_filename_from_uri (uri, NULL, error); - if (!comics_document->archive) - return FALSE; + file = g_file_new_for_uri (uri); + comics_document->archive_path = g_file_get_path (file); + g_object_unref (file); - 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; - } - - 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, - _("File corrupted")); + _("Can not get local path for archive")); 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); - g_free (std_out); + mime_type = ev_file_get_mime_type (uri, FALSE, error); - if (!cb_files) { - g_set_error_literal (error, - EV_DOCUMENT_ERROR, - EV_DOCUMENT_ERROR_INVALID, - _("No files in archive")); + if (mime_type == NULL) 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); + if (!comics_check_decompress_support (mime_type, comics_document, error)) { + g_free (mime_type); + return FALSE; } - 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); @@ -627,7 +397,6 @@ comics_document_load (EvDocument *document, return TRUE; } - static gboolean comics_document_save (EvDocument *document, const char *uri, @@ -635,7 +404,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 @@ -649,6 +418,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, @@ -656,74 +442,87 @@ 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 * @@ -731,69 +530,70 @@ 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); - - 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); - } else if (bytes <= 0) { - close (outpipe); - gdk_pixbuf_loader_close (loader, NULL); - outpipe = -1; + 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; + } + + 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 { + 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; } @@ -805,79 +605,28 @@ 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); - - return surface; -} + g_clear_object (&pixbuf); -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)); + return surface; } 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); } @@ -900,122 +649,51 @@ 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 * comics_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document, - EvRenderContext *rc, - gboolean border) + EvRenderContext *rc, + gboolean border) { - GdkPixbuf *thumbnail; + GdkPixbuf *thumbnail; - thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document), rc); + thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document), rc); - if (border) { - GdkPixbuf *tmp_pixbuf = thumbnail; - - thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf); - g_object_unref (tmp_pixbuf); - } + if (border) { + GdkPixbuf *tmp_pixbuf = thumbnail; + + thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf); + g_object_unref (tmp_pixbuf); + } - return thumbnail; + return thumbnail; } static void comics_document_thumbnails_get_dimensions (EvDocumentThumbnails *document, - EvRenderContext *rc, - gint *width, - gint *height) + EvRenderContext *rc, + gint *width, + gint *height) { - gdouble page_width, page_height; - - comics_document_get_page_size (EV_DOCUMENT (document), rc->page, - &page_width, &page_height); - - if (rc->rotation == 90 || rc->rotation == 270) { - *width = (gint) (page_height * rc->scale); - *height = (gint) (page_width * rc->scale); - } else { - *width = (gint) (page_width * rc->scale); - *height = (gint) (page_height * rc->scale); - } + gdouble page_width, page_height; + + comics_document_get_page_size (EV_DOCUMENT (document), rc->page, + &page_width, &page_height); + + if (rc->rotation == 90 || rc->rotation == 270) { + *width = (gint) (page_height * rc->scale); + *height = (gint) (page_width * rc->scale); + } else { + *width = (gint) (page_width * rc->scale); + *height = (gint) (page_height * rc->scale); + } } static void comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface) { - 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; + iface->get_thumbnail = comics_document_thumbnails_get_thumbnail; + iface->get_dimensions = comics_document_thumbnails_get_dimensions; } diff --git a/backend/comics/comics-document.h b/backend/comics/comics-document.h index 8a02f3d4..0a308cff 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,6 @@ G_BEGIN_DECLS typedef struct _ComicsDocument ComicsDocument; GType comics_document_get_type (void) G_GNUC_CONST; +GType register_evince_backend (GTypeModule *module); -G_MODULE_EXPORT GType register_xreader_backend (GTypeModule *module); - G_END_DECLS - -#endif /* __COMICS_DOCUMENT_H__ */ diff --git a/backend/comics/comicsdocument.xreader-backend.in b/backend/comics/comicsdocument.xreader-backend.in index dbadbe55..552fb648 100644 --- a/backend/comics/comicsdocument.xreader-backend.in +++ b/backend/comics/comicsdocument.xreader-backend.in @@ -1,4 +1,4 @@ [Xreader Backend] Module=comicsdocument -_TypeDescription=Comic Books -MimeType=application/x-cbr;application/x-cbz;application/x-cb7;application/x-cbt;application/vnd.comicbook+zip; +TypeDescription=Comic Books +MimeType=@COMICS_MIME_TYPES@; 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/backend/comics/meson.build b/backend/comics/meson.build index 396596a4..bcb935eb 100644 --- a/backend/comics/meson.build +++ b/backend/comics/meson.build @@ -1,12 +1,24 @@ -comics_sources = [ - 'comics-document.c', - 'comics-document.h' -] +backend_desktop_conf = configuration_data() +backend_desktop_conf.set('COMICS_MIME_TYPES', comic_mimetypes) + +backend_desktop = configure_file( + input: 'comicsdocument.xreader-backend.in', + output: 'comicsdocument.xreader-backend', + configuration: backend_desktop_conf, + install: true, + install_dir: backendsdir, +) + +comics_sources = files( + 'comics-document.c', + 'ev-archive.c', +) comics_deps = [ - glib, - cairo, - gtk + cairo, + glib, + gtk, + libarchive_dep, ] shared_module( @@ -17,14 +29,21 @@ shared_module( include_directories: include_dirs, dependencies: comics_deps, install: true, - install_dir: backendsdir, + install_dir: backendsdir ) -custom_target( - 'comics_backend', - input: 'comicsdocument.xreader-backend.in', - output: 'comicsdocument.xreader-backend', - command: [intltool_merge, '-d', '-u', po_dir, '@INPUT@', '@OUTPUT@'], - install: true, - install_dir: backendsdir, + + +test_name = 'test-ev-archive' + +test_sources = files( + 'ev-archive.c', + 'test-ev-archive.c', +) + +executable( + test_name, + test_sources, + include_directories: include_dirs, + dependencies: comics_deps, ) diff --git a/backend/comics/test-ev-archive.c b/backend/comics/test-ev-archive.c new file mode 100644 index 00000000..772495a9 --- /dev/null +++ b/backend/comics/test-ev-archive.c @@ -0,0 +1,116 @@ +/* -*- 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" + +static void +usage (const char *prog) +{ + g_print ("- Lists file in a supported archive format\n"); + g_print ("Usage: %s archive-type filename\n", prog); + g_print ("Where archive-type is one of rar, zip, 7z or tar\n"); +} + +static EvArchiveType +str_to_archive_type (const char *str) +{ + g_return_val_if_fail (str != NULL, EV_ARCHIVE_TYPE_NONE); + + if (g_strcmp0 (str, "rar") == 0) + return EV_ARCHIVE_TYPE_RAR; + if (g_strcmp0 (str, "zip") == 0) + return EV_ARCHIVE_TYPE_ZIP; + if (g_strcmp0 (str, "7z") == 0) + return EV_ARCHIVE_TYPE_7Z; + if (g_strcmp0 (str, "tar") == 0) + return EV_ARCHIVE_TYPE_TAR; + + g_warning ("Archive type '%s' not supported", str); + return EV_ARCHIVE_TYPE_NONE; +} + +int +main (int argc, char **argv) +{ + EvArchive *ar; + EvArchiveType ar_type; + GError *error = NULL; + gboolean printed_header = FALSE; + + if (argc != 3) { + usage (argv[0]); + return 1; + } + + ar_type = str_to_archive_type (argv[1]); + if (ar_type == EV_ARCHIVE_TYPE_NONE) + return 1; + + ar = ev_archive_new (); + if (!ev_archive_set_archive_type (ar, ar_type)) { + g_warning ("Failed to set archive type"); + goto out; + } + + if (!ev_archive_open_filename (ar, argv[2], &error)) { + g_warning ("Failed to open '%s': %s", + argv[2], error->message); + g_error_free (error); + goto out; + } + + while (1) { + const char *name; + gboolean is_encrypted; + gint64 size; + + if (!ev_archive_read_next_header (ar, &error)) { + if (error != NULL) { + g_warning ("Fatal error handling archive: %s", error->message); + g_clear_error (&error); + goto out; + } + break; + } + + name = ev_archive_get_entry_pathname (ar); + is_encrypted = ev_archive_get_entry_is_encrypted (ar); + size = ev_archive_get_entry_size (ar); + + if (!printed_header) { + g_print ("P\tSIZE\tNAME\n"); + printed_header = TRUE; + } + + g_print ("%c\t%"G_GINT64_FORMAT"\t%s\n", + is_encrypted ? 'P' : ' ', + size, name); + } + + ev_archive_reset (ar); + g_clear_object (&ar); + + return 0; + +out: + g_clear_object (&ar); + return 1; +} diff --git a/debian/control b/debian/control index 7dd64d9b..8b809c30 100644 --- a/debian/control +++ b/debian/control @@ -5,6 +5,7 @@ Maintainer: Linux Mint Build-Depends: debhelper-compat (= 12), gobject-introspection, intltool, + libarchive-dev (>= 3.6.0), libdjvulibre-dev, libgail-3-dev, libgirepository1.0-dev, diff --git a/libdocument/ev-document.h b/libdocument/ev-document.h index 114c9530..1918adf8 100644 --- a/libdocument/ev-document.h +++ b/libdocument/ev-document.h @@ -56,6 +56,7 @@ typedef struct _EvDocumentPrivate EvDocumentPrivate; typedef enum { EV_DOCUMENT_ERROR_INVALID, + EV_DOCUMENT_ERROR_UNSUPPORTED_CONTENT, EV_DOCUMENT_ERROR_ENCRYPTED } EvDocumentError; diff --git a/meson.build b/meson.build index 48702b0b..a2e12efe 100644 --- a/meson.build +++ b/meson.build @@ -98,8 +98,22 @@ if get_option('pixbuf') xreader_conf.set10('ENABLE_PIXBUF', true) endif if get_option('comics') + libarchive_req_version = '>= 3.6.0' + libarchive_dep = dependency('libarchive', version: '>= 3.6.0', required: true) backend_subdirs += 'comics' - xreader_mime_types += 'application/x-cbr;application/x-cbz;application/x-cb7;application/x-cbt;application/vnd.comicbook+zip;application/vnd.comicbook-rar;' + comic_mimetypes = ';'.join([ + 'application/vnd.comicbook-rar', + 'application/vnd.comicbook+zip', + 'application/x-cb7', + 'application/x-cbr', + 'application/x-cbt', + 'application/x-cbz', + 'application/x-ext-cb7', + 'application/x-ext-cbr', + 'application/x-ext-cbt', + 'application/x-ext-cbz', + ]) + xreader_mime_types += comic_mimetypes endif if get_option('xps') xps = dependency('libgxps', version: '>= 0.2.1')