nautilus-file-operations.c 103 KB
Newer Older
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2

3
/* nautilus-file-operations.c - Nautilus file operations.
Ettore Perazzoli's avatar
Ettore Perazzoli committed
4

Elliot Lee's avatar
Elliot Lee committed
5
   Copyright (C) 1999, 2000 Free Software Foundation
6
   Copyright (C) 2000, 2001 Eazel, Inc.
Alexander Larsson's avatar
Alexander Larsson committed
7
   Copyright (C) 2007 Red Hat, Inc.
Ettore Perazzoli's avatar
Ettore Perazzoli committed
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.
   
   You should have received a copy of the GNU General Public
   License along with this program; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
   
Alexander Larsson's avatar
Alexander Larsson committed
24 25
   Authors: Alexander Larsson <alexl@redhat.com>
            Ettore Perazzoli <ettore@gnu.org> 
26
            Pavel Cisler <pavel@eazel.com> 
27
 */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
28 29

#include <config.h>
30
#include <string.h>
31
#include <stdio.h>
Alexander Larsson's avatar
Alexander Larsson committed
32
#include <stdarg.h>
33
#include <locale.h>
Alexander Larsson's avatar
Alexander Larsson committed
34
#include <math.h>
35
#include "nautilus-file-operations.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
36

Alexander Larsson's avatar
 
Alexander Larsson committed
37
#include "nautilus-debug-log.h"
38
#include "nautilus-file-operations-progress.h"
Alexander Larsson's avatar
Alexander Larsson committed
39
#include "nautilus-file-changes-queue.h"
40
#include "nautilus-lib-self-check-functions.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
41

Alexander Larsson's avatar
Alexander Larsson committed
42 43 44
#include "nautilus-progress-info.h"

#include <eel/eel-alert-dialog.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
45
#include <eel/eel-glib-extensions.h>
46
#include <eel/eel-pango-extensions.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
47 48
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-stock-dialogs.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
49 50
#include <eel/eel-vfs-extensions.h>

51
#include <glib/gstdio.h>
Ettore Perazzoli's avatar
Ettore Perazzoli committed
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 <libgnomevfs/gnome-vfs-async-ops.h>
58
#include <libgnomevfs/gnome-vfs-find-directory.h>
59 60
#include <libgnomevfs/gnome-vfs-ops.h>
#include <libgnomevfs/gnome-vfs-result.h>
Pavel Cisler's avatar
Pavel Cisler committed
61
#include <libgnomevfs/gnome-vfs-uri.h>
62
#include <libgnomevfs/gnome-vfs-utils.h>
63 64
#include <libgnomevfs/gnome-vfs-volume.h>
#include <libgnomevfs/gnome-vfs-volume-monitor.h>
Alexander Larsson's avatar
Alexander Larsson committed
65 66 67
#include <gio/gfile.h>
#include <glib/gurifuncs.h>
#include <gio/gioscheduler.h>
68
#include "nautilus-file-changes-queue.h"
Alexander Larsson's avatar
Alexander Larsson committed
69 70 71
#include "nautilus-file-private.h"
#include "nautilus-desktop-icon-file.h"
#include "nautilus-desktop-link-monitor.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
72 73 74
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
#include "nautilus-trash-monitor.h"
75
#include "nautilus-file-utilities.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
76

Alexander Larsson's avatar
Alexander Larsson committed
77 78 79 80
static gboolean confirm_trash_auto_value;

/* TODO:
 *  Implement missing functions:
Alexander Larsson's avatar
Alexander Larsson committed
81
 *   new file, new folder, set_permissions recursive
Alexander Larsson's avatar
Alexander Larsson committed
82 83 84 85 86 87
 * TESTING!!!
 */

typedef struct {
	GIOJob *io_job;	
	GTimer *time;
88 89
	GtkWindow *parent_window;
	int screen_num;
Alexander Larsson's avatar
Alexander Larsson committed
90 91 92 93 94 95 96 97
	NautilusProgressInfo *progress;
	GCancellable *cancellable;
	GHashTable *skip_files;
	GHashTable *skip_readdir_error;
	gboolean skip_all_error;
	gboolean skip_all_conflict;
	gboolean merge_all;
	gboolean replace_all;
98
	gboolean delete_all;
Alexander Larsson's avatar
Alexander Larsson committed
99 100 101 102 103 104 105 106 107 108 109 110 111 112
} CommonJob;

typedef struct {
	CommonJob common;
	gboolean is_move;
	GList *files;
	GFile *destination;
	GdkPoint *icon_positions;
	int n_icon_positions;
	GHashTable *debuting_files;
	NautilusCopyCallback  done_callback;
	gpointer done_callback_data;
} CopyMoveJob;

113 114 115 116 117 118 119 120 121
typedef struct {
	CommonJob common;
	GList *files;
	gboolean try_trash;
	gboolean delete_if_all_already_in_trash;
	NautilusDeleteCallback done_callback;
	gpointer done_callback_data;
} DeleteJob;

