2
* trash-empty.c: a routine to empty the trash
4
* Copyright © 2008 Ryan Lortie
6
* This program is free software; you can redistribute it and/or
7
* modify it under the terms of the GNU General Public License as
8
* published by the Free Software Foundation; either version 2 of the
9
* License, or (at your option) any later version.
11
* This program is distributed in the hope that it will be useful, but
12
* WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
* General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22
#include <gconf/gconf-client.h>
26
#include "trash-empty.h"
29
/* only one concurrent trash empty operation can occur */
30
static GtkDialog *trash_empty_confirm_dialog;
31
static GtkDialog *trash_empty_dialog;
32
static GtkProgressBar *trash_empty_progress_bar;
33
static GtkLabel *trash_empty_location;
34
static GtkLabel *trash_empty_file;
37
* 1) nothing here may be modified while trash_empty_update_pending.
38
* 2) an idle may only be scheduled if trash_empty_update_pending.
39
* 3) only the worker may set trash_empty_update_pending = TRUE.
40
* 4) only the UI updater may set trash_empty_update_pending = FALSE.
42
* i -think- this is threadsafe... ((famous last words...))
44
static GFile * volatile trash_empty_current_file;
45
static gsize volatile trash_empty_deleted_files;
46
static gsize volatile trash_empty_total_files;
47
static gboolean volatile trash_empty_update_pending;
50
trash_empty_clear_pending (gpointer user_data)
52
trash_empty_update_pending = FALSE;
58
trash_empty_update_dialog (gpointer user_data)
63
g_assert (trash_empty_update_pending);
65
deleted = trash_empty_deleted_files;
66
total = trash_empty_total_files;
67
file = trash_empty_current_file;
69
/* maybe the done() got processed first. */
70
if (trash_empty_dialog)
72
char *index_str, *total_str;
76
/* this is seriously broken, but we're string frozen... */
77
/* FIXME: change to %d of %d after branching.
79
index_str = g_strdup_printf ("%"G_GSIZE_FORMAT, deleted + 1);
80
total_str = g_strdup_printf ("%"G_GSIZE_FORMAT, total);
81
text = g_strdup_printf (_("Removing item %s of %s"),
82
index_str, total_str);
83
gtk_progress_bar_set_text (trash_empty_progress_bar, text);
89
gtk_progress_bar_set_fraction (trash_empty_progress_bar, 1.0);
91
gtk_progress_bar_set_fraction (trash_empty_progress_bar,
92
(gdouble) deleted / (gdouble) total);
94
/* no g_file_get_basename? */
98
parent = g_file_get_parent (file);
99
text = g_file_get_uri (parent);
100
g_object_unref (parent);
102
gtk_label_set_text (trash_empty_location, text);
105
tmp = g_file_get_basename (file);
106
text = g_markup_printf_escaped (_("<i>Removing: %s</i>"), tmp);
107
gtk_label_set_markup (trash_empty_file, text);
111
/* unhide the labels */
112
gtk_widget_show_all (GTK_WIDGET (trash_empty_dialog));
115
trash_empty_current_file = NULL;
116
g_object_unref (file);
118
trash_empty_update_pending = FALSE;
124
trash_empty_done (gpointer user_data)
126
gtk_object_destroy (GTK_OBJECT (trash_empty_dialog));
128
g_assert (trash_empty_dialog == NULL);
133
/* =============== worker thread code begins here =============== */
135
trash_empty_maybe_schedule_update (GIOSchedulerJob *job,
139
if (!trash_empty_update_pending)
141
g_assert (trash_empty_current_file == NULL);
143
trash_empty_current_file = g_object_ref (file);
144
trash_empty_deleted_files = deleted;
146
trash_empty_update_pending = TRUE;
147
g_io_scheduler_job_send_to_mainloop_async (job,
148
trash_empty_update_dialog,
154
trash_empty_delete_contents (GIOSchedulerJob *job,
155
GCancellable *cancellable,
157
gboolean actually_delete,
160
GFileEnumerator *enumerator;
164
if (g_cancellable_is_cancelled (cancellable))
167
enumerator = g_file_enumerate_children (file,
168
G_FILE_ATTRIBUTE_STANDARD_NAME ","
169
G_FILE_ATTRIBUTE_STANDARD_TYPE,
170
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
174
while ((info = g_file_enumerator_next_file (enumerator,
175
cancellable, NULL)) != NULL)
177
child = g_file_get_child (file, g_file_info_get_name (info));
179
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
180
trash_empty_delete_contents (job, cancellable, child,
181
actually_delete, deleted);
185
trash_empty_maybe_schedule_update (job, child, *deleted);
186
g_file_delete (child, cancellable, NULL);
191
g_object_unref (child);
192
g_object_unref (info);
194
if (g_cancellable_is_cancelled (cancellable))
198
g_object_unref (enumerator);
203
trash_empty_job (GIOSchedulerJob *job,
204
GCancellable *cancellable,
210
trash = g_file_new_for_uri ("trash:///");
212
/* first do a dry run to count the number of files */
214
trash_empty_delete_contents (job, cancellable, trash, FALSE, &deleted);
215
trash_empty_total_files = deleted;
217
/* now do the real thing */
219
trash_empty_delete_contents (job, cancellable, trash, TRUE, &deleted);
222
g_object_unref (trash);
223
g_io_scheduler_job_send_to_mainloop_async (job,
229
/* ================ worker thread code ends here ================ */
232
trash_empty_start (GtkWidget *parent)
234
struct { const char *name; gpointer *pointer; } widgets[] =
236
{ "empty_trash", (gpointer *) &trash_empty_dialog },
237
{ "progressbar", (gpointer *) &trash_empty_progress_bar },
238
{ "location_label", (gpointer *) &trash_empty_location },
239
{ "file_label", (gpointer *) &trash_empty_file }
241
GCancellable *cancellable;
245
builder = gtk_builder_new ();
246
gtk_builder_add_from_file (builder,
247
GNOME_GLADEDIR "/trashapplet-empty-progress.ui",
250
for (i = 0; i < G_N_ELEMENTS (widgets); i++)
254
object = gtk_builder_get_object (builder, widgets[i].name);
258
g_critical ("failed to parse trash-empty dialog markup");
260
if (trash_empty_dialog)
261
gtk_object_destroy (GTK_OBJECT (trash_empty_dialog));
263
g_object_unref (builder);
267
*widgets[i].pointer = object;
268
g_object_add_weak_pointer (object, widgets[i].pointer);
270
g_object_unref (builder);
272
cancellable = g_cancellable_new ();
273
g_signal_connect_object (trash_empty_dialog, "response",
274
G_CALLBACK (g_cancellable_cancel),
275
cancellable, G_CONNECT_SWAPPED);
276
g_io_scheduler_push_job (trash_empty_job, NULL, NULL, 0, cancellable);
277
g_object_unref (cancellable);
279
gtk_window_set_screen (GTK_WINDOW (trash_empty_dialog),
280
gtk_widget_get_screen (parent));
281
gtk_widget_show (GTK_WIDGET (trash_empty_dialog));
285
trash_empty_require_confirmation (void)
287
return gconf_client_get_bool (gconf_client_get_default (),
288
"/apps/nautilus/preferences/confirm_trash",
293
trash_empty_confirmation_response (GtkDialog *dialog,
297
if (response_id == GTK_RESPONSE_YES)
298
trash_empty_start (GTK_WIDGET (dialog));
300
gtk_object_destroy (GTK_OBJECT (dialog));
301
g_assert (trash_empty_confirm_dialog == NULL);
305
* The code in trash_empty_show_confirmation_dialog() was taken from
306
* libnautilus-private/nautilus-file-operations.c (confirm_empty_trash)
307
* by Michiel Sikkes <michiel@eyesopened.nl> and adapted for the applet.
310
trash_empty_show_confirmation_dialog (GtkWidget *parent)
316
if (!trash_empty_require_confirmation ())
318
trash_empty_start (parent);
322
screen = gtk_widget_get_screen (parent);
324
dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
327
_("Empty all of the items from "
329
trash_empty_confirm_dialog = GTK_DIALOG (dialog);
330
g_object_add_weak_pointer (G_OBJECT (dialog),
331
(gpointer *) &trash_empty_confirm_dialog);
333
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
334
_("If you choose to empty "
335
"the trash, all items in "
336
"it will be permanently "
337
"lost. Please note that "
338
"you can also delete them "
341
gtk_window_set_screen (GTK_WINDOW (dialog), screen);
342
atk_object_set_role (gtk_widget_get_accessible (dialog), ATK_ROLE_ALERT);
343
gtk_window_set_wmclass (GTK_WINDOW (dialog), "empty_trash", "Nautilus");
345
/* Make transient for the window group */
346
gtk_widget_realize (dialog);
347
gdk_window_set_transient_for (dialog->window,
348
gdk_screen_get_root_window (screen));
350
gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL,
351
GTK_RESPONSE_CANCEL);
353
button = gtk_button_new_with_mnemonic (_("_Empty Trash"));
354
gtk_widget_show (button);
355
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
357
gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
360
gtk_dialog_set_default_response (GTK_DIALOG (dialog),
363
gtk_widget_show (dialog);
365
g_signal_connect (dialog, "response",
366
G_CALLBACK (trash_empty_confirmation_response), NULL);
370
trash_empty (GtkWidget *parent)
372
if (trash_empty_confirm_dialog)
373
gtk_window_present (GTK_WINDOW (trash_empty_confirm_dialog));
374
else if (trash_empty_dialog)
375
gtk_window_present (GTK_WINDOW (trash_empty_dialog));
377
/* theoretically possible that an update is pending, but very unlikely. */
378
else if (!trash_empty_update_pending)
379
trash_empty_show_confirmation_dialog (parent);