gvfs r2132 - in trunk: . daemon daemon/trashlib



Author: ryanl
Date: Fri Dec 12 05:33:16 2008
New Revision: 2132
URL: http://svn.gnome.org/viewvc/gvfs?rev=2132&view=rev

Log:
2008-12-11  Ryan Lortie  <desrt desrt ca>

        New trash:/ backend.

        * daemon/trashlib: implementation of the reader side of the fd.o
        trash specification
        * daemon/gvfsbackendtrash.[ch]: rewrite based on trashlib
        * configure.ac: add daemon/trashlib/Makefile to output
        * daemon/Makefile.am: add trashlib/ subdir and include in trash
        backend libraries



Added:
   trunk/daemon/trashlib/
   trunk/daemon/trashlib/Makefile.am
   trunk/daemon/trashlib/dirwatch.c
   trunk/daemon/trashlib/dirwatch.h
   trunk/daemon/trashlib/trashdir.c
   trunk/daemon/trashlib/trashdir.h
   trunk/daemon/trashlib/trashexpunge.c
   trunk/daemon/trashlib/trashexpunge.h
   trunk/daemon/trashlib/trashitem.c
   trunk/daemon/trashlib/trashitem.h
   trunk/daemon/trashlib/trashwatcher.c
   trunk/daemon/trashlib/trashwatcher.h
Modified:
   trunk/ChangeLog
   trunk/configure.ac
   trunk/daemon/Makefile.am
   trunk/daemon/gvfsbackendtrash.c
   trunk/daemon/gvfsbackendtrash.h

Modified: trunk/configure.ac
==============================================================================
--- trunk/configure.ac	(original)
+++ trunk/configure.ac	Fri Dec 12 05:33:16 2008
@@ -553,6 +553,7 @@
 Makefile
 common/Makefile
 client/Makefile
+daemon/trashlib/Makefile
 daemon/Makefile
 monitor/Makefile
 monitor/proxy/Makefile

Modified: trunk/daemon/Makefile.am
==============================================================================
--- trunk/daemon/Makefile.am	(original)
+++ trunk/daemon/Makefile.am	Fri Dec 12 05:33:16 2008
@@ -1,3 +1,5 @@
+SUBDIRS = trashlib
+
 NULL =
 
 mountdir = $(datadir)/gvfs/mounts
@@ -264,9 +266,10 @@
 	-DBACKEND_HEADER=gvfsbackendtrash.h \
 	-DDEFAULT_BACKEND_TYPE=trash \
 	-DMAX_JOB_THREADS=10 \
-	-DBACKEND_TYPES='"trash", G_VFS_TYPE_BACKEND_TRASH,'
+	-DBACKEND_TYPES='"trash", G_VFS_TYPE_BACKEND_TRASH,' \
+	-Itrashlib
 
-gvfsd_trash_LDADD = $(libraries)
+gvfsd_trash_LDADD = $(libraries) trashlib/libtrash.a
 
 gvfsd_computer_SOURCES = \
 	gvfsbackendcomputer.c gvfsbackendcomputer.h \

Modified: trunk/daemon/gvfsbackendtrash.c
==============================================================================
--- trunk/daemon/gvfsbackendtrash.c	(original)
+++ trunk/daemon/gvfsbackendtrash.c	Fri Dec 12 05:33:16 2008
@@ -1,1862 +1,722 @@
-/* GIO - GLib Input, Output and Streaming Library
- * 
- * Copyright (C) 2006-2007 Red Hat, Inc.
+/*
+ * Copyright  2008 Ryan Lortie
  *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General
- * Public License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
- * Boston, MA 02111-1307, USA.
- *
- * Author: Alexander Larsson <alexl redhat com>
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
  */
 
+#include "gvfsbackendtrash.h"
 
-#include <config.h>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <errno.h>
-#include <unistd.h>
-#include <fcntl.h>
+#include <glib/gi18n.h> /* _() */
 #include <string.h>
 
-#include <glib/gstdio.h>
-#include <glib/gi18n.h>
-#include <gio/gio.h>
-#include <gio/gunixmounts.h>
-#include <glib/gurifuncs.h>
+#include "trashwatcher.h"
+#include "trashitem.h"
 
-#include "gvfsbackendtrash.h"
-#include "gvfsmonitor.h"
+#include "gvfsjobcreatemonitor.h"
 #include "gvfsjobopenforread.h"
-#include "gvfsjobread.h"
-#include "gvfsjobseekread.h"
-#include "gvfsjobopenforwrite.h"
-#include "gvfsjobwrite.h"
-#include "gvfsjobclosewrite.h"
-#include "gvfsjobseekwrite.h"
-#include "gvfsjobsetdisplayname.h"
-#include "gvfsjobqueryinfo.h"
-#include "gvfsjobdelete.h"
 #include "gvfsjobqueryfsinfo.h"
-#include "gvfsjobqueryattributes.h"
+#include "gvfsjobqueryinfo.h"
 #include "gvfsjobenumerate.h"
-#include "gvfsjobcreatemonitor.h"
-#include "gvfsdaemonprotocol.h"
-
-/* This is an implementation of the read side of the freedesktop trash specification.
-   For more details on that, see: http://www.freedesktop.org/wiki/Specifications/trash-spec
-
-   It supplies the virtual trash: location that contains the merged contents of all the
-   trash directories currently mounted on the system. In order to not have filename
-   conflicts in the toplevel trash: directory (as files in different trash dirs can have
-   the same name) we encode the filenames in the toplevel in a way such that:
-   1) There are no conflicts
-   2) You can know from the filename itself which trash directory it came from
-   3) Files in the trash in your homedir look "nicest"
-   This is handled by escape_pathname() and unescape_pathname()
-
-   This should be pretty straightforward, but there are two complications:
-
-   * When looking for trash directories we need to look in the root of all mounted
-     filesystems. This is problematic, beacuse it means as soon as there is at least
-     one hanged NFS mount the whole process will block until that comes back. This
-     is pretty common on some kinds of machines. We avoid this by forking and doing the
-     stats in a child process.
-
-   * Monitoring the root directory is complicated, as its spread over many directories
-     that all have to be monitored. Furthermore, directories can be added/removed
-     at any time if something is mounted/unmounted, and in the case of unmounts we
-     need to generate deleted events for the files we lost. So, we need to keep track
-     of the current contents of the trash dirs.
-
-     The solution used is to keep a list of all the files currently in the toplevel
-     directory, and then whenever we re-scan that we send out events based on changes
-     from the old list to the new list. And, in addition to user read-dirs we re-scan
-     the toplevel when filesystems are mounted/unmounted and when files are added/deleted
-     in currently monitored trash directories.
- */ 
-
+#include "gvfsjobseekread.h"
+#include "gvfsjobread.h"
 
+typedef GVfsBackendClass GVfsBackendTrashClass;
 
-struct _GVfsBackendTrash
+struct OPAQUE_TYPE__GVfsBackendTrash
 {
   GVfsBackend parent_instance;
 
-  GMountSpec *mount_spec;
+  GVfsMonitor *file_monitor;
+  GVfsMonitor *dir_monitor;
 
-  /* This is only set on the main thread */
-  GList *top_files; /* Files in toplevel dir */
-  guint num_top_files;
-
-  /* All these are protected by the root_monitor lock */
-  GVfsMonitor *file_vfs_monitor;
-  GVfsMonitor *vfs_monitor;
-  GList *trash_dir_monitors; /* GFileMonitor objects */
-  GUnixMountMonitor *mount_monitor;
-  gboolean trash_file_update_running;
-  gboolean trash_file_update_scheduled;
-  gboolean trash_file_update_monitors_scheduled;
+  TrashWatcher *watcher;
+  TrashRoot *root;
 };
 
-G_LOCK_DEFINE_STATIC(root_monitor);
+G_DEFINE_TYPE (GVfsBackendTrash, g_vfs_backend_trash, G_VFS_TYPE_BACKEND);
 
-G_DEFINE_TYPE (GVfsBackendTrash, g_vfs_backend_trash, G_VFS_TYPE_BACKEND)
+static GVfsMonitor *
+trash_backend_get_file_monitor (GVfsBackendTrash  *backend,
+                                gboolean           create)
+{
+  if (backend->file_monitor == NULL && create == FALSE)
+    return NULL;
 
-static void   schedule_update_trash_files  (GVfsBackendTrash *backend,
-                                            gboolean          update_trash_dirs);
-static GList *enumerate_root               (GVfsBackend      *backend,
-                                            GVfsJobEnumerate *job);
-static GVfsMonitor *do_create_root_monitor (GVfsBackend      *backend);
-
-static char *
-escape_pathname (const char *dir)
-{
-  const char *p;
-  char *d, *res;
-  int count;
-  char c;
-  const char *basename;
-  const char *user_data_dir;
-
-  /* Special case the homedir trash to get nice filenames for that */
-  user_data_dir = g_get_user_data_dir ();
-  if (g_str_has_prefix (dir, user_data_dir) &&
-      (g_str_has_prefix (dir + strlen (user_data_dir), "/Trash/")))
+  else if (backend->file_monitor == NULL)
     {
-      basename = dir + strlen (user_data_dir) + strlen ("/Trash/");
+      /* 'create' is only ever set in the main thread, so we will have
+       * no possibility here for creating more than one new monitor.
+       */
+      if (backend->dir_monitor == NULL)
+        trash_watcher_watch (backend->watcher);
 
-      res = g_malloc (strlen (basename) + 2);
-      res[0] = '_';
-      strcpy (res + 1, basename);
-      return res;
+      backend->file_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend));
     }
 
-  /* Skip initial slashes, we don't need those since they are always there */
-  while (*dir == '/')
-    dir++;
-
-  /* count characters that need to be escaped. */
-  count = 0;
-  p = dir;
-  while (*p)
-    {
-      if (*p == '_')
-        count++;
-      if (*p == '/')
-        count++;
-      if (*p == '%')
-        count++;
-      p++;
-    }
-  
-  res = g_malloc (strlen (dir) + count*2 + 1);
-  
-  p = dir;
-  d = res;
-  while (*p)
-    {
-      c = *p++;
-      if (c == '_')
-        {
-          *d++ = '%';
-          *d++ = '5';
-          *d++ = 'f';
-        }
-      else if (c == '/')
-        {
-          *d++ = '%';
-          *d++ = '2';
-          *d++ = 'f';
-          
-          /* Skip consecutive slashes, they are unnecessary,
-             and break our escaping */
-          while (*p == '/')
-            p++;
-        }
-      else if (c == '%')
-        {
-          *d++ = '%';
-          *d++ = '2';
-          *d++ = '5';
-        }
-      else
-        *d++ = c;
-    }
-  *d = 0;
-  
-  return res;
+  return g_object_ref (backend->file_monitor);
 }
 
-static char *
-unescape_pathname (const char *escaped_dir, int len)
+static GVfsMonitor *
+trash_backend_get_dir_monitor (GVfsBackendTrash *backend,
+                               gboolean          create)
 {
-  char *dir, *d;
-  const char *p, *end;
-  char c;
+  if (backend->dir_monitor == NULL && create == FALSE)
+    return NULL;
 
-  if (len == -1)
-    len = strlen (escaped_dir);
-
-  /* If first char is _ this is a homedir trash file */
-  if (len > 1 && *escaped_dir == '_')
+  else if (backend->dir_monitor == NULL)
     {
-      char *trashname;
-      trashname = g_strndup (escaped_dir + 1, len - 1);
-      dir = g_build_filename (g_get_user_data_dir (), "Trash", trashname, NULL);
-      g_free (trashname);
-      return dir;
-    }
-  
-  dir = g_malloc (len + 1 + 1);
+      /* 'create' is only ever set in the main thread, so we will have
+       * no possibility here for creating more than one new monitor.
+       */
+      if (backend->file_monitor == NULL)
+        trash_watcher_watch (backend->watcher);
 
-  p = escaped_dir;
-  d = dir;
-  *d++ = '/';
-  end = p + len;
-  while (p < end)
-    {
-      c = *p++;
-      if (c == '%' && p < (end-1))
-        {
-          if (*(p) == '2' && *(p+1) == 'f')
-            {
-              *d++ = '/';
-              p+=2;
-            }
-          else if (*(p) == '2' && *(p+1) == '5')
-            {
-              *d++ = '%';
-              p+=2;
-            }
-          else if (*(p) == '5' && *(p+1) == 'f')
-            {
-              *d++ = '_';
-              p+=2;
-            }
-        }
-      else
-        *d++ = c;
+      backend->dir_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend));
     }
-  *d = 0;
 
-  return dir;
+  return g_object_ref (backend->dir_monitor);
 }
 
-static char *
-get_top_dir_for_trash_dir (const char *trash_dir)
+static void
+trash_backend_item_created (TrashItem *item,
+                            gpointer   user_data)
 {
-  char *basename, *dirname;
-  char *user_trash_basename;
-  char *user_sys_dir, *res;
+  GVfsBackendTrash *backend = user_data;
+  GVfsMonitor *monitor;
 
-  basename = g_path_get_basename (trash_dir);
-  if (strcmp (basename, "Trash") == 0)
-    {
-      /* This is $XDG_DATA_DIR/Trash */
-      g_free (basename);
-      return g_path_get_dirname (trash_dir);
-    }
-  
-  user_trash_basename =  g_strdup_printf (".Trash-%u", getuid());
-  if (strcmp (basename, user_trash_basename) == 0)
-    {
-      g_free (user_trash_basename);
-      g_free (basename);
-      return g_path_get_dirname (trash_dir);
-    }
-  g_free (user_trash_basename);
+  monitor = trash_backend_get_dir_monitor (backend, FALSE);
 
-  user_sys_dir = g_strdup_printf ("%u", getuid());
-  if (strcmp (basename, user_sys_dir) == 0)
+  if (monitor)
     {
-      g_free (user_sys_dir);
-      dirname = g_path_get_dirname (trash_dir);
-      g_free (basename);
-      basename = g_path_get_basename (dirname);
+      char *slashname;
 
-      if (strcmp (basename, ".Trash") == 0)
-        {
-          res = g_path_get_dirname (dirname);
-          g_free (dirname);
-          g_free (basename);
-          return res;
-        }
-      
-      g_free (dirname);
-    }
-  g_free (user_sys_dir);
-  g_free (basename);
+      slashname = g_strconcat ("/", trash_item_get_escaped_name (item), NULL);
 
-  /* Weird, but we return something at least */
-  return g_strdup (trash_dir);
+      g_vfs_monitor_emit_event (monitor, G_FILE_MONITOR_EVENT_CREATED,
+                                slashname, NULL);
+      g_object_unref (monitor);
+      g_free (slashname);
+    }
 }
 