Alexander Larsson's avatar
Alexander Larsson committed
122 123 124
typedef struct {
	CommonJob common;
} EmptyTrashJob;
125

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
typedef enum {
	OP_KIND_COPY,
	OP_KIND_MOVE,
	OP_KIND_DELETE,
	OP_KIND_TRASH
} OpKind;

typedef struct {
	int num_files;
	goffset num_bytes;
	int num_files_since_progress;
	OpKind op;
} SourceInfo;

typedef struct {
	int num_files;
	goffset num_bytes;
	OpKind op;
	guint64 last_report_time;
} TransferInfo;

Alexander Larsson's avatar
Alexander Larsson committed
147 148 149 150 151 152 153 154 155
#define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 15
#define NSEC_PER_SEC 1000000000
#define NSEC_PER_MSEC 1000000

#define IS_IO_ERROR(__error, KIND) (((__error)->domain == G_IO_ERROR && (__error)->code == G_IO_ERROR_ ## KIND))

#define SKIP _("_Skip")
#define SKIP_ALL _("S_kip All")
#define RETRY _("_Retry")
156
#define DELETE_ALL _("Delete _All")
Alexander Larsson's avatar
Alexander Larsson committed
157 158 159 160 161
#define REPLACE _("_Replace")
#define REPLACE_ALL _("Replace _All")
#define MERGE _("_Merge")
#define MERGE_ALL _("Merge _All")

162 163 164 165 166 167
static void scan_sources (GList *files,
			  SourceInfo *source_info,
			  CommonJob *job,
			  OpKind kind);


Alexander Larsson's avatar
Alexander Larsson committed
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
static char *
format_time (int seconds)
{
	int minutes;
	int hours;
	char *res;

	if (seconds < 0) {
		/* Just to make sure... */
		seconds = 0;
	}
	
	if (seconds < 60) {
		return g_strdup_printf (ngettext ("%d second","%d seconds", (int) seconds), (int) seconds);
	}

	if (seconds < 60*60) {
		minutes = (seconds + 30) / 60;
186
		return g_strdup_printf (ngettext ("%d minute", "%d minutes", minutes), minutes);
Alexander Larsson's avatar
Alexander Larsson committed
187 188 189 190 191 192 193 194 195
	}

	hours = seconds / (60*60);
	
	if (seconds < 60*60*4) {
		char *h, *m;

		minutes = (seconds - hours * 60 * 60 + 30) / 60;
		
196 197
		h = g_strdup_printf (ngettext ("%d hour", "%d hours", hours), hours);
		m = g_strdup_printf (ngettext ("%d minute", "%d minutes", minutes), minutes);
Alexander Larsson's avatar
Alexander Larsson committed
198 199 200 201 202 203
		res = g_strconcat (h, ", ", m, NULL);
		g_free (h);
		g_free (m);
		return res;
	}
	
204 205 206
	return g_strdup_printf (ngettext ("approximately %d hour",
					  "approximately %d hours",
					  hours), hours);
Alexander Larsson's avatar
Alexander Larsson committed
207 208
}

209
/* Note that we have these two separate functions with separate format
210
 * strings for ease of localization.
211 212 213
 */

static char *
Alexander Larsson's avatar
Alexander Larsson committed
214
get_link_name (const char *name, int count) 
215
{
216
	const char *format;
Alexander Larsson's avatar
Alexander Larsson committed
217
	char *result;
218
	
219 220 221 222 223 224 225
	g_assert (name != NULL);

	if (count < 1) {
		g_warning ("bad count in get_link_name");
		count = 1;
	}

226 227 228
	if (count <= 2) {
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
229
		 */
230
		switch (count) {
231 232 233
		default:
			g_assert_not_reached ();
			/* fall through */
234
		case 1:
235
			/* appended to new link file */
236
			format = _("Link to %s");
237 238
			break;
		case 2:
239
			/* appended to new link file */
240
			format = _("Another link to %s");
241 242
			break;
		}
Alexander Larsson's avatar
Alexander Larsson committed
243
		result = g_strdup_printf (format, name);
244 245 246 247 248

	} else {
		/* Handle special cases for the first few numbers of each ten.
		 * For locales where getting this exactly right is difficult,
		 * these can just be made all the same as the general case below.
249
		 */
250 251 252 253 254 255 256 257 258
		switch (count % 10) {
		case 1:
			/* Localizers: Feel free to leave out the "st" suffix
			 * if there's no way to do that nicely for a
			 * particular language.
			 */
			format = _("%dst link to %s");
			break;
		case 2:
259
			/* appended to new link file */
260 261 262
			format = _("%dnd link to %s");
			break;
		case 3:
263
			/* appended to new link file */
264 265 266
			format = _("%drd link to %s");
			break;
		default:
267
			/* appended to new link file */
268 269 270
			format = _("%dth link to %s");
			break;
		}
Alexander Larsson's avatar
Alexander Larsson committed
271
		result = g_strdup_printf (format, count, name);
272
	}
273

274
	return result;
275 276
}

