summaryrefslogtreecommitdiff
path: root/trashapplet/src/trash-empty.c
diff options
context:
space:
mode:
Diffstat (limited to 'trashapplet/src/trash-empty.c')
-rw-r--r--trashapplet/src/trash-empty.c378
1 files changed, 378 insertions, 0 deletions
diff --git a/trashapplet/src/trash-empty.c b/trashapplet/src/trash-empty.c
new file mode 100644
index 00000000..1c311e5e
--- /dev/null
+++ b/trashapplet/src/trash-empty.c
@@ -0,0 +1,378 @@
+/*
+ * trash-empty.c: a routine to empty the trash
+ *
+ * Copyright © 2008 Ryan Lortie
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <mateconf/mateconf-client.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include "trash-empty.h"
+#include "config.h"
+
+/* only one concurrent trash empty operation can occur */
+static GtkDialog *trash_empty_confirm_dialog;
+static GtkDialog *trash_empty_dialog;
+static GtkProgressBar *trash_empty_progress_bar;
+static GtkLabel *trash_empty_location;
+static GtkLabel *trash_empty_file;
+
+/* the rules:
+ * 1) nothing here may be modified while trash_empty_update_pending.
+ * 2) an idle may only be scheduled if trash_empty_update_pending.
+ * 3) only the worker may set trash_empty_update_pending = TRUE.
+ * 4) only the UI updater may set trash_empty_update_pending = FALSE.
+ *
+ * i -think- this is threadsafe... ((famous last words...))
+ */
+static GFile * volatile trash_empty_current_file;
+static gsize volatile trash_empty_deleted_files;
+static gsize volatile trash_empty_total_files;
+static gboolean volatile trash_empty_update_pending;
+
+static gboolean
+trash_empty_clear_pending (gpointer user_data)
+{
+ trash_empty_update_pending = FALSE;
+
+ return FALSE;
+}
+
+static gboolean
+trash_empty_update_dialog (gpointer user_data)
+{
+ gsize deleted, total;
+ GFile *file;
+
+ g_assert (trash_empty_update_pending);
+
+ deleted = trash_empty_deleted_files;
+ total = trash_empty_total_files;
+ file = trash_empty_current_file;
+
+ /* maybe the done() got processed first. */
+ if (trash_empty_dialog)
+ {
+ char *index_str, *total_str;
+ char *text_tmp, *text;
+ char *tmp;
+
+ /* The i18n tools can't handle a direct embedding of the
+ * size format using a macro. This is a work-around. */
+ index_str = g_strdup_printf ("%"G_GSIZE_FORMAT, deleted + 1);
+ total_str = g_strdup_printf ("%"G_GSIZE_FORMAT, total);
+ /* Translators: the %s in this string should be read as %d. */
+ text = g_strdup_printf (_("Removing item %s of %s"),
+ index_str, total_str);
+ gtk_progress_bar_set_text (trash_empty_progress_bar, text);
+ g_free (total_str);
+ g_free (index_str);
+ g_free (text);
+
+ if (deleted > total)
+ gtk_progress_bar_set_fraction (trash_empty_progress_bar, 1.0);
+ else
+ gtk_progress_bar_set_fraction (trash_empty_progress_bar,
+ (gdouble) deleted / (gdouble) total);
+
+ /* no g_file_get_basename? */
+ {
+ GFile *parent;
+
+ parent = g_file_get_parent (file);
+ text = g_file_get_uri (parent);
+ g_object_unref (parent);
+ }
+ gtk_label_set_text (trash_empty_location, text);
+ g_free (text);
+
+ tmp = g_file_get_basename (file);
+ /* Translators: %s is a file name */
+ text_tmp = g_strdup_printf (_("Removing: %s"), tmp);
+ text = g_markup_printf_escaped ("<i>%s</i>", text_tmp);
+ gtk_label_set_markup (trash_empty_file, text);
+ g_free (text);
+ g_free (text_tmp);
+ g_free (tmp);
+
+ /* unhide the labels */
+ gtk_widget_show_all (GTK_WIDGET (trash_empty_dialog));
+ }
+
+ trash_empty_current_file = NULL;
+ g_object_unref (file);
+
+ trash_empty_update_pending = FALSE;
+
+ return FALSE;
+}
+
+static gboolean
+trash_empty_done (gpointer user_data)
+{
+ gtk_object_destroy (GTK_OBJECT (trash_empty_dialog));
+
+ g_assert (trash_empty_dialog == NULL);
+
+ return FALSE;
+}
+
+/* =============== worker thread code begins here =============== */
+static void
+trash_empty_maybe_schedule_update (GIOSchedulerJob *job,
+ GFile *file,
+ gsize deleted)
+{
+ if (!trash_empty_update_pending)
+ {
+ g_assert (trash_empty_current_file == NULL);
+
+ trash_empty_current_file = g_object_ref (file);
+ trash_empty_deleted_files = deleted;
+
+ trash_empty_update_pending = TRUE;
+ g_io_scheduler_job_send_to_mainloop_async (job,
+ trash_empty_update_dialog,
+ NULL, NULL);
+ }
+}
+
+static void
+trash_empty_delete_contents (GIOSchedulerJob *job,
+ GCancellable *cancellable,
+ GFile *file,
+ gboolean actually_delete,
+ gsize *deleted)
+{
+ GFileEnumerator *enumerator;
+ GFileInfo *info;
+ GFile *child;
+
+ if (g_cancellable_is_cancelled (cancellable))
+ return;
+
+ enumerator = g_file_enumerate_children (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, NULL);
+ if (enumerator)
+ {
+ while ((info = g_file_enumerator_next_file (enumerator,
+ cancellable, NULL)) != NULL)
+ {
+ child = g_file_get_child (file, g_file_info_get_name (info));
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ trash_empty_delete_contents (job, cancellable, child,
+ actually_delete, deleted);
+
+ if (actually_delete)
+ {
+ trash_empty_maybe_schedule_update (job, child, *deleted);
+ g_file_delete (child, cancellable, NULL);
+ }
+
+ (*deleted)++;
+
+ g_object_unref (child);
+ g_object_unref (info);
+
+ if (g_cancellable_is_cancelled (cancellable))
+ break;
+ }
+
+ g_object_unref (enumerator);
+ }
+}
+
+static gboolean
+trash_empty_job (GIOSchedulerJob *job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ gsize deleted;
+ GFile *trash;
+
+ trash = g_file_new_for_uri ("trash:///");
+
+ /* first do a dry run to count the number of files */
+ deleted = 0;
+ trash_empty_delete_contents (job, cancellable, trash, FALSE, &deleted);
+ trash_empty_total_files = deleted;
+
+ /* now do the real thing */
+ deleted = 0;
+ trash_empty_delete_contents (job, cancellable, trash, TRUE, &deleted);
+
+ /* done */
+ g_object_unref (trash);
+ g_io_scheduler_job_send_to_mainloop_async (job,
+ trash_empty_done,
+ NULL, NULL);
+
+ return FALSE;
+}
+/* ================ worker thread code ends here ================ */
+
+static void
+trash_empty_start (GtkWidget *parent)
+{
+ struct { const char *name; gpointer *pointer; } widgets[] =
+ {
+ { "empty_trash", (gpointer *) &trash_empty_dialog },
+ { "progressbar", (gpointer *) &trash_empty_progress_bar },
+ { "location_label", (gpointer *) &trash_empty_location },
+ { "file_label", (gpointer *) &trash_empty_file }
+ };
+ GCancellable *cancellable;
+ GtkBuilder *builder;
+ gint i;
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_file (builder,
+ GTK_BUILDERDIR "/trashapplet-empty-progress.ui",
+ NULL);
+
+ for (i = 0; i < G_N_ELEMENTS (widgets); i++)
+ {
+ GObject *object;
+
+ object = gtk_builder_get_object (builder, widgets[i].name);
+
+ if (object == NULL)
+ {
+ g_critical ("failed to parse trash-empty dialog markup");
+
+ if (trash_empty_dialog)
+ gtk_object_destroy (GTK_OBJECT (trash_empty_dialog));
+
+ g_object_unref (builder);
+ return;
+ }
+
+ *widgets[i].pointer = object;
+ g_object_add_weak_pointer (object, widgets[i].pointer);
+ }
+ g_object_unref (builder);
+
+ cancellable = g_cancellable_new ();
+ g_signal_connect_object (trash_empty_dialog, "response",
+ G_CALLBACK (g_cancellable_cancel),
+ cancellable, G_CONNECT_SWAPPED);
+ g_io_scheduler_push_job (trash_empty_job, NULL, NULL, 0, cancellable);
+ g_object_unref (cancellable);
+
+ gtk_window_set_screen (GTK_WINDOW (trash_empty_dialog),
+ gtk_widget_get_screen (parent));
+ gtk_widget_show (GTK_WIDGET (trash_empty_dialog));
+}
+
+static gboolean
+trash_empty_require_confirmation (void)
+{
+ return mateconf_client_get_bool (mateconf_client_get_default (),
+ "/apps/caja/preferences/confirm_trash",
+ NULL);
+}
+
+static void
+trash_empty_confirmation_response (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ if (response_id == GTK_RESPONSE_YES)
+ trash_empty_start (GTK_WIDGET (dialog));
+
+ gtk_object_destroy (GTK_OBJECT (dialog));
+ g_assert (trash_empty_confirm_dialog == NULL);
+}
+
+/*
+ * The code in trash_empty_show_confirmation_dialog() was taken from
+ * libcaja-private/caja-file-operations.c (confirm_empty_trash)
+ * by Michiel Sikkes <[email protected]> and adapted for the applet.
+ */
+static void
+trash_empty_show_confirmation_dialog (GtkWidget *parent)
+{
+ GtkWidget *dialog;
+ GtkWidget *button;
+ GdkScreen *screen;
+
+ if (!trash_empty_require_confirmation ())
+ {
+ trash_empty_start (parent);
+ return;
+ }
+
+ screen = gtk_widget_get_screen (parent);
+
+ dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ _("Empty all of the items from "
+ "the trash?"));
+ trash_empty_confirm_dialog = GTK_DIALOG (dialog);
+ g_object_add_weak_pointer (G_OBJECT (dialog),
+ (gpointer *) &trash_empty_confirm_dialog);
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("If you choose to empty "
+ "the trash, all items in "
+ "it will be permanently "
+ "lost. Please note that "
+ "you can also delete them "
+ "separately."));
+
+ gtk_window_set_screen (GTK_WINDOW (dialog), screen);
+ atk_object_set_role (gtk_widget_get_accessible (dialog), ATK_ROLE_ALERT);
+ gtk_window_set_wmclass (GTK_WINDOW (dialog), "empty_trash", "Caja");
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+
+ button = gtk_button_new_with_mnemonic (_("_Empty Trash"));
+ gtk_widget_show (button);
+ gtk_widget_set_can_default (button, TRUE);
+
+ gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
+ GTK_RESPONSE_YES);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog),
+ GTK_RESPONSE_YES);
+
+ gtk_widget_show (dialog);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (trash_empty_confirmation_response), NULL);
+}
+
+void
+trash_empty (GtkWidget *parent)
+{
+ if (trash_empty_confirm_dialog)
+ gtk_window_present (GTK_WINDOW (trash_empty_confirm_dialog));
+ else if (trash_empty_dialog)
+ gtk_window_present (GTK_WINDOW (trash_empty_dialog));
+
+ /* theoretically possible that an update is pending, but very unlikely. */
+ else if (!trash_empty_update_pending)
+ trash_empty_show_confirmation_dialog (parent);
+}