-/* FALSE => root */
-static gboolean
-decode_path (const char *filename, char **trashdir, char **trashfile, char **relative_path, char **topdir)
+static void
+trash_backend_item_deleted (TrashItem *item,
+                            gpointer   user_data)
 {
-  const char *first_entry, *first_entry_end;
-  char *first_item;
-  
-  if (*filename == 0 || *filename != '/')
-    return FALSE;
-
-  while (*filename == '/')
-    filename++;
-
-  if (*filename == 0)
-    return FALSE;
+  GVfsBackendTrash *backend = user_data;
+  GVfsMonitor *monitor;
 
-  first_entry = filename;
+  monitor = trash_backend_get_dir_monitor (backend, FALSE);
 
-  while (*filename != 0 && *filename != '/')
-    filename++;
-
-  first_entry_end = filename;
-
-  while (*filename == '/')
-    filename++;
-  
-  first_item = unescape_pathname (first_entry, first_entry_end - first_entry);
-  *trashfile = g_path_get_basename (first_item);
-  *trashdir = g_path_get_dirname (first_item);
-  *topdir = get_top_dir_for_trash_dir (*trashdir);
-
-  if (*filename)
-    *relative_path = g_strdup (filename);
-  else
-    *relative_path = NULL;
-  return TRUE;
-}
-
-typedef enum {
-  HAS_SYSTEM_DIR = 1<<0,
-  HAS_USER_DIR = 1<<1,
-  HAS_TRASH_FILES = 1<<1
-} TopdirInfo;
-
-static TopdirInfo
-check_topdir (const char *topdir)
-{
-  TopdirInfo res;
-  struct stat statbuf;
-  char *sysadmin_dir, *sysadmin_dir_uid;
-  char *user_trash_basename, *user_trash;
-
-  res = 0;
-  
-  sysadmin_dir = g_build_filename (topdir, ".Trash", NULL);
-  if (lstat (sysadmin_dir, &statbuf) == 0 &&
-      S_ISDIR (statbuf.st_mode) &&
-      statbuf.st_mode & S_ISVTX)
+  if (monitor)
     {
-      /* We have a valid sysadmin .Trash dir, look for uid subdir */
-      sysadmin_dir_uid = g_strdup_printf ("%s/%u", sysadmin_dir, getuid());
-      
-      if (lstat (sysadmin_dir_uid, &statbuf) == 0 &&
-          S_ISDIR (statbuf.st_mode) &&
-          statbuf.st_uid == getuid())
-        {
-          res |= HAS_SYSTEM_DIR;
-
-          if (statbuf.st_nlink != 2)
-            res |= HAS_TRASH_FILES;
-        }
-      
-      g_free (sysadmin_dir_uid);
-    }
-  g_free (sysadmin_dir);
+      char *slashname;
 
-  user_trash_basename =  g_strdup_printf (".Trash-%u", getuid());
-  user_trash = g_build_filename (topdir, user_trash_basename, NULL);
-  g_free (user_trash_basename);
-  
-  if (lstat (user_trash, &statbuf) == 0 &&
-      S_ISDIR (statbuf.st_mode) &&
-      statbuf.st_uid == getuid())
-    {
-      res |= HAS_USER_DIR;
-      
-      if (statbuf.st_nlink != 2)
-        res |= HAS_TRASH_FILES;
+      slashname = g_strconcat ("/", trash_item_get_escaped_name (item), NULL);
+      g_vfs_monitor_emit_event (monitor, G_FILE_MONITOR_EVENT_DELETED,
+                                slashname, NULL);
+      g_object_unref (monitor);
+      g_free (slashname);
     }
-
-  g_free (user_trash);
-
-  return res;
 }
 
-static gboolean
-wait_for_fd_with_timeout (int fd, int timeout_secs)
+static void
+trash_backend_item_count_changed (gpointer user_data)
 {
-  int res;
-          
-  do
+  GVfsBackendTrash *backend = user_data;
+  GVfsMonitor *file_monitor;
+  GVfsMonitor *dir_monitor;
+
+  file_monitor = trash_backend_get_file_monitor (backend, FALSE);
+  dir_monitor = trash_backend_get_dir_monitor (backend, FALSE);
+
+  if (file_monitor)
     {
-#ifdef HAVE_POLL
-      struct pollfd poll_fd;
-      poll_fd.fd = fd;
-      poll_fd.events = POLLIN;
-      res = poll (&poll_fd, 1, timeout_secs * 1000);
-#else
-      struct timeval tv;
-      fd_set read_fds;
-      
-      tv.tv_sec = timeout_secs;
-      tv.tv_usec = 0;
-      
-      FD_ZERO(&read_fds);
-      FD_SET(fd, &read_fds);
-      
-      res = select (fd + 1, &read_fds, NULL, NULL, &tv);
-#endif
-    } while (res == -1 && errno == EINTR);
-  
-  return res > 0;
-}
+      g_vfs_monitor_emit_event (file_monitor,
+                                G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
+                                "/", NULL);
 
-/* We do this convoluted fork + pipe thing to avoid hanging
-   on e.g stuck NFS mounts, which is somewhat common since
-   we're basically stat:ing all mounted filesystems */
-static GList *
-get_topdir_info (GList *topdirs)
-{
-	GList *result = NULL;
+      g_object_unref (file_monitor);
+    }
 
-	while (topdirs)
+  if (dir_monitor)
     {
-      guint32 topdir_info = 0;
-      pid_t pid;
-      int pipes[2];
-      int status;
-      
-      if (pipe (pipes) == -1)
-        goto error;
-      
-      pid = fork ();
-      if (pid == -1)
-        {
-          close (pipes[0]);
-          close (pipes[1]);
-          goto error;
-        }
-      
-      if (pid == 0)
-        {
-          /* Child */
-          close (pipes[0]);
-          
-          /* Fork an intermediate child that immediately exits
-           * so we can waitpid it. This means the final process
-           * will get owned by init and not go zombie.
-           */
-          pid = fork ();
-          
-          if (pid == 0)
-            {
-              /* Grandchild */
-              while (topdirs)
-                {
-                  guint32 info;
-                  info = check_topdir ((char *)topdirs->data);
-                  write (pipes[1], (char *)&info, sizeof (guint32));
-                  topdirs = topdirs->next;
-                }
-            }
-          close (pipes[1]);
-          _exit (0);
-          g_assert_not_reached ();
-        }
-      
-      /* Parent */
-      close (pipes[1]);
+      g_vfs_monitor_emit_event (dir_monitor,
+                                G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
+                                "/", NULL);
 
-      /* Wait for the intermidate process to die */
-    retry_waitpid:
-      if (waitpid (pid, &status, 0) < 0)
-        {
-          if (errno == EINTR)
-            goto retry_waitpid;
-          else if (errno == ECHILD)
-            ; /* do nothing, child already reaped */
-          else
-            g_warning ("waitpid() should not fail in get_topdir_info");
-        }
-      
-      while (topdirs)
-        {
-          if (!wait_for_fd_with_timeout (pipes[0], 3) ||
-              read (pipes[0], (char *)&topdir_info, sizeof (guint32)) != sizeof (guint32))
-            break;
-          
-          result = g_list_prepend (result, GUINT_TO_POINTER (topdir_info));
-          topdirs = topdirs->next;
-        }
-      
-      close (pipes[0]);
-      
-    error:
-      if (topdirs)
-        {
-          topdir_info = 0;
-          result = g_list_prepend (result, GUINT_TO_POINTER (topdir_info));
-          topdirs = topdirs->next;
-        }
+      g_object_unref (dir_monitor);
     }
-  
-	return g_list_reverse (result);
 }
 
-static GList *
-list_trash_dirs (void)
+
+static GFile *
+trash_backend_get_file (GVfsBackendTrash  *backend,
+                        const char        *filename,
+                        TrashItem        **item_ret,
+                        gboolean          *is_toplevel,
+                        GError           **error)
 {
-  GList *mounts, *l, *li;
-  const char *topdir;
-  char *home_trash;
-  GUnixMountEntry *mount;
-  GList *dirs;
-  GList *topdirs;
-  GList *topdirs_info;
-  struct stat statbuf;
-  gboolean has_trash_files;
-  int stat_result;
+  const char *slash;
+  gboolean is_top;
+  TrashItem *item;
+  GFile *file;
 
-  dirs = NULL;
-  has_trash_files = FALSE;
-  
-  home_trash = g_build_filename (g_get_user_data_dir (), "Trash", NULL);
+  file = NULL;
+  filename++;
+
+  slash = strchr (filename, '/');
+  is_top = slash == NULL;
 
-  stat_result = g_lstat (home_trash, &statbuf);
+  if (is_toplevel)
+    *is_toplevel = is_top;
 
-  /* If the home trash directory doesn't exist at this point, we must create
-   * it in order to monitor it. */
-  if (stat_result != 0)
+  if (!is_top)
     {
-      gchar *home_trash_files = g_build_filename (home_trash, "files", NULL);
-      gchar *home_trash_info  = g_build_filename (home_trash, "info", NULL);
+      char *toplevel;
 
-      g_mkdir_with_parents (home_trash_files, 0700);
-      g_mkdir_with_parents (home_trash_info, 0700);
+      g_assert (slash[1]);
 
-      g_free (home_trash_files);
-      g_free (home_trash_info);
+      toplevel = g_strndup (filename, slash - filename);
+      if ((item = trash_root_lookup_item (backend->root, toplevel)))
+        {
+          file = trash_item_get_file (item);
+          file = g_file_get_child (file, slash + 1);
 
-      stat_result = g_lstat (home_trash, &statbuf);
-    }
+          if (item_ret)
+            *item_ret = item;
+          else
+            trash_item_unref (item);
+        }
 
-  if (stat_result == 0 &&
-      S_ISDIR (statbuf.st_mode))
-    {
-      dirs = g_list_prepend (dirs, home_trash);
-      if (statbuf.st_nlink != 2)
-        has_trash_files = TRUE;
+      g_free (toplevel);
     }
   else
-    g_free (home_trash);
-
-  topdirs = NULL;
-  mounts = g_unix_mounts_get (NULL);
-  for (l = mounts; l != NULL; l = l->next)
     {
-      mount = l->data;
-      
-      if (!g_unix_mount_is_system_internal (mount) )
+      if ((item = trash_root_lookup_item (backend->root, filename)))
         {
-          topdir = g_unix_mount_get_mount_path (mount);
-          topdirs = g_list_prepend (topdirs, g_strdup (topdir));
+          file = g_object_ref (trash_item_get_file (item));
+
+          if (item_ret)
+            *item_ret = item;
+          else
+            trash_item_unref (item);
         }
-      
-      g_unix_mount_free (mount);
     }
-  g_list_free (mounts);
 
-  topdirs_info = get_topdir_info (topdirs);
+  if (file == NULL)
+    g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                 _("No such file or directory"));
+
+  return file;
+}
+
+/* ======================= method implementations ======================= */
+static gboolean
+trash_backend_open_for_read (GVfsBackend        *vfs_backend,
+                             GVfsJobOpenForRead *job,
+                             const char         *filename)
+{
+  GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
+  GError *error = NULL;
+
+  if (filename[1] == '\0')
+    g_set_error (&error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
+                 _("Can't open directory"));
 
-  for (l = topdirs, li = topdirs_info; l != NULL && li != NULL; l = l->next, li = li->next)
+  else
     {
-      TopdirInfo info = GPOINTER_TO_UINT (li->data);
-      char *basename, *trashdir;
-      topdir = l->data;
+      GFile *real;
+
+      real = trash_backend_get_file (backend, filename, NULL, NULL, &error);
 
-      if (info & HAS_SYSTEM_DIR)
+      if (real)
         {
-          basename = g_strdup_printf ("%u", getuid());
-          trashdir = g_build_filename (topdir, ".Trash", basename, NULL);
-          g_free (basename);
-          dirs = g_list_prepend (dirs, trashdir);
-        }
+          GFileInputStream *stream;
+
+          stream = g_file_read (real, NULL, &error);
       
-      if (info & HAS_USER_DIR)
-        {
-          basename = g_strdup_printf (".Trash-%u", getuid());
-          trashdir = g_build_filename (topdir, basename, NULL);
-          g_free (basename);
-          dirs = g_list_prepend (dirs, trashdir);
-        }
+          if (stream)
+            {
+              g_vfs_job_open_for_read_set_handle (job, stream);
+              g_vfs_job_open_for_read_set_can_seek (job, TRUE);
+              g_vfs_job_succeeded (G_VFS_JOB (job));
 
-      if (info & HAS_TRASH_FILES)
-        has_trash_files = TRUE;
+              return TRUE;
+            }
+        }
     }
 
-  g_list_foreach (topdirs, (GFunc) g_free, NULL);
-  g_list_free (topdirs);
-  g_list_free (topdirs_info);
+  g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
 
-  return g_list_reverse (dirs);
+  return TRUE;
 }
 
-static void
-g_vfs_backend_trash_finalize (GObject *object)
+static gboolean
+trash_backend_read (GVfsBackend       *backend,
+                    GVfsJobRead       *job,
+                    GVfsBackendHandle  handle,
+                    char              *buffer,
+                    gsize              bytes_requested)
 {
-  GVfsBackendTrash *backend;
-
-  backend = G_VFS_BACKEND_TRASH (object);
+  GError *error = NULL;
+  gssize bytes;
 
-  g_mount_spec_unref (backend->mount_spec);
+  bytes = g_input_stream_read (handle, buffer, bytes_requested,
+                               NULL, &error);
 
-  
-  if (G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize)
-    (*G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize) (object);
-}
+  if (bytes >= 0)
+    {
+      g_vfs_job_read_set_size (job, bytes);
+      g_vfs_job_succeeded (G_VFS_JOB (job));
 
-static void
-g_vfs_backend_trash_init (GVfsBackendTrash *trash_backend)
-{
-  GVfsBackend *backend = G_VFS_BACKEND (trash_backend);
-  GMountSpec *mount_spec;
+      return TRUE;
+    }
 
-  /* translators: This is the name of the backend */
-  g_vfs_backend_set_display_name (backend, _("Trash"));
-  g_vfs_backend_set_icon_name (backend, "user-trash");
-  g_vfs_backend_set_user_visible (backend, FALSE);  
+  g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
 
-  mount_spec = g_mount_spec_new ("trash");
-  g_vfs_backend_set_mount_spec (backend, mount_spec);
-  trash_backend->mount_spec = mount_spec;
+  return TRUE;
 }
 
-static void
-do_mount (GVfsBackend *backend,
-          GVfsJobMount *job,
-          GMountSpec *mount_spec,
-          GMountSource *mount_source,
-          gboolean is_automount)
-{
-  GVfsBackendTrash *trash_backend = G_VFS_BACKEND_TRASH (backend);
-  GList *names;
-
-  names = enumerate_root (backend, NULL);
-  trash_backend->num_top_files = g_list_length (names);
-  trash_backend->top_files = g_list_sort (names, (GCompareFunc)strcmp);
-  do_create_root_monitor (backend);
-  
-  g_vfs_job_succeeded (G_VFS_JOB (job));
- }
+static gboolean
+trash_backend_seek_on_read (GVfsBackend       *backend,
+                            GVfsJobSeekRead   *job,
+                            GVfsBackendHandle  handle,
+                            goffset            offset,
+                            GSeekType          type) 
+{
+  GError *error = NULL;
 
-static void
-do_open_for_read (GVfsBackend *backend,
-                  GVfsJobOpenForRead *job,
-                  const char *filename)
-{
-  char *trashdir, *topdir, *relative_path, *trashfile;
-
-  if (!decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir)) 
-    g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
-                      G_IO_ERROR_IS_DIRECTORY,
-                      _("Can't open directory"));
-  else
+  if (g_seekable_seek (handle, offset, type, NULL, &error))
     {
-      GFile *file;
-      char *dir;
-      GError *error;
-      GFileInputStream *stream;
-      
-      dir = g_build_filename (trashdir, "files", trashfile, relative_path, NULL);
-      file = g_file_new_for_path (dir);
-      
-      error = NULL;
-      stream = g_file_read (file,
-                            G_VFS_JOB (job)->cancellable,
-                            &error);
-      g_object_unref (file);
+      g_vfs_job_seek_read_set_offset (job, g_seekable_tell (handle));
+      g_vfs_job_succeeded (G_VFS_JOB (job));
 
-      if (stream)
-        {
-          g_vfs_job_open_for_read_set_handle (job, stream);
-          g_vfs_job_open_for_read_set_can_seek  (job,
-                                                 g_seekable_can_seek (G_SEEKABLE (stream)));
-          g_vfs_job_succeeded (G_VFS_JOB (job));
-        }
-      else
-        {
-          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-          g_error_free (error);
-        }
-      
-      g_free (trashdir);
-      g_free (trashfile);
-      g_free (relative_path);
-      g_free (topdir);
+      return TRUE;
     }
-}
 
-static void
-do_read (GVfsBackend *backend,
-         GVfsJobRead *job,
-         GVfsBackendHandle _handle,
-         char *buffer,
-         gsize bytes_requested)
-{
-  GInputStream *stream;
-  gssize res;
-  GError *error;
-
-  stream = G_INPUT_STREAM (_handle);
-
-  error = NULL;
-  res = g_input_stream_read (stream,
-                             buffer, bytes_requested,
-                             G_VFS_JOB (job)->cancellable,
-                             &error);
+  g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
 
-  if (res != -1)
-    {
-      g_vfs_job_read_set_size (job, res);
-      g_vfs_job_succeeded (G_VFS_JOB (job));
-    }
-  else
-    {
-      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-      g_error_free (error);
-    }
+  return TRUE;
 }
 
