]> www.fi.muni.cz Git - evince.git/blob - backend/comics/comics-document.c
[comics] Adds comics_regex_quote()
[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;
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                 comics_document->list_command =
265                            g_strdup_printf (command_usage_def[type].list,
266                                             comics_document->alternative_command,
267                                             comics_document->archive);
268         } else {
269                 quoted_file = g_shell_quote (comics_document->archive);
270                 comics_document->list_command =
271                                 g_strdup_printf (command_usage_def[type].list,
272                                                  quoted_command, quoted_file);
273         }
274         comics_document->extract_command =
275                             g_strdup_printf (command_usage_def[type].extract,
276                                              quoted_command);
277         comics_document->offset = command_usage_def[type].offset;
278         if (command_usage_def[type].decompress_tmp) {
279                 comics_document->dir = ev_mkdtemp ("evince-comics-XXXXXX", error);
280                 if (comics_document->dir == NULL)
281                         return FALSE;
282
283                 /* unrar-free can't create directories, but ev_mkdtemp already created the dir */
284
285                 comics_document->decompress_tmp =
286                         g_strdup_printf (command_usage_def[type].decompress_tmp, 
287                                          quoted_command, quoted_file,
288                                          comics_document->dir);
289                 g_free (quoted_file);
290                 g_free (quoted_command);
291
292                 if (!comics_decompress_temp_dir (comics_document->decompress_tmp,
293                     comics_document->selected_command, error))
294                         return FALSE;
295                 else
296                         return TRUE;
297         } else {
298                 g_free (quoted_file);
299                 g_free (quoted_command);
300                 return TRUE;
301         }
302
303 }
304
305 /* This function chooses an external command for decompressing a comic 
306  * book based on its mime tipe. */
307 static gboolean 
308 comics_check_decompress_command (gchar          *mime_type, 
309                                  ComicsDocument *comics_document,
310                                  GError         **error)
311 {
312         gboolean success;
313         gchar *std_out, *std_err;
314         gint retval;
315         GError *err = NULL;
316         
317         /* FIXME, use proper cbr/cbz mime types once they're
318          * included in shared-mime-info */
319         
320         if (!strcmp (mime_type, "application/x-cbr") ||
321             !strcmp (mime_type, "application/x-rar")) {
322                 /* The RARLAB provides a no-charge proprietary (freeware) 
323                 * decompress-only client for Linux called unrar. Another 
324                 * option is a GPLv2-licensed command-line tool developed by 
325                 * the Gna! project. Confusingly enough, the free software RAR 
326                 * decoder is also named unrar. For this reason we need to add 
327                 * some lines for disambiguation. Sorry for the added the 
328                 * complexity but it's life :)
329                 * Finally, some distributions, like Debian, rename this free 
330                 * option as unrar-free. 
331                 * */
332                 comics_document->selected_command = 
333                                         g_find_program_in_path ("unrar");
334                 if (comics_document->selected_command) {
335                         /* We only use std_err to avoid printing useless error 
336                          * messages on the terminal */
337                         success = 
338                                 g_spawn_command_line_sync (
339                                               comics_document->selected_command, 
340                                                            &std_out, &std_err,
341                                                            &retval, &err);
342                         if (!success) {
343                                 g_propagate_error (error, err);
344                                 g_error_free (err);
345                                 return FALSE;
346                         /* I don't check retval status because RARLAB unrar 
347                          * doesn't have a way to return 0 without involving an 
348                          * operation with a file*/
349                         } else if (WIFEXITED (retval)) {
350                                 if (g_strrstr (std_out,"freeware") != NULL)
351                                         /* The RARLAB freeware client */
352                                         comics_document->command_usage = RARLABS;
353                                 else
354                                         /* The Gna! free software client */
355                                         comics_document->command_usage = GNAUNRAR;
356
357                                 g_free (std_out);
358                                 g_free (std_err);
359                                 return TRUE;
360                         }
361                 }
362                 /* The Gna! free software client with Debian naming convention */
363                 comics_document->selected_command = 
364                                 g_find_program_in_path ("unrar-free");
365                 if (comics_document->selected_command) {
366                         comics_document->command_usage = GNAUNRAR;
367                         return TRUE;
368                 }
369
370         } else if (!strcmp (mime_type, "application/x-cbz") ||
371                    !strcmp (mime_type, "application/zip")) {
372                 /* InfoZIP's unzip program */
373                 comics_document->selected_command = 
374                                 g_find_program_in_path ("unzip");
375                 comics_document->alternative_command =
376                                 g_find_program_in_path ("zipnote");
377                 if (comics_document->selected_command &&
378                     comics_document->alternative_command) {
379                         comics_document->command_usage = UNZIP;
380                         return TRUE;
381                 }
382
383         } else if (!strcmp (mime_type, "application/x-cb7") ||
384                    !strcmp (mime_type, "application/x-7z-compressed")) {
385                 /* 7zr, 7za and 7z are the commands from the p7zip project able 
386                  * to decompress .7z files */ 
387                         comics_document->selected_command = 
388                                 g_find_program_in_path ("7zr");
389                         if (comics_document->selected_command) {
390                                 comics_document->command_usage = P7ZIP;
391                                 return TRUE;
392                         }
393                         comics_document->selected_command = 
394                                 g_find_program_in_path ("7za");
395                         if (comics_document->selected_command) {
396                                 comics_document->command_usage = P7ZIP;
397                                 return TRUE;
398                         }
399                         comics_document->selected_command = 
400                                 g_find_program_in_path ("7z");
401                         if (comics_document->selected_command) {
402                                 comics_document->command_usage = P7ZIP;
403                                 return TRUE;
404                         }
405         } else if (!strcmp (mime_type, "application/x-cbt") ||
406                    !strcmp (mime_type, "application/x-tar")) {
407                 /* tar utility (Tape ARchive) */
408                 comics_document->selected_command =
409                                 g_find_program_in_path ("tar");
410                 if (comics_document->selected_command) {
411                         comics_document->command_usage = TAR;
412                         return TRUE;
413                 }
414         } else {
415                 g_set_error (error,
416                              EV_DOCUMENT_ERROR,
417                              EV_DOCUMENT_ERROR_INVALID,
418                              _("Not a comic book MIME type: %s"),
419                              mime_type);
420                              return FALSE;
421         }
422         g_set_error_literal (error,
423                              EV_DOCUMENT_ERROR,
424                              EV_DOCUMENT_ERROR_INVALID,
425                              _("Can't find an appropriate command to "
426                              "decompress this type of comic book"));
427         return FALSE;
428 }
429
430 static int
431 sort_page_names (gconstpointer a,
432                  gconstpointer b)
433 {
434   return strcmp (* (const char **) a, * (const char **) b);
435 }
436
437 static gboolean
438 comics_document_load (EvDocument *document,
439                       const char *uri,
440                       GError    **error)
441 {
442         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
443         GSList *supported_extensions;
444         gchar *std_out;
445         gchar *mime_type;
446         gchar **cb_files, *cb_file;
447         gboolean success;
448         int i, retval;
449         GError *err = NULL;
450
451         comics_document->archive = g_filename_from_uri (uri, NULL, error);
452         if (!comics_document->archive)
453                 return FALSE;
454
455         mime_type = ev_file_get_mime_type (uri, FALSE, &err);
456         if (!mime_type) {
457                 if (err) {
458                         g_propagate_error (error, err);
459                 } else {
460                         g_set_error_literal (error,
461                                              EV_DOCUMENT_ERROR,
462                                              EV_DOCUMENT_ERROR_INVALID,
463                                              _("Unknown MIME Type"));
464                 }
465
466                 return FALSE;
467         }
468         
469         if (!comics_check_decompress_command (mime_type, comics_document, 
470         error)) {       
471                 g_free (mime_type);
472                 return FALSE;
473         } else if (!comics_generate_command_lines (comics_document, error)) {
474                    g_free (mime_type);
475                 return FALSE;
476         }
477
478         g_free (mime_type);
479
480         /* Get list of files in archive */
481         success = g_spawn_command_line_sync (comics_document->list_command,
482                                              &std_out, NULL, &retval, error);
483
484         if (!success) {
485                 return FALSE;
486         } else if (!WIFEXITED(retval) || WEXITSTATUS(retval) != EXIT_SUCCESS) {
487                 g_set_error_literal (error,
488                                      EV_DOCUMENT_ERROR,
489                                      EV_DOCUMENT_ERROR_INVALID,
490                                      _("File corrupted"));
491                 return FALSE;
492         }
493
494         /* FIXME: is this safe against filenames containing \n in the archive ? */
495         cb_files = g_strsplit (std_out, EV_EOL, 0);
496
497         g_free (std_out);
498
499         if (!cb_files) {
500                 g_set_error_literal (error,
501                                      EV_DOCUMENT_ERROR,
502                                      EV_DOCUMENT_ERROR_INVALID,
503                                      _("No files in archive"));
504                 return FALSE;
505         }
506
507         comics_document->page_names = g_ptr_array_sized_new (64);
508
509         supported_extensions = get_supported_image_extensions ();
510         for (i = 0; cb_files[i] != NULL; i++) {
511                 if (comics_document->offset != NO_OFFSET) {
512                         if (g_utf8_strlen (cb_files[i],-1) > 
513                             comics_document->offset) {
514                                 cb_file = 
515                                         g_utf8_offset_to_pointer (cb_files[i], 
516                                                        comics_document->offset);
517                         } else {
518                                 continue;
519                         }
520                 } else {
521                         cb_file = cb_files[i];
522                 }
523                 gchar *suffix = g_strrstr (cb_file, ".");
524                 if (!suffix)
525                         continue;
526                 suffix = g_ascii_strdown (suffix + 1, -1);
527                 if (g_slist_find_custom (supported_extensions, suffix,
528                                          (GCompareFunc) strcmp) != NULL) {
529                         g_ptr_array_add (comics_document->page_names,
530                                          g_strstrip (g_strdup (cb_file)));
531                 }
532                 g_free (suffix);
533         }
534         g_strfreev (cb_files);
535         g_slist_foreach (supported_extensions, (GFunc) g_free, NULL);
536         g_slist_free (supported_extensions);
537
538         if (comics_document->page_names->len == 0) {
539                 g_set_error (error,
540                              EV_DOCUMENT_ERROR,
541                              EV_DOCUMENT_ERROR_INVALID,
542                              _("No images found in archive %s"),
543                              uri);
544                 return FALSE;
545         }
546
547         /* Now sort the pages */
548         g_ptr_array_sort (comics_document->page_names, sort_page_names);
549
550         return TRUE;
551 }
552
553
554 static gboolean
555 comics_document_save (EvDocument *document,
556                       const char *uri,
557                       GError    **error)
558 {
559         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
560
561         return ev_xfer_uri_simple (comics_document->archive, uri, error);
562 }
563
564 static int
565 comics_document_get_n_pages (EvDocument *document)
566 {
567         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
568
569         if (comics_document->page_names == NULL)
570                 return 0;
571
572         return comics_document->page_names->len;
573 }
574
575 static void
576 comics_document_get_page_size (EvDocument *document,
577                                EvPage     *page,
578                                double     *width,
579                                double     *height)
580 {
581         GdkPixbufLoader *loader;
582         char **argv;
583         guchar buf[1024];
584         gboolean success, got_size = FALSE;
585         gint outpipe = -1;
586         GPid child_pid;
587         gssize bytes;
588         GdkPixbuf *pixbuf;
589         gchar *filename;
590         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
591         
592         if (!comics_document->decompress_tmp) {
593                 argv = extract_argv (document, page->index);
594                 success = g_spawn_async_with_pipes (NULL, argv, NULL,
595                                                     G_SPAWN_SEARCH_PATH | 
596                                                     G_SPAWN_STDERR_TO_DEV_NULL,
597                                                     NULL, NULL,
598                                                     &child_pid,
599                                                     NULL, &outpipe, NULL, NULL);
600                 g_strfreev (argv);
601                 g_return_if_fail (success == TRUE);
602
603                 loader = gdk_pixbuf_loader_new ();
604                 g_signal_connect (loader, "area-prepared",
605                                   G_CALLBACK (get_page_size_area_prepared_cb),
606                                   &got_size);
607
608                 while (outpipe >= 0) {
609                         bytes = read (outpipe, buf, 1024);
610                 
611                         if (bytes > 0)
612                         gdk_pixbuf_loader_write (loader, buf, bytes, NULL);
613                         if (bytes <= 0 || got_size) {
614                                 close (outpipe);
615                                 outpipe = -1;
616                                 gdk_pixbuf_loader_close (loader, NULL);
617                         }
618                 }
619
620                 if (gdk_pixbuf_loader_get_pixbuf (loader)) {
621                         pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
622                         if (width)
623                                 *width = gdk_pixbuf_get_width (pixbuf);
624                         if (height)
625                                 *height = gdk_pixbuf_get_height (pixbuf);
626                 }
627
628                 g_spawn_close_pid (child_pid);
629                 g_object_unref (loader);
630         } else {
631                 filename = g_build_filename (comics_document->dir,
632                                              (char *) comics_document->page_names->pdata[page->index],
633                                              NULL);
634                 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
635                 g_free (filename);
636                 if (width)
637                         *width = gdk_pixbuf_get_width (pixbuf);
638                 if (height)
639                         *height = gdk_pixbuf_get_height (pixbuf);
640         }
641 }
642
643 static void
644 get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
645                                 gpointer         data)
646 {
647         gboolean *got_size = data;
648         *got_size = TRUE;
649 }
650
651 static GdkPixbuf *
652 comics_document_render_pixbuf (EvDocument      *document,
653                                EvRenderContext *rc)
654 {
655         GdkPixbufLoader *loader;
656         GdkPixbuf *rotated_pixbuf;
657         char **argv;
658         guchar buf[4096];
659         gboolean success;
660         gint outpipe = -1;
661         GPid child_pid;
662         gssize bytes;
663         gint width, height;
664         gchar *filename;
665         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
666         
667         if (!comics_document->decompress_tmp) {
668                 argv = extract_argv (document, rc->page->index);
669                 success = g_spawn_async_with_pipes (NULL, argv, NULL,
670                                                     G_SPAWN_SEARCH_PATH | 
671                                                     G_SPAWN_STDERR_TO_DEV_NULL,
672                                                     NULL, NULL,
673                                                     &child_pid,
674                                                     NULL, &outpipe, NULL, NULL);
675                 g_strfreev (argv);
676                 g_return_val_if_fail (success == TRUE, NULL);
677
678                 loader = gdk_pixbuf_loader_new ();
679                 g_signal_connect (loader, "size-prepared",
680                                   G_CALLBACK (render_pixbuf_size_prepared_cb), 
681                                   &rc->scale);
682
683                 while (outpipe >= 0) {
684                         bytes = read (outpipe, buf, 4096);
685
686                         if (bytes > 0) {
687                                 gdk_pixbuf_loader_write (loader, buf, bytes, 
688                                 NULL);
689                         } else if (bytes <= 0) {
690                                 close (outpipe);
691                                 gdk_pixbuf_loader_close (loader, NULL);
692                                 outpipe = -1;
693                         }
694                 }
695
696                 rotated_pixbuf = gdk_pixbuf_rotate_simple (
697                                         gdk_pixbuf_loader_get_pixbuf (loader),
698                                         360 - rc->rotation);
699                 g_spawn_close_pid (child_pid);
700                 g_object_unref (loader);
701         } else {
702                 filename = 
703                         g_build_filename (comics_document->dir,
704                                           (char *) comics_document->page_names->pdata[rc->page->index],
705                                           NULL);
706            
707                 gdk_pixbuf_get_file_info (filename, &width, &height);
708                 
709                 rotated_pixbuf = 
710                   gdk_pixbuf_rotate_simple (gdk_pixbuf_new_from_file_at_size (
711                                             filename, width * (rc->scale) + 0.5,
712                                             height * (rc->scale) + 0.5, NULL),
713                                             360 - rc->rotation);
714                 g_free (filename);
715         
716         }
717         return rotated_pixbuf;
718 }
719
720 static cairo_surface_t *
721 comics_document_render (EvDocument      *document,
722                         EvRenderContext *rc)
723 {
724         GdkPixbuf       *pixbuf;
725         cairo_surface_t *surface;
726
727         pixbuf = comics_document_render_pixbuf (document, rc);
728         surface = ev_document_misc_surface_from_pixbuf (pixbuf);
729         g_object_unref (pixbuf);
730         
731         return surface;
732 }
733
734 static void
735 render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
736                                 gint             width,
737                                 gint             height,
738                                 gpointer         data)
739 {
740         double *scale = data;
741         int w = (width  * (*scale) + 0.5);
742         int h = (height * (*scale) + 0.5);
743
744         gdk_pixbuf_loader_set_size (loader, w, h);
745 }
746
747 /**
748  * comics_remove_dir: Removes a directory recursively. 
749  * Returns:
750  *      0 if it was successfully deleted,
751  *      -1 if an error occurred                 
752  */
753 static int 
754 comics_remove_dir (gchar *path_name) 
755 {
756         GDir  *content_dir;
757         const gchar *filename;
758         gchar *filename_with_path;
759         
760         if (g_file_test (path_name, G_FILE_TEST_IS_DIR)) {
761                 content_dir = g_dir_open  (path_name, 0, NULL);
762                 filename  = g_dir_read_name (content_dir);
763                 while (filename) {
764                         filename_with_path = 
765                                 g_build_filename (path_name, 
766                                                   filename, NULL);
767                         comics_remove_dir (filename_with_path);
768                         g_free (filename_with_path);
769                         filename = g_dir_read_name (content_dir);
770                 }
771                 g_dir_close (content_dir);
772         }
773         /* Note from g_remove() documentation: on Windows, it is in general not 
774          * possible to remove a file that is open to some process, or mapped 
775          * into memory.*/
776         return (g_remove (path_name));
777 }
778
779 static void
780 comics_document_finalize (GObject *object)
781 {
782         ComicsDocument *comics_document = COMICS_DOCUMENT (object);
783         
784         if (comics_document->decompress_tmp) {
785                 if (comics_remove_dir (comics_document->dir) == -1)
786                         g_warning (_("There was an error deleting “%s”."),
787                                    comics_document->dir);
788                 g_free (comics_document->dir);
789         }
790         
791         if (comics_document->page_names) {
792                 g_ptr_array_foreach (comics_document->page_names, (GFunc) g_free, NULL);
793                 g_ptr_array_free (comics_document->page_names, TRUE);
794         }
795
796         g_free (comics_document->archive);
797         g_free (comics_document->selected_command);
798         g_free (comics_document->alternative_command);
799         g_free (comics_document->extract_command);
800         g_free (comics_document->list_command);
801
802         G_OBJECT_CLASS (comics_document_parent_class)->finalize (object);
803 }
804
805 static void
806 comics_document_class_init (ComicsDocumentClass *klass)
807 {
808         GObjectClass    *gobject_class = G_OBJECT_CLASS (klass);
809         EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass);
810
811         gobject_class->finalize = comics_document_finalize;
812
813         ev_document_class->load = comics_document_load;
814         ev_document_class->save = comics_document_save;
815         ev_document_class->get_n_pages = comics_document_get_n_pages;
816         ev_document_class->get_page_size = comics_document_get_page_size;
817         ev_document_class->render = comics_document_render;
818 }
819
820 static void
821 comics_document_init (ComicsDocument *comics_document)
822 {
823         comics_document->archive = NULL;
824         comics_document->page_names = NULL;
825         comics_document->extract_command = NULL;
826 }
827
828 /* Returns a list of file extensions supported by gdk-pixbuf */
829 static GSList*
830 get_supported_image_extensions()
831 {
832         GSList *extensions = NULL;
833         GSList *formats = gdk_pixbuf_get_formats ();
834         GSList *l;
835
836         for (l = formats; l != NULL; l = l->next) {
837                 int i;
838                 gchar **ext = gdk_pixbuf_format_get_extensions (l->data);
839
840                 for (i = 0; ext[i] != NULL; i++) {
841                         extensions = g_slist_append (extensions,
842                                                      g_strdup (ext[i]));
843                 }
844
845                 g_strfreev (ext);
846         }
847
848         g_slist_free (formats);
849         return extensions;
850 }
851
852 static GdkPixbuf *
853 comics_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document,
854                                           EvRenderContext      *rc,
855                                           gboolean              border)
856 {
857         GdkPixbuf *thumbnail;
858
859         thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document), rc);
860
861         if (border) {
862               GdkPixbuf *tmp_pixbuf = thumbnail;
863               
864               thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf);
865               g_object_unref (tmp_pixbuf);
866         }
867
868         return thumbnail;
869 }
870
871 static void
872 comics_document_thumbnails_get_dimensions (EvDocumentThumbnails *document,
873                                            EvRenderContext      *rc,
874                                            gint                 *width,
875                                            gint                 *height)
876 {
877         gdouble page_width, page_height;
878         
879         comics_document_get_page_size (EV_DOCUMENT (document), rc->page,
880                                        &page_width, &page_height);
881
882         if (rc->rotation == 90 || rc->rotation == 270) {
883                 *width = (gint) (page_height * rc->scale);
884                 *height = (gint) (page_width * rc->scale);
885         } else {
886                 *width = (gint) (page_width * rc->scale);
887                 *height = (gint) (page_height * rc->scale);
888         }
889 }
890
891 static void
892 comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface)
893 {
894         iface->get_thumbnail = comics_document_thumbnails_get_thumbnail;
895         iface->get_dimensions = comics_document_thumbnails_get_dimensions;
896 }
897
898 static char**
899 extract_argv (EvDocument *document, gint page)
900 {
901         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
902         char **argv;
903         char *command_line, *quoted_archive, *quoted_filename;
904         GError *err = NULL;
905
906         if (page >= comics_document->page_names->len)
907                 return NULL;
908
909         if (comics_document->regex_arg) {
910                 quoted_archive = comics_regex_quote (comics_document->archive);
911                 quoted_filename =
912                         comics_regex_quote (comics_document->page_names->pdata[page]);
913         } else {
914                 quoted_archive = g_shell_quote (comics_document->archive);
915                 quoted_filename = g_shell_quote (comics_document->page_names->pdata[page]);
916         }
917
918         command_line = g_strdup_printf ("%s %s %s",
919                                         comics_document->extract_command,
920                                         quoted_archive,
921                                         quoted_filename);
922         g_shell_parse_argv (command_line, NULL, &argv, &err);
923
924         if (err) {
925                 g_warning (_("Error %s"), err->message);
926                 g_error_free (err);
927                 return NULL;
928         }
929
930         g_free (command_line);
931         g_free (quoted_archive);
932         g_free (quoted_filename);
933         return argv;
934 }