nautilus-file-operations.c 162 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 36 37 38
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

39
#include "nautilus-file-operations.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
40

Alexander Larsson's avatar
Alexander Larsson committed
41
#include "nautilus-file-changes-queue.h"
42
#include "nautilus-lib-self-check-functions.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
43

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

Ramiro Estrugo's avatar
Ramiro Estrugo committed
46 47 48
#include <eel/eel-glib-extensions.h>
#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/gi18n.h>
52
#include <glib/gstdio.h>
53
#include <gdk/gdk.h>
54
#include <gtk/gtk.h>
55
#include <gio/gio.h>
56
#include <glib.h>
57
#include "nautilus-file-changes-queue.h"
Alexander Larsson's avatar
Alexander Larsson committed
58 59 60
#include "nautilus-file-private.h"
#include "nautilus-desktop-icon-file.h"
#include "nautilus-desktop-link-monitor.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
61 62 63
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
#include "nautilus-trash-monitor.h"
64
#include "nautilus-file-utilities.h"
65
#include "nautilus-file-conflict-dialog.h"
Amos Brocco's avatar
Amos Brocco committed
66 67
#include "nautilus-file-undo-operations.h"
#include "nautilus-file-undo-manager.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
68

69
/* TODO: TESTING!!! */
Alexander Larsson's avatar
Alexander Larsson committed
70 71

typedef struct {
72
	GIOSchedulerJob *io_job;	
Alexander Larsson's avatar
Alexander Larsson committed
73
	GTimer *time;
74 75
	GtkWindow *parent_window;
	int screen_num;
76
	int inhibit_cookie;
Alexander Larsson's avatar
Alexander Larsson committed
77 78 79 80
	NautilusProgressInfo *progress;
	GCancellable *cancellable;
	GHashTable *skip_files;
	GHashTable *skip_readdir_error;
81
	NautilusFileUndoInfo *undo_info;
Alexander Larsson's avatar
Alexander Larsson committed
82 83 84 85
	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
} CommonJob;

typedef struct {
	CommonJob common;
	gboolean is_move;
	GList *files;
	GFile *destination;
94
	GFile *desktop_location;
95
	GFile *fake_display_source;
Alexander Larsson's avatar
Alexander Larsson committed
96 97 98
	GdkPoint *icon_positions;
	int n_icon_positions;
	GHashTable *debuting_files;
99
	gchar *target_name;
Alexander Larsson's avatar
Alexander Larsson committed
100 101 102 103
	NautilusCopyCallback  done_callback;
	gpointer done_callback_data;
} CopyMoveJob;

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

113 114 115 116 117 118 119
typedef struct {
	CommonJob common;
	GFile *dest_dir;
	char *filename;
	gboolean make_dir;
	GFile *src;
	char *src_data;
James Dietrich's avatar
James Dietrich committed
120
	int length;
121 122 123 124 125 126 127 128
	GdkPoint position;
	gboolean has_position;
	GFile *created_file;
	NautilusCreateCallback done_callback;
	gpointer done_callback_data;
} CreateJob;


Alexander Larsson's avatar
Alexander Larsson committed
129 130
typedef struct {
	CommonJob common;
131
	GList *trash_dirs;
132
	gboolean should_confirm;
133 134
	NautilusOpCallback done_callback;
	gpointer done_callback_data;
Alexander Larsson's avatar
Alexander Larsson committed
135
} EmptyTrashJob;
136

137 138 139
typedef struct {
	CommonJob common;
	GFile *file;
140
	gboolean interactive;
141 142 143 144
	NautilusOpCallback done_callback;
	gpointer done_callback_data;
} MarkTrustedJob;

145 146 147 148 149 150 151 152 153 154 155
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;

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
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;
175
	int last_reported_files_left;
176 177
} TransferInfo;

Alexander Larsson's avatar
Alexander Larsson committed
178
#define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 15
179
#define NSEC_PER_MICROSEC 1000
Alexander Larsson's avatar
Alexander Larsson committed
180

