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