-static void
-do_seek_on_read (GVfsBackend *backend,
-                 GVfsJobSeekRead *job,
-                 GVfsBackendHandle _handle,
-                 goffset    offset,
-                 GSeekType  type)
-{
-  GFileInputStream *stream;
-  GError *error;
-
-  stream = G_FILE_INPUT_STREAM (_handle);
-
-  error = NULL;
-  if (g_seekable_seek (G_SEEKABLE (stream),
-                       offset, type,
-                       G_VFS_JOB (job)->cancellable,
-                       &error))
+static gboolean
+trash_backend_close_read (GVfsBackend       *backend,
+                          GVfsJobCloseRead  *job,
+                          GVfsBackendHandle  handle)
+{
+  GError *error = NULL;
+
+  if (g_input_stream_close (handle, NULL, &error))
     {
-      g_vfs_job_seek_read_set_offset (job,
-                                      g_seekable_tell (G_SEEKABLE (stream)));
       g_vfs_job_succeeded (G_VFS_JOB (job));
-    }
-  else
-    {
-      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-      g_error_free (error);
-    }
-}
 
-static void
-do_close_read (GVfsBackend *backend,
-               GVfsJobCloseRead *job,
-               GVfsBackendHandle _handle)
-{
-  GInputStream *stream;
-  GError *error;
-
-  stream = G_INPUT_STREAM (_handle);
-
-  error = NULL;
-  if (g_input_stream_close (stream,
-                            G_VFS_JOB (job)->cancellable,
-                            &error))
-    g_vfs_job_succeeded (G_VFS_JOB (job));
-  else
-    {
-      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-      g_error_free (error);
+      return TRUE;
     }
-}
 
-typedef struct {
-  GVfsBackend *backend;
-  GList *names;
-} SetHasTrashFilesData;
+  g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+
+  return TRUE;
+}
 
 static gboolean
-set_trash_files (gpointer _data)
+trash_backend_delete (GVfsBackend   *vfs_backend,
+                      GVfsJobDelete *job,
+                      const char    *filename)
 {
-  GVfsMonitor *vfs_monitor, *file_vfs_monitor;
-  GVfsBackendTrash *trash_backend;
-  SetHasTrashFilesData *data = _data;
-  GList *new_list, *old_list;
-  guint old_count, new_count;
-
-  trash_backend = G_VFS_BACKEND_TRASH (data->backend);
-  new_list = g_list_sort (data->names, (GCompareFunc)strcmp);
-  g_slice_free (SetHasTrashFilesData, data);
-
-  old_list = trash_backend->top_files;
-  old_count = trash_backend->num_top_files;
-
-  /* do the replacement now, before we send notifications.  */
-  new_count = g_list_length (new_list);
-  trash_backend->num_top_files = new_count;
-  trash_backend->top_files = new_list;
- 
-  G_LOCK (root_monitor);
-  vfs_monitor = NULL;
-  if (trash_backend->vfs_monitor)
-    vfs_monitor = g_object_ref (trash_backend->vfs_monitor);
-  
-  file_vfs_monitor = NULL;
-  if (trash_backend->file_vfs_monitor)
-    file_vfs_monitor = g_object_ref (trash_backend->file_vfs_monitor);
-  G_UNLOCK (root_monitor);
+  GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
+  GError *error = NULL;
 
-  if (vfs_monitor)
+  if (filename[1] == '\0')
+    g_set_error (&error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+                 _("Can't delete trash"));
+  else
     {
-      GList *added, *removed;
-      GList *new, *old, *l;
-      char *name;
-      int cmp;
+      gboolean is_toplevel;
+      TrashItem *item;
+      GFile *real;
 
-      added = NULL;
-      removed = NULL;
+      real = trash_backend_get_file (backend, filename,
+                                     &item, &is_toplevel, &error);
 
-      new = new_list;
-      old = old_list;
-      
-      while (new != NULL || old != NULL)
+      if (real)
         {
-          if (new == NULL)
-            {
-              /* old deleted */
-              removed = g_list_prepend (removed, old->data);
-              old = old->next;
-            }
-          else if (old == NULL)
-            {
-              /* new added */
-              added = g_list_prepend (added, new->data);
-              new = new->next;
-            }
-          else if ((cmp = strcmp (new->data, old->data)) == 0)
-            {
-              old = old->next;
-              new = new->next;
-            }
-          else if (cmp < 0)
-            {
-              /* new added */
-              added = g_list_prepend (added, new->data);
-              new = new->next;
-            }
-          else if (cmp > 0)
+          if (!is_toplevel)
+            g_set_error (&error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+                         _("Items in the trash may not be modified"));
+
+          else
             {
-              /* old deleted */
-              removed = g_list_prepend (removed, old->data);
-              old = old->next;
-            }
-        }
+              if (trash_item_delete (item, &error))
+                {
+                  g_vfs_job_succeeded (G_VFS_JOB (job));
 
-      for (l = removed; l != NULL; l = l->next)
-        {
-          name = g_strconcat ("/", l->data, NULL);
-          g_vfs_monitor_emit_event (vfs_monitor,
-                                    G_FILE_MONITOR_EVENT_DELETED,
-                                    name,
-                                    NULL);
-          g_free (name);
-        }
-      g_list_free (removed);
-      
-      for (l = added; l != NULL; l = l->next)
-        {
-          name = g_strconcat ("/", l->data, NULL);
-          g_vfs_monitor_emit_event (vfs_monitor,
-                                    G_FILE_MONITOR_EVENT_CREATED,
-                                    name,
-                                    NULL);
-          g_free (name);
-        }
-      g_list_free (added);
-      
-      if (new_count != old_count)
-        {
-          /* trash::item-count changed. */
-          /* icon change only ever occurs when item-count also changes */
-          g_vfs_monitor_emit_event (vfs_monitor,
-                                    G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
-                                    "/",
-                                    NULL);
-        }
-      
-      g_object_unref (vfs_monitor);
-    }
+                  return TRUE;
+                }
+            }
 
-  if (file_vfs_monitor)
-    {
-      if (new_count != old_count)
-        {
-          /* trash::item-count changed. */
-          /* icon change only ever occurs when item-count also changes */
-          g_vfs_monitor_emit_event (file_vfs_monitor,
-                                    G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
-                                    "/",
-                                    NULL);
+          trash_item_unref (item);
         }
-      
-      g_object_unref (file_vfs_monitor);
+ 
     }
 
-  g_list_foreach (old_list, (GFunc)g_free, NULL);
-  g_list_free (old_list);
+  g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
   
-  return FALSE;
+  return TRUE;
 }
+
 static void
-queue_set_trash_files (GVfsBackend *backend,
-                       GList *names)
+trash_backend_add_info (TrashItem *item,
+                        GFileInfo *info,
+                        gboolean   is_toplevel)
 {
-  SetHasTrashFilesData *data;
+  GFile *original;
 
-  data = g_slice_new (SetHasTrashFilesData);
-  data->backend = backend;
-  data->names = names;
-  g_idle_add (set_trash_files, data);
-}
+  if (is_toplevel && item)
+    {
+      original = trash_item_get_original (item);
 
-static void
-add_extra_trash_info (GFileInfo *file_info,
-                      const char *topdir,
-                      const char *info_dir,
-                      const char *filename,
-                      const char *relative_path)
-{
-  char *info_filename;
-  char *info_path;
-  char *orig_path, *orig_path_key, *orig_path_unescaped, *date;
-  GKeyFile *keyfile;
-  char *display_name;
-  char *desc;
+      if (original)
+        {
+          gchar *basename;
 
-  /* Override all writability */
-  g_file_info_set_attribute_boolean (file_info,
+          basename = g_file_get_basename (original);
+          /* XXX: utf8ify or something... */
+          g_file_info_set_display_name (info, basename);
+          g_free (basename);
+        }
+    }
+
+  g_file_info_set_attribute_boolean (info,
                                      G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
                                      FALSE);
-  g_file_info_set_attribute_boolean (file_info,
+  g_file_info_set_attribute_boolean (info,
                                      G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
                                      FALSE);
-  g_file_info_set_attribute_boolean (file_info,
+  g_file_info_set_attribute_boolean (info,
                                      G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME,
                                      FALSE);
-  g_file_info_set_attribute_boolean (file_info,
+  g_file_info_set_attribute_boolean (info,
                                      G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
                                      FALSE);
-  
-  /* But we can delete */
-  g_file_info_set_attribute_boolean (file_info,
+  g_file_info_set_attribute_boolean (info,
                                      G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE,
-                                     TRUE);
-  
-  info_filename = g_strconcat (filename, ".trashinfo", NULL);
-  info_path = g_build_filename (info_dir, info_filename, NULL);
-  g_free (info_filename);
+                                     is_toplevel);
+}
+
+static gboolean
+trash_backend_enumerate (GVfsBackend           *vfs_backend,
+                         GVfsJobEnumerate      *job,
+                         const char            *filename,
+                         GFileAttributeMatcher *attribute_matcher,
+                         GFileQueryInfoFlags    flags)
+{
+  GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
+
+  g_assert (filename[0] == '/');
+
+  trash_watcher_rescan (backend->watcher);
 
-  keyfile = g_key_file_new ();
-  if (g_key_file_load_from_file (keyfile, info_path, G_KEY_FILE_NONE, NULL))
+  if (filename[1])
+    /* not root case */
     {
-      orig_path_key = g_key_file_get_string (keyfile, "Trash Info", "Path", NULL);
-      if (orig_path_key)
+      GError *error = NULL;
+      GFile *real;
+
+      real = trash_backend_get_file (backend, filename, NULL, NULL, &error);
+
+      if (real)
         {
-          orig_path_unescaped = g_uri_unescape_string (orig_path_key, "");
+          GFileEnumerator *enumerator;
 
-          if (orig_path_unescaped)
+          enumerator = g_file_enumerate_children (real, job->attributes,
+                                                  job->flags, NULL, &error);
+
+          if (enumerator)
             {
-              if (g_path_is_absolute (orig_path_unescaped))
-                orig_path = g_build_filename (orig_path_unescaped, relative_path, NULL);
-              else
-                orig_path = g_build_filename (topdir, orig_path_unescaped, relative_path, NULL);
-              g_free (orig_path_unescaped);
-
-              /* Set display name and edit name based of original basename */
-              display_name = g_filename_display_basename (orig_path);
-              g_file_info_set_edit_name (file_info, display_name);
-              
-              if (strstr (display_name, "\357\277\275") != NULL)
+              GFileInfo *info;
+
+              g_vfs_job_succeeded (G_VFS_JOB (job));
+
+              while ((info = g_file_enumerator_next_file (enumerator,
+                                                          NULL, &error)))
                 {
-                  char *p = display_name;
-                  display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL);
-                  g_free (p);
-                  
-                  g_file_info_set_attribute_string (file_info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, NULL);
+                  trash_backend_add_info (NULL, info, FALSE);
+                  g_vfs_job_enumerate_add_info (job, info);
+                  g_object_unref (info);
                 }
-              else
-		{
-		  g_file_info_set_attribute_string (file_info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, display_name);
-		  g_file_info_set_attribute_string (file_info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, display_name);
-		}
-                
-              g_file_info_set_display_name (file_info, display_name);
-
-              desc = g_strdup_printf (_("%s (in trash)"), display_name);
-              g_file_info_set_attribute_string (file_info, G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION, desc);
-              g_free (desc);
-              
-              g_free (display_name);
-              
-              g_file_info_set_attribute_byte_string (file_info,
-                                                     "trash::orig-path",
-                                                     orig_path);
-              g_free (orig_path);
-            }
 
-          g_free (orig_path_key);
-        }
-      
-      date = g_key_file_get_string (keyfile, "Trash Info", "DeletionDate", NULL);
-      if (date && g_utf8_validate (date, -1, NULL))
-        g_file_info_set_attribute_string (file_info,
-                                          "trash::deletion-date",
-                                          date);
-      g_free (date);
-    }
-  g_key_file_free (keyfile);
-  g_free (info_path);
-}
+              g_object_unref (enumerator);
 
-static void
-enumerate_root_trashdir (GVfsBackend *backend,
-                         GVfsJobEnumerate *job,
-                         const char *topdir,
-                         const char *trashdir,
-                         GList **names)
-{ 
-  GFile *file, *files_file;
-  GFileEnumerator *enumerator;
-  char *info_dir;
+              if (!error)
+                {
+                  g_vfs_job_enumerate_done (job);
 
-  info_dir = g_build_filename (trashdir, "info", NULL);
-  
-  file = g_file_new_for_path (trashdir);
-  files_file = g_file_get_child (file, "files");
-  enumerator =
-    g_file_enumerate_children (files_file,
-                               job ? job->attributes : G_FILE_ATTRIBUTE_STANDARD_NAME,
-                               job ? job->flags : 0,
-                               job ? G_VFS_JOB (job)->cancellable : NULL,
-                               NULL);
-  g_object_unref (files_file);
-  g_object_unref (file);
+                  return TRUE;
+                }
+            }
+        }
 
-  if (enumerator)
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error); /* wrote when drunk at uds, plz check later, k thx. XXX */
+    }
+  else
     {
-      GFileInfo *info;
+      GCancellable *cancellable;
+      GList *items;
+      GList *node;
+
+      cancellable = G_VFS_JOB (job)->cancellable;
+      g_vfs_job_succeeded (G_VFS_JOB (job));
 
-      while ((info = g_file_enumerator_next_file (enumerator,
-                                                  job ? G_VFS_JOB (job)->cancellable : NULL,
-                                                  NULL)) != NULL)
+      items = trash_root_get_items (backend->root);
+
+      for (node = items; node; node = node->next)
         {
-          const char *name;
-          char *new_name, *new_name_escaped;
+          TrashItem *item = node->data;
+          GFileInfo *info;
+          GFile *original;
+
+          info = g_file_query_info (trash_item_get_file (item),
+                                    job->attributes,
+                                    flags, cancellable, NULL);
 
-          name = g_file_info_get_name (info);
+          g_file_info_set_attribute_mask (info, attribute_matcher);
+          trash_backend_add_info (item, info, TRUE);
+          g_file_info_set_name (info, trash_item_get_escaped_name (item));
+
+          original = trash_item_get_original (item);
+          if (original)
+            {
+              char *basename;
 
-          /* Get the display name, etc */
-          add_extra_trash_info (info,
-                                topdir,
-                                info_dir,
-                                name,
-                                NULL);
-
-          /* Update the name to also have the trash dir */
-          new_name = g_build_filename (trashdir, name, NULL);
-          new_name_escaped = escape_pathname (new_name);
-          g_free (new_name);
-          g_file_info_set_name (info, new_name_escaped);
+              basename = g_file_get_basename (original);
 
-          *names = g_list_prepend (*names, new_name_escaped); /* Takes over ownership */
+              /* XXX utf8 */
+              g_file_info_set_display_name (info, basename);
+              g_free (basename);
+            }
 
-          if (job)
-            g_vfs_job_enumerate_add_info (job, info);
+          g_vfs_job_enumerate_add_info (job, info);
           g_object_unref (info);
         }
-      
-      g_file_enumerator_close (enumerator,
-                               job ? G_VFS_JOB (job)->cancellable : NULL,
-                               NULL);
-      g_object_unref (enumerator);
     }
 
-  g_free (info_dir);
-}
-
-static GList *
-enumerate_root (GVfsBackend *backend,
-                GVfsJobEnumerate *job)
-{
-  GList *trashdirs, *l;
-  char *trashdir;
-  char *topdir;
-  GList *names;
-
-  trashdirs = list_trash_dirs ();
+  g_vfs_job_enumerate_done (job);
 
-  names = NULL;
-  for (l = trashdirs; l != NULL; l = l->next)
-    {
-      trashdir = l->data;
-      topdir = get_top_dir_for_trash_dir (trashdir);
+  return TRUE;
+}
 
-      enumerate_root_trashdir (backend, job, topdir, trashdir, &names);
-      g_free (trashdir);
-      g_free (topdir);
-    }
-  g_list_free (trashdirs);
+static gboolean
+trash_backend_mount (GVfsBackend  *vfs_backend,
+                     GVfsJobMount *job,
+                     GMountSpec   *mount_spec,
+                     GMountSource *mount_source,
+                     gboolean      is_automount)
+{
+  GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
+
+  backend->file_monitor = NULL;
+  backend->dir_monitor = NULL;
+  backend->root = trash_root_new (trash_backend_item_created,
+                                  trash_backend_item_deleted,
+                                  trash_backend_item_count_changed,
+                                  backend);
+  backend->watcher = trash_watcher_new (backend->root);
 
-  if (job)
-    g_vfs_job_enumerate_done (job);
+  g_vfs_job_succeeded (G_VFS_JOB (job));
 
-  return names;
+  return TRUE;
 }
 