181 182
#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50

Alexander Larsson's avatar
Alexander Larsson committed
183 184
#define IS_IO_ERROR(__error, KIND) (((__error)->domain == G_IO_ERROR && (__error)->code == G_IO_ERROR_ ## KIND))

185
#define CANCEL _("_Cancel")
Alexander Larsson's avatar
Alexander Larsson committed
186 187 188
#define SKIP _("_Skip")
#define SKIP_ALL _("S_kip All")
#define RETRY _("_Retry")
189
#define DELETE _("_Delete")
190
#define DELETE_ALL _("Delete _All")
Alexander Larsson's avatar
Alexander Larsson committed
191 192 193 194
#define REPLACE _("_Replace")
#define REPLACE_ALL _("Replace _All")
#define MERGE _("_Merge")
#define MERGE_ALL _("Merge _All")
195
#define COPY_FORCE _("Copy _Anyway")
Alexander Larsson's avatar
Alexander Larsson committed
196

197 198 199 200 201 202
static void
mark_desktop_file_trusted (CommonJob *common,
			   GCancellable *cancellable,
			   GFile *file,
			   gboolean interactive);

203 204 205 206 207 208 209 210 211 212 213
static gboolean
is_all_button_text (const char *button_text)
{
	g_assert (button_text != NULL);

	return !strcmp (button_text, SKIP_ALL) ||
	       !strcmp (button_text, REPLACE_ALL) ||
	       !strcmp (button_text, DELETE_ALL) ||
	       !strcmp (button_text, MERGE_ALL);
}

214 215 216 217 218 219
static void scan_sources (GList *files,
			  SourceInfo *source_info,
			  CommonJob *job,
			  OpKind kind);


220 221 222
static gboolean empty_trash_job (GIOSchedulerJob *io_job,
				 GCancellable *cancellable,
				 gpointer user_data);
223

224 225 226
static char * query_fs_type (GFile *file,
			     GCancellable *cancellable);

227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
/* keep in time with format_time()
 *
 * This counts and outputs the number of “time units”
 * formatted and displayed by format_time().
 * For instance, if format_time outputs “3 hours, 4 minutes”
 * it yields 7.
 */
static int
seconds_count_format_time_units (int seconds)
{
	int minutes;
	int hours;

	if (seconds < 0) {
		/* Just to make sure... */
		seconds = 0;
	}

	if (seconds < 60) {
		/* seconds */
		return seconds;
	}

	if (seconds < 60*60) {
		/* minutes */
252
		minutes = seconds / 60;
253 254 255 256 257 258 259
		return minutes;
	}

	hours = seconds / (60*60);

	if (seconds < 60*60*4) {
		/* minutes + hours */
260
		minutes = (seconds - hours * 60 * 60) / 60;
261 262 263 264 265 266
		return minutes + hours;
	}

	return hours;
}

Alexander Larsson's avatar
Alexander Larsson committed
267 268 269 270 271 272 273 274 275 276 277 278 279
static char *
format_time (int seconds)
{
	int minutes;
	int hours;
	char *res;

	if (seconds < 0) {
		/* Just to make sure... */
		seconds = 0;
	}
	
	if (seconds < 60) {
280
		return g_strdup_printf (ngettext ("%'d second","%'d seconds", (int) seconds), (int) seconds);
Alexander Larsson's avatar
Alexander Larsson committed
281 282 283
	}

	if (seconds < 60*60) {
284
		minutes = seconds / 60;
285
		return g_strdup_printf (ngettext ("%'d minute", "%'d minutes", minutes), minutes);
Alexander Larsson's avatar
Alexander Larsson committed
286 287 288 289 290 291 292
	}

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

293
		minutes = (seconds - hours * 60 * 60) / 60;
Alexander Larsson's avatar
Alexander Larsson committed
294
		
295 296
		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
297 298 299 300 301 302
		res = g_strconcat (h, ", ", m, NULL);
		g_free (h);
		g_free (m);
		return res;
	}
	
