]> www.fi.muni.cz Git - evince.git/blobdiff - libdocument/ev-file-helpers.c
[dualscreen] fix crash on ctrl+w and fix control window closing
[evince.git] / libdocument / ev-file-helpers.c
index d9b6448418b1739e33196265d76a37f433e5cb89..cab0ad542c250e1b872bb3b4f875655e83d8a15b 100644 (file)
@@ -1,5 +1,6 @@
 /*
  *  Copyright (C) 2002 Jorn Baayen
+ *  Copyright © 2009 Christian Persch
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- *  $Id$
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
+#include <config.h>
 
 #include <stdlib.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <string.h>
+#include <errno.h>
+
 #include <glib.h>
 #include <glib/gstdio.h>
-#include <errno.h>
-#include <libgnomevfs/gnome-vfs-uri.h>
-#include <libgnomevfs/gnome-vfs-utils.h>
-#include <libgnomevfs/gnome-vfs-ops.h>
-#include <libgnomevfs/gnome-vfs-xfer.h>
-#include <libgnome/gnome-init.h>
+#include <glib/gi18n-lib.h>
 
 #include "ev-file-helpers.h"
 
-static gchar *dot_dir = NULL;
 static gchar *tmp_dir = NULL;
-static gint   count = 0;
 
+/*
+ * ev_dir_ensure_exists:
+ * @dir: the directory name
+ * @mode: permissions to use when creating the directory
+ * @error: a location to store a #GError
+ *
+ * Create @dir recursively with permissions @mode.
+ *
+ * Returns: %TRUE on success, or %FALSE on error with @error filled in
+ */
 static gboolean
-ensure_dir_exists (const char *dir)
+_ev_dir_ensure_exists (const gchar *dir,
+                       int          mode,
+                       GError     **error)
 {
-       if (g_file_test (dir, G_FILE_TEST_IS_DIR))
-               return TRUE;
-       
-       if (g_mkdir (dir, 488) == 0)
+        int errsv;
+        char *display_name;
+
+        g_return_val_if_fail (dir != NULL, FALSE);
+
+        errno = 0;
+       if (g_mkdir_with_parents (dir, mode) == 0)
                return TRUE;
 
-       if (errno == EEXIST)
-               return g_file_test (dir, G_FILE_TEST_IS_DIR);
-       
-       g_warning ("Failed to create directory %s: %s", dir, strerror (errno));
+        errsv = errno;
+       if (errsv == EEXIST && g_file_test (dir, G_FILE_TEST_IS_DIR))
+                return TRUE;
+
+        display_name = g_filename_display_name (dir);
+        g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                     "Failed to create directory '%s': %s",
+                     display_name, g_strerror (errsv));
+        g_free (display_name);
+
        return FALSE;
 }
 
-const gchar *
-ev_dot_dir (void)
+/*
+ * _ev_tmp_dir:
+ * @error: a location to store a #GError
+ *
+ * Returns the tmp directory.
+ *
+ * Returns: the tmp directory, or %NULL with @error filled in if the
+ *   directory could not be created
+ */
+static const char *
+_ev_tmp_dir (GError **error)
 {
-       if (dot_dir == NULL) {
-               gboolean exists;
-
-               dot_dir = g_build_filename (gnome_user_dir_get (),
-                                           "evince",
-                                           NULL);
 
-               exists = ensure_dir_exists (dot_dir);
-               if (!exists)
-                       exit (1);
-       }
+        if (tmp_dir == NULL) {
+                gchar *dirname, *prgname;
 
-       return dot_dir;
-}
+                prgname = g_get_prgname ();
+                dirname = g_strdup_printf ("%s-%u", prgname ? prgname : "unknown", getpid ());
+                tmp_dir = g_build_filename (g_get_tmp_dir (), dirname, NULL);
+                g_free (dirname);
+        }
 
-const gchar *
-ev_tmp_dir (void)
-{
-       if (tmp_dir == NULL) {
-               gboolean exists;
-               gchar   *dirname;
-
-               dirname = g_strdup_printf ("evince-%u", getpid ());
-               tmp_dir = g_build_filename (g_get_tmp_dir (),
-                                           dirname,
-                                           NULL);
-               g_free (dirname);
-
-               exists = ensure_dir_exists (tmp_dir);
-               g_assert (exists);
-       }
+        if (!_ev_dir_ensure_exists (tmp_dir, 0700, error))
+                return NULL;
 
        return tmp_dir;
 }
 
 void