-static void
-do_enumerate (GVfsBackend *backend,
-              GVfsJobEnumerate *job,
-              const char *filename,
-              GFileAttributeMatcher *attribute_matcher,
-              GFileQueryInfoFlags flags)
+static gboolean
+trash_backend_query_info (GVfsBackend           *vfs_backend,
+                          GVfsJobQueryInfo      *job,
+                          const char            *filename,
+                          GFileQueryInfoFlags    flags,
+                          GFileInfo             *info,
+                          GFileAttributeMatcher *matcher)
 {
-  char *trashdir, *topdir, *relative_path, *trashfile;
-  GList *names;
+  GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
 
-  if (!decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir))
-    {
-      /* Always succeeds */
-      g_vfs_job_succeeded (G_VFS_JOB (job));
-      
-      names = enumerate_root (backend, job);
-      queue_set_trash_files (backend, names);
-    }
-  else
+  g_assert (filename[0] == '/');
+
+  if (filename[1])
     {
-      GFile *file;
-      GFileEnumerator *enumerator;
-      GFileInfo *info;
-      char *dir;
-      GError *error;
-
-      dir = g_build_filename (trashdir, "files", trashfile, relative_path, NULL);
-      file = g_file_new_for_path (dir);
-      error = NULL;
-      enumerator =
-        g_file_enumerate_children (file,
-                                   job->attributes,
-                                   job->flags,
-                                   G_VFS_JOB (job)->cancellable,
-                                   &error);
-      g_free (dir);
-      g_object_unref (file);
+      GError *error = NULL;
+      gboolean is_toplevel;
+      TrashItem *item;
+      GFile *real;
+
+      real = trash_backend_get_file (backend, filename,
+                                     &item, &is_toplevel, &error);
 
-      if (enumerator)
+      if (real)
         {
-          g_vfs_job_succeeded (G_VFS_JOB (job));
-          
-          while ((info = g_file_enumerator_next_file (enumerator,
-                                                      G_VFS_JOB (job)->cancellable,
-                                                      NULL)) != NULL)
+          GFileInfo *real_info;
+
+          real_info = g_file_query_info (real, job->attributes,
+                                         flags, NULL, &error);
+
+          if (real_info)
             {
-              g_vfs_job_enumerate_add_info   (job, info);
-              g_object_unref (info);
+              g_file_info_copy_into (real_info, info);
+              trash_backend_add_info (item, info, is_toplevel);
+              g_vfs_job_succeeded (G_VFS_JOB (job));
+
+              return TRUE;
             }
-          
-          g_file_enumerator_close (enumerator,
-                                   G_VFS_JOB (job)->cancellable,
-                                   NULL);
-          g_object_unref (enumerator);
-               
-          g_vfs_job_enumerate_done (job);
-        }
-      else
-        {
-          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-          g_error_free (error);
+
+          trash_item_unref (item);
         }
 
-      g_free (trashdir);
-      g_free (trashfile);
-      g_free (relative_path);
-      g_free (topdir);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
     }
-}
-
-static void
-do_query_info (GVfsBackend *backend,
-               GVfsJobQueryInfo *job,
-               const char *filename,
-               GFileQueryInfoFlags flags,
-               GFileInfo *info,
-               GFileAttributeMatcher *matcher)
-{
-  char *trashdir, *topdir, *relative_path, *trashfile;
-  GIcon *icon;
-
-  if (!decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir))
+  else
     {
-      GVfsBackendTrash *trash_backend = G_VFS_BACKEND_TRASH (backend);
+      GIcon *icon;
+      int n_items;
+
+      n_items = trash_root_get_n_items (backend->root);
 
-      /* The trash:/// root */
       g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
       g_file_info_set_name (info, "/");
       /* Translators: this is the display name of the backend */
       g_file_info_set_display_name (info, _("Trash"));
       g_file_info_set_content_type (info, "inode/directory");
 
-      if (trash_backend->top_files != NULL)
-        icon = g_themed_icon_new ("user-trash-full");
-      else
-        icon = g_themed_icon_new ("user-trash");
-        
+      icon = g_themed_icon_new (n_items ? "user-trash-full" : "user-trash");
       g_file_info_set_icon (info, icon);
       g_object_unref (icon);
-     
-      g_file_info_set_attribute_boolean (info,
-                                         G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL,
-                                         TRUE);
-      g_file_info_set_attribute_boolean (info,
-                                         G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
-                                         TRUE);
-      g_file_info_set_attribute_boolean (info,
-                                         G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
-                                         FALSE);
-      g_file_info_set_attribute_boolean (info,
-                                         G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
-                                         FALSE);
-      g_file_info_set_attribute_boolean (info,
-                                         G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE,
-                                         FALSE);
-      g_file_info_set_attribute_boolean (info,
-                                         G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
-                                         FALSE);
-      g_file_info_set_attribute_boolean (info,
-                                         G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME,
-                                         FALSE);
-      g_file_info_set_attribute_uint32 (info,
-                                        G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT,
-                                        trash_backend->num_top_files);
-      
-      g_vfs_job_succeeded (G_VFS_JOB (job));
-    }
-  else
-    {
-      GFile *file;
-      GFileInfo *local_info;
-      char *path;
-      GError *error; 
-      char *info_dir;
-      char *basename;
-     
-      path = g_build_filename (trashdir, "files", trashfile, relative_path, NULL);
-      file = g_file_new_for_path (path);
-      g_free (path);
-      
-      error = NULL;
-      local_info = g_file_query_info (file,
-                                      job->attributes,
-                                      job->flags,
-                                      G_VFS_JOB (job)->cancellable,
-                                      &error);
-      g_object_unref (file);
-      
-      if (local_info)
-        {
-          g_file_info_copy_into (local_info, info);
 
-          basename = g_path_get_basename (filename);
-          g_file_info_set_name (info, basename);
-          g_free (basename);
-      
-          info_dir = g_build_filename (trashdir, "info", NULL);
-          add_extra_trash_info (info,
-                                topdir,
-                                info_dir,
-                                trashfile,
-                                relative_path);
-          g_free (info_dir);
-          
-          g_object_unref (local_info);
-          g_vfs_job_succeeded (G_VFS_JOB (job));
-        }
-      else
-        {
-          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-          g_error_free (error);
-        }
-  
-      g_free (trashdir);
-      g_free (trashfile);
-      g_free (relative_path);
-      g_free (topdir);
-    }
-}
+      g_file_info_set_attribute_uint32 (info, "trash::item-count", n_items);
 
-static void
-do_delete (GVfsBackend *backend,
-           GVfsJobDelete *job,
-           const char *filename)
-{
-  char *trashdir, *topdir, *relative_path, *trashfile;
-  
-  if (!decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir))
-    g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
-                      G_IO_ERROR_PERMISSION_DENIED,
-                      _("Can't delete trash"));
-  else
-    {
-      GFile *file;
-      GError *error; 
-      char *path, *info_filename, *info_path;
-
-      path = g_build_filename (trashdir, "files", trashfile, relative_path, NULL);
-      file = g_file_new_for_path (path);
-      g_free (path);
-      
-      error = NULL;
-      if (g_file_delete (file,
-                         G_VFS_JOB (job)->cancellable,
-                         &error))
-        {
-          g_vfs_job_succeeded (G_VFS_JOB (job));
-
-          if (relative_path == NULL)
-            {
-              info_filename = g_strconcat (trashfile, ".trashinfo", NULL);
-              info_path = g_build_filename (trashdir, "info", info_filename, NULL);
-              g_free (info_filename);
-              g_unlink (info_path);
-              g_free (info_path);
-            }
-        }
-      else
-        {
-          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-          g_error_free (error);
-        }
-      
-      g_object_unref (file);
-  
-      g_free (trashdir);
-      g_free (trashfile);
-      g_free (relative_path);
-      g_free (topdir);
-    }
-}
-
-typedef struct {
-  GVfsMonitor *vfs_monitor;
-  GObject *monitor;
-  GFile *base_file;
-  char *base_path;
-} MonitorProxy;
-
-static void
-monitor_proxy_free (MonitorProxy *proxy)
-{
-  g_object_unref (proxy->monitor);
-  g_object_unref (proxy->base_file);
-  g_free (proxy->base_path);
-  g_free (proxy);
-}
-
-static char *
-proxy_get_trash_path (MonitorProxy *proxy,
-                      GFile *file)
-{
-  char *file_path, *basename;
-  
-  if (g_file_equal (file, proxy->base_file))
-    file_path = g_strdup (proxy->base_path);
-  else
-    {
-      basename = g_file_get_relative_path (proxy->base_file, file);
-      file_path = g_build_filename (proxy->base_path, basename, NULL);
-      g_free (basename);
+      g_vfs_job_succeeded (G_VFS_JOB (job));
     }
 
-  return file_path;
+  return TRUE;
 }
 
