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