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