-static void
-proxy_changed (GFileMonitor* monitor,
-               GFile* file,
-               GFile* other_file,
-               GFileMonitorEvent event_type,
-               MonitorProxy *proxy)
-{
-  char *file_path;
-  char *other_file_path;
+static gboolean
+trash_backend_query_fs_info (GVfsBackend           *vfs_backend,
+                             GVfsJobQueryFsInfo    *job,
+                             const char            *filename,
+                             GFileInfo             *info,
+                             GFileAttributeMatcher *matcher)
+{
+  g_file_info_set_attribute_string (info,
+                                    G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
+                                    "trash");
 
-  file_path = proxy_get_trash_path (proxy, file);
+  g_file_info_set_attribute_boolean (info,
+                                     G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
+                                     FALSE);
 
-  if (other_file)
-    other_file_path = proxy_get_trash_path (proxy, other_file);
-  else
-    other_file_path = NULL;
-  
-  g_vfs_monitor_emit_event (proxy->vfs_monitor,
-                            event_type,
-                            file_path,
-                            other_file_path);
+  g_file_info_set_attribute_uint32 (info,
+                                    G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW,
+                                    G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL);
 
-  g_free (file_path);
-  g_free (other_file_path);
-}
+  g_vfs_job_succeeded (G_VFS_JOB (job));
 
-static void
-trash_dir_changed (GFileMonitor           *monitor,
-                   GFile                  *child,
-                   GFile                  *other_file,
-                   GFileMonitorEvent       event_type,
-                   GVfsBackendTrash       *backend)
-{
-  if (event_type == G_FILE_MONITOR_EVENT_DELETED ||
-      event_type == G_FILE_MONITOR_EVENT_CREATED)
-    schedule_update_trash_files (backend, FALSE);
+  return TRUE;
 }
 
-static void
-update_trash_dir_monitors (GVfsBackendTrash *backend)
+static gboolean
+trash_backend_create_dir_monitor (GVfsBackend          *vfs_backend,
+                                  GVfsJobCreateMonitor *job,
+                                  const char           *filename,
+                                  GFileMonitorFlags     flags)
 {
-  GFile *file;
-  char *trashdir;
-  GFileMonitor *monitor;
-  GList *monitors, *l, *trashdirs;
-
-  /* Remove old monitors */
-
-  G_LOCK (root_monitor);
-  monitors = backend->trash_dir_monitors;
-  backend->trash_dir_monitors = NULL;
-  G_UNLOCK (root_monitor);
-  
-  for (l = monitors; l != NULL; l = l->next)
-    {
-      monitor = l->data;
-      g_object_unref (monitor);
-    }
-  
-  g_list_free (monitors);
+  GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
+  GVfsMonitor *monitor;
 
-  monitors = NULL;
+  if (filename[1])
+    monitor = g_vfs_monitor_new (vfs_backend);
+  else
+    monitor = trash_backend_get_dir_monitor (backend, TRUE);
 
-  /* Add new ones for all trash dirs */
-  
-  trashdirs = list_trash_dirs ();
-  for (l = trashdirs; l != NULL; l = l->next)
-    {
-      char *filesdir;
-      
-      trashdir = l->data;
-      
-      filesdir = g_build_filename (trashdir, "files", NULL);
-      file = g_file_new_for_path (filesdir);
-      g_free (filesdir);
-      monitor = g_file_monitor_directory (file, 0, NULL, NULL);
-      g_object_unref (file);
-      
-      if (monitor)
-        {
-          g_signal_connect (monitor, "changed", G_CALLBACK (trash_dir_changed), backend);
-          monitors = g_list_prepend (monitors, monitor);
-        }
-      g_free (trashdir);
-    }
-  
-  g_list_free (trashdirs);
+  g_vfs_job_create_monitor_set_monitor (job, monitor);
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+  g_object_unref (monitor);
 
-  G_LOCK (root_monitor);
-  backend->trash_dir_monitors = g_list_concat (backend->trash_dir_monitors, monitors);
-  G_UNLOCK (root_monitor);
+  return TRUE;
 }
 
-
-static gpointer
-update_trash_files_in_thread (GVfsBackendTrash *backend)
+static gboolean
+trash_backend_create_file_monitor (GVfsBackend          *vfs_backend,
+                                   GVfsJobCreateMonitor *job,
+                                   const char           *filename,
+                                   GFileMonitorFlags     flags)
 {
-  GList *names;
-  gboolean do_monitors;
-
-  G_LOCK (root_monitor);
-  backend->trash_file_update_running = TRUE;
-  backend->trash_file_update_scheduled = FALSE;
-  do_monitors = backend->trash_file_update_monitors_scheduled;
-  backend->trash_file_update_monitors_scheduled = FALSE;
-  G_UNLOCK (root_monitor);
+  GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
+  GVfsMonitor *monitor;
 
- loop:
-  if (do_monitors)
-    update_trash_dir_monitors (backend);
-  
-  names = enumerate_root (G_VFS_BACKEND (backend), NULL);
-  queue_set_trash_files (G_VFS_BACKEND (backend), names);
-
-  G_LOCK (root_monitor);
-  if (backend->trash_file_update_scheduled)
-    {
-      backend->trash_file_update_scheduled = FALSE;
-      do_monitors = backend->trash_file_update_monitors_scheduled;
-      backend->trash_file_update_monitors_scheduled = FALSE;
-      G_UNLOCK (root_monitor);
-      goto loop;
-    }
-  
-  backend->trash_file_update_running = FALSE;
-  G_UNLOCK (root_monitor);
-  
-  return NULL;
-  
-}
+  if (filename[1])
+    monitor = g_vfs_monitor_new (vfs_backend);
+  else
+    monitor = trash_backend_get_file_monitor (backend, TRUE);
 
-static void
-schedule_update_trash_files (GVfsBackendTrash *backend,
-                             gboolean update_trash_dirs)
-{
-  G_LOCK (root_monitor);
+  g_vfs_job_create_monitor_set_monitor (job, monitor);
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+  g_object_unref (monitor);
 
-  backend->trash_file_update_scheduled = TRUE;
-  backend->trash_file_update_monitors_scheduled |= update_trash_dirs;
-  
-  if (!backend->trash_file_update_running)
-    g_thread_create ((GThreadFunc)update_trash_files_in_thread, backend, FALSE, NULL);
-      
-  G_UNLOCK (root_monitor);
+  return TRUE;
 }
 
 static void
-mounts_changed (GUnixMountMonitor *mount_monitor,
-                GVfsBackendTrash *backend)
+trash_backend_finalize (GObject *object)
 {
-  schedule_update_trash_files (backend, TRUE);
-}
+  GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (object);
 
-static void
-vfs_monitor_gone 	(gpointer      data,
-                   GObject      *where_the_object_was)
-{
-  GVfsBackendTrash *backend;
+  /* get rid of these first to stop a flood of event notifications
+   * from being emitted while we're tearing down the TrashWatcher
+   */
+  if (backend->file_monitor)
+    g_object_unref (backend->file_monitor);
+  backend->file_monitor = NULL;
 
-  backend = G_VFS_BACKEND_TRASH (data);
-  
-  G_LOCK (root_monitor);
-  g_assert ((void *)backend->vfs_monitor == (void *)where_the_object_was);
-  backend->vfs_monitor = NULL;
-  g_object_unref (backend->mount_monitor);
-  backend->mount_monitor = NULL;
-
-  g_list_foreach (backend->trash_dir_monitors, (GFunc)g_object_unref, NULL);
-  g_list_free (backend->trash_dir_monitors);
-  backend->trash_dir_monitors = NULL;
+  if (backend->dir_monitor)
+    g_object_unref (backend->dir_monitor);
+  backend->dir_monitor = NULL;
 
-  backend->trash_file_update_scheduled = FALSE;
-  backend->trash_file_update_monitors_scheduled = FALSE;
-  
-  G_UNLOCK (root_monitor);
-}
-
-static GVfsMonitor *
-do_create_root_monitor (GVfsBackend *backend)
-{
-  GVfsBackendTrash *trash_backend;
-  GVfsMonitor *vfs_monitor;
-  gboolean created;
-
-  trash_backend = G_VFS_BACKEND_TRASH (backend);
-  
-  created = FALSE;
-  G_LOCK (root_monitor);
-  if (trash_backend->vfs_monitor == NULL)
-    {
-      trash_backend->vfs_monitor = g_vfs_monitor_new (backend);
-      created = TRUE;
-    }
-  
-  vfs_monitor = trash_backend->vfs_monitor;
-  G_UNLOCK (root_monitor);
-  
-  if (created)
-    {
-      update_trash_dir_monitors (trash_backend);
-      trash_backend->mount_monitor = g_unix_mount_monitor_new ();
-      g_signal_connect (trash_backend->mount_monitor,
-                        "mounts_changed", G_CALLBACK (mounts_changed), backend);
-      
-      g_object_weak_ref (G_OBJECT (vfs_monitor),
-                         vfs_monitor_gone,
-                         backend);
-      
-    }
-  
-  return vfs_monitor;
+  trash_watcher_free (backend->watcher);
+  trash_root_free (backend->root);
 }
 
 static void
-do_create_dir_monitor (GVfsBackend *backend,
-                       GVfsJobCreateMonitor *job,
-                       const char *filename,
-                       GFileMonitorFlags flags)
+g_vfs_backend_trash_init (GVfsBackendTrash *backend)
 {
-  char *trashdir, *topdir, *relative_path, *trashfile;
-  GVfsMonitor *vfs_monitor;
-
-  if (!decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir))
-    {
-      /* The trash:/// root */
-      vfs_monitor = do_create_root_monitor (backend);
-      
-      g_vfs_job_create_monitor_set_monitor (job,
-                                            vfs_monitor);
-      g_vfs_job_succeeded (G_VFS_JOB (job));
-      
-      g_object_unref (vfs_monitor);
-    }
-  else
-    {
-      GFile *file;
-      char *path;
-      GFileMonitor *monitor;
-      MonitorProxy *proxy;
-      
-      path = g_build_filename (trashdir, "files", trashfile, relative_path, NULL);
-      file = g_file_new_for_path (path);
-      g_free (path);
-
-      monitor = g_file_monitor_directory (file,
-                                          flags,
-                                          G_VFS_JOB (job)->cancellable,
-					  NULL);
-      
-      if (monitor)
-        {
-          proxy = g_new0 (MonitorProxy, 1); 
-          proxy->vfs_monitor = g_vfs_monitor_new (backend);
-          proxy->monitor = G_OBJECT (monitor);
-          proxy->base_path = g_strdup (filename);
-          proxy->base_file = g_object_ref (file);
-          
-          g_object_set_data_full (G_OBJECT (proxy->vfs_monitor), "monitor-proxy", proxy,
-                                  (GDestroyNotify) monitor_proxy_free);
-          g_signal_connect (monitor, "changed", G_CALLBACK (proxy_changed), proxy);
-
-          g_vfs_job_create_monitor_set_monitor (job,
-                                                proxy->vfs_monitor);
-          g_object_unref (proxy->vfs_monitor);
-
-          g_vfs_job_succeeded (G_VFS_JOB (job));
-        }
-      else
-        {
-          g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
-                            G_IO_ERROR_NOT_SUPPORTED,
-                            _("Trash directory notification not supported"));
-        }
-      g_object_unref (file);
-  
-      g_free (trashdir);
-      g_free (trashfile);
-      g_free (relative_path);
-      g_free (topdir);
-    }
-}
-
-static void
-do_create_file_monitor (GVfsBackend *backend,
-                        GVfsJobCreateMonitor *job,
-                        const char *filename,
-                        GFileMonitorFlags flags)
-{
-  GVfsBackendTrash *trash_backend;
-  char *trashdir, *topdir, *relative_path, *trashfile;
-  GVfsMonitor *vfs_monitor;
-
-  trash_backend = G_VFS_BACKEND_TRASH (backend);
-  
-  if (!decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir))
-    {
-      /* The trash:/// root */
-      G_LOCK (root_monitor);
-      if (trash_backend->file_vfs_monitor == NULL)
-        trash_backend->file_vfs_monitor = g_vfs_monitor_new (backend);
-
-      vfs_monitor = trash_backend->file_vfs_monitor;
-      g_object_add_weak_pointer (G_OBJECT (vfs_monitor), (gpointer *)&trash_backend->file_vfs_monitor);
-      G_UNLOCK (root_monitor);
-      
-      g_vfs_job_create_monitor_set_monitor (job, vfs_monitor);
-      g_vfs_job_succeeded (G_VFS_JOB (job));
-      g_object_unref (vfs_monitor);
-    }
-  else
-    {
-      GFile *file;
-      char *path;
-      GFileMonitor *monitor;
-      MonitorProxy *proxy;
-      
-      path = g_build_filename (trashdir, "files", trashfile, relative_path, NULL);
-      file = g_file_new_for_path (path);
-      g_free (path);
-
-      monitor = g_file_monitor_file (file,
-                                     flags,
-                                     G_VFS_JOB (job)->cancellable,
-				     NULL);
-      
-      if (monitor)
-        {
-          proxy = g_new0 (MonitorProxy, 1); 
-          proxy->vfs_monitor = g_vfs_monitor_new (backend);
-          proxy->monitor = G_OBJECT (monitor);
-          proxy->base_path = g_strdup (filename);
-          proxy->base_file = g_object_ref (file);
-          
-          g_object_set_data_full (G_OBJECT (proxy->vfs_monitor), "monitor-proxy", proxy, (GDestroyNotify) monitor_proxy_free);
-          g_signal_connect (monitor, "changed", G_CALLBACK (proxy_changed), proxy);
-
-          g_vfs_job_create_monitor_set_monitor (job,
-                                                proxy->vfs_monitor);
-          g_object_unref (proxy->vfs_monitor);
-
-          g_vfs_job_succeeded (G_VFS_JOB (job));
-        }
-      else
-        {
-          g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
-                            G_IO_ERROR_NOT_SUPPORTED,
-                            _("Trash directory notification not supported"));
-        }
-      g_object_unref (file);
-  
-      g_free (trashdir);
-      g_free (trashfile);
-      g_free (relative_path);
-      g_free (topdir);
-    }
-}
-
-static void
-do_pull (GVfsBackend *backend,
-         GVfsJobPull *job,
-         const char *filename,
-         const char *local_destination,
-         GFileCopyFlags flags,
-         gboolean remove_source,
-         GFileProgressCallback progress_callback,
-         gpointer progress_callback_data)
-{
-  GFile *dst_file;
-  GFile *src_file;
-  char *trashdir, *topdir, *relative_path, *trashfile;
-  char *src_path;
-  gboolean res;
-  GError *error = NULL;
-
-  res = decode_path (filename, &trashdir, &trashfile, &relative_path, &topdir);
-
-  if (res == FALSE)
-    {
-      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
-                        G_IO_ERROR_IS_DIRECTORY,
-                        _("Can't open directory"));
-      return;
-    }
-
-  dst_file = g_file_new_for_path (local_destination);
-
-  src_path = g_build_filename (trashdir, "files", trashfile, relative_path, NULL);
-  src_file = g_file_new_for_path (src_path);
-
-  if (remove_source)
-    res = g_file_move (src_file,
-                       dst_file,
-                       flags,
-                       G_VFS_JOB (job)->cancellable,
-                       progress_callback,
-                       progress_callback_data,
-                       &error);
-  else
-    res = g_file_copy (src_file,
-                       dst_file,
-                       flags,
-                       G_VFS_JOB (job)->cancellable,
-                       progress_callback,
-                       progress_callback_data,
-                       &error);
-
-  if (res == FALSE)
-    {
-      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-      g_error_free (error);
-    }
-  else
-    {
-      g_vfs_job_succeeded (G_VFS_JOB (job));
-
-      if (relative_path == NULL)
-        {
-          char *info_filename, *info_path;
-
-          info_filename = g_strconcat (trashfile, ".trashinfo", NULL);
-          info_path = g_build_filename (trashdir, "info", info_filename, NULL);
-          g_free (info_filename);
-          g_unlink (info_path);
-          g_free (info_path);
-        }
-    }
+  GVfsBackend *vfs_backend = G_VFS_BACKEND (backend);
+  GMountSpec *mount_spec;
 
-  g_object_unref (src_file);
-  g_object_unref (dst_file);
+  /* translators: This is the name of the backend */
+  g_vfs_backend_set_display_name (vfs_backend, _("Trash"));
+  g_vfs_backend_set_icon_name (vfs_backend, "user-trash");
+  g_vfs_backend_set_user_visible (vfs_backend, FALSE);
 
-  g_free (trashdir);
-  g_free (trashfile);
-  g_free (relative_path);
-  g_free (topdir);
+  mount_spec = g_mount_spec_new ("trash");
+  g_vfs_backend_set_mount_spec (vfs_backend, mount_spec);
+  g_mount_spec_unref (mount_spec);
 }
 
-
 static void
-g_vfs_backend_trash_class_init (GVfsBackendTrashClass *klass)
+g_vfs_backend_trash_class_init (GVfsBackendTrashClass *class)
 {
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
-  
-  gobject_class->finalize = g_vfs_backend_trash_finalize;
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (class);
+
+  gobject_class->finalize = trash_backend_finalize;
 
-  backend_class->mount = do_mount;
-  backend_class->open_for_read = do_open_for_read;
-  backend_class->read = do_read;
-  backend_class->seek_on_read = do_seek_on_read;
-  backend_class->close_read = do_close_read;
-  backend_class->query_info = do_query_info;
-  backend_class->enumerate = do_enumerate;
-  backend_class->delete = do_delete;
-  backend_class->create_dir_monitor = do_create_dir_monitor;
-  backend_class->create_file_monitor = do_create_file_monitor;
-  backend_class->pull = do_pull;
+  backend_class->try_mount = trash_backend_mount;
+  backend_class->try_open_for_read = trash_backend_open_for_read;
+  backend_class->try_read = trash_backend_read;
+  backend_class->try_seek_on_read = trash_backend_seek_on_read;
+  backend_class->try_close_read = trash_backend_close_read;
+  backend_class->try_query_info = trash_backend_query_info;
+  backend_class->try_query_fs_info = trash_backend_query_fs_info;
+  backend_class->try_enumerate = trash_backend_enumerate;
+  backend_class->try_delete = trash_backend_delete;
+  backend_class->try_create_dir_monitor = trash_backend_create_dir_monitor;
+  backend_class->try_create_file_monitor = trash_backend_create_file_monitor;
 }

Modified: trunk/daemon/gvfsbackendtrash.h
==============================================================================
--- trunk/daemon/gvfsbackendtrash.h	(original)
+++ trunk/daemon/gvfsbackendtrash.h	Fri Dec 12 05:33:16 2008
@@ -1,50 +1,22 @@
-/* GIO - GLib Input, Output and Streaming Library
- * 
- * Copyright (C) 2006-2007 Red Hat, Inc.
+/*
+ * Copyright  2008 Ryan Lortie
  *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General
- * Public License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
- * Boston, MA 02111-1307, USA.
- *
- * Author: Alexander Larsson <alexl redhat com>
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
  */
 
-#ifndef __G_VFS_BACKEND_TRASH_H__
-#define __G_VFS_BACKEND_TRASH_H__
+#ifndef _gvfsbackendtrash_h_
+#define _gvfsbackendtrash_h_
 
 #include <gvfsbackend.h>
-#include <gmountspec.h>
-
-G_BEGIN_DECLS
-
-#define G_VFS_TYPE_BACKEND_TRASH         (g_vfs_backend_trash_get_type ())
-#define G_VFS_BACKEND_TRASH(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_BACKEND_TRASH, GVfsBackendTrash))
-#define G_VFS_BACKEND_TRASH_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_BACKEND_TRASH, GVfsBackendTrashClass))
-#define G_VFS_IS_BACKEND_TRASH(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_VFS_TYPE_BACKEND_TRASH))
-#define G_VFS_IS_BACKEND_TRASH_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_VFS_TYPE_BACKEND_TRASH))
-#define G_VFS_BACKEND_TRASH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_BACKEND_TRASH, GVfsBackendTrashClass))
-
-typedef struct _GVfsBackendTrash        GVfsBackendTrash;
-typedef struct _GVfsBackendTrashClass   GVfsBackendTrashClass;
-
-struct _GVfsBackendTrashClass
-{
-  GVfsBackendClass parent_class;
-};
 
-GType g_vfs_backend_trash_get_type (void) G_GNUC_CONST;
+#define G_VFS_TYPE_BACKEND_TRASH    (g_vfs_backend_trash_get_type ())
+#define G_VFS_BACKEND_TRASH(obj)    (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+                                     G_VFS_TYPE_BACKEND_TRASH, \
+                                     GVfsBackendTrash))
 
-G_END_DECLS
+typedef struct OPAQUE_TYPE__GVfsBackendTrash GVfsBackendTrash;
+GType g_vfs_backend_trash_get_type (void);
 
-#endif /* __G_VFS_BACKEND_TRASH_H__ */
+#endif /* _gvfsbackendtrash_h_ */

Added: trunk/daemon/trashlib/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/Makefile.am	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,15 @@
+noinst_LIBRARIES = libtrash.a
+
+libtrash_a_CFLAGS = $(GLIB_CFLAGS)
+
+libtrash_a_SOURCES = \
+	dirwatch.h	\
+	dirwatch.c	\
+	trashdir.h	\
+	trashdir.c	\
+	trashitem.h	\
+	trashitem.c	\
+       	trashwatcher.h	\
+       	trashwatcher.c	\
+	trashexpunge.h	\
+	trashexpunge.c

