]> www.fi.muni.cz Git - evince.git/blob - backend/comics/comics-document.c
ca154710f41729653b6d69964fa5f4f219c7d92e
[evince.git] / backend / comics / comics-document.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
2 /*
3  * Copyright (C) 2005, Teemu Tervo <teemu.tervo@gmx.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include <config.h>
23 #include <unistd.h>
24 #include <string.h>
25 #include <glib/gi18n-lib.h>
26 #include <glib/gstdio.h>
27 #include <gio/gio.h>
28 #include <sys/wait.h>
29 #include <stdlib.h>
30 #include <errno.h>
31
32 #include "comics-document.h"
33 #include "ev-document-misc.h"
34 #include "ev-document-thumbnails.h"
35 #include "ev-file-helpers.h"
36
37 struct _ComicsDocumentClass
38 {
39         GObjectClass parent_class;
40 };
41  
42 typedef enum 
43 {
44         RARLABS,
45         GNAUNRAR,
46         UNZIP,
47         P7ZIP
48 } ComicBookDecompressType;
49
50 struct _ComicsDocument
51 {
52         GObject parent_instance;
53         gchar    *archive, *dir;
54         GSList   *page_names;
55         gint     n_pages;
56         gchar    *selected_command;
57         gchar    *extract_command, *list_command, *decompress_tmp;
58         gboolean regex_arg;
59         ComicBookDecompressType command_usage;
60 };
61
62 struct 
63 {
64         char *extract, *list, *decompress_tmp; 
65         gboolean regex_arg;
66 } command_usage_def[] = {
67         {"%s p -c- -ierr", "%s vb -c- -- %s", NULL          , FALSE},
68         {NULL            , "%s t %s"        , "%s -xf %s %s", TRUE },
69         {"%s -p -C"      , "%s -Z -1 -- %s" , NULL          , TRUE },
70         {"%s x -so"      , "%s l -- %s"     , NULL          , TRUE }
71 };
72
73
74 typedef struct _ComicsDocumentClass ComicsDocumentClass;
75
76 static void       comics_document_document_iface_init (EvDocumentIface *iface);
77 static void       comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsIface *iface);
78
79 static GSList*    get_supported_image_extensions (void);
80 static void       get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
81                                                   gpointer data);
82 static void       render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
83                                                   gint width,
84                                                   gint height,
85                                                   gpointer data);
86 static char**     extract_argv                   (EvDocument *document,
87                                                   gint page);
88
89
90 EV_BACKEND_REGISTER_WITH_CODE (ComicsDocument, comics_document,
91         {
92                 EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS,
93                                                 comics_document_document_thumbnails_iface_init);
94         } );
95
96 static char *
97 comics_regex_quote (const char *s)
98 {
99     char *ret, *d;
100
101     d = ret = g_malloc (strlen (s) * 4 + 3);
102     
103     *d++ = '\'';
104
105     for (; *s; s++, d++) {
106         switch (*s) {
107         case '?':
108         case '|':
109         case '[':
110         case ']':
111         case '*':
112         case '\\':
113             *d++ = '\\';
114             break;
115         case '\'':
116             *d++ = '\'';
117             *d++ = '\\';
118             *d++ = '\'';
119             break;
120         }
121         *d = *s;
122     }
123     
124     *d++ = '\'';
125     *d = '\0';
126
127     return ret;
128 }
129
130 /* This function manages the command for decompressing a comic book */
131 static gboolean 
132 comics_decompress_temp_dir (const gchar *command_decompress_tmp,
133                             const gchar *command, 
134                             GError      **error)
135 {
136         gboolean success;
137         gchar *std_out, *basename;
138         GError *err = NULL;
139         gint retval;
140         
141         success = g_spawn_command_line_sync (command_decompress_tmp, &std_out, 
142                                              NULL, &retval, &err);
143         basename = g_path_get_basename (command);
144         if (!success) {
145                 g_set_error (error,
146                              EV_DOCUMENT_ERROR, 
147                              EV_DOCUMENT_ERROR_INVALID,
148                              _("Error launching the command “%s” in order to "
149                              "decompress the comic book: %s"),
150                              basename,
151                              err->message);
152                 g_error_free (err);
153         } else if (WIFEXITED (retval)) {
154                 if (WEXITSTATUS (retval) == EXIT_SUCCESS) {
155                         g_free (std_out);
156                         g_free (basename);
157                         return TRUE;
158                 } else {
159                         g_set_error (error,
160                                      EV_DOCUMENT_ERROR,
161                                      EV_DOCUMENT_ERROR_INVALID,
162                                      _("The command “%s” failed at "
163                                      "decompressing the comic book."),
164                                      basename);
165                         g_free (std_out);
166                 }
167         } else {
168                 g_set_error (error,
169                              EV_DOCUMENT_ERROR,
170                              EV_DOCUMENT_ERROR_INVALID,
171                              _("The command “%s” did not end normally."),
172                              basename);
173                 g_free (std_out);
174         }
175         g_free (basename);
176         return FALSE;
177 }
178
179 /* This function shows how to use the choosen command for decompressing a
180  * comic book file. It modifies fields of the ComicsDocument struct with 
181  * this information */
182 static gboolean 
183 comics_generate_command_lines (ComicsDocument *comics_document, 
184                                GError         **error)
185 {
186         gchar *quoted_file;
187         ComicBookDecompressType type;
188         
189         type = comics_document->command_usage;
190         quoted_file = g_shell_quote (comics_document->archive);
191         
192         comics_document->extract_command = 
193                             g_strdup_printf (command_usage_def[type].extract, 
194                                              comics_document->selected_command);
195         comics_document->list_command =
196                             g_strdup_printf (command_usage_def[type].list, 
197                                              comics_document->selected_command, 
198                                              quoted_file);
199         comics_document->regex_arg = command_usage_def[type].regex_arg;
200         if (command_usage_def[type].decompress_tmp) {
201                 comics_document->dir = ev_tmp_directory (NULL); 
202                 comics_document->decompress_tmp = 
203                         g_strdup_printf (command_usage_def[type].decompress_tmp, 
204                                          comics_document->selected_command, 
205                                          quoted_file, 
206                                          comics_document->dir);
207                 g_free (quoted_file);
208                 /* unrar-free can't create directories so we do it on its 
209                  * behalf */
210                 if (type == GNAUNRAR) {
211                         if (g_mkdir_with_parents (comics_document->dir, 0700) != 
212                             0) {
213                                 int errsv = errno;
214                                 g_set_error (error,
215                                              EV_DOCUMENT_ERROR,
216                                              EV_DOCUMENT_ERROR_INVALID,
217                                              _("Failed to create a temporary "
218                                              "directory."));
219                                 g_warning ("Failed to create directory %s: %s", 
220                                            comics_document->dir, 
221                                            g_strerror (errsv));
222                                 
223                                 return FALSE;
224                         }
225                 }
226                 if (!comics_decompress_temp_dir (comics_document->decompress_tmp, 
227                     comics_document->selected_command, error))
228                         return FALSE;
229                 else
230                         return TRUE;
231         } else {
232                 g_free (quoted_file);
233                 return TRUE;
234         }
235
236 }
237
238 /* This function chooses an external command for decompressing a comic 
239  * book based on its mime tipe. */
240 static gboolean 
241 comics_check_decompress_command (gchar          *mime_type, 
242                                  ComicsDocument *comics_document,
243                                  GError         **error)
244 {
245         gboolean success;
246         gchar *std_out, *std_err;
247         gint retval;
248         GError *err = NULL;
249         
250         /* FIXME, use proper cbr/cbz mime types once they're
251          * included in shared-mime-info */
252         
253         if (!strcmp (mime_type, "application/x-cbr") ||
254             !strcmp (mime_type, "application/x-rar")) {
255                 /* The RARLAB provides a no-charge proprietary (freeware) 
256                 * decompress-only client for Linux called unrar. Another 
257                 * option is a GPLv2-licensed command-line tool developed by 
258                 * the Gna! project. Confusingly enough, the free software RAR 
259                 * decoder is also named unrar. For this reason we need to add 
260                 * some lines for disambiguation. Sorry for the added the 
261                 * complexity but it's life :)
262                 * Finally, some distributions, like Debian, rename this free 
263                 * option as unrar-free. 
264                 * */
265                 comics_document->selected_command = 
266                                         g_find_program_in_path ("unrar");
267                 if (comics_document->selected_command) {
268                         /* We only use std_err to avoid printing useless error 
269                          * messages on the terminal */
270                         success = 
271                                 g_spawn_command_line_sync (
272                                               comics_document->selected_command, 
273                                                            &std_out, &std_err,
274                                                            &retval, &err);
275                         if (!success) {
276                                 g_propagate_error (error, err);
277                                 g_error_free (err);
278                                 return FALSE;
279                         /* I don't check retval status because RARLAB unrar 
280                          * doesn't have a way to return 0 without involving an 
281                          * operation with a file*/
282                         } else if (WIFEXITED (retval)) {
283                                 if (g_strrstr (std_out,"freeware") != NULL)
284                                         /* The RARLAB freeware client */
285                                         comics_document->command_usage = RARLABS;
286                                 else
287                                         /* The Gna! free software client */
288                                         comics_document->command_usage = GNAUNRAR;
289
290                                 g_free (std_out);
291                                 g_free (std_err);
292                                 return TRUE;
293                         }
294                 }
295                 /* The Gna! free software client with Debian naming convention */
296                 comics_document->selected_command = 
297                                 g_find_program_in_path ("unrar-free");
298                 if (comics_document->selected_command) {
299                         comics_document->command_usage = GNAUNRAR;
300                         return TRUE;
301                 }
302
303         } else if (!strcmp (mime_type, "application/x-cbz") ||
304                    !strcmp (mime_type, "application/zip")) {
305                 /* InfoZIP's unzip program */
306                 comics_document->selected_command = 
307                                 g_find_program_in_path ("unzip");
308                 if (comics_document->selected_command) {
309                         comics_document->command_usage = UNZIP;
310                         return TRUE;
311                 }
312
313         } else if (!strcmp (mime_type, "application/x-cb7") ||
314                    !strcmp (mime_type, "application/x-7z-compressed")) {
315                 /* 7zr is a light stand-alone executable that supports only 
316                  * 7z/LZMA/BCJ */
317                         comics_document->selected_command = 
318                                 g_find_program_in_path ("7zr");
319                         if (comics_document->selected_command) {
320                                 comics_document->command_usage = P7ZIP;
321                                 return TRUE;
322                         }
323         } else {
324                 g_set_error (error,
325                              EV_DOCUMENT_ERROR,
326                              EV_DOCUMENT_ERROR_INVALID,
327                              _("Not a comic book MIME type: %s"),
328                              mime_type);
329                              return FALSE;
330         }
331         g_set_error_literal (error,
332                              EV_DOCUMENT_ERROR,
333                              EV_DOCUMENT_ERROR_INVALID,
334                              _("Can't find an appropiate command to "
335                              "decompress this type of comic book"));
336         return FALSE;
337 }
338
339 static gboolean
340 comics_document_load (EvDocument *document,
341                       const char *uri,
342                       GError    **error)
343 {
344         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
345         GSList *supported_extensions;
346         gchar *std_out;
347         gchar *mime_type;
348         gchar **cbr_files;
349         gboolean success;
350         int i, retval;
351         GError *err = NULL;
352
353         comics_document->archive = g_filename_from_uri (uri, NULL, error);
354         if (!comics_document->archive)
355                 return FALSE;
356
357         mime_type = ev_file_get_mime_type (uri, FALSE, &err);
358         if (!mime_type) {
359                 if (err) {
360                         g_propagate_error (error, err);
361                 } else {
362                         g_set_error_literal (error,
363                                              EV_DOCUMENT_ERROR,
364                                              EV_DOCUMENT_ERROR_INVALID,
365                                              _("Unknown MIME Type"));
366                 }
367
368                 return FALSE;
369         }
370         
371         if (!comics_check_decompress_command (mime_type, comics_document, 
372         error)) {       
373                 g_free (mime_type);
374                 return FALSE;
375         } else if (!comics_generate_command_lines (comics_document, error)) {
376                    g_free (mime_type);
377                 return FALSE;
378         }
379
380         g_free (mime_type);
381
382         /* Get list of files in archive */
383         success = g_spawn_command_line_sync (comics_document->list_command,
384                                              &std_out, NULL, &retval, error);
385
386         if (!success) {
387                 return FALSE;
388         } else if (!WIFEXITED(retval) || WEXITSTATUS(retval) != EXIT_SUCCESS) {
389                 g_set_error_literal (error,
390                                      EV_DOCUMENT_ERROR,
391                                      EV_DOCUMENT_ERROR_INVALID,
392                                      _("File corrupted"));
393                 return FALSE;
394         }
395
396         /* FIXME: is this safe against filenames containing \n in the archive ? */
397         cbr_files = g_strsplit (std_out, "\n", 0);
398         g_free (std_out);
399
400         if (!cbr_files) {
401                 g_set_error_literal (error,
402                                      EV_DOCUMENT_ERROR,
403                                      EV_DOCUMENT_ERROR_INVALID,
404                                      _("No files in archive"));
405                 return FALSE;
406         }
407
408         supported_extensions = get_supported_image_extensions ();
409         for (i = 0; cbr_files[i] != NULL; i++) {
410                 gchar *suffix = g_strrstr (cbr_files[i], ".");
411                 if (!suffix)
412                         continue;
413                 suffix = g_ascii_strdown (suffix + 1, -1);
414
415                 if (g_slist_find_custom (supported_extensions, suffix,
416                                          (GCompareFunc) strcmp) != NULL) {
417                         comics_document->page_names =
418                                 g_slist_insert_sorted (
419                                         comics_document->page_names,
420                                         g_strdup (g_strstrip (cbr_files[i])),
421                                         (GCompareFunc) strcmp);
422                         comics_document->n_pages++;
423                 }
424
425                 g_free (suffix);
426         }
427
428         g_strfreev (cbr_files);
429         g_slist_foreach (supported_extensions, (GFunc) g_free, NULL);
430         g_slist_free (supported_extensions);
431
432         if (comics_document->n_pages == 0) {
433                 g_set_error (error,
434                              EV_DOCUMENT_ERROR,
435                              EV_DOCUMENT_ERROR_INVALID,
436                              _("No images found in archive %s"),
437                              uri);
438                 return FALSE;
439         }
440
441         return TRUE;
442 }
443
444
445 static gboolean
446 comics_document_save (EvDocument *document,
447                       const char *uri,
448                       GError    **error)
449 {
450         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
451
452         return ev_xfer_uri_simple (comics_document->archive, uri, error);
453 }
454
455 static int
456 comics_document_get_n_pages (EvDocument *document)
457 {
458         return COMICS_DOCUMENT (document)->n_pages;
459 }
460
461 static void
462 comics_document_get_page_size (EvDocument *document,
463                                EvPage     *page,
464                                double     *width,
465                                double     *height)
466 {
467         GdkPixbufLoader *loader;
468         char **argv;
469         guchar buf[1024];
470         gboolean success, got_size = FALSE;
471         gint outpipe = -1;
472         GPid child_pid = -1;
473         gssize bytes;
474         GdkPixbuf *pixbuf;
475         gchar *filename;
476         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
477         
478         if (!comics_document->decompress_tmp) {
479                 argv = extract_argv (document, page->index);
480                 success = g_spawn_async_with_pipes (NULL, argv, NULL,
481                                                     G_SPAWN_SEARCH_PATH | 
482                                                     G_SPAWN_STDERR_TO_DEV_NULL,
483                                                     NULL, NULL,
484                                                     &child_pid,
485                                                     NULL, &outpipe, NULL, NULL);
486                 g_strfreev (argv);
487                 g_return_if_fail (success == TRUE);
488
489                 loader = gdk_pixbuf_loader_new ();
490                 g_signal_connect (loader, "area-prepared",
491                                   G_CALLBACK (get_page_size_area_prepared_cb),
492                                   &got_size);
493
494                 while (outpipe >= 0) {
495                         bytes = read (outpipe, buf, 1024);
496                 
497                         if (bytes > 0)
498                         gdk_pixbuf_loader_write (loader, buf, bytes, NULL);
499                         if (bytes <= 0 || got_size) {
500                                 close (outpipe);
501                                 outpipe = -1;
502                                 gdk_pixbuf_loader_close (loader, NULL);
503                         }
504                 }
505
506                 if (gdk_pixbuf_loader_get_pixbuf (loader)) {
507                         pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
508                         if (width)
509                                 *width = gdk_pixbuf_get_width (pixbuf);
510                         if (height)
511                                 *height = gdk_pixbuf_get_height (pixbuf);
512                 }
513
514                 g_spawn_close_pid (child_pid);
515                 g_object_unref (loader);
516         } else {
517                 filename = g_build_filename (comics_document->dir,      
518                                              (char*) g_slist_nth_data (
519                                              comics_document->page_names, 
520                                              page->index),
521                                              NULL);
522                 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
523                 g_free (filename);
524                 if (width)
525                         *width = gdk_pixbuf_get_width (pixbuf);
526                 if (height)
527                         *height = gdk_pixbuf_get_height (pixbuf);
528         }
529 }
530
531 static void
532 get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
533                                 gpointer         data)
534 {
535         gboolean *got_size = data;
536         *got_size = TRUE;
537 }
538
539 static GdkPixbuf *
540 comics_document_render_pixbuf (EvDocument      *document,
541                                EvRenderContext *rc)
542 {
543         GdkPixbufLoader *loader;
544         GdkPixbuf *rotated_pixbuf;
545         char **argv;
546         guchar buf[4096];
547         gboolean success;
548         gint outpipe = -1;
549         GPid child_pid = -1;
550         gssize bytes;
551         gint width, height;
552         gchar *filename;
553         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
554         
555         if (!comics_document->decompress_tmp) {
556                 argv = extract_argv (document, rc->page->index);
557                 success = g_spawn_async_with_pipes (NULL, argv, NULL,
558                                                     G_SPAWN_SEARCH_PATH | 
559                                                     G_SPAWN_STDERR_TO_DEV_NULL,
560                                                     NULL, NULL,
561                                                     &child_pid,
562                                                     NULL, &outpipe, NULL, NULL);
563                 g_strfreev (argv);
564                 g_return_val_if_fail (success == TRUE, NULL);
565
566                 loader = gdk_pixbuf_loader_new ();
567                 g_signal_connect (loader, "size-prepared",
568                                   G_CALLBACK (render_pixbuf_size_prepared_cb), 
569                                   &rc->scale);
570
571                 while (outpipe >= 0) {
572                         bytes = read (outpipe, buf, 4096);
573
574                         if (bytes > 0) {
575                                 gdk_pixbuf_loader_write (loader, buf, bytes, 
576                                 NULL);
577                         } else if (bytes <= 0) {
578                                 close (outpipe);
579                                 gdk_pixbuf_loader_close (loader, NULL);
580                                 outpipe = -1;
581                         }
582                 }
583
584                 rotated_pixbuf = gdk_pixbuf_rotate_simple (
585                                         gdk_pixbuf_loader_get_pixbuf (loader),
586                                         360 - rc->rotation);
587                 g_spawn_close_pid (child_pid);
588                 g_object_unref (loader);
589         } else {
590                 filename = 
591                         g_build_filename (comics_document->dir,
592                                           (char*) g_slist_nth_data (
593                                                 comics_document->page_names, 
594                                                 rc->page->index),
595                                           NULL);
596            
597                 gdk_pixbuf_get_file_info (filename, &width, &height);
598                 
599                 rotated_pixbuf = 
600                   gdk_pixbuf_rotate_simple (gdk_pixbuf_new_from_file_at_size (
601                                             filename, width * (rc->scale) + 0.5,
602                                             height * (rc->scale) + 0.5, NULL),
603                                             360 - rc->rotation);
604                 g_free (filename);
605         
606         }
607         return rotated_pixbuf;
608 }
609
610 static cairo_surface_t *
611 comics_document_render (EvDocument      *document,
612                         EvRenderContext *rc)
613 {
614         GdkPixbuf       *pixbuf;
615         cairo_surface_t *surface;
616
617         pixbuf = comics_document_render_pixbuf (document, rc);
618         surface = ev_document_misc_surface_from_pixbuf (pixbuf);
619         g_object_unref (pixbuf);
620         
621         return surface;
622 }
623
624 static void
625 render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
626                                 gint             width,
627                                 gint             height,
628                                 gpointer         data)
629 {
630         double *scale = data;
631         int w = (width  * (*scale) + 0.5);
632         int h = (height * (*scale) + 0.5);
633
634         gdk_pixbuf_loader_set_size (loader, w, h);
635 }
636
637 /**
638  * comics_remove_dir: Removes a directory recursively. 
639  * Returns:
640  *      0 if it was successfully deleted,
641  *      -1 if an error occurred                 
642  */
643 static int 
644 comics_remove_dir (gchar *path_name) 
645 {
646         GDir  *content_dir;
647         const gchar *filename;
648         gchar *filename_with_path;
649         
650         if (g_file_test (path_name, G_FILE_TEST_IS_DIR)) {
651                 content_dir = g_dir_open  (path_name, 0, NULL);
652                 filename  = g_dir_read_name (content_dir);
653                 while (filename) {
654                         filename_with_path = 
655                                 g_build_filename (path_name, 
656                                                   filename, NULL);
657                         comics_remove_dir (filename_with_path);
658                         g_free (filename_with_path);
659                         filename = g_dir_read_name (content_dir);
660                 }
661                 g_dir_close (content_dir);
662         }
663         /* Note from g_remove() documentation: on Windows, it is in general not 
664          * possible to remove a file that is open to some process, or mapped 
665          * into memory.*/
666         return (g_remove (path_name));
667 }
668
669 static void
670 comics_document_finalize (GObject *object)
671 {
672         ComicsDocument *comics_document = COMICS_DOCUMENT (object);
673         
674         if (comics_document->decompress_tmp) {
675                 if (comics_remove_dir (comics_document->dir) == -1)
676                         g_warning (_("There was an error deleting “%s”."),
677                                    comics_document->dir);
678                 g_free (comics_document->dir);
679                 g_remove (ev_tmp_dir ());
680         }
681         
682         if (comics_document->page_names) {
683                 g_slist_foreach (comics_document->page_names,
684                                  (GFunc) g_free, NULL);
685                 g_slist_free (comics_document->page_names);
686         }
687
688         g_free (comics_document->archive);
689         g_free (comics_document->selected_command);
690         g_free (comics_document->extract_command);
691         g_free (comics_document->list_command);
692
693         G_OBJECT_CLASS (comics_document_parent_class)->finalize (object);
694 }
695
696 static void
697 comics_document_class_init (ComicsDocumentClass *klass)
698 {
699         GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
700         gobject_class->finalize = comics_document_finalize;
701 }
702
703 static EvDocumentInfo *
704 comics_document_get_info (EvDocument *document)
705 {
706         EvDocumentInfo *info;
707         info = g_new0 (EvDocumentInfo, 1);
708         return info;
709 }
710
711 static void
712 comics_document_document_iface_init (EvDocumentIface *iface)
713 {
714         iface->load = comics_document_load;
715         iface->save = comics_document_save;
716         iface->get_n_pages = comics_document_get_n_pages;
717         iface->get_page_size = comics_document_get_page_size;
718         iface->render = comics_document_render;
719         iface->get_info = comics_document_get_info;
720 }
721
722 static void
723 comics_document_init (ComicsDocument *comics_document)
724 {
725         comics_document->archive = NULL;
726         comics_document->page_names = NULL;
727         comics_document->extract_command = NULL;
728         comics_document->n_pages = 0;
729 }
730
731 /* Returns a list of file extensions supported by gdk-pixbuf */
732 static GSList*
733 get_supported_image_extensions()
734 {
735         GSList *extensions = NULL;
736         GSList *formats = gdk_pixbuf_get_formats ();
737         GSList *l;
738
739         for (l = formats; l != NULL; l = l->next) {
740                 int i;
741                 gchar **ext = gdk_pixbuf_format_get_extensions (l->data);
742
743                 for (i = 0; ext[i] != NULL; i++) {
744                         extensions = g_slist_append (extensions,
745                                                      g_strdup (ext[i]));
746                 }
747
748                 g_strfreev (ext);
749         }
750
751         g_slist_free (formats);
752         return extensions;
753 }
754
755 static GdkPixbuf *
756 comics_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document,
757                                           EvRenderContext      *rc,
758                                           gboolean              border)
759 {
760         GdkPixbuf *thumbnail;
761
762         thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document), rc);
763
764         if (border) {
765               GdkPixbuf *tmp_pixbuf = thumbnail;
766               
767               thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf);
768               g_object_unref (tmp_pixbuf);
769         }
770
771         return thumbnail;
772 }
773
774 static void
775 comics_document_thumbnails_get_dimensions (EvDocumentThumbnails *document,
776                                            EvRenderContext      *rc,
777                                            gint                 *width,
778                                            gint                 *height)
779 {
780         gdouble page_width, page_height;
781         
782         comics_document_get_page_size (EV_DOCUMENT (document), rc->page,
783                                        &page_width, &page_height);
784
785         if (rc->rotation == 90 || rc->rotation == 270) {
786                 *width = (gint) (page_height * rc->scale);
787                 *height = (gint) (page_width * rc->scale);
788         } else {
789                 *width = (gint) (page_width * rc->scale);
790                 *height = (gint) (page_height * rc->scale);
791         }
792 }
793
794 static void
795 comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsIface *iface)
796 {
797         iface->get_thumbnail = comics_document_thumbnails_get_thumbnail;
798         iface->get_dimensions = comics_document_thumbnails_get_dimensions;
799 }
800
801 static char**
802 extract_argv (EvDocument *document, gint page)
803 {
804         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
805         char **argv;
806         char *command_line, *quoted_archive, *quoted_filename;
807         GError *err = NULL;
808
809         quoted_archive = g_shell_quote (comics_document->archive);
810         if (comics_document->regex_arg) {
811                 quoted_filename = comics_regex_quote (
812                         g_slist_nth_data (comics_document->page_names, page));
813         } else {
814                 quoted_filename = g_shell_quote (
815                         g_slist_nth_data (comics_document->page_names, page));
816         }
817
818         command_line = g_strdup_printf ("%s -- %s %s",
819                                         comics_document->extract_command,
820                                         quoted_archive,
821                                         quoted_filename);
822
823         g_shell_parse_argv (command_line, NULL, &argv, &err);
824         
825         if (err) {
826                 g_warning (_("Error %s"), err->message);
827                 g_error_free (err);
828                 return NULL;
829         }
830         
831         g_free (command_line);
832         g_free (quoted_archive);
833         g_free (quoted_filename);
834         return argv;
835 }