~ubuntu-branches/ubuntu/hardy/gnome-applets/hardy

« back to all changes in this revision

Viewing changes to trashapplet/src/trash-empty.c

  • Committer: Bazaar Package Importer
  • Author(s): Sebastien Bacher
  • Date: 2008-02-26 09:27:38 UTC
  • mto: This revision was merged to the branch mainline in revision 66.
  • Revision ID: james.westby@ubuntu.com-20080226092738-75jsq058qu9w4qwq
Tags: upstream-2.21.92
Import upstream version 2.21.92

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * trash-empty.c: a routine to empty the trash
 
3
 *
 
4
 * Copyright © 2008 Ryan Lortie
 
5
 *
 
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.
 
10
 *
 
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.
 
15
 *
 
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
 
19
 * 02111-1307, USA.
 
20
 */
 
21
 
 
22
#include <gconf/gconf-client.h>
 
23
#include <gio/gio.h>
 
24
#include <gnome.h>
 
25
 
 
26
#include "trash-empty.h"
 
27
#include "config.h"
 
28
 
 
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;
 
35
 
 
36
/* the rules:
 
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.
 
41
 *
 
42
 * i -think- this is threadsafe...  ((famous last words...))
 
43
 */
 
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;
 
48
 
 
49
static gboolean
 
50
trash_empty_clear_pending (gpointer user_data)
 
51
{
 
52
  trash_empty_update_pending = FALSE;
 
53
 
 
54
  return FALSE;
 
55
}
 
56
 
 
57
static gboolean
 
58
trash_empty_update_dialog (gpointer user_data)
 
59
{
 
60
  gsize deleted, total;
 
61
  GFile *file;
 
62
 
 
63
  g_assert (trash_empty_update_pending);
 
64
 
 
65
  deleted = trash_empty_deleted_files;
 
66
  total = trash_empty_total_files;
 
67
  file = trash_empty_current_file;
 
68
 
 
69
  /* maybe the done() got processed first. */
 
70
  if (trash_empty_dialog)
 
71
    {
 
72
      char *index_str, *total_str;
 
73
      char *text;
 
74
      char *tmp;
 
75
 
 
76
      /* this is seriously broken, but we're string frozen... */
 
77
      /* FIXME: change to %d of %d after branching.
 
78
       */
 
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);
 
84
      g_free (total_str);
 
85
      g_free (index_str);
 
86
      g_free (text);
 
87
 
 
88
      if (deleted > total)
 
89
        gtk_progress_bar_set_fraction (trash_empty_progress_bar, 1.0);
 
90
      else
 
91
        gtk_progress_bar_set_fraction (trash_empty_progress_bar,
 
92
                                       (gdouble) deleted / (gdouble) total);
 
93
 
 
94
      /* no g_file_get_basename? */
 
95
      {
 
96
        GFile *parent;
 
97
       
 
98
        parent = g_file_get_parent (file);
 
99
        text = g_file_get_uri (parent);
 
100
        g_object_unref (parent);
 
101
      }
 
102
      gtk_label_set_text (trash_empty_location, text);
 
103
      g_free (text);
 
104
 
 
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);
 
108
      g_free (text);
 
109
      g_free (tmp);
 
110
 
 
111
      /* unhide the labels */
 
112
      gtk_widget_show_all (GTK_WIDGET (trash_empty_dialog));
 
113
    }
 
114
 
 
115
  trash_empty_current_file = NULL;
 
116
  g_object_unref (file);
 
117
 
 
118
  trash_empty_update_pending = FALSE;
 
119
 
 
120
  return FALSE;
 
121
}
 
122
 
 
123
static gboolean
 
124
trash_empty_done (gpointer user_data)
 
125
{
 
126
  gtk_object_destroy (GTK_OBJECT (trash_empty_dialog));
 
127
 
 
128
  g_assert (trash_empty_dialog == NULL);
 
129
 
 
130
  return FALSE;
 
131
}
 
