nautilus-file-operations.c 87.4 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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
static gboolean confirm_trash_auto_value;

/* TODO:
 *  Implement missing functions:
 *   duplicate, new file, new folder, empty trash, set_permissions recursive
 *  Make delete handle recursive deletes
 *  Use CommonJob in trash/delete code
 * TESTING!!!
 */

typedef struct {
	GIOJob *io_job;	
	GTimer *time;
	GtkWidget *parent_window;
	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;
} CommonJob;

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

#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")
#define REPLACE _("_Replace")
#define REPLACE_ALL _("Replace _All")
#define MERGE _("_Merge")
#define MERGE_ALL _("Merge _All")

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;
146
		return g_strdup_printf (ngettext ("%d minute", "%d minutes", minutes), minutes);
Alexander Larsson's avatar
Alexander Larsson committed
147 148 149 150 151 152 153 154 155
	}

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

		minutes = (seconds - hours * 60 * 60 + 30) / 60;
		
156 157
		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
158 159 160 161 162 163
		res = g_strconcat (h, ", ", m, NULL);
		g_free (h);
		g_free (m);
		return res;
	}
	
164 165 166
	return g_strdup_printf (ngettext ("approximately %d hour",
					  "approximately %d hours",
					  hours), hours);
Alexander Larsson's avatar
Alexander Larsson committed
167 168
}

169
#ifdef GIO_CONVERSION_DONE
Pavel Cisler's avatar
Pavel Cisler committed
170

171
/* Note that we have these two separate functions with separate format
172
 * strings for ease of localization.
173 174 175
 */

static char *
176
get_link_name (char *name, int count) 
177
{
178
	char *result;
179
	char *unescaped_name;
180
	char *unescaped_tmp_name;
181
	char *unescaped_result;
182
	char *new_file;
183 184

	const char *format;
185
	
186 187
	g_assert (name != NULL);

188
	unescaped_tmp_name = gnome_vfs_unescape_string (name, "/");
189 190
	g_free (name);

191 192
	unescaped_name = g_filename_to_utf8 (unescaped_tmp_name, -1,
					     NULL, NULL, NULL);
193 194 195 196 197 198 199 200 201 202 203 204

	if (!unescaped_name) {
		/* Couldn't convert to utf8 - probably
		 * G_BROKEN_FILENAMES not set when it should be.
		 * Try converting from the locale */
		unescaped_name = g_locale_to_utf8 (unescaped_tmp_name, -1, NULL, NULL, NULL);	

		if (!unescaped_name) {
			unescaped_name = eel_make_valid_utf8 (unescaped_tmp_name);
		}
	}

205 206
	g_free (unescaped_tmp_name);

207 208 209 210 211
	if (count < 1) {
		g_warning ("bad count in get_link_name");
		count = 1;
	}

212 213 214
	if (count <= 2) {
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
215
		 */
216
		switch (count) {
217 218 219
		default:
			g_assert_not_reached ();
			/* fall through */
220
		case 1:
221
			/* appended to new link file */
222
			format = _("Link to %s");
223 224
			break;
		case 2:
225
			/* appended to new link file */
226
			format = _("Another link to %s");
227 228
			break;
		}
229
		unescaped_result = g_strdup_printf (format, unescaped_name);
230 231 232 233 234

	} 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.
235
		 */
236 237 238 239 240 241 242 243 244
		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:
245
			/* appended to new link file */
246 247 248
			format = _("%dnd link to %s");
			break;
		case 3:
249
			/* appended to new link file */
250 251 252
			format = _("%drd link to %s");
			break;
		default:
253
			/* appended to new link file */
254 255 256
			format = _("%dth link to %s");
			break;
		}
257
		unescaped_result = g_strdup_printf (format, count, unescaped_name);
258
	}
259 260
	new_file = g_filename_from_utf8 (unescaped_result, -1, NULL, NULL, NULL);
	result = gnome_vfs_escape_path_string (new_file);
261 262 263
	
	g_free (unescaped_name);
	g_free (unescaped_result);
264
	g_free (new_file);
265

266
	return result;
267 268
}

269 270 271 272 273
/* Localizers: 
 * Feel free to leave out the st, nd, rd and th suffix or
 * make some or all of them match.
 */

274
/* localizers: tag used to detect the first copy of a file */
275
static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
276
/* localizers: tag used to detect the second copy of a file */
277
static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)");
278 279 280 281 282 283 284 285

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

286
/* localizers: tag used to detect the x1st copy of a file */
287
static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)");
288
/* localizers: tag used to detect the x2nd copy of a file */
289
static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)");
290
/* localizers: tag used to detect the x3rd copy of a file */
291
static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)");
292

