nautilus-file-operations.c 129 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"
Alexander Larsson's avatar
Alexander Larsson committed
38
#include "nautilus-file-changes-queue.h"
39
#include "nautilus-lib-self-check-functions.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
40

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

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

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 <gio/gio.h>
Alexander Larsson's avatar
Alexander Larsson committed
58
#include <glib/gurifuncs.h>
59
#include "nautilus-file-changes-queue.h"
Alexander Larsson's avatar
Alexander Larsson committed
60 61 62
#include "nautilus-file-private.h"
#include "nautilus-desktop-icon-file.h"
#include "nautilus-desktop-link-monitor.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
63 64
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
65
#include "nautilus-autorun.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
66
#include "nautilus-trash-monitor.h"
67
#include "nautilus-file-utilities.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
68

Alexander Larsson's avatar
Alexander Larsson committed
69 70
static gboolean confirm_trash_auto_value;

71
/* TODO: TESTING!!! */
Alexander Larsson's avatar
Alexander Larsson committed
72 73

typedef struct {
74
	GIOSchedulerJob *io_job;	
Alexander Larsson's avatar
Alexander Larsson committed
75
	GTimer *time;
76 77
	GtkWindow *parent_window;
	int screen_num;
Alexander Larsson's avatar
Alexander Larsson committed
78 79 80 81 82 83 84 85
	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;
86
	gboolean delete_all;
Alexander Larsson's avatar
Alexander Larsson committed
87 88 89 90 91 92 93 94 95 96 97 98 99 100
} 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;

101 102 103 104
typedef struct {
	CommonJob common;
	GList *files;
	gboolean try_trash;
105
	gboolean user_cancel;
106 107 108 109
	NautilusDeleteCallback done_callback;
	gpointer done_callback_data;
} DeleteJob;

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
typedef struct {
	CommonJob common;
	GFile *dest_dir;
	char *filename;
	gboolean make_dir;
	GFile *src;
	char *src_data;
	GdkPoint position;
	gboolean has_position;
	GFile *created_file;
	NautilusCreateCallback done_callback;
	gpointer done_callback_data;
} CreateJob;


Alexander Larsson's avatar
Alexander Larsson committed
125 126
typedef struct {
	CommonJob common;
127
	GList *trash_dirs;
128
	gboolean should_confirm;
129 130
	NautilusOpCallback done_callback;
	gpointer done_callback_data;
Alexander Larsson's avatar
Alexander Larsson committed
131
} EmptyTrashJob;
132

133 134 135 136 137 138 139 140 141 142 143
typedef struct {
	CommonJob common;
	GFile *file;
	NautilusOpCallback done_callback;
	gpointer done_callback_data;
	guint32 file_permissions;
	guint32 file_mask;
	guint32 dir_permissions;
	guint32 dir_mask;
} SetPermissionsJob;

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
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;
163
	int last_reported_files_left;
164 165
} TransferInfo;

Alexander Larsson's avatar
Alexander Larsson committed
166 167 168 169 170 171 172 173 174
#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")
175
#define DELETE_ALL _("Delete _All")
Alexander Larsson's avatar
Alexander Larsson committed
176 177 178 179 180
#define REPLACE _("_Replace")
#define REPLACE_ALL _("Replace _All")
#define MERGE _("_Merge")
#define MERGE_ALL _("Merge _All")

181 182 183 184 185 186
static void scan_sources (GList *files,
			  SourceInfo *source_info,
			  CommonJob *job,
			  OpKind kind);


187 188 189
static gboolean empty_trash_job (GIOSchedulerJob *io_job,
				 GCancellable *cancellable,
				 gpointer user_data);
190