-ev_file_helpers_init (void)
+_ev_file_helpers_init (void)
 {
 }
 
 void
-ev_file_helpers_shutdown (void)
+_ev_file_helpers_shutdown (void)
 {      
        if (tmp_dir != NULL)    
                g_rmdir (tmp_dir);
 
        g_free (tmp_dir);
-       g_free (dot_dir);
-
-       dot_dir = NULL;
        tmp_dir = NULL;
 }
 
-gchar * 
-ev_tmp_filename (const gchar *prefix)
+/**
+ * ev_mkstemp:
+ * @template: a template string; must contain 'XXXXXX', but not necessarily as a suffix
+ * @file_name: a location to store the filename of the temp file
+ * @error: a location to store a #GError
+ *
+ * Creates a temp file in the evince temp directory.
+ *
+ * Returns: a file descriptor to the newly created temp file name, or %-1
+ *   on error with @error filled in
+ */
+int
+ev_mkstemp (const char  *template,
+            char       **file_name,
+            GError     **error)
 {
-       gchar *basename;
-       gchar *filename = NULL;
-
-       do {
-               if (filename != NULL)
-                       g_free (filename);
-                       
-               basename = g_strdup_printf ("%s-%d",
-                                           prefix ? prefix : "document",
-                                           count ++);
-               
-               filename = g_build_filename (ev_tmp_dir (),
-                                            basename, NULL);
+        const char *tmp;
+        char *name;
+        int fd;
+
+        if ((tmp = _ev_tmp_dir (error)) == NULL)
+              return -1;
+
+        name = g_build_filename (tmp, template, NULL);
+        fd = g_mkstemp (name);
+
+        if (fd == -1) {
+               int errsv = errno;
+
+                g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                             _("Failed to create a temporary file: %s"),
+                             g_strerror (errsv));
+
+                g_free (name);
+                return -1;
+        }
+
+        if (file_name)
+                *file_name = name;
+
+        return fd;
+}
+
+static void
+close_fd_cb (gpointer fdptr)
+{
+        int fd = GPOINTER_TO_INT (fdptr);
+
+        close (fd);
+}
+
+/**
+ * ev_mkstemp_file:
+ * @template: a template string; must contain 'XXXXXX', but not necessarily as a suffix
+ * @error: a location to store a #GError
+ *
+ * Creates a temp #GFile in the evince temp directory. See ev_mkstemp() for more information.
+ *
+ * Returns: a newly allocated #GFile for the newly created temp file name, or %NULL
+ *   on error with @error filled in
+ */
+GFile *
+ev_mkstemp_file (const char        *template,
+                 GError           **error)
+{
+        char *file_name;
+        int fd;
+        GFile *file;
+
+        fd = ev_mkstemp (template, &file_name, error);
+        if (fd == -1)
+                return NULL;
+
+        file = g_file_new_for_path (file_name);
+        g_free (file_name);
+
+        g_object_set_data_full (G_OBJECT (file), "ev-mkstemp-fd",
+                                GINT_TO_POINTER (fd), (GDestroyNotify) close_fd_cb);
+
+        return file;
+}
+
+/**
+ * This function is copied from
+ * http://bugzilla.gnome.org/show_bug.cgi?id=524831
+ * and renamed from g_mkdtemp to _ev_g_mkdtemp.
+ *
+ * If/when this function gets added to glib, it can be removed from
+ * evince' sources.
+ *
+ *
+ * g_mkdtemp:
+ * @tmpl: template directory name
+ *
+ * Creates a temporary directory. See the mkdtemp() documentation
+ * on most UNIX-like systems.
+ *
+ * The parameter is a string that should follow the rules for
+ * mkdtemp() templates, i.e. contain the string "XXXXXX".  g_mkdtemp()
+ * is slightly more flexible than mkdtemp() in that the sequence does
+ * not have to occur at the very end of the template. The X string
+ * will be modified to form the name of a directory that didn't
+ * already exist.  The string should be in the GLib file name
+ * encoding. Most importantly, on Windows it should be in UTF-8.
+ *
+ * Return value: If a temporary directory was successfully created,
+ * @tmpl will be returned with the XXXXXX string modified in such a
+ * way as to make the path unique.  In case of errors, %NULL is
+ * returned.
+ */
+static gchar *
+_ev_g_mkdtemp (gchar *tmpl)
+{
+  static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+  static const int NLETTERS = sizeof (letters) - 1;
+  static int counter = 0;
+  char *XXXXXX;
+  GTimeVal tv;
+  glong value;
+  int count;
+
+  /* find the last occurrence of "XXXXXX" */
+  XXXXXX = g_strrstr (tmpl, "XXXXXX");
+
+  if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6))
+    {
+      errno = EINVAL;
+      return NULL;
+    }
+
+  /* Get some more or less random data.  */
+  g_get_current_time (&tv);
+  value = (tv.tv_usec ^ tv.tv_sec) + counter++;
+
+  for (count = 0; count < 100; value += 7777, ++count)
+    {
+      glong v = value;
+
+      /* Fill in the random bits.  */
+      XXXXXX[0] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[1] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[2] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[3] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[4] = letters[v % NLETTERS];
+      v /= NLETTERS;
+      XXXXXX[5] = letters[v % NLETTERS];
+
+      /* tmpl is in UTF-8 on Windows, thus use g_mkdir() */
+      if (g_mkdir (tmpl, 0700) == 0)
+        return tmpl;
+
+      if (errno != EEXIST)
+         /* Any other error will apply also to other names we might
+         *  try, and there are 2^32 or so of them, so give up now.
+         */
+         return NULL;
+    }
+
+  /* We got out of the loop because we ran out of combinations to try.  */
+  errno = EEXIST;
+  return NULL;
+}
+
+/**
+ * ev_mkdtemp:
+ * @template: a template string; must end in 'XXXXXX'
+ * @error: a location to store a #GError
+ *
+ * Creates a temp directory in the evince temp directory.
+ *
+ * Returns: a newly allocated string with the temp directory name, or %NULL
+ *   on error with @error filled in
+ */
+gchar *
+ev_mkdtemp (const char        *template,
+            GError           **error)
+{
+        const char *tmp;
+        char *name;
+
+        if ((tmp = _ev_tmp_dir (error)) == NULL)
+              return NULL;
+
+        name = g_build_filename (tmp, template, NULL);
+        if (_ev_g_mkdtemp (name) == NULL) {
+               int errsv = errno;
+
+                g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                             _("Failed to create a temporary directory: %s"),
+                             g_strerror (errsv));
+
+                g_free (name);
+                return NULL;
+        }
+
+        return name;
+}
+
+/* Remove a local temp file created by evince */
+void
+ev_tmp_filename_unlink (const gchar *filename)
+{
+       if (!filename)
+               return;
+
+       if (!tmp_dir)
+               return;
+
+       if (g_str_has_prefix (filename, tmp_dir)) {
+               g_unlink (filename);
+       }
+}
+
+void
+ev_tmp_file_unlink (GFile *file)
+{
+       gboolean res;
+       GError  *error = NULL;
+
+       if (!file)
+               return;
+       
+       res = g_file_delete (file, NULL, &error);
+       if (!res) {
+               char *uri;
                
-               g_free (basename);
-       } while (g_file_test (filename, G_FILE_TEST_EXISTS));
-                       
-       return filename;
+               uri = g_file_get_uri (file);
+               g_warning ("Unable to delete temp file %s: %s\n", uri, error->message);
+               g_free (uri);
+               g_error_free (error);
+       }
+}
+
+void
+ev_tmp_uri_unlink (const gchar *uri)
+{
+       GFile *file;
+       
+       if (!uri)
+               return;
+       
+       file = g_file_new_for_uri (uri);
+       if (!g_file_is_native (file)) {
+               g_warning ("Attempting to delete non native uri: %s\n", uri);
+               g_object_unref (file);
+               return;
+       }
+       
+       ev_tmp_file_unlink (file);
+       g_object_unref (file);
+}
+
+gboolean
+ev_file_is_temp (GFile *file)
+{
+       gchar   *path;
+       gboolean retval;
+
+       if (!g_file_is_native (file))
+               return FALSE;
+
+       path = g_file_get_path (file);
+       if (!path)
+               return FALSE;
+
+       retval = g_str_has_prefix (path, g_get_tmp_dir ());
+       g_free (path);
+
+       return retval;
 }
 
