]> www.fi.muni.cz Git - evince.git/blob - libdocument/ev-file-helpers.c
3845519f67c2cbcb7184e1e639cb1b3fa9dc5615
[evince.git] / libdocument / ev-file-helpers.c
1 /*
2  *  Copyright (C) 2002 Jorn Baayen
3  *  Copyright © 2009 Christian Persch
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 <stdlib.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <errno.h>
27
28 #include <glib.h>
29 #include <glib/gstdio.h>
30 #include <glib/gi18n-lib.h>
31
32 #include "ev-file-helpers.h"
33
34 static gchar *tmp_dir = NULL;
35
36 /*
37  * ev_dir_ensure_exists:
38  * @dir: the directory name
39  * @mode: permissions to use when creating the directory
40  * @error: a location to store a #GError
41  *
42  * Create @dir recursively with permissions @mode.
43  *
44  * Returns: %TRUE on success, or %FALSE on error with @error filled in
45  */
46 static gboolean
47 _ev_dir_ensure_exists (const gchar *dir,
48                        int          mode,
49                        GError     **error)
50 {
51         int errsv;
52         char *display_name;
53
54         g_return_val_if_fail (dir != NULL, FALSE);
55
56         errno = 0;
57         if (g_mkdir_with_parents (dir, mode) == 0)
58                 return TRUE;
59
60         errsv = errno;
61         if (errsv == EEXIST && g_file_test (dir, G_FILE_TEST_IS_DIR))
62                 return TRUE;
63
64         display_name = g_filename_display_name (dir);
65         g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
66                      "Failed to create directory '%s': %s",
67                      display_name, g_strerror (errsv));
68         g_free (display_name);
69
70         return FALSE;
71 }
72
73 /*
74  * _ev_tmp_dir:
75  * @error: a location to store a #GError
76  *
77  * Returns the tmp directory.
78  *
79  * Returns: the tmp directory, or %NULL with @error filled in if the
80  *   directory could not be created
81  */
82 static const char *
83 _ev_tmp_dir (GError **error)
84 {
85
86         if (tmp_dir == NULL) {
87                 gchar *dirname, *prgname;
88
89                 prgname = g_get_prgname ();
90                 dirname = g_strdup_printf ("%s-%u", prgname ? prgname : "unknown", getpid ());
91                 tmp_dir = g_build_filename (g_get_tmp_dir (), dirname, NULL);
92                 g_free (dirname);
93         }
94
95         if (!_ev_dir_ensure_exists (tmp_dir, 0700, error))
96                 return NULL;
97
98         return tmp_dir;
99 }
100
101 void
102 _ev_file_helpers_init (void)
103 {
104 }
105
106 void
107 _ev_file_helpers_shutdown (void)
108 {       
109         if (tmp_dir != NULL)    
110                 g_rmdir (tmp_dir);
111
112         g_free (tmp_dir);
113         tmp_dir = NULL;
114 }
115
116 /**
117  * ev_mkstemp:
118  * @template: a template string; must contain 'XXXXXX', but not necessarily as a suffix
119  * @file_name: a location to store the filename of the temp file
120  * @error: a location to store a #GError
121  *
122  * Creates a temp file in the evince temp directory.
123  *
124  * Returns: a file descriptor to the newly created temp file name, or %-1
125  *   on error with @error filled in
126  */
127 int
128 ev_mkstemp (const char  *template,
129             char       **file_name,
130             GError     **error)
131 {
132         const char *tmp;
133         char *name;
134         int fd;
135
136         if ((tmp = _ev_tmp_dir (error)) == NULL)
137               return -1;
138
139         name = g_build_filename (tmp, template, NULL);
140         fd = g_mkstemp (name);
141
142         if (fd == -1) {
143                 int errsv = errno;
144
145                 g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
146                              _("Failed to create a temporary file: %s"),
147                              g_strerror (errsv));
148
149                 g_free (name);
150                 return -1;
151         }
152
153         if (file_name)
154                 *file_name = name;
155
156         return fd;
157 }
158
159 static void
160 close_fd_cb (gpointer fdptr)
161 {
162         int fd = GPOINTER_TO_INT (fdptr);
163
164         close (fd);
165 }
166
167 /**
168  * ev_mkstemp_file:
169  * @template: a template string; must contain 'XXXXXX', but not necessarily as a suffix
170  * @error: a location to store a #GError
171  *
172  * Creates a temp #GFile in the evince temp directory. See ev_mkstemp() for more information.
173  *
174  * Returns: a newly allocated #GFile for the newly created temp file name, or %NULL
175  *   on error with @error filled in
176  */
177 GFile *
178 ev_mkstemp_file (const char        *template,
179                  GError           **error)
180 {
181         char *file_name;
182         int fd;
183         GFile *file;
184
185         fd = ev_mkstemp (template, &file_name, error);
186         if (fd == -1)
187                 return NULL;
188
189         file = g_file_new_for_path (file_name);
190         g_free (file_name);
191
192         g_object_set_data_full (G_OBJECT (file), "ev-mkstemp-fd",
193                                 GINT_TO_POINTER (fd), (GDestroyNotify) close_fd_cb);
194
195         return file;
196 }
197
198 /**
199  * ev_mkdtemp:
200  * @template: a template string; must end in 'XXXXXX'
201  * @error: a location to store a #GError
202  *
203  * Creates a temp directory in the evince temp directory.
204  *
205  * Returns: a newly allocated string with the temp directory name, or %NULL
206  *   on error with @error filled in
207  */
208 gchar *
209 ev_mkdtemp (const char        *template,
210             GError           **error)
211 {
212         const char *tmp;
213         char *name;
214
215         if ((tmp = _ev_tmp_dir (error)) == NULL)
216               return NULL;
217
218         name = g_build_filename (tmp, template, NULL);
219         if (mkdtemp (name) == NULL) {
220                 int errsv = errno;
221
222                 g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
223                              _("Failed to create a temporary directory: %s"),
224                              g_strerror (errsv));
225
226                 g_free (name);
227                 return NULL;
228         }
229
230         return name;
231 }
232
233 /* Remove a local temp file created by evince */
234 void
235 ev_tmp_filename_unlink (const gchar *filename)
236 {
237         const gchar *tempdir;
238         
239         if (!filename)
240                 return;
241
242         tempdir = g_get_tmp_dir ();
243         if (g_str_has_prefix (filename, tempdir) == 0) {
244                 g_unlink (filename);
245         }
246 }
247
248 void
249 ev_tmp_file_unlink (GFile *file)
250 {
251         gboolean res;
252         GError  *error = NULL;
253
254         if (!file)
255                 return;
256         
257         res = g_file_delete (file, NULL, &error);
258         if (!res) {
259                 char *uri;
260                 
261                 uri = g_file_get_uri (file);
262                 g_warning ("Unable to delete temp file %s: %s\n", uri, error->message);
263                 g_free (uri);
264                 g_error_free (error);
265         }
266 }
267
268 void
269 ev_tmp_uri_unlink (const gchar *uri)
270 {
271         GFile *file;
272         
273         if (!uri)
274                 return;
275         
276         file = g_file_new_for_uri (uri);
277         if (!g_file_is_native (file)) {
278                 g_warning ("Attempting to delete non native uri: %s\n", uri);
279                 g_object_unref (file);
280                 return;
281         }
282         
283         ev_tmp_file_unlink (file);
284         g_object_unref (file);
285 }
286
287 /**
288  * ev_xfer_uri_simple:
289  * @from: the source URI
290  * @to: the target URI
291  * @error: a #GError location to store an error, or %NULL
292  *
293  * Performs a g_file_copy() from @from to @to.
294  *
295  * Returns: %TRUE on success, or %FALSE on error with @error filled in
296  */
297 gboolean
298 ev_xfer_uri_simple (const char *from,
299                     const char *to,
300                     GError     **error)
301 {
302         GFile *source_file;
303         GFile *target_file;
304         gboolean result;
305         
306         if (!from)
307                 return TRUE;
308
309         g_return_val_if_fail (to != NULL, TRUE);
310
311         source_file = g_file_new_for_uri (from);
312         target_file = g_file_new_for_uri (to);
313         
314         result = g_file_copy (source_file, target_file,
315 #if GLIB_CHECK_VERSION(2,19,0)
316                               G_FILE_COPY_TARGET_DEFAULT_PERMS |
317 #endif
318                               G_FILE_COPY_OVERWRITE,
319                               NULL, NULL, NULL, error);
320
321         g_object_unref (target_file);
322         g_object_unref (source_file);
323     
324         return result;
325 }
326
327 static gchar *
328 get_mime_type_from_uri (const gchar *uri, GError **error)
329 {
330         GFile       *file;
331         GFileInfo   *file_info;
332         const gchar *content_type;
333         gchar       *mime_type = NULL;
334
335         file = g_file_new_for_uri (uri);
336         file_info = g_file_query_info (file,
337                                        G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
338                                        0, NULL, error);
339         g_object_unref (file);
340
341         if (file_info == NULL)
342                 return NULL;
343
344         content_type = g_file_info_get_content_type (file_info);
345         if (content_type) {
346                 mime_type = g_content_type_get_mime_type (content_type);
347         }
348
349         g_object_unref (file_info);
350         return mime_type;
351 }
352
353 static gchar *
354 get_mime_type_from_data (const gchar *uri, GError **error)
355 {
356         GFile            *file;
357         GFileInputStream *input_stream;
358         gssize            size_read;
359         guchar            buffer[1024];
360         gboolean          retval;
361         gchar            *content_type, *mime_type;
362
363         file = g_file_new_for_uri (uri);
364         
365         input_stream = g_file_read (file, NULL, error);
366         if (!input_stream) {
367                 g_object_unref (file);
368                 return NULL;
369         }
370
371         size_read = g_input_stream_read (G_INPUT_STREAM (input_stream),
372                                          buffer, sizeof (buffer), NULL, error);
373         if (size_read == -1) {
374                 g_object_unref (input_stream);
375                 g_object_unref (file);
376                 return NULL;
377         }
378
379         retval = g_input_stream_close (G_INPUT_STREAM (input_stream), NULL, error);
380
381         g_object_unref (input_stream);
382         g_object_unref (file);
383         if (!retval)
384                 return NULL;
385
386         content_type = g_content_type_guess (NULL, /* no filename */
387                                              buffer, size_read,
388                                              NULL);
389         if (!content_type)
390                 return NULL;
391
392         mime_type = g_content_type_get_mime_type (content_type);
393         g_free (content_type);
394         return mime_type;
395 }
396
397 /**
398  * ev_file_get_mime_type:
399  * @uri: the URI
400  * @fast: whether to use fast MIME type detection
401  * @error: a #GError location to store an error, or %NULL
402  *
403  * Note: on unknown MIME types, this may return NULL without @error
404  * being filled in.
405  * 
406  * Returns: a newly allocated string with the MIME type of the file at
407  *   @uri, or %NULL on error or if the MIME type could not be determined
408  */
409 gchar *
410 ev_file_get_mime_type (const gchar *uri,
411                        gboolean     fast,
412                        GError     **error)
413 {
414         return fast ? get_mime_type_from_uri (uri, error) : get_mime_type_from_data (uri, error);
415 }
416
417 /* Compressed files support */
418 #define BZIPCOMMAND "bzip2"
419 #define GZIPCOMMAND "gzip"
420 #define N_ARGS      4
421 #define BUFFER_SIZE 1024
422
423 static gchar *
424 compression_run (const gchar       *uri,
425                  EvCompressionType  type,
426                  gboolean           compress, 
427                  GError           **error)
428 {
429         gchar *argv[N_ARGS];
430         gchar *uri_dst = NULL;
431         gchar *filename, *filename_dst = NULL;
432         gchar *cmd;
433         gint   fd, pout;
434         GError *err = NULL;
435
436         if (type == EV_COMPRESSION_NONE)
437                 return NULL;
438
439         cmd = g_find_program_in_path ((type == EV_COMPRESSION_BZIP2) ? BZIPCOMMAND : GZIPCOMMAND);
440         if (!cmd) {
441                 /* FIXME: better error codes! */
442                 /* FIXME: i18n later */
443                 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
444                              "Failed to find the \"%s\" command in the search path.",
445                              type == EV_COMPRESSION_BZIP2 ? BZIPCOMMAND : GZIPCOMMAND);
446                 return NULL;
447         }
448
449         filename = g_filename_from_uri (uri, NULL, error);
450         if (!filename) {
451                 g_free (cmd);
452                 return NULL;
453         }
454
455         fd = ev_mkstemp ("comp.XXXXXX", &filename_dst, error);
456         if (fd == -1) {
457                 g_free (cmd);
458                 g_free (filename);
459
460                 return NULL;
461         }
462
463         argv[0] = cmd;
464         argv[1] = compress ? "-c" : "-cd";
465         argv[2] = filename;
466         argv[3] = NULL;
467
468         if (g_spawn_async_with_pipes (NULL, argv, NULL,
469                                       G_SPAWN_STDERR_TO_DEV_NULL,
470                                       NULL, NULL, NULL,
471                                       NULL, &pout, NULL, &err)) {
472                 GIOChannel *in, *out;
473                 gchar buf[BUFFER_SIZE];
474                 GIOStatus read_st, write_st;
475                 gsize bytes_read, bytes_written;
476
477                 in = g_io_channel_unix_new (pout);
478                 g_io_channel_set_encoding (in, NULL, NULL);
479                 out = g_io_channel_unix_new (fd);
480                 g_io_channel_set_encoding (out, NULL, NULL);
481
482                 do {
483                         read_st = g_io_channel_read_chars (in, buf,
484                                                            BUFFER_SIZE,
485                                                            &bytes_read,
486                                                            error);
487                         if (read_st == G_IO_STATUS_NORMAL) {
488                                 write_st = g_io_channel_write_chars (out, buf,
489                                                                      bytes_read,
490                                                                      &bytes_written,
491                                                                      error);
492                                 if (write_st == G_IO_STATUS_ERROR)
493                                         break;
494                         } else if (read_st == G_IO_STATUS_ERROR) {
495                                 break;
496                         }
497                 } while (bytes_read > 0);
498
499                 g_io_channel_unref (in);
500                 g_io_channel_unref (out);
501         }
502
503         close (fd);
504
505         if (err) {
506                 g_propagate_error (error, err);
507         } else {
508                 uri_dst = g_filename_to_uri (filename_dst, NULL, error);
509         }
510
511         g_free (cmd);
512         g_free (filename);
513         g_free (filename_dst);
514
515         return uri_dst;
516 }
517
518 /**
519  * ev_file_uncompress:
520  * @uri: a file URI
521  * @type: the compression type
522  * @error: a #GError location to store an error, or %NULL
523  *
524  * Uncompresses the file at @uri.
525  *
526  * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
527  *
528  * Otherwise, it returns the filename of a
529  * temporary file containing the decompressed data from the file at @uri.
530  * On error it returns %NULL and fills in @error.
531  *
532  * It is the caller's responsibility to unlink the temp file after use.
533  *
534  * Returns: a newly allocated string URI, or %NULL on error
535  */
536 gchar *
537 ev_file_uncompress (const gchar       *uri,
538                     EvCompressionType  type,
539                     GError           **error)
540 {
541         g_return_val_if_fail (uri != NULL, NULL);
542
543         return compression_run (uri, type, FALSE, error);
544 }
545
546 /**
547  * ev_file_compress:
548  * @uri: a file URI
549  * @type: the compression type
550  * @error: a #GError location to store an error, or %NULL
551  *
552  * Compresses the file at @uri.
553  
554  * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
555  *
556  * Otherwise, it returns the filename of a
557  * temporary file containing the compressed data from the file at @uri.
558  *
559  * On error it returns %NULL and fills in @error.
560  *
561  * It is the caller's responsibility to unlink the temp file after use.
562  *
563  * Returns: a newly allocated string URI, or %NULL on error
564  */
565 gchar *
566 ev_file_compress (const gchar       *uri,
567                   EvCompressionType  type,
568                   GError           **error)
569 {
570         g_return_val_if_fail (uri != NULL, NULL);
571
572         return compression_run (uri, type, TRUE, error);
573 }