Alexander Larsson's avatar
Alexander Larsson committed
277

278 279 280 281 282
/* Localizers: 
 * Feel free to leave out the st, nd, rd and th suffix or
 * make some or all of them match.
 */

283
/* localizers: tag used to detect the first copy of a file */
284
static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
285
/* localizers: tag used to detect the second copy of a file */
286
static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)");
287 288 289 290 291 292 293 294

/* localizers: tag used to detect the x11th copy of a file */
static const char untranslated_x11th_copy_duplicate_tag[] = N_("th copy)");
/* localizers: tag used to detect the x12th copy of a file */
static const char untranslated_x12th_copy_duplicate_tag[] = N_("th copy)");
/* localizers: tag used to detect the x13th copy of a file */
static const char untranslated_x13th_copy_duplicate_tag[] = N_("th copy)");

295
/* localizers: tag used to detect the x1st copy of a file */
296
static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)");
297
/* localizers: tag used to detect the x2nd copy of a file */
298
static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)");
299
/* localizers: tag used to detect the x3rd copy of a file */
300
static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)");
301

302
/* localizers: tag used to detect the xxth copy of a file */
303 304 305 306
static const char untranslated_th_copy_duplicate_tag[] = N_("th copy)");

#define COPY_DUPLICATE_TAG _(untranslated_copy_duplicate_tag)
#define ANOTHER_COPY_DUPLICATE_TAG _(untranslated_another_copy_duplicate_tag)
307 308 309 310
#define X11TH_COPY_DUPLICATE_TAG _(untranslated_x11th_copy_duplicate_tag)
#define X12TH_COPY_DUPLICATE_TAG _(untranslated_x12th_copy_duplicate_tag)
#define X13TH_COPY_DUPLICATE_TAG _(untranslated_x13th_copy_duplicate_tag)

311 312 313 314
#define ST_COPY_DUPLICATE_TAG _(untranslated_st_copy_duplicate_tag)
#define ND_COPY_DUPLICATE_TAG _(untranslated_nd_copy_duplicate_tag)
#define RD_COPY_DUPLICATE_TAG _(untranslated_rd_copy_duplicate_tag)
#define TH_COPY_DUPLICATE_TAG _(untranslated_th_copy_duplicate_tag)
315 316

/* localizers: appended to first file copy */
317
static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s");
318
/* localizers: appended to second file copy */
319
static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s");
320 321 322 323 324 325 326 327

/* localizers: appended to x11th file copy */
static const char untranslated_x11th_copy_duplicate_format[] = N_("%s (%dth copy)%s");
/* localizers: appended to x12th file copy */
static const char untranslated_x12th_copy_duplicate_format[] = N_("%s (%dth copy)%s");
/* localizers: appended to x13th file copy */
static const char untranslated_x13th_copy_duplicate_format[] = N_("%s (%dth copy)%s");

328
/* localizers: appended to x1st file copy */
329
static const char untranslated_st_copy_duplicate_format[] = N_("%s (%dst copy)%s");
330
/* localizers: appended to x2nd file copy */
331
static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%dnd copy)%s");
332
/* localizers: appended to x3rd file copy */
333
static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%drd copy)%s");
334
/* localizers: appended to xxth file copy */
335 336 337 338
static const char untranslated_th_copy_duplicate_format[] = N_("%s (%dth copy)%s");

#define FIRST_COPY_DUPLICATE_FORMAT _(untranslated_first_copy_duplicate_format)
#define SECOND_COPY_DUPLICATE_FORMAT _(untranslated_second_copy_duplicate_format)
339 340 341 342
#define X11TH_COPY_DUPLICATE_FORMAT _(untranslated_x11th_copy_duplicate_format)
#define X12TH_COPY_DUPLICATE_FORMAT _(untranslated_x12th_copy_duplicate_format)
#define X13TH_COPY_DUPLICATE_FORMAT _(untranslated_x13th_copy_duplicate_format)

343 344 345 346
#define ST_COPY_DUPLICATE_FORMAT _(untranslated_st_copy_duplicate_format)
#define ND_COPY_DUPLICATE_FORMAT _(untranslated_nd_copy_duplicate_format)
#define RD_COPY_DUPLICATE_FORMAT _(untranslated_rd_copy_duplicate_format)
#define TH_COPY_DUPLICATE_FORMAT _(untranslated_th_copy_duplicate_format)
347 348