303 304
	return g_strdup_printf (ngettext ("approximately %'d hour",
					  "approximately %'d hours",
305
					  hours), hours);
Alexander Larsson's avatar
Alexander Larsson committed
306 307
}

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
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;
	}
}

346
/* Note that we have these two separate functions with separate format
347
 * strings for ease of localization.
348 349 350
 */

static char *
351
get_link_name (const char *name, int count, int max_length)
352
{
353
	const char *format;
Alexander Larsson's avatar
Alexander Larsson committed
354
	char *result;
355 356
	int unshortened_length;
	gboolean use_count;
357
	
358 359
	g_assert (name != NULL);

360
	if (count < 0) {
361
		g_warning ("bad count in get_link_name");
362
		count = 0;
363 364
	}

365 366 367
	if (count <= 2) {
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
368
		 */
369
		switch (count) {
370 371 372
		default:
			g_assert_not_reached ();
			/* fall through */
373 374
		case 0:
			/* duplicate original file name */
375
			format = "%s";
376
			break;
377
		case 1:
378
			/* appended to new link file */
379
			format = _("Link to %s");
380 381
			break;
		case 2:
382
			/* appended to new link file */
383
			format = _("Another link to %s");
384 385 386
			break;
		}

387
		use_count = FALSE;
388 389 390 391
	} 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.
392
		 */
393 394 395 396 397 398
		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.
			 */
399
			format = _("%'dst link to %s");
400 401
			break;
		case 2:
402
			/* appended to new link file */
403
			format = _("%'dnd link to %s");
404 405
			break;
		case 3:
406
			/* appended to new link file */
407
			format = _("%'drd link to %s");
408 409
			break;
		default:
410
			/* appended to new link file */
411
			format = _("%'dth link to %s");
412 413
			break;
		}
414 415 416 417 418

		use_count = TRUE;
	}

	if (use_count)
Alexander Larsson's avatar
Alexander Larsson committed
419
		result = g_strdup_printf (format, count, name);
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
	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);
		}
438
	}
439

440
	return result;
441 442
}

Alexander Larsson's avatar
Alexander Larsson committed
443

444 445 446 447 448
/* Localizers: 
 * Feel free to leave out the st, nd, rd and th suffix or
 * make some or all of them match.
 */

449
/* localizers: tag used to detect the first copy of a file */
450
static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
451
/* localizers: tag used to detect the second copy of a file */
452
static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)");
453 454 455 456 457 458 459 460

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

461
/* localizers: tag used to detect the x1st copy of a file */
462
static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)");
463
/* localizers: tag used to detect the x2nd copy of a file */
464
static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)");
465
/* localizers: tag used to detect the x3rd copy of a file */
466
static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)");
467

468
/* localizers: tag used to detect the xxth copy of a file */
469 470 471 472
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)
473 474 475 476
#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)

477 478 479 480
#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)
481 482

/* localizers: appended to first file copy */
483
static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s");
484
/* localizers: appended to second file copy */
485
static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s");
486 487

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

494 495 496 497 498
/* localizers: if in your language there's no difference between 1st, 2nd, 3rd and nth
 * plurals, you can leave the st, nd, rd suffixes out and just make all the translated
 * strings look like "%s (copy %'d)%s".
 */

499
/* localizers: appended to x1st file copy */
500
static const char untranslated_st_copy_duplicate_format[] = N_("%s (%'dst copy)%s");
501
/* localizers: appended to x2nd file copy */
502
static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%'dnd copy)%s");
503
/* localizers: appended to x3rd file copy */
504
static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%'drd copy)%s");
505
/* localizers: appended to xxth file copy */
506
static const char untranslated_th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
507 508 509

#define FIRST_COPY_DUPLICATE_FORMAT _(untranslated_first_copy_duplicate_format)
#define SECOND_COPY_DUPLICATE_FORMAT _(untranslated_second_copy_duplicate_format)
510 511 512 513
#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)

514 515 516 517
#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)
518 519