Alexander Larsson's avatar
Alexander Larsson committed
191 192 193 194 195 196 197 198 199 200 201 202 203
static char *
format_time (int seconds)
{
	int minutes;
	int hours;
	char *res;

	if (seconds < 0) {
		/* Just to make sure... */
		seconds = 0;
	}
	
	if (seconds < 60) {
204
		return g_strdup_printf (ngettext ("%'d second","%'d seconds", (int) seconds), (int) seconds);
Alexander Larsson's avatar
Alexander Larsson committed
205 206 207 208
	}

	if (seconds < 60*60) {
		minutes = (seconds + 30) / 60;
209
		return g_strdup_printf (ngettext ("%'d minute", "%'d minutes", minutes), minutes);
Alexander Larsson's avatar
Alexander Larsson committed
210 211 212 213 214 215 216 217 218
	}

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

		minutes = (seconds - hours * 60 * 60 + 30) / 60;
		
219 220
		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
221 222 223 224 225 226
		res = g_strconcat (h, ", ", m, NULL);
		g_free (h);
		g_free (m);
		return res;
	}
	
227 228
	return g_strdup_printf (ngettext ("approximately %'d hour",
					  "approximately %'d hours",
229
					  hours), hours);
Alexander Larsson's avatar
Alexander Larsson committed
230 231
}

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
static char *
shorten_utf8_string (const char *base, int reduce_by_num_bytes)
{
	int len;
	char *ret;
	const char *p;
	
	len = strlen (base);
	len -= reduce_by_num_bytes;
	
	if (len <= 0) {
		return NULL;
	}

	ret = g_new (char, len + 1);

	p = base;
	while (len) {
		char *next;
		next = g_utf8_next_char (p);
		if (next - p > len || *next == '\0') {
			break;
		}
		
		len -= next - p;
		p = next;
	}
	
	if (p - base == 0) {
		g_free (ret);
		return NULL;
	} else {
		memcpy (ret, base, p - base);
		ret[p - base] = '\0';
		return ret;
	}
}

270
/* Note that we have these two separate functions with separate format
271
 * strings for ease of localization.
272 273 274
 */

static char *
275
get_link_name (const char *name, int count, int max_length)
276
{
277
	const char *format;
Alexander Larsson's avatar
Alexander Larsson committed
278
	char *result;
279 280
	int unshortened_length;
	gboolean use_count;
281
	
282 283 284 285 286 287 288
	g_assert (name != NULL);

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

289 290 291
	if (count <= 2) {
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
292
		 */
293
		switch (count) {
294 295 296
		default:
			g_assert_not_reached ();
			/* fall through */
297
		case 1:
298
			/* appended to new link file */
299
			format = _("Link to %s");
300 301
			break;
		case 2:
302
			/* appended to new link file */
303
			format = _("Another link to %s");
304 305 306
			break;
		}

307
		use_count = FALSE;
308 309 310 311
	} 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.
312
		 */
313 314 315 316 317 318
		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.
			 */
319
			format = _("%'dst link to %s");
320 321
			break;
		case 2:
322
			/* appended to new link file */
323
			format = _("%'dnd link to %s");
324 325
			break;
		case 3:
326
			/* appended to new link file */
327
			format = _("%'drd link to %s");
328 329
			break;
		default:
330
			/* appended to new link file */
331
			format = _("%'dth link to %s");
332 333
			break;
		}
334 335 336 337 338

		use_count = TRUE;
	}

	if (use_count)
Alexander Larsson's avatar
Alexander Larsson committed
339
		result = g_strdup_printf (format, count, name);
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
	else
		result = g_strdup_printf (format, name);

	if (max_length > 0 && (unshortened_length = strlen (result)) > max_length) {
		char *new_name;

		new_name = shorten_utf8_string (name, unshortened_length - max_length);
		if (new_name) {
			g_free (result);

			if (use_count)
				result = g_strdup_printf (format, count, new_name);
			else
				result = g_strdup_printf (format, new_name);

			g_assert (strlen (result) <= max_length);
			g_free (new_name);
		}
358
	}
359

360
	return result;
361 362
}

Alexander Larsson's avatar
Alexander Larsson committed
363

364 365 366 367 368
/* Localizers: 
 * Feel free to leave out the st, nd, rd and th suffix or
 * make some or all of them match.
 */

369
/* localizers: tag used to detect the first copy of a file */
370
static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
371
/* localizers: tag used to detect the second copy of a file */
372
static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)");
373 374 375 376 377 378 379 380

