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