static char *
520
extract_string_until (const char *original, const char *until_substring)
521 522 523
{
	char *result;
	
524
	g_assert ((int) strlen (original) >= until_substring - original);
525
	g_assert (until_substring - original >= 0);
526

527 528 529
	result = g_malloc (until_substring - original + 1);
	strncpy (result, original, until_substring - original);
	result[until_substring - original] = '\0';
530 531 532 533
	
	return result;
}

534 535 536 537
/* 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.
 */
538
static void
539 540 541 542
parse_previous_duplicate_name (const char *name,
			       char **name_base,
			       const char **suffix,
			       int *count)
543 544
{
	const char *tag;
545 546

	g_assert (name[0] != '\0');
547

548
	*suffix = eel_filename_get_extension_offset (name);
549

550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
	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;
	}


579
	/* Check to see if we got one of st, nd, rd, th. */
580 581 582 583 584 585 586 587
	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);
	}
588

589 590 591
	if (tag == NULL) {
		tag = strstr (name, ST_COPY_DUPLICATE_TAG);
	}
592 593 594 595 596 597 598 599 600 601
	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);
	}

602
	/* If we got one of st, nd, rd, th, fish out the duplicate number. */
603
	if (tag != NULL) {
604
		/* localizers: opening parentheses to match the "th copy)" string */
605
		tag = strstr (name, _(" ("));
606 607 608 609 610 611
		if (tag != NULL) {
			if (tag > *suffix) {
				/* handle case "foo. (22nd copy)" */
				*suffix = "";
			}
			*name_base = extract_string_until (name, tag);
612
			/* localizers: opening parentheses of the "th copy)" string */
613
			if (sscanf (tag, _(" (%'d"), count) == 1) {
614 615 616 617
				if (*count < 1 || *count > 1000000) {
					/* keep the count within a reasonable range */
					*count = 0;
				}
618 619 620 621 622 623 624 625 626 627
				return;
			}
			*count = 0;
			return;
		}
	}

	
	*count = 0;
	if (**suffix != '\0') {
628 629
		*name_base = extract_string_until (name, *suffix);
	} else {
630
		*name_base = g_strdup (name);
631 632 633
	}
}

634
static char *
635
make_next_duplicate_name (const char *base, const char *suffix, int count, int max_length)
636
{
637 638
	const char *format;
	char *result;
639 640
	int unshortened_length;
	gboolean use_count;
641 642

	if (count < 1) {
643
		g_warning ("bad count %d in get_duplicate_name", count);
644 645 646
		count = 1;
	}

647
	if (count <= 2) {
648

649 650 651 652
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
		 */
		switch (count) {
653 654 655
		default:
			g_assert_not_reached ();
			/* fall through */
656
		case 1:
657
			format = FIRST_COPY_DUPLICATE_FORMAT;
658 659
			break;
		case 2:
660
			format = SECOND_COPY_DUPLICATE_FORMAT;
661
			break;
662

663
		}
664 665

		use_count = FALSE;
666 667 668 669 670 671
	} 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.
		 */
672 673 674 675 676 677

		/* Handle special cases for x11th - x20th.
		 */
		switch (count % 100) {
		case 11:
			format = X11TH_COPY_DUPLICATE_FORMAT;
678
			break;
679 680
		case 12:
			format = X12TH_COPY_DUPLICATE_FORMAT;
681
			break;
682 683
		case 13:
			format = X13TH_COPY_DUPLICATE_FORMAT;
684 685
			break;
		default:
686
			format = NULL;
687 688 689
			break;
		}

690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
		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;
			}
		}

708 709 710 711 712
		use_count = TRUE;

	}

	if (use_count)
713
		result = g_strdup_printf (format, base, count, suffix);
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
	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);
		}
732 733
	}

734 735 736 737
	return result;
}

