summaryrefslogtreecommitdiff
path: root/daemon/gvfsbackendtrash.c
diff options
context:
space:
mode:
authorRyan Lortie <desrt@desrt.ca>2008-12-12 05:33:16 +0000
committerRyan Lortie <ryanl@src.gnome.org>2008-12-12 05:33:16 +0000
commit1394c3a853596f9a08275e49f8b70f2a4174738f (patch)
tree85518fe7303fa98704b7ce38b32ac77b0d68ba6d /daemon/gvfsbackendtrash.c
parent297cefc8d3215f6400f260ff6ba54a6925bdb471 (diff)
downloadgvfs-1394c3a853596f9a08275e49f8b70f2a4174738f.tar.gz
New trash:/ backend.
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 svn path=/trunk/; revision=2132
Diffstat (limited to 'daemon/gvfsbackendtrash.c')
-rw-r--r--daemon/gvfsbackendtrash.c2132
1 files changed, 496 insertions, 1636 deletions
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;
}