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