+/**
+ * ev_xfer_uri_simple:
+ * @from: the source URI
+ * @to: the target URI
+ * @error: a #GError location to store an error, or %NULL
+ *
+ * Performs a g_file_copy() from @from to @to.
+ *
+ * Returns: %TRUE on success, or %FALSE on error with @error filled in
+ */
 gboolean
 ev_xfer_uri_simple (const char *from,
                    const char *to,
                    GError     **error)
 {
-       GnomeVFSResult result;
-       GnomeVFSURI *source_uri;
-       GnomeVFSURI *target_uri;
+       GFile *source_file;
+       GFile *target_file;
+       gboolean result;
        
        if (!from)
-               return FALSE;
+               return TRUE;
+
+        g_return_val_if_fail (to != NULL, TRUE);
+
+       source_file = g_file_new_for_uri (from);
+       target_file = g_file_new_for_uri (to);
        
-       source_uri = gnome_vfs_uri_new (from);
-       target_uri = gnome_vfs_uri_new (to);
-
-       result = gnome_vfs_xfer_uri (source_uri, target_uri, 
-                                    GNOME_VFS_XFER_DEFAULT | GNOME_VFS_XFER_FOLLOW_LINKS,
-                                    GNOME_VFS_XFER_ERROR_MODE_ABORT,
-                                    GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE,
-                                    NULL,
-                                    NULL);
-       gnome_vfs_uri_unref (target_uri);
-       gnome_vfs_uri_unref (source_uri);
+       result = g_file_copy (source_file, target_file,
+                             G_FILE_COPY_TARGET_DEFAULT_PERMS |
+                             G_FILE_COPY_OVERWRITE,
+                             NULL, NULL, NULL, error);
+
+       g_object_unref (target_file);
+       g_object_unref (source_file);
     
-       if (result != GNOME_VFS_OK)
-               g_set_error (error,
-                            G_FILE_ERROR,
-                            G_FILE_ERROR_FAILED,
-                            gnome_vfs_result_to_string (result));
-       return (result == GNOME_VFS_OK);
+       return result;
+}
+
+static gchar *
+get_mime_type_from_uri (const gchar *uri, GError **error)
+{
+       GFile       *file;
+       GFileInfo   *file_info;
+       const gchar *content_type;
+        gchar       *mime_type = NULL;
+
+       file = g_file_new_for_uri (uri);
+       file_info = g_file_query_info (file,
+                                      G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+                                      0, NULL, error);
+       g_object_unref (file);
+
+       if (file_info == NULL)
+               return NULL;
 
+       content_type = g_file_info_get_content_type (file_info);
+       if (content_type) {
+                mime_type = g_content_type_get_mime_type (content_type);
+        }
+
+       g_object_unref (file_info);
+       return mime_type;
+}
+
+static gchar *
+get_mime_type_from_data (const gchar *uri, GError **error)
+{
+#ifndef G_OS_WIN32
+       GFile            *file;
+       GFileInputStream *input_stream;
+       gssize            size_read;
+       guchar            buffer[1024];
+       gboolean          retval;
+       gchar            *content_type, *mime_type;
+
+       file = g_file_new_for_uri (uri);
+       
+       input_stream = g_file_read (file, NULL, error);
+       if (!input_stream) {
+               g_object_unref (file);
+               return NULL;
+       }
+
+       size_read = g_input_stream_read (G_INPUT_STREAM (input_stream),
+                                        buffer, sizeof (buffer), NULL, error);
+       if (size_read == -1) {
+               g_object_unref (input_stream);
+               g_object_unref (file);
+               return NULL;
+       }
+
+       retval = g_input_stream_close (G_INPUT_STREAM (input_stream), NULL, error);
+
+       g_object_unref (input_stream);
+       g_object_unref (file);
+       if (!retval)
+               return NULL;
+
+       content_type = g_content_type_guess (NULL, /* no filename */
+                                            buffer, size_read,
+                                            NULL);
+       if (!content_type)
+               return NULL;
+
+       mime_type = g_content_type_get_mime_type (content_type);
+       g_free (content_type);
+       return mime_type;
+#else
+       /*
+        * On Windows, the implementation of g_content_type_guess() is too limited at the moment, so we do not
+        * use it and fall back to get_mime_type_from_uri()
+        */
+       return get_mime_type_from_uri (uri, error);
+#endif /* G_OS_WIN32 */
+}
+
+/**
+ * ev_file_get_mime_type:
+ * @uri: the URI
+ * @fast: whether to use fast MIME type detection
+ * @error: a #GError location to store an error, or %NULL
+ *
+ * Note: on unknown MIME types, this may return NULL without @error
+ * being filled in.
+ * 
+ * Returns: a newly allocated string with the MIME type of the file at
+ *   @uri, or %NULL on error or if the MIME type could not be determined
+ */
+gchar *
+ev_file_get_mime_type (const gchar *uri,
+                      gboolean     fast,
+                      GError     **error)
+{
+       return fast ? get_mime_type_from_uri (uri, error) : get_mime_type_from_data (uri, error);
 }
 
 /* Compressed files support */