132
 
 
133
/* =============== worker thread code begins here =============== */
 
134
static void
 
135
trash_empty_maybe_schedule_update (GIOSchedulerJob *job,
 
136
                                   GFile           *file,
 
137
                                   gsize            deleted)
 
138
{
 
139
  if (!trash_empty_update_pending)
 
140
    {
 
141
      g_assert (trash_empty_current_file == NULL);
 
142
 
 
143
      trash_empty_current_file = g_object_ref (file);
 
144
      trash_empty_deleted_files = deleted;
 
145
 
 
146
      trash_empty_update_pending = TRUE;
 
147
      g_io_scheduler_job_send_to_mainloop_async (job,
 
148
                                                 trash_empty_update_dialog,
 
149
                                                 NULL, NULL);
 
150
    }
 
151
}
 
152
 
 
153
static void
 
154
trash_empty_delete_contents (GIOSchedulerJob *job,
 
155
                             GCancellable    *cancellable,
 
156
                             GFile           *file,
 
157
                             gboolean         actually_delete,
 
158
                             gsize           *deleted)
 
159
{
 
160
  GFileEnumerator *enumerator;
 
161
  GFileInfo *info;
 
162
  GFile *child;
 
163
 
 
164
  if (g_cancellable_is_cancelled (cancellable))
 
165
    return;
 
166
 
 
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,
 
171
                                          cancellable, NULL);
 
172
  if (enumerator) 
 
173
    {
 
174
      while ((info = g_file_enumerator_next_file (enumerator,
 
175
                                                  cancellable, NULL)) != NULL)
 
176
        {
 
177
          child = g_file_get_child (file, g_file_info_get_name (info));
 
178
 
 
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);
 
182
 
 
183
          if (actually_delete)
 
184
            {
 
185
              trash_empty_maybe_schedule_update (job, child, *deleted);
 
186
              g_file_delete (child, cancellable, NULL);
 
187
            }
 
188
 
 
189
          (*deleted)++;
 
190
 
 
191
          g_object_unref (child);
 
192
          g_object_unref (info);
 
193
 
 
194
          if (g_cancellable_is_cancelled (cancellable))
 
195
            break;
 
196
        }
 
197
 
 
198
      g_object_unref (enumerator);
 
199
    }
 
200
}
 
201
 
 
202
static gboolean
 
203
trash_empty_job (GIOSchedulerJob *job,
 
204
                 GCancellable    *cancellable,
 
205
                 gpointer         user_data)
 
206
{
 
207
  gsize deleted;
 
208
  GFile *trash;
 
209
 
 
210
  trash = g_file_new_for_uri ("trash:///");
 
211
 
 
212
  /* first do a dry run to count the number of files */
 
213
  deleted = 0;
 
214
  trash_empty_delete_contents (job, cancellable, trash, FALSE, &deleted);
 
215
  trash_empty_total_files = deleted;
 
216
 
 
217
  /* now do the real thing */ 
 
218
  deleted = 0;
 
219
  trash_empty_delete_contents (job, cancellable, trash, TRUE, &deleted);
 
220
 
 
221
  /* done */
 
222
  g_object_unref (trash);
 
223
  g_io_scheduler_job_send_to_mainloop_async (job,
 
224
                                             trash_empty_done,
 
225
                                             NULL, NULL);
 
226
 
 
227
  return FALSE;
 
228
}
 
229
/* ================ worker thread code ends here ================ */
 
230
 
 
231
static void
 
232
trash_empty_start (GtkWidget *parent)
 
233
{
 
234
  struct { const char *name; gpointer *pointer; } widgets[] =
 
235
    {
 
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          }
 
240
    };
 
241
  GCancellable *cancellable;
 
242
  GtkBuilder *builder;
 
243
  gint i;
 
244
 
 
245
  builder = gtk_builder_new ();
 
