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