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