static char *
349
extract_string_until (const char *original, const char *until_substring)
350 351 352
{
	char *result;
	
353
	g_assert ((int) strlen (original) >= until_substring - original);
354
	g_assert (until_substring - original >= 0);
355

356 357 358
	result = g_malloc (until_substring - original + 1);
	strncpy (result, original, until_substring - original);
	result[until_substring - original] = '\0';
359 360 361 362
	
	return result;
}

363 364 365 366
/* Dismantle a file name, separating the base name, the file suffix and removing any
 * (xxxcopy), etc. string. Figure out the count that corresponds to the given
 * (xxxcopy) substring.
 */
367
static void
368 369 370 371
parse_previous_duplicate_name (const char *name,
			       char **name_base,
			       const char **suffix,
			       int *count)
372 373
{
	const char *tag;
374 375

	g_assert (name[0] != '\0');
376
	
377
	*suffix = strchr (name + 1, '.');
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
	if (*suffix == NULL || (*suffix)[1] == '\0') {
		/* no suffix */
		*suffix = "";
	}

	tag = strstr (name, COPY_DUPLICATE_TAG);
	if (tag != NULL) {
		if (tag > *suffix) {
			/* handle case "foo. (copy)" */
			*suffix = "";
		}
		*name_base = extract_string_until (name, tag);
		*count = 1;
		return;
	}


	tag = strstr (name, ANOTHER_COPY_DUPLICATE_TAG);
	if (tag != NULL) {
		if (tag > *suffix) {
			/* handle case "foo. (another copy)" */
			*suffix = "";
		}
		*name_base = extract_string_until (name, tag);
		*count = 2;
		return;
	}


407
	/* Check to see if we got one of st, nd, rd, th. */
408 409 410 411 412 413 414 415
	tag = strstr (name, X11TH_COPY_DUPLICATE_TAG);

	if (tag == NULL) {
		tag = strstr (name, X12TH_COPY_DUPLICATE_TAG);
	}
	if (tag == NULL) {
		tag = strstr (name, X13TH_COPY_DUPLICATE_TAG);
	}
416

417 418 419
	if (tag == NULL) {
		tag = strstr (name, ST_COPY_DUPLICATE_TAG);
	}
420 421 422 423 424 425 426 427 428 429
	if (tag == NULL) {
		tag = strstr (name, ND_COPY_DUPLICATE_TAG);
	}
	if (tag == NULL) {
		tag = strstr (name, RD_COPY_DUPLICATE_TAG);
	}
	if (tag == NULL) {
		tag = strstr (name, TH_COPY_DUPLICATE_TAG);
	}

430
	/* If we got one of st, nd, rd, th, fish out the duplicate number. */
431
	if (tag != NULL) {
432
		/* localizers: opening parentheses to match the "th copy)" string */
433
		tag = strstr (name, _(" ("));
434 435 436 437 438 439
		if (tag != NULL) {
			if (tag > *suffix) {
				/* handle case "foo. (22nd copy)" */
				*suffix = "";
			}
			*name_base = extract_string_until (name, tag);
440
			/* localizers: opening parentheses of the "th copy)" string */
441
			if (sscanf (tag, _(" (%d"), count) == 1) {
442 443 444 445
				if (*count < 1 || *count > 1000000) {
					/* keep the count within a reasonable range */
					*count = 0;
				}
446 447 448 449 450 451 452 453 454 455
				return;
			}
			*count = 0;
			return;
		}
	}

	
	*count = 0;
	if (**suffix != '\0') {
456 457
		*name_base = extract_string_until (name, *suffix);
	} else {
458
		*name_base = g_strdup (name);
459 460 461
	}
}

462
static char *
463
make_next_duplicate_name (const char *base, const char *suffix, int count)
464
{
465 466 467
	const char *format;
	char *result;

468 469

	if (count < 1) {
470
		g_warning ("bad count %d in get_duplicate_name", count);
471 472 473
		count = 1;
	}

474
	if (count <= 2) {
475

476 477 478 479
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
		 */
		switch (count) {
480 481 482
		default:
			g_assert_not_reached ();
			/* fall through */
483
		case 1:
484
			format = FIRST_COPY_DUPLICATE_FORMAT;
485 486
			break;
		case 2:
487
			format = SECOND_COPY_DUPLICATE_FORMAT;
488
			break;
489

490
		}
491
		result = g_strdup_printf (format, base, suffix);
492 493 494 495 496 497
	} else {

		/* Handle special cases for the first few numbers of each ten.
		 * For locales where getting this exactly right is difficult,
		 * these can just be made all the same as the general case below.
		 */
498 499 500 501 502 503

		/* Handle special cases for x11th - x20th.
		 */
		switch (count % 100) {
		case 11:
			format = X11TH_COPY_DUPLICATE_FORMAT;
504
			break;
505 506
		case 12:
			format = X12TH_COPY_DUPLICATE_FORMAT;
507
			break;
508 509
		case 13:
			format = X13TH_COPY_DUPLICATE_FORMAT;
510 511
			break;
		default:
512
			format = NULL;
513 514 515
			break;
		}

516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
		if (format == NULL) {
			switch (count % 10) {
			case 1:
				format = ST_COPY_DUPLICATE_FORMAT;
				break;
			case 2:
				format = ND_COPY_DUPLICATE_FORMAT;
				break;
			case 3:
				format = RD_COPY_DUPLICATE_FORMAT;
				break;
			default:
				/* The general case. */
				format = TH_COPY_DUPLICATE_FORMAT;
				break;
			}
		}

534
		result = g_strdup_printf (format, base, count, suffix);
535 536
	}

537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
	return result;
}