293
/* localizers: tag used to detect the xxth copy of a file */
294 295 296 297
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)
298 299 300 301
#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)

302 303 304 305
#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)
306 307

/* localizers: appended to first file copy */
308
static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s");
309
/* localizers: appended to second file copy */
310
static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s");
311 312 313 314 315 316 317 318

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

319
/* localizers: appended to x1st file copy */
320
static const char untranslated_st_copy_duplicate_format[] = N_("%s (%dst copy)%s");
321
/* localizers: appended to x2nd file copy */
322
static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%dnd copy)%s");
323
/* localizers: appended to x3rd file copy */
324
static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%drd copy)%s");
325
/* localizers: appended to xxth file copy */
326 327 328 329
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)
330 331 332 333
#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)

334 335 336 337
#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)
338 339

static char *
340
extract_string_until (const char *original, const char *until_substring)
341 342 343
{
	char *result;
	
344
	g_assert ((int) strlen (original) >= until_substring - original);
345
	g_assert (until_substring - original >= 0);
346

347 348 349
	result = g_malloc (until_substring - original + 1);
	strncpy (result, original, until_substring - original);
	result[until_substring - original] = '\0';
350 351 352 353
	
	return result;
}

354 355 356 357
/* 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.
 */
358
static void
359 360 361 362
parse_previous_duplicate_name (const char *name,
			       char **name_base,
			       const char **suffix,
			       int *count)
363 364
{
	const char *tag;
365 366

	g_assert (name[0] != '\0');
367
	
368
	*suffix = strchr (name + 1, '.');
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
	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;
	}


398
	/* Check to see if we got one of st, nd, rd, th. */
399 400 401 402 403 404 405 406
	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);
	}
407

408 409 410
	if (tag == NULL) {
		tag = strstr (name, ST_COPY_DUPLICATE_TAG);
	}
411 412 413 414 415 416 417 418 419 420
	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);
	}

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

	
	*count = 0;
	if (**suffix != '\0') {
447 448
		*name_base = extract_string_until (name, *suffix);
	} else {
449
		*name_base = g_strdup (name);
450 451 452
	}
}

453
static char *
454
make_next_duplicate_name (const char *base, const char *suffix, int count)
455
{
456 457 458
	const char *format;
	char *result;

459 460

	if (count < 1) {
461
		g_warning ("bad count %d in get_duplicate_name", count);
462 463 464
		count = 1;
	}

465
	if (count <= 2) {
466

467 468 469 470
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
		 */
		switch (count) {
471 472 473
		default:
			g_assert_not_reached ();
			/* fall through */
474
		case 1:
475
			format = FIRST_COPY_DUPLICATE_FORMAT;
476 477
			break;
		case 2:
478
			format = SECOND_COPY_DUPLICATE_FORMAT;
479
			break;
480

481
		}
482
		result = g_strdup_printf (format, base, suffix);
483 484 485 486 487 488
	} 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.
		 */
489 490 491 492 493 494

		/* Handle special cases for x11th - x20th.
		 */
		switch (count % 100) {
		case 11:
			format = X11TH_COPY_DUPLICATE_FORMAT;
495
			break;
496 497
		case 12:
			format = X12TH_COPY_DUPLICATE_FORMAT;
498
			break;
499 500
		case 13:
			format = X13TH_COPY_DUPLICATE_FORMAT;
501 502
			break;
		default:
503
			format = NULL;
504 505 506
			break;
		}

507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
		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;
			}
		}

525
		result = g_strdup_printf (format, base, count, suffix);
526 527
	}

528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
	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;
}

static char *
get_next_duplicate_name (char *name, int count_increment)
{
550
	char *unescaped_name;
551
	char *unescaped_tmp_name;
552
	char *unescaped_result;
553
	char *result;
554
	char *new_file;
555

556
	unescaped_tmp_name = gnome_vfs_unescape_string (name, "/");
557
	g_free (name);
558

559 560
	unescaped_name = g_filename_to_utf8 (unescaped_tmp_name, -1,
					     NULL, NULL, NULL);
561 562 563 564 565 566 567 568 569 570 571
	if (!unescaped_name) {
		/* Couldn't convert to utf8 - probably
		 * G_BROKEN_FILENAMES not set when it should be.
		 * Try converting from the locale */
		unescaped_name = g_locale_to_utf8 (unescaped_tmp_name, -1, NULL, NULL, NULL);	

		if (!unescaped_name) {
			unescaped_name = eel_make_valid_utf8 (unescaped_tmp_name);
		}
	}
		
572
	g_free (unescaped_tmp_name);
573
	
574
	unescaped_result = get_duplicate_name (unescaped_name, count_increment);
575
	g_free (unescaped_name);
576

577 578
	new_file = g_filename_from_utf8 (unescaped_result, -1, NULL, NULL, NULL);
	result = gnome_vfs_escape_path_string (new_file);
579
	g_free (unescaped_result);
580
	g_free (new_file);
581
	return result;
582 583
}