static char *
738
get_duplicate_name (const char *name, int count_increment, int max_length)
739 740 741 742 743 744 745
{
	char *result;
	char *name_base;
	const char *suffix;
	int count;

	parse_previous_duplicate_name (name, &name_base, &suffix, &count);
746
	result = make_next_duplicate_name (name_base, suffix, count + count_increment, max_length);
747 748 749 750 751 752

	g_free (name_base);

	return result;
}

753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
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;
}


775 776 777 778
static char *
custom_full_name_to_string (char *format, va_list va)
{
	GFile *file;
779
	
780
	file = va_arg (va, GFile *);
781
	
782
	return g_file_get_parse_name (file);
783 784 785
}

static void
786
custom_full_name_skip (va_list *va)
787
{
788
	(void) va_arg (*va, GFile *);
789 790
}

791 792
static char *
custom_basename_to_string (char *format, va_list va)
793
{
794 795
	GFile *file;
	GFileInfo *info;
796
	char *name, *basename, *tmp;
797

798
	file = va_arg (va, GFile *);
799

800
	info = g_file_query_info (file,
801
				  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
802 803 804
				  0,
				  g_cancellable_get_current (),
				  NULL);
805
	
806 807 808 809 810
	name = NULL;
	if (info) {
		name = g_strdup (g_file_info_get_display_name (info));
		g_object_unref (info);
	}
811
	
812 813 814 815 816 817 818
	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);
819 820
		}
	}
821 822 823 824 825 826 827

	/* 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);
	}
828 829 830 831 832 833 834 835

	/* Finally, if the string is too long, truncate it. */
	if (name != NULL) {
		tmp = name;
		name = eel_str_middle_truncate (tmp, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH);
		g_free (tmp);
	}

836 837
	
	return name;
838 839
}

840 841
static void
custom_basename_skip (va_list *va)
842
{
843
	(void) va_arg (*va, GFile *);
844
}
845 846


847 848 849 850
static char *
custom_size_to_string (char *format, va_list va)
{
	goffset size;
851

852
	size = va_arg (va, goffset);
853
	return g_format_size (size);
854 855
}

856
static void
857
custom_size_skip (va_list *va)
858
{
859
	(void) va_arg (*va, goffset);
860 861
}

862 863
static char *
custom_time_to_string (char *format, va_list va)
864
{
865
	int secs;
866

867 868 869
	secs = va_arg (va, int);
	return format_time (secs);
}
870

871 872 873
static void
custom_time_skip (va_list *va)
{
874
	(void) va_arg (*va, int);
875
}
876

877
static char *
878
custom_mount_to_string (char *format, va_list va)
879
{
880
	GMount *mount;
881

882 883
	mount = va_arg (va, GMount *);
	return g_mount_get_name (mount);
884 885 886
}

static void
887
custom_mount_skip (va_list *va)
888
{
889
	(void) va_arg (*va, GMount *);
890 891 892
}


893 894 895 896 897
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 },
898
	{ 'V', custom_mount_to_string, custom_mount_skip },
899 900
	{ 0 }
};
901 902


903 904 905 906
static char *
f (const char *format, ...) {
	va_list va;
	char *res;
907
	
908 909 910
	va_start (va, format);
	res = eel_strdup_vprintf_with_custom (handlers, format, va);
	va_end (va);
911

912 913
	return res;
}
914

915 916 917 918 919 920 921 922 923 924
#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);
925 926

	if (parent_window) {
927
		common->parent_window = parent_window;
928 929 930
		g_object_add_weak_pointer (G_OBJECT (common->parent_window),
					   (gpointer *) &common->parent_window);

931
	}
932 933 934
	common->progress = nautilus_progress_info_new ();
	common->cancellable = nautilus_progress_info_get_cancellable (common->progress);
	common->time = g_timer_new ();
935
	common->inhibit_cookie = -1;
936 937 938 939 940 941 942 943 944 945 946 947 948 949
	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);

950
	if (common->inhibit_cookie != -1) {
951 952
		gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()),
					   common->inhibit_cookie);
953
	}
954

955 956
	common->inhibit_cookie = -1;
	g_timer_destroy (common->time);