-#define BZIPCOMMAND "bzip2"
-#define GZIPCOMMAND "gzip"
+
+static const char *compressor_cmds[] = {
+  NULL,
+  "bzip2",
+  "gzip",
+  "xz"
+};
+
 #define N_ARGS      4
 #define BUFFER_SIZE 1024
 
@@ -183,29 +544,35 @@ compression_run (const gchar       *uri,
 {
        gchar *argv[N_ARGS];
        gchar *uri_dst = NULL;
-       gchar *filename, *filename_dst;
+       gchar *filename, *filename_dst = NULL;
        gchar *cmd;
        gint   fd, pout;
+       GError *err = NULL;
 
        if (type == EV_COMPRESSION_NONE)
                return NULL;
 
-       cmd = g_find_program_in_path ((type == EV_COMPRESSION_BZIP2) ? BZIPCOMMAND : GZIPCOMMAND);
-       if (!cmd)
+       cmd = g_find_program_in_path (compressor_cmds[type]);
+       if (!cmd) {
+               /* FIXME: better error codes! */
+               /* FIXME: i18n later */
+               g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                            "Failed to find the \"%s\" command in the search path.",
+                             compressor_cmds[type]);
                return NULL;
+       }
 
-       filename = g_filename_from_uri (uri, NULL, NULL);
+       filename = g_filename_from_uri (uri, NULL, error);
        if (!filename) {
                g_free (cmd);
                return NULL;
        }
-       
-       filename_dst = g_build_filename (ev_tmp_dir (), "evinceXXXXXX", NULL);
-       fd = g_mkstemp (filename_dst);
-       if (fd < 0) {
+
+        fd = ev_mkstemp ("comp.XXXXXX", &filename_dst, error);
+       if (fd == -1) {
                g_free (cmd);
                g_free (filename);
-               g_free (filename_dst);
+
                return NULL;
        }
 
@@ -217,7 +584,7 @@ compression_run (const gchar       *uri,
        if (g_spawn_async_with_pipes (NULL, argv, NULL,
                                      G_SPAWN_STDERR_TO_DEV_NULL,
                                      NULL, NULL, NULL,
-                                     NULL, &pout, NULL, error)) {
+                                     NULL, &pout, NULL, &err)) {
                GIOChannel *in, *out;
                gchar buf[BUFFER_SIZE];
                GIOStatus read_st, write_st;
@@ -251,8 +618,10 @@ compression_run (const gchar       *uri,
 
        close (fd);
 
-       if (*error == NULL) {
-               uri_dst = g_filename_to_uri (filename_dst, NULL, NULL);
+       if (err) {
+               g_propagate_error (error, err);
+       } else {
+               uri_dst = g_filename_to_uri (filename_dst, NULL, error);
        }
 
        g_free (cmd);
@@ -262,6 +631,24 @@ compression_run (const gchar       *uri,
        return uri_dst;
 }
 
+/**
+ * ev_file_uncompress:
+ * @uri: a file URI
+ * @type: the compression type
+ * @error: a #GError location to store an error, or %NULL
+ *
+ * Uncompresses the file at @uri.
+ *
+ * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
+ *
+ * Otherwise, it returns the filename of a
+ * temporary file containing the decompressed data from the file at @uri.
+ * On error it returns %NULL and fills in @error.
+ *
+ * It is the caller's responsibility to unlink the temp file after use.
+ *
+ * Returns: a newly allocated string URI, or %NULL on error
+ */
 gchar *
 ev_file_uncompress (const gchar       *uri,
                    EvCompressionType  type,
@@ -272,6 +659,25 @@ ev_file_uncompress (const gchar       *uri,
        return compression_run (uri, type, FALSE, error);
 }
 
+/**
+ * ev_file_compress:
+ * @uri: a file URI
+ * @type: the compression type
+ * @error: a #GError location to store an error, or %NULL
+ *
+ * Compresses the file at @uri.
+ * If @type is %EV_COMPRESSION_NONE, it does nothing and returns %NULL.
+ *
+ * Otherwise, it returns the filename of a
+ * temporary file containing the compressed data from the file at @uri.
+ *
+ * On error it returns %NULL and fills in @error.
+ *
+ * It is the caller's responsibility to unlink the temp file after use.
+ *
+ * Returns: a newly allocated string URI, or %NULL on error
+ */
 gchar *
 ev_file_compress (const gchar       *uri,
                  EvCompressionType  type,