summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog11
-rw-r--r--configure.ac1
-rw-r--r--daemon/Makefile.am7
-rw-r--r--daemon/gvfsbackendtrash.c2132
-rw-r--r--daemon/gvfsbackendtrash.h56
-rw-r--r--daemon/trashlib/Makefile.am15
-rw-r--r--daemon/trashlib/dirwatch.c317
-rw-r--r--daemon/trashlib/dirwatch.h33
-rw-r--r--daemon/trashlib/trashdir.c318
-rw-r--r--daemon/trashlib/trashdir.h36
-rw-r--r--daemon/trashlib/trashexpunge.c127
-rw-r--r--daemon/trashlib/trashexpunge.h17
-rw-r--r--daemon/trashlib/trashitem.c522
-rw-r--r--daemon/trashlib/trashitem.h56
-rw-r--r--daemon/trashlib/trashwatcher.c332
-rw-r--r--daemon/trashlib/trashwatcher.h23
16 files changed, 2323 insertions, 1680 deletions
diff --git a/ChangeLog b/ChangeLog
index 20c64944..35a20db1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+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
+
2008-12-11 Dan Winship <danw@gnome.org>
* configure.ac: require libsoup-gnome, for SoupProxyResolverGNOME
diff --git a/configure.ac b/configure.ac
index 063e761a..0e20de97 100644
--- a/configure.ac
+++ b/configure.ac
@@ -553,6 +553,7 @@ AC_OUTPUT([
Makefile
common/Makefile
client/Makefile
+daemon/trashlib/Makefile
daemon/Makefile
monitor/Makefile
monitor/proxy/Makefile
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index e54de039..526865ac 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = trashlib
+
NULL =
mountdir = $(datadir)/gvfs/mounts
@@ -264,9 +266,10 @@ gvfsd_trash_CPPFLAGS = \
-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 \
diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c
index 0b99e21b..b4b5916a 100644
--- a/daemon/gvfsbackendtrash.c
+++ b/daemon/gvfsbackendtrash.c
@@ -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 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)
+static GVfsMonitor *
+trash_backend_get_file_monitor (GVfsBackendTrash *backend,
+ gboolean create)
{
- 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/")))
+ if (backend->file_monitor == NULL && create == FALSE)
+ return NULL;
+
+ 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++;
+ GVfsBackendTrash *backend = user_data;
+ GVfsMonitor *monitor;
- if (*filename == 0)
- return FALSE;
+ monitor = trash_backend_get_dir_monitor (backend, FALSE);
- first_entry = filename;
-
- 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 (monitor)
+ {
+ char *slashname;
- if (*filename)
- *relative_path = g_strdup (filename);
- else
- *relative_path = NULL;
- return TRUE;
+ 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);
+ }
}
-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)
+static void
+trash_backend_item_count_changed (gpointer user_data)
{
- TopdirInfo res;
- struct stat statbuf;
- char *sysadmin_dir, *sysadmin_dir_uid;
- char *user_trash_basename, *user_trash;
+ GVfsBackendTrash *backend = user_data;
+ GVfsMonitor *file_monitor;
+ GVfsMonitor *dir_monitor;
- 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)
+ file_monitor = trash_backend_get_file_monitor (backend, FALSE);
+ dir_monitor = trash_backend_get_dir_monitor (backend, FALSE);
+
+ if (file_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;
+ g_vfs_monitor_emit_event (file_monitor,
+ G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
+ "/", NULL);
- if (statbuf.st_nlink != 2)
- res |= HAS_TRASH_FILES;
- }
-
- g_free (sysadmin_dir_uid);
+ g_object_unref (file_monitor);
}
- g_free (sysadmin_dir);
- 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())
+ if (dir_monitor)
{
- res |= HAS_USER_DIR;
-
- if (statbuf.st_nlink != 2)
- res |= HAS_TRASH_FILES;
- }
+ g_vfs_monitor_emit_event (dir_monitor,
+ G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
+ "/", NULL);
- g_free (user_trash);
-
- return res;
+ g_object_unref (dir_monitor);
+ }
}
-static gboolean
-wait_for_fd_with_timeout (int fd, int timeout_secs)
-{
- int res;
-
- do
- {
-#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;
-}
-/* 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)
+static GFile *
+trash_backend_get_file (GVfsBackendTrash *backend,
+ const char *filename,
+ TrashItem **item_ret,
+ gboolean *is_toplevel,
+ GError **error)
{
- GList *result = NULL;
-
- while (topdirs)
- {
- 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]);
+ const char *slash;
+ gboolean is_top;
+ TrashItem *item;
+ GFile *file;
- /* 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;
- }
- }
-
- return g_list_reverse (result);
-}
+ file = NULL;
+ filename++;
-static GList *
-list_trash_dirs (void)
-{
- 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;
-
- dirs = NULL;
- has_trash_files = FALSE;
-
- home_trash = g_build_filename (g_get_user_data_dir (), "Trash", NULL);
+ 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));
- }
-
- g_unix_mount_free (mount);
- }
- g_list_free (mounts);
-
- topdirs_info = get_topdir_info (topdirs);
-
- for (l = topdirs, li = topdirs_info; l != NULL && li != NULL; l = l->next, li = li->next)
- {
- TopdirInfo info = GPOINTER_TO_UINT (li->data);
- char *basename, *trashdir;
- topdir = l->data;
+ file = g_object_ref (trash_item_get_file (item));
- if (info & HAS_SYSTEM_DIR)
- {
- basename = g_strdup_printf ("%u", getuid());
- trashdir = g_build_filename (topdir, ".Trash", basename, NULL);
- g_free (basename);
- dirs = g_list_prepend (dirs, trashdir);
- }
-
- 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 (item_ret)
+ *item_ret = item;
+ else
+ trash_item_unref (item);
}
-
- if (info & HAS_TRASH_FILES)
- has_trash_files = TRUE;
}
- g_list_foreach (topdirs, (GFunc) g_free, NULL);
- g_list_free (topdirs);
- g_list_free (topdirs_info);
-
- return g_list_reverse (dirs);
-}
-
-static void
-g_vfs_backend_trash_finalize (GObject *object)
-{
- GVfsBackendTrash *backend;
-
- backend = G_VFS_BACKEND_TRASH (object);
+ if (file == NULL)
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ _("No such file or directory"));
- g_mount_spec_unref (backend->mount_spec);
-
-
- if (G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize)
- (*G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize) (object);
+ return file;
}
-static void
-g_vfs_backend_trash_init (GVfsBackendTrash *trash_backend)
+/* ======================= method implementations ======================= */
+static gboolean
+trash_backend_open_for_read (GVfsBackend *vfs_backend,
+ GVfsJobOpenForRead *job,
+ const char *filename)
{
- GVfsBackend *backend = G_VFS_BACKEND (trash_backend);
- GMountSpec *mount_spec;
-
- /* 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);
+ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
+ GError *error = NULL;
- mount_spec = g_mount_spec_new ("trash");
- g_vfs_backend_set_mount_spec (backend, mount_spec);
- trash_backend->mount_spec = mount_spec;
-}
+ if (filename[1] == '\0')
+ g_set_error (&error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
+ _("Can't open directory"));
-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;
+ else
+ {
+ GFile *real;
- 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));
- }
+ real = trash_backend_get_file (backend, filename, NULL, NULL, &error);
-static void
-do_open_for_read (GVfsBackend *backend,
- GVfsJobOpenForRead *job,
- const char *filename)
-{
- char *trashdir, *topdir, *relative_path, *trashfile;
+ if (real)
+ {
+ GFileInputStream *stream;
- 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
- {
- 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);
+ stream = g_file_read (real, NULL, &error);
- error = NULL;
- stream = g_file_read (file,
- G_VFS_JOB (job)->cancellable,
- &error);
- g_object_unref (file);
+ 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 (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);
+ return TRUE;
+ }
}
-
- g_free (trashdir);
- g_free (trashfile);
- g_free (relative_path);
- g_free (topdir);
}
+
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+
+ return TRUE;
}
-static void
-do_read (GVfsBackend *backend,
- GVfsJobRead *job,
- GVfsBackendHandle _handle,
- char *buffer,
- gsize bytes_requested)
+static gboolean
+trash_backend_read (GVfsBackend *backend,
+ GVfsJobRead *job,
+ GVfsBackendHandle handle,
+ char *buffer,
+ gsize bytes_requested)
{
- GInputStream *stream;
- gssize res;
- GError *error;
-
- stream = G_INPUT_STREAM (_handle);
+ GError *error = NULL;
+ gssize bytes;
- error = NULL;
- res = g_input_stream_read (stream,
- buffer, bytes_requested,
- G_VFS_JOB (job)->cancellable,
- &error);
+ bytes = g_input_stream_read (handle, buffer, bytes_requested,
+ NULL, &error);
- if (res != -1)
+ if (bytes >= 0)
{
- g_vfs_job_read_set_size (job, res);
+ g_vfs_job_read_set_size (job, bytes);
g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ return TRUE;
}
- else
- {
- g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
- g_error_free (error);
- }
+
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+
+ return TRUE;
}
-static void
-do_seek_on_read (GVfsBackend *backend,
- GVfsJobSeekRead *job,
- GVfsBackendHandle _handle,
- goffset offset,
- GSeekType type)
+static gboolean
+trash_backend_seek_on_read (GVfsBackend *backend,
+ GVfsJobSeekRead *job,
+ GVfsBackendHandle handle,
+ goffset offset,
+ GSeekType type)
{
- GFileInputStream *stream;
- GError *error;
-
- stream = G_FILE_INPUT_STREAM (_handle);
+ GError *error = NULL;
- error = NULL;
- if (g_seekable_seek (G_SEEKABLE (stream),
- offset, type,
- G_VFS_JOB (job)->cancellable,
- &error))
+ if (g_seekable_seek (handle, offset, type, NULL, &error))
{
- g_vfs_job_seek_read_set_offset (job,
- g_seekable_tell (G_SEEKABLE (stream)));
+ g_vfs_job_seek_read_set_offset (job, g_seekable_tell (handle));
g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ return TRUE;
}
- else
- {
- g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
- g_error_free (error);
- }
+
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+
+ return TRUE;
}
-static void
-do_close_read (GVfsBackend *backend,
- GVfsJobCloseRead *job,
- GVfsBackendHandle _handle)
+static gboolean
+trash_backend_close_read (GVfsBackend *backend,
+ GVfsJobCloseRead *job,
+ GVfsBackendHandle handle)
{
- GInputStream *stream;
- GError *error;
-
- stream = G_INPUT_STREAM (_handle);
+ GError *error = NULL;
- error = NULL;
- if (g_input_stream_close (stream,
- G_VFS_JOB (job)->cancellable,
- &error))
- g_vfs_job_succeeded (G_VFS_JOB (job));
- else
+ if (g_input_stream_close (handle, NULL, &error))
{
- g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
- g_error_free (error);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ 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;
-
- /* Override all writability */
- g_file_info_set_attribute_boolean (file_info,
+ if (original)
+ {
+ gchar *basename;
+
+ 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] == '/');
- keyfile = g_key_file_new ();
- if (g_key_file_load_from_file (keyfile, info_path, G_KEY_FILE_NONE, NULL))
+ trash_watcher_rescan (backend->watcher);
+
+ 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;
+
+ enumerator = g_file_enumerate_children (real, job->attributes,
+ job->flags, NULL, &error);
- if (orig_path_unescaped)
+ 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);
+ g_object_unref (enumerator);
+
+ if (!error)
+ {
+ g_vfs_job_enumerate_done (job);
+
+ return TRUE;
+ }
+ }
}
-
- 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);
-}
-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;
-
- 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);
-
- 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));
+
+ items = trash_root_get_items (backend->root);
- while ((info = g_file_enumerator_next_file (enumerator,
- job ? G_VFS_JOB (job)->cancellable : NULL,
- NULL)) != NULL)
+ for (node = items; node; node = node->next)
{
- const char *name;
- char *new_name, *new_name_escaped;
+ TrashItem *item = node->data;
+ GFileInfo *info;
+ GFile *original;
- name = g_file_info_get_name (info);
+ info = g_file_query_info (trash_item_get_file (item),
+ job->attributes,
+ flags, cancellable, NULL);
- /* Get the display name, etc */
- add_extra_trash_info (info,
- topdir,
- info_dir,
- name,
- NULL);
+ 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;
- /* 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);
-
- if (enumerator)
+ GError *error = NULL;
+ gboolean is_toplevel;
+ TrashItem *item;
+ GFile *real;
+
+ real = trash_backend_get_file (backend, filename,
+ &item, &is_toplevel, &error);
+
+ 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;
-}
-
-static void
-proxy_changed (GFileMonitor* monitor,
- GFile* file,
- GFile* other_file,
- GFileMonitorEvent event_type,
- MonitorProxy *proxy)
-{
- char *file_path;
- char *other_file_path;
-
- file_path = proxy_get_trash_path (proxy, file);
-
- 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_free (file_path);
- g_free (other_file_path);
-}
-
-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)
-{
- 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);
+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");
+
+ g_file_info_set_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
+ FALSE);
- monitors = NULL;
+ g_file_info_set_attribute_uint32 (info,
+ G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW,
+ G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL);
- /* 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_succeeded (G_VFS_JOB (job));
- 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_dir_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);
-
- 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);
+ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
+ GVfsMonitor *monitor;
- 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;
-
-}
-
-static void
-schedule_update_trash_files (GVfsBackendTrash *backend,
- gboolean update_trash_dirs)
-{
- G_LOCK (root_monitor);
+ if (filename[1])
+ monitor = g_vfs_monitor_new (vfs_backend);
+ else
+ monitor = trash_backend_get_dir_monitor (backend, TRUE);
- 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);
-}
+ g_vfs_job_create_monitor_set_monitor (job, monitor);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+ g_object_unref (monitor);
-static void
-mounts_changed (GUnixMountMonitor *mount_monitor,
- GVfsBackendTrash *backend)
-{
- schedule_update_trash_files (backend, TRUE);
+ return TRUE;
}
-static void
-vfs_monitor_gone (gpointer data,
- GObject *where_the_object_was)
+static gboolean
+trash_backend_create_file_monitor (GVfsBackend *vfs_backend,
+ GVfsJobCreateMonitor *job,
+ const char *filename,
+ GFileMonitorFlags flags)
{
- GVfsBackendTrash *backend;
+ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
+ GVfsMonitor *monitor;
- 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;
-
- backend->trash_file_update_scheduled = FALSE;
- backend->trash_file_update_monitors_scheduled = FALSE;
-
- G_UNLOCK (root_monitor);
-}
+ if (filename[1])
+ monitor = g_vfs_monitor_new (vfs_backend);
+ else
+ monitor = trash_backend_get_file_monitor (backend, TRUE);
-static GVfsMonitor *
-do_create_root_monitor (GVfsBackend *backend)
-{
- GVfsBackendTrash *trash_backend;
- GVfsMonitor *vfs_monitor;
- gboolean created;
+ g_vfs_job_create_monitor_set_monitor (job, monitor);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+ g_object_unref (monitor);
- 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;
+ return TRUE;
}
static void
-do_create_dir_monitor (GVfsBackend *backend,
- GVfsJobCreateMonitor *job,
- const char *filename,
- GFileMonitorFlags flags)
+trash_backend_finalize (GObject *object)
{
- char *trashdir, *topdir, *relative_path, *trashfile;
- GVfsMonitor *vfs_monitor;
+ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (object);
- 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);
- }
-}
+ /* 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;
-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;
+ if (backend->dir_monitor)
+ g_object_unref (backend->dir_monitor);
+ backend->dir_monitor = NULL;
- 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);
- }
+ trash_watcher_free (backend->watcher);
+ trash_root_free (backend->root);
}
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)
+g_vfs_backend_trash_init (GVfsBackendTrash *backend)
{
- 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)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
-
- gobject_class->finalize = g_vfs_backend_trash_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;
+g_vfs_backend_trash_class_init (GVfsBackendTrashClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+ GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (class);
+
+ gobject_class->finalize = trash_backend_finalize;
+
+ 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;
}
diff --git a/daemon/gvfsbackendtrash.h b/daemon/gvfsbackendtrash.h
index ee13ae37..dc6f25b3 100644
--- a/daemon/gvfsbackendtrash.h
+++ b/daemon/gvfsbackendtrash.h
@@ -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_ */
diff --git a/daemon/trashlib/Makefile.am b/daemon/trashlib/Makefile.am
new file mode 100644
index 00000000..ca907fe7
--- /dev/null
+++ b/daemon/trashlib/Makefile.am
@@ -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
diff --git a/daemon/trashlib/dirwatch.c b/daemon/trashlib/dirwatch.c
new file mode 100644
index 00000000..d64992c7
--- /dev/null
+++ b/daemon/trashlib/dirwatch.c
@@ -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;
+}
diff --git a/daemon/trashlib/dirwatch.h b/daemon/trashlib/dirwatch.h
new file mode 100644
index 00000000..c1b7f46e
--- /dev/null
+++ b/daemon/trashlib/dirwatch.h
@@ -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_ */
diff --git a/daemon/trashlib/trashdir.c b/daemon/trashlib/trashdir.c
new file mode 100644
index 00000000..bdc64e0e
--- /dev/null
+++ b/daemon/trashlib/trashdir.c
@@ -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);
+}
diff --git a/daemon/trashlib/trashdir.h b/daemon/trashlib/trashdir.h
new file mode 100644
index 00000000..5612f9ba
--- /dev/null
+++ b/daemon/trashlib/trashdir.h
@@ -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_ */
diff --git a/daemon/trashlib/trashexpunge.c b/daemon/trashlib/trashexpunge.c
new file mode 100644
index 00000000..b54b3b2e
--- /dev/null
+++ b/daemon/trashlib/trashexpunge.c
@@ -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);
+}
diff --git a/daemon/trashlib/trashexpunge.h b/daemon/trashlib/trashexpunge.h
new file mode 100644
index 00000000..d6ab4f8f
--- /dev/null
+++ b/daemon/trashlib/trashexpunge.h
@@ -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_ */
diff --git a/daemon/trashlib/trashitem.c b/daemon/trashlib/trashitem.c
new file mode 100644
index 00000000..dc01a929
--- /dev/null
+++ b/daemon/trashlib/trashitem.c
@@ -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;
+}
diff --git a/daemon/trashlib/trashitem.h b/daemon/trashlib/trashitem.h
new file mode 100644
index 00000000..785ef8aa
--- /dev/null
+++ b/daemon/trashlib/trashitem.h
@@ -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_ */
diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c
new file mode 100644
index 00000000..e10d6e3d
--- /dev/null
+++ b/daemon/trashlib/trashwatcher.c
@@ -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]);
+ }
+}
diff --git a/daemon/trashlib/trashwatcher.h b/daemon/trashlib/trashwatcher.h
new file mode 100644
index 00000000..fbe31200
--- /dev/null
+++ b/daemon/trashlib/trashwatcher.h
@@ -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_ */