2008-03-07 Cosimo Cecchi <cosimoc@gnome.org>
[nautilus.git] / libnautilus-private / nautilus-file-operations.c
blob4844aa69ebc7671aa322d5523ab49187b2e393df
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
3 /* nautilus-file-operations.c - Nautilus file operations.
5 Copyright (C) 1999, 2000 Free Software Foundation
6 Copyright (C) 2000, 2001 Eazel, Inc.
7 Copyright (C) 2007 Red Hat, Inc.
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public
20 License along with this program; if not, write to the
21 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 Boston, MA 02111-1307, USA.
24 Authors: Alexander Larsson <alexl@redhat.com>
25 Ettore Perazzoli <ettore@gnu.org>
26 Pavel Cisler <pavel@eazel.com>
29 #include <config.h>
30 #include <string.h>
31 #include <stdio.h>
32 #include <stdarg.h>
33 #include <locale.h>
34 #include <math.h>
35 #include "nautilus-file-operations.h"
37 #include "nautilus-debug-log.h"
38 #include "nautilus-file-changes-queue.h"
39 #include "nautilus-lib-self-check-functions.h"
41 #include "nautilus-progress-info.h"
43 #include <eel/eel-alert-dialog.h>
44 #include <eel/eel-glib-extensions.h>
45 #include <eel/eel-pango-extensions.h>
46 #include <eel/eel-gtk-extensions.h>
47 #include <eel/eel-stock-dialogs.h>
48 #include <eel/eel-vfs-extensions.h>
49 #include <eel/eel-mount-operation.h>
51 #include <glib/gstdio.h>
52 #include <gnome.h>
53 #include <gdk/gdkdnd.h>
54 #include <gtk/gtklabel.h>
55 #include <gtk/gtkmessagedialog.h>
56 #include <gtk/gtkwidget.h>
57 #include <gio/gio.h>
58 #include <glib/gurifuncs.h>
59 #include "nautilus-file-changes-queue.h"
60 #include "nautilus-file-private.h"
61 #include "nautilus-desktop-icon-file.h"
62 #include "nautilus-desktop-link-monitor.h"
63 #include "nautilus-global-preferences.h"
64 #include "nautilus-link.h"
65 #include "nautilus-autorun.h"
66 #include "nautilus-trash-monitor.h"
67 #include "nautilus-file-utilities.h"
69 static gboolean confirm_trash_auto_value;
71 /* TODO: TESTING!!! */
73 typedef struct {
74 GIOSchedulerJob *io_job;
75 GTimer *time;
76 GtkWindow *parent_window;
77 int screen_num;
78 NautilusProgressInfo *progress;
79 GCancellable *cancellable;
80 GHashTable *skip_files;
81 GHashTable *skip_readdir_error;
82 gboolean skip_all_error;
83 gboolean skip_all_conflict;
84 gboolean merge_all;
85 gboolean replace_all;
86 gboolean delete_all;
87 } CommonJob;
89 typedef struct {
90 CommonJob common;
91 gboolean is_move;
92 GList *files;
93 GFile *destination;
94 GdkPoint *icon_positions;
95 int n_icon_positions;
96 GHashTable *debuting_files;
97 NautilusCopyCallback done_callback;
98 gpointer done_callback_data;
99 } CopyMoveJob;
101 typedef struct {
102 CommonJob common;
103 GList *files;
104 gboolean try_trash;
105 gboolean user_cancel;
106 NautilusDeleteCallback done_callback;
107 gpointer done_callback_data;
108 } DeleteJob;
110 typedef struct {
111 CommonJob common;
112 GFile *dest_dir;
113 char *filename;
114 gboolean make_dir;
115 GFile *src;
116 char *src_data;
117 GdkPoint position;
118 gboolean has_position;
119 GFile *created_file;
120 NautilusCreateCallback done_callback;
121 gpointer done_callback_data;
122 } CreateJob;
125 typedef struct {
126 CommonJob common;
127 GList *trash_dirs;
128 NautilusOpCallback done_callback;
129 gpointer done_callback_data;
130 } EmptyTrashJob;
132 typedef struct {
133 CommonJob common;
134 GFile *file;
135 NautilusOpCallback done_callback;
136 gpointer done_callback_data;
137 guint32 file_permissions;
138 guint32 file_mask;
139 guint32 dir_permissions;
140 guint32 dir_mask;
141 } SetPermissionsJob;
143 typedef enum {
144 OP_KIND_COPY,
145 OP_KIND_MOVE,
146 OP_KIND_DELETE,
147 OP_KIND_TRASH
148 } OpKind;
150 typedef struct {
151 int num_files;
152 goffset num_bytes;
153 int num_files_since_progress;
154 OpKind op;
155 } SourceInfo;
157 typedef struct {
158 int num_files;
159 goffset num_bytes;
160 OpKind op;
161 guint64 last_report_time;
162 int last_reported_files_left;
163 } TransferInfo;
165 #define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 15
166 #define NSEC_PER_SEC 1000000000
167 #define NSEC_PER_MSEC 1000000
169 #define IS_IO_ERROR(__error, KIND) (((__error)->domain == G_IO_ERROR && (__error)->code == G_IO_ERROR_ ## KIND))
171 #define SKIP _("_Skip")
172 #define SKIP_ALL _("S_kip All")
173 #define RETRY _("_Retry")
174 #define DELETE_ALL _("Delete _All")
175 #define REPLACE _("_Replace")
176 #define REPLACE_ALL _("Replace _All")
177 #define MERGE _("_Merge")
178 #define MERGE_ALL _("Merge _All")
180 static void scan_sources (GList *files,
181 SourceInfo *source_info,
182 CommonJob *job,
183 OpKind kind);
186 static gboolean empty_trash_job (GIOSchedulerJob *io_job,
187 GCancellable *cancellable,
188 gpointer user_data);
190 static char *
191 format_time (int seconds)
193 int minutes;
194 int hours;
195 char *res;
197 if (seconds < 0) {
198 /* Just to make sure... */
199 seconds = 0;
202 if (seconds < 60) {
203 return g_strdup_printf (ngettext ("%'d second","%'d seconds", (int) seconds), (int) seconds);
206 if (seconds < 60*60) {
207 minutes = (seconds + 30) / 60;
208 return g_strdup_printf (ngettext ("%'d minute", "%'d minutes", minutes), minutes);
211 hours = seconds / (60*60);
213 if (seconds < 60*60*4) {
214 char *h, *m;
216 minutes = (seconds - hours * 60 * 60 + 30) / 60;
218 h = g_strdup_printf (ngettext ("%'d hour", "%'d hours", hours), hours);
219 m = g_strdup_printf (ngettext ("%'d minute", "%'d minutes", minutes), minutes);
220 res = g_strconcat (h, ", ", m, NULL);
221 g_free (h);
222 g_free (m);
223 return res;
226 return g_strdup_printf (ngettext ("approximately %'d hour",
227 "approximately %'d hours",
228 hours), hours);
231 /* Note that we have these two separate functions with separate format
232 * strings for ease of localization.
235 static char *
236 get_link_name (const char *name, int count)
238 const char *format;
239 char *result;
241 g_assert (name != NULL);
243 if (count < 1) {
244 g_warning ("bad count in get_link_name");
245 count = 1;
248 if (count <= 2) {
249 /* Handle special cases for low numbers.
250 * Perhaps for some locales we will need to add more.
252 switch (count) {
253 default:
254 g_assert_not_reached ();
255 /* fall through */
256 case 1:
257 /* appended to new link file */
258 format = _("Link to %s");
259 break;
260 case 2:
261 /* appended to new link file */
262 format = _("Another link to %s");
263 break;
265 result = g_strdup_printf (format, name);
267 } else {
268 /* Handle special cases for the first few numbers of each ten.
269 * For locales where getting this exactly right is difficult,
270 * these can just be made all the same as the general case below.
272 switch (count % 10) {
273 case 1:
274 /* Localizers: Feel free to leave out the "st" suffix
275 * if there's no way to do that nicely for a
276 * particular language.
278 format = _("%'dst link to %s");
279 break;
280 case 2:
281 /* appended to new link file */
282 format = _("%'dnd link to %s");
283 break;
284 case 3:
285 /* appended to new link file */
286 format = _("%'drd link to %s");
287 break;
288 default:
289 /* appended to new link file */
290 format = _("%'dth link to %s");
291 break;
293 result = g_strdup_printf (format, count, name);
296 return result;
300 /* Localizers:
301 * Feel free to leave out the st, nd, rd and th suffix or
302 * make some or all of them match.
305 /* localizers: tag used to detect the first copy of a file */
306 static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
307 /* localizers: tag used to detect the second copy of a file */
308 static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)");
310 /* localizers: tag used to detect the x11th copy of a file */
311 static const char untranslated_x11th_copy_duplicate_tag[] = N_("th copy)");
312 /* localizers: tag used to detect the x12th copy of a file */
313 static const char untranslated_x12th_copy_duplicate_tag[] = N_("th copy)");
314 /* localizers: tag used to detect the x13th copy of a file */
315 static const char untranslated_x13th_copy_duplicate_tag[] = N_("th copy)");
317 /* localizers: tag used to detect the x1st copy of a file */
318 static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)");
319 /* localizers: tag used to detect the x2nd copy of a file */
320 static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)");
321 /* localizers: tag used to detect the x3rd copy of a file */
322 static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)");
324 /* localizers: tag used to detect the xxth copy of a file */
325 static const char untranslated_th_copy_duplicate_tag[] = N_("th copy)");
327 #define COPY_DUPLICATE_TAG _(untranslated_copy_duplicate_tag)
328 #define ANOTHER_COPY_DUPLICATE_TAG _(untranslated_another_copy_duplicate_tag)
329 #define X11TH_COPY_DUPLICATE_TAG _(untranslated_x11th_copy_duplicate_tag)
330 #define X12TH_COPY_DUPLICATE_TAG _(untranslated_x12th_copy_duplicate_tag)
331 #define X13TH_COPY_DUPLICATE_TAG _(untranslated_x13th_copy_duplicate_tag)
333 #define ST_COPY_DUPLICATE_TAG _(untranslated_st_copy_duplicate_tag)
334 #define ND_COPY_DUPLICATE_TAG _(untranslated_nd_copy_duplicate_tag)
335 #define RD_COPY_DUPLICATE_TAG _(untranslated_rd_copy_duplicate_tag)
336 #define TH_COPY_DUPLICATE_TAG _(untranslated_th_copy_duplicate_tag)
338 /* localizers: appended to first file copy */
339 static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s");
340 /* localizers: appended to second file copy */
341 static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s");
343 /* localizers: appended to x11th file copy */
344 static const char untranslated_x11th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
345 /* localizers: appended to x12th file copy */
346 static const char untranslated_x12th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
347 /* localizers: appended to x13th file copy */
348 static const char untranslated_x13th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
350 /* localizers: appended to x1st file copy */
351 static const char untranslated_st_copy_duplicate_format[] = N_("%s (%'dst copy)%s");
352 /* localizers: appended to x2nd file copy */
353 static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%'dnd copy)%s");
354 /* localizers: appended to x3rd file copy */
355 static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%'drd copy)%s");
356 /* localizers: appended to xxth file copy */
357 static const char untranslated_th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
359 #define FIRST_COPY_DUPLICATE_FORMAT _(untranslated_first_copy_duplicate_format)
360 #define SECOND_COPY_DUPLICATE_FORMAT _(untranslated_second_copy_duplicate_format)
361 #define X11TH_COPY_DUPLICATE_FORMAT _(untranslated_x11th_copy_duplicate_format)
362 #define X12TH_COPY_DUPLICATE_FORMAT _(untranslated_x12th_copy_duplicate_format)
363 #define X13TH_COPY_DUPLICATE_FORMAT _(untranslated_x13th_copy_duplicate_format)
365 #define ST_COPY_DUPLICATE_FORMAT _(untranslated_st_copy_duplicate_format)
366 #define ND_COPY_DUPLICATE_FORMAT _(untranslated_nd_copy_duplicate_format)
367 #define RD_COPY_DUPLICATE_FORMAT _(untranslated_rd_copy_duplicate_format)
368 #define TH_COPY_DUPLICATE_FORMAT _(untranslated_th_copy_duplicate_format)
370 static char *
371 extract_string_until (const char *original, const char *until_substring)
373 char *result;
375 g_assert ((int) strlen (original) >= until_substring - original);
376 g_assert (until_substring - original >= 0);
378 result = g_malloc (until_substring - original + 1);
379 strncpy (result, original, until_substring - original);
380 result[until_substring - original] = '\0';
382 return result;
385 /* Dismantle a file name, separating the base name, the file suffix and removing any
386 * (xxxcopy), etc. string. Figure out the count that corresponds to the given
387 * (xxxcopy) substring.
389 static void
390 parse_previous_duplicate_name (const char *name,
391 char **name_base,
392 const char **suffix,
393 int *count)
395 const char *tag;
397 g_assert (name[0] != '\0');
399 *suffix = strchr (name + 1, '.');
400 if (*suffix == NULL || (*suffix)[1] == '\0') {
401 /* no suffix */
402 *suffix = "";
405 tag = strstr (name, COPY_DUPLICATE_TAG);
406 if (tag != NULL) {
407 if (tag > *suffix) {
408 /* handle case "foo. (copy)" */
409 *suffix = "";
411 *name_base = extract_string_until (name, tag);
412 *count = 1;
413 return;
417 tag = strstr (name, ANOTHER_COPY_DUPLICATE_TAG);
418 if (tag != NULL) {
419 if (tag > *suffix) {
420 /* handle case "foo. (another copy)" */
421 *suffix = "";
423 *name_base = extract_string_until (name, tag);
424 *count = 2;
425 return;
429 /* Check to see if we got one of st, nd, rd, th. */
430 tag = strstr (name, X11TH_COPY_DUPLICATE_TAG);
432 if (tag == NULL) {
433 tag = strstr (name, X12TH_COPY_DUPLICATE_TAG);
435 if (tag == NULL) {
436 tag = strstr (name, X13TH_COPY_DUPLICATE_TAG);
439 if (tag == NULL) {
440 tag = strstr (name, ST_COPY_DUPLICATE_TAG);
442 if (tag == NULL) {
443 tag = strstr (name, ND_COPY_DUPLICATE_TAG);
445 if (tag == NULL) {
446 tag = strstr (name, RD_COPY_DUPLICATE_TAG);
448 if (tag == NULL) {
449 tag = strstr (name, TH_COPY_DUPLICATE_TAG);
452 /* If we got one of st, nd, rd, th, fish out the duplicate number. */
453 if (tag != NULL) {
454 /* localizers: opening parentheses to match the "th copy)" string */
455 tag = strstr (name, _(" ("));
456 if (tag != NULL) {
457 if (tag > *suffix) {
458 /* handle case "foo. (22nd copy)" */
459 *suffix = "";
461 *name_base = extract_string_until (name, tag);
462 /* localizers: opening parentheses of the "th copy)" string */
463 if (sscanf (tag, _(" (%'d"), count) == 1) {
464 if (*count < 1 || *count > 1000000) {
465 /* keep the count within a reasonable range */
466 *count = 0;
468 return;
470 *count = 0;
471 return;
476 *count = 0;
477 if (**suffix != '\0') {
478 *name_base = extract_string_until (name, *suffix);
479 } else {
480 *name_base = g_strdup (name);
484 static char *
485 make_next_duplicate_name (const char *base, const char *suffix, int count)
487 const char *format;
488 char *result;
491 if (count < 1) {
492 g_warning ("bad count %d in get_duplicate_name", count);
493 count = 1;
496 if (count <= 2) {
498 /* Handle special cases for low numbers.
499 * Perhaps for some locales we will need to add more.
501 switch (count) {
502 default:
503 g_assert_not_reached ();
504 /* fall through */
505 case 1:
506 format = FIRST_COPY_DUPLICATE_FORMAT;
507 break;
508 case 2:
509 format = SECOND_COPY_DUPLICATE_FORMAT;
510 break;
513 result = g_strdup_printf (format, base, suffix);
514 } else {
516 /* Handle special cases for the first few numbers of each ten.
517 * For locales where getting this exactly right is difficult,
518 * these can just be made all the same as the general case below.
521 /* Handle special cases for x11th - x20th.
523 switch (count % 100) {
524 case 11:
525 format = X11TH_COPY_DUPLICATE_FORMAT;
526 break;
527 case 12:
528 format = X12TH_COPY_DUPLICATE_FORMAT;
529 break;
530 case 13:
531 format = X13TH_COPY_DUPLICATE_FORMAT;
532 break;
533 default:
534 format = NULL;
535 break;
538 if (format == NULL) {
539 switch (count % 10) {
540 case 1:
541 format = ST_COPY_DUPLICATE_FORMAT;
542 break;
543 case 2:
544 format = ND_COPY_DUPLICATE_FORMAT;
545 break;
546 case 3:
547 format = RD_COPY_DUPLICATE_FORMAT;
548 break;
549 default:
550 /* The general case. */
551 format = TH_COPY_DUPLICATE_FORMAT;
552 break;
556 result = g_strdup_printf (format, base, count, suffix);
559 return result;
562 static char *
563 get_duplicate_name (const char *name, int count_increment)
565 char *result;
566 char *name_base;
567 const char *suffix;
568 int count;
570 parse_previous_duplicate_name (name, &name_base, &suffix, &count);
571 result = make_next_duplicate_name (name_base, suffix, count + count_increment);
573 g_free (name_base);
575 return result;
578 static gboolean
579 has_invalid_xml_char (char *str)
581 gunichar c;
583 while (*str != 0) {
584 c = g_utf8_get_char (str);
585 /* characters XML permits */
586 if (!(c == 0x9 ||
587 c == 0xA ||
588 c == 0xD ||
589 (c >= 0x20 && c <= 0xD7FF) ||
590 (c >= 0xE000 && c <= 0xFFFD) ||
591 (c >= 0x10000 && c <= 0x10FFFF))) {
592 return TRUE;
594 str = g_utf8_next_char (str);
596 return FALSE;
600 static char *
601 custom_full_name_to_string (char *format, va_list va)
603 GFile *file;
605 file = va_arg (va, GFile *);
607 return g_file_get_parse_name (file);
610 static void
611 custom_full_name_skip (va_list *va)
613 va_arg (*va, GFile *);
616 static char *
617 custom_basename_to_string (char *format, va_list va)
619 GFile *file;
620 GFileInfo *info;
621 char *name, *basename, *tmp;
623 file = va_arg (va, GFile *);
625 info = g_file_query_info (file,
626 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
628 g_cancellable_get_current (),
629 NULL);
631 name = NULL;
632 if (info) {
633 name = g_strdup (g_file_info_get_display_name (info));
634 g_object_unref (info);
637 if (name == NULL) {
638 basename = g_file_get_basename (file);
639 if (g_utf8_validate (basename, -1, NULL)) {
640 name = basename;
641 } else {
642 name = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
643 g_free (basename);
647 /* Some chars can't be put in the markup we use for the dialogs... */
648 if (has_invalid_xml_char (name)) {
649 tmp = name;
650 name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
651 g_free (tmp);
654 return name;
657 static void
658 custom_basename_skip (va_list *va)
660 va_arg (*va, GFile *);
664 static char *
665 custom_size_to_string (char *format, va_list va)
667 goffset size;
669 size = va_arg (va, goffset);
670 return g_format_size_for_display (size);
673 static void
674 custom_size_skip (va_list *va)
676 va_arg (*va, goffset);
679 static char *
680 custom_time_to_string (char *format, va_list va)
682 int secs;
684 secs = va_arg (va, int);
685 return format_time (secs);
688 static void
689 custom_time_skip (va_list *va)
691 va_arg (*va, int);
694 static char *
695 custom_mount_to_string (char *format, va_list va)
697 GMount *mount;
699 mount = va_arg (va, GMount *);
700 return g_mount_get_name (mount);
703 static void
704 custom_mount_skip (va_list *va)
706 va_arg (*va, GMount *);
710 static EelPrintfHandler handlers[] = {
711 { 'F', custom_full_name_to_string, custom_full_name_skip },
712 { 'B', custom_basename_to_string, custom_basename_skip },
713 { 'S', custom_size_to_string, custom_size_skip },
714 { 'T', custom_time_to_string, custom_time_skip },
715 { 'V', custom_mount_to_string, custom_mount_skip },
716 { 0 }
720 static char *
721 f (const char *format, ...) {
722 va_list va;
723 char *res;
725 va_start (va, format);
726 res = eel_strdup_vprintf_with_custom (handlers, format, va);
727 va_end (va);
729 return res;
732 #define op_job_new(__type, parent_window) ((__type *)(init_common (sizeof(__type), parent_window)))
734 static gpointer
735 init_common (gsize job_size,
736 GtkWindow *parent_window)
738 CommonJob *common;
739 GdkScreen *screen;
741 common = g_malloc0 (job_size);
743 if (parent_window) {
744 common->parent_window = parent_window;
745 eel_add_weak_pointer (&common->parent_window);
747 common->progress = nautilus_progress_info_new ();
748 common->cancellable = nautilus_progress_info_get_cancellable (common->progress);
749 common->time = g_timer_new ();
751 common->screen_num = 0;
752 if (parent_window) {
753 screen = gtk_widget_get_screen (GTK_WIDGET (parent_window));
754 common->screen_num = gdk_screen_get_number (screen);
757 return common;
760 static void
761 finalize_common (CommonJob *common)
763 nautilus_progress_info_finish (common->progress);
765 g_timer_destroy (common->time);
767 eel_remove_weak_pointer (&common->parent_window);
768 if (common->skip_files) {
769 g_hash_table_destroy (common->skip_files);
771 if (common->skip_readdir_error) {
772 g_hash_table_destroy (common->skip_readdir_error);
774 g_object_unref (common->progress);
775 g_object_unref (common->cancellable);
776 g_free (common);
779 static void
780 skip_file (CommonJob *common,
781 GFile *file)
783 if (common->skip_files == NULL) {
784 common->skip_files =
785 g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
788 g_hash_table_insert (common->skip_files, g_object_ref (file), file);
791 static void
792 skip_readdir_error (CommonJob *common,
793 GFile *dir)
795 if (common->skip_readdir_error == NULL) {
796 common->skip_readdir_error =
797 g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
800 g_hash_table_insert (common->skip_readdir_error, g_object_ref (dir), dir);
803 static gboolean
804 should_skip_file (CommonJob *common,
805 GFile *file)
807 if (common->skip_files != NULL) {
808 return g_hash_table_lookup (common->skip_files, file) != NULL;
810 return FALSE;
813 static gboolean
814 should_skip_readdir_error (CommonJob *common,
815 GFile *dir)
817 if (common->skip_readdir_error != NULL) {
818 return g_hash_table_lookup (common->skip_readdir_error, dir) != NULL;
820 return FALSE;
823 static void
824 setup_autos (void)
826 static gboolean setup_autos = FALSE;
827 if (!setup_autos) {
828 setup_autos = TRUE;
829 eel_preferences_add_auto_boolean (NAUTILUS_PREFERENCES_CONFIRM_TRASH,
830 &confirm_trash_auto_value);
834 static gboolean
835 can_delete_without_confirm (GFile *file)
837 if (g_file_has_uri_scheme (file, "burn") ||
838 g_file_has_uri_scheme (file, "x-nautilus-desktop")) {
839 return TRUE;
842 return FALSE;
845 static gboolean
846 can_delete_files_without_confirm (GList *files)
848 g_assert (files != NULL);
850 while (files != NULL) {
851 if (!can_delete_without_confirm (files->data)) {
852 return FALSE;
855 files = files->next;
858 return TRUE;
861 typedef struct {
862 GtkWindow **parent_window;
863 gboolean ignore_close_box;
864 GtkMessageType message_type;
865 const char *primary_text;
866 const char *secondary_text;
867 const char *details_text;
868 const char **button_titles;
870 int result;
871 } RunSimpleDialogData;
873 static gboolean
874 do_run_simple_dialog (gpointer _data)
876 RunSimpleDialogData *data = _data;
877 const char *button_title;
878 GtkWidget *dialog;
879 int result;
880 int response_id;
882 /* Create the dialog. */
883 dialog = eel_alert_dialog_new (*data->parent_window,
885 data->message_type,
886 GTK_BUTTONS_NONE,
887 data->primary_text,
888 data->secondary_text);
890 for (response_id = 0;
891 data->button_titles[response_id] != NULL;
892 response_id++) {
893 button_title = data->button_titles[response_id];
894 gtk_dialog_add_button (GTK_DIALOG (dialog), button_title, response_id);
895 gtk_dialog_set_default_response (GTK_DIALOG (dialog), response_id);
898 if (data->details_text) {
899 eel_alert_dialog_set_details_label (EEL_ALERT_DIALOG (dialog),
900 data->details_text);
903 /* Run it. */
904 gtk_widget_show (dialog);
905 result = gtk_dialog_run (GTK_DIALOG (dialog));
907 while ((result == GTK_RESPONSE_NONE || result == GTK_RESPONSE_DELETE_EVENT) && data->ignore_close_box) {
908 gtk_widget_show (GTK_WIDGET (dialog));
909 result = gtk_dialog_run (GTK_DIALOG (dialog));
912 gtk_object_destroy (GTK_OBJECT (dialog));
914 data->result = result;
916 return FALSE;
919 /* NOTE: This frees the primary / secondary strings, in order to
920 avoid doing that everywhere. So, make sure they are strduped */
922 static int
923 run_simple_dialog_va (CommonJob *job,
924 gboolean ignore_close_box,
925 GtkMessageType message_type,
926 char *primary_text,
927 char *secondary_text,
928 const char *details_text,
929 va_list varargs)
931 RunSimpleDialogData *data;
932 int res;
933 const char *button_title;
934 GPtrArray *ptr_array;
936 g_timer_stop (job->time);
938 data = g_new0 (RunSimpleDialogData, 1);
939 data->parent_window = &job->parent_window;
940 data->ignore_close_box = ignore_close_box;
941 data->message_type = message_type;
942 data->primary_text = primary_text;
943 data->secondary_text = secondary_text;
944 data->details_text = details_text;
946 ptr_array = g_ptr_array_new ();
947 while ((button_title = va_arg (varargs, const char *)) != NULL) {
948 g_ptr_array_add (ptr_array, (char *)button_title);
950 g_ptr_array_add (ptr_array, NULL);
951 data->button_titles = (const char **)g_ptr_array_free (ptr_array, FALSE);
953 nautilus_progress_info_pause (job->progress);
954 g_io_scheduler_job_send_to_mainloop (job->io_job,
955 do_run_simple_dialog,
956 data,
957 NULL);
958 nautilus_progress_info_resume (job->progress);
959 res = data->result;
961 g_free (data->button_titles);
962 g_free (data);
964 g_timer_continue (job->time);
966 g_free (primary_text);
967 g_free (secondary_text);
969 return res;
972 #if 0 /* Not used at the moment */
973 static int
974 run_simple_dialog (CommonJob *job,
975 gboolean ignore_close_box,
976 GtkMessageType message_type,
977 char *primary_text,
978 char *secondary_text,
979 const char *details_text,
980 ...)
982 va_list varargs;
983 int res;
985 va_start (varargs, details_text);
986 res = run_simple_dialog_va (job,
987 ignore_close_box,
988 message_type,
989 primary_text,
990 secondary_text,
991 details_text,
992 varargs);
993 va_end (varargs);
994 return res;
996 #endif
998 static int
999 run_error (CommonJob *job,
1000 char *primary_text,
1001 char *secondary_text,
1002 const char *details_text,
1003 ...)
1005 va_list varargs;
1006 int res;
1008 va_start (varargs, details_text);
1009 res = run_simple_dialog_va (job,
1010 FALSE,
1011 GTK_MESSAGE_ERROR,
1012 primary_text,
1013 secondary_text,
1014 details_text,
1015 varargs);
1016 va_end (varargs);
1017 return res;
1020 static int
1021 run_warning (CommonJob *job,
1022 char *primary_text,
1023 char *secondary_text,
1024 const char *details_text,
1025 ...)
1027 va_list varargs;
1028 int res;
1030 va_start (varargs, details_text);
1031 res = run_simple_dialog_va (job,
1032 FALSE,
1033 GTK_MESSAGE_WARNING,
1034 primary_text,
1035 secondary_text,
1036 details_text,
1037 varargs);
1038 va_end (varargs);
1039 return res;
1042 static int
1043 run_question (CommonJob *job,
1044 char *primary_text,
1045 char *secondary_text,
1046 const char *details_text,
1047 ...)
1049 va_list varargs;
1050 int res;
1052 va_start (varargs, details_text);
1053 res = run_simple_dialog_va (job,
1054 FALSE,
1055 GTK_MESSAGE_QUESTION,
1056 primary_text,
1057 secondary_text,
1058 details_text,
1059 varargs);
1060 va_end (varargs);
1061 return res;
1064 static void
1065 abort_job (CommonJob *job)
1067 g_cancellable_cancel (job->cancellable);
1071 static gboolean
1072 job_aborted (CommonJob *job)
1074 return g_cancellable_is_cancelled (job->cancellable);
1077 static gboolean
1078 confirm_delete_from_trash (CommonJob *job,
1079 GList *files)
1081 char *prompt;
1082 int file_count;
1083 int response;
1085 /* Just Say Yes if the preference says not to confirm. */
1086 if (!confirm_trash_auto_value) {
1087 return TRUE;
1090 file_count = g_list_length (files);
1091 g_assert (file_count > 0);
1093 if (file_count == 1) {
1094 prompt = f (_("Are you sure you want to permanently delete \"%B\" "
1095 "from the trash?"), files->data);
1096 } else {
1097 prompt = f (ngettext("Are you sure you want to permanently delete "
1098 "the %'d selected item from the trash?",
1099 "Are you sure you want to permanently delete "
1100 "the %'d selected items from the trash?",
1101 file_count),
1102 file_count);
1105 response = run_warning (job,
1106 prompt,
1107 f (_("If you delete an item, it will be permanently lost.")),
1108 NULL,
1109 GTK_STOCK_CANCEL, GTK_STOCK_DELETE,
1110 NULL);
1112 return (response == 1);
1115 static gboolean
1116 confirm_empty_trash (CommonJob *job)
1118 char *prompt;
1119 int response;
1121 /* Just Say Yes if the preference says not to confirm. */
1122 if (!confirm_trash_auto_value) {
1123 return TRUE;
1126 prompt = f (_("Empty all of the items from the trash?"));
1128 response = run_warning (job,
1129 prompt,
1130 f(_("If you choose to empty the trash, all items "
1131 "in it will be permanently lost. Please note "
1132 "that you can also delete them separately.")),
1133 NULL,
1134 GTK_STOCK_CANCEL, GTK_STOCK_DELETE,
1135 NULL);
1137 return (response == 1);
1140 static gboolean
1141 confirm_delete_directly (CommonJob *job,
1142 GList *files)
1144 char *prompt;
1145 int file_count;
1146 int response;
1148 /* Just Say Yes if the preference says not to confirm. */
1149 if (!confirm_trash_auto_value) {
1150 return TRUE;
1153 file_count = g_list_length (files);
1154 g_assert (file_count > 0);
1156 if (can_delete_files_without_confirm (files)) {
1157 return TRUE;
1160 if (file_count == 1) {
1161 prompt = f (_("Are you sure you want to permanently delete \"%B\"?"),
1162 files->data);
1163 } else {
1164 prompt = f (ngettext("Are you sure you want to permanently delete "
1165 "the %'d selected item?",
1166 "Are you sure you want to permanently delete "
1167 "the %'d selected items?", file_count),
1168 file_count);
1171 response = run_warning (job,
1172 prompt,
1173 f (_("If you delete an item, it will be permanently lost.")),
1174 NULL,
1175 GTK_STOCK_CANCEL, GTK_STOCK_DELETE,
1176 NULL);
1178 return response == 1;
1181 static void
1182 report_delete_progress (CommonJob *job,
1183 SourceInfo *source_info,
1184 TransferInfo *transfer_info)
1186 int files_left;
1187 double elapsed, transfer_rate;
1188 int remaining_time;
1189 guint64 now;
1191 now = g_thread_gettime ();
1192 if (transfer_info->last_report_time != 0 &&
1193 ABS (transfer_info->last_report_time - now) < 100 * NSEC_PER_MSEC) {
1194 return;
1196 transfer_info->last_report_time = now;
1198 files_left = source_info->num_files - transfer_info->num_files;
1200 /* Races and whatnot could cause this to be negative... */
1201 if (files_left < 0) {
1202 files_left = 1;
1205 nautilus_progress_info_take_status (job->progress,
1206 f (_("Deleting files")));
1208 elapsed = g_timer_elapsed (job->time, NULL);
1209 if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE) {
1210 char *s;
1211 s = f (ngettext ("%'d file left to delete",
1212 "%'d files left to delete",
1213 files_left),
1214 files_left);
1215 nautilus_progress_info_take_details (job->progress, s);
1216 } else {
1217 char *s;
1218 transfer_rate = transfer_info->num_files / elapsed;
1219 remaining_time = files_left / transfer_rate;
1221 /* To translators: %T will expand to a time like "2 minutes" */
1222 s = f (ngettext ("%'d file left to delete \xE2\x80\x94 %T left",
1223 "%'d files left to delete \xE2\x80\x94 %T left",
1224 files_left),
1225 files_left, remaining_time);
1226 nautilus_progress_info_take_details (job->progress, s);
1229 if (source_info->num_files != 0) {
1230 nautilus_progress_info_set_progress (job->progress, transfer_info->num_files, source_info->num_files);
1234 static void delete_file (CommonJob *job, GFile *file,
1235 gboolean *skipped_file,
1236 SourceInfo *source_info,
1237 TransferInfo *transfer_info,
1238 gboolean toplevel);
1240 static void
1241 delete_dir (CommonJob *job, GFile *dir,
1242 gboolean *skipped_file,
1243 SourceInfo *source_info,
1244 TransferInfo *transfer_info,
1245 gboolean toplevel)
1247 GFileInfo *info;
1248 GError *error;
1249 GFile *file;
1250 GFileEnumerator *enumerator;
1251 char *primary, *secondary, *details;
1252 int response;
1253 gboolean skip_error;
1254 gboolean local_skipped_file;
1256 local_skipped_file = FALSE;
1258 skip_error = should_skip_readdir_error (job, dir);
1259 retry:
1260 error = NULL;
1261 enumerator = g_file_enumerate_children (dir,
1262 G_FILE_ATTRIBUTE_STANDARD_NAME,
1263 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
1264 job->cancellable,
1265 &error);
1266 if (enumerator) {
1267 error = NULL;
1269 while (!job_aborted (job) &&
1270 (info = g_file_enumerator_next_file (enumerator, job->cancellable, skip_error?NULL:&error)) != NULL) {
1271 file = g_file_get_child (dir,
1272 g_file_info_get_name (info));
1273 delete_file (job, file, &local_skipped_file, source_info, transfer_info, FALSE);
1274 g_object_unref (file);
1275 g_object_unref (info);
1277 g_file_enumerator_close (enumerator, job->cancellable, NULL);
1278 g_object_unref (enumerator);
1280 if (error && IS_IO_ERROR (error, CANCELLED)) {
1281 g_error_free (error);
1282 } else if (error) {
1283 primary = f (_("Error while deleting."));
1284 details = NULL;
1286 if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
1287 secondary = f (_("Files in the folder \"%B\" cannot be deleted because you do "
1288 "not have permissions to see them."), dir);
1289 } else {
1290 secondary = f (_("There was an error getting information about the files in the folder \"%B\"."), dir);
1291 details = error->message;
1294 response = run_warning (job,
1295 primary,
1296 secondary,
1297 details,
1298 GTK_STOCK_CANCEL, _("_Skip files"),
1299 NULL);
1301 g_error_free (error);
1303 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
1304 abort_job (job);
1305 } else if (response == 1) {
1306 /* Skip: Do Nothing */
1307 local_skipped_file = TRUE;
1308 } else {
1309 g_assert_not_reached ();
1313 } else if (IS_IO_ERROR (error, CANCELLED)) {
1314 g_error_free (error);
1315 } else {
1316 primary = f (_("Error while deleting."));
1317 details = NULL;
1318 if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
1319 secondary = f (_("The folder \"%B\" cannot be deleted because you do not have "
1320 "permissions to read it."), dir);
1321 } else {
1322 secondary = f (_("There was an error reading the folder \"%B\"."), dir);
1323 details = error->message;
1326 response = run_warning (job,
1327 primary,
1328 secondary,
1329 details,
1330 GTK_STOCK_CANCEL, SKIP, RETRY,
1331 NULL);
1333 g_error_free (error);
1335 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
1336 abort_job (job);
1337 } else if (response == 1) {
1338 /* Skip: Do Nothing */
1339 local_skipped_file = TRUE;
1340 } else if (response == 2) {
1341 goto retry;
1342 } else {
1343 g_assert_not_reached ();
1347 if (!job_aborted (job) &&
1348 /* Don't delete dir if there was a skipped file */
1349 !local_skipped_file) {
1350 if (!g_file_delete (dir, job->cancellable, &error)) {
1351 if (job->skip_all_error) {
1352 goto skip;
1354 primary = f (_("Error while deleting."));
1355 secondary = f (_("Couldn't remove the folder %B."), dir);
1356 details = error->message;
1358 response = run_warning (job,
1359 primary,
1360 secondary,
1361 details,
1362 GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
1363 NULL);
1365 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
1366 abort_job (job);
1367 } else if (response == 1) { /* skip all */
1368 job->skip_all_error = TRUE;
1369 local_skipped_file = TRUE;
1370 } else if (response == 2) { /* skip */
1371 local_skipped_file = TRUE;
1372 } else {
1373 g_assert_not_reached ();
1376 skip:
1377 g_error_free (error);
1378 } else {
1379 if (toplevel) {
1380 nautilus_file_changes_queue_schedule_metadata_remove (dir);
1382 nautilus_file_changes_queue_file_removed (dir);
1383 transfer_info->num_files ++;
1384 report_delete_progress (job, source_info, transfer_info);
1385 return;
1389 if (local_skipped_file) {
1390 *skipped_file = TRUE;
1394 static void
1395 delete_file (CommonJob *job, GFile *file,
1396 gboolean *skipped_file,
1397 SourceInfo *source_info,
1398 TransferInfo *transfer_info,
1399 gboolean toplevel)
1401 GError *error;
1402 char *primary, *secondary, *details;
1403 int response;
1405 if (should_skip_file (job, file)) {
1406 *skipped_file = TRUE;
1407 return;
1410 error = NULL;
1411 if (g_file_delete (file, job->cancellable, &error)) {
1412 if (toplevel) {
1413 nautilus_file_changes_queue_schedule_metadata_remove (file);
1415 nautilus_file_changes_queue_file_removed (file);
1416 transfer_info->num_files ++;
1417 report_delete_progress (job, source_info, transfer_info);
1418 return;
1421 if (IS_IO_ERROR (error, NOT_EMPTY)) {
1422 g_error_free (error);
1423 delete_dir (job, file,
1424 skipped_file,
1425 source_info, transfer_info,
1426 toplevel);
1427 return;
1429 } else if (IS_IO_ERROR (error, CANCELLED)) {
1430 g_error_free (error);
1432 } else {
1433 if (job->skip_all_error) {
1434 goto skip;
1436 primary = f (_("Error while deleting."));
1437 secondary = f (_("There was an error deleting %B."), file);
1438 details = error->message;
1440 response = run_warning (job,
1441 primary,
1442 secondary,
1443 details,
1444 GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
1445 NULL);
1447 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
1448 abort_job (job);
1449 } else if (response == 1) { /* skip all */
1450 job->skip_all_error = TRUE;
1451 } else if (response == 2) { /* skip */
1452 /* do nothing */
1453 } else {
1454 g_assert_not_reached ();
1456 skip:
1457 g_error_free (error);
1460 *skipped_file = TRUE;
1463 static void
1464 delete_files (CommonJob *job, GList *files)
1466 GList *l;
1467 GFile *file;
1468 SourceInfo source_info;
1469 TransferInfo transfer_info;
1470 gboolean skipped_file;
1472 if (job_aborted (job)) {
1473 return;
1476 scan_sources (files,
1477 &source_info,
1478 job,
1479 OP_KIND_DELETE);
1480 if (job_aborted (job)) {
1481 return;
1484 g_timer_start (job->time);
1486 memset (&transfer_info, 0, sizeof (transfer_info));
1487 report_delete_progress (job, &source_info, &transfer_info);
1489 for (l = files;
1490 l != NULL && !job_aborted (job);
1491 l = l->next) {
1492 file = l->data;
1494 skipped_file = FALSE;
1495 delete_file (job, file,
1496 &skipped_file,
1497 &source_info, &transfer_info,
1498 TRUE);
1502 static void
1503 report_trash_progress (CommonJob *job,
1504 int files_trashed,
1505 int total_files)
1507 int files_left;
1508 char *s;
1510 files_left = total_files - files_trashed;
1512 nautilus_progress_info_take_status (job->progress,
1513 f (_("Moving files to trash")));
1515 s = f (ngettext ("%'d file left to trash",
1516 "%'d files left to trash",
1517 files_left),
1518 files_left);
1519 nautilus_progress_info_take_details (job->progress, s);
1521 if (total_files != 0) {
1522 nautilus_progress_info_set_progress (job->progress, files_trashed, total_files);
1527 static void
1528 trash_files (CommonJob *job, GList *files)
1530 GList *l;
1531 GFile *file;
1532 GList *to_delete;
1533 GError *error;
1534 int total_files, files_trashed;
1535 char *primary, *secondary, *details;
1536 int response;
1538 if (job_aborted (job)) {
1539 return;
1542 total_files = g_list_length (files);
1543 files_trashed = 0;
1545 report_trash_progress (job, files_trashed, total_files);
1547 to_delete = NULL;
1548 for (l = files;
1549 l != NULL && !job_aborted (job);
1550 l = l->next) {
1551 file = l->data;
1553 error = NULL;
1554 if (!g_file_trash (file, job->cancellable, &error)) {
1555 if (job->skip_all_error) {
1556 goto skip;
1559 if (job->delete_all) {
1560 to_delete = g_list_prepend (to_delete, file);
1561 goto skip;
1564 primary = f (_("Cannot move file to trash, do you want to delete immediately?"));
1565 secondary = f (_("The file \"%B\" cannot be moved to the trash."), file);
1566 details = NULL;
1567 if (!IS_IO_ERROR (error, NOT_SUPPORTED)) {
1568 details = error->message;
1571 response = run_question (job,
1572 primary,
1573 secondary,
1574 details,
1575 GTK_STOCK_CANCEL, SKIP_ALL, SKIP, DELETE_ALL, GTK_STOCK_DELETE,
1576 NULL);
1578 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
1579 abort_job (job);
1580 } else if (response == 1) { /* skip all */
1581 job->skip_all_error = TRUE;
1582 } else if (response == 2) { /* skip */
1583 /* nothing */
1584 } else if (response == 3) { /* delete all */
1585 to_delete = g_list_prepend (to_delete, file);
1586 job->delete_all = TRUE;
1587 } else if (response == 4) { /* delete */
1588 to_delete = g_list_prepend (to_delete, file);
1591 skip:
1592 g_error_free (error);
1593 total_files--;
1594 } else {
1595 nautilus_file_changes_queue_schedule_metadata_remove (file);
1596 nautilus_file_changes_queue_file_removed (file);
1598 files_trashed++;
1599 report_trash_progress (job, files_trashed, total_files);
1603 if (to_delete) {
1604 to_delete = g_list_reverse (to_delete);
1605 delete_files (job, to_delete);
1606 g_list_free (to_delete);
1610 static gboolean
1611 delete_job_done (gpointer user_data)
1613 DeleteJob *job;
1614 GHashTable *debuting_uris;
1616 job = user_data;
1618 eel_g_object_list_free (job->files);
1620 if (job->done_callback) {
1621 debuting_uris = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
1622 job->done_callback (debuting_uris, job->user_cancel, job->done_callback_data);
1623 g_hash_table_unref (debuting_uris);
1626 finalize_common ((CommonJob *)job);
1628 nautilus_file_changes_consume_changes (TRUE);
1630 return FALSE;
1633 static gboolean
1634 delete_job (GIOSchedulerJob *io_job,
1635 GCancellable *cancellable,
1636 gpointer user_data)
1638 DeleteJob *job = user_data;
1639 GList *to_trash_files;
1640 GList *to_delete_files;
1641 GList *l;
1642 GFile *file;
1643 gboolean confirmed;
1644 CommonJob *common;
1645 gboolean must_confirm_delete_in_trash;
1646 gboolean must_confirm_delete;
1648 common = (CommonJob *)job;
1649 common->io_job = io_job;
1651 nautilus_progress_info_start (job->common.progress);
1653 to_trash_files = NULL;
1654 to_delete_files = NULL;
1656 must_confirm_delete_in_trash = FALSE;
1657 must_confirm_delete = FALSE;
1659 for (l = job->files; l != NULL; l = l->next) {
1660 file = l->data;
1662 if (job->try_trash &&
1663 g_file_has_uri_scheme (file, "trash")) {
1664 must_confirm_delete_in_trash = TRUE;
1665 to_delete_files = g_list_prepend (to_delete_files, file);
1666 } else if (can_delete_without_confirm (file)) {
1667 to_delete_files = g_list_prepend (to_delete_files, file);
1668 } else {
1669 if (job->try_trash) {
1670 to_trash_files = g_list_prepend (to_trash_files, file);
1671 } else {
1672 must_confirm_delete = TRUE;
1673 to_delete_files = g_list_prepend (to_delete_files, file);
1678 if (to_delete_files != NULL) {
1679 to_delete_files = g_list_reverse (to_delete_files);
1680 confirmed = TRUE;
1681 if (must_confirm_delete_in_trash) {
1682 confirmed = confirm_delete_from_trash (common, to_delete_files);
1683 } else if (must_confirm_delete) {
1684 confirmed = confirm_delete_directly (common, to_delete_files);
1686 if (confirmed) {
1687 delete_files (common, to_delete_files);
1688 } else {
1689 job->user_cancel = TRUE;
1693 if (to_trash_files != NULL) {
1694 to_trash_files = g_list_reverse (to_trash_files);
1696 trash_files (common, to_trash_files);
1699 g_list_free (to_trash_files);
1700 g_list_free (to_delete_files);
1702 g_io_scheduler_job_send_to_mainloop_async (io_job,
1703 delete_job_done,
1704 job,
1705 NULL);
1707 return FALSE;
1710 static void
1711 trash_or_delete_internal (GList *files,
1712 GtkWindow *parent_window,
1713 gboolean try_trash,
1714 NautilusDeleteCallback done_callback,
1715 gpointer done_callback_data)
1717 DeleteJob *job;
1719 setup_autos ();
1721 /* TODO: special case desktop icon link files ... */
1723 job = op_job_new (DeleteJob, parent_window);
1724 job->files = eel_g_object_list_copy (files);
1725 job->try_trash = try_trash;
1726 job->user_cancel = FALSE;
1727 job->done_callback = done_callback;
1728 job->done_callback_data = done_callback_data;
1730 g_io_scheduler_push_job (delete_job,
1731 job,
1732 NULL,
1734 NULL);
1737 void
1738 nautilus_file_operations_trash_or_delete (GList *files,
1739 GtkWindow *parent_window,
1740 NautilusDeleteCallback done_callback,
1741 gpointer done_callback_data)
1743 trash_or_delete_internal (files, parent_window,
1744 TRUE,
1745 done_callback, done_callback_data);
1748 void
1749 nautilus_file_operations_delete (GList *files,
1750 GtkWindow *parent_window,
1751 NautilusDeleteCallback done_callback,
1752 gpointer done_callback_data)
1754 trash_or_delete_internal (files, parent_window,
1755 FALSE,
1756 done_callback, done_callback_data);
1761 typedef struct {
1762 gboolean eject;
1763 GMount *mount;
1764 GtkWindow *parent_window;
1765 } UnmountData;
1767 static void
1768 unmount_mount_callback (GObject *source_object,
1769 GAsyncResult *res,
1770 gpointer user_data)
1772 UnmountData *data = user_data;
1773 GError *error;
1774 char *primary;
1775 gboolean unmounted;
1777 error = NULL;
1778 if (data->eject) {
1779 unmounted = g_mount_unmount_finish (G_MOUNT (source_object),
1780 res, &error);
1781 } else {
1782 unmounted = g_mount_eject_finish (G_MOUNT (source_object),
1783 res, &error);
1786 if (! unmounted) {
1787 if (error->code != G_IO_ERROR_FAILED_HANDLED) {
1788 if (data->eject) {
1789 primary = f (_("Unable to eject %V"), source_object);
1790 } else {
1791 primary = f (_("Unable to unmount %V"), source_object);
1793 eel_show_error_dialog (primary,
1794 error->message,
1795 data->parent_window);
1796 g_free (primary);
1798 g_error_free (error);
1801 eel_remove_weak_pointer (&data->parent_window);
1802 g_object_unref (data->mount);
1803 g_free (data);
1806 static void
1807 do_unmount (UnmountData *data)
1809 if (data->eject) {
1810 g_mount_eject (data->mount,
1811 0, NULL,
1812 unmount_mount_callback,
1813 data);
1814 } else {
1815 g_mount_unmount (data->mount,
1816 0, NULL,
1817 unmount_mount_callback,
1818 data);
1822 static gboolean
1823 dir_has_files (GFile *dir)
1825 GFileEnumerator *enumerator;
1826 gboolean res;
1827 GFileInfo *file_info;
1829 res = FALSE;
1831 enumerator = g_file_enumerate_children (dir,
1832 G_FILE_ATTRIBUTE_STANDARD_NAME,
1834 NULL, NULL);
1835 if (enumerator) {
1836 file_info = g_file_enumerator_next_file (enumerator, NULL, NULL);
1837 if (file_info != NULL) {
1838 res = TRUE;
1839 g_object_unref (file_info);
1842 g_file_enumerator_close (enumerator, NULL, NULL);
1843 g_object_unref (enumerator);
1847 return res;
1850 static GList *
1851 get_trash_dirs_for_mount (GMount *mount)
1853 GFile *root;
1854 GFile *trash;
1855 char *relpath;
1856 GList *list;
1858 root = g_mount_get_root (mount);
1859 if (root == NULL) {
1860 return NULL;
1863 list = NULL;
1865 if (g_file_is_native (root)) {
1866 relpath = g_strdup_printf (".Trash/%d", getuid ());
1867 trash = g_file_resolve_relative_path (root, relpath);
1868 g_free (relpath);
1870 list = g_list_prepend (list, g_file_get_child (trash, "files"));
1871 list = g_list_prepend (list, g_file_get_child (trash, "info"));
1873 g_object_unref (trash);
1875 relpath = g_strdup_printf (".Trash-%d", getuid ());
1876 trash = g_file_get_child (root, relpath);
1877 g_free (relpath);
1879 list = g_list_prepend (list, g_file_get_child (trash, "files"));
1880 list = g_list_prepend (list, g_file_get_child (trash, "info"));
1882 g_object_unref (trash);
1885 g_object_unref (root);
1887 return list;
1890 static gboolean
1891 has_trash_files (GMount *mount)
1893 GList *dirs, *l;
1894 GFile *dir;
1895 gboolean res;
1897 dirs = get_trash_dirs_for_mount (mount);
1899 res = FALSE;
1901 for (l = dirs; l != NULL; l = l->next) {
1902 dir = l->data;
1904 if (dir_has_files (dir)) {
1905 res = TRUE;
1906 break;
1910 eel_g_object_list_free (dirs);
1912 return res;
1916 static gint
1917 prompt_empty_trash (GtkWindow *parent_window)
1919 gint result;
1920 GtkWidget *dialog;
1921 GdkScreen *screen;
1923 screen = NULL;
1924 if (parent_window != NULL) {
1925 screen = gtk_widget_get_screen (GTK_WIDGET (parent_window));
1928 /* Do we need to be modal ? */
1929 dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
1930 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1931 _("Do you want to empty the trash before you unmount?"));
1932 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1933 _("In order to regain the "
1934 "free space on this volume "
1935 "the trash must be emptied. "
1936 "All trashed items on the volume "
1937 "will be permanently lost."));
1938 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1939 _("Don't Empty Trash"), GTK_RESPONSE_REJECT,
1940 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1941 _("Empty Trash"), GTK_RESPONSE_ACCEPT, NULL);
1942 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
1943 gtk_window_set_title (GTK_WINDOW (dialog), ""); /* as per HIG */
1944 gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), TRUE);
1945 if (screen) {
1946 gtk_window_set_screen (GTK_WINDOW (dialog), screen);
1948 atk_object_set_role (gtk_widget_get_accessible (dialog), ATK_ROLE_ALERT);
1949 gtk_window_set_wmclass (GTK_WINDOW (dialog), "empty_trash",
1950 "Nautilus");
1952 /* Make transient for the window group */
1953 gtk_widget_realize (dialog);
1954 if (screen != NULL) {
1955 gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
1956 gdk_screen_get_root_window (screen));
1959 result = gtk_dialog_run (GTK_DIALOG (dialog));
1960 gtk_widget_destroy (dialog);
1961 return result;
1964 void
1965 nautilus_file_operations_unmount_mount (GtkWindow *parent_window,
1966 GMount *mount,
1967 gboolean eject,
1968 gboolean check_trash)
1970 UnmountData *data;
1971 int response;
1973 data = g_new0 (UnmountData, 1);
1974 if (parent_window) {
1975 data->parent_window = parent_window;
1976 eel_add_weak_pointer (&data->parent_window);
1979 data->eject = eject;
1980 data->mount = g_object_ref (mount);
1982 if (check_trash && has_trash_files (mount)) {
1983 response = prompt_empty_trash (parent_window);
1985 if (response == GTK_RESPONSE_ACCEPT) {
1986 EmptyTrashJob *job;
1988 job = op_job_new (EmptyTrashJob, parent_window);
1989 job->trash_dirs = get_trash_dirs_for_mount (mount);
1990 job->done_callback = (NautilusOpCallback)do_unmount;
1991 job->done_callback_data = data;
1992 g_io_scheduler_push_job (empty_trash_job,
1993 job,
1994 NULL,
1996 NULL);
1997 return;
1998 } else if (response == GTK_RESPONSE_CANCEL) {
1999 eel_remove_weak_pointer (&data->parent_window);
2000 g_object_unref (data->mount);
2001 g_free (data);
2002 return;
2006 do_unmount (data);
2009 static void
2010 volume_mount_cb (GObject *source_object,
2011 GAsyncResult *res,
2012 gpointer user_data)
2014 GMountOperation *mount_op = user_data;
2015 GError *error;
2016 char *primary;
2017 char *name;
2019 error = NULL;
2020 if (!g_volume_mount_finish (G_VOLUME (source_object), res, &error)) {
2021 if (error->code != G_IO_ERROR_FAILED_HANDLED) {
2022 name = g_volume_get_name (G_VOLUME (source_object));
2023 primary = g_strdup_printf (_("Unable to mount %s"), name);
2024 g_free (name);
2025 eel_show_error_dialog (primary,
2026 error->message,
2027 NULL);
2028 g_free (primary);
2030 g_error_free (error);
2033 g_object_unref (mount_op);
2037 void
2038 nautilus_file_operations_mount_volume (GtkWindow *parent_window,
2039 GVolume *volume)
2041 GMountOperation *mount_op;
2043 mount_op = eel_mount_operation_new (parent_window);
2044 g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, mount_op);
2048 static void
2049 report_count_progress (CommonJob *job,
2050 SourceInfo *source_info)
2052 char *s;
2054 switch (source_info->op) {
2055 default:
2056 case OP_KIND_COPY:
2057 s = f (ngettext("Preparing to copy %'d file (%S)",
2058 "Preparing to copy %'d files (%S)",
2059 source_info->num_files),
2060 source_info->num_files, source_info->num_bytes);
2061 break;
2062 case OP_KIND_MOVE:
2063 s = f (ngettext("Preparing to move %'d file (%S)",
2064 "Preparing to move %'d files (%S)",
2065 source_info->num_files),
2066 source_info->num_files, source_info->num_bytes);
2067 break;
2068 case OP_KIND_DELETE:
2069 s = f (ngettext("Preparing to delete %'d file (%S)",
2070 "Preparing to delete %'d files (%S)",
2071 source_info->num_files),
2072 source_info->num_files, source_info->num_bytes);
2073 break;
2074 case OP_KIND_TRASH:
2075 s = f (ngettext("Preparing to trash %'d file",
2076 "Preparing to trash %'d files",
2077 source_info->num_files),
2078 source_info->num_files);
2079 break;
2082 nautilus_progress_info_take_details (job->progress, s);
2083 nautilus_progress_info_pulse_progress (job->progress);
2086 static void
2087 count_file (GFileInfo *info,
2088 CommonJob *job,
2089 SourceInfo *source_info)
2091 source_info->num_files += 1;
2092 source_info->num_bytes += g_file_info_get_size (info);
2094 if (source_info->num_files_since_progress++ > 100) {
2095 report_count_progress (job, source_info);
2096 source_info->num_files_since_progress = 0;
2100 static char *
2101 get_scan_primary (OpKind kind)
2103 switch (kind) {
2104 default:
2105 case OP_KIND_COPY:
2106 return f (_("Error while copying."));
2107 case OP_KIND_MOVE:
2108 return f (_("Error while moving."));
2109 case OP_KIND_DELETE:
2110 return f (_("Error while deleting."));
2111 case OP_KIND_TRASH:
2112 return f (_("Error while moving files to trash."));
2116 static void
2117 scan_dir (GFile *dir,
2118 SourceInfo *source_info,
2119 CommonJob *job,
2120 GQueue *dirs)
2122 GFileInfo *info;
2123 GError *error;
2124 GFile *subdir;
2125 GFileEnumerator *enumerator;
2126 char *primary, *secondary, *details;
2127 int response;
2128 SourceInfo saved_info;
2130 saved_info = *source_info;
2132 retry:
2133 error = NULL;
2134 enumerator = g_file_enumerate_children (dir,
2135 G_FILE_ATTRIBUTE_STANDARD_NAME","
2136 G_FILE_ATTRIBUTE_STANDARD_TYPE","
2137 G_FILE_ATTRIBUTE_STANDARD_SIZE,
2138 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
2139 job->cancellable,
2140 &error);
2141 if (enumerator) {
2142 error = NULL;
2143 while ((info = g_file_enumerator_next_file (enumerator, job->cancellable, &error)) != NULL) {
2144 count_file (info, job, source_info);
2146 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
2147 subdir = g_file_get_child (dir,
2148 g_file_info_get_name (info));
2150 /* Push to head, since we want depth-first */
2151 g_queue_push_head (dirs, subdir);
2154 g_object_unref (info);
2156 g_file_enumerator_close (enumerator, job->cancellable, NULL);
2157 g_object_unref (enumerator);
2159 if (error && IS_IO_ERROR (error, CANCELLED)) {
2160 g_error_free (error);
2161 } else if (error) {
2162 primary = get_scan_primary (source_info->op);
2163 details = NULL;
2165 if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
2166 secondary = f (_("Files in the folder \"%B\" cannot be handled because you do "
2167 "not have permissions to see them."), dir);
2168 } else {
2169 secondary = f (_("There was an error getting information about the files in the folder \"%B\"."), dir);
2170 details = error->message;
2173 response = run_warning (job,
2174 primary,
2175 secondary,
2176 details,
2177 GTK_STOCK_CANCEL, RETRY, SKIP,
2178 NULL);
2180 g_error_free (error);
2182 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
2183 abort_job (job);
2184 } else if (response == 1) {
2185 *source_info = saved_info;
2186 goto retry;
2187 } else if (response == 2) {
2188 skip_readdir_error (job, dir);
2189 } else {
2190 g_assert_not_reached ();
2194 } else if (job->skip_all_error) {
2195 g_error_free (error);
2196 skip_file (job, dir);
2197 } else if (IS_IO_ERROR (error, CANCELLED)) {
2198 g_error_free (error);
2199 } else {
2200 primary = get_scan_primary (source_info->op);
2201 details = NULL;
2203 if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
2204 secondary = f (_("The folder \"%B\" cannot be handled because you do not have "
2205 "permissions to read it."), dir);
2206 } else {
2207 secondary = f (_("There was an error reading the folder \"%B\"."), dir);
2208 details = error->message;
2211 response = run_warning (job,
2212 primary,
2213 secondary,
2214 details,
2215 GTK_STOCK_CANCEL, SKIP_ALL, SKIP, RETRY,
2216 NULL);
2218 g_error_free (error);
2220 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
2221 abort_job (job);
2222 } else if (response == 1 || response == 2) {
2223 if (response == 1) {
2224 job->skip_all_error = TRUE;
2226 skip_file (job, dir);
2227 } else if (response == 3) {
2228 goto retry;
2229 } else {
2230 g_assert_not_reached ();
2235 static void
2236 scan_file (GFile *file,
2237 SourceInfo *source_info,
2238 CommonJob *job)
2240 GFileInfo *info;
2241 GError *error;
2242 GQueue *dirs;
2243 GFile *dir;
2244 char *primary;
2245 char *secondary;
2246 char *details;
2247 int response;
2249 dirs = g_queue_new ();
2251 retry:
2252 error = NULL;
2253 info = g_file_query_info (file,
2254 G_FILE_ATTRIBUTE_STANDARD_TYPE","
2255 G_FILE_ATTRIBUTE_STANDARD_SIZE,
2256 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
2257 job->cancellable,
2258 &error);
2260 if (info) {
2261 count_file (info, job, source_info);
2263 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
2264 g_queue_push_head (dirs, g_object_ref (file));
2267 g_object_unref (info);
2268 } else if (job->skip_all_error) {
2269 g_error_free (error);
2270 skip_file (job, file);
2271 } else if (IS_IO_ERROR (error, CANCELLED)) {
2272 g_error_free (error);
2273 } else {
2274 primary = get_scan_primary (source_info->op);
2275 details = NULL;
2277 if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
2278 secondary = f (_("The file \"%B\" cannot be handled because you do not have "
2279 "permissions to read it."), file);
2280 } else {
2281 secondary = f (_("There was an error getting information about \"%B\"."), file);
2282 details = error->message;
2285 response = run_warning (job,
2286 primary,
2287 secondary,
2288 details,
2289 GTK_STOCK_CANCEL, SKIP_ALL, SKIP, RETRY,
2290 NULL);
2292 g_error_free (error);
2294 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
2295 abort_job (job);
2296 } else if (response == 1 || response == 2) {
2297 if (response == 1) {
2298 job->skip_all_error = TRUE;
2300 skip_file (job, file);
2301 } else if (response == 3) {
2302 goto retry;
2303 } else {
2304 g_assert_not_reached ();
2308 while (!job_aborted (job) &&
2309 (dir = g_queue_pop_head (dirs)) != NULL) {
2310 scan_dir (dir, source_info, job, dirs);
2311 g_object_unref (dir);
2314 /* Free all from queue if we exited early */
2315 g_queue_foreach (dirs, (GFunc)g_object_unref, NULL);
2316 g_queue_free (dirs);
2319 static void
2320 scan_sources (GList *files,
2321 SourceInfo *source_info,
2322 CommonJob *job,
2323 OpKind kind)
2325 GList *l;
2326 GFile *file;
2328 memset (source_info, 0, sizeof (SourceInfo));
2329 source_info->op = kind;
2331 report_count_progress (job, source_info);
2333 for (l = files; l != NULL && !job_aborted (job); l = l->next) {
2334 file = l->data;
2336 scan_file (file,
2337 source_info,
2338 job);
2341 /* Make sure we report the final count */
2342 report_count_progress (job, source_info);
2345 static void
2346 verify_destination (CommonJob *job,
2347 GFile *dest,
2348 char **dest_fs_id,
2349 goffset required_size)
2351 GFileInfo *info, *fsinfo;
2352 GError *error;
2353 guint64 free_size;
2354 char *primary, *secondary, *details;
2355 int response;
2356 GFileType file_type;
2358 if (dest_fs_id) {
2359 *dest_fs_id = NULL;
2362 retry:
2364 error = NULL;
2365 info = g_file_query_info (dest,
2366 G_FILE_ATTRIBUTE_STANDARD_TYPE","
2367 G_FILE_ATTRIBUTE_ID_FILESYSTEM,
2369 job->cancellable,
2370 &error);
2372 if (info == NULL) {
2373 if (IS_IO_ERROR (error, CANCELLED)) {
2374 g_error_free (error);
2375 return;
2378 primary = f (_("Error while copying to \"%B\"."), dest);
2379 details = NULL;
2381 if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
2382 secondary = f (_("You don't have permissions to access the destination folder."));
2383 } else {
2384 secondary = f (_("There was an error getting information about the destination."));
2385 details = error->message;
2388 response = run_error (job,
2389 primary,
2390 secondary,
2391 details,
2392 GTK_STOCK_CANCEL, RETRY,
2393 NULL);
2395 g_error_free (error);
2397 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
2398 abort_job (job);
2399 } else if (response == 1) {
2400 goto retry;
2401 } else {
2402 g_assert_not_reached ();
2405 return;
2408 file_type = g_file_info_get_file_type (info);
2410 if (dest_fs_id) {
2411 *dest_fs_id =
2412 g_strdup (g_file_info_get_attribute_string (info,
2413 G_FILE_ATTRIBUTE_ID_FILESYSTEM));
2416 g_object_unref (info);
2418 if (file_type != G_FILE_TYPE_DIRECTORY) {
2419 primary = f (_("Error while copying to \"%B\"."), dest);
2420 secondary = f (_("The destination is not a folder."));
2422 response = run_error (job,
2423 primary,
2424 secondary,
2425 NULL,
2426 GTK_STOCK_CANCEL,
2427 NULL);
2429 abort_job (job);
2430 return;
2433 fsinfo = g_file_query_filesystem_info (dest,
2434 G_FILE_ATTRIBUTE_FILESYSTEM_FREE","
2435 G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
2436 job->cancellable,
2437 NULL);
2438 if (fsinfo == NULL) {
2439 /* All sorts of things can go wrong getting the fs info (like not supported)
2440 * only check these things if the fs returns them
2442 return;
2445 if (required_size > 0 &&
2446 g_file_info_has_attribute (fsinfo, G_FILE_ATTRIBUTE_FILESYSTEM_FREE)) {
2447 free_size = g_file_info_get_attribute_uint64 (fsinfo,
2448 G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
2450 if (free_size < required_size) {
2451 primary = f (_("Error while copying to \"%B\"."), dest);
2452 secondary = f(_("There is not enough space on the destination. Try to remove files to make space."));
2454 details = f (_("There is %S available, but %S is required."), free_size, required_size);
2456 response = run_warning (job,
2457 primary,
2458 secondary,
2459 details,
2460 GTK_STOCK_CANCEL, RETRY,
2461 NULL);
2463 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
2464 abort_job (job);
2465 } else if (response == 1) {
2466 goto retry;
2467 } else {
2468 g_assert_not_reached ();
2473 if (!job_aborted (job) &&
2474 g_file_info_get_attribute_boolean (fsinfo,
2475 G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) {
2476 primary = f (_("Error while copying to \"%B\"."), dest);
2477 secondary = f (_("The destination is read-only."));
2479 response = run_error (job,
2480 primary,
2481 secondary,
2482 NULL,
2483 GTK_STOCK_CANCEL,
2484 NULL);
2486 g_error_free (error);
2488 abort_job (job);
2491 g_object_unref (fsinfo);
2494 static void
2495 report_copy_progress (CopyMoveJob *copy_job,
2496 SourceInfo *source_info,
2497 TransferInfo *transfer_info)
2499 int files_left;
2500 goffset total_size;
2501 double elapsed, transfer_rate;
2502 int remaining_time;
2503 guint64 now;
2504 CommonJob *job;
2505 gboolean is_move;
2507 job = (CommonJob *)copy_job;
2509 is_move = copy_job->is_move;
2511 now = g_thread_gettime ();
2513 if (transfer_info->last_report_time != 0 &&
2514 ABS (transfer_info->last_report_time - now) < 100 * NSEC_PER_MSEC) {
2515 return;
2517 transfer_info->last_report_time = now;
2519 files_left = source_info->num_files - transfer_info->num_files;
2521 /* Races and whatnot could cause this to be negative... */
2522 if (files_left < 0) {
2523 files_left = 1;
2526 if (files_left != transfer_info->last_reported_files_left ||
2527 transfer_info->last_reported_files_left == 0) {
2528 /* Avoid changing this unless files_left changed since last time */
2529 transfer_info->last_reported_files_left = files_left;
2531 if (source_info->num_files == 1) {
2532 if (copy_job->destination != NULL) {
2533 nautilus_progress_info_take_status (job->progress,
2534 f (is_move ?
2535 _("Moving \"%B\" to \"%B\""):
2536 _("Copying \"%B\" to \"%B\""),
2537 (GFile *)copy_job->files->data,
2538 copy_job->destination));
2539 } else {
2540 nautilus_progress_info_take_status (job->progress,
2541 f (_("Duplicating \"%B\""),
2542 (GFile *)copy_job->files->data));
2544 } else if (copy_job->files != NULL &&
2545 copy_job->files->next == NULL) {
2546 if (copy_job->destination != NULL) {
2547 nautilus_progress_info_take_status (job->progress,
2548 f (is_move?
2549 ngettext ("Moving %'d file (in \"%B\") to \"%B\"",
2550 "Moving %'d files (in \"%B\") to \"%B\"",
2551 files_left)
2553 ngettext ("Copying %'d file (in \"%B\") to \"%B\"",
2554 "Copying %'d files (in \"%B\") to \"%B\"",
2555 files_left),
2556 files_left,
2557 (GFile *)copy_job->files->data,
2558 copy_job->destination));
2559 } else {
2560 nautilus_progress_info_take_status (job->progress,
2561 f (ngettext ("Duplicating %'d file (in \"%B\")",
2562 "Duplicating %'d files (in \"%B\")",
2563 files_left),
2564 files_left,
2565 (GFile *)copy_job->files->data));
2567 } else {
2568 if (copy_job->destination != NULL) {
2569 nautilus_progress_info_take_status (job->progress,
2570 f (is_move?
2571 ngettext ("Moving %'d file to \"%B\"",
2572 "Moving %'d files to \"%B\"",
2573 files_left)
2575 ngettext ("Copying %'d file to \"%B\"",
2576 "Copying %'d files to \"%B\"",
2577 files_left),
2578 files_left, copy_job->destination));
2579 } else {
2580 nautilus_progress_info_take_status (job->progress,
2581 f (ngettext ("Duplicating %'d file",
2582 "Duplicating %'d files",
2583 files_left),
2584 files_left));
2589 total_size = MAX (source_info->num_bytes, transfer_info->num_bytes);
2591 elapsed = g_timer_elapsed (job->time, NULL);
2592 transfer_rate = 0;
2593 if (elapsed > 0) {
2594 transfer_rate = transfer_info->num_bytes / elapsed;
2597 if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE &&
2598 transfer_rate > 0) {
2599 char *s;
2600 /* To translators: %S will expand to a size like "2 bytes" or "3 MB", so something like "4 kb of 4 MB" */
2601 s = f (_("%S of %S"), transfer_info->num_bytes, total_size);
2602 nautilus_progress_info_take_details (job->progress, s);
2603 } else {
2604 char *s;
2605 remaining_time = (total_size - transfer_info->num_bytes) / transfer_rate;
2607 /* To translators: %S will expand to a size like "2 bytes" or "3 MB", %T to a time duration like
2608 * "2 minutes". So the whole thing will be something like "2 kb of 4 MB -- 2 hours left (4kb/sec)"
2610 s = f (_("%S of %S \xE2\x80\x94 %T left (%S/sec)"),
2611 transfer_info->num_bytes, total_size,
2612 remaining_time,
2613 (goffset)transfer_rate);
2614 nautilus_progress_info_take_details (job->progress, s);
2617 nautilus_progress_info_set_progress (job->progress, transfer_info->num_bytes, total_size);
2620 static GFile *
2621 get_unique_target_file (GFile *src,
2622 GFile *dest_dir,
2623 gboolean same_fs,
2624 int count)
2626 const char *editname, *end;
2627 char *basename, *new_name;
2628 GFileInfo *info;
2629 GFile *dest;
2631 dest = NULL;
2632 info = g_file_query_info (src,
2633 G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
2634 0, NULL, NULL);
2635 if (info != NULL) {
2636 editname = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME);
2638 if (editname != NULL) {
2639 new_name = get_duplicate_name (editname, count);
2640 dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
2641 g_free (new_name);
2644 g_object_unref (info);
2647 if (dest == NULL) {
2648 basename = g_file_get_basename (src);
2650 if (g_utf8_validate (basename, -1, NULL)) {
2651 new_name = get_duplicate_name (basename, count);
2652 dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
2653 g_free (new_name);
2656 if (dest == NULL) {
2657 end = strrchr (basename, '.');
2658 if (end != NULL) {
2659 count += atoi (end + 1);
2661 new_name = g_strdup_printf ("%s.%d", basename, count);
2662 dest = g_file_get_child (dest_dir, new_name);
2663 g_free (new_name);
2666 g_free (basename);
2669 return dest;
2672 static GFile *
2673 get_target_file_for_link (GFile *src,
2674 GFile *dest_dir,
2675 int count)
2677 const char *editname;
2678 char *basename, *new_name;
2679 GFileInfo *info;
2680 GFile *dest;
2682 dest = NULL;
2683 info = g_file_query_info (src,
2684 G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
2685 0, NULL, NULL);
2686 if (info != NULL) {
2687 editname = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME);
2689 if (editname != NULL) {
2690 new_name = get_link_name (editname, count);
2691 dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
2692 g_free (new_name);
2695 g_object_unref (info);
2698 if (dest == NULL) {
2699 basename = g_file_get_basename (src);
2701 if (g_utf8_validate (basename, -1, NULL)) {
2702 new_name = get_link_name (basename, count);
2703 dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
2704 g_free (new_name);
2707 if (dest == NULL) {
2708 if (count == 1) {
2709 new_name = g_strdup_printf ("%s.lnk", basename);
2710 } else {
2711 new_name = g_strdup_printf ("%s.lnk%d", basename, count);
2713 dest = g_file_get_child (dest_dir, new_name);
2714 g_free (new_name);
2717 g_free (basename);
2720 return dest;
2723 static GFile *
2724 get_target_file (GFile *src,
2725 GFile *dest_dir,
2726 gboolean same_fs)
2728 char *basename;
2729 GFile *dest;
2730 GFileInfo *info;
2731 const char *copyname;
2733 dest = NULL;
2734 if (!same_fs) {
2735 info = g_file_query_info (src,
2736 G_FILE_ATTRIBUTE_STANDARD_COPY_NAME,
2737 0, NULL, NULL);
2739 if (info) {
2740 copyname = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME);
2742 if (copyname) {
2743 dest = g_file_get_child_for_display_name (dest_dir, copyname, NULL);
2746 g_object_unref (info);
2750 if (dest == NULL) {
2751 basename = g_file_get_basename (src);
2752 dest = g_file_get_child (dest_dir, basename);
2753 g_free (basename);
2756 return dest;
2759 static gboolean
2760 has_fs_id (GFile *file, const char *fs_id)
2762 const char *id;
2763 GFileInfo *info;
2764 gboolean res;
2766 res = FALSE;
2767 info = g_file_query_info (file,
2768 G_FILE_ATTRIBUTE_ID_FILESYSTEM,
2769 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
2770 NULL, NULL);
2772 if (info) {
2773 id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM);
2775 if (id && strcmp (id, fs_id) == 0) {
2776 res = TRUE;
2779 g_object_unref (info);
2782 return res;
2785 static gboolean
2786 is_dir (GFile *file)
2788 GFileInfo *info;
2789 gboolean res;
2791 res = FALSE;
2792 info = g_file_query_info (file,
2793 G_FILE_ATTRIBUTE_STANDARD_TYPE,
2794 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
2795 NULL, NULL);
2796 if (info) {
2797 res = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
2798 g_object_unref (info);
2801 return res;
2804 static void copy_move_file (CopyMoveJob *job,
2805 GFile *src,
2806 GFile *dest_dir,
2807 gboolean same_fs,
2808 gboolean unique_names,
2809 SourceInfo *source_info,
2810 TransferInfo *transfer_info,
2811 GHashTable *debuting_files,
2812 GdkPoint *point,
2813 gboolean overwrite,
2814 gboolean *skipped_file);
2816 static gboolean
2817 create_dest_dir (CommonJob *job,
2818 GFile *src,
2819 GFile *dest)
2821 GError *error;
2822 char *primary, *secondary, *details;
2823 int response;
2825 retry:
2826 /* First create the directory, then copy stuff to it before
2827 copying the attributes, because we need to be sure we can write to it */
2829 error = NULL;
2830 if (!g_file_make_directory (dest, job->cancellable, &error)) {
2831 if (IS_IO_ERROR (error, CANCELLED)) {
2832 g_error_free (error);
2833 return FALSE;
2835 primary = f (_("Error while copying."));
2836 details = NULL;
2838 if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
2839 secondary = f (_("The folder \"%B\" cannot be copied because you do not have "
2840 "permissions to create it in the destination."), src);
2841 } else {
2842 secondary = f (_("There was an error creating the folder \"%B\"."), src);
2843 details = error->message;
2846 response = run_warning (job,
2847 primary,
2848 secondary,
2849 details,
2850 GTK_STOCK_CANCEL, SKIP, RETRY,
2851 NULL);
2853 g_error_free (error);
2855 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
2856 abort_job (job);
2857 } else if (response == 1) {
2858 /* Skip: Do Nothing */
2859 } else if (response == 2) {
2860 goto retry;
2861 } else {
2862 g_assert_not_reached ();
2864 return FALSE;
2866 nautilus_file_changes_queue_file_added (dest);
2867 return TRUE;
2870 static void
2871 copy_move_directory (CopyMoveJob *copy_job,
2872 GFile *src,
2873 GFile *dest,
2874 gboolean same_fs,
2875 gboolean create_dest,
2876 SourceInfo *source_info,
2877 TransferInfo *transfer_info,
2878 GHashTable *debuting_files,
2879 gboolean *skipped_file)
2881 GFileInfo *info;
2882 GError *error;
2883 GFile *src_file;
2884 GFileEnumerator *enumerator;
2885 char *primary, *secondary, *details;
2886 int response;
2887 gboolean skip_error;
2888 gboolean local_skipped_file;
2889 CommonJob *job;
2891 job = (CommonJob *)copy_job;
2893 if (create_dest) {
2894 if (!create_dest_dir (job, src, dest)) {
2895 *skipped_file = TRUE;
2896 return;
2898 if (debuting_files) {
2899 g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
2904 local_skipped_file = FALSE;
2906 skip_error = should_skip_readdir_error (job, src);
2907 retry:
2908 error = NULL;
2909 enumerator = g_file_enumerate_children (src,
2910 G_FILE_ATTRIBUTE_STANDARD_NAME,
2911 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
2912 job->cancellable,
2913 &error);
2914 if (enumerator) {
2915 error = NULL;
2917 while (!job_aborted (job) &&
2918 (info = g_file_enumerator_next_file (enumerator, job->cancellable, skip_error?NULL:&error)) != NULL) {
2919 src_file = g_file_get_child (src,
2920 g_file_info_get_name (info));
2921 copy_move_file (copy_job, src_file, dest, same_fs, FALSE, source_info, transfer_info, NULL, NULL, FALSE, &local_skipped_file);
2922 g_object_unref (src_file);
2923 g_object_unref (info);
2925 g_file_enumerator_close (enumerator, job->cancellable, NULL);
2926 g_object_unref (enumerator);
2928 if (error && IS_IO_ERROR (error, CANCELLED)) {
2929 g_error_free (error);
2930 } else if (error) {
2931 if (copy_job->is_move) {
2932 primary = f (_("Error while moving."));
2933 } else {
2934 primary = f (_("Error while copying."));
2936 details = NULL;
2938 if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
2939 secondary = f (_("Files in the folder \"%B\" cannot be copied because you do "
2940 "not have permissions to see them."), src);
2941 } else {
2942 secondary = f (_("There was an error getting information about the files in the folder \"%B\"."), src);
2943 details = error->message;
2946 response = run_warning (job,
2947 primary,
2948 secondary,
2949 details,
2950 GTK_STOCK_CANCEL, _("_Skip files"),
2951 NULL);
2953 g_error_free (error);
2955 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
2956 abort_job (job);
2957 } else if (response == 1) {
2958 /* Skip: Do Nothing */
2959 local_skipped_file = TRUE;
2960 } else {
2961 g_assert_not_reached ();
2965 /* Count the copied directory as a file */
2966 transfer_info->num_files ++;
2967 report_copy_progress (copy_job, source_info, transfer_info);
2969 if (debuting_files) {
2970 g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (create_dest));
2972 } else if (IS_IO_ERROR (error, CANCELLED)) {
2973 g_error_free (error);
2974 } else {
2975 if (copy_job->is_move) {
2976 primary = f (_("Error while moving."));
2977 } else {
2978 primary = f (_("Error while copying."));
2980 details = NULL;
2982 if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
2983 secondary = f (_("The folder \"%B\" cannot be copied because you do not have "
2984 "permissions to read it."), src);
2985 } else {
2986 secondary = f (_("There was an error reading the folder \"%B\"."), src);
2987 details = error->message;
2990 response = run_warning (job,
2991 primary,
2992 secondary,
2993 details,
2994 GTK_STOCK_CANCEL, SKIP, RETRY,
2995 NULL);
2997 g_error_free (error);
2999 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
3000 abort_job (job);
3001 } else if (response == 1) {
3002 /* Skip: Do Nothing */
3003 local_skipped_file = TRUE;
3004 } else if (response == 2) {
3005 goto retry;
3006 } else {
3007 g_assert_not_reached ();
3011 if (create_dest) {
3012 /* Ignore errors here. Failure to copy metadata is not a hard error */
3013 g_file_copy_attributes (src, dest,
3014 G_FILE_COPY_NOFOLLOW_SYMLINKS,
3015 job->cancellable, NULL);
3018 if (!job_aborted (job) && copy_job->is_move &&
3019 /* Don't delete source if there was a skipped file */
3020 !local_skipped_file) {
3021 if (!g_file_delete (src, job->cancellable, &error)) {
3022 if (job->skip_all_error) {
3023 goto skip;
3025 primary = f (_("Error while moving \"%B\"."), src);
3026 secondary = f (_("Couldn't remove the source folder."));
3027 details = error->message;
3029 response = run_warning (job,
3030 primary,
3031 secondary,
3032 details,
3033 GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
3034 NULL);
3036 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
3037 abort_job (job);
3038 } else if (response == 1) { /* skip all */
3039 job->skip_all_error = TRUE;
3040 local_skipped_file = TRUE;
3041 } else if (response == 2) { /* skip */
3042 local_skipped_file = TRUE;
3043 } else {
3044 g_assert_not_reached ();
3047 skip:
3048 g_error_free (error);
3052 if (local_skipped_file) {
3053 *skipped_file = TRUE;
3057 static gboolean
3058 remove_target_recursively (CommonJob *job,
3059 GFile *src,
3060 GFile *toplevel_dest,
3061 GFile *file)
3063 GFileEnumerator *enumerator;
3064 GError *error;
3065 GFile *child;
3066 gboolean stop;
3067 char *primary, *secondary, *details;
3068 int response;
3069 GFileInfo *info;
3071 stop = FALSE;
3073 error = NULL;
3074 enumerator = g_file_enumerate_children (file,
3075 G_FILE_ATTRIBUTE_STANDARD_NAME,
3076 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
3077 job->cancellable,
3078 &error);
3079 if (enumerator) {
3080 error = NULL;
3082 while (!job_aborted (job) &&
3083 (info = g_file_enumerator_next_file (enumerator, job->cancellable, &error)) != NULL) {
3084 child = g_file_get_child (file,
3085 g_file_info_get_name (info));
3086 if (!remove_target_recursively (job, src, toplevel_dest, child)) {
3087 stop = TRUE;
3088 break;
3090 g_object_unref (child);
3091 g_object_unref (info);
3093 g_file_enumerator_close (enumerator, job->cancellable, NULL);
3094 g_object_unref (enumerator);
3096 } else if (IS_IO_ERROR (error, NOT_DIRECTORY)) {
3097 /* Not a dir, continue */
3098 g_error_free (error);
3100 } else if (IS_IO_ERROR (error, CANCELLED)) {
3101 g_error_free (error);
3102 } else {
3103 if (job->skip_all_error) {
3104 goto skip1;
3107 primary = f (_("Error while copying \"%B\"."), src);
3108 secondary = f (_("Couldn't remove files from the already existing folder %F."), file);
3109 details = error->message;
3111 response = run_warning (job,
3112 primary,
3113 secondary,
3114 details,
3115 GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
3116 NULL);
3118 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
3119 abort_job (job);
3120 } else if (response == 1) { /* skip all */
3121 job->skip_all_error = TRUE;
3122 } else if (response == 2) { /* skip */
3123 /* do nothing */
3124 } else {
3125 g_assert_not_reached ();
3127 skip1:
3128 g_error_free (error);
3130 stop = TRUE;
3133 if (stop) {
3134 return FALSE;
3137 error = NULL;
3139 if (!g_file_delete (file, job->cancellable, &error)) {
3140 if (job->skip_all_error ||
3141 IS_IO_ERROR (error, CANCELLED)) {
3142 goto skip2;
3144 primary = f (_("Error while copying \"%B\"."), src);
3145 secondary = f (_("Couldn't remove the already existing file %F."), file);
3146 details = error->message;
3148 response = run_warning (job,
3149 primary,
3150 secondary,
3151 details,
3152 GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
3153 NULL);
3155 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
3156 abort_job (job);
3157 } else if (response == 1) { /* skip all */
3158 job->skip_all_error = TRUE;
3159 } else if (response == 2) { /* skip */
3160 /* do nothing */
3161 } else {
3162 g_assert_not_reached ();
3165 skip2:
3166 g_error_free (error);
3168 return FALSE;
3170 nautilus_file_changes_queue_file_removed (file);
3171 nautilus_file_changes_queue_schedule_metadata_remove (file);
3173 return TRUE;
3177 typedef struct {
3178 CopyMoveJob *job;
3179 goffset last_size;
3180 SourceInfo *source_info;
3181 TransferInfo *transfer_info;
3182 } ProgressData;
3184 static void
3185 copy_file_progress_callback (goffset current_num_bytes,
3186 goffset total_num_bytes,
3187 gpointer user_data)
3189 ProgressData *pdata;
3190 goffset new_size;
3192 pdata = user_data;
3194 new_size = current_num_bytes - pdata->last_size;
3196 if (new_size > 0) {
3197 pdata->transfer_info->num_bytes += new_size;
3198 pdata->last_size = current_num_bytes;
3199 report_copy_progress (pdata->job,
3200 pdata->source_info,
3201 pdata->transfer_info);
3205 /* Debuting files is non-NULL only for toplevel items */
3206 static void
3207 copy_move_file (CopyMoveJob *copy_job,
3208 GFile *src,
3209 GFile *dest_dir,
3210 gboolean same_fs,
3211 gboolean unique_names,
3212 SourceInfo *source_info,
3213 TransferInfo *transfer_info,
3214 GHashTable *debuting_files,
3215 GdkPoint *position,
3216 gboolean overwrite,
3217 gboolean *skipped_file)
3219 GFile *dest;
3220 GError *error;
3221 GFileCopyFlags flags;
3222 char *primary, *secondary, *details;
3223 int response;
3224 ProgressData pdata;
3225 gboolean would_recurse;
3226 CommonJob *job;
3227 gboolean res;
3228 int unique_name_nr;
3230 job = (CommonJob *)copy_job;
3232 if (should_skip_file (job, src)) {
3233 *skipped_file = TRUE;
3234 return;
3237 unique_name_nr = 1;
3239 if (unique_names) {
3240 dest = get_unique_target_file (src, dest_dir, same_fs, unique_name_nr++);
3241 } else {
3242 dest = get_target_file (src, dest_dir, same_fs);
3245 retry:
3247 error = NULL;
3248 flags = G_FILE_COPY_NOFOLLOW_SYMLINKS;
3249 if (overwrite) {
3250 flags |= G_FILE_COPY_OVERWRITE;
3252 pdata.job = copy_job;
3253 pdata.last_size = 0;
3254 pdata.source_info = source_info;
3255 pdata.transfer_info = transfer_info;
3257 if (copy_job->is_move) {
3258 res = g_file_move (src, dest,
3259 flags,
3260 job->cancellable,
3261 copy_file_progress_callback,
3262 &pdata,
3263 &error);
3264 } else {
3265 res = g_file_copy (src, dest,
3266 flags,
3267 job->cancellable,
3268 copy_file_progress_callback,
3269 &pdata,
3270 &error);
3273 if (res) {
3274 transfer_info->num_files ++;
3275 report_copy_progress (copy_job, source_info, transfer_info);
3277 if (debuting_files) {
3278 nautilus_file_changes_queue_schedule_metadata_copy (src, dest);
3279 if (position) {
3280 nautilus_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
3281 } else {
3282 nautilus_file_changes_queue_schedule_position_remove (dest);
3285 g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
3287 nautilus_file_changes_queue_file_added (dest);
3288 g_object_unref (dest);
3289 return;
3292 /* Conflict */
3293 if (!overwrite &&
3294 IS_IO_ERROR (error, EXISTS)) {
3295 gboolean is_merge;
3297 if (unique_names) {
3298 g_object_unref (dest);
3299 dest = get_unique_target_file (src, dest_dir, same_fs, unique_name_nr++);
3300 g_error_free (error);
3301 goto retry;
3304 is_merge = FALSE;
3305 if (is_dir (dest)) {
3306 if (is_dir (src)) {
3307 is_merge = TRUE;
3308 primary = f (_("A folder named \"%B\" already exists. Do you want to merge the source folder?"),
3309 dest);
3310 secondary = f (_("The source folder already exists in \"%B\". "
3311 "Merging will ask for confirmation before replacing any files in the folder that conflict with the files being copied."),
3312 dest_dir);
3314 } else {
3315 primary = f (_("A folder named \"%B\" already exists. Do you want to replace it?"),
3316 dest);
3317 secondary = f (_("The folder already exists in \"%F\". "
3318 "Replacing it will remove all files in the folder."),
3319 dest_dir);
3321 } else {
3322 primary = f (_("A file named \"%B\" already exists. Do you want to replace it?"),
3323 dest);
3324 secondary = f (_("The file already exists in \"%F\". "
3325 "Replacing it will overwrite its content."),
3326 dest_dir);
3329 if ((is_merge && job->merge_all) ||
3330 (!is_merge && job->replace_all)) {
3331 g_free (primary);
3332 g_free (secondary);
3333 g_error_free (error);
3335 overwrite = TRUE;
3336 goto retry;
3339 if (job->skip_all_conflict) {
3340 g_free (primary);
3341 g_free (secondary);
3342 g_error_free (error);
3344 goto out;
3347 response = run_warning (job,
3348 primary,
3349 secondary,
3350 NULL,
3351 GTK_STOCK_CANCEL,
3352 SKIP_ALL,
3353 is_merge?MERGE_ALL:REPLACE_ALL,
3354 SKIP,
3355 is_merge?MERGE:REPLACE,
3356 NULL);
3358 g_error_free (error);
3360 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
3361 abort_job (job);
3362 } else if (response == 1 || response == 3) { /* skip all / skip */
3363 if (response == 1) {
3364 job->skip_all_conflict = TRUE;
3366 } else if (response == 2 || response == 4) { /* merge/replace all / merge/replace*/
3367 if (response == 2) {
3368 if (is_merge) {
3369 job->merge_all = TRUE;
3370 } else {
3371 job->replace_all = TRUE;
3374 overwrite = TRUE;
3375 goto retry;
3376 } else {
3377 g_assert_not_reached ();
3381 else if (overwrite &&
3382 IS_IO_ERROR (error, IS_DIRECTORY)) {
3384 g_error_free (error);
3386 if (remove_target_recursively (job, src, dest, dest)) {
3387 goto retry;
3391 /* Needs to recurse */
3392 else if (IS_IO_ERROR (error, WOULD_RECURSE) ||
3393 IS_IO_ERROR (error, WOULD_MERGE)) {
3394 would_recurse = error->code == G_IO_ERROR_WOULD_RECURSE;
3395 g_error_free (error);
3397 if (overwrite && would_recurse) {
3398 error = NULL;
3400 /* Copying a dir onto file, first remove the file */
3401 if (!g_file_delete (dest, job->cancellable, &error) &&
3402 !IS_IO_ERROR (error, NOT_FOUND)) {
3403 if (job->skip_all_error) {
3404 g_error_free (error);
3405 goto out;
3407 if (copy_job->is_move) {
3408 primary = f (_("Error while moving \"%B\"."), src);
3409 } else {
3410 primary = f (_("Error while copying \"%B\"."), src);
3412 secondary = f (_("Couldn't remove the already existing file with the same name in %F."), dest_dir);
3413 details = error->message;
3415 response = run_warning (job,
3416 primary,
3417 secondary,
3418 details,
3419 GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
3420 NULL);
3422 g_error_free (error);
3424 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
3425 abort_job (job);
3426 } else if (response == 1) { /* skip all */
3427 job->skip_all_error = TRUE;
3428 } else if (response == 2) { /* skip */
3429 /* do nothing */
3430 } else {
3431 g_assert_not_reached ();
3433 goto out;
3436 if (error) {
3437 g_error_free (error);
3438 error = NULL;
3440 if (debuting_files) { /* Only remove metadata for toplevel items */
3441 nautilus_file_changes_queue_schedule_metadata_remove (dest);
3443 nautilus_file_changes_queue_file_removed (dest);
3446 copy_move_directory (copy_job, src, dest, same_fs,
3447 would_recurse,
3448 source_info, transfer_info,
3449 debuting_files, skipped_file);
3451 g_object_unref (dest);
3452 return;
3455 else if (IS_IO_ERROR (error, CANCELLED)) {
3456 g_error_free (error);
3459 /* Other error */
3460 else {
3461 if (job->skip_all_error) {
3462 g_error_free (error);
3463 goto out;
3465 primary = f (_("Error while copying \"%B\"."), src);
3466 secondary = f (_("There was an error copying the file into %F."), dest_dir);
3467 details = error->message;
3469 response = run_warning (job,
3470 primary,
3471 secondary,
3472 details,
3473 GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
3474 NULL);
3476 g_error_free (error);
3478 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
3479 abort_job (job);
3480 } else if (response == 1) { /* skip all */
3481 job->skip_all_error = TRUE;
3482 } else if (response == 2) { /* skip */
3483 /* do nothing */
3484 } else {
3485 g_assert_not_reached ();
3488 out:
3489 *skipped_file = TRUE; /* Or aborted, but same-same */
3490 g_object_unref (dest);
3493 static void
3494 copy_files (CopyMoveJob *job,
3495 const char *dest_fs_id,
3496 SourceInfo *source_info,
3497 TransferInfo *transfer_info)
3499 CommonJob *common;
3500 GList *l;
3501 GFile *src;
3502 gboolean same_fs;
3503 int i;
3504 GdkPoint *point;
3505 gboolean skipped_file;
3506 gboolean unique_names;
3507 GFile *dest;
3509 common = &job->common;
3511 report_copy_progress (job, source_info, transfer_info);
3513 unique_names = (job->destination == NULL);
3514 i = 0;
3515 for (l = job->files;
3516 l != NULL && !job_aborted (common);
3517 l = l->next) {
3518 src = l->data;
3520 if (i < job->n_icon_positions) {
3521 point = &job->icon_positions[i];
3522 } else {
3523 point = NULL;
3527 same_fs = FALSE;
3528 if (dest_fs_id) {
3529 same_fs = has_fs_id (src, dest_fs_id);
3532 if (job->destination) {
3533 dest = g_object_ref (job->destination);
3534 } else {
3535 dest = g_file_get_parent (src);
3538 if (dest) {
3539 skipped_file = FALSE;
3540 copy_move_file (job, src, dest,
3541 same_fs, unique_names,
3542 source_info, transfer_info,
3543 job->debuting_files,
3544 point, FALSE, &skipped_file);
3545 g_object_unref (dest);
3547 i++;
3551 static gboolean
3552 copy_job_done (gpointer user_data)
3554 CopyMoveJob *job;
3556 job = user_data;
3557 if (job->done_callback) {
3558 job->done_callback (job->debuting_files, job->done_callback_data);
3561 eel_g_object_list_free (job->files);
3562 if (job->destination) {
3563 g_object_unref (job->destination);
3565 g_hash_table_unref (job->debuting_files);
3566 g_free (job->icon_positions);
3568 finalize_common ((CommonJob *)job);
3570 nautilus_file_changes_consume_changes (TRUE);
3571 return FALSE;
3574 static gboolean
3575 copy_job (GIOSchedulerJob *io_job,
3576 GCancellable *cancellable,
3577 gpointer user_data)
3579 CopyMoveJob *job;
3580 CommonJob *common;
3581 SourceInfo source_info;
3582 TransferInfo transfer_info;
3583 char *dest_fs_id;
3584 GFile *dest;
3586 job = user_data;
3587 common = &job->common;
3588 common->io_job = io_job;
3590 dest_fs_id = NULL;
3592 nautilus_progress_info_start (job->common.progress);
3594 scan_sources (job->files,
3595 &source_info,
3596 common,
3597 OP_KIND_COPY);
3598 if (job_aborted (common)) {
3599 goto aborted;
3602 if (job->destination) {
3603 dest = g_object_ref (job->destination);
3604 } else {
3605 /* Duplication, no dest,
3606 * use source for free size, etc
3608 dest = g_file_get_parent (job->files->data);
3611 verify_destination (&job->common,
3612 dest,
3613 &dest_fs_id,
3614 source_info.num_bytes);
3615 g_object_unref (dest);
3616 if (job_aborted (common)) {
3617 goto aborted;
3620 g_timer_start (job->common.time);
3622 memset (&transfer_info, 0, sizeof (transfer_info));
3623 copy_files (job,
3624 dest_fs_id,
3625 &source_info, &transfer_info);
3627 aborted:
3629 g_free (dest_fs_id);
3631 g_io_scheduler_job_send_to_mainloop_async (io_job,
3632 copy_job_done,
3633 job,
3634 NULL);
3636 return FALSE;
3639 void
3640 nautilus_file_operations_copy (GList *files,
3641 GArray *relative_item_points,
3642 GFile *target_dir,
3643 GtkWindow *parent_window,
3644 NautilusCopyCallback done_callback,
3645 gpointer done_callback_data)
3647 CopyMoveJob *job;
3649 job = op_job_new (CopyMoveJob, parent_window);
3650 job->done_callback = done_callback;
3651 job->done_callback_data = done_callback_data;
3652 job->files = eel_g_object_list_copy (files);
3653 job->destination = g_object_ref (target_dir);
3654 if (relative_item_points != NULL &&
3655 relative_item_points->len > 0) {
3656 job->icon_positions =
3657 g_memdup (relative_item_points->data,
3658 sizeof (GdkPoint) * relative_item_points->len);
3659 job->n_icon_positions = relative_item_points->len;
3661 job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
3663 g_io_scheduler_push_job (copy_job,
3664 job,
3665 NULL, /* destroy notify */
3667 job->common.cancellable);
3670 static void
3671 report_move_progress (CopyMoveJob *move_job, int total, int left)
3673 CommonJob *job;
3675 job = (CommonJob *)move_job;
3677 nautilus_progress_info_take_status (job->progress,
3678 f (_("Preparing to Move to \"%B\""),
3679 move_job->destination));
3681 nautilus_progress_info_take_details (job->progress,
3682 f (ngettext ("Preparing to move %'d file",
3683 "Preparing to move %'d files",
3684 left), left));
3686 nautilus_progress_info_pulse_progress (job->progress);
3689 static void
3690 move_file_prepare (CopyMoveJob *move_job,
3691 GFile *src,
3692 GFile *dest_dir,
3693 gboolean same_fs,
3694 GHashTable *debuting_files,
3695 GdkPoint *position,
3696 GList **copy_files,
3697 GArray *copy_positions)
3699 GFile *dest;
3700 GError *error;
3701 CommonJob *job;
3702 gboolean overwrite;
3703 char *primary, *secondary, *details;
3704 int response;
3705 GFileCopyFlags flags;
3706 gboolean would_recurse;
3708 overwrite = FALSE;
3710 job = (CommonJob *)move_job;
3712 dest = get_target_file (src, dest_dir, same_fs);
3714 retry:
3716 flags = G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_NO_FALLBACK_FOR_MOVE;
3717 if (overwrite) {
3718 flags |= G_FILE_COPY_OVERWRITE;
3721 error = NULL;
3722 if (g_file_move (src, dest,
3723 flags,
3724 job->cancellable,
3725 NULL,
3726 NULL,
3727 &error)) {
3729 if (debuting_files) {
3730 g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
3733 nautilus_file_changes_queue_file_moved (src, dest);
3734 nautilus_file_changes_queue_schedule_metadata_move (src, dest);
3735 if (position) {
3736 nautilus_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
3737 } else {
3738 nautilus_file_changes_queue_schedule_position_remove (dest);
3741 return;
3744 /* Conflict */
3745 if (!overwrite &&
3746 IS_IO_ERROR (error, EXISTS)) {
3747 gboolean is_merge;
3749 g_error_free (error);
3751 is_merge = FALSE;
3752 if (is_dir (dest)) {
3753 if (is_dir (src)) {
3754 is_merge = TRUE;
3755 primary = f (_("A folder named \"%B\" already exists. Do you want to merge the source folder?"),
3756 dest);
3757 secondary = f (_("The source folder already exists in \"%B\". "
3758 "Merging will ask for confirmation before replacing any files in the folder that conflict with the files being moved."),
3759 dest_dir);
3761 } else {
3762 primary = f (_("A folder named \"%B\" already exists. Do you want to replace it?"),
3763 dest);
3764 secondary = f (_("The folder already exists in \"%F\". "
3765 "Replacing it will remove all files in the folder."),
3766 dest_dir);
3768 } else {
3769 primary = f (_("A file named \"%B\" already exists. Do you want to replace it?"),
3770 dest);
3771 secondary = f (_("The file already exists in \"%F\". "
3772 "Replacing it will overwrite its content."),
3773 dest_dir);
3776 if ((is_merge && job->merge_all) ||
3777 (!is_merge && job->replace_all)) {
3778 g_free (primary);
3779 g_free (secondary);
3781 overwrite = TRUE;
3782 goto retry;
3785 if (job->skip_all_conflict) {
3786 g_free (primary);
3787 g_free (secondary);
3789 goto out;
3792 response = run_warning (job,
3793 primary,
3794 secondary,
3795 NULL,
3796 GTK_STOCK_CANCEL,
3797 SKIP_ALL,
3798 is_merge?MERGE_ALL:REPLACE_ALL,
3799 SKIP,
3800 is_merge?MERGE:REPLACE,
3801 NULL);
3803 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
3804 abort_job (job);
3805 } else if (response == 1 || response == 3) { /* skip all / skip */
3806 if (response == 1) {
3807 job->skip_all_conflict = TRUE;
3809 } else if (response == 2 || response == 4) { /* merge/replace all / merge/replace*/
3810 if (response == 2) {
3811 if (is_merge) {
3812 job->merge_all = TRUE;
3813 } else {
3814 job->replace_all = TRUE;
3817 overwrite = TRUE;
3818 goto retry;
3819 } else {
3820 g_assert_not_reached ();
3824 else if (IS_IO_ERROR (error, WOULD_RECURSE) ||
3825 IS_IO_ERROR (error, WOULD_MERGE) ||
3826 IS_IO_ERROR (error, NOT_SUPPORTED)) {
3827 gboolean delete_dest;
3828 would_recurse = error->code == G_IO_ERROR_WOULD_RECURSE;
3829 g_error_free (error);
3831 delete_dest = FALSE;
3832 if (overwrite && would_recurse) {
3833 delete_dest = TRUE;
3835 error = NULL;
3838 *copy_files = g_list_prepend (*copy_files, src);
3839 if (position) {
3840 g_array_append_val (copy_positions, *position);
3844 else if (IS_IO_ERROR (error, CANCELLED)) {
3845 g_error_free (error);
3848 /* Other error */
3849 else {
3850 if (job->skip_all_error) {
3851 goto out;
3853 primary = f (_("Error while moving \"%B\"."), src);
3854 secondary = f (_("There was an error moving the file into %F."), dest_dir);
3855 details = error->message;
3857 response = run_warning (job,
3858 primary,
3859 secondary,
3860 details,
3861 GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
3862 NULL);
3864 g_error_free (error);
3866 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
3867 abort_job (job);
3868 } else if (response == 1) { /* skip all */
3869 job->skip_all_error = TRUE;
3870 } else if (response == 2) { /* skip */
3871 /* do nothing */
3872 } else {
3873 g_assert_not_reached ();
3877 out:
3878 g_object_unref (dest);
3881 static void
3882 move_files_prepare (CopyMoveJob *job,
3883 const char *dest_fs_id,
3884 GList **copy_files,
3885 GArray *copy_positions)
3887 CommonJob *common;
3888 GList *l;
3889 GFile *src;
3890 gboolean same_fs;
3891 int i;
3892 GdkPoint *point;
3893 int total, left;
3895 common = &job->common;
3897 total = left = g_list_length (job->files);
3899 report_move_progress (job, total, left);
3901 i = 0;
3902 for (l = job->files;
3903 l != NULL && !job_aborted (common);
3904 l = l->next) {
3905 src = l->data;
3907 if (i < job->n_icon_positions) {
3908 point = &job->icon_positions[i];
3909 } else {
3910 point = NULL;
3914 same_fs = FALSE;
3915 if (dest_fs_id) {
3916 same_fs = has_fs_id (src, dest_fs_id);
3919 move_file_prepare (job, src, job->destination,
3920 same_fs,
3921 job->debuting_files,
3922 point,
3923 copy_files,
3924 copy_positions);
3925 report_move_progress (job, total, --left);
3926 i++;
3931 static void
3932 move_files (CopyMoveJob *job,
3933 GList *files,
3934 GArray *icon_positions,
3935 const char *dest_fs_id,
3936 SourceInfo *source_info,
3937 TransferInfo *transfer_info)
3939 CommonJob *common;
3940 GList *l;
3941 GFile *src;
3942 gboolean same_fs;
3943 int i;
3944 GdkPoint *point;
3945 gboolean skipped_file;
3947 common = &job->common;
3949 report_copy_progress (job, source_info, transfer_info);
3951 i = 0;
3952 for (l = files;
3953 l != NULL && !job_aborted (common);
3954 l = l->next) {
3955 src = l->data;
3957 if (i < icon_positions->len) {
3958 point = &g_array_index (icon_positions, GdkPoint, i);
3959 } else {
3960 point = NULL;
3963 same_fs = FALSE;
3964 if (dest_fs_id) {
3965 same_fs = has_fs_id (src, dest_fs_id);
3968 /* Set overwrite to true, as the user has
3969 selected overwrite on all toplevel items */
3970 skipped_file = FALSE;
3971 copy_move_file (job, src, job->destination,
3972 same_fs, FALSE,
3973 source_info, transfer_info,
3974 job->debuting_files,
3975 point, TRUE, &skipped_file);
3976 i++;
3981 static gboolean
3982 move_job_done (gpointer user_data)
3984 CopyMoveJob *job;
3986 job = user_data;
3987 if (job->done_callback) {
3988 job->done_callback (job->debuting_files, job->done_callback_data);
3991 eel_g_object_list_free (job->files);
3992 g_object_unref (job->destination);
3993 g_hash_table_unref (job->debuting_files);
3994 g_free (job->icon_positions);
3996 finalize_common ((CommonJob *)job);
3998 nautilus_file_changes_consume_changes (TRUE);
3999 return FALSE;
4002 static gboolean
4003 move_job (GIOSchedulerJob *io_job,
4004 GCancellable *cancellable,
4005 gpointer user_data)
4007 CopyMoveJob *job;
4008 CommonJob *common;
4009 GList *copy_files;
4010 GArray *copy_positions;
4011 SourceInfo source_info;
4012 TransferInfo transfer_info;
4013 char *dest_fs_id;
4015 job = user_data;
4016 common = &job->common;
4017 common->io_job = io_job;
4019 dest_fs_id = NULL;
4021 copy_files = NULL;
4022 copy_positions = NULL;
4024 nautilus_progress_info_start (job->common.progress);
4026 verify_destination (&job->common,
4027 job->destination,
4028 &dest_fs_id,
4029 -1);
4030 if (job_aborted (common)) {
4031 goto aborted;
4034 copy_files = NULL;
4035 copy_positions = g_array_new (FALSE, TRUE, sizeof (GdkPoint));
4037 /* This moves all files that we can do without copy + delete */
4038 move_files_prepare (job, dest_fs_id, &copy_files, copy_positions);
4039 if (job_aborted (common)) {
4040 goto aborted;
4043 copy_files = g_list_reverse (copy_files);
4045 /* The rest we need to do deep copy + delete behind on,
4046 so scan for size */
4048 scan_sources (copy_files,
4049 &source_info,
4050 common,
4051 OP_KIND_MOVE);
4052 if (job_aborted (common)) {
4053 goto aborted;
4056 verify_destination (&job->common,
4057 job->destination,
4058 &dest_fs_id,
4059 source_info.num_bytes);
4060 if (job_aborted (common)) {
4061 goto aborted;
4064 memset (&transfer_info, 0, sizeof (transfer_info));
4065 move_files (job,
4066 copy_files,
4067 copy_positions,
4068 dest_fs_id,
4069 &source_info, &transfer_info);
4071 aborted:
4072 g_list_free (copy_files);
4073 if (copy_positions) {
4074 g_array_free (copy_positions, TRUE);
4077 g_free (dest_fs_id);
4079 g_io_scheduler_job_send_to_mainloop (io_job,
4080 move_job_done,
4081 job,
4082 NULL);
4084 return FALSE;
4087 void
4088 nautilus_file_operations_move (GList *files,
4089 GArray *relative_item_points,
4090 GFile *target_dir,
4091 GtkWindow *parent_window,
4092 NautilusCopyCallback done_callback,
4093 gpointer done_callback_data)
4095 CopyMoveJob *job;
4097 job = op_job_new (CopyMoveJob, parent_window);
4098 job->is_move = TRUE;
4099 job->done_callback = done_callback;
4100 job->done_callback_data = done_callback_data;
4101 job->files = eel_g_object_list_copy (files);
4102 job->destination = g_object_ref (target_dir);
4103 if (relative_item_points != NULL &&
4104 relative_item_points->len > 0) {
4105 job->icon_positions =
4106 g_memdup (relative_item_points->data,
4107 sizeof (GdkPoint) * relative_item_points->len);
4108 job->n_icon_positions = relative_item_points->len;
4110 job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
4112 g_io_scheduler_push_job (move_job,
4113 job,
4114 NULL, /* destroy notify */
4116 job->common.cancellable);
4119 static void
4120 report_link_progress (CopyMoveJob *link_job, int total, int left)
4122 CommonJob *job;
4124 job = (CommonJob *)link_job;
4126 nautilus_progress_info_take_status (job->progress,
4127 f (_("Creating links in \"%B\""),
4128 link_job->destination));
4130 nautilus_progress_info_take_details (job->progress,
4131 f (ngettext ("Making link to %'d file",
4132 "Making links to %'d files",
4133 left), left));
4135 nautilus_progress_info_set_progress (job->progress, left, total);
4139 static void
4140 link_file (CopyMoveJob *job,
4141 GFile *src, GFile *dest_dir,
4142 GHashTable *debuting_files,
4143 GdkPoint *position)
4145 GFile *dest;
4146 int count;
4147 char *path;
4148 gboolean not_local;
4149 GError *error;
4150 CommonJob *common;
4151 char *primary, *secondary, *details;
4152 int response;
4154 common = (CommonJob *)job;
4156 count = 1;
4158 dest = get_target_file_for_link (src, dest_dir, count);
4160 retry:
4161 error = NULL;
4162 not_local = FALSE;
4163 path = g_file_get_path (src);
4164 if (path == NULL) {
4165 not_local = TRUE;
4166 } else if (g_file_make_symbolic_link (dest,
4167 path,
4168 common->cancellable,
4169 &error)) {
4170 g_free (path);
4171 if (debuting_files) {
4172 g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
4175 nautilus_file_changes_queue_file_added (dest);
4176 if (position) {
4177 nautilus_file_changes_queue_schedule_position_set (dest, *position, common->screen_num);
4178 } else {
4179 nautilus_file_changes_queue_schedule_position_remove (dest);
4182 g_object_unref (dest);
4184 return;
4186 g_free (path);
4188 /* Conflict */
4189 if (error != NULL && IS_IO_ERROR (error, EXISTS)) {
4190 g_object_unref (dest);
4191 dest = get_target_file_for_link (src, dest_dir, count++);
4192 g_error_free (error);
4193 goto retry;
4196 else if (error != NULL && IS_IO_ERROR (error, CANCELLED)) {
4197 g_error_free (error);
4200 /* Other error */
4201 else {
4202 if (common->skip_all_error) {
4203 goto out;
4205 primary = f (_("Error while creating link to %B."), src);
4206 if (not_local) {
4207 secondary = f (_("Symbolic links only supported for local files"));
4208 details = NULL;
4209 } else if (IS_IO_ERROR (error, NOT_SUPPORTED)) {
4210 secondary = f (_("The target doesn't support symbolic links."));
4211 details = NULL;
4212 } else {
4213 secondary = f (_("There was an error creating the symlink in %F."), dest_dir);
4214 details = error->message;
4217 response = run_warning (common,
4218 primary,
4219 secondary,
4220 details,
4221 GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
4222 NULL);
4224 if (error) {
4225 g_error_free (error);
4228 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
4229 abort_job (common);
4230 } else if (response == 1) { /* skip all */
4231 common->skip_all_error = TRUE;
4232 } else if (response == 2) { /* skip */
4233 /* do nothing */
4234 } else {
4235 g_assert_not_reached ();
4239 out:
4240 g_object_unref (dest);
4243 static gboolean
4244 link_job_done (gpointer user_data)
4246 CopyMoveJob *job;
4248 job = user_data;
4249 if (job->done_callback) {
4250 job->done_callback (job->debuting_files, job->done_callback_data);
4253 eel_g_object_list_free (job->files);
4254 g_object_unref (job->destination);
4255 g_hash_table_unref (job->debuting_files);
4256 g_free (job->icon_positions);
4258 finalize_common ((CommonJob *)job);
4260 nautilus_file_changes_consume_changes (TRUE);
4261 return FALSE;
4264 static gboolean
4265 link_job (GIOSchedulerJob *io_job,
4266 GCancellable *cancellable,
4267 gpointer user_data)
4269 CopyMoveJob *job;
4270 CommonJob *common;
4271 GList *copy_files;
4272 GArray *copy_positions;
4273 GFile *src;
4274 GdkPoint *point;
4275 int total, left;
4276 int i;
4277 GList *l;
4279 job = user_data;
4280 common = &job->common;
4281 common->io_job = io_job;
4283 copy_files = NULL;
4284 copy_positions = NULL;
4286 nautilus_progress_info_start (job->common.progress);
4288 verify_destination (&job->common,
4289 job->destination,
4290 NULL,
4291 -1);
4292 if (job_aborted (common)) {
4293 goto aborted;
4296 total = left = g_list_length (job->files);
4298 report_link_progress (job, total, left);
4300 i = 0;
4301 for (l = job->files;
4302 l != NULL && !job_aborted (common);
4303 l = l->next) {
4304 src = l->data;
4306 if (i < job->n_icon_positions) {
4307 point = &job->icon_positions[i];
4308 } else {
4309 point = NULL;
4313 link_file (job, src, job->destination,
4314 job->debuting_files,
4315 point);
4316 report_link_progress (job, total, --left);
4317 i++;
4321 aborted:
4323 g_io_scheduler_job_send_to_mainloop (io_job,
4324 link_job_done,
4325 job,
4326 NULL);
4328 return FALSE;
4331 void
4332 nautilus_file_operations_link (GList *files,
4333 GArray *relative_item_points,
4334 GFile *target_dir,
4335 GtkWindow *parent_window,
4336 NautilusCopyCallback done_callback,
4337 gpointer done_callback_data)
4339 CopyMoveJob *job;
4341 job = op_job_new (CopyMoveJob, parent_window);
4342 job->done_callback = done_callback;
4343 job->done_callback_data = done_callback_data;
4344 job->files = eel_g_object_list_copy (files);
4345 job->destination = g_object_ref (target_dir);
4346 if (relative_item_points != NULL &&
4347 relative_item_points->len > 0) {
4348 job->icon_positions =
4349 g_memdup (relative_item_points->data,
4350 sizeof (GdkPoint) * relative_item_points->len);
4351 job->n_icon_positions = relative_item_points->len;
4353 job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
4355 g_io_scheduler_push_job (link_job,
4356 job,
4357 NULL, /* destroy notify */
4359 job->common.cancellable);
4363 void
4364 nautilus_file_operations_duplicate (GList *files,
4365 GArray *relative_item_points,
4366 GtkWindow *parent_window,
4367 NautilusCopyCallback done_callback,
4368 gpointer done_callback_data)
4370 CopyMoveJob *job;
4372 job = op_job_new (CopyMoveJob, parent_window);
4373 job->done_callback = done_callback;
4374 job->done_callback_data = done_callback_data;
4375 job->files = eel_g_object_list_copy (files);
4376 job->destination = NULL;
4377 if (relative_item_points != NULL &&
4378 relative_item_points->len > 0) {
4379 job->icon_positions =
4380 g_memdup (relative_item_points->data,
4381 sizeof (GdkPoint) * relative_item_points->len);
4382 job->n_icon_positions = relative_item_points->len;
4384 job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
4386 g_io_scheduler_push_job (copy_job,
4387 job,
4388 NULL, /* destroy notify */
4390 job->common.cancellable);
4393 static gboolean
4394 set_permissions_job_done (gpointer user_data)
4396 SetPermissionsJob *job;
4398 job = user_data;
4400 g_object_unref (job->file);
4402 if (job->done_callback) {
4403 job->done_callback (job->done_callback_data);
4406 finalize_common ((CommonJob *)job);
4407 return FALSE;
4410 static void
4411 set_permissions_file (SetPermissionsJob *job,
4412 GFile *file,
4413 GFileInfo *info)
4415 CommonJob *common;
4416 GFileInfo *child_info;
4417 gboolean free_info;
4418 guint32 current;
4419 guint32 value;
4420 guint32 mask;
4421 GFileEnumerator *enumerator;
4422 GFile *child;
4424 common = (CommonJob *)job;
4426 nautilus_progress_info_pulse_progress (common->progress);
4428 free_info = FALSE;
4429 if (info == NULL) {
4430 free_info = TRUE;
4431 info = g_file_query_info (file,
4432 G_FILE_ATTRIBUTE_STANDARD_TYPE","
4433 G_FILE_ATTRIBUTE_UNIX_MODE,
4434 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
4435 common->cancellable,
4436 NULL);
4437 /* Ignore errors */
4438 if (info == NULL) {
4439 return;
4443 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
4444 value = job->dir_permissions;
4445 mask = job->dir_mask;
4446 } else {
4447 value = job->file_permissions;
4448 mask = job->file_mask;
4452 if (!job_aborted (common) &&
4453 g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE)) {
4454 current = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
4455 current = (current & ~mask) | value;
4457 g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE,
4458 current, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
4459 common->cancellable, NULL);
4462 if (!job_aborted (common) &&
4463 g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
4464 enumerator = g_file_enumerate_children (file,
4465 G_FILE_ATTRIBUTE_STANDARD_NAME","
4466 G_FILE_ATTRIBUTE_STANDARD_TYPE","
4467 G_FILE_ATTRIBUTE_UNIX_MODE,
4468 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
4469 common->cancellable,
4470 NULL);
4471 if (enumerator) {
4472 while (!job_aborted (common) &&
4473 (child_info = g_file_enumerator_next_file (enumerator, common->cancellable, NULL)) != NULL) {
4474 child = g_file_get_child (file,
4475 g_file_info_get_name (child_info));
4476 set_permissions_file (job, child, child_info);
4477 g_object_unref (child);
4478 g_object_unref (child_info);
4480 g_file_enumerator_close (enumerator, common->cancellable, NULL);
4481 g_object_unref (enumerator);
4484 if (free_info) {
4485 g_object_unref (info);
4490 static gboolean
4491 set_permissions_job (GIOSchedulerJob *io_job,
4492 GCancellable *cancellable,
4493 gpointer user_data)
4495 SetPermissionsJob *job = user_data;
4496 CommonJob *common;
4498 common = (CommonJob *)job;
4499 common->io_job = io_job;
4501 nautilus_progress_info_set_status (common->progress,
4502 _("Setting permissions"));
4504 nautilus_progress_info_start (job->common.progress);
4506 set_permissions_file (job, job->file, NULL);
4508 g_io_scheduler_job_send_to_mainloop_async (io_job,
4509 set_permissions_job_done,
4510 job,
4511 NULL);
4513 return FALSE;
4518 void
4519 nautilus_file_set_permissions_recursive (const char *directory,
4520 guint32 file_permissions,
4521 guint32 file_mask,
4522 guint32 dir_permissions,
4523 guint32 dir_mask,
4524 NautilusOpCallback callback,
4525 gpointer callback_data)
4527 SetPermissionsJob *job;
4529 job = op_job_new (SetPermissionsJob, NULL);
4530 job->file = g_file_new_for_uri (directory);
4531 job->file_permissions = file_permissions;
4532 job->file_mask = file_mask;
4533 job->dir_permissions = dir_permissions;
4534 job->dir_mask = dir_mask;
4535 job->done_callback = callback;
4536 job->done_callback_data = callback_data;
4538 g_io_scheduler_push_job (set_permissions_job,
4539 job,
4540 NULL,
4542 NULL);
4545 static GList *
4546 location_list_from_uri_list (const GList *uris)
4548 const GList *l;
4549 GList *files;
4550 GFile *f;
4552 files = NULL;
4553 for (l = uris; l != NULL; l = l->next) {
4554 f = g_file_new_for_uri (l->data);
4555 files = g_list_prepend (files, f);
4558 return g_list_reverse (files);
4561 typedef struct {
4562 NautilusCopyCallback real_callback;
4563 gpointer real_data;
4564 } MoveTrashCBData;
4566 static void
4567 callback_for_move_to_trash (GHashTable *debuting_uris,
4568 gboolean user_cancelled,
4569 MoveTrashCBData *data)
4571 if (data->real_callback)
4572 data->real_callback (debuting_uris, data->real_data);
4573 g_slice_free (MoveTrashCBData, data);
4576 void
4577 nautilus_file_operations_copy_move (const GList *item_uris,
4578 GArray *relative_item_points,
4579 const char *target_dir,
4580 GdkDragAction copy_action,
4581 GtkWidget *parent_view,
4582 NautilusCopyCallback done_callback,
4583 gpointer done_callback_data)
4585 GList *locations;
4586 GFile *dest, *src_dir;
4587 GtkWindow *parent_window;
4589 dest = NULL;
4590 if (target_dir) {
4591 dest = g_file_new_for_uri (target_dir);
4593 locations = location_list_from_uri_list (item_uris);
4595 parent_window = NULL;
4596 if (parent_view) {
4597 parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
4600 if (copy_action == GDK_ACTION_COPY) {
4601 src_dir = g_file_get_parent (locations->data);
4602 if (target_dir == NULL ||
4603 (src_dir != NULL &&
4604 g_file_equal (src_dir, dest))) {
4605 nautilus_file_operations_duplicate (locations,
4606 relative_item_points,
4607 parent_window,
4608 done_callback, done_callback_data);
4609 } else {
4610 nautilus_file_operations_copy (locations,
4611 relative_item_points,
4612 dest,
4613 parent_window,
4614 done_callback, done_callback_data);
4616 if (src_dir) {
4617 g_object_unref (src_dir);
4620 } else if (copy_action == GDK_ACTION_MOVE) {
4621 if (g_file_has_uri_scheme (dest, "trash")) {
4622 MoveTrashCBData *cb_data;
4624 cb_data = g_slice_new0 (MoveTrashCBData);
4625 cb_data->real_callback = done_callback;
4626 cb_data->real_data = done_callback_data;
4627 nautilus_file_operations_trash_or_delete (locations,
4628 parent_window,
4629 (NautilusDeleteCallback) callback_for_move_to_trash,
4630 cb_data);
4631 } else {
4632 nautilus_file_operations_move (locations,
4633 relative_item_points,
4634 dest,
4635 parent_window,
4636 done_callback, done_callback_data);
4638 } else {
4639 nautilus_file_operations_link (locations,
4640 relative_item_points,
4641 dest,
4642 parent_window,
4643 done_callback, done_callback_data);
4646 eel_g_object_list_free (locations);
4647 if (dest) {
4648 g_object_unref (dest);
4652 static gboolean
4653 create_job_done (gpointer user_data)
4655 CreateJob *job;
4657 job = user_data;
4658 if (job->done_callback) {
4659 job->done_callback (job->created_file, job->done_callback_data);
4662 g_object_unref (job->dest_dir);
4663 if (job->src) {
4664 g_object_unref (job->src);
4666 g_free (job->src_data);
4667 g_free (job->filename);
4668 if (job->created_file) {
4669 g_object_unref (job->created_file);
4672 finalize_common ((CommonJob *)job);
4674 nautilus_file_changes_consume_changes (TRUE);
4675 return FALSE;
4678 static gboolean
4679 create_job (GIOSchedulerJob *io_job,
4680 GCancellable *cancellable,
4681 gpointer user_data)
4683 CreateJob *job;
4684 CommonJob *common;
4685 int count;
4686 GFile *dest;
4687 char *filename, *filename2;
4688 GError *error;
4689 gboolean res;
4690 gboolean filename_is_utf8;
4691 char *primary, *secondary, *details;
4692 int response;
4693 char *data;
4694 GFileOutputStream *out;
4696 job = user_data;
4697 common = &job->common;
4698 common->io_job = io_job;
4700 nautilus_progress_info_start (job->common.progress);
4702 filename = NULL;
4703 dest = NULL;
4705 verify_destination (common,
4706 job->dest_dir,
4707 NULL, -1);
4708 if (job_aborted (common)) {
4709 goto aborted;
4712 filename = g_strdup (job->filename);
4713 filename_is_utf8 = FALSE;
4714 if (filename) {
4715 filename_is_utf8 = g_utf8_validate (filename, -1, NULL);
4717 if (filename == NULL) {
4718 if (job->make_dir) {
4719 /* localizers: the initial name of a new folder */
4720 filename = g_strdup (_("untitled folder"));
4721 filename_is_utf8 = TRUE; /* Pass in utf8 */
4722 } else {
4723 if (job->src != NULL) {
4724 filename = g_file_get_basename (job->src);
4726 if (filename == NULL) {
4727 /* localizers: the initial name of a new empty file */
4728 filename = g_strdup (_("new file"));
4729 filename_is_utf8 = TRUE; /* Pass in utf8 */
4734 if (filename_is_utf8) {
4735 dest = g_file_get_child_for_display_name (job->dest_dir, filename, NULL);
4737 if (dest == NULL) {
4738 dest = g_file_get_child (job->dest_dir, filename);
4740 count = 1;
4742 retry:
4744 error = NULL;
4745 if (job->make_dir) {
4746 res = g_file_make_directory (dest,
4747 common->cancellable,
4748 &error);
4749 } else {
4750 if (job->src) {
4751 res = g_file_copy (job->src,
4752 dest,
4753 G_FILE_COPY_NONE,
4754 common->cancellable,
4755 NULL, NULL,
4756 &error);
4757 } else {
4758 data = "";
4759 if (job->src_data) {
4760 data = job->src_data;
4763 out = g_file_create (dest,
4764 G_FILE_CREATE_NONE,
4765 common->cancellable,
4766 &error);
4767 if (out) {
4768 res = g_output_stream_write_all (G_OUTPUT_STREAM (out),
4769 data, strlen (data),
4770 NULL,
4771 common->cancellable,
4772 &error);
4773 if (res) {
4774 res = g_output_stream_close (G_OUTPUT_STREAM (out),
4775 common->cancellable,
4776 &error);
4779 /* This will close if the write failed and we didn't close */
4780 g_object_unref (out);
4781 } else {
4782 res = FALSE;
4787 if (res) {
4788 job->created_file = g_object_ref (dest);
4789 nautilus_file_changes_queue_file_added (dest);
4790 if (job->has_position) {
4791 nautilus_file_changes_queue_schedule_position_set (dest, job->position, common->screen_num);
4792 } else {
4793 nautilus_file_changes_queue_schedule_position_remove (dest);
4795 } else {
4796 if (error != NULL && IS_IO_ERROR (error, EXISTS)) {
4797 g_object_unref (dest);
4798 dest = NULL;
4799 filename2 = g_strdup_printf ("%s %d", filename, ++count);
4800 if (filename_is_utf8) {
4801 dest = g_file_get_child_for_display_name (job->dest_dir, filename2, NULL);
4803 if (dest == NULL) {
4804 dest = g_file_get_child (job->dest_dir, filename2);
4806 g_free (filename2);
4807 g_error_free (error);
4808 goto retry;
4811 else if (error != NULL && IS_IO_ERROR (error, CANCELLED)) {
4812 g_error_free (error);
4815 /* Other error */
4816 else {
4817 if (job->make_dir) {
4818 primary = f (_("Error while creating directory %B."), dest);
4819 } else {
4820 primary = f (_("Error while creating file %B."), dest);
4822 secondary = f (_("There was an error creating the directory in %F."), job->dest_dir);
4823 details = error->message;
4825 response = run_warning (common,
4826 primary,
4827 secondary,
4828 details,
4829 GTK_STOCK_CANCEL, SKIP,
4830 NULL);
4832 g_error_free (error);
4834 if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
4835 abort_job (common);
4836 } else if (response == 1) { /* skip */
4837 /* do nothing */
4838 } else {
4839 g_assert_not_reached ();
4844 aborted:
4845 if (dest) {
4846 g_object_unref (dest);
4848 g_free (filename);
4849 g_io_scheduler_job_send_to_mainloop_async (io_job,
4850 create_job_done,
4851 job,
4852 NULL);
4854 return FALSE;
4857 void
4858 nautilus_file_operations_new_folder (GtkWidget *parent_view,
4859 GdkPoint *target_point,
4860 const char *parent_dir,
4861 NautilusCreateCallback done_callback,
4862 gpointer done_callback_data)
4864 CreateJob *job;
4865 GtkWindow *parent_window;
4867 parent_window = NULL;
4868 if (parent_view) {
4869 parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
4872 job = op_job_new (CreateJob, parent_window);
4873 job->done_callback = done_callback;
4874 job->done_callback_data = done_callback_data;
4875 job->dest_dir = g_file_new_for_uri (parent_dir);
4876 job->make_dir = TRUE;
4877 if (target_point != NULL) {
4878 job->position = *target_point;
4879 job->has_position = TRUE;
4882 g_io_scheduler_push_job (create_job,
4883 job,
4884 NULL, /* destroy notify */
4886 job->common.cancellable);
4889 void
4890 nautilus_file_operations_new_file_from_template (GtkWidget *parent_view,
4891 GdkPoint *target_point,
4892 const char *parent_dir,
4893 const char *target_filename,
4894 const char *template_uri,
4895 NautilusCreateCallback done_callback,
4896 gpointer done_callback_data)
4898 CreateJob *job;
4899 GtkWindow *parent_window;
4901 parent_window = NULL;
4902 if (parent_view) {
4903 parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
4906 job = op_job_new (CreateJob, parent_window);
4907 job->done_callback = done_callback;
4908 job->done_callback_data = done_callback_data;
4909 job->dest_dir = g_file_new_for_uri (parent_dir);
4910 if (target_point != NULL) {
4911 job->position = *target_point;
4912 job->has_position = TRUE;
4914 job->filename = g_strdup (target_filename);
4916 if (template_uri) {
4917 job->src = g_file_new_for_uri (template_uri);
4920 g_io_scheduler_push_job (create_job,
4921 job,
4922 NULL, /* destroy notify */
4924 job->common.cancellable);
4927 void
4928 nautilus_file_operations_new_file (GtkWidget *parent_view,
4929 GdkPoint *target_point,
4930 const char *parent_dir,
4931 const char *target_filename,
4932 const char *initial_contents,
4933 NautilusCreateCallback done_callback,
4934 gpointer done_callback_data)
4936 CreateJob *job;
4937 GtkWindow *parent_window;
4939 parent_window = NULL;
4940 if (parent_view) {
4941 parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
4944 job = op_job_new (CreateJob, parent_window);
4945 job->done_callback = done_callback;
4946 job->done_callback_data = done_callback_data;
4947 job->dest_dir = g_file_new_for_uri (parent_dir);
4948 if (target_point != NULL) {
4949 job->position = *target_point;
4950 job->has_position = TRUE;
4952 job->src_data = g_strdup (initial_contents);
4953 job->filename = g_strdup (target_filename);
4955 g_io_scheduler_push_job (create_job,
4956 job,
4957 NULL, /* destroy notify */
4959 job->common.cancellable);
4964 static void
4965 delete_trash_file (CommonJob *job,
4966 GFile *file,
4967 gboolean del_file,
4968 gboolean del_children)
4970 GFileInfo *info;
4971 GFile *child;
4972 GFileEnumerator *enumerator;
4974 if (job_aborted (job)) {
4975 return;
4978 if (del_children) {
4979 enumerator = g_file_enumerate_children (file,
4980 G_FILE_ATTRIBUTE_STANDARD_NAME ","
4981 G_FILE_ATTRIBUTE_STANDARD_TYPE,
4982 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
4983 job->cancellable,
4984 NULL);
4985 if (enumerator) {
4986 while (!job_aborted (job) &&
4987 (info = g_file_enumerator_next_file (enumerator, job->cancellable, NULL)) != NULL) {
4988 child = g_file_get_child (file,
4989 g_file_info_get_name (info));
4990 delete_trash_file (job, child, TRUE,
4991 g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY);
4992 g_object_unref (child);
4993 g_object_unref (info);
4995 g_file_enumerator_close (enumerator, job->cancellable, NULL);
4996 g_object_unref (enumerator);
5000 if (!job_aborted (job) && del_file) {
5001 g_file_delete (file, job->cancellable, NULL);
5005 static gboolean
5006 empty_trash_job_done (gpointer user_data)
5008 EmptyTrashJob *job;
5010 job = user_data;
5012 eel_g_object_list_free (job->trash_dirs);
5014 if (job->done_callback) {
5015 job->done_callback (job->done_callback_data);
5018 finalize_common ((CommonJob *)job);
5019 return FALSE;
5022 static gboolean
5023 empty_trash_job (GIOSchedulerJob *io_job,
5024 GCancellable *cancellable,
5025 gpointer user_data)
5027 EmptyTrashJob *job = user_data;
5028 CommonJob *common;
5029 GList *l;
5031 common = (CommonJob *)job;
5032 common->io_job = io_job;
5034 nautilus_progress_info_start (job->common.progress);
5036 if (confirm_empty_trash (common)) {
5037 for (l = job->trash_dirs;
5038 l != NULL && !job_aborted (common);
5039 l = l->next) {
5040 delete_trash_file (common, l->data, FALSE, TRUE);
5044 g_io_scheduler_job_send_to_mainloop_async (io_job,
5045 empty_trash_job_done,
5046 job,
5047 NULL);
5049 return FALSE;
5052 void
5053 nautilus_file_operations_empty_trash (GtkWidget *parent_view)
5055 EmptyTrashJob *job;
5056 GtkWindow *parent_window;
5058 parent_window = NULL;
5059 if (parent_view) {
5060 parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
5063 job = op_job_new (EmptyTrashJob, parent_window);
5064 job->trash_dirs = g_list_prepend (job->trash_dirs,
5065 g_file_new_for_uri ("trash:"));
5067 g_io_scheduler_push_job (empty_trash_job,
5068 job,
5069 NULL,
5071 NULL);
5075 #if !defined (NAUTILUS_OMIT_SELF_CHECK)
5077 void
5078 nautilus_self_check_file_operations (void)
5080 setlocale (LC_MESSAGES, "C");
5083 /* test the next duplicate name generator */
5084 EEL_CHECK_STRING_RESULT (get_duplicate_name (" (copy)", 1), " (another copy)");
5085 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo", 1), "foo (copy)");
5086 EEL_CHECK_STRING_RESULT (get_duplicate_name (".bashrc", 1), ".bashrc (copy)");
5087 EEL_CHECK_STRING_RESULT (get_duplicate_name (".foo.txt", 1), ".foo (copy).txt");
5088 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo", 1), "foo foo (copy)");
5089 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo.txt", 1), "foo (copy).txt");
5090 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt", 1), "foo foo (copy).txt");
5091 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt txt", 1), "foo foo (copy).txt txt");
5092 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...txt", 1), "foo (copy)...txt");
5093 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...", 1), "foo (copy)...");
5094 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo. (copy)", 1), "foo. (another copy)");
5095 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy)", 1), "foo (another copy)");
5096 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy).txt", 1), "foo (another copy).txt");
5097 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy)", 1), "foo (3rd copy)");
5098 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy).txt", 1), "foo (3rd copy).txt");
5099 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (another copy).txt", 1), "foo foo (3rd copy).txt");
5100 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy)", 1), "foo (14th copy)");
5101 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy).txt", 1), "foo (14th copy).txt");
5102 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy)", 1), "foo (22nd copy)");
5103 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy).txt", 1), "foo (22nd copy).txt");
5104 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy)", 1), "foo (23rd copy)");
5105 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy).txt", 1), "foo (23rd copy).txt");
5106 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy)", 1), "foo (24th copy)");
5107 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy).txt", 1), "foo (24th copy).txt");
5108 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy)", 1), "foo (25th copy)");
5109 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy).txt", 1), "foo (25th copy).txt");
5110 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy)", 1), "foo foo (25th copy)");
5111 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy).txt", 1), "foo foo (25th copy).txt");
5112 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (100000000000000th copy).txt", 1), "foo foo (copy).txt");
5113 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy)", 1), "foo (11th copy)");
5114 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy).txt", 1), "foo (11th copy).txt");
5115 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy)", 1), "foo (12th copy)");
5116 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy).txt", 1), "foo (12th copy).txt");
5117 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy)", 1), "foo (13th copy)");
5118 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy).txt", 1), "foo (13th copy).txt");
5119 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy)", 1), "foo (111th copy)");
5120 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy).txt", 1), "foo (111th copy).txt");
5121 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy)", 1), "foo (123rd copy)");
5122 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy).txt", 1), "foo (123rd copy).txt");
5123 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy)", 1), "foo (124th copy)");
5124 EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy).txt", 1), "foo (124th copy).txt");
5126 setlocale (LC_MESSAGES, "");
5129 #endif