Added: trunk/daemon/trashlib/dirwatch.c
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/dirwatch.c	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,317 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#include <sys/stat.h>
+
+#include "dirwatch.h"
+
+/* DirWatch
+ *
+ * a directory watcher utility for use by the trash:/ backend.
+ *
+ * A DirWatch monitors a given directory for existence under a very
+ * specific set of circumstances.  When the directory comes into
+ * existence, the create() callback is invoked.  When the directory
+ * stops existing the destroy() callback is invoked.  If the directory
+ * initially exists, then create() is invoked before the call to
+ * dir_watch_new() returns.
+ *
+ * The directory to watch is considered to exist only if it is a
+ * directory (and not a symlink) and its parent directory also exists.
+ * A topdir must be given, which is always assumed to "exist".
+ *
+ * For example, if '/mnt/disk/.Trash/1000/files/' is monitored with
+ * '/mnt/disk/' as a topdir then the following conditions must be true
+ * in order for the directory to be reported as existing:
+ *
+ *   /mnt/disk/ is blindly assumed to exist
+ *   /mnt/disk/.Trash must be a directory (not a symlink)
+ *   /mnt/disk/.Trash/1000 must be a directory (not a symlink)
+ *   /mnt/disk/.Trash/1000/files must be a directory (not a symlink)
+ *
+ * If any of these ceases to be true (even momentarily), the directory
+ * will be reported as having been destroyed.  create() and destroy()
+ * callbacks are never issued spuriously (ie: two calls to one
+ * callback will never occur in a row).  Events where the directory
+ * exists momentarily might be missed, but events where the directory
+ * stops existing momentarily will (hopefully) always be reported.
+ * The first call (if it happens) will always be to create().
+ *
+ * This implementation is currently tweaked a bit for how GFileMonitor
+ * currently works with inotify.  If GFileMonitor's implementation is
+ * changed it might be a good idea to take another look at this code.
+ */
+
+struct OPAQUE_TYPE__DirWatch
+{
+  GFile *directory;
+  GFile *topdir;
+
+  DirWatchFunc create;
+  DirWatchFunc destroy;
+  gpointer user_data;
+  gboolean state;
+  gboolean active;
+
+  DirWatch *parent;
+
+  GFileMonitor *parent_monitor;
+};
+
+#ifdef DIR_WATCH_DEBUG
+# define dir_watch_created(watch) \
+    G_STMT_START {                                              \
+      char *path = g_file_get_path ((watch)->directory);        \
+      g_print (">> created '%s'\n", path);                      \
+      g_free (path);                                            \
+      (watch)->create ((watch)->user_data);                     \
+    } G_STMT_END
+
+# define dir_watch_destroyed(watch) \
+    G_STMT_START {                                              \
+      char *path = g_file_get_path ((watch)->directory);        \
+      g_print (">> destroyed '%s'\n", path);                    \
+      g_free (path);                                            \
+      (watch)->destroy ((watch)->user_data);                    \
+    } G_STMT_END
+#else
+# define dir_watch_created(watch) (watch)->create ((watch)->user_data)
+# define dir_watch_destroyed(watch) (watch)->destroy ((watch)->user_data)
+#endif
+
+#ifdef DIR_WATCH_DEBUG
+#include <errno.h>
+#include <string.h>
+#endif
+
+static gboolean
+dir_exists (GFile *file)
+{
+  gboolean result;
+  struct stat buf;
+  char *path;
+
+  path = g_file_get_path (file);
+#ifdef DIR_WATCH_DEBUG
+  errno = 0;
+#endif
+  result = !lstat (path, &buf) && S_ISDIR (buf.st_mode);
+
+#ifdef DIR_WATCH_DEBUG
+  g_print ("    lstat ('%s') -> is%s a directory (%s)\n",
+           path, result ? "" : " not", strerror (errno));
+#endif
+  g_free (path);
+
+  return result;
+}
+
+static void
+dir_watch_parent_changed (GFileMonitor      *monitor,
+                          GFile             *file,
+                          GFile             *other_file,
+                          GFileMonitorEvent  event_type,
+                          gpointer           user_data)
+{
+  DirWatch *watch = user_data;
+
+  g_assert (watch->parent_monitor == monitor);
+
+  if (!g_file_equal (file, watch->directory))
+    return;
+
+  if (event_type == G_FILE_MONITOR_EVENT_CREATED)
+    {
+      if (watch->state)
+        return;
+
+      /* we were just created.  ensure that it's a directory. */
+      if (dir_exists (file))
+        {
+          /* we're official now.  report it. */
+          watch->state = TRUE;
+          dir_watch_created (watch);
+        }
+    }
+  else if (event_type == G_FILE_MONITOR_EVENT_DELETED)
+    {
+      if (!watch->state)
+        return;
+
+      watch->state = FALSE;
+      dir_watch_destroyed (watch);
+    }
+}
+
+static void
+dir_watch_recursive_create (gpointer user_data)
+{
+  DirWatch *watch = user_data;
+  GFile *parent;
+
+  g_assert (watch->parent_monitor == NULL);
+
+  parent = g_file_get_parent (watch->directory);
+  watch->parent_monitor = g_file_monitor_directory (parent, 0,
+                                                    NULL, NULL);
+  g_object_unref (parent);
+  g_signal_connect (watch->parent_monitor, "changed",
+                    G_CALLBACK (dir_watch_parent_changed), watch);
+  
+  /* check if directory was created before we started to monitor */
+  if (dir_exists (watch->directory))
+    {
+      watch->state = TRUE;
+      dir_watch_created (watch);
+    }
+}
+
+static void
+dir_watch_recursive_destroy (gpointer user_data)
+{
+  DirWatch *watch = user_data;
+
+  /* exactly one monitor should be active */
+  g_assert (watch->parent_monitor != NULL);
+
+  /* if we were monitoring the directory... */
+  if (watch->state)
+    {
+      dir_watch_destroyed (watch);
+      watch->state = FALSE;
+    }
+
+  g_object_unref (watch->parent_monitor);
+  watch->parent_monitor = NULL;
+}
+
+DirWatch *
+dir_watch_new (GFile        *directory,
+               GFile        *topdir,
+               gboolean      watching,
+               DirWatchFunc  create,
+               DirWatchFunc  destroy,
+               gpointer      user_data)
+{
+  DirWatch *watch;
+
+  watch = g_slice_new0 (DirWatch);
+  watch->create = create;
+  watch->destroy = destroy;
+  watch->user_data = user_data;
+
+  watch->directory = g_object_ref (directory);
+  watch->topdir = g_object_ref (topdir);
+
+  /* the top directory always exists */
+  if (g_file_equal (directory, topdir))
+    {
+      if (watching)
+        dir_watch_created (watch);
+
+      watch->state = TRUE;
+    }
+
+  else
+    {
+      GFile *parent;
+
+      parent = g_file_get_parent (directory);
+      g_assert (parent != NULL);
+
+      watch->parent = dir_watch_new (parent, topdir, watching,
+                                     dir_watch_recursive_create,
+                                     dir_watch_recursive_destroy,
+                                     watch);
+
+      g_object_unref (parent);
+
+      if (!watching)
+        watch->state = watch->parent->state && dir_exists (directory);
+    }
+
+  return watch;
+}
+
+void
+dir_watch_free (DirWatch *watch)
+{
+  if (watch != NULL)
+    {
+      if (watch->parent_monitor)
+        g_object_unref (watch->parent_monitor);
+
+      g_object_unref (watch->directory);
+      g_object_unref (watch->topdir);
+
+      dir_watch_free (watch->parent);
+    }
+}
+
+void
+dir_watch_enable (DirWatch *watch)
+{
+  /* topdir always exists.  say so. */
+  if (watch->parent == NULL)
+    dir_watch_created (watch);
+
+  else
+    dir_watch_enable (watch->parent);
+}
+
+void
+dir_watch_disable (DirWatch *watch)
+{
+  if (watch->parent_monitor)
+    g_object_unref (watch->parent_monitor);
+
+  watch->parent_monitor = NULL;
+
+  if (watch->parent)
+    dir_watch_disable (watch->parent);
+}
+
+gboolean
+dir_watch_is_valid (DirWatch *watch)
+{
+  return watch->state;
+}
+
+gboolean
+dir_watch_double_check (DirWatch *watch)
+{
+  gboolean old_state;
+
+  old_state = watch->state;
+
+  if (watch->parent == NULL)
+    {
+      g_assert (watch->state == TRUE);
+      return TRUE;
+    }
+
+  if (dir_watch_double_check (watch->parent))
+    {
+      if (dir_watch_is_valid (watch->parent))
+        watch->state = dir_exists (watch->directory);
+      else
+        watch->state = FALSE;
+
+
+      if (!old_state && watch->state && watch->parent_monitor)
+        dir_watch_created (watch);
+
+      else if (old_state && !watch->state && watch->parent_monitor)
+        dir_watch_destroyed (watch);
+
+      else if (old_state || watch->state)
+        return TRUE;
+    }
+
+  return FALSE;
+}

Added: trunk/daemon/trashlib/dirwatch.h
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/dirwatch.h	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,33 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#ifndef _dirwatch_h_
+#define _dirwatch_h_
+
+#include <gio/gio.h>
+
+typedef void          (*DirWatchFunc)           (gpointer       user_data);
+typedef struct          OPAQUE_TYPE__DirWatch    DirWatch;
+
+DirWatch               *dir_watch_new           (GFile         *directory,
+                                                 GFile         *topdir,
+                                                 gboolean       watching,
+                                                 DirWatchFunc   create,
+                                                 DirWatchFunc   destroy,
+                                                 gpointer       user_data);
+
+void                    dir_watch_enable        (DirWatch      *watch);
+void                    dir_watch_disable       (DirWatch      *watch);
+gboolean                dir_watch_double_check  (DirWatch      *watch);
+
+gboolean                dir_watch_is_valid      (DirWatch      *watch);
+gboolean                dir_watch_needs_update  (DirWatch      *watch);
+
+void                    dir_watch_free          (DirWatch      *watch);
+
+#endif /* _dirwatch_h_ */

Added: trunk/daemon/trashlib/trashdir.c
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/trashdir.c	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,318 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#include "trashdir.h"
+
+#include <string.h>
+
+#include "dirwatch.h"
+
+struct OPAQUE_TYPE__TrashDir
+{
+  TrashRoot *root;
+  GSList *items;
+
+  GFile *directory;
+  GFile *topdir;
+  gboolean is_homedir;
+
+  DirWatch *watch;
+  GFileMonitor *monitor;
+};
+
+static gint
+compare_basename (gconstpointer a,
+                  gconstpointer b)
+{
+  GFile *file_a, *file_b;
+  char *name_a, *name_b;
+  gint result;
+
+  file_a = (GFile *) a;
+  file_b = (GFile *) b;
+
+  name_a = g_file_get_basename (file_a);
+  name_b = g_file_get_basename (file_b);
+
+  result = strcmp (name_a, name_b);
+
+  g_free (name_a);
+  g_free (name_b);
+
+  return result;
+}
+
+static void
+trash_dir_set_files (TrashDir *dir,
+                     GSList   *items)
+{
+  GSList **old, *new;
+
+  items = g_slist_sort (items, (GCompareFunc) compare_basename);
+  old = &dir->items;
+  new = items;
+
+  while (new || *old)
+    {
+      int result;
+
+      if ((result = (new == NULL) - (*old == NULL)) == 0)
+        result = compare_basename (new->data, (*old)->data);
+
+      if (result < 0)
+        {
+          /* new entry.  add it. */
+          *old = g_slist_prepend (*old, new->data); /* take reference */
+          old = &(*old)->next;
+          trash_root_add_item (dir->root, new->data, dir->is_homedir);
+          new = new->next;
+        }
+      else if (result > 0)
+        {
+          /* old entry.  remove it. */
+          trash_root_remove_item (dir->root, (*old)->data, dir->is_homedir);
+          g_object_unref ((*old)->data);
+          *old = g_slist_delete_link (*old, *old);
+        }
+      else
+        {
+          /* match.  no change. */
+          old = &(*old)->next;
+          g_object_unref (new->data);
+          new = new->next;
+        }
+    }
+
+  g_slist_free (items);
+
+  trash_root_thaw (dir->root);
+}
+
+static void
+trash_dir_empty (TrashDir *dir)
+{
+  trash_dir_set_files (dir, NULL);
+}
+
+static void
+trash_dir_enumerate (TrashDir *dir)
+{
+  GFileEnumerator *enumerator;
+  GSList *files = NULL;
+
+  enumerator = g_file_enumerate_children (dir->directory,
+                                          G_FILE_ATTRIBUTE_STANDARD_NAME,
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          NULL, NULL);
+
+  if (enumerator)
+    {
+      GFileInfo *info;
+
+      while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)))
+        {
+          GFile *file;
+
+          file = g_file_get_child (dir->directory,
+                                   g_file_info_get_name (info));
+          files = g_slist_prepend (files, file);
+
+          g_object_unref (info);
+        }
+
+      g_object_unref (enumerator);
+    }
+
+  trash_dir_set_files (dir, files); /* consumes files */
+}
+
+static void
+trash_dir_changed (GFileMonitor      *monitor,
+                   GFile             *file,
+                   GFile             *other_file,
+                   GFileMonitorEvent  event_type,
+                   gpointer           user_data)
+{
+  TrashDir *dir = user_data;
+
+  if (event_type == G_FILE_MONITOR_EVENT_CREATED)
+    trash_root_add_item (dir->root, file, dir->is_homedir);
+
+  else if (event_type == G_FILE_MONITOR_EVENT_DELETED)
+    trash_root_remove_item (dir->root, file, dir->is_homedir);
+
+  else
+    {
+      static gboolean already_did_warning;
+      char *dirname;
+      char *name;
+
+      g_warning ("*** Unsupported operation detected on trash directory");
+      if (!already_did_warning)
+        {
+          g_warning ("    A trash files/ directory should only have files "
+                     "linked or unlinked (via moves or deletes).  Some other "
+                     "operation has been detected on a file in the directory "
+                     "(eg: a file has been modified).  Likely, the data "
+                     "reported by the trash backend will now be "
+                     "inconsistent.");
+          already_did_warning = TRUE;
+        }
+
+      name = file ? g_file_get_basename (file) : NULL;
+      dirname = g_file_get_path (dir->directory);
+      g_warning ("  dir: %s, file: %s, type: %d\n\n",
+                 dirname, name, event_type);
+    }
+
+  trash_root_thaw (dir->root);
+}
+
+static void
+trash_dir_created (gpointer user_data)
+{
+  TrashDir *dir = user_data;
+
+  g_assert (dir->monitor == NULL);
+  dir->monitor = g_file_monitor_directory (dir->directory, 0, NULL, NULL);
+  g_signal_connect (dir->monitor, "changed",
+                    G_CALLBACK (trash_dir_changed), dir);
+  trash_dir_enumerate (dir);
+}
+
+static void
+trash_dir_destroyed (gpointer user_data)
+{
+  TrashDir *dir = user_data;
+
+  g_assert (dir->monitor != NULL);
+  g_object_unref (dir->monitor);
+  dir->monitor = NULL;
+
+  trash_dir_empty (dir);
+}
+
+void
+trash_dir_watch (TrashDir *dir)
+{
+  g_assert (dir->monitor == NULL);
+
+  /* start monitoring after a period of not monitoring.
+   *
+   * there are two possible cases here:
+   *   1) the directory now exists
+   *      - we have to rescan the directory to ensure that we notice
+   *        any changes that have occured since we last looked
+   *
+   *   2) the directory does not exist
+   *      - if it existed last time we looked then we may have stale
+   *        toplevel items that need to be removed.
+   *
+   * in case 1, trash_dir_created() will be called from
+   * dir_watch_new().  it calls trash_dir_rescan() itself.
+   *
+   * in case 2, no other function will be called and we must manually
+   * call trash_dir_empty().
+   *
+   * we can tell if case 1 happened because trash_dir_created() also
+   * sets the dir->monitor.
+   */
+  dir_watch_enable (dir->watch);
+
+  if (dir->monitor == NULL)
+    /* case 2 */
+    trash_dir_empty (dir);
+}
+
+void
+trash_dir_unwatch (TrashDir *dir)
+{
+  /* stop monitoring.
+   *
+   * in all cases, we just fall silent.
+   */
+
+  if (dir->monitor != NULL)
+    {
+      g_object_unref (dir->monitor);
+      dir->monitor = NULL;
+    }
+
+  dir_watch_disable (dir->watch);
+}
+
+void
+trash_dir_rescan (TrashDir *dir)
+{
+  if (dir_watch_double_check (dir->watch))
+    {
+      if (dir_watch_is_valid (dir->watch))
+        trash_dir_enumerate (dir);
+      else
+        trash_dir_empty (dir);
+    }
+}
+
+static trash_dir_ui_hook ui_hook;
+
+TrashDir *
+trash_dir_new (TrashRoot  *root,
+               gboolean    watching,
+               gboolean    is_homedir,
+               const char *mount_point,
+               const char *format,
+               ...)
+{
+  TrashDir *dir;
+  va_list ap;
+  char *rel;
+
+  va_start (ap, format);
+  rel = g_strdup_vprintf (format, ap);
+  va_end (ap);
+
+  dir = g_slice_new (TrashDir);
+
+  dir->root = root;
+  dir->items = NULL;
+  dir->topdir = g_file_new_for_path (mount_point);
+  dir->directory = g_file_get_child (dir->topdir, rel);
+  dir->monitor = NULL;
+  dir->is_homedir = is_homedir;
+
+  dir->watch = dir_watch_new (dir->directory, dir->topdir, watching,
+                              trash_dir_created, trash_dir_destroyed,
+                              dir);
+
+  if (ui_hook)
+    ui_hook (dir, dir->directory);
+
+  return dir;
+}
+
+void
+trash_dir_set_ui_hook (trash_dir_ui_hook _ui_hook)
+{
+  ui_hook = _ui_hook;
+}
+
+void
+trash_dir_free (TrashDir *dir)
+{
+  dir_watch_free (dir->watch);
+
+  if (dir->monitor)
+    g_object_unref (dir->monitor);
+
+  trash_dir_set_files (dir, NULL);
+
+  g_object_unref (dir->directory);
+  g_object_unref (dir->topdir);
+
+  g_slice_free (TrashDir, dir);
+}