246
  gtk_builder_add_from_file (builder,
 
247
                             GNOME_GLADEDIR "/trashapplet-empty-progress.ui",
 
248
                             NULL);
 
249
 
 
250
  for (i = 0; i < G_N_ELEMENTS (widgets); i++)
 
251
    {
 
252
      GObject *object;
 
253
 
 
254
      object = gtk_builder_get_object (builder, widgets[i].name);
 
255
 
 
256
      if (object == NULL)
 
257
        {
 
258
          g_critical ("failed to parse trash-empty dialog markup");
 
259
 
 
260
          if (trash_empty_dialog)
 
261
            gtk_object_destroy (GTK_OBJECT (trash_empty_dialog));
 
262
 
 
263
          g_object_unref (builder);
 
264
          return;
 
265
        }
 
266
 
 
267
      *widgets[i].pointer = object;
 
268
      g_object_add_weak_pointer (object, widgets[i].pointer);
 
269
    }
 
270
  g_object_unref (builder);
 
271
 
 
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);
 
278
 
 
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));
 
282
}
 
283
 
 
284
static gboolean
 
285
trash_empty_require_confirmation (void)
 
286
{
 
287
  return gconf_client_get_bool (gconf_client_get_default (),
 
288
                                "/apps/nautilus/preferences/confirm_trash",
 
289
                                NULL);
 
290
}
 
291
 
 
292
static void
 
293
trash_empty_confirmation_response (GtkDialog *dialog,
 
294
                                   gint       response_id,
 
295
                                   gpointer   user_data)
 
296
{
 
297
  if (response_id == GTK_RESPONSE_YES)
 
298
    trash_empty_start (GTK_WIDGET (dialog));
 
299
 
 
300
  gtk_object_destroy (GTK_OBJECT (dialog));
 
301
  g_assert (trash_empty_confirm_dialog == NULL);
 
302
}
 
303
 
 
304
/*
 
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.
 
308
 */
 
309
static void
 
310
trash_empty_show_confirmation_dialog (GtkWidget *parent)
 
311
{
 
312
  GtkWidget *dialog;
 
313
  GtkWidget *button;
 
314
  GdkScreen *screen;
 
315
 
 
316
  if (!trash_empty_require_confirmation ())
 
317
    {
 
318
      trash_empty_start (parent);
 
319
      return;
 
320
    }
 
321
 
 
322
  screen = gtk_widget_get_screen (parent);
 
323
 
 
324
  dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
 
325
                                   GTK_MESSAGE_WARNING,
 
326
                                   GTK_BUTTONS_NONE,
 
327
                                   _("Empty all of the items from "
 
328
                                     "the trash?"));
 
329
  trash_empty_confirm_dialog = GTK_DIALOG (dialog);
 
330
  g_object_add_weak_pointer (G_OBJECT (dialog),
 
331
                             (gpointer *) &trash_empty_confirm_dialog);
 
332
 
 
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 "
 
339
                                              "separately."));
 
340
 
 
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");
 
344
 
 
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));
 
349
 
 
350
  gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL,
 
351
                         GTK_RESPONSE_CANCEL);
 
352
 
 
353
  button = gtk_button_new_with_mnemonic (_("_Empty Trash"));
 
354
  gtk_widget_show (button);
 
355
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
 
356
 
 
357
  gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
 
358
                                GTK_RESPONSE_YES);
 
359
 
 
360
  gtk_dialog_set_default_response (GTK_DIALOG (dialog),
 
361
                                   GTK_RESPONSE_YES);
 
362
 
 
363
  gtk_widget_show (dialog);
 
364
 
 
365
  g_signal_connect (dialog, "response",
 
366
                    G_CALLBACK (trash_empty_confirmation_response), NULL);
 
367
}
 
368
 
 
369
void
 
370
trash_empty (GtkWidget *parent)
 
371
{
 
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));
 
376
 
 
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);
 
380
}