static char *
get_duplicate_name (const char *name, int count_increment)
{
	char *result;
	char *name_base;
	const char *suffix;
	int count;

	parse_previous_duplicate_name (name, &name_base, &suffix, &count);
	result = make_next_duplicate_name (name_base, suffix, count + count_increment);

	g_free (name_base);

	return result;
}

556 557 558 559
static char *
custom_full_name_to_string (char *format, va_list va)
{
	GFile *file;
560
	
561
	file = va_arg (va, GFile *);
562
	
563
	return g_file_get_parse_name (file);
564 565 566
}

static void
567
custom_full_name_skip (va_list *va)
568
{
569
	va_arg (*va, GFile *);
570 571
}

572 573
static char *
custom_basename_to_string (char *format, va_list va)
574
{
575 576 577
	GFile *file;
	GFileInfo *info;
	char *name, *basename;
578

579
	file = va_arg (va, GFile *);
580

581 582 583 584 585
	info = g_file_query_info (file,
				  G_FILE_ATTRIBUTE_STD_DISPLAY_NAME,
				  0,
				  g_cancellable_get_current (),
				  NULL);
586
	
587 588 589 590 591
	name = NULL;
	if (info) {
		name = g_strdup (g_file_info_get_display_name (info));
		g_object_unref (info);
	}
592
	
593 594 595 596 597 598 599
	if (name == NULL) {
		basename = g_file_get_basename (file);
		if (g_utf8_validate (basename, -1, NULL)) {
			name = basename;
		} else {
			name = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
			g_free (basename);
600 601
		}
	}
602 603
	
	return name;
604 605
}

606 607
static void
custom_basename_skip (va_list *va)
608
{
609 610
	va_arg (*va, GFile *);
}
611 612


613 614 615 616
static char *
custom_size_to_string (char *format, va_list va)
{
	goffset size;
617

618 619
	size = va_arg (va, goffset);
	return g_format_file_size_for_display (size);
620 621
}

622
static void
623
custom_size_skip (va_list *va)
624
{
625
	va_arg (*va, goffset);
626 627
}

628 629
static char *
custom_time_to_string (char *format, va_list va)
630
{
631
	int secs;
632

633 634 635
	secs = va_arg (va, int);
	return format_time (secs);
}
636

637 638 639 640 641
static void
custom_time_skip (va_list *va)
{
	va_arg (*va, int);
}
642

643 644 645 646 647 648 649
static EelPrintfHandler handlers[] = {
	{ 'F', custom_full_name_to_string, custom_full_name_skip },
	{ 'B', custom_basename_to_string, custom_basename_skip },
	{ 'S', custom_size_to_string, custom_size_skip },
	{ 'T', custom_time_to_string, custom_time_skip },
	{ 0 }
};
650 651


652 653 654 655
static char *
f (const char *format, ...) {
	va_list va;
	char *res;
656
	
657 658 659
	va_start (va, format);
	res = eel_strdup_vprintf_with_custom (handlers, format, va);
	va_end (va);
660

661 662
	return res;
}
663

664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
#define op_job_new(__type, parent_window) ((__type *)(init_common (sizeof(__type), parent_window)))

static gpointer
init_common (gsize job_size,
	     GtkWindow *parent_window)
{
	CommonJob *common;
	GdkScreen *screen;

	common = g_malloc0 (job_size);
	
	common->parent_window = g_object_ref (parent_window);
	common->progress = nautilus_progress_info_new ();
	common->cancellable = nautilus_progress_info_get_cancellable (common->progress);
	common->time = g_timer_new ();

	common->screen_num = 0;
	if (parent_window) {
		screen = gtk_widget_get_screen (GTK_WIDGET (parent_window));
		common->screen_num = gdk_screen_get_number (screen);
	}
	
	return common;
}

static void
finalize_common (CommonJob *common)
{
	nautilus_progress_info_finish (common->progress);

	g_timer_destroy (common->time);
	
	if (common->parent_window) {
		 g_object_unref (common->parent_window);
	}
	if (common->skip_files) {
		g_hash_table_destroy (common->skip_files);
	}
	if (common->skip_readdir_error) {
		g_hash_table_destroy (common->skip_readdir_error);
	}
	g_object_unref (common->progress);
	g_object_unref (common->cancellable);
	g_free (common);
}

