]> www.fi.muni.cz Git - evince.git/blob - comics/comics-document.c
Use common save function for backends.
[evince.git] / 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 <unistd.h>
21 #include <string.h>
22 #include <glib/gi18n.h>
23 #include <libgnomevfs/gnome-vfs-mime-utils.h>
24
25 #include "comics-document.h"
26 #include "ev-document-misc.h"
27 #include "ev-document-thumbnails.h"
28
29 struct _ComicsDocumentClass
30 {
31         GObjectClass parent_class;
32 };
33
34 struct _ComicsDocument
35 {
36         GObject parent_instance;
37
38         gchar  *archive;
39         GSList *page_names;
40         int     n_pages;
41         char   *extract_command;
42 };
43
44 typedef struct _ComicsDocumentClass ComicsDocumentClass;
45
46 static void       comics_document_document_iface_init (EvDocumentIface *iface);
47 static void       comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsIface *iface);
48
49 static GSList*    get_supported_image_extensions (void);
50 static void       get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
51                                                   gpointer data);
52 static void       render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
53                                                   gint width,
54                                                   gint height,
55                                                   gpointer data);
56 static char**     extract_argv                   (EvDocument *document,
57                                                   gint page);
58
59
60 G_DEFINE_TYPE_WITH_CODE (
61         ComicsDocument, comics_document, G_TYPE_OBJECT,
62         {
63                 G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT,
64                                        comics_document_document_iface_init);
65                 G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS,
66                                        comics_document_document_thumbnails_iface_init);
67         } );
68
69 static gboolean
70 comics_document_load (EvDocument *document,
71                       const char *uri,
72                       GError    **error)
73 {
74         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
75         GSList *supported_extensions;
76         gchar *list_files_command = NULL, *stdout, *quoted_file, *mime_type;
77         gchar **cbr_files;
78         gboolean success;
79         int i, retval;
80
81         comics_document->archive = g_filename_from_uri (uri, NULL, error);
82         g_return_val_if_fail (comics_document->archive != NULL, FALSE);
83
84         quoted_file = g_shell_quote (comics_document->archive);
85         mime_type = gnome_vfs_get_mime_type (uri);
86
87         /* FIXME, use proper cbr/cbz mime types once they're
88          * included in shared-mime-info */
89         if (!strcmp (mime_type, "application/x-cbr")) {
90                 comics_document->extract_command =
91                         g_strdup ("unrar p -c- -ierr");
92                 list_files_command =
93                         g_strdup_printf ("unrar vb -c- -- %s", quoted_file);
94         } else if (!strcmp (mime_type, "application/x-cbz")) {
95                 comics_document->extract_command =
96                         g_strdup ("unzip -p -C");
97                 list_files_command = 
98                         g_strdup_printf ("zipinfo -1 -- %s", quoted_file);
99         }
100
101         g_free (quoted_file);
102
103         /* Get list of files in archive */
104         success = g_spawn_command_line_sync (list_files_command,
105                                              &stdout, NULL, &retval, error);
106         g_free (list_files_command);
107
108         if (!success) {
109                 g_free (mime_type);
110                 return FALSE;
111         } else if (retval != 0) {
112                 g_set_error (error,
113                              EV_DOCUMENT_ERROR,
114                              EV_DOCUMENT_ERROR_INVALID,
115                              _("File corrupted."));
116                 g_free (mime_type);
117                 return FALSE;
118         }
119
120         cbr_files = g_strsplit (stdout, "\n", 0);
121         supported_extensions = get_supported_image_extensions ();
122         for (i = 0; cbr_files[i] != NULL; i++) {
123                 gchar *suffix = g_strrstr (cbr_files[i], ".");
124                 if (!suffix)
125                         continue;
126                 suffix = g_ascii_strdown (suffix + 1, -1);
127
128                 if (g_slist_find_custom (supported_extensions, suffix,
129                                          (GCompareFunc) strcmp) != NULL) {
130                         comics_document->page_names =
131                                 g_slist_insert_sorted (
132                                         comics_document->page_names,
133                                         g_strdup (g_strstrip (cbr_files[i])),
134                                         (GCompareFunc) strcmp);
135                         comics_document->n_pages++;
136                 }
137
138                 g_free (suffix);
139         }
140
141         g_free (stdout);
142         g_free (mime_type);
143         g_strfreev (cbr_files);
144         g_slist_foreach (supported_extensions, (GFunc) g_free, NULL);
145         g_slist_free (supported_extensions);
146
147         if (comics_document->n_pages == 0) {
148                 g_set_error (error,
149                              EV_DOCUMENT_ERROR,
150                              EV_DOCUMENT_ERROR_INVALID,
151                              _("No images found in archive %s"),
152                              uri);
153                 return FALSE;
154         }
155
156         return TRUE;
157 }
158
159
160 static gboolean
161 comics_document_save (EvDocument *document,
162                       const char *uri,
163                       GError    **error)
164 {
165         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
166
167         return ev_xfer_uri_simple (comics_document->archive, uri, error);
168 }
169
170 static int
171 comics_document_get_n_pages (EvDocument *document)
172 {
173         return COMICS_DOCUMENT (document)->n_pages;
174 }
175
176 static void
177 comics_document_get_page_size (EvDocument *document,
178                                int         page,
179                                double     *width,
180                                double     *height)
181 {
182         GdkPixbufLoader *loader;
183         char **argv;
184         guchar buf[1024];
185         gboolean success, got_size = FALSE;
186         gint outpipe = -1;
187         GPid child_pid = -1;
188
189         argv = extract_argv (document, page);
190         success = g_spawn_async_with_pipes (NULL, argv, NULL,
191                                             G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
192                                             NULL, NULL,
193                                             &child_pid,
194                                             NULL, &outpipe, NULL, NULL);
195         g_strfreev (argv);
196         g_return_if_fail (success == TRUE);
197
198         loader = gdk_pixbuf_loader_new ();
199         g_signal_connect (loader, "area-prepared",
200                           G_CALLBACK (get_page_size_area_prepared_cb),
201                           &got_size);
202
203         while (outpipe >= 0) {
204                 gssize bytes = read (outpipe, buf, 1024);
205                 
206                 if (bytes > 0)
207                         gdk_pixbuf_loader_write (loader, buf, bytes, NULL);
208                 if (bytes <= 0 || got_size) {
209                         close (outpipe);
210                         outpipe = -1;
211                         gdk_pixbuf_loader_close (loader, NULL);
212                 }
213         }
214
215         if (gdk_pixbuf_loader_get_pixbuf (loader)) {
216                 GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
217                 if (width)
218                         *width = gdk_pixbuf_get_width (pixbuf);
219                 if (height)
220                         *height = gdk_pixbuf_get_height (pixbuf);
221         }
222
223         g_spawn_close_pid (child_pid);
224         g_object_unref (loader);
225 }
226
227 static void
228 get_page_size_area_prepared_cb (GdkPixbufLoader *loader,
229                                 gpointer         data)
230 {
231         gboolean *got_size = data;
232         *got_size = TRUE;
233 }
234
235 static GdkPixbuf *
236 comics_document_render_pixbuf (EvDocument  *document,
237                                EvRenderContext *rc)
238 {
239         GdkPixbufLoader *loader;
240         GdkPixbuf *rotated_pixbuf;
241         char **argv;
242         guchar buf[4096];
243         gboolean success;
244         gint outpipe = -1;
245         GPid child_pid = -1;
246
247         argv = extract_argv (document, rc->page);
248         success = g_spawn_async_with_pipes (NULL, argv, NULL,
249                                             G_SPAWN_SEARCH_PATH
250                                             | G_SPAWN_STDERR_TO_DEV_NULL,
251                                             NULL, NULL,
252                                             &child_pid,
253                                             NULL, &outpipe, NULL, NULL);
254         g_strfreev (argv);
255         g_return_val_if_fail (success == TRUE, NULL);
256
257         loader = gdk_pixbuf_loader_new ();
258         g_signal_connect (loader, "size-prepared",
259                           G_CALLBACK (render_pixbuf_size_prepared_cb), &rc->scale);
260
261         while (outpipe >= 0) {
262                 gssize bytes = read (outpipe, buf, 4096);
263
264                 if (bytes > 0) {
265                         gdk_pixbuf_loader_write (loader, buf, bytes, NULL);
266                 } else if (bytes <= 0) {
267                         close (outpipe);
268                         gdk_pixbuf_loader_close (loader, NULL);
269                         outpipe = -1;
270                 }
271         }
272
273         rotated_pixbuf = gdk_pixbuf_rotate_simple (gdk_pixbuf_loader_get_pixbuf (loader),
274                                                    360 - rc->rotation);
275         g_spawn_close_pid (child_pid);
276         g_object_unref (loader);
277         return rotated_pixbuf;
278 }
279
280 static void
281 render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
282                                 gint             width,
283                                 gint             height,
284                                 gpointer         data)
285 {
286         double *scale = data;
287         int w = width  * (*scale);
288         int h = height * (*scale);
289
290         gdk_pixbuf_loader_set_size (loader, w, h);
291 }
292
293 static void
294 comics_document_finalize (GObject *object)
295 {
296         ComicsDocument *comics_document = COMICS_DOCUMENT (object);
297
298         if (comics_document->archive)
299                 g_free (comics_document->archive);
300
301         if (comics_document->page_names) {
302                 g_slist_foreach (comics_document->page_names,
303                                  (GFunc) g_free, NULL);
304                 g_slist_free (comics_document->page_names);
305         }
306
307         if (comics_document->extract_command)
308                 g_free (comics_document->extract_command);
309
310         G_OBJECT_CLASS (comics_document_parent_class)->finalize (object);
311 }
312
313 static void
314 comics_document_class_init (ComicsDocumentClass *klass)
315 {
316         GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
317         gobject_class->finalize = comics_document_finalize;
318 }
319
320 static gboolean
321 comics_document_can_get_text (EvDocument *document)
322 {
323         return FALSE;
324 }
325
326 static EvDocumentInfo *
327 comics_document_get_info (EvDocument *document)
328 {
329         EvDocumentInfo *info;
330         info = g_new0 (EvDocumentInfo, 1);
331         return info;
332 }
333
334 static void
335 comics_document_document_iface_init (EvDocumentIface *iface)
336 {
337         iface->load = comics_document_load;
338         iface->save = comics_document_save;
339         iface->can_get_text  = comics_document_can_get_text;
340         iface->get_n_pages   = comics_document_get_n_pages;
341         iface->get_page_size = comics_document_get_page_size;
342         iface->render_pixbuf = comics_document_render_pixbuf;
343         iface->get_info      = comics_document_get_info;
344 }
345
346 static void
347 comics_document_init (ComicsDocument *comics_document)
348 {
349         comics_document->archive = NULL;
350         comics_document->page_names = NULL;
351         comics_document->extract_command = NULL;
352         comics_document->n_pages = 0;
353 }
354
355 /* Returns a list of file extensions supported by gdk-pixbuf */
356 static GSList*
357 get_supported_image_extensions()
358 {
359         GSList *extensions = NULL;
360         GSList *formats = gdk_pixbuf_get_formats ();
361         GSList *l;
362
363         for (l = formats; l != NULL; l = l->next) {
364                 int i;
365                 gchar **ext = gdk_pixbuf_format_get_extensions (l->data);
366
367                 for (i = 0; ext[i] != NULL; i++) {
368                         extensions = g_slist_append (extensions,
369                                                      g_strdup (ext[i]));
370                 }
371
372                 g_strfreev (ext);
373         }
374
375         g_slist_free (formats);
376         return extensions;
377 }
378
379 static void 
380 comics_document_thumbnails_get_geometry (EvDocumentThumbnails *document,
381                                          gint                  page,
382                                          gint                  suggested_width,
383                                          gint                 *width,
384                                          gint                 *height,
385                                          gdouble              *scale_factor)
386 {
387         gdouble orig_width, orig_height, scale;
388
389         comics_document_get_page_size (EV_DOCUMENT (document), page,
390                                        &orig_width, &orig_height);
391         scale = suggested_width / orig_width;
392
393         if (width)
394                 *width = suggested_width;
395         if (height)
396                 *height = orig_height * scale;
397         if (scale_factor)
398                 *scale_factor = scale;
399 }
400
401 static GdkPixbuf *
402 comics_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document,
403                                         gint                  page,
404                                         gint                  rotation,
405                                         gint                  size,
406                                         gboolean              border)
407 {
408         GdkPixbuf *thumbnail, *framed;
409         gint thumb_width, thumb_height;
410         gdouble scale;
411         EvRenderContext *rc;
412
413         comics_document_thumbnails_get_geometry (document, page, size,
414                                                  &thumb_width, &thumb_height,
415                                                  &scale);
416
417         rc = ev_render_context_new (rotation, page, scale);
418         thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document),
419                                                    rc);
420         g_object_unref (G_OBJECT (rc));
421
422         if (border) {
423               GdkPixbuf *tmp_pixbuf = thumbnail;
424               thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, 0, tmp_pixbuf);
425               g_object_unref (tmp_pixbuf);
426         }
427
428         return thumbnail;
429 }
430
431 static void
432 comics_document_thumbnails_get_dimensions (EvDocumentThumbnails *document,
433                                            gint                  page,
434                                            gint                  suggested_width,
435                                            gint                  *width,
436                                            gint                  *height)
437 {
438         comics_document_thumbnails_get_geometry (document, page,
439                                                  suggested_width,
440                                                  width, height, NULL);
441 }
442
443 static void
444 comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsIface *iface)
445 {
446         iface->get_thumbnail = comics_document_thumbnails_get_thumbnail;
447         iface->get_dimensions = comics_document_thumbnails_get_dimensions;
448 }
449
450 static char**
451 extract_argv (EvDocument *document, gint page)
452 {
453         ComicsDocument *comics_document = COMICS_DOCUMENT (document);
454         char **argv;
455         char *command_line, *quoted_archive, *quoted_filename;
456
457         quoted_archive = g_shell_quote (comics_document->archive);
458         quoted_filename = g_shell_quote (
459                 g_slist_nth_data (comics_document->page_names, page));
460
461         command_line = g_strdup_printf ("%s -- %s %s",
462                                         comics_document->extract_command,
463                                         quoted_archive,
464                                         quoted_filename);
465         g_shell_parse_argv (command_line, NULL, &argv, NULL);
466
467         g_free (command_line);
468         g_free (quoted_archive);
469         g_free (quoted_filename);
470         return argv;
471 }