summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorErnestas Kulik <ernestask@gnome.org>2018-04-28 13:21:50 +0300
committerErnestas Kulik <ernestask@gnome.org>2018-05-22 16:52:48 +0300
commit673c81cf9f1d68b71041220e6e44624dee44dbfc (patch)
tree85637603bbf6e2333ae7a0bbcb52068ca655ad8d /src
parent0aca206f8a47c3b35d3d0bc2964e5f672d7a3794 (diff)
downloadnautilus-673c81cf9f1d68b71041220e6e44624dee44dbfc.tar.gz
general: Copy gnome-desktop thumbnailing code
This is one of prerequisite steps to take before fully switching to GTK+ 4, as gnome-desktop has code, depending on GTK+ 3. Since the thumbnailing machinery is self-contained, it can easily be just copied over.
Diffstat (limited to 'src')
-rw-r--r--src/gnome-desktop/gnome-desktop-thumbnail-script.c832
-rw-r--r--src/gnome-desktop/gnome-desktop-thumbnail-script.h38
-rw-r--r--src/gnome-desktop/gnome-desktop-thumbnail.c1301
-rw-r--r--src/gnome-desktop/gnome-desktop-thumbnail.h102
-rw-r--r--src/meson.build7
-rw-r--r--src/nautilus-properties-window.c2
-rw-r--r--src/nautilus-thumbnails.c2
7 files changed, 2281 insertions, 3 deletions
diff --git a/src/gnome-desktop/gnome-desktop-thumbnail-script.c b/src/gnome-desktop/gnome-desktop-thumbnail-script.c
new file mode 100644
index 000000000..14e2fed3a
--- /dev/null
+++ b/src/gnome-desktop/gnome-desktop-thumbnail-script.c
@@ -0,0 +1,832 @@
+/*
+ * Copyright (C) 2002, 2017 Red Hat, Inc.
+ * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org>
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Authors: Alexander Larsson <alexl@redhat.com>
+ * Carlos Garcia Campos <carlosgc@gnome.org>
+ * Bastien Nocera <hadess@hadess.net>
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#ifdef ENABLE_SECCOMP
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/utsname.h>
+#include <seccomp.h>
+#endif
+
+#include "gnome-desktop-thumbnail-script.h"
+
+typedef struct {
+ gboolean sandbox;
+ char *thumbnailer_name;
+ GArray *fd_array;
+ /* Input/output file paths outside the sandbox */
+ char *infile;
+ char *infile_tmp; /* the host version of /tmp/gnome-desktop-file-to-thumbnail.* */
+ char *outfile;
+ char *outdir; /* outdir is outfile's parent dir, if it needs to be deleted */
+ /* I/O file paths inside the sandbox */
+ char *s_infile;
+ char *s_outfile;
+} ScriptExec;
+
+static char *
+expand_thumbnailing_elem (const char *elem,
+ const int size,
+ const char *infile,
+ const char *outfile,
+ gboolean *got_input,
+ gboolean *got_output)
+{
+ GString *str;
+ const char *p, *last;
+ char *inuri;
+
+ str = g_string_new (NULL);
+
+ last = elem;
+ while ((p = strchr (last, '%')) != NULL)
+ {
+ g_string_append_len (str, last, p - last);
+ p++;
+
+ switch (*p) {
+ case 'u':
+ inuri = g_filename_to_uri (infile, NULL, NULL);
+ if (inuri)
+ {
+ g_string_append (str, inuri);
+ *got_input = TRUE;
+ g_free (inuri);
+ }
+ p++;
+ break;
+ case 'i':
+ g_string_append (str, infile);
+ *got_input = TRUE;
+ p++;
+ break;
+ case 'o':
+ g_string_append (str, outfile);
+ *got_output = TRUE;
+ p++;
+ break;
+ case 's':
+ g_string_append_printf (str, "%d", size);
+ p++;
+ break;
+ case '%':
+ g_string_append_c (str, '%');
+ p++;
+ break;
+ case 0:
+ default:
+ break;
+ }
+ last = p;
+ }
+ g_string_append (str, last);
+
+ return g_string_free (str, FALSE);
+}
+
+/* From https://github.com/flatpak/flatpak/blob/master/common/flatpak-run.c */
+G_GNUC_NULL_TERMINATED
+static void
+add_args (GPtrArray *argv_array, ...)
+{
+ va_list args;
+ const gchar *arg;
+
+ va_start (args, argv_array);
+ while ((arg = va_arg (args, const gchar *)))
+ g_ptr_array_add (argv_array, g_strdup (arg));
+ va_end (args);
+}
+
+static void
+add_env (GPtrArray *array,
+ const char *envvar)
+{
+ if (g_getenv (envvar) != NULL)
+ add_args (array,
+ "--setenv", envvar, g_getenv (envvar),
+ NULL);
+}
+
+static char *
+get_extension (const char *path)
+{
+ g_autofree char *basename = NULL;
+ char *p;
+
+ basename = g_path_get_basename (path);
+ p = strrchr (basename, '.');
+ if (p == NULL)
+ return NULL;
+ return g_strdup (p + 1);
+}
+
+#ifdef ENABLE_SECCOMP
+static gboolean
+flatpak_fail (GError **error,
+ const char *msg,
+ ...)
+{
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, msg);
+ return FALSE;
+}
+
+/* From https://github.com/flatpak/flatpak/blob/master/common/flatpak-utils.c */
+#if !defined(__i386__) && !defined(__x86_64__) && !defined(__aarch64__) && !defined(__arm__)
+static const char *
+flatpak_get_kernel_arch (void)
+{
+ static struct utsname buf;
+ static char *arch = NULL;
+ char *m;
+
+ if (arch != NULL)
+ return arch;
+
+ if (uname (&buf))
+ {
+ arch = "unknown";
+ return arch;
+ }
+
+ /* By default, just pass on machine, good enough for most arches */
+ arch = buf.machine;
+
+ /* Override for some arches */
+
+ m = buf.machine;
+ /* i?86 */
+ if (strlen (m) == 4 && m[0] == 'i' && m[2] == '8' && m[3] == '6')
+ {
+ arch = "i386";
+ }
+ else if (g_str_has_prefix (m, "arm"))
+ {
+ if (g_str_has_suffix (m, "b"))
+ arch = "armeb";
+ else
+ arch = "arm";
+ }
+ else if (strcmp (m, "mips") == 0)
+ {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ arch = "mipsel";
+#endif
+ }
+ else if (strcmp (m, "mips64") == 0)
+ {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ arch = "mips64el";
+#endif
+ }
+
+ return arch;
+}
+#endif
+
+/* This maps the kernel-reported uname to a single string representing
+ * the cpu family, in the sense that all members of this family would
+ * be able to understand and link to a binary file with such cpu
+ * opcodes. That doesn't necessarily mean that all members of the
+ * family can run all opcodes, for instance for modern 32bit intel we
+ * report "i386", even though they support instructions that the
+ * original i386 cpu cannot run. Still, such an executable would
+ * at least try to execute a 386, whereas an arm binary would not.
+ */
+static const char *
+flatpak_get_arch (void)
+{
+ /* Avoid using uname on multiarch machines, because uname reports the kernels
+ * arch, and that may be different from userspace. If e.g. the kernel is 64bit and
+ * the userspace is 32bit we want to use 32bit by default. So, we take the current build
+ * arch as the default. */
+#if defined(__i386__)
+ return "i386";
+#elif defined(__x86_64__)
+ return "x86_64";
+#elif defined(__aarch64__)
+ return "aarch64";
+#elif defined(__arm__)
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ return "arm";
+#else
+ return "armeb";
+#endif
+#else
+ return flatpak_get_kernel_arch ();
+#endif
+}
+
+/* From https://github.com/flatpak/flatpak/blob/master/common/flatpak-run.c */
+static const uint32_t seccomp_x86_64_extra_arches[] = { SCMP_ARCH_X86, 0, };
+
+#ifdef SCMP_ARCH_AARCH64
+static const uint32_t seccomp_aarch64_extra_arches[] = { SCMP_ARCH_ARM, 0 };
+#endif
+
+static inline void
+cleanup_seccomp (void *p)
+{
+ scmp_filter_ctx *pp = (scmp_filter_ctx *) p;
+
+ if (*pp)
+ seccomp_release (*pp);
+}
+
+static gboolean
+setup_seccomp (GPtrArray *argv_array,
+ GArray *fd_array,
+ const char *arch,
+ gboolean multiarch,
+ gboolean devel,
+ GError **error)
+{
+ __attribute__((cleanup (cleanup_seccomp))) scmp_filter_ctx seccomp = NULL;
+
+ /**** BEGIN NOTE ON CODE SHARING
+ *
+ * There are today a number of different Linux container
+ * implementations. That will likely continue for long into the
+ * future. But we can still try to share code, and it's important
+ * to do so because it affects what library and application writers
+ * can do, and we should support code portability between different
+ * container tools.
+ *
+ * This syscall blacklist is copied from linux-user-chroot, which was in turn
+ * clearly influenced by the Sandstorm.io blacklist.
+ *
+ * If you make any changes here, I suggest sending the changes along
+ * to other sandbox maintainers. Using the libseccomp list is also
+ * an appropriate venue:
+ * https://groups.google.com/forum/#!topic/libseccomp
+ *
+ * A non-exhaustive list of links to container tooling that might
+ * want to share this blacklist:
+ *
+ * https://github.com/sandstorm-io/sandstorm
+ * in src/sandstorm/supervisor.c++
+ * http://cgit.freedesktop.org/xdg-app/xdg-app/
+ * in common/flatpak-run.c
+ * https://git.gnome.org/browse/linux-user-chroot
+ * in src/setup-seccomp.c
+ *
+ **** END NOTE ON CODE SHARING
+ */
+ struct
+ {
+ int scall;
+ struct scmp_arg_cmp *arg;
+ } syscall_blacklist[] = {
+ /* Block dmesg */
+ {SCMP_SYS (syslog)},
+ /* Useless old syscall */
+ {SCMP_SYS (uselib)},
+ /* Don't allow you to switch to bsd emulation or whatnot */
+ {SCMP_SYS (personality)},
+ /* Don't allow disabling accounting */
+ {SCMP_SYS (acct)},
+ /* 16-bit code is unnecessary in the sandbox, and modify_ldt is a
+ historic source of interesting information leaks. */
+ {SCMP_SYS (modify_ldt)},
+ /* Don't allow reading current quota use */
+ {SCMP_SYS (quotactl)},
+
+ /* Don't allow access to the kernel keyring */
+ {SCMP_SYS (add_key)},
+ {SCMP_SYS (keyctl)},
+ {SCMP_SYS (request_key)},
+
+ /* Scary VM/NUMA ops */
+ {SCMP_SYS (move_pages)},
+ {SCMP_SYS (mbind)},
+ {SCMP_SYS (get_mempolicy)},
+ {SCMP_SYS (set_mempolicy)},
+ {SCMP_SYS (migrate_pages)},
+
+ /* Don't allow subnamespace setups: */
+ {SCMP_SYS (unshare)},
+ {SCMP_SYS (mount)},
+ {SCMP_SYS (pivot_root)},
+ {SCMP_SYS (clone), &SCMP_A0 (SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)},
+
+ /* Don't allow faking input to the controlling tty (CVE-2017-5226) */
+ {SCMP_SYS (ioctl), &SCMP_A1(SCMP_CMP_EQ, (int)TIOCSTI)},
+ };
+
+ struct
+ {
+ int scall;
+ struct scmp_arg_cmp *arg;
+ } syscall_nondevel_blacklist[] = {
+ /* Profiling operations; we expect these to be done by tools from outside
+ * the sandbox. In particular perf has been the source of many CVEs.
+ */
+ {SCMP_SYS (perf_event_open)},
+ {SCMP_SYS (ptrace)}
+ };
+ /* Blacklist all but unix, inet, inet6 and netlink */
+ int socket_family_blacklist[] = {
+ AF_AX25,
+ AF_IPX,
+ AF_APPLETALK,
+ AF_NETROM,
+ AF_BRIDGE,
+ AF_ATMPVC,
+ AF_X25,
+ AF_ROSE,
+ AF_DECnet,
+ AF_NETBEUI,
+ AF_SECURITY,
+ AF_KEY,
+ AF_NETLINK + 1, /* Last gets CMP_GE, so order is important */
+ };
+ guint i;
+ int r;
+ int fd = -1;
+ g_autofree char *fd_str = NULL;
+ g_autofree char *path = NULL;
+
+ seccomp = seccomp_init (SCMP_ACT_ALLOW);
+ if (!seccomp)
+ return flatpak_fail (error, "Initialize seccomp failed");
+
+ if (arch != NULL)
+ {
+ uint32_t arch_id = 0;
+ const uint32_t *extra_arches = NULL;
+
+ if (strcmp (arch, "i386") == 0)
+ {
+ arch_id = SCMP_ARCH_X86;
+ }
+ else if (strcmp (arch, "x86_64") == 0)
+ {
+ arch_id = SCMP_ARCH_X86_64;
+ extra_arches = seccomp_x86_64_extra_arches;
+ }
+ else if (strcmp (arch, "arm") == 0)
+ {
+ arch_id = SCMP_ARCH_ARM;
+ }
+#ifdef SCMP_ARCH_AARCH64
+ else if (strcmp (arch, "aarch64") == 0)
+ {
+ arch_id = SCMP_ARCH_AARCH64;
+ extra_arches = seccomp_aarch64_extra_arches;
+ }
+#endif
+
+ /* We only really need to handle arches on multiarch systems.
+ * If only one arch is supported the default is fine */
+ if (arch_id != 0)
+ {
+ /* This *adds* the target arch, instead of replacing the
+ native one. This is not ideal, because we'd like to only
+ allow the target arch, but we can't really disallow the
+ native arch at this point, because then bubblewrap
+ couldn't continue running. */
+ r = seccomp_arch_add (seccomp, arch_id);
+ if (r < 0 && r != -EEXIST)
+ return flatpak_fail (error, "Failed to add architecture to seccomp filter");
+
+ if (multiarch && extra_arches != NULL)
+ {
+ for (i = 0; extra_arches[i] != 0; i++)
+ {
+ r = seccomp_arch_add (seccomp, extra_arches[i]);
+ if (r < 0 && r != -EEXIST)
+ return flatpak_fail (error, "Failed to add multiarch architecture to seccomp filter");
+ }
+ }
+ }
+ }
+
+ /* TODO: Should we filter the kernel keyring syscalls in some way?
+ * We do want them to be used by desktop apps, but they could also perhaps
+ * leak system stuff or secrets from other apps.
+ */
+
+ for (i = 0; i < G_N_ELEMENTS (syscall_blacklist); i++)
+ {
+ int scall = syscall_blacklist[i].scall;
+ if (syscall_blacklist[i].arg)
+ r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_blacklist[i].arg);
+ else
+ r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0);
+ if (r < 0 && r == -EFAULT /* unknown syscall */)
+ return flatpak_fail (error, "Failed to block syscall %d", scall);
+ }
+
+ if (!devel)
+ {
+ for (i = 0; i < G_N_ELEMENTS (syscall_nondevel_blacklist); i++)
+ {
+ int scall = syscall_nondevel_blacklist[i].scall;
+ if (syscall_nondevel_blacklist[i].arg)
+ r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_nondevel_blacklist[i].arg);
+ else
+ r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0);
+
+ if (r < 0 && r == -EFAULT /* unknown syscall */)
+ return flatpak_fail (error, "Failed to block syscall %d", scall);
+ }
+ }
+
+ /* Socket filtering doesn't work on e.g. i386, so ignore failures here
+ * However, we need to user seccomp_rule_add_exact to avoid libseccomp doing
+ * something else: https://github.com/seccomp/libseccomp/issues/8 */
+ for (i = 0; i < G_N_ELEMENTS (socket_family_blacklist); i++)
+ {
+ int family = socket_family_blacklist[i];
+ if (i == G_N_ELEMENTS (socket_family_blacklist) - 1)
+ seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_GE, family));
+ else
+ seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_EQ, family));
+ }
+
+ fd = g_file_open_tmp ("flatpak-seccomp-XXXXXX", &path, error);
+ if (fd == -1)
+ return FALSE;
+
+ unlink (path);
+
+ if (seccomp_export_bpf (seccomp, fd) != 0)
+ {
+ close (fd);
+ return flatpak_fail (error, "Failed to export bpf");
+ }
+
+ lseek (fd, 0, SEEK_SET);
+
+ fd_str = g_strdup_printf ("%d", fd);
+ if (fd_array)
+ g_array_append_val (fd_array, fd);
+
+ add_args (argv_array,
+ "--seccomp", fd_str,
+ NULL);
+
+ fd = -1; /* Don't close on success */
+
+ return TRUE;
+}
+#endif
+
+#ifdef HAVE_BWRAP
+static gboolean
+add_bwrap (GPtrArray *array,
+ ScriptExec *script)
+{
+ g_return_val_if_fail (script->outdir != NULL, FALSE);
+ g_return_val_if_fail (script->s_infile != NULL, FALSE);
+
+ add_args (array,
+ "bwrap",
+ "--ro-bind", "/usr", "/usr",
+ "--ro-bind", "/lib", "/lib",
+ "--ro-bind", "/lib64", "/lib64",
+ "--proc", "/proc",
+ "--dev", "/dev",
+ "--symlink", "usr/bin", "/bin",
+ "--symlink", "usr/sbin", "/sbin",
+ "--chdir", "/",
+ "--setenv", "GIO_USE_VFS", "local",
+ "--unshare-all",
+ "--die-with-parent",
+ NULL);
+
+ add_env (array, "G_MESSAGES_DEBUG");
+ add_env (array, "G_MESSAGES_PREFIXED");
+
+ /* Add gnome-desktop's install prefix if needed */
+ if (g_strcmp0 (INSTALL_PREFIX, "") != 0 &&
+ g_strcmp0 (INSTALL_PREFIX, "/usr") != 0 &&
+ g_strcmp0 (INSTALL_PREFIX, "/usr/") != 0)
+ {
+ add_args (array,
+ "--ro-bind", INSTALL_PREFIX, INSTALL_PREFIX,
+ NULL);
+ }
+
+ g_ptr_array_add (array, g_strdup ("--bind"));
+ g_ptr_array_add (array, g_strdup (script->outdir));
+ g_ptr_array_add (array, g_strdup ("/tmp"));
+
+ /* We make sure to also re-use the original file's original
+ * extension in case it's useful for the thumbnailer to
+ * identify the file type */
+ g_ptr_array_add (array, g_strdup ("--ro-bind"));
+ g_ptr_array_add (array, g_strdup (script->infile));
+ g_ptr_array_add (array, g_strdup (script->s_infile));
+
+ return TRUE;
+}
+#endif /* HAVE_BWRAP */
+
+static char **
+expand_thumbnailing_cmd (const char *cmd,
+ ScriptExec *script,
+ int size,
+ GError **error)
+{
+ GPtrArray *array;
+ g_auto(GStrv) cmd_elems = NULL;
+ guint i;
+ gboolean got_in, got_out;
+
+ if (!g_shell_parse_argv (cmd, NULL, &cmd_elems, error))
+ return NULL;
+
+ script->thumbnailer_name = g_strdup (cmd_elems[0]);
+
+ array = g_ptr_array_new_with_free_func (g_free);
+
+#ifdef HAVE_BWRAP
+ if (script->sandbox)
+ {
+ if (!add_bwrap (array, script))
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Bubblewrap setup failed");
+ goto bail;
+ }
+ }
+#endif
+
+#ifdef ENABLE_SECCOMP
+ if (script->sandbox)
+ {
+ const char *arch;
+
+ arch = flatpak_get_arch ();
+ g_assert (arch);
+ if (!setup_seccomp (array,
+ script->fd_array,
+ arch,
+ FALSE,
+ FALSE,
+ error))
+ {
+ goto bail;
+ }
+ }
+#endif
+
+ got_in = got_out = FALSE;
+ for (i = 0; cmd_elems[i] != NULL; i++)
+ {
+ char *expanded;
+
+ expanded = expand_thumbnailing_elem (cmd_elems[i],
+ size,
+ script->s_infile ? script->s_infile : script->infile,
+ script->s_outfile ? script->s_outfile : script->outfile,
+ &got_in,
+ &got_out);
+
+ g_ptr_array_add (array, expanded);
+ }
+
+ if (!got_in)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Input file could not be set");
+ goto bail;
+ }
+ else if (!got_out)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Output file could not be set");
+ goto bail;
+ }
+
+ g_ptr_array_add (array, NULL);
+
+ return (char **) g_ptr_array_free (array, FALSE);
+
+bail:
+ g_ptr_array_free (array, TRUE);
+ return NULL;
+}
+
+static void
+child_setup (gpointer user_data)
+{
+ GArray *fd_array = user_data;
+ guint i;
+
+ /* If no fd_array was specified, don't care. */
+ if (fd_array == NULL)
+ return;
+
+ /* Otherwise, mark not - close-on-exec all the fds in the array */
+ for (i = 0; i < fd_array->len; i++)
+ fcntl (g_array_index (fd_array, int, i), F_SETFD, 0);
+}
+
+static void
+script_exec_free (ScriptExec *exec)
+{
+ if (exec == NULL)
+ return;
+
+ g_free (exec->thumbnailer_name);
+ g_free (exec->infile);
+ if (exec->infile_tmp)
+ {
+ g_unlink (exec->infile_tmp);
+ g_free (exec->infile_tmp);
+ }
+ if (exec->outfile)
+ {
+ g_unlink (exec->outfile);
+ g_free (exec->outfile);
+ }
+ if (exec->outdir)
+ {
+ if (g_rmdir (exec->outdir) < 0)
+ {
+ g_warning ("Could not remove %s, thumbnailer %s left files in directory",
+ exec->outdir, exec->thumbnailer_name);
+ }
+ g_free (exec->outdir);
+ }
+ g_free (exec->s_infile);
+ g_free (exec->s_outfile);
+ if (exec->fd_array)
+ g_array_free (exec->fd_array, TRUE);
+ g_free (exec);
+}
+
+static void
+clear_fd (gpointer data)
+{
+ int *fd_p = data;
+ if (fd_p != NULL && *fd_p != -1)
+ close (*fd_p);
+}
+
+static ScriptExec *
+script_exec_new (const char *uri,
+ GError **error)
+{
+ ScriptExec *exec;
+ g_autoptr(GFile) file = NULL;
+
+ exec = g_new0 (ScriptExec, 1);
+#ifdef HAVE_BWRAP
+ /* Bubblewrap is not used if the application is already sandboxed in
+ * Flatpak as all privileges to create a new namespace are dropped when
+ * the initial one is created. */
+ if (!g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR))
+ exec->sandbox = TRUE;
+#endif
+
+ file = g_file_new_for_uri (uri);
+
+ exec->infile = g_file_get_path (file);
+ if (!exec->infile)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Could not get path for URI '%s'", uri);
+ goto bail;
+ }
+
+#ifdef HAVE_BWRAP
+ if (exec->sandbox)
+ {
+ char *tmpl;
+ g_autofree char *ext = NULL;
+ g_autofree char *infile = NULL;
+
+ exec->fd_array = g_array_new (FALSE, TRUE, sizeof (int));
+ g_array_set_clear_func (exec->fd_array, clear_fd);
+
+ tmpl = g_strdup ("/tmp/gnome-desktop-thumbnailer-XXXXXX");
+ exec->outdir = g_mkdtemp (tmpl);
+ if (!exec->outdir)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Could not create temporary sandbox directory");
+ goto bail;
+ }
+ exec->outfile = g_build_filename (exec->outdir, "gnome-desktop-thumbnailer.png", NULL);
+ ext = get_extension (exec->infile);
+ infile = g_strdup_printf ("gnome-desktop-file-to-thumbnail.%s", ext);
+ exec->infile_tmp = g_build_filename (exec->outdir, infile, NULL);
+
+ exec->s_infile = g_build_filename ("/tmp/", infile, NULL);
+ exec->s_outfile = g_build_filename ("/tmp/", "gnome-desktop-thumbnailer.png", NULL);
+ }
+ else
+#endif
+ {
+ int fd;
+ g_autofree char *tmpname = NULL;
+
+ fd = g_file_open_tmp (".gnome_desktop_thumbnail.XXXXXX", &tmpname, error);
+ if (fd == -1)
+ goto bail;
+ close (fd);
+ exec->outfile = g_steal_pointer (&tmpname);
+ }
+
+ return exec;
+
+bail:
+ script_exec_free (exec);
+ return NULL;
+}
+
+static void
+print_script_debug (GStrv expanded_script)
+{
+ GString *out;
+ guint i;
+
+ out = g_string_new (NULL);
+
+ for (i = 0; expanded_script[i]; i++)
+ g_string_append_printf (out, "%s ", expanded_script[i]);
+ g_string_append_printf (out, "\n");
+
+ g_debug ("About to launch script: %s", out->str);
+ g_string_free (out, TRUE);
+}
+
+GBytes *
+gnome_desktop_thumbnail_script_exec (const char *cmd,
+ int size,
+ const char *uri,
+ GError **error)
+{
+ g_autofree char *error_out = NULL;
+ g_auto(GStrv) expanded_script = NULL;
+ int exit_status;
+ gboolean ret;
+ GBytes *image = NULL;
+ ScriptExec *exec;
+
+ exec = script_exec_new (uri, error);
+ if (!exec)
+ goto out;
+ expanded_script = expand_thumbnailing_cmd (cmd, exec, size, error);
+ if (expanded_script == NULL)
+ goto out;
+
+ print_script_debug (expanded_script);
+
+ ret = g_spawn_sync (NULL, expanded_script, NULL, G_SPAWN_SEARCH_PATH,
+ child_setup, exec->fd_array, NULL, &error_out,
+ &exit_status, error);
+ if (ret && g_spawn_check_exit_status (exit_status, error))
+ {
+ char *contents;
+ gsize length;
+
+ if (g_file_get_contents (exec->outfile, &contents, &length, error))
+ image = g_bytes_new_take (contents, length);
+ }
+ else
+ {
+ g_debug ("Failed to launch script: %s", !ret ? (*error)->message : error_out);
+ }
+
+out:
+ script_exec_free (exec);
+ return image;
+}
+
diff --git a/src/gnome-desktop/gnome-desktop-thumbnail-script.h b/src/gnome-desktop/gnome-desktop-thumbnail-script.h
new file mode 100644
index 000000000..cbd6bbf67
--- /dev/null
+++ b/src/gnome-desktop/gnome-desktop-thumbnail-script.h
@@ -0,0 +1,38 @@
+/*
+ * gnome-thumbnail.h: Utilities for handling thumbnails
+ *
+ * Copyright (C) 2002, 2017 Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Authors: Alexander Larsson <alexl@redhat.com>
+ * Bastien Nocera <hadess@hadess.net>
+ */
+
+#ifndef GNOME_DESKTOP_THUMBNAIL_SCRIPT_H
+#define GNOME_DESKTOP_THUMBNAIL_SCRIPT_H
+
+#include <glib.h>
+
+GBytes *
+gnome_desktop_thumbnail_script_exec (const char *cmd,
+ int size,
+ const char *uri,
+ GError **error);
+
+#endif /* GNOME_DESKTOP_THUMBNAIL_SCRIPT_H */
diff --git a/src/gnome-desktop/gnome-desktop-thumbnail.c b/src/gnome-desktop/gnome-desktop-thumbnail.c
new file mode 100644
index 000000000..b31bad58d
--- /dev/null
+++ b/src/gnome-desktop/gnome-desktop-thumbnail.c
@@ -0,0 +1,1301 @@
+/*
+ * gnome-thumbnail.c: Utilities for handling thumbnails
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org>
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ */
+
+/**
+ * SECTION:gnome-desktop-thumbnail
+ * @short_description: Generates and looks up thumbnails of files and
+ * directories
+ * @stability: Unstable
+ * @include: libgnome-desktop/gnome-desktop-thumbnail.h
+ *
+ * #GnomeDesktopThumbnailFactory allows generation and loading of thumbnails for
+ * local and remote files and directories. It uses a collection of programs
+ * called <firstterm>thumbnailers</firstterm>, each one generating thumbnails
+ * for a specific set of content-types of files. For example,
+ * <application>totem-video-thumbnailer</application> generates thumbnails for
+ * video files using GStreamer; <application>evince-thumbnailer</application>
+ * generates thumbnails for PDFs and other document files. If no specific
+ * thumbnailer exists for a file, or if the thumbnailer fails, gdk-pixbuf is
+ * used as a fallback.
+ *
+ * To generate a thumbnail, an appropriate thumbnailer program is selected then
+ * executed, passing it the URI of the file to thumbnail, plus a path to write
+ * the thumbnail image to. If thumbnailing succeeds, the thumbnailer should have
+ * written the image to disk before terminating; but if thumbnailing fails, no
+ * image should be written, and the thumbnailer should return a non-zero exit
+ * status. #GnomeDesktopThumbnailFactory will then fall back to using gdk-pixbuf
+ * to generate a thumbnail, if possible.
+ *
+ * Thumbnailers are chosen by examining a series of
+ * <filename>.thumbnailer</filename> files in
+ * <filename><replaceable>$PREFIX</replaceable>/share/thumbnailers</filename>.
+ * Each is in a simple key-file format:
+ * <informalexample><programlisting>
+ * [Thumbnailer Entry]
+ * Exec=evince-thumbnailer -s %s %u %o
+ * MimeType=application/pdf;application/x-bzpdf;application/x-gzpdf;
+ * </programlisting></informalexample>
+ *
+ * The <filename>.thumbnailer</filename> format supports three keys:
+ * <variablelist>
+ * <varlistentry><term><code>Exec</code></term><listitem><para>
+ * Required. The command to execute the thumbnailer. It supports a few different
+ * parameters which are replaced before calling the thumbnailer:
+ * <replaceable>%u</replaceable> is the URI of the file being thumbnailed;
+ * <replaceable>%i</replaceable> is its path; <replaceable>%o</replaceable>
+ * is the path of the image file to be written to;
+ * <replaceable>%s</replaceable> is the maximum desired size of the thumbnail
+ * image (the maximum width or height, in pixels); and
+ * <replaceable>%%</replaceable> is a literal percent character.
+ * </para></listitem></varlistentry>
+ * <varlistentry><term><code>MimeType</code></term><listitem><para>
+ * Required. A semicolon-separated list of MIME types which the thumbnailer
+ * supports generating thumbnails for.
+ * </para></listitem></varlistentry>
+ * </variablelist>
+ *
+ * So in the example <filename>.thumbnailer</filename> file above, the command
+ * passes the requested thumbnail size, then the input file’s URI, then the
+ * path for the output image file to
+ * <application>evince-thumbnailer</application>.
+ *
+ * The code to examine and call a thumbnailer is contained in
+ * #GnomeDesktopThumbnailFactory, which handles looking up the right thumbnailer
+ * script, building and executing the command for it, and loading the resulting
+ * thumbnail image into a #GdkPixbuf.
+ *
+ * Thumbnail caching is also supported by #GnomeDesktopThumbnailFactory. When
+ * calling a thumbnailer, the path passed for the output image file is in
+ * <filename><envar>$XDG_CACHE_HOME</envar>/thumbnails/
+ * <replaceable>$SIZE</replaceable>/</filename>. The cached image file is given
+ * a (probably) unique filename, generated by hashing the original file’s URI,
+ * so the thumbnail can be looked up in future. #GnomeDesktopThumbnailFactory
+ * supports two sizes of thumbnails: %GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL and
+ * %GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE. Normal thumbnails are up to 128×128
+ * pixels, whereas large thumbnails are up to 256×256 pixels. Thumbnails which
+ * are larger than this are scaled down before being cached, and non-square
+ * thumbnails are scaled so their largest dimension is at most 128 or 256
+ * pixels.
+ *
+ * #GnomeDesktopThumbnailFactory also handles failed thumbnails. If a
+ * thumbnailer can’t generate a thumbnail for a file (e.g. because the file is
+ * corrupt or because the right video codecs aren’t available), it returns a
+ * non-zero exit status. The thumbnail factory then writes an entry to
+ * <filename><envar>$XDG_CACHE_HOME</envar>/thumbnails/fail/
+ * gnome-thumbnail-factory/</filename> which is named after the hash of the
+ * input file URI (just like a successful cached thumbnail). For future queries
+ * for thumbnails for that file, #GnomeDesktopThumbnailFactory can immediately
+ * return an error after looking up the fail entry.
+ *
+ * If a file changes content, #GnomeDesktopThumbnailFactory will generate a new
+ * thumbnail because each cached image has associated metadata (stored as PNG
+ * tEXt keys) storing the full URI of the thumbnailed file (to check for hash
+ * collisions) and its last modification time at the point of thumbnailing. If
+ * the stored modification time doesn’t match the file’s current one, a new
+ * thumbnail is generated.
+ *
+ * Since: 2.2
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include "gnome-desktop-thumbnail.h"
+#include "gnome-desktop-thumbnail-script.h"
+
+static void
+thumbnailers_directory_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ GnomeDesktopThumbnailFactory *factory);
+
+struct _GnomeDesktopThumbnailFactoryPrivate {
+ GnomeDesktopThumbnailSize size;
+
+ GMutex lock;
+
+ GList *thumbnailers;
+ GHashTable *mime_types_map;
+ GList *monitors;
+
+ GSettings *settings;
+ gboolean loaded : 1;
+ gboolean disabled : 1;
+ gchar **disabled_types;
+};
+
+static const char *appname = "gnome-thumbnail-factory";
+
+G_DEFINE_TYPE (GnomeDesktopThumbnailFactory,
+ gnome_desktop_thumbnail_factory,
+ G_TYPE_OBJECT)
+#define parent_class gnome_desktop_thumbnail_factory_parent_class
+
+#define GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryPrivate))
+
+#define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
+#define THUMBNAILER_EXTENSION ".thumbnailer"
+
+typedef struct {
+ volatile gint ref_count;
+
+ gchar *path;
+
+ gchar *command;
+ gchar **mime_types;
+} Thumbnailer;
+
+static Thumbnailer *
+thumbnailer_ref (Thumbnailer *thumb)
+{
+ g_return_val_if_fail (thumb != NULL, NULL);
+ g_return_val_if_fail (thumb->ref_count > 0, NULL);
+
+ g_atomic_int_inc (&thumb->ref_count);
+ return thumb;
+}
+
+static void
+thumbnailer_unref (Thumbnailer *thumb)
+{
+ g_return_if_fail (thumb != NULL);
+ g_return_if_fail (thumb->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&thumb->ref_count))
+ {
+ g_free (thumb->path);
+ g_free (thumb->command);
+ g_strfreev (thumb->mime_types);
+
+ g_slice_free (Thumbnailer, thumb);
+ }
+}
+
+static Thumbnailer *
+thumbnailer_load (Thumbnailer *thumb)
+{
+ GKeyFile *key_file;
+ GError *error = NULL;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (key_file, thumb->path, 0, &error))
+ {
+ g_warning ("Failed to load thumbnailer from \"%s\": %s\n", thumb->path, error->message);
+ g_error_free (error);
+ thumbnailer_unref (thumb);
+ g_key_file_free (key_file);
+
+ return NULL;
+ }
+
+ if (!g_key_file_has_group (key_file, THUMBNAILER_ENTRY_GROUP))
+ {
+ g_warning ("Invalid thumbnailer: missing group \"%s\"\n", THUMBNAILER_ENTRY_GROUP);
+ thumbnailer_unref (thumb);
+ g_key_file_free (key_file);
+
+ return NULL;
+ }
+
+ thumb->command = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL);
+ if (!thumb->command)
+ {
+ g_warning ("Invalid thumbnailer: missing Exec key\n");
+ thumbnailer_unref (thumb);
+ g_key_file_free (key_file);
+
+ return NULL;
+ }
+
+ thumb->mime_types = g_key_file_get_string_list (key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL);
+ if (!thumb->mime_types)
+ {
+ g_warning ("Invalid thumbnailer: missing MimeType key\n");
+ thumbnailer_unref (thumb);
+ g_key_file_free (key_file);
+
+ return NULL;
+ }
+
+ g_key_file_free (key_file);
+
+ return thumb;
+}
+
+static Thumbnailer *
+thumbnailer_reload (Thumbnailer *thumb)
+{
+ g_return_val_if_fail (thumb != NULL, NULL);
+
+ g_free (thumb->command);
+ thumb->command = NULL;
+ g_strfreev (thumb->mime_types);
+ thumb->mime_types = NULL;
+
+ return thumbnailer_load (thumb);
+}
+
+static Thumbnailer *
+thumbnailer_new (const gchar *path)
+{
+ Thumbnailer *thumb;
+
+ thumb = g_slice_new0 (Thumbnailer);
+ thumb->ref_count = 1;
+ thumb->path = g_strdup (path);
+
+ return thumbnailer_load (thumb);
+}
+
+static gpointer
+init_thumbnailers_dirs (gpointer data)
+{
+ const gchar * const *data_dirs;
+ GPtrArray *thumbs_dirs;
+ guint i;
+
+ data_dirs = g_get_system_data_dirs ();
+ thumbs_dirs = g_ptr_array_new ();
+
+ g_ptr_array_add (thumbs_dirs, g_build_filename (g_get_user_data_dir (), "thumbnailers", NULL));
+ for (i = 0; data_dirs[i] != NULL; i++)
+ g_ptr_array_add (thumbs_dirs, g_build_filename (data_dirs[i], "thumbnailers", NULL));
+ g_ptr_array_add (thumbs_dirs, NULL);
+
+ return g_ptr_array_free (thumbs_dirs, FALSE);
+}
+
+static const gchar * const *
+get_thumbnailers_dirs (void)
+{
+ static GOnce once_init = G_ONCE_INIT;
+ return g_once (&once_init, init_thumbnailers_dirs, NULL);
+}
+
+/* These should be called with the lock held */
+static void
+gnome_desktop_thumbnail_factory_register_mime_types (GnomeDesktopThumbnailFactory *factory,
+ Thumbnailer *thumb)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ gint i;
+
+ for (i = 0; thumb->mime_types[i]; i++)
+ {
+ if (!g_hash_table_lookup (priv->mime_types_map, thumb->mime_types[i]))
+ g_hash_table_insert (priv->mime_types_map,
+ g_strdup (thumb->mime_types[i]),
+ thumbnailer_ref (thumb));
+ }
+}
+
+static void
+gnome_desktop_thumbnail_factory_add_thumbnailer (GnomeDesktopThumbnailFactory *factory,
+ Thumbnailer *thumb)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+ gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
+ priv->thumbnailers = g_list_prepend (priv->thumbnailers, thumb);
+}
+
+static gboolean
+gnome_desktop_thumbnail_factory_is_disabled (GnomeDesktopThumbnailFactory *factory,
+ const gchar *mime_type)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ guint i;
+
+ if (priv->disabled)
+ return TRUE;
+
+ if (!priv->disabled_types)
+ return FALSE;
+
+ for (i = 0; priv->disabled_types[i]; i++)
+ {
+ if (g_strcmp0 (priv->disabled_types[i], mime_type) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+remove_thumbnailer_from_mime_type_map (gchar *key,
+ Thumbnailer *value,
+ gchar *path)
+{
+ return (strcmp (value->path, path) == 0);
+}
+
+static void
+update_or_create_thumbnailer (GnomeDesktopThumbnailFactory *factory,
+ const gchar *path)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ GList *l;
+ Thumbnailer *thumb;
+ gboolean found = FALSE;
+
+ g_mutex_lock (&priv->lock);
+
+ for (l = priv->thumbnailers; l && !found; l = g_list_next (l))
+ {
+ thumb = (Thumbnailer *)l->data;
+
+ if (strcmp (thumb->path, path) == 0)
+ {
+ found = TRUE;
+
+ /* First remove the mime_types associated to this thumbnailer */
+ g_hash_table_foreach_remove (priv->mime_types_map,
+ (GHRFunc)remove_thumbnailer_from_mime_type_map,
+ (gpointer)path);
+ if (!thumbnailer_reload (thumb))
+ priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
+ else
+ gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
+ }
+ }
+
+ if (!found)
+ {
+ thumb = thumbnailer_new (path);
+ if (thumb)
+ gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
+ }
+
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+remove_thumbnailer (GnomeDesktopThumbnailFactory *factory,
+ const gchar *path)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ GList *l;
+ Thumbnailer *thumb;
+
+ g_mutex_lock (&priv->lock);
+
+ for (l = priv->thumbnailers; l; l = g_list_next (l))
+ {
+ thumb = (Thumbnailer *)l->data;
+
+ if (strcmp (thumb->path, path) == 0)
+ {
+ priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
+ g_hash_table_foreach_remove (priv->mime_types_map,
+ (GHRFunc)remove_thumbnailer_from_mime_type_map,
+ (gpointer)path);
+ thumbnailer_unref (thumb);
+
+ break;
+ }
+ }
+
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+remove_thumbnailers_for_dir (GnomeDesktopThumbnailFactory *factory,
+ const gchar *thumbnailer_dir,
+ GFileMonitor *monitor)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ GList *l;
+ Thumbnailer *thumb;
+
+ g_mutex_lock (&priv->lock);
+
+ /* Remove all the thumbnailers inside this @thumbnailer_dir. */
+ for (l = priv->thumbnailers; l; l = g_list_next (l))
+ {
+ thumb = (Thumbnailer *)l->data;
+
+ if (g_str_has_prefix (thumb->path, thumbnailer_dir) == TRUE)
+ {
+ priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
+ g_hash_table_foreach_remove (priv->mime_types_map,
+ (GHRFunc)remove_thumbnailer_from_mime_type_map,
+ (gpointer)thumb->path);
+ thumbnailer_unref (thumb);
+
+ break;
+ }
+ }
+
+ /* Remove the monitor for @thumbnailer_dir. */
+ priv->monitors = g_list_remove (priv->monitors, monitor);
+ g_signal_handlers_disconnect_by_func (monitor, thumbnailers_directory_changed, factory);
+
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+gnome_desktop_thumbnail_factory_load_thumbnailers_for_dir (GnomeDesktopThumbnailFactory *factory,
+ const gchar *path)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ GDir *dir;
+ GFile *dir_file;
+ GFileMonitor *monitor;
+ const gchar *dirent;
+
+ dir = g_dir_open (path, 0, NULL);
+ if (!dir)
+ return;
+
+ /* Monitor dir */
+ dir_file = g_file_new_for_path (path);
+ monitor = g_file_monitor_directory (dir_file,
+ G_FILE_MONITOR_NONE,
+ NULL, NULL);
+ if (monitor)
+ {
+ g_signal_connect (monitor, "changed",
+ G_CALLBACK (thumbnailers_directory_changed),
+ factory);
+ priv->monitors = g_list_prepend (priv->monitors, monitor);
+ }
+ g_object_unref (dir_file);
+
+ while ((dirent = g_dir_read_name (dir)))
+ {
+ Thumbnailer *thumb;
+ gchar *filename;
+
+ if (!g_str_has_suffix (dirent, THUMBNAILER_EXTENSION))
+ continue;
+
+ filename = g_build_filename (path, dirent, NULL);
+ thumb = thumbnailer_new (filename);
+ g_free (filename);
+
+ if (thumb)
+ gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
+ }
+
+ g_dir_close (dir);
+}
+
+static void
+thumbnailers_directory_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ GnomeDesktopThumbnailFactory *factory)
+{
+ gchar *path;
+
+ switch (event_type)
+ {
+ case G_FILE_MONITOR_EVENT_CREATED:
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ case G_FILE_MONITOR_EVENT_DELETED:
+ path = g_file_get_path (file);
+ if (!g_str_has_suffix (path, THUMBNAILER_EXTENSION))
+ {
+ g_free (path);
+ return;
+ }
+
+ if (event_type == G_FILE_MONITOR_EVENT_DELETED)
+ remove_thumbnailer (factory, path);
+ else
+ update_or_create_thumbnailer (factory, path);
+
+ g_free (path);
+ break;
+ case G_FILE_MONITOR_EVENT_UNMOUNTED:
+ case G_FILE_MONITOR_EVENT_MOVED:
+ path = g_file_get_path (file);
+ remove_thumbnailers_for_dir (factory, path, monitor);
+
+ if (event_type == G_FILE_MONITOR_EVENT_MOVED)
+ gnome_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, path);
+
+ g_free (path);
+ break;
+ case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+ case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+ case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+ case G_FILE_MONITOR_EVENT_RENAMED:
+ case G_FILE_MONITOR_EVENT_MOVED_IN:
+ case G_FILE_MONITOR_EVENT_MOVED_OUT:
+ default:
+ break;
+ }
+}
+
+static void
+gnome_desktop_thumbnail_factory_load_thumbnailers (GnomeDesktopThumbnailFactory *factory)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ const gchar * const *dirs;
+ guint i;
+
+ if (priv->loaded)
+ return;
+
+ dirs = get_thumbnailers_dirs ();
+ for (i = 0; dirs[i]; i++)
+ {
+ gnome_desktop_thumbnail_factory_load_thumbnailers_for_dir (factory, dirs[i]);
+ }
+
+ priv->loaded = TRUE;
+}
+
+static void
+external_thumbnailers_disabled_all_changed_cb (GSettings *settings,
+ const gchar *key,
+ GnomeDesktopThumbnailFactory *factory)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+ g_mutex_lock (&priv->lock);
+
+ priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
+ if (priv->disabled)
+ {
+ g_strfreev (priv->disabled_types);
+ priv->disabled_types = NULL;
+ }
+ else
+ {
+ priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
+ gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
+ }
+
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+external_thumbnailers_disabled_changed_cb (GSettings *settings,
+ const gchar *key,
+ GnomeDesktopThumbnailFactory *factory)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+ g_mutex_lock (&priv->lock);
+
+ if (!priv->disabled)
+ {
+ g_strfreev (priv->disabled_types);
+ priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
+ }
+
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+gnome_desktop_thumbnail_factory_init (GnomeDesktopThumbnailFactory *factory)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv;
+
+ factory->priv = GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE (factory);
+
+ priv = factory->priv;
+
+ priv->size = GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL;
+
+ priv->mime_types_map = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)thumbnailer_unref);
+
+ g_mutex_init (&priv->lock);
+
+ priv->settings = g_settings_new ("org.gnome.desktop.thumbnailers");
+ priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
+ if (!priv->disabled)
+ priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
+ g_signal_connect (priv->settings, "changed::disable-all",
+ G_CALLBACK (external_thumbnailers_disabled_all_changed_cb),
+ factory);
+ g_signal_connect (priv->settings, "changed::disable",
+ G_CALLBACK (external_thumbnailers_disabled_changed_cb),
+ factory);
+
+ if (!priv->disabled)
+ gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
+}
+
+static void
+gnome_desktop_thumbnail_factory_finalize (GObject *object)
+{
+ GnomeDesktopThumbnailFactory *factory;
+ GnomeDesktopThumbnailFactoryPrivate *priv;
+
+ factory = GNOME_DESKTOP_THUMBNAIL_FACTORY (object);
+
+ priv = factory->priv;
+
+ if (priv->thumbnailers)
+ {
+ g_list_free_full (priv->thumbnailers, (GDestroyNotify)thumbnailer_unref);
+ priv->thumbnailers = NULL;
+ }
+
+ g_clear_pointer (&priv->mime_types_map, g_hash_table_destroy);
+
+ if (priv->monitors)
+ {
+ g_list_free_full (priv->monitors, (GDestroyNotify)g_object_unref);
+ priv->monitors = NULL;
+ }
+
+ g_mutex_clear (&priv->lock);
+
+ g_clear_pointer (&priv->disabled_types, g_strfreev);
+
+ if (priv->settings)
+ {
+ g_signal_handlers_disconnect_by_func (priv->settings,
+ external_thumbnailers_disabled_all_changed_cb,
+ factory);
+ g_signal_handlers_disconnect_by_func (priv->settings,
+ external_thumbnailers_disabled_changed_cb,
+ factory);
+ g_clear_object (&priv->settings);
+ }
+
+ if (G_OBJECT_CLASS (parent_class)->finalize)
+ (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+gnome_desktop_thumbnail_factory_class_init (GnomeDesktopThumbnailFactoryClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->finalize = gnome_desktop_thumbnail_factory_finalize;
+
+ g_type_class_add_private (class, sizeof (GnomeDesktopThumbnailFactoryPrivate));
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_new:
+ * @size: The thumbnail size to use
+ *
+ * Creates a new #GnomeDesktopThumbnailFactory.
+ *
+ * This function must be called on the main thread and is non-blocking.
+ *
+ * Return value: a new #GnomeDesktopThumbnailFactory
+ *
+ * Since: 2.2
+ **/
+GnomeDesktopThumbnailFactory *
+gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size)
+{
+ GnomeDesktopThumbnailFactory *factory;
+
+ factory = g_object_new (GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL);
+
+ factory->priv->size = size;
+
+ return factory;
+}
+
+static char *
+thumbnail_filename (const char *uri)
+{
+ GChecksum *checksum;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+ char *file;
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+ g_checksum_get_digest (checksum, digest, &digest_len);
+ g_assert (digest_len == 16);
+
+ file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+ g_checksum_free (checksum);
+
+ return file;
+}
+
+static char *
+thumbnail_path (const char *uri,
+ GnomeDesktopThumbnailSize size)
+{
+ char *path, *file;
+
+ file = thumbnail_filename (uri);
+ path = g_build_filename (g_get_user_cache_dir (),
+ "thumbnails",
+ size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE ? "large" : "normal",
+ file,
+ NULL);
+ g_free (file);
+ return path;
+}
+
+static char *
+thumbnail_failed_path (const char *uri)
+{
+ char *path, *file;
+
+ file = thumbnail_filename (uri);
+ /* XXX: appname is only used for failed thumbnails. Is this a mistake? */
+ path = g_build_filename (g_get_user_cache_dir (),
+ "thumbnails",
+ "fail",
+ appname,
+ file,
+ NULL);
+ g_free (file);
+ return path;
+}
+
+static char *
+validate_thumbnail_path (char *path,
+ const char *uri,
+ time_t mtime,
+ GnomeDesktopThumbnailSize size)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ if (pixbuf == NULL ||
+ !gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime)) {
+ g_free (path);
+ return NULL;
+ }
+
+ g_clear_object (&pixbuf);
+
+ return path;
+}
+
+static char *
+lookup_thumbnail_path (const char *uri,
+ time_t mtime,
+ GnomeDesktopThumbnailSize size)
+{
+ char *path = thumbnail_path (uri, size);
+ return validate_thumbnail_path (path, uri, mtime, size);
+}
+
+static char *
+lookup_failed_thumbnail_path (const char *uri,
+ time_t mtime,
+ GnomeDesktopThumbnailSize size)
+{
+ char *path = thumbnail_failed_path (uri);
+ return validate_thumbnail_path (path, uri, mtime, size);
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_lookup:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the mtime of the file
+ *
+ * Tries to locate an existing thumbnail for the file specified.
+ *
+ * Usage of this function is threadsafe and does blocking I/O.
+ *
+ * Return value: The absolute path of the thumbnail, or %NULL if none exist.
+ *
+ * Since: 2.2
+ **/
+char *
+gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ return lookup_thumbnail_path (uri, mtime, priv->size);
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the mtime of the file
+ *
+ * Tries to locate an failed thumbnail for the file specified. Writing
+ * and looking for failed thumbnails is important to avoid to try to
+ * thumbnail e.g. broken images several times.
+ *
+ * Usage of this function is threadsafe and does blocking I/O.
+ *
+ * Return value: TRUE if there is a failed thumbnail for the file.
+ *
+ * Since: 2.2
+ **/
+gboolean
+gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime)
+{
+ char *path;
+
+ g_return_val_if_fail (uri != NULL, FALSE);
+
+ path = lookup_failed_thumbnail_path (uri, mtime, factory->priv->size);
+ if (path == NULL)
+ return FALSE;
+
+ g_free (path);
+
+ return TRUE;
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_can_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mime_type: the mime type of the file
+ * @mtime: the mtime of the file
+ *
+ * Returns TRUE if this GnomeDesktopThumbnailFactory can (at least try) to thumbnail
+ * this file. Thumbnails or files with failed thumbnails won't be thumbnailed.
+ *
+ * Usage of this function is threadsafe and does blocking I/O.
+ *
+ * Return value: TRUE if the file can be thumbnailed.
+ *
+ * Since: 2.2
+ **/
+gboolean
+gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type,
+ time_t mtime)
+{
+ gboolean have_script = FALSE;
+
+ /* Don't thumbnail thumbnails */
+ if (uri &&
+ strncmp (uri, "file:/", 6) == 0 &&
+ strstr (uri, "/thumbnails/") != NULL)
+ return FALSE;
+
+ if (!mime_type)
+ return FALSE;
+
+ g_mutex_lock (&factory->priv->lock);
+ if (!gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type))
+ {
+ Thumbnailer *thumb;
+
+ thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
+ have_script = (thumb != NULL);
+ }
+ g_mutex_unlock (&factory->priv->lock);
+
+ if (have_script)
+ {
+ return !gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
+ uri,
+ mtime);
+ }
+
+ return FALSE;
+}
+
+static GdkPixbuf *
+get_preview_thumbnail (const char *uri,
+ int size)
+{
+ GdkPixbuf *pixbuf;
+ GFile *file;
+ GFileInfo *file_info;
+ GInputStream *input_stream;
+ GObject *object;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ input_stream = NULL;
+
+ file = g_file_new_for_uri (uri);
+
+ /* First see if we can get an input stream via preview::icon */
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_PREVIEW_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, /* GCancellable */
+ NULL); /* return location for GError */
+ g_object_unref (file);
+
+ if (file_info == NULL)
+ return NULL;
+
+ object = g_file_info_get_attribute_object (file_info,
+ G_FILE_ATTRIBUTE_PREVIEW_ICON);
+ g_object_unref (file_info);
+
+ if (!object)
+ return NULL;
+ if (!G_IS_LOADABLE_ICON (object)) {
+ g_object_unref (object);
+ return NULL;
+ }
+
+ input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object),
+ 0, /* size */
+ NULL, /* return location for type */
+ NULL, /* GCancellable */
+ NULL); /* return location for GError */
+ g_object_unref (object);
+
+ if (!input_stream)
+ return NULL;
+
+ pixbuf = gdk_pixbuf_new_from_stream_at_scale (input_stream,
+ size, size,
+ TRUE, NULL, NULL);
+ g_object_unref (input_stream);
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+pixbuf_new_from_bytes (GBytes *bytes,
+ GError **error)
+{
+ g_autoptr(GdkPixbufLoader) loader = NULL;
+
+ loader = gdk_pixbuf_loader_new_with_mime_type ("image/png", error);
+ if (!loader)
+ return NULL;
+
+ if (!gdk_pixbuf_loader_write (loader,
+ g_bytes_get_data (bytes, NULL),
+ g_bytes_get_size (bytes),
+ error))
+ {
+ return NULL;
+ }
+
+ if (!gdk_pixbuf_loader_close (loader, error))
+ return NULL;
+
+ return g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader));
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_generate_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mime_type: the mime type of the file
+ *
+ * Tries to generate a thumbnail for the specified file. If it succeeds
+ * it returns a pixbuf that can be used as a thumbnail.
+ *
+ * Usage of this function is threadsafe and does blocking I/O.
+ *
+ * Return value: (transfer full): thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise.
+ *
+ * Since: 2.2
+ **/
+GdkPixbuf *
+gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type)
+{
+ GdkPixbuf *pixbuf;
+ char *script;
+ int size;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (mime_type != NULL, NULL);
+
+ /* Doesn't access any volatile fields in factory, so it's threadsafe */
+
+ size = 128;
+ if (factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE)
+ size = 256;
+
+ pixbuf = get_preview_thumbnail (uri, size);
+ if (pixbuf != NULL)
+ return pixbuf;
+
+ script = NULL;
+ g_mutex_lock (&factory->priv->lock);
+ if (!gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type))
+ {
+ Thumbnailer *thumb;
+
+ thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
+ if (thumb)
+ script = g_strdup (thumb->command);
+ }
+ g_mutex_unlock (&factory->priv->lock);
+
+ if (script)
+ {
+ GBytes *data;
+ GError *error = NULL;
+
+ data = gnome_desktop_thumbnail_script_exec (script, size, uri, &error);
+ if (data)
+ {
+ pixbuf = pixbuf_new_from_bytes (data, &error);
+ if (!pixbuf)
+ {
+ g_debug ("Could not load thumbnail pixbuf: %s", error->message);
+ g_error_free (error);
+ }
+ g_bytes_unref (data);
+ }
+ else
+ {
+ g_debug ("Thumbnail script ('%s') failed for '%s': %s",
+ script, uri, error ? error->message : "no details");
+ g_clear_error (&error);
+ }
+ }
+ else
+ {
+ g_debug ("Could not find thumbnailer for mime-type '%s'",
+ mime_type);
+ }
+
+ g_free (script);
+
+ return pixbuf;
+}
+
+static gboolean
+save_thumbnail (GdkPixbuf *pixbuf,
+ char *path,
+ const char *uri,
+ time_t mtime)
+{
+ char *dirname;
+ char *tmp_path = NULL;
+ int tmp_fd;
+ char mtime_str[21];
+ gboolean ret = FALSE;
+ GError *error = NULL;
+ const char *width, *height;
+
+ if (pixbuf == NULL)
+ return FALSE;
+
+ dirname = g_path_get_dirname (path);
+
+ if (g_mkdir_with_parents (dirname, 0700) != 0)
+ goto out;
+
+ tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+ tmp_fd = g_mkstemp (tmp_path);
+
+ if (tmp_fd == -1)
+ goto out;
+ close (tmp_fd);
+
+ g_snprintf (mtime_str, 21, "%" G_GINT64_FORMAT, (gint64) mtime);
+ width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
+ height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
+
+ error = NULL;
+ if (width != NULL && height != NULL)
+ ret = gdk_pixbuf_save (pixbuf,
+ tmp_path,
+ "png", &error,
+ "tEXt::Thumb::Image::Width", width,
+ "tEXt::Thumb::Image::Height", height,
+ "tEXt::Thumb::URI", uri,
+ "tEXt::Thumb::MTime", mtime_str,
+ "tEXt::Software", "GNOME::ThumbnailFactory",
+ NULL);
+ else
+ ret = gdk_pixbuf_save (pixbuf,
+ tmp_path,
+ "png", &error,
+ "tEXt::Thumb::URI", uri,
+ "tEXt::Thumb::MTime", mtime_str,
+ "tEXt::Software", "GNOME::ThumbnailFactory",
+ NULL);
+
+ if (!ret)
+ goto out;
+
+ g_chmod (tmp_path, 0600);
+ g_rename (tmp_path, path);
+
+ out:
+ if (error != NULL)
+ {
+ g_warning ("Failed to create thumbnail %s: %s", tmp_path, error->message);
+ g_error_free (error);
+ }
+ g_unlink (tmp_path);
+ g_free (tmp_path);
+ g_free (dirname);
+ return ret;
+}
+
+static GdkPixbuf *
+make_failed_thumbnail (void)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
+ gdk_pixbuf_fill (pixbuf, 0x00000000);
+ return pixbuf;
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_save_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @thumbnail: the thumbnail as a pixbuf
+ * @uri: the uri of a file
+ * @original_mtime: the modification time of the original file
+ *
+ * Saves @thumbnail at the right place. If the save fails a
+ * failed thumbnail is written.
+ *
+ * Usage of this function is threadsafe and does blocking I/O.
+ *
+ * Since: 2.2
+ **/
+void
+gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ GdkPixbuf *thumbnail,
+ const char *uri,
+ time_t original_mtime)
+{
+ char *path;
+
+ path = thumbnail_path (uri, factory->priv->size);
+ if (!save_thumbnail (thumbnail, path, uri, original_mtime))
+ {
+ thumbnail = make_failed_thumbnail ();
+ g_free (path);
+ path = thumbnail_failed_path (uri);
+ save_thumbnail (thumbnail, path, uri, original_mtime);
+ g_object_unref (thumbnail);
+ }
+ g_free (path);
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_create_failed_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the modification time of the file
+ *
+ * Creates a failed thumbnail for the file so that we don't try
+ * to re-thumbnail the file later.
+ *
+ * Usage of this function is threadsafe and does blocking I/O.
+ *
+ * Since: 2.2
+ **/
+void
+gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime)
+{
+ char *path;
+ GdkPixbuf *pixbuf;
+
+ path = thumbnail_failed_path (uri);
+ pixbuf = make_failed_thumbnail ();
+ save_thumbnail (pixbuf, path, uri, mtime);
+ g_free (path);
+ g_object_unref (pixbuf);
+}
+
+/**
+ * gnome_desktop_thumbnail_path_for_uri:
+ * @uri: an uri
+ * @size: a thumbnail size
+ *
+ * Returns the filename that a thumbnail of size @size for @uri would have.
+ * This function is threadsafe and does no blocking I/O.
+ *
+ * Return value: an absolute filename
+ *
+ * Since: 2.2
+ **/
+char *
+gnome_desktop_thumbnail_path_for_uri (const char *uri,
+ GnomeDesktopThumbnailSize size)
+{
+ return thumbnail_path (uri, size);
+}
+
+/**
+ * gnome_desktop_thumbnail_is_valid:
+ * @pixbuf: an loaded thumbnail #GdkPixbuf
+ * @uri: a uri
+ * @mtime: the mtime
+ *
+ * Returns whether the thumbnail has the correct uri and mtime embedded in the
+ * png options. This function is threadsafe and does no blocking I/O.
+ *
+ * Return value: TRUE if the thumbnail has the right @uri and @mtime
+ *
+ * Since: 2.2
+ **/
+gboolean
+gnome_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf,
+ const char *uri,
+ time_t mtime)
+{
+ const char *thumb_uri, *thumb_mtime_str;
+ time_t thumb_mtime;
+
+ thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
+ if (g_strcmp0 (uri, thumb_uri) != 0)
+ return FALSE;
+
+ thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
+ if (!thumb_mtime_str)
+ return FALSE;
+ thumb_mtime = atol (thumb_mtime_str);
+ if (mtime != thumb_mtime)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/src/gnome-desktop/gnome-desktop-thumbnail.h b/src/gnome-desktop/gnome-desktop-thumbnail.h
new file mode 100644
index 000000000..186534ff3
--- /dev/null
+++ b/src/gnome-desktop/gnome-desktop-thumbnail.h
@@ -0,0 +1,102 @@
+/*
+ * gnome-thumbnail.h: Utilities for handling thumbnails
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ */
+
+#ifndef GNOME_DESKTOP_THUMBNAIL_H
+#define GNOME_DESKTOP_THUMBNAIL_H
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+#error GnomeDesktopThumbnail is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including gnome-desktop-thumbnail.h
+#endif
+
+#include <glib.h>
+#include <glib-object.h>
+#include <time.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL,
+ GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE
+} GnomeDesktopThumbnailSize;
+
+#define GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY (gnome_desktop_thumbnail_factory_get_type ())
+#define GNOME_DESKTOP_THUMBNAIL_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactory))
+#define GNOME_DESKTOP_THUMBNAIL_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryClass))
+#define GNOME_DESKTOP_IS_THUMBNAIL_FACTORY(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY))
+#define GNOME_DESKTOP_IS_THUMBNAIL_FACTORY_CLASS(klass) (G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY))
+
+typedef struct _GnomeDesktopThumbnailFactory GnomeDesktopThumbnailFactory;
+typedef struct _GnomeDesktopThumbnailFactoryClass GnomeDesktopThumbnailFactoryClass;
+typedef struct _GnomeDesktopThumbnailFactoryPrivate GnomeDesktopThumbnailFactoryPrivate;
+
+struct _GnomeDesktopThumbnailFactory {
+ GObject parent;
+
+ GnomeDesktopThumbnailFactoryPrivate *priv;
+};
+
+struct _GnomeDesktopThumbnailFactoryClass {
+ GObjectClass parent;
+};
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GnomeDesktopThumbnailFactory, g_object_unref)
+
+GType gnome_desktop_thumbnail_factory_get_type (void);
+GnomeDesktopThumbnailFactory *gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size);
+
+char * gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime);
+
+gboolean gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime);
+gboolean gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type,
+ time_t mtime);
+GdkPixbuf * gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type);
+void gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ GdkPixbuf *thumbnail,
+ const char *uri,
+ time_t original_mtime);
+void gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime);
+
+
+/* Thumbnailing utils: */
+gboolean gnome_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf,
+ const char *uri,
+ time_t mtime);
+char * gnome_desktop_thumbnail_path_for_uri (const char *uri,
+ GnomeDesktopThumbnailSize size);
+
+G_END_DECLS
+
+#endif /* GNOME_DESKTOP_THUMBNAIL_H */
diff --git a/src/meson.build b/src/meson.build
index a947a05f2..3f7ae077d 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -54,6 +54,10 @@ libnautilus_sources = [
'animation/ide-box-theatric.h',
'animation/ide-cairo.c',
'animation/ide-cairo.h',
+ 'gnome-desktop/gnome-desktop-thumbnail.c',
+ 'gnome-desktop/gnome-desktop-thumbnail.h',
+ 'gnome-desktop/gnome-desktop-thumbnail-script.c',
+ 'gnome-desktop/gnome-desktop-thumbnail-script.h',
'gtk/nautilusgtkplacesview.c',
'gtk/nautilusgtkplacesviewprivate.h',
'gtk/nautilusgtkplacesviewrow.c',
@@ -271,9 +275,10 @@ nautilus_deps = [
gio_unix,
gmodule,
gnome_autoar,
- gnome_desktop,
+ gsettings_desktop_schemas,
libgd_dep,
nautilus_extension,
+ seccomp,
selinux,
tracker_sparql,
xml
diff --git a/src/nautilus-properties-window.c b/src/nautilus-properties-window.c
index d211873e4..d239263e2 100644
--- a/src/nautilus-properties-window.c
+++ b/src/nautilus-properties-window.c
@@ -35,7 +35,7 @@
#include <sys/stat.h>
#define GNOME_DESKTOP_USE_UNSTABLE_API
-#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+#include "gnome-desktop/gnome-desktop-thumbnail.h"
#include "nautilus-enums.h"
#include "nautilus-error-reporting.h"
diff --git a/src/nautilus-thumbnails.c b/src/nautilus-thumbnails.c
index 991bfb6f6..b90dc0daa 100644
--- a/src/nautilus-thumbnails.c
+++ b/src/nautilus-thumbnails.c
@@ -40,7 +40,7 @@
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
-#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+#include "gnome-desktop/gnome-desktop-thumbnail.h"
#define DEBUG_FLAG NAUTILUS_DEBUG_THUMBNAILS
#include "nautilus-debug.h"