diff options
Diffstat (limited to 'daemon/gvfsbackendtrash.c')
-rw-r--r-- | daemon/gvfsbackendtrash.c | 2132 |
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; } |