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