static void
skip_file (CommonJob *common,
	   GFile *file)
{
	if (common->skip_files == NULL) {
		common->skip_files =
			g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
	}

	g_hash_table_insert (common->skip_files, g_object_ref (file), file);
}

static void
skip_readdir_error (CommonJob *common,
		    GFile *dir)
{
	if (common->skip_readdir_error == NULL) {
		common->skip_readdir_error =
			g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
	}

	g_hash_table_insert (common->skip_readdir_error, g_object_ref (dir), dir);
}

static gboolean
should_skip_file (CommonJob *common,
		  GFile *file)
{
	if (common->skip_files != NULL) {
		return g_hash_table_lookup (common->skip_files, file) != NULL;
	}
	return FALSE;
}

static gboolean
should_skip_readdir_error (CommonJob *common,
			   GFile *dir)
{
	if (common->skip_readdir_error != NULL) {
		return g_hash_table_lookup (common->skip_readdir_error, dir) != NULL;
	}
	return FALSE;
}

754 755 756 757 758 759 760 761 762 763
static void
setup_autos (void)
{
	static gboolean setup_autos = FALSE;
	if (!setup_autos) {
		setup_autos = TRUE;
		eel_preferences_add_auto_boolean (NAUTILUS_PREFERENCES_CONFIRM_TRASH,
						  &confirm_trash_auto_value);
	}
}
Alexander Larsson's avatar
 
Alexander Larsson committed
764

Alexander Larsson's avatar
Alexander Larsson committed
765 766 767
static gboolean
can_delete_without_confirm (GFile *file)
{
768 769
	if (g_file_has_uri_scheme (file, "burn") ||
	    g_file_has_uri_scheme (file, "x-nautilus-desktop")) {
Alexander Larsson's avatar
Alexander Larsson committed
770
		return TRUE;
Alexander Larsson's avatar
Alexander Larsson committed
771 772
	}

Alexander Larsson's avatar
Alexander Larsson committed
773
	return FALSE;
774 775
}

Alexander Larsson's avatar
Alexander Larsson committed
776 777
static gboolean
can_delete_files_without_confirm (GList *files)
778
{
Alexander Larsson's avatar
Alexander Larsson committed
779
	g_assert (files != NULL);
780

Alexander Larsson's avatar
Alexander Larsson committed
781 782 783 784
	while (files != NULL) {
		if (!can_delete_without_confirm (files->data)) {
			return FALSE;
		}
Alexander Larsson's avatar
 
Alexander Larsson committed
785

Alexander Larsson's avatar
Alexander Larsson committed
786 787
		files = files->next;
	}
Pavel Cisler's avatar
Pavel Cisler committed
788

Alexander Larsson's avatar
Alexander Larsson committed
789 790
	return TRUE;
}
791

Alexander Larsson's avatar
Alexander Larsson committed
792 793 794 795 796 797 798 799 800 801 802
typedef struct {
	GtkWindow *parent_window;
	gboolean ignore_close_box;
	GtkMessageType message_type;
	const char *primary_text;
	const char *secondary_text;
	const char *details_text;
	const char **button_titles;
	
	int result;
} RunSimpleDialogData;
803

Alexander Larsson's avatar
Alexander Larsson committed
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827
static void
do_run_simple_dialog (gpointer _data)
{
	RunSimpleDialogData *data = _data;
	const char *button_title;
        GtkWidget *dialog;
	int result;
	int response_id;

	/* Create the dialog. */
	dialog = eel_alert_dialog_new (data->parent_window, 
	                               0,
	                               data->message_type,
	                               GTK_BUTTONS_NONE,
	                               data->primary_text,
	                               data->secondary_text);

	for (response_id = 0;
	     data->button_titles[response_id] != NULL;
	     response_id++) {
		button_title = data->button_titles[response_id];
		gtk_dialog_add_button (GTK_DIALOG (dialog), button_title, response_id);
		gtk_dialog_set_default_response (GTK_DIALOG (dialog), response_id);
	}
828

Alexander Larsson's avatar
Alexander Larsson committed
829 830 831 832 833 834 835 836 837 838 839 840 841 842
	if (data->details_text) {
		eel_alert_dialog_set_details_label (EEL_ALERT_DIALOG (dialog),
						    data->details_text);
	}
	
	/* Run it. */
        gtk_widget_show (dialog);
        result = gtk_dialog_run (GTK_DIALOG (dialog));
	
	while ((result == GTK_RESPONSE_NONE || result == GTK_RESPONSE_DELETE_EVENT) && data->ignore_close_box) {
		gtk_widget_show (GTK_WIDGET (dialog));
		result = gtk_dialog_run (GTK_DIALOG (dialog));
	}
	
843 844
	gtk_object_destroy (GTK_OBJECT (dialog));

Alexander Larsson's avatar
Alexander Larsson committed
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
	data->result = result;
}