/* 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)");

381
/* localizers: tag used to detect the x1st copy of a file */
382
static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)");
383
/* localizers: tag used to detect the x2nd copy of a file */
384
static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)");
385
/* localizers: tag used to detect the x3rd copy of a file */
386
static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)");
387

388
/* localizers: tag used to detect the xxth copy of a file */
389 390 391 392
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)
393 394 395 396
#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)

397 398 399 400
#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)
401 402

/* localizers: appended to first file copy */
403
static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s");
404
/* localizers: appended to second file copy */
405
static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s");
406 407

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

414
/* localizers: appended to x1st file copy */
415
static const char untranslated_st_copy_duplicate_format[] = N_("%s (%'dst copy)%s");
416
/* localizers: appended to x2nd file copy */
417
static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%'dnd copy)%s");
418
/* localizers: appended to x3rd file copy */
419
static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%'drd copy)%s");
420
/* localizers: appended to xxth file copy */
421
static const char untranslated_th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
422 423 424

#define FIRST_COPY_DUPLICATE_FORMAT _(untranslated_first_copy_duplicate_format)
#define SECOND_COPY_DUPLICATE_FORMAT _(untranslated_second_copy_duplicate_format)
425 426 427 428
#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)

429 430 431 432
#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)
433 434

static char *
435
extract_string_until (const char *original, const char *until_substring)
436 437 438
{
	char *result;
	
439
	g_assert ((int) strlen (original) >= until_substring - original);
440
	g_assert (until_substring - original >= 0);
441

442 443 444
	result = g_malloc (until_substring - original + 1);
	strncpy (result, original, until_substring - original);
	result[until_substring - original] = '\0';
445 446 447 448
	
	return result;
}

449 450 451 452
/* 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.
 */
453
static void
454 455 456 457
parse_previous_duplicate_name (const char *name,
			       char **name_base,
			       const char **suffix,
			       int *count)
458 459
{
	const char *tag;
460 461

	g_assert (name[0] != '\0');
462
	
463
	*suffix = strchr (name + 1, '.');
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
	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;
	}


493
	/* Check to see if we got one of st, nd, rd, th. */
494 495 496 497 498 499 500 501
	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);
	}
502

503 504 505
	if (tag == NULL) {
		tag = strstr (name, ST_COPY_DUPLICATE_TAG);
	}
506 507 508 509 510 511 512 513 514 515
	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);
	}

516
	/* If we got one of st, nd, rd, th, fish out the duplicate number. */
517
	if (tag != NULL) {
518
		/* localizers: opening parentheses to match the "th copy)" string */
519
		tag = strstr (name, _(" ("));
520 521 522 523 524 525
		if (tag != NULL) {
			if (tag > *suffix) {
				/* handle case "foo. (22nd copy)" */
				*suffix = "";
			}
			*name_base = extract_string_until (name, tag);
526
			/* localizers: opening parentheses of the "th copy)" string */
527
			if (sscanf (tag, _(" (%'d"), count) == 1) {
528 529 530 531
				if (*count < 1 || *count > 1000000) {
					/* keep the count within a reasonable range */
					*count = 0;
				}
532 533 534 535 536 537 538 539 540 541
				return;
			}
			*count = 0;
			return;
		}
	}

	
	*count = 0;
	if (**suffix != '\0') {
542 543
		*name_base = extract_string_until (name, *suffix);
	} else {
544
		*name_base = g_strdup (name);
545 546 547
	}
}

548
static char *
549
make_next_duplicate_name (const char *base, const char *suffix, int count, int max_length)
550
{
551 552
	const char *format;
	char *result;
553 554
	int unshortened_length;
	gboolean use_count;
555 556

	if (count < 1) {
557
		g_warning ("bad count %d in get_duplicate_name", count);
558 559 560
		count = 1;
	}

561
	if (count <= 2) {
562

563 564 565 566
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
		 */
		switch (count) {
567 568 569
		default:
			g_assert_not_reached ();
			/* fall through */
570
		case 1:
571
			format = FIRST_COPY_DUPLICATE_FORMAT;
572 573
			break;
		case 2:
574
			format = SECOND_COPY_DUPLICATE_FORMAT;
575
			break;
576

577
		}
578 579

		use_count = FALSE;
580 581 582 583 584 585
	} 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.
		 */
586 587 588 589 590 591

		/* Handle special cases for x11th - x20th.
		 */
		switch (count % 100) {
		case 11:
			format = X11TH_COPY_DUPLICATE_FORMAT;
592
			break;
593 594
		case 12:
			format = X12TH_COPY_DUPLICATE_FORMAT;
595
			break;
596 597
		case 13:
			format = X13TH_COPY_DUPLICATE_FORMAT;
598 599
			break;
		default:
600
			format = NULL;
601 602 603
			break;
		}

604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
		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;
			}
		}