957 958 959 960 961 962

	if (common->parent_window) {
		g_object_remove_weak_pointer (G_OBJECT (common->parent_window),
					      (gpointer *) &common->parent_window);
	}

963 964 965 966 967 968
	if (common->skip_files) {
		g_hash_table_destroy (common->skip_files);
	}
	if (common->skip_readdir_error) {
		g_hash_table_destroy (common->skip_readdir_error);
	}
Amos Brocco's avatar
Amos Brocco committed
969

970
	if (common->undo_info != NULL) {
971
		nautilus_file_undo_manager_set_action (common->undo_info);
972
		g_object_unref (common->undo_info);
Amos Brocco's avatar
Amos Brocco committed
973 974
	}

975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023
	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;
}

Alexander Larsson's avatar
Alexander Larsson committed
1024 1025 1026
static gboolean
can_delete_without_confirm (GFile *file)
{
1027
	if (g_file_has_uri_scheme (file, "burn") ||
William Jon McCann's avatar
William Jon McCann committed
1028
	    g_file_has_uri_scheme (file, "recent") ||
1029
	    g_file_has_uri_scheme (file, "x-nautilus-desktop")) {
Alexander Larsson's avatar
Alexander Larsson committed
1030
		return TRUE;
Alexander Larsson's avatar
Alexander Larsson committed
1031 1032
	}

Alexander Larsson's avatar
Alexander Larsson committed
1033
	return FALSE;
1034 1035
}

Alexander Larsson's avatar
Alexander Larsson committed
1036 1037
static gboolean
can_delete_files_without_confirm (GList *files)
1038
{
Alexander Larsson's avatar
Alexander Larsson committed
1039
	g_assert (files != NULL);
1040

Alexander Larsson's avatar
Alexander Larsson committed
1041 1042 1043 1044
	while (files != NULL) {
		if (!can_delete_without_confirm (files->data)) {
			return FALSE;
		}
Alexander Larsson's avatar
 
Alexander Larsson committed
1045

Alexander Larsson's avatar
Alexander Larsson committed
1046 1047
		files = files->next;
	}
Pavel Cisler's avatar
Pavel Cisler committed
1048

Alexander Larsson's avatar
Alexander Larsson committed
1049 1050
	return TRUE;
}
1051

Alexander Larsson's avatar
Alexander Larsson committed
1052
typedef struct {
1053
	GtkWindow **parent_window;
Alexander Larsson's avatar
Alexander Larsson committed
1054 1055 1056 1057 1058 1059
	gboolean ignore_close_box;
	GtkMessageType message_type;
	const char *primary_text;
	const char *secondary_text;
	const char *details_text;
	const char **button_titles;
1060
	gboolean show_all;
Alexander Larsson's avatar
Alexander Larsson committed
1061 1062 1063
	
	int result;
} RunSimpleDialogData;
1064

1065
static gboolean
Alexander Larsson's avatar
Alexander Larsson committed
1066 1067 1068 1069 1070 1071 1072 1073 1074
do_run_simple_dialog (gpointer _data)
{
	RunSimpleDialogData *data = _data;
	const char *button_title;
        GtkWidget *dialog;
	int result;
	int response_id;

	/* Create the dialog. */
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
	dialog = gtk_message_dialog_new (*data->parent_window,
					 0,
					 data->message_type,
					 GTK_BUTTONS_NONE,
					 NULL);

	g_object_set (dialog,
		      "text", data->primary_text,
		      "secondary-text", data->secondary_text,
		      NULL);
Alexander Larsson's avatar
Alexander Larsson committed
1085 1086 1087 1088 1089

	for (response_id = 0;
	     data->button_titles[response_id] != NULL;
	     response_id++) {
		button_title = data->button_titles[response_id];
1090 1091 1092 1093
		if (!data->show_all && is_all_button_text (button_title)) {
			continue;
		}

Alexander Larsson's avatar
Alexander Larsson committed
1094 1095 1096
		gtk_dialog_add_button (GTK_DIALOG (dialog), button_title, response_id);
		gtk_dialog_set_default_response (GTK_DIALOG (dialog), response_id);
	}
1097

Alexander Larsson's avatar
Alexander Larsson committed
1098
	if (data->details_text) {
1099 1100
		eel_gtk_message_dialog_set_details_label (GTK_MESSAGE_DIALOG (dialog),
							  data->details_text);
Alexander Larsson's avatar
Alexander Larsson committed
1101 1102 1103 1104 1105 1106 1107 1108 1109
	}
	
	/* Run it. */
        result = gtk_dialog_run (GTK_DIALOG (dialog));
	
	while ((result == GTK_RESPONSE_NONE || result == GTK_RESPONSE_DELETE_EVENT) && data->ignore_close_box) {
		result = gtk_dialog_run (GTK_DIALOG (dialog));
	}
	
1110
	gtk_widget_destroy (dialog);
1111

Alexander Larsson's avatar
Alexander Larsson committed
1112
	data->result = result;
1113 1114
	
	return FALSE;
Alexander Larsson's avatar
Alexander Larsson committed
1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126
}