/* NOTE: This frees the primary / secondary strings, in order to
   avoid doing that everywhere. So, make sure they are strduped */

static int
run_simple_dialog_va (CommonJob *job,
		      gboolean ignore_close_box,
		      GtkMessageType message_type,
		      char *primary_text,
		      char *secondary_text,
		      const char *details_text,
		      va_list varargs)
{
	RunSimpleDialogData *data;
	int res;
	const char *button_title;
	GPtrArray *ptr_array;

	g_timer_stop (job->time);
	
	data = g_new0 (RunSimpleDialogData, 1);
	data->parent_window = GTK_WINDOW (job->parent_window);
	data->ignore_close_box = ignore_close_box;
	data->message_type = message_type;
	data->primary_text = primary_text;
	data->secondary_text = secondary_text;
	data->details_text = details_text;

	ptr_array = g_ptr_array_new ();
	while ((button_title = va_arg (varargs, const char *)) != NULL) {
		g_ptr_array_add (ptr_array, (char *)button_title);
	}
	g_ptr_array_add (ptr_array, NULL);
	data->button_titles = (const char **)g_ptr_array_free (ptr_array, FALSE);

	g_io_job_send_to_mainloop (job->io_job,
				   do_run_simple_dialog,
				   data,
				   NULL,
				   TRUE);

	res = data->result;

	g_free (data->button_titles);
	g_free (data);

	g_timer_continue (job->time);

	g_free (primary_text);
	g_free (secondary_text);
	
	return res;
}

#if 0 /* Not used at the moment */
static int
run_simple_dialog (CommonJob *job,
		   gboolean ignore_close_box,
		   GtkMessageType message_type,
		   char *primary_text,
		   char *secondary_text,
		   const char *details_text,
		   ...)
{
	va_list varargs;
	int res;

	va_start (varargs, details_text);
	res = run_simple_dialog_va (job,
				    ignore_close_box,
				    message_type,
				    primary_text,
				    secondary_text,
				    details_text,
				    varargs);
	va_end (varargs);
	return res;
}
#endif

static int
run_error (CommonJob *job,
	   char *primary_text,
	   char *secondary_text,
	   const char *details_text,
	   ...)
{
	va_list varargs;
	int res;

	va_start (varargs, details_text);
	res = run_simple_dialog_va (job,
				    FALSE,
				    GTK_MESSAGE_ERROR,
				    primary_text,
				    secondary_text,
				    details_text,
				    varargs);
	va_end (varargs);
	return res;
}

static int
run_warning (CommonJob *job,
	     char *primary_text,
	     char *secondary_text,
	     const char *details_text,
	     ...)
{
	va_list varargs;
	int res;

	va_start (varargs, details_text);
	res = run_simple_dialog_va (job,
				    FALSE,
				    GTK_MESSAGE_WARNING,
				    primary_text,
				    secondary_text,
				    details_text,
				    varargs);
	va_end (varargs);
	return res;
}

971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
static int
run_question (CommonJob *job,
	      char *primary_text,
	      char *secondary_text,
	      const char *details_text,
	      ...)
{
	va_list varargs;
	int res;

	va_start (varargs, details_text);
	res = run_simple_dialog_va (job,
				    FALSE,
				    GTK_MESSAGE_QUESTION,
				    primary_text,
				    secondary_text,
				    details_text,
				    varargs);
	va_end (varargs);
	return res;
}

993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
static void
abort_job (CommonJob *job)
{
	g_cancellable_cancel (job->cancellable);
	
}

static gboolean
job_aborted (CommonJob *job)
{
	return g_cancellable_is_cancelled (job->cancellable);
}

Alexander Larsson's avatar
Alexander Larsson committed
1006
static gboolean
1007
confirm_delete_from_trash (CommonJob *job,
Alexander Larsson's avatar
Alexander Larsson committed
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022
			   GList *files)
{
	char *prompt;
	int file_count;
	int response;

	/* Just Say Yes if the preference says not to confirm. */
	if (!confirm_trash_auto_value) {
		return TRUE;
	}

	file_count = g_list_length (files);
	g_assert (file_count > 0);
	
	if (file_count == 1) {
1023 1024
		prompt = f (_("Are you sure you want to permanently delete \"%B\" "
					    "from the trash?"), files->data);
Alexander Larsson's avatar
Alexander Larsson committed
1025
	} else {
1026 1027 1028 1029 1030 1031
		prompt = f (ngettext("Are you sure you want to permanently delete "
				     "the %d selected item from the trash?",
				     "Are you sure you want to permanently delete "
				     "the %d selected items from the trash?",
				     file_count), 
			    file_count);
Alexander Larsson's avatar
Alexander Larsson committed
1032 1033
	}

1034 1035 1036 1037 1038 1039
	response = run_warning (job,
				prompt,
				f (_("If you delete an item, it will be permanently lost.")),
				NULL,
				GTK_STOCK_CANCEL, GTK_STOCK_DELETE,
				NULL);
Alexander Larsson's avatar
Alexander Larsson committed
1040 1041 1042 1043 1044
	
	return (response == 1);
}

