/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * msd-ldsm-trash-empty.c * Copyright (C) Chris Coulson 2009 <chrisccoulson@googlemail.com> * (C) Ryan Lortie 2008 * * msd-ldsm-trash-empty.c 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 3 of the License, or * (at your option) any later version. * * msd-ldsm-trash-empty.c 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, see <http://www.gnu.org/licenses/>. */ #include <glib/gi18n.h> #include <gio/gio.h> #include "msd-ldsm-trash-empty.h" #define CAJA_PREFS_SCHEMA "org.mate.caja.preferences" #define CAJA_CONFIRM_TRASH_KEY "confirm-trash" /* Some of this code has been borrowed from the trash-applet, courtesy of Ryan Lortie */ static GtkWidget *trash_empty_confirm_dialog = NULL; static GtkWidget *trash_empty_dialog = NULL; static GtkWidget *location_label; static GtkWidget *file_label; static GtkWidget *progressbar; static gsize trash_empty_total_files; static gboolean trash_empty_update_pending = FALSE; static GFile *trash_empty_current_file = NULL; static gsize trash_empty_deleted_files; static GTimer *timer = NULL; static gboolean trash_empty_actually_deleting; static gboolean trash_empty_done (gpointer data) { gtk_widget_destroy (trash_empty_dialog); trash_empty_dialog = NULL; if (timer) { g_timer_destroy (timer); timer = NULL; } return FALSE; } static gboolean trash_empty_update_dialog (gpointer user_data) { gsize deleted, total; GFile *file; gboolean actually_deleting; g_assert (trash_empty_update_pending); deleted = trash_empty_deleted_files; total = trash_empty_total_files; file = trash_empty_current_file; actually_deleting = trash_empty_actually_deleting; /* maybe the done() got processed first. */ if (!trash_empty_dialog) goto out; if (!actually_deleting) { /* If we havent finished counting yet, then pulse the progressbar every 100ms. * This stops the user from thinking the dialog has frozen if there are * a lot of files to delete. We don't pulse it every time we are called from the * worker thread, otherwise it moves to fast and looks hideous */ if (timer) { if (g_timer_elapsed (timer, NULL) > 0.1) { gtk_progress_bar_pulse (GTK_PROGRESS_BAR (progressbar)); g_timer_start (timer); } } else { timer = g_timer_new (); g_timer_start (timer); gtk_progress_bar_pulse (GTK_PROGRESS_BAR (progressbar)); } } else { gchar *text; gchar *tmp; gchar *markup; GFile *parent; text = g_strdup_printf (_("Removing item %lu of %lu"), deleted, total); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progressbar), text); g_free (text); if (deleted > total) gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progressbar), 1.0); else gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progressbar), (gdouble) deleted / (gdouble) total); parent = g_file_get_parent (file); text = g_file_get_uri (parent); g_object_unref (parent); gtk_label_set_text (GTK_LABEL (location_label), text); g_free (text); tmp = g_file_get_basename (file); text = g_markup_printf_escaped (_("Removing: %s"), tmp); markup = g_strdup_printf ("<i>%s</i>", text); gtk_label_set_markup (GTK_LABEL (file_label), text); g_free (markup); g_free (text); g_free (tmp); /* unhide the labels */ gtk_widget_show_all (GTK_WIDGET (trash_empty_dialog)); } out: trash_empty_current_file = NULL; g_object_unref (file); trash_empty_update_pending = FALSE; return FALSE; } /* Worker thread begin */ static void trash_empty_maybe_schedule_update (GIOSchedulerJob *job, GFile *file, gsize deleted, gboolean actually_deleting) { 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_actually_deleting = actually_deleting; 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); trash_empty_maybe_schedule_update (job, child, *deleted, actually_delete); if (actually_delete) 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 end */ static void trash_empty_start () { GtkWidget *vbox1, *vbox2, *hbox; GtkWidget *label1, *label3; gchar *markup; GCancellable *cancellable; trash_empty_dialog = gtk_dialog_new (); gtk_window_set_default_size (GTK_WINDOW (trash_empty_dialog), 400, -1); gtk_window_set_icon_name (GTK_WINDOW (trash_empty_dialog), "user-trash"); gtk_window_set_title (GTK_WINDOW (trash_empty_dialog), _("Emptying the trash")); vbox1 = gtk_vbox_new (FALSE, 12); vbox2 = gtk_vbox_new (FALSE, 0); hbox = gtk_hbox_new (FALSE, 0); label1 = gtk_label_new (NULL); gtk_label_set_line_wrap (GTK_LABEL (label1), TRUE); gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.5); label3 = gtk_label_new (NULL); gtk_label_set_line_wrap (GTK_LABEL (label3), TRUE); gtk_misc_set_alignment (GTK_MISC (label3), 0.0, 0.5); gtk_widget_hide (label3); location_label = gtk_label_new (NULL); gtk_label_set_line_wrap (GTK_LABEL (location_label), TRUE); gtk_misc_set_alignment (GTK_MISC (location_label), 0.0, 0.5); file_label = gtk_label_new (NULL); gtk_label_set_line_wrap (GTK_LABEL (file_label), TRUE); gtk_misc_set_alignment (GTK_MISC (file_label), 0.0, 0.5); progressbar = gtk_progress_bar_new (); gtk_progress_bar_set_pulse_step (GTK_PROGRESS_BAR (progressbar), 0.1); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progressbar), _("Preparing to empty trash…")); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (trash_empty_dialog))), vbox1, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (vbox1), label1, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), label3, FALSE, TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), location_label, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (vbox1), hbox, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (vbox2), progressbar, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (vbox2), file_label, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (vbox1), vbox2, TRUE, TRUE, 0); gtk_widget_show (label1); gtk_widget_show (vbox1); gtk_widget_show_all (vbox2); gtk_widget_show (hbox); gtk_widget_show (location_label); gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (trash_empty_dialog))), 6); gtk_container_set_border_width (GTK_CONTAINER (vbox1), 6); gtk_dialog_add_button (GTK_DIALOG (trash_empty_dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); markup = g_markup_printf_escaped ("<big><b>%s</b></big>", _("Emptying the trash")); gtk_label_set_markup (GTK_LABEL (label1), markup); /* Translators: "Emptying trash from <device>" */ gtk_label_set_text (GTK_LABEL (label3), _("From: ")); 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); gtk_widget_show (trash_empty_dialog); g_free (markup); g_object_unref (cancellable); } 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_destroy (GTK_WIDGET (dialog)); trash_empty_confirm_dialog = NULL; } static gboolean trash_empty_require_confirmation () { GSettings *settings; gboolean require_confirmation = TRUE; settings = g_settings_new (CAJA_PREFS_SCHEMA); require_confirmation = g_settings_get_boolean (settings, CAJA_CONFIRM_TRASH_KEY); g_object_unref (settings); return require_confirmation; } static void trash_empty_show_confirmation_dialog () { GtkWidget *button; if (!trash_empty_require_confirmation ()) { trash_empty_start (); return; } trash_empty_confirm_dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _("Empty all of the items from the trash?")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (trash_empty_confirm_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_dialog_add_button (GTK_DIALOG (trash_empty_confirm_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 (trash_empty_confirm_dialog), button, GTK_RESPONSE_YES); gtk_dialog_set_default_response (GTK_DIALOG (trash_empty_confirm_dialog), GTK_RESPONSE_YES); gtk_window_set_icon_name (GTK_WINDOW (trash_empty_confirm_dialog), "user-trash"); gtk_widget_show (trash_empty_confirm_dialog); g_signal_connect (trash_empty_confirm_dialog, "response", G_CALLBACK (trash_empty_confirmation_response), NULL); } void msd_ldsm_trash_empty () { 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)); else trash_empty_show_confirmation_dialog (); }