622 623 624 625 626
		use_count = TRUE;

	}

	if (use_count)
627
		result = g_strdup_printf (format, base, count, suffix);
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
	else
		result = g_strdup_printf (format, base, suffix);

	if (max_length > 0 && (unshortened_length = strlen (result)) > max_length) {
		char *new_base;

		new_base = shorten_utf8_string (base, unshortened_length - max_length);
		if (new_base) {
			g_free (result);

			if (use_count)
				result = g_strdup_printf (format, new_base, count, suffix);
			else
				result = g_strdup_printf (format, new_base, suffix);

			g_assert (strlen (result) <= max_length);
			g_free (new_base);
		}
646 647
	}

648 649 650 651
	return result;
}

static char *
652
get_duplicate_name (const char *name, int count_increment, int max_length)
653 654 655 656 657 658 659
{
	char *result;
	char *name_base;
	const char *suffix;
	int count;

	parse_previous_duplicate_name (name, &name_base, &suffix, &count);
660
	result = make_next_duplicate_name (name_base, suffix, count + count_increment, max_length);
661 662 663 664 665 666

	g_free (name_base);

	return result;
}

667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
static gboolean
has_invalid_xml_char (char *str)
{
	gunichar c;

	while (*str != 0) {
		c = g_utf8_get_char (str);
		/* characters XML permits */
		if (!(c == 0x9 ||
		      c == 0xA ||
		      c == 0xD ||
		      (c >= 0x20 && c <= 0xD7FF) ||
		      (c >= 0xE000 && c <= 0xFFFD) ||
		      (c >= 0x10000 && c <= 0x10FFFF))) {
			return TRUE;
		}
		str = g_utf8_next_char (str);
	}
	return FALSE;
}


689 690 691 692
static char *
custom_full_name_to_string (char *format, va_list va)
{
	GFile *file;
693
	
694
	file = va_arg (va, GFile *);
695
	
696
	return g_file_get_parse_name (file);
697 698 699
}

static void
700
custom_full_name_skip (va_list *va)
701
{
702
	(void) va_arg (*va, GFile *);
703 704
}

705 706
static char *
custom_basename_to_string (char *format, va_list va)
707
{
708 709
	GFile *file;
	GFileInfo *info;
710
	char *name, *basename, *tmp;
711

712
	file = va_arg (va, GFile *);
713

714
	info = g_file_query_info (file,
715
				  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
716 717 718
				  0,
				  g_cancellable_get_current (),
				  NULL);
719
	
720 721 722 723 724
	name = NULL;
	if (info) {
		name = g_strdup (g_file_info_get_display_name (info));
		g_object_unref (info);
	}
725
	
726 727 728 729 730 731 732
	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);
733 734
		}
	}
735 736 737 738 739 740 741

	/* Some chars can't be put in the markup we use for the dialogs... */
	if (has_invalid_xml_char (name)) {
		tmp = name;
		name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
		g_free (tmp);
	}
742 743
	
	return name;
744 745
}

746 747
static void
custom_basename_skip (va_list *va)
748
{
749
	(void) va_arg (*va, GFile *);
750
}
751 752


753 754 755 756
static char *
custom_size_to_string (char *format, va_list va)
{
	goffset size;
757

758
	size = va_arg (va, goffset);
759
	return g_format_size_for_display (size);
760 761
}

762
static void
763
custom_size_skip (va_list *va)
764
{
765
	(void) va_arg (*va, goffset);
766 767
}