/* 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,
1127
		      gboolean show_all,
Alexander Larsson's avatar
Alexander Larsson committed
1128 1129 1130 1131 1132 1133 1134 1135 1136 1137
		      va_list varargs)
{
	RunSimpleDialogData *data;
	int res;
	const char *button_title;
	GPtrArray *ptr_array;

	g_timer_stop (job->time);
	
	data = g_new0 (RunSimpleDialogData, 1);
1138
	data->parent_window = &job->parent_window;
Alexander Larsson's avatar
Alexander Larsson committed
1139 1140 1141 1142 1143
	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;
1144
	data->show_all = show_all;
Alexander Larsson's avatar
Alexander Larsson committed
1145 1146 1147 1148 1149 1150 1151 1152

	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);

1153
	nautilus_progress_info_pause (job->progress);
1154 1155 1156 1157
	g_io_scheduler_job_send_to_mainloop (job->io_job,
					     do_run_simple_dialog,
					     data,
					     NULL);
1158
	nautilus_progress_info_resume (job->progress);
Alexander Larsson's avatar
Alexander Larsson committed
1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
	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,
1203
	   gboolean show_all,
Alexander Larsson's avatar
Alexander Larsson committed
1204 1205 1206 1207 1208
	   ...)
{
	va_list varargs;
	int res;

1209
	va_start (varargs, show_all);
Alexander Larsson's avatar
Alexander Larsson committed
1210 1211 1212 1213 1214 1215
	res = run_simple_dialog_va (job,
				    FALSE,
				    GTK_MESSAGE_ERROR,
				    primary_text,
				    secondary_text,
				    details_text,
1216
				    show_all,
Alexander Larsson's avatar
Alexander Larsson committed
1217 1218 1219 1220 1221 1222 1223 1224 1225 1226
				    varargs);
	va_end (varargs);
	return res;
}

static int
run_warning (CommonJob *job,
	     char *primary_text,
	     char *secondary_text,
	     const char *details_text,
1227
	     gboolean show_all,
Alexander Larsson's avatar
Alexander Larsson committed
1228 1229 1230 1231 1232
	     ...)
{
	va_list varargs;
	int res;

1233
	va_start (varargs, show_all);
Alexander Larsson's avatar
Alexander Larsson committed
1234 1235 1236 1237 1238 1239
	res = run_simple_dialog_va (job,
				    FALSE,
				    GTK_MESSAGE_WARNING,
				    primary_text,
				    secondary_text,
				    details_text,
1240
				    show_all,
Alexander Larsson's avatar
Alexander Larsson committed
1241 1242 1243 1244 1245
				    varargs);
	va_end (varargs);
	return res;
}

1246 1247 1248 1249 1250
static int
run_question (CommonJob *job,
	      char *primary_text,
	      char *secondary_text,
	      const char *details_text,
1251
	      gboolean show_all,