/* * 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 * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #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 <glib/gi18n-lib.h> #include "ev-file-helpers.h" static gchar *tmp_dir = NULL; /* * 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 _ev_dir_ensure_exists (const gchar *dir, int mode, GError **error) { 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; 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; } /* * _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 (tmp_dir == NULL) { gchar *dirname, *prgname; 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); } if (!_ev_dir_ensure_exists (tmp_dir, 0700, error)) return NULL; return tmp_dir; } void _ev_file_helpers_init (void) { } void _ev_file_helpers_shutdown (void) { if (tmp_dir != NULL) g_rmdir (tmp_dir); g_free (tmp_dir); tmp_dir = NULL; } /** * 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 atril 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) { 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 atril 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 * atril' 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 atril 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 atril */ 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; 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) { GFile *source_file; GFile *target_file; gboolean result; if (!from) 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); 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); 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 */ static const char *compressor_cmds[] = { NULL, "bzip2", "gzip", "xz" }; #define N_ARGS 4 #define BUFFER_SIZE 1024 static gchar * compression_run (const gchar *uri, EvCompressionType type, gboolean compress, GError **error) { gchar *argv[N_ARGS]; gchar *uri_dst = NULL; 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 (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, error); if (!filename) { g_free (cmd); return NULL; } fd = ev_mkstemp ("comp.XXXXXX", &filename_dst, error); if (fd == -1) { g_free (cmd); g_free (filename); return NULL; } argv[0] = cmd; argv[1] = compress ? "-c" : "-cd"; argv[2] = filename; argv[3] = NULL; if (g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, &pout, NULL, &err)) { GIOChannel *in, *out; gchar buf[BUFFER_SIZE]; GIOStatus read_st, write_st; gsize bytes_read, bytes_written; in = g_io_channel_unix_new (pout); g_io_channel_set_encoding (in, NULL, NULL); out = g_io_channel_unix_new (fd); g_io_channel_set_encoding (out, NULL, NULL); do { read_st = g_io_channel_read_chars (in, buf, BUFFER_SIZE, &bytes_read, error); if (read_st == G_IO_STATUS_NORMAL) { write_st = g_io_channel_write_chars (out, buf, bytes_read, &bytes_written, error); if (write_st == G_IO_STATUS_ERROR) break; } else if (read_st == G_IO_STATUS_ERROR) { break; } } while (bytes_read > 0); g_io_channel_unref (in); g_io_channel_unref (out); } close (fd); if (err) { g_propagate_error (error, err); } else { uri_dst = g_filename_to_uri (filename_dst, NULL, error); } g_free (cmd); g_free (filename); g_free (filename_dst); 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, GError **error) { g_return_val_if_fail (uri != NULL, NULL); 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, GError **error) { g_return_val_if_fail (uri != NULL, NULL); return compression_run (uri, type, TRUE, error); }