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