Added: trunk/daemon/trashlib/trashdir.h
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/trashdir.h	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,36 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#ifndef _trashdir_h_
+#define _trashdir_h_
+
+#include <gio/gio.h>
+
+#include "trashitem.h"
+
+typedef struct          OPAQUE_TYPE__TrashDir    TrashDir;
+
+typedef void (*trash_dir_ui_hook) (TrashDir *dir,
+                                   GFile    *directory);
+
+TrashDir               *trash_dir_new           (TrashRoot  *root,
+                                                 gboolean    watching,
+                                                 gboolean    is_homedir,
+                                                 const char *mount_point,
+                                                 const char *format,
+                                                 ...);
+
+void                    trash_dir_free          (TrashDir   *dir);
+
+void                    trash_dir_watch         (TrashDir   *dir);
+void                    trash_dir_unwatch       (TrashDir   *dir);
+void                    trash_dir_rescan        (TrashDir   *dir);
+
+void                    trash_dir_set_ui_hook   (trash_dir_ui_hook ui_hook);
+
+#endif /* _trashdir_h_ */

Added: trunk/daemon/trashlib/trashexpunge.c
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/trashexpunge.c	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,127 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#include "trashexpunge.h"
+
+static gsize trash_expunge_initialised;
+static GHashTable *trash_expunge_queue;
+static gboolean trash_expunge_alive;
+static GMutex *trash_expunge_lock;
+static GCond *trash_expunge_wait;
+
+static void
+trash_expunge_delete_everything_under (GFile *directory)
+{
+  GFileEnumerator *enumerator;
+
+  enumerator = g_file_enumerate_children (directory,
+                                          G_FILE_ATTRIBUTE_STANDARD_NAME,
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          NULL, NULL);
+
+  if (enumerator)
+    {
+      GFileInfo *info;
+
+      while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)))
+        {
+          const gchar *basename;
+          GFile *sub;
+          
+          basename = g_file_info_get_name (info);
+          sub = g_file_get_child (directory, basename);
+          trash_expunge_delete_everything_under (sub);
+
+          /* do the delete here */
+          g_file_delete (sub, NULL, NULL);
+
+          g_object_unref (sub);
+        }
+      g_object_unref (enumerator);
+    }
+}
+
+static gboolean
+just_return_true (gpointer a,
+                  gpointer b,
+                  gpointer c)
+{
+  return TRUE;
+}
+
+static gpointer
+trash_expunge_thread (gpointer data)
+{
+  GTimeVal timeval;
+
+  g_mutex_lock (trash_expunge_lock);
+
+  do
+    {
+      while (g_hash_table_size (trash_expunge_queue))
+        {
+          GFile *directory;
+         
+          directory = g_hash_table_find (trash_expunge_queue,
+                                         just_return_true, NULL);
+          g_hash_table_remove (trash_expunge_queue, directory);
+
+          g_mutex_unlock (trash_expunge_lock);
+          trash_expunge_delete_everything_under (directory);
+          g_mutex_lock (trash_expunge_lock);
+
+          g_object_unref (directory);
+        }
+
+      g_get_current_time (&timeval);
+      g_time_val_add (&timeval, 60 * 1000000); /* 1min */
+    }
+  while (g_cond_timed_wait (trash_expunge_wait,
+                            trash_expunge_lock,
+                            &timeval));
+
+  trash_expunge_alive = FALSE;
+
+  g_mutex_unlock (trash_expunge_lock);
+
+  return NULL;
+}
+
+void
+trash_expunge (GFile *directory)
+{
+  if G_UNLIKELY (g_once_init_enter (&trash_expunge_initialised))
+    {
+      trash_expunge_queue = g_hash_table_new (g_file_hash,
+                                              (GEqualFunc) g_file_equal);
+      trash_expunge_lock = g_mutex_new ();
+      trash_expunge_wait = g_cond_new ();
+
+      g_once_init_leave (&trash_expunge_initialised, 1);
+    }
+
+  g_mutex_lock (trash_expunge_lock);
+
+  if (!g_hash_table_lookup (trash_expunge_queue, directory))
+    g_hash_table_insert (trash_expunge_queue,
+                         g_object_ref (directory),
+                         directory);
+
+  if (trash_expunge_alive == FALSE)
+    {
+      GThread *thread;
+
+      thread = g_thread_create (trash_expunge_thread, NULL, FALSE, NULL);
+      g_assert (thread != NULL);
+      trash_expunge_alive = TRUE;
+    }
+  else
+    g_cond_signal (trash_expunge_wait);
+
+  g_mutex_unlock (trash_expunge_lock);
+}

Added: trunk/daemon/trashlib/trashexpunge.h
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/trashexpunge.h	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,17 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#ifndef _trashexpunger_h_
+#define _trashexpunger_h_
+
+#include <gio/gio.h>
+
+typedef struct OPAQUE_TYPE__TrashExpunger TrashExpunger;
+void trash_expunge (GFile *expunge_directory);
+
+#endif /* _trashexpunger_h_ */

Added: trunk/daemon/trashlib/trashitem.c
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/trashitem.c	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,522 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#include "trashexpunge.h"
+#include "trashitem.h"
+
+#include <glib/gstdio.h>
+
+typedef struct
+{
+  trash_item_notify func;
+  TrashItem *item;
+  gpointer user_data;
+} NotifyClosure;
+
+struct OPAQUE_TYPE__TrashRoot
+{
+  GStaticRWLock lock;
+  GQueue *notifications;
+
+  trash_item_notify create_notify;
+  trash_item_notify delete_notify;
+  trash_size_change size_change;
+  gpointer user_data;
+
+  GHashTable *item_table;
+  gboolean is_homedir;
+  int old_size;
+};
+
+struct OPAQUE_TYPE__TrashItem
+{
+  TrashRoot *root;
+  gint ref_count;
+
+  char *escaped_name;
+  GFile *file;
+
+  GFile *original;
+  char *delete_date;
+};
+
+static char *
+trash_item_escape_name (GFile    *file,
+                        gboolean  in_homedir)
+{
+  /*
+   * make unique names as follows:
+   *
+   * - items in home directory use their basename (never starts with '/')
+   *
+   *     - if the basename starts with '\' then it is changed to '`\'
+   *
+   *     - if the basename starts with '`' then it is changed to '``'
+   *
+   *     - this means that home directory items never start with '\'
+   *
+   * - items in others use full path name (always starts with '/')
+   *
+   *     - each '/' (including the first) is changed to '\'
+   *
+   *     - this means that all of these items start with '\'
+   *
+   *     - each '\' is changed to '`\'
+   *
+   *     - each '`' is changed to '``'
+   */
+#define ESCAPE_SYMBOL1 '\\'
+#define ESCAPE_SYMBOL2 '`'
+
+  if (in_homedir)
+    {
+      char *basename;
+      char *escaped;
+
+      basename = g_file_get_basename (file);
+
+      if (basename[0] != ESCAPE_SYMBOL1 && basename[0] != ESCAPE_SYMBOL2)
+        return basename;
+
+      escaped = g_strdup_printf ("%c%s", ESCAPE_SYMBOL2, basename);
+      g_free (basename);
+
+      return escaped;
+    }
+  else
+    {
+      char *uri, *src, *dest, *escaped;
+      int need_bytes = 0;
+
+      uri = g_file_get_uri (file);
+      g_assert (g_str_has_prefix (uri, "file:///"));
+
+      src = uri + 7; /* keep the first '/' */
+      while (*src)
+        {
+          if (*src == ESCAPE_SYMBOL1 || *src == ESCAPE_SYMBOL2)
+            need_bytes += 2;
+          else
+            need_bytes++;
+
+          src++;
+        }
+
+      escaped = g_malloc (need_bytes + 1);
+
+      dest = escaped;
+      src = uri + 7;
+      while (*src)
+        {
+          if (*src == ESCAPE_SYMBOL1 || *src == ESCAPE_SYMBOL2)
+            {
+              *dest++ = ESCAPE_SYMBOL2;
+              *dest++ = *src;
+            }
+          else if (*src == '/')
+            *dest++ = ESCAPE_SYMBOL1;
+          else
+            *dest++ = *src;
+
+          src++;
+        }
+
+      g_free (uri);
+      *src = '\0';
+
+      return escaped;
+    }
+}
+
+static void
+trash_item_get_trashinfo (GFile  *path,
+                          GFile **original,
+                          char  **date)
+{
+  GFile *files, *trashdir;
+  GKeyFile *keyfile;
+  char *trashpath;
+  char *trashinfo;
+  char *basename;
+
+  files = g_file_get_parent (path);
+  trashdir = g_file_get_parent (files);
+  trashpath = g_file_get_path (trashdir);
+  g_object_unref (files);
+
+  basename = g_file_get_basename (path);
+
+  trashinfo = g_strdup_printf ("%s/info/%s.trashinfo", trashpath, basename);
+  g_free (trashpath);
+  g_free (basename);
+
+  keyfile = g_key_file_new ();
+
+  *original = NULL;
+  *date = NULL;
+
+  if (g_key_file_load_from_file (keyfile, trashinfo, 0, NULL))
+    {
+      char *orig;
+
+      orig = g_key_file_get_string (keyfile,
+                                    "Trash Info", "Path",
+                                    NULL);
+
+      if (orig == NULL)
+        *original = NULL;
+
+      else if (g_path_is_absolute (orig))
+        *original = g_file_new_for_path (orig);
+
+      else
+        {
+          GFile *rootdir;
+
+          rootdir = g_file_get_parent (trashdir);
+          *original = g_file_get_child (rootdir, orig);
+        }
+
+      g_free (orig);
+
+      *date = g_key_file_get_string (keyfile,
+                                     "Trash Info", "DeletionDate",
+                                     NULL);
+    }
+
+  g_object_unref (trashdir);
+  g_key_file_free (keyfile);
+  g_free (trashinfo);
+}
+
+static TrashItem *
+trash_item_new (TrashRoot *root,
+                GFile         *file,
+                gboolean       in_homedir)
+{
+  TrashItem *item;
+
+  item = g_slice_new (TrashItem);
+  item->root = root;
+  item->ref_count = 1;
+  item->file = g_object_ref (file);
+  item->escaped_name = trash_item_escape_name (file, in_homedir);
+  trash_item_get_trashinfo (item->file, &item->original, &item->delete_date);
+
+  return item;
+}
+
+static TrashItem *
+trash_item_ref (TrashItem *item)
+{
+  g_atomic_int_inc (&item->ref_count);
+  return item;
+}
+
+void
+trash_item_unref (TrashItem *item)
+{
+  if (g_atomic_int_dec_and_test (&item->ref_count))
+    {
+      g_object_unref (item->file);
+
+      if (item->original)
+        g_object_unref (item->original);
+
+      g_free (item->delete_date);
+      g_free (item->escaped_name);
+
+      g_slice_free (TrashItem, item);
+    }
+}
+
+const char *
+trash_item_get_escaped_name (TrashItem *item)
+{
+  return item->escaped_name;
+}
+
+const char *
+trash_item_get_delete_date (TrashItem *item)
+{
+  return item->delete_date;
+}
+
+GFile *
+trash_item_get_original (TrashItem *item)
+{
+  return item->original;
+}
+
+GFile *
+trash_item_get_file (TrashItem *item)
+{
+  return item->file;
+}
+
+static void
+trash_item_queue_notify (TrashItem         *item,
+                         trash_item_notify  func)
+{
+  NotifyClosure *closure;
+
+  closure = g_slice_new (NotifyClosure);
+  closure->func = func;
+  closure->item = trash_item_ref (item);
+  closure->user_data = item->root->user_data;
+
+  g_queue_push_tail (item->root->notifications, closure);
+}
+
+static void
+trash_item_invoke_closure (NotifyClosure *closure)
+{
+  closure->func (closure->item, closure->user_data);
+  trash_item_unref (closure->item);
+  g_slice_free (NotifyClosure, closure);
+}
+
+void
+trash_root_thaw (TrashRoot *root)
+{
+  NotifyClosure *closure;
+  gboolean size_changed;
+  int size;
+
+  /* send notifications until we have none */
+  while (TRUE)
+    {
+      g_static_rw_lock_writer_lock (&root->lock);
+      if (g_queue_is_empty (root->notifications))
+        break;
+
+      closure = g_queue_pop_head (root->notifications);
+      g_static_rw_lock_writer_unlock (&root->lock);
+
+      trash_item_invoke_closure (closure);
+    }
+
+  /* still holding lock... */
+  size = g_hash_table_size (root->item_table);
+  size_changed = root->old_size != size;
+  root->old_size = size;
+
+  g_static_rw_lock_writer_unlock (&root->lock);
+
+  if (size_changed)
+    root->size_change (root->user_data);
+}
+
+static void
+trash_item_removed (gpointer data)
+{
+  TrashItem *item = data;
+
+  trash_item_queue_notify (item, item->root->delete_notify);
+  trash_item_unref (item);
+}
+
+TrashRoot *
+trash_root_new (trash_item_notify create,
+                trash_item_notify delete,
+                trash_size_change size_change,
+                gpointer          user_data)
+{
+  TrashRoot *root;
+
+  root = g_slice_new (TrashRoot);
+  g_static_rw_lock_init (&root->lock);
+  root->create_notify = create;
+  root->delete_notify = delete;
+  root->size_change = size_change;
+  root->user_data = user_data;
+  root->notifications = g_queue_new ();
+  root->item_table = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                            NULL, trash_item_removed);
+  root->old_size = 0;
+
+  return root;
+}
+
+void
+trash_root_free (TrashRoot *root)
+{
+  g_hash_table_destroy (root->item_table);
+
+  while (!g_queue_is_empty (root->notifications))
+    {
+      NotifyClosure *closure;
+      
+      closure = g_queue_pop_head (root->notifications);
+      trash_item_unref (closure->item);
+      g_slice_free (NotifyClosure, closure);
+    }
+  g_queue_free (root->notifications);
+
+  g_slice_free (TrashRoot, root);
+}
+
+void
+trash_root_add_item (TrashRoot *list,
+                     GFile     *file,
+                     gboolean   in_homedir)
+{
+  TrashItem *item;
+
+  item = trash_item_new (list, file, in_homedir);
+
+  g_static_rw_lock_writer_lock (&list->lock);
+
+  if (g_hash_table_lookup (list->item_table, item->escaped_name))
+    {
+      g_static_rw_lock_writer_unlock (&list->lock);
+
+      /* already exists... */
+      trash_item_unref (item);
+      return;
+    }
+
+  g_hash_table_insert (list->item_table, item->escaped_name, item);
+  trash_item_queue_notify (item, item->root->create_notify);
+
+  g_static_rw_lock_writer_unlock (&list->lock);
+}
+
+void
+trash_root_remove_item (TrashRoot *list,
+                        GFile     *file,
+                        gboolean   in_homedir)
+{
+  char *escaped;
+
+  escaped = trash_item_escape_name (file, in_homedir);
+
+  g_static_rw_lock_writer_lock (&list->lock);
+  g_hash_table_remove (list->item_table, escaped);
+  g_static_rw_lock_writer_unlock (&list->lock);
+
+  g_free (escaped);
+}
+
+GList *
+trash_root_get_items (TrashRoot *root)
+{
+  GList *items, *node;
+
+  g_static_rw_lock_reader_lock (&root->lock);
+
+  items = g_hash_table_get_values (root->item_table);
+  for (node = items; node; node = node->next)
+    trash_item_ref (node->data);
+
+  g_static_rw_lock_reader_unlock (&root->lock);
+
+  return items;
+}
+
+void
+trash_item_list_free (GList *list)
+{
+  GList *node;
+
+  for (node = list; node; node = node->next)
+    trash_item_unref (node->data);
+  g_list_free (list);
+}
+
+TrashItem *
+trash_root_lookup_item (TrashRoot  *root,
+                        const char *escaped)
+{
+  TrashItem *item;
+
+  g_static_rw_lock_reader_lock (&root->lock);
+
+  if ((item = g_hash_table_lookup (root->item_table, escaped)))
+    trash_item_ref (item);
+
+  g_static_rw_lock_reader_unlock (&root->lock);
+
+  return item;
+}
+
+int
+trash_root_get_n_items (TrashRoot *root)
+{
+  int size;
+
+  g_static_rw_lock_reader_lock (&root->lock);
+  size = g_hash_table_size (root->item_table);
+  g_static_rw_lock_reader_unlock (&root->lock);
+
+  return size;
+}
+
+gboolean
+trash_item_delete (TrashItem  *item,
+                   GError    **error)
+{
+  gboolean success;
+  GFile *expunged;
+  guint unique;
+  guint i;
+
+  expunged = g_file_resolve_relative_path (item->file,
+                                           "../../expunged");
+  g_file_make_directory_with_parents (expunged, NULL, NULL);
+  unique = g_random_int ();
+
+  for (success = FALSE, i = 0; !success && i < 1000; i++)
+    {
+      GFile *temp_name;
+      char buffer[16];
+
+      g_sprintf (buffer, "%u", unique + i);
+      temp_name = g_file_get_child (expunged, buffer);
+
+      if (g_file_move (item->file, temp_name,
+                       G_FILE_COPY_OVERWRITE |
+                       G_FILE_COPY_NOFOLLOW_SYMLINKS |
+                       G_FILE_COPY_NO_FALLBACK_FOR_MOVE,
+                       NULL, NULL, NULL, NULL))
+        {
+          g_static_rw_lock_writer_lock (&item->root->lock);
+          g_hash_table_remove (item->root->item_table, item->escaped_name);
+          g_static_rw_lock_writer_unlock (&item->root->lock);
+
+          {
+            GFile *trashinfo;
+            gchar *basename;
+            gchar *relname;
+
+            basename = g_file_get_basename (item->file);
+            relname = g_strdup_printf ("../../info/%s.trashinfo", basename);
+            trashinfo = g_file_resolve_relative_path (item->file, relname);
+            g_free (basename);
+            g_free (relname);
+
+            g_file_delete (trashinfo, NULL, NULL);
+          }
+
+          trash_expunge (expunged);
+          success = TRUE;
+        }
+
+      g_object_unref (temp_name);
+    }
+
+  g_object_unref (expunged);
+
+  if (!success)
+    g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                 "Failed to delete the item from the trash");
+
+  trash_root_thaw (item->root);
+
+  return success;
+}