584
#endif /* GIO_CONVERSION_DONE */
585

586 587 588 589
static char *
custom_full_name_to_string (char *format, va_list va)
{
	GFile *file;
590
	
591
	file = va_arg (va, GFile *);
592
	
593
	return g_file_get_parse_name (file);
594 595 596
}

static void
597
custom_full_name_skip (va_list *va)
598
{
599
	va_arg (*va, GFile *);
600 601
}

602 603
static char *
custom_basename_to_string (char *format, va_list va)
604
{
605 606 607
	GFile *file;
	GFileInfo *info;
	char *name, *basename;
608

609
	file = va_arg (va, GFile *);
610

611 612 613 614 615
	info = g_file_query_info (file,
				  G_FILE_ATTRIBUTE_STD_DISPLAY_NAME,
				  0,
				  g_cancellable_get_current (),
				  NULL);
616
	
617 618 619 620 621
	name = NULL;
	if (info) {
		name = g_strdup (g_file_info_get_display_name (info));
		g_object_unref (info);
	}
622
	
623 624 625 626 627 628 629
	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);
630 631
		}
	}
632 633
	
	return name;
634 635
}

636 637
static void
custom_basename_skip (va_list *va)
638
{
639 640
	va_arg (*va, GFile *);
}
641 642


643 644 645 646
static char *
custom_size_to_string (char *format, va_list va)
{
	goffset size;
647

648 649
	size = va_arg (va, goffset);
	return g_format_file_size_for_display (size);
650 651
}

652
static void
653
custom_size_skip (va_list *va)
654
{
655
	va_arg (*va, goffset);
656 657
}

658 659
static char *
custom_time_to_string (char *format, va_list va)
660
{
661
	int secs;
662

663 664 665
	secs = va_arg (va, int);
	return format_time (secs);
}
666

667 668 669 670 671
static void
custom_time_skip (va_list *va)
{
	va_arg (*va, int);
}
672

673 674 675 676 677 678 679
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 }
};
680 681


682 683 684 685
static char *
f (const char *format, ...) {
	va_list va;
	char *res;
686
	
687 688 689
	va_start (va, format);
	res = eel_strdup_vprintf_with_custom (handlers, format, va);
	va_end (va);
690

691 692
	return res;
}
693

694 695 696 697 698 699 700 701 702 703
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
704

Alexander Larsson's avatar
Alexander Larsson committed
705 706 707 708 709 710 711 712
typedef struct {
	GList *files;
	GtkWindow *parent_window;
	gboolean try_trash;
	gboolean delete_if_all_already_in_trash;
	NautilusDeleteCallback done_callback;
	gpointer done_callback_data;
} DeleteJob;
713

Alexander Larsson's avatar
Alexander Larsson committed
714 715 716 717 718
static gboolean
can_delete_without_confirm (GFile *file)
{
	if (g_file_has_uri_scheme (file, "burn")) {
		return TRUE;
Alexander Larsson's avatar
Alexander Larsson committed
719 720
	}

Alexander Larsson's avatar
Alexander Larsson committed
721
	return FALSE;
722 723
}

Alexander Larsson's avatar
Alexander Larsson committed
724 725
static gboolean
can_delete_files_without_confirm (GList *files)
726
{
Alexander Larsson's avatar
Alexander Larsson committed
727
	g_assert (files != NULL);
728

Alexander Larsson's avatar
Alexander Larsson committed
729 730 731 732
	while (files != NULL) {
		if (!can_delete_without_confirm (files->data)) {
			return FALSE;
		}
Alexander Larsson's avatar
 
Alexander Larsson committed
733

Alexander Larsson's avatar
Alexander Larsson committed
734 735
		files = files->next;
	}
Pavel Cisler's avatar
Pavel Cisler committed
736

Alexander Larsson's avatar
Alexander Larsson committed
737 738
	return TRUE;
}
739

Alexander Larsson's avatar
Alexander Larsson committed
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
static gboolean
can_trash_file (GFile *file, GCancellable *cancellable)
{
	GFileInfo *info;
	gboolean res;

	res = FALSE;
	info = g_file_query_info (file, 
				  G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
				  0,
				  cancellable,
				  NULL);

	if (info) {
		res = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH);
		g_object_unref (info);
756 757
	}

Alexander Larsson's avatar
Alexander Larsson committed
758
	return res;
759 760
}