768 769
static char *
custom_time_to_string (char *format, va_list va)
770
{
771
	int secs;
772

773 774 775
	secs = va_arg (va, int);
	return format_time (secs);
}
776

777 778 779
static void
custom_time_skip (va_list *va)
{
780
	(void) va_arg (*va, int);
781
}
782

783
static char *
784
custom_mount_to_string (char *format, va_list va)
785
{
786
	GMount *mount;
787

788 789
	mount = va_arg (va, GMount *);
	return g_mount_get_name (mount);
790 791 792
}

static void
793
custom_mount_skip (va_list *va)
794
{
795
	(void) va_arg (*va, GMount *);
796 797 798
}


799 800 801 802 803
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 },
804
	{ 'V', custom_mount_to_string, custom_mount_skip },
805 806
	{ 0 }
};
807 808


809 810 811 812
static char *
f (const char *format, ...) {
	va_list va;
	char *res;
813
	
814 815 816
	va_start (va, format);
	res = eel_strdup_vprintf_with_custom (handlers, format, va);
	va_end (va);
817

818 819
	return res;
}
820

821 822 823 824 825 826 827 828 829 830
#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);
831 832

	if (parent_window) {
833 834
		common->parent_window = parent_window;
		eel_add_weak_pointer (&common->parent_window);
835
	}
836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
	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);
855 856

	eel_remove_weak_pointer (&common->parent_window);
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
	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;
}

912 913 914 915 916 917 918 919 920 921
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
922

Alexander Larsson's avatar
Alexander Larsson committed
923 924 925
static gboolean
can_delete_without_confirm (GFile *file)
{
926 927
	if (g_file_has_uri_scheme (file, "burn") ||
	    g_file_has_uri_scheme (file, "x-nautilus-desktop")) {
Alexander Larsson's avatar
Alexander Larsson committed
928
		return TRUE;
Alexander Larsson's avatar
Alexander Larsson committed
929 930
	}

Alexander Larsson's avatar
Alexander Larsson committed
931
	return FALSE;
932 933
}

Alexander Larsson's avatar
Alexander Larsson committed
934 935
static gboolean
can_delete_files_without_confirm (GList *files)
936
{
Alexander Larsson's avatar
Alexander Larsson committed
937
	g_assert (files != NULL);
938

Alexander Larsson's avatar
Alexander Larsson committed
939 940 941 942
	while (files != NULL) {
		if (!can_delete_without_confirm (files->data)) {
			return FALSE;
		}
Alexander Larsson's avatar
 
Alexander Larsson committed
943

Alexander Larsson's avatar
Alexander Larsson committed
944 945
		files = files->next;
	}
Pavel Cisler's avatar
Pavel Cisler committed
946

Alexander Larsson's avatar
Alexander Larsson committed
947 948
	return TRUE;
}
949

Alexander Larsson's avatar
Alexander Larsson committed
950
typedef struct {
951
	GtkWindow **parent_window;
Alexander Larsson's avatar
Alexander Larsson committed
952 953 954 955 956 957 958 959 960
	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;
961

962
static gboolean
Alexander Larsson's avatar
Alexander Larsson committed
963 964 965 966 967 968 969 970 971
do_run_simple_dialog (gpointer _data)
{
	RunSimpleDialogData *data = _data;
	const char *button_title;
        GtkWidget *dialog;
	int result;
	int response_id;

	/* Create the dialog. */
972
	dialog = eel_alert_dialog_new (*data->parent_window, 
Alexander Larsson's avatar
Alexander Larsson committed
973 974 975 976 977 978 979 980 981 982 983 984 985
	                               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);
	}
986

Alexander Larsson's avatar
Alexander Larsson committed
987 988 989 990 991 992 993 994 995 996 997 998 999 1000
	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));
	}
	
1001 1002
	gtk_object_destroy (GTK_OBJECT (dialog));

Alexander Larsson's avatar
Alexander Larsson committed
1003
	data->result = result;
1004 1005
	
	return FALSE;
Alexander Larsson's avatar
Alexander Larsson committed
1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
}

/* 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