[nautilus/wip/ernestask/gtk4: 4/19] general: Copy gnome-desktop thumbnailing code



commit 82a6285bdd36c890e6a9e03fa571bf291c72f035
Author: Ernestas Kulik <ernestask gnome org>
Date:   Sat Apr 28 13:21:50 2018 +0300

    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.

 .gitlab-ci.yml                                     |   12 +-
 build-aux/flatpak/org.gnome.Nautilus.json          |   10 -
 meson.build                                        |   19 +-
 src/gnome-desktop/gnome-desktop-thumbnail-script.c |  832 +++++++++++++
 src/gnome-desktop/gnome-desktop-thumbnail-script.h |   38 +
 src/gnome-desktop/gnome-desktop-thumbnail.c        | 1301 ++++++++++++++++++++
 src/gnome-desktop/gnome-desktop-thumbnail.h        |  102 ++
 src/meson.build                                    |    7 +-
 src/nautilus-properties-window.c                   |    2 +-
 src/nautilus-thumbnails.c                          |    2 +-
 10 files changed, 2305 insertions(+), 20 deletions(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e7e0f3125..af5f5c5c3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -91,11 +91,11 @@ ubuntu:devel:
         # Ubuntu requires running update to fetch metadata and find packges
         - apt update
         - apt install -y gcc meson gettext itstool git libgtk-3-dev
-                        libgnome-autoar-0-dev libgnome-desktop-3-dev
-                        gobject-introspection libxml2-dev
-                        libtracker-control-2.0-dev desktop-file-utils libgexiv2-dev
-                        libgail-3-dev libtracker-sparql-2.0-dev
-                        libgirepository1.0-dev
+                         libgnome-autoar-0-dev
+                         gobject-introspection libxml2-dev
+                         libtracker-control-2.0-dev desktop-file-utils libgexiv2-dev
+                         libgail-3-dev libtracker-sparql-2.0-dev
+                         libgirepository1.0-dev
     <<: *distro_test
     only:
         - schedules
@@ -107,7 +107,7 @@ opensuse:tumbleweed:
     stage: cross_distro
     before_script:
         - zypper install -y gcc meson gettext-runtime gettext-tools itstool git
-                            gtk3-devel gnome-autoar-devel libgnome-desktop-3-devel
+                            gtk3-devel gnome-autoar-devel
                             gobject-introspection-devel libxml2-devel tracker-devel
                             desktop-file-utils libgexiv2-devel
     <<: *distro_test
diff --git a/build-aux/flatpak/org.gnome.Nautilus.json b/build-aux/flatpak/org.gnome.Nautilus.json
index b8a3fe988..caf231c16 100644
--- a/build-aux/flatpak/org.gnome.Nautilus.json
+++ b/build-aux/flatpak/org.gnome.Nautilus.json
@@ -73,16 +73,6 @@
                 }
             ]
         },
-        {
-            "name": "gnome-desktop",
-            "config-opts": ["--disable-debug-tools", "--disable-udev"],
-            "sources": [
-                {
-                    "type": "git",
-                    "url": "https://git.gnome.org/browse/gnome-desktop";
-                }
-            ]
-        },
         {
             "name": "gnome-autoar",
             "sources": [
diff --git a/meson.build b/meson.build
index f909183c4..b68cf04ac 100644
--- a/meson.build
+++ b/meson.build
@@ -78,8 +78,9 @@ gio_unix = dependency('gio-unix-2.0', version: glib_ver)
 glib = dependency('glib-2.0', version: glib_ver)
 gmodule = dependency('gmodule-no-export-2.0', version: glib_ver)
 gnome_autoar = dependency('gnome-autoar-0', version: '>= 0.2.1')
-gnome_desktop = dependency('gnome-desktop-3.0', version: '>= 3.0.0')
+gsettings_desktop_schemas = dependency('gsettings-desktop-schemas')
 gtk = dependency('gtk+-3.0', version: '>= 3.22.27')
+seccomp = dependency('libseccomp')
 selinux = []
 if get_option('selinux')
   selinux = dependency('libselinux', version: '>= 2.0')
@@ -113,9 +114,25 @@ conf.set_quoted('LOCALEDIR', join_paths(prefix, get_option('localedir')))
 conf.set_quoted('NAUTILUS_DATADIR', join_paths(datadir, 'nautilus'))
 conf.set_quoted('NAUTILUS_EXTENSIONDIR', join_paths(prefix, extensiondir))
 conf.set_quoted('APPLICATION_ID', application_id)
+conf.set_quoted('INSTALL_PREFIX', prefix)
 conf.set_quoted('PROFILE', profile)
 conf.set_quoted('NAME_SUFFIX', name_suffix)
 
+
+#                                            _           _    _
+#  __ _ _ __   ___  _ __ ___   ___        __| | ___  ___| | _| |_ ___  _ __
+# / _` | '_ \ / _ \| '_ ` _ \ / _ \_____ / _` |/ _ \/ __| |/ / __/ _ \| '_ \
+#| (_| | | | | (_) | | | | | |  __/_____| (_| |  __/\__ \   <| || (_) | |_) |
+# \__, |_| |_|\___/|_| |_| |_|\___|      \__,_|\___||___/_|\_\\__\___/| .__/
+# |___/                                                               |_|
+#
+# gnome-desktop macros for thumbnailer sandboxing.
+####################################
+conf.set10('_GNU_SOURCE', true)    #    `man user_namespaces` /CLONE_NEWUSER
+conf.set10('ENABLE_SECCOMP', true) #
+conf.set10('HAVE_BWRAP', true)     #
+####################################
+
 if get_option('packagekit')
   conf.set10('ENABLE_PACKAGEKIT', true)
 endif
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"
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]