]> www.fi.muni.cz Git - evince.git/blob - libdocument/ev-file-helpers.c
[dualscreen] fix crash on ctrl+w and fix control window closing
[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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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  * This function is copied from
200  * http://bugzilla.gnome.org/show_bug.cgi?id=524831
201  * and renamed from g_mkdtemp to _ev_g_mkdtemp.
202  *
203  * If/when this function gets added to glib, it can be removed from
204  * evince' sources.
205  *
206  *
207  * g_mkdtemp:
208  * @tmpl: template directory name
209  *
210  * Creates a temporary directory. See the mkdtemp() documentation
211  * on most UNIX-like systems.
212  *
213  * The parameter is a string that should follow the rules for
214  * mkdtemp() templates, i.e. contain the string "XXXXXX".  g_mkdtemp()
215  * is slightly more flexible than mkdtemp() in that the sequence does
216  * not have to occur at the very end of the template. The X string
217  * will be modified to form the name of a directory that didn't
218  * already exist.  The string should be in the GLib file name
219  * encoding. Most importantly, on Windows it should be in UTF-8.
220  *
221  * Return value: If a temporary directory was successfully created,
222  * @tmpl will be returned with the XXXXXX string modified in such a
223  * way as to make the path unique.  In case of errors, %NULL is
224  * returned.
225  */
226 static gchar *
227 _ev_g_mkdtemp (gchar *tmpl)
228 {
229   static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
230   static const int NLETTERS = sizeof (letters) - 1;
231   static int counter = 0;
232   char *XXXXXX;
233   GTimeVal tv;
234   glong value;
235   int count;
236
237   /* find the last occurrence of "XXXXXX" */
238   XXXXXX = g_strrstr (tmpl, "XXXXXX");
239
240   if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6))
241     {
242       errno = EINVAL;
243       return NULL;
244     }
245
246   /* Get some more or less random data.  */
247   g_get_current_time (&tv);
248   value = (tv.tv_usec ^ tv.tv_sec) + counter++;
249
250   for (count = 0; count < 100; value += 7777, ++count)
251     {
252       glong v = value;
253
254       /* Fill in the random bits.  */
255       XXXXXX[0] = letters[v % NLETTERS];
256       v /= NLETTERS;
257       XXXXXX[1] = letters[v % NLETTERS];
258       v /= NLETTERS;
259       XXXXXX[2] = letters[v % NLETTERS];
260       v /= NLETTERS;
261       XXXXXX[3] = letters[v % NLETTERS];
262       v /= NLETTERS;
263       XXXXXX[4] = letters[v % NLETTERS];
264       v /= NLETTERS;
265       XXXXXX[5] = letters[v % NLETTERS];
266
267       /* tmpl is in UTF-8 on Windows, thus use g_mkdir() */
268       if (g_mkdir (tmpl, 0700) == 0)
269         return tmpl;
270
271       if (errno != EEXIST)
272          /* Any other error will apply also to other names we might
273          *  try, and there are 2^32 or so of them, so give up now.
274          */
275          return NULL;
276     }
277
278   /* We got out of the loop because we ran out of combinations to try.  */
279   errno = EEXIST;
280   return NULL;
281 }
282
283 /**
284  * ev_mkdtemp:
285  * @template: a template string; must end in 'XXXXXX'
286  * @error: a location to store a #GError
287  *
288  * Creates a temp directory in the evince temp directory.
289  *
290  * Returns: a newly allocated string with the temp directory name, or %NULL
291  *   on error with @error filled in
292  */
293 gchar *
294 ev_mkdtemp (const char        *template,
295             GError           **error)
296 {
297         const char *tmp;
298         char *name;
299
300         if ((tmp = _ev_tmp_dir (error)) == NULL)
301               return NULL;
302
303         name = g_build_filename (tmp, template, NULL);
304         if (_ev_g_mkdtemp (name) == NULL) {
305                 int errsv = errno;
306
307                 g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
308                              _("Failed to create a temporary directory: %s"),
309                              g_strerror (errsv));
310
311                 g_free (name);
312                 return NULL;
313         }
314
315         return name;
316 }
317
318 /* Remove a local temp file created by evince */
319 void
320 ev_tmp_filename_unlink (const gchar *filename)
321 {
322         if (!filename)
323                 return;
324
325         if (!tmp_dir)
326                 return;
327
328         if (g_str_has_prefix (filename, tmp_dir)) {
329                 g_unlink (filename);
330         }
331 }
332
333 void
334 ev_tmp_file_unlink (GFile *file)
335 {
336         gboolean res;
337         GError  *error = NULL;
338
339         if (!file)
340                 return;
341         
342         res = g_file_delete (file, NULL, &error);
343         if (!res) {
344                 char *uri;
345                 
346                 uri = g_file_get_uri (file);
347                 g_warning ("Unable to delete temp file %s: %s\n", uri, error->message);
348                 g_free (uri);
349                 g_error_free (error);
350         }
351 }
352
353 void
354 ev_tmp_uri_unlink (const gchar *uri)
355 {
356         GFile *file;
357         
358         if (!uri)
359                 return;
360         
361         file = g_file_new_for_uri (uri);
362         if (!g_file_is_native (file)) {
363                 g_warning ("Attempting to delete non native uri: %s\n", uri);
364                 g_object_unref (file);
365                 return;
366         }
367         
368         ev_tmp_file_unlink (file);
369         g_object_unref (file);
370 }
371
372 gboolean
373 ev_file_is_temp (GFile *file)
374 {
375         gchar   *path;
376         gboolean retval;
377
378         if (!g_file_is_native (file))
379                 return FALSE;
380
381         path = g_file_get_path (file);
382         if (!path)
383                 return FALSE;
384
385         retval = g_str_has_prefix (path, g_get_tmp_dir ());
386         g_free (path);
387
388         return retval;
389 }
390
391 /**
392  * ev_xfer_uri_simple:
393  * @from: the source URI
394  * @to: the target URI
395  * @error: a #GError location to store an error, or %NULL
396  *
397  * Performs a g_file_copy() from @from to @to.
398  *
399  * Returns: %TRUE on success, or %FALSE on error with @error filled in
400  */
401 gboolean
402 ev_xfer_uri_simple (const char *from,
403                     const char *to,
404                     GError     **error)
405 {
406         GFile *source_file;
407         GFile *target_file;
408         gboolean result;
409         
410         if (!from)
411                 return TRUE;
412
413         g_return_val_if_fail (to != NULL, TRUE);
414
415         source_file = g_file_new_for_uri (from);
416         target_file = g_file_new_for_uri (to);
417         
418         result = g_file_copy (source_file, target_file,
419                               G_FILE_COPY_TARGET_DEFAULT_PERMS |
420                               G_FILE_COPY_OVERWRITE,
421                               NULL, NULL, NULL, error);
422
423         g_object_unref (target_file);
424         g_object_unref (source_file);
425     
426         return result;
427 }
428
429 static gchar *
430 get_mime_type_from_uri (const gchar *uri, GError **error)
431 {
432         GFile       *file;
433         GFileInfo   *file_info;
434         const gchar *content_type;
435         gchar       *mime_type = NULL;
436
437         file = g_file_new_for_uri (uri);
438         file_info = g_file_query_info (file,
439                                        G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
440                                        0, NULL, error);
441         g_object_unref (file);
442
443         if (file_info == NULL)
444                 return NULL;
445
446         content_type = g_file_info_get_content_type (file_info);
447         if (content_type) {
448                 mime_type = g_content_type_get_mime_type (content_type);
449         }
450
451         g_object_unref (file_info);
452         return mime_type;
453 }
454
455 static gchar *
456 get_mime_type_from_data (const gchar *uri, GError **error)
457 {
458 #ifndef G_OS_WIN32
459         GFile            *file;
460         GFileInputStream *input_stream;
461         gssize            size_read;
462         guchar            buffer[1024];
463         gboolean          retval;
464         gchar            *content_type, *mime_type;
465
466         file = g_file_new_for_uri (uri);
467         
468         input_stream = g_file_read (file, NULL, error);
469         if (!input_stream) {
470                 g_object_unref (file);
471                 return NULL;
472         }
473
474         size_read = g_input_stream_read (G_INPUT_STREAM (input_stream),
475                                          buffer, sizeof (buffer), NULL, error);
476         if (size_read == -1) {
477                 g_object_unref (input_stream);
478                 g_object_unref (file);
479                 return NULL;
480         }
481
482         retval = g_input_stream_close (G_INPUT_STREAM (input_stream), NULL, error);
483
484         g_object_unref (input_stream);
485         g_object_unref (file);
486         if (!retval)
487                 return NULL;
488
489         content_type = g_content_type_guess (NULL, /* no filename */
490                                              buffer, size_read,
491                                              NULL);
492         if (!content_type)
493                 return NULL;
494
495         mime_type = g_content_type_get_mime_type (content_type);
496         g_free (content_type);
497         return mime_type;
498 #else
499         /*
500          * On Windows, the implementation of g_content_type_guess() is too limited at the moment, so we do not
501          * use it and fall back to get_mime_type_from_uri()
502          */
503         return get_mime_type_from_uri (uri, error);
504 #endif /* G_OS_WIN32 */
505 }
506
507 /**
508  * ev_file_get_mime_type:
509  * @uri: the URI
510  * @fast: whether to use fast MIME type detection
511  * @error: a #GError location to store an error, or %NULL
512  *
513  * Note: on unknown MIME types, this may return NULL without @error
514  * being filled in.
515  * 
516  * Returns: a newly allocated string with the MIME type of the file at
517  *   @uri, or %NULL on error or if the MIME type could not be determined
518  */
519 gchar *
520 ev_file_get_mime_type (const gchar *uri,
521                        gboolean     fast,
522                        GError     **error)
523 {
524         return fast ? get_mime_type_from_uri (uri, error) : get_mime_type_from_data (uri, error);
525 }
526
527 /* Compressed files support */
528
529 static const char *compressor_cmds[] = {
530   NULL,
531   "bzip2",
532   "gzip",
533   "xz"
534 };
535
536 #define N_ARGS      4
537 #define BUFFER_SIZE 1024
538
539 static gchar *
540 compression_run (const gchar       *uri,
541                  EvCompressionType  type,
542                  gboolean           compress, 
543                  GError           **error)
544 {
545         gchar *argv[N_ARGS];
546         gchar *uri_dst = NULL;
547         gchar *filename, *filename_dst = NULL;
548         gchar *cmd;
549         gint   fd, pout;
550         GError *err = NULL;
551
552         if (type == EV_COMPRESSION_NONE)
553                 return NULL;
554
555         cmd = g_find_program_in_path (compressor_cmds[type]);
556         if (!cmd) {
557                 /* FIXME: better error codes! */
558                 /* FIXME: i18n later */
559                 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
560                              "Failed to find the \"%s\" command in the search path.",
561                              compressor_cmds[type]);
562                 return NULL;
563         }
564
565         filename = g_filename_from_uri (uri, NULL, error);
566         if (!filename) {
567                 g_free (cmd);
568                 return NULL;
569         }
570
571         fd = ev_mkstemp ("comp.XXXXXX", &filename_dst, error);
572         if (fd == -1) {
573                 g_free (cmd);
574                 g_free (filename);
575
576                 return NULL;
577         }
578
579         argv[0] = cmd;
580         argv[1] = compress ? "-c" : "-cd";
581         argv[2] = filename;
582         argv[3] = NULL;
583
584         if (g_spawn_async_with_pipes (NULL, argv, NULL,
585                                       G_SPAWN_STDERR_TO_DEV_NULL,
586                                       NULL, NULL, NULL,
587                                       NULL, &pout, NULL, &err)) {
588                 GIOChannel *in, *out;
589                 gchar buf[BUFFER_SIZE];
590                 GIOStatus read_st, write_st;
591                 gsize bytes_read, bytes_written;
592
593                 in = g_io_channel_unix_new (pout);
594                 g_io_channel_set_encoding (in, NULL, NULL);
595                 out = g_io_channel_unix_new (fd);
596                 g_io_channel_set_encoding (out, NULL, NULL);
597
598                 do {
599                         read_st = g_io_channel_read_chars (in, buf,
600                                                            BUFFER_SIZE,
601                                                            &bytes_read,
602                                                            error);
603                         if (read_st == G_IO_STATUS_NORMAL) {
604                                 write_st = g_io_channel_write_chars (out, buf,
605                                                                      bytes_read,
606                                                                      &bytes_written,
607                                                                      error);
608                                 if (write_st == G_IO_STATUS_ERROR)
609                                         break;
610                         } else if (read_st == G_IO_STATUS_ERROR) {
611                                 break;
612                         }
613                 } while (bytes_read > 0);
614
615                 g_io_channel_unref (in);
616                 g_io_channel_unref (out);
617         }
618
619         close (fd);
620
621         if (err) {
622                 g_propagate_error (error, err);
623         } else {
624                 uri_dst = g_filename_to_uri (filename_dst, NULL, error);
625         }
626
627         g_free (cmd);
628         g_free (filename);
629         g_free (filename_dst);
630
631         return uri_dst;
632 }
633
634 /**
635  * ev_file_uncompress:
636  * @uri: a file URI
637  * @type: the compression type
638  * @error: a #GError location to store an error, or %NULL
639  *
640  * Uncompresses the file at @uri.
641  *
642  * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
643  *
644  * Otherwise, it returns the filename of a
645  * temporary file containing the decompressed data from the file at @uri.
646  * On error it returns %NULL and fills in @error.
647  *
648  * It is the caller's responsibility to unlink the temp file after use.
649  *
650  * Returns: a newly allocated string URI, or %NULL on error
651  */
652 gchar *
653 ev_file_uncompress (const gchar       *uri,
654                     EvCompressionType  type,
655                     GError           **error)
656 {
657         g_return_val_if_fail (uri != NULL, NULL);
658
659         return compression_run (uri, type, FALSE, error);
660 }
661
662 /**
663  * ev_file_compress:
664  * @uri: a file URI
665  * @type: the compression type
666  * @error: a #GError location to store an error, or %NULL
667  *
668  * Compresses the file at @uri.
669  
670  * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
671  *
672  * Otherwise, it returns the filename of a
673  * temporary file containing the compressed data from the file at @uri.
674  *
675  * On error it returns %NULL and fills in @error.
676  *
677  * It is the caller's responsibility to unlink the temp file after use.
678  *
679  * Returns: a newly allocated string URI, or %NULL on error
680  */
681 gchar *
682 ev_file_compress (const gchar       *uri,
683                   EvCompressionType  type,
684                   GError           **error)
685 {
686         g_return_val_if_fail (uri != NULL, NULL);
687
688         return compression_run (uri, type, TRUE, error);
689 }