static gboolean
1045
confirm_delete_directly (CommonJob *job,
Alexander Larsson's avatar
Alexander Larsson committed
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
			 GList *files)
{
	char *prompt;
	int file_count;
	int response;

	/* Just Say Yes if the preference says not to confirm. */
	if (!confirm_trash_auto_value) {
		return TRUE;
	}

	file_count = g_list_length (files);
	g_assert (file_count > 0);

	if (can_delete_files_without_confirm (files)) {
		return TRUE;
	}

	if (file_count == 1) {
1065 1066
		prompt = f (_("Are you sure you want to permanently delete \"%B\"?"), 
			    files->data);
Alexander Larsson's avatar
Alexander Larsson committed
1067
	} else {
1068 1069 1070 1071 1072
		prompt = f (ngettext("Are you sure you want to permanently delete "
				     "the %d selected item?",
				     "Are you sure you want to permanently delete "
				     "the %d selected items?", file_count),
			    file_count);
Alexander Larsson's avatar
Alexander Larsson committed
1073
	}
1074 1075 1076 1077 1078 1079 1080
	
	response = run_warning (job, 
				prompt,
				f (_("If you delete an item, it will be permanently lost.")),
				NULL,
				GTK_STOCK_CANCEL, GTK_STOCK_DELETE,
				NULL);
Alexander Larsson's avatar
Alexander Larsson committed
1081 1082 1083 1084

	return response == 1;
}

1085 1086
static void
report_delete_progress (CommonJob *job,
1087 1088
			SourceInfo *source_info,
			TransferInfo *transfer_info)
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
{
	int files_left;
	double elapsed, transfer_rate;
	int remaining_time;
	guint64 now;

	now = g_thread_gettime ();
	if (transfer_info->last_report_time != 0 &&
	    ABS (transfer_info->last_report_time - now) < 100 * NSEC_PER_MSEC) {
		return;
	}
	transfer_info->last_report_time = now;
	
	files_left = source_info->num_files - transfer_info->num_files;

	/* Races and whatnot could cause this to be negative... */
	if (files_left < 0) {
		files_left = 1;
	}

	nautilus_progress_info_take_status (job->progress,
					    f (_("Deleting files")));

	elapsed = g_timer_elapsed (job->time, NULL);
	if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE) {
		char *s;
		s = f (ngettext ("%d file left to delete",
				 "%d files left to delete",
				 files_left),
		       files_left);
		nautilus_progress_info_take_details (job->progress, s);
	} else {
		char *s;
		transfer_rate = transfer_info->num_files / elapsed;
		remaining_time = files_left / transfer_rate;

		/* To translators: %T will expand to a time like "2 minutes" */		
		s = f (ngettext ("%d file left to delete \xE2\x80\x94 %T left",
				 "%d files left to delete \xE2\x80\x94 %T left",
				 files_left),
		       files_left, remaining_time);
		nautilus_progress_info_take_details (job->progress, s);
	}

1133 1134 1135
	if (source_info->num_files != 0) {
		nautilus_progress_info_set_progress (job->progress, (double)transfer_info->num_files / source_info->num_files);
	}
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
}

static void delete_file (CommonJob *job, GFile *file,
			 gboolean *skipped_file,
			 SourceInfo *source_info,
			 TransferInfo *transfer_info,
			 gboolean toplevel);

static void
delete_dir (CommonJob *job, GFile *dir,
	    gboolean *skipped_file,
	    SourceInfo *source_info,
	    TransferInfo *transfer_info,
	    gboolean toplevel)
{
	GFileInfo *info;
	GError *error;
	GFile *file;
	GFileEnumerator *enumerator;
	char *primary, *secondary, *details;
	int response;
	gboolean skip_error;
	gboolean local_skipped_file;

	local_skipped_file = FALSE;
	
	skip_error = should_skip_readdir_error (job, dir);
 retry:
	error = NULL;
	enumerator = g_file_enumerate_children (dir,
						G_FILE_ATTRIBUTE_STD_NAME,
						G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
						job->cancellable,
						&error);
	if (enumerator) {
		error = NULL;
		
		while (!job_aborted (job) &&
		       (info = g_file_enumerator_next_file (enumerator, job->cancellable, skip_error?NULL:&error)) != NULL) {
			file = g_file_get_child (dir,
						 g_file_info_get_name (info));
			delete_file (job, file, &local_skipped_file, source_info, transfer_info, FALSE);
Alexander Larsson's avatar
Alexander Larsson committed
1178
			g_object_unref (file);
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197