Alexander Larsson's avatar
Alexander Larsson committed
761 762
static char *
get_display_name (GFile *file, GCancellable *cancellable)
763
{
Alexander Larsson's avatar
Alexander Larsson committed
764 765
	GFileInfo *info;
	char *name, *basename;
766

Alexander Larsson's avatar
Alexander Larsson committed
767 768 769 770 771 772 773 774
	info = g_file_query_info (file,
				  G_FILE_ATTRIBUTE_STD_DISPLAY_NAME,
				  0, cancellable, NULL);

	name = NULL;
	if (info) {
		name = g_strdup (g_file_info_get_display_name (info));
		g_object_unref (info);
775 776
	}
	
Alexander Larsson's avatar
Alexander Larsson committed
777 778 779 780 781 782 783 784 785 786 787 788
	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);
		}
	}
	
	return name;
}
789

Alexander Larsson's avatar
Alexander Larsson committed
790 791 792 793 794 795
static void
delete_files (GList *files, GCancellable  *cancellable, GtkWindow *parent_window)
{
	GList *l;
	GFile *file;
	GError *error;
796

Alexander Larsson's avatar
Alexander Larsson committed
797 798
	for (l = files; l != NULL; l = l->next) {
		file = l->data;
799

Alexander Larsson's avatar
Alexander Larsson committed
800 801 802 803 804 805 806 807 808 809
		error = NULL;
		if (!g_file_delete (file, cancellable, &error)) {
			/* TODO-gio: Dialog here, and handle recursive deletes */
			g_print ("Error deleting file: %s\n", error->message);
		} else {
			nautilus_file_changes_queue_schedule_metadata_remove (file);
			nautilus_file_changes_queue_file_removed (file);
		}
	}
}
810

Alexander Larsson's avatar
Alexander Larsson committed
811 812 813 814 815 816
static void
trash_files (GList *files, GCancellable  *cancellable, GtkWindow *parent_window)
{
	GList *l;
	GFile *file;
	GError *error;
817

Alexander Larsson's avatar
Alexander Larsson committed
818 819
	for (l = files; l != NULL; l = l->next) {
		file = l->data;
820

Alexander Larsson's avatar
Alexander Larsson committed
821 822 823 824 825 826 827 828 829 830
		error = NULL;
		if (!g_file_trash (file, cancellable, &error)) {
			/* TODO-gio: Dialog here, allow delete instead of trash, etc */
			g_print ("Error trashing file: %s\n", error->message);
		} else {
			nautilus_file_changes_queue_schedule_metadata_remove (file);
			nautilus_file_changes_queue_file_removed (file);
		}
	}
}
831

Alexander Larsson's avatar
Alexander Larsson committed
832 833 834 835 836 837 838 839 840 841 842
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;
843

Alexander Larsson's avatar
Alexander Larsson committed
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867
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);
	}
868

Alexander Larsson's avatar
Alexander Larsson committed
869 870 871 872 873 874 875 876 877 878 879 880 881 882
	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));
	}
	
883 884
	gtk_object_destroy (GTK_OBJECT (dialog));

Alexander Larsson's avatar
Alexander Larsson committed
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 971 972 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 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
	data->result = result;
}

static int
_run_simple_dialog (GIOJob *job,
		   GtkWindow *parent_window,
		   gboolean ignore_close_box,
		   GtkMessageType message_type,
		   const char *primary_text,
		   const char *secondary_text,
		   const char *details_text,
		   ...)
{
	RunSimpleDialogData *data;
	va_list varargs;
	int res;
	const char *button_title;
	GPtrArray *ptr_array;

	data = g_new0 (RunSimpleDialogData, 1);
	data->parent_window = 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 ();
	va_start (varargs, details_text);
	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);
	va_end (varargs);

	g_io_job_send_to_mainloop (job,
				   do_run_simple_dialog,
				   data,
				   NULL,
				   TRUE);

	res = data->result;

	g_free (data->button_titles);
	g_free (data);
	return res;
}

static int 
_run_alert (GIOJob *job,
	   GtkWindow *parent_window,
	   const char *primary_message,
	   const char *secondary_message,
	   const char *ok_label)
{
	return _run_simple_dialog (job, parent_window,
				  FALSE,
				  GTK_MESSAGE_WARNING,
				  primary_message,
				  secondary_message,
				  NULL,
				  GTK_STOCK_CANCEL, 
				  ok_label,
				  NULL);
}

static int
_run_yes_no_dialog (GIOJob *job,
		   const char *prompt,
		   const char *detail,
		   const char *yes_label,
		   const char *no_label,
		   GtkWindow *parent_window)
{
	return _run_simple_dialog (job, parent_window,
				  FALSE,
				  GTK_MESSAGE_QUESTION,
				  prompt,
				  detail,
				  NULL,
				  no_label,
				  yes_label,
				  NULL);
}

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

1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
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
1107 1108 1109 1110 1111