Added: trunk/daemon/trashlib/trashitem.h
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/trashitem.h	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,56 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#ifndef _trashitem_h_
+#define _trashitem_h_
+
+#include <gio/gio.h>
+
+typedef struct  OPAQUE_TYPE__TrashRoot        TrashRoot;
+typedef struct  OPAQUE_TYPE__TrashItem        TrashItem;
+
+typedef void  (*trash_item_notify)           (TrashItem          *item,
+                                              gpointer            user_data);
+typedef void  (*trash_size_change)           (gpointer            user_data);
+
+/* trash root -- the set of all toplevel trash items */
+TrashRoot      *trash_root_new               (trash_item_notify   create,
+                                              trash_item_notify   delete,
+                                              trash_size_change   size_change,
+                                              gpointer            user_data);
+void            trash_root_free              (TrashRoot          *root);
+
+/* add/remove trash items (safe only from one thread) */
+void            trash_root_add_item          (TrashRoot          *root,
+                                              GFile              *file,
+                                              gboolean            in_homedir);
+void            trash_root_remove_item       (TrashRoot          *root,
+                                              GFile              *file,
+                                              gboolean            in_homedir);
+void            trash_root_thaw              (TrashRoot          *root);
+
+/* query trash items, holding references (safe from any thread) */
+int             trash_root_get_n_items       (TrashRoot          *root);
+GList          *trash_root_get_items         (TrashRoot          *root);
+TrashItem      *trash_root_lookup_item       (TrashRoot          *root,
+                                              const char         *escaped);
+
+void            trash_item_list_free         (GList              *list);
+void            trash_item_unref             (TrashItem          *item);
+
+/* query a trash item (safe while holding a reference to it) */
+const char     *trash_item_get_escaped_name  (TrashItem          *item);
+const char     *trash_item_get_delete_date   (TrashItem          *item);
+GFile          *trash_item_get_original      (TrashItem          *item);
+GFile          *trash_item_get_file          (TrashItem          *item);
+
+/* delete a trash item (safe while holding a reference to it) */
+gboolean        trash_item_delete            (TrashItem          *item,
+                                              GError            **error);
+
+#endif /* _trashitem_h_ */

Added: trunk/daemon/trashlib/trashwatcher.c
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/trashwatcher.c	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,332 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#include "trashwatcher.h"
+
+#include <gio/gunixmounts.h>
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "trashitem.h"
+#include "trashdir.h"
+
+typedef enum
+{
+  TRASH_WATCHER_TRUSTED,
+  TRASH_WATCHER_WATCH,
+  TRASH_WATCHER_NO_WATCH,
+} WatchType;
+
+/* decide_watch_type:
+ *
+ * This function is responsible for determining what sort of watching
+ * we should do on a given mountpoint according to the type of
+ * filesystem.  It must return one of the WatchType constants above.
+ *
+ *   TRASH_WATCHER_TRUSTED:
+ *
+ *     This is used for filesystems on which notification is supported
+ *     and all file events are reliably reported.  After initialisation
+ *     the trash directories are never manually rescanned since any
+ *     changes are already known to us from the notifications we
+ *     received about them (that's where the "trust" comes in).
+ *
+ *     This should be used for local filesystems such as ext3.
+ *
+ *   TRASH_WATCHER_WATCH:
+ *
+ *     This is used for filesystems on which notification is supported
+ *     but works unreliably.  Some changes to the filesystem may not
+ *     be delivered by the operating system.  The events which are
+ *     delivered are immediately reported but events which are not
+ *     delivered are not reported until the directory is manually
+ *     rescanned (ie: trash_watcher_rescan() is called).
+ *
+ *     This should be used for filesystems like NFS where local
+ *     changes are reported by the kernel but changes made on other
+ *     hosts are not.
+ *
+ *   TRASH_WATCHER_NO_WATCH:
+ *
+ *     Don't bother watching at all.  No change events are ever
+ *     delivered except while running trash_watcher_rescan().
+ *
+ *     This should be used for filesystems where change notification
+ *     is unsupported or is supported, but buggy enough to cause
+ *     problems when using the other two options.
+ */
+static WatchType
+decide_watch_type (GUnixMountEntry *mount,
+                   gboolean         is_home_trash)
+{
+  return TRASH_WATCHER_TRUSTED;
+}
+
+/* find the mount entry for the directory containing 'file'.
+ * used to figure out what sort of filesystem the home trash
+ * folder is sitting on.
+ */
+static GUnixMountEntry *
+find_mount_entry_for_file (GFile *file)
+{
+  GUnixMountEntry *entry;
+  char *pathname;
+
+  pathname = g_file_get_path (file);
+  do
+    {
+      char *slash;
+
+      slash = strrchr (pathname, '/');
+
+      /* leave the leading '/' in place */
+      if (slash == pathname)
+        slash++;
+
+      *slash = '\0';
+
+      entry = g_unix_mount_at (pathname, NULL);
+    }
+  while (entry == NULL && pathname[1]);
+
+  g_free (pathname);
+
+  /* if the GUnixMount stuff is gummed up, this might fail.  we can't
+   * really proceed, since decide_watch_type() needs to know this.
+   */
+  g_assert (entry != NULL);
+
+  return entry;
+}
+
+typedef struct _TrashMount TrashMount;
+
+struct OPAQUE_TYPE__TrashWatcher
+{
+  TrashRoot *root;
+
+  GUnixMountMonitor *mount_monitor;
+  TrashMount *mounts;
+
+  TrashDir *homedir_trashdir;
+  WatchType homedir_type;
+
+  gboolean watching;
+};
+
+struct _TrashMount
+{
+  GUnixMountEntry *mount_entry;
+  TrashDir *dirs[2];
+  WatchType type;
+
+  TrashMount *next;
+};
+
+static void
+trash_mount_insert (TrashWatcher      *watcher,
+                    TrashMount      ***mount_ptr_ptr,
+                    GUnixMountEntry   *mount_entry)
+{
+  const char *mountpoint;
+  gboolean watching;
+  TrashMount *mount;
+
+  mountpoint = g_unix_mount_get_mount_path (mount_entry);
+
+  mount = g_slice_new (TrashMount);
+  mount->mount_entry = mount_entry;
+  mount->type = decide_watch_type (mount_entry, FALSE);
+
+  watching = watcher->watching && mount->type != TRASH_WATCHER_NO_WATCH;
+
+  /* """
+   *   For showing trashed files, implementations SHOULD support (1) and
+   *   (2) at the same time (i.e. if both $topdir/.Trash/$uid and
+   *   $topdir/.Trash-$uid are present, it should list trashed files
+   *   from both of them).
+   * """
+   */
+
+  /* (1) */
+  mount->dirs[0] = trash_dir_new (watcher->root, watching, FALSE, mountpoint,
+                                  ".Trash/%d/files", (int) getuid ());
+
+  /* (2) */
+  mount->dirs[1] = trash_dir_new (watcher->root, watching, FALSE, mountpoint,
+                                  ".Trash-%d/files", (int) getuid ());
+
+  mount->next = **mount_ptr_ptr;
+
+  **mount_ptr_ptr = mount;
+  *mount_ptr_ptr = &mount->next;
+}
+
+static void
+trash_mount_remove (TrashMount **mount_ptr)
+{
+  TrashMount *mount = *mount_ptr;
+
+  /* first, the dirs */
+  trash_dir_free (mount->dirs[0]);
+  trash_dir_free (mount->dirs[1]);
+
+  /* detach from list */
+  *mount_ptr = mount->next;
+
+  g_unix_mount_free (mount->mount_entry);
+  g_slice_free (TrashMount, mount);
+}
+
+static void
+trash_watcher_remount (TrashWatcher *watcher)
+{
+  TrashMount **old;
+  GList *mounts;
+  GList *new;
+
+  mounts = g_unix_mounts_get (NULL);
+  mounts = g_list_sort (mounts, (GCompareFunc) g_unix_mount_compare);
+
+  old = &watcher->mounts;
+  new = mounts;
+
+  /* synchronise the two lists */
+  while (*old || new)
+    {
+      int result;
+
+      if (new && g_unix_mount_is_system_internal (new->data))
+        {
+          g_unix_mount_free (new->data);
+          new = new->next;
+          continue;
+        }
+
+      if ((result = (new == NULL) - (*old == NULL)) == 0)
+        result = g_unix_mount_compare (new->data, (*old)->mount_entry);
+
+      if (result < 0)
+        {
+          /* new entry.  add it. */
+          trash_mount_insert (watcher, &old, new->data);
+          new = new->next;
+        }
+      else if (result > 0)
+        {
+          /* old entry.  remove it. */
+          trash_mount_remove (old);
+        }
+      else
+        {
+          /* match.  no change. */
+          g_unix_mount_free (new->data);
+
+          old = &(*old)->next;
+          new = new->next;
+        }
+    }
+
+  g_list_free (mounts);
+}
+
+TrashWatcher *
+trash_watcher_new (TrashRoot *root)
+{
+  GUnixMountEntry *homedir_mount;
+  GFile *homedir_trashdir;
+  TrashWatcher *watcher;
+  GFile *user_datadir;
+
+  watcher = g_slice_new (TrashWatcher);
+  watcher->root = root;
+  watcher->mounts = NULL;
+  watcher->watching = FALSE;
+  watcher->mount_monitor = g_unix_mount_monitor_new ();
+  g_signal_connect_swapped (watcher->mount_monitor, "mounts_changed",
+                            G_CALLBACK (trash_watcher_remount), watcher);
+
+  user_datadir = g_file_new_for_path (g_get_user_data_dir ());
+  homedir_trashdir = g_file_get_child (user_datadir, "Trash/files");
+  homedir_mount = find_mount_entry_for_file (homedir_trashdir);
+  watcher->homedir_type = decide_watch_type (homedir_mount, TRUE);
+  watcher->homedir_trashdir = trash_dir_new (watcher->root,
+                                             FALSE, TRUE,
+                                             g_get_user_data_dir (),
+                                             "Trash/files");
+
+  g_object_unref (homedir_trashdir);
+  g_object_unref (user_datadir);
+
+  trash_watcher_remount (watcher);
+
+  return watcher;
+}
+
+void
+trash_watcher_free (TrashWatcher *watcher)
+{
+  g_assert_not_reached ();
+}
+
+void
+trash_watcher_watch (TrashWatcher *watcher)
+{
+  TrashMount *mount;
+
+  g_assert (!watcher->watching);
+
+  if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH)
+    trash_dir_watch (watcher->homedir_trashdir);
+
+  for (mount = watcher->mounts; mount; mount = mount->next)
+    if (mount->type != TRASH_WATCHER_NO_WATCH)
+      {
+        trash_dir_watch (mount->dirs[0]);
+        trash_dir_watch (mount->dirs[1]);
+      }
+
+  watcher->watching = TRUE;
+}
+
+void
+trash_watcher_unwatch (TrashWatcher *watcher)
+{
+  TrashMount *mount;
+
+  g_assert (watcher->watching);
+
+  if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH)
+    trash_dir_unwatch (watcher->homedir_trashdir);
+
+  for (mount = watcher->mounts; mount; mount = mount->next)
+    if (mount->type != TRASH_WATCHER_NO_WATCH)
+      {
+        trash_dir_unwatch (mount->dirs[0]);
+        trash_dir_unwatch (mount->dirs[1]);
+      }
+
+  watcher->watching = FALSE;
+}
+
+void
+trash_watcher_rescan (TrashWatcher *watcher)
+{
+  TrashMount *mount;
+
+  if (!watcher->watching || watcher->homedir_type != TRASH_WATCHER_TRUSTED)
+    trash_dir_rescan (watcher->homedir_trashdir);
+
+  for (mount = watcher->mounts; mount; mount = mount->next)
+    if (!watcher->watching || mount->type != TRASH_WATCHER_TRUSTED)
+      {
+        trash_dir_rescan (mount->dirs[0]);
+        trash_dir_rescan (mount->dirs[1]);
+      }
+}

Added: trunk/daemon/trashlib/trashwatcher.h
==============================================================================
--- (empty file)
+++ trunk/daemon/trashlib/trashwatcher.h	Fri Dec 12 05:33:16 2008
@@ -0,0 +1,23 @@
+/*
+ * Copyright  2008 Ryan Lortie
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.   
+ */
+
+#ifndef _trashwatcher_h_
+#define _trashwatcher_h_
+
+#include "trashitem.h"
+
+typedef struct  OPAQUE_TYPE__TrashWatcher     TrashWatcher;
+
+TrashWatcher   *trash_watcher_new            (TrashRoot    *root);
+void            trash_watcher_free           (TrashWatcher *watcher);
+
+void            trash_watcher_watch          (TrashWatcher *watcher);
+void            trash_watcher_unwatch        (TrashWatcher *watcher);
+void            trash_watcher_rescan         (TrashWatcher *watcher);
+
+#endif /* _trashitem_h_ */



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