nautilus-file-operations.c 118 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>
65
#include <gio/gio.h>
Alexander Larsson's avatar
Alexander Larsson committed
66
#include <glib/gurifuncs.h>
67
#include "nautilus-file-changes-queue.h"
Alexander Larsson's avatar
Alexander Larsson committed
68 69 70
#include "nautilus-file-private.h"
#include "nautilus-desktop-icon-file.h"
#include "nautilus-desktop-link-monitor.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
71 72 73
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
#include "nautilus-trash-monitor.h"
74
#include "nautilus-file-utilities.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
75

Alexander Larsson's avatar
Alexander Larsson committed
76 77
static gboolean confirm_trash_auto_value;

78
/* TODO: TESTING!!! */
Alexander Larsson's avatar
Alexander Larsson committed
79 80

typedef struct {
81
	GIOSchedulerJob *io_job;	
Alexander Larsson's avatar
Alexander Larsson committed
82
	GTimer *time;
83 84
	GtkWindow *parent_window;
	int screen_num;
Alexander Larsson's avatar
Alexander Larsson committed
85 86 87 88 89 90 91 92
	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;
93
	gboolean delete_all;
Alexander Larsson's avatar
Alexander Larsson committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107
} 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;

108 109 110 111 112 113 114 115 116
typedef struct {
	CommonJob common;
	GList *files;
	gboolean try_trash;
	gboolean delete_if_all_already_in_trash;
	NautilusDeleteCallback done_callback;
	gpointer done_callback_data;
} DeleteJob;

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
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
132 133
typedef struct {
	CommonJob common;
134 135 136
	GList *trash_dirs;
	NautilusOpCallback done_callback;
	gpointer done_callback_data;
Alexander Larsson's avatar
Alexander Larsson committed
137
} EmptyTrashJob;
138

139 140 141 142 143 144 145 146 147 148 149
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;

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
typedef enum {
	OP_KIND_COPY,
	OP_KIND_MOVE,
	OP_KIND_DELETE,
	OP_KIND_TRASH
} OpKind;

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

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

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

186 187 188 189 190 191
static void scan_sources (GList *files,
			  SourceInfo *source_info,
			  CommonJob *job,
			  OpKind kind);


192
static void empty_trash_job (GIOSchedulerJob *io_job,
193 194 195
			     GCancellable *cancellable,
			     gpointer user_data);

Alexander Larsson's avatar
Alexander Larsson committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
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;
214
		return g_strdup_printf (ngettext ("%d minute", "%d minutes", minutes), minutes);
Alexander Larsson's avatar
Alexander Larsson committed
215 216 217 218 219 220 221 222 223
	}

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

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

237
/* Note that we have these two separate functions with separate format
238
 * strings for ease of localization.
239 240 241
 */

static char *
Alexander Larsson's avatar
Alexander Larsson committed
242
get_link_name (const char *name, int count) 
243
{
244
	const char *format;
Alexander Larsson's avatar
Alexander Larsson committed
245
	char *result;
246
	
247 248 249 250 251 252 253
	g_assert (name != NULL);

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

254 255 256
	if (count <= 2) {
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
257
		 */
258
		switch (count) {
259 260 261
		default:
			g_assert_not_reached ();
			/* fall through */
262
		case 1:
263
			/* appended to new link file */
264
			format = _("Link to %s");
265 266
			break;
		case 2:
267
			/* appended to new link file */
268
			format = _("Another link to %s");
269 270
			break;
		}
Alexander Larsson's avatar
Alexander Larsson committed
271
		result = g_strdup_printf (format, name);
272 273 274 275 276

	} 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.
277
		 */
278 279 280 281 282 283 284 285 286
		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:
287
			/* appended to new link file */
288 289 290
			format = _("%dnd link to %s");
			break;
		case 3:
291
			/* appended to new link file */
292 293 294
			format = _("%drd link to %s");
			break;
		default:
295
			/* appended to new link file */
296 297 298
			format = _("%dth link to %s");
			break;
		}
Alexander Larsson's avatar
Alexander Larsson committed
299
		result = g_strdup_printf (format, count, name);
300
	}
301

302
	return result;
303 304
}

Alexander Larsson's avatar
Alexander Larsson committed
305

306 307 308 309 310
/* Localizers: 
 * Feel free to leave out the st, nd, rd and th suffix or
 * make some or all of them match.
 */

311
/* localizers: tag used to detect the first copy of a file */
312
static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
313
/* localizers: tag used to detect the second copy of a file */
314
static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)");
315 316 317 318 319 320 321 322

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

323
/* localizers: tag used to detect the x1st copy of a file */
324
static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)");
325
/* localizers: tag used to detect the x2nd copy of a file */
326
static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)");
327
/* localizers: tag used to detect the x3rd copy of a file */
328
static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)");
329

330
/* localizers: tag used to detect the xxth copy of a file */
331 332 333 334
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)
335 336 337 338
#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)

339 340 341 342
#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)
343 344

/* localizers: appended to first file copy */
345
static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s");
346
/* localizers: appended to second file copy */
347
static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s");
348 349 350 351 352 353 354 355

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

356
/* localizers: appended to x1st file copy */
357
static const char untranslated_st_copy_duplicate_format[] = N_("%s (%dst copy)%s");
358
/* localizers: appended to x2nd file copy */
359
static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%dnd copy)%s");
360
/* localizers: appended to x3rd file copy */
361
static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%drd copy)%s");
362
/* localizers: appended to xxth file copy */
363 364 365 366
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)
367 368 369 370
#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)

371 372 373 374
#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)
375 376

static char *
377
extract_string_until (const char *original, const char *until_substring)
378 379 380
{
	char *result;
	
381
	g_assert ((int) strlen (original) >= until_substring - original);
382
	g_assert (until_substring - original >= 0);
383

384 385 386
	result = g_malloc (until_substring - original + 1);
	strncpy (result, original, until_substring - original);
	result[until_substring - original] = '\0';
387 388 389 390
	
	return result;
}

391 392 393 394
/* 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.
 */
395
static void
396 397 398 399
parse_previous_duplicate_name (const char *name,
			       char **name_base,
			       const char **suffix,
			       int *count)
400 401
{
	const char *tag;
402 403

	g_assert (name[0] != '\0');
404
	
405
	*suffix = strchr (name + 1, '.');
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
	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;
	}


435
	/* Check to see if we got one of st, nd, rd, th. */
436 437 438 439 440 441 442 443
	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);
	}
444

445 446 447
	if (tag == NULL) {
		tag = strstr (name, ST_COPY_DUPLICATE_TAG);
	}
448 449 450 451 452 453 454 455 456 457
	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);
	}

458
	/* If we got one of st, nd, rd, th, fish out the duplicate number. */
459
	if (tag != NULL) {
460
		/* localizers: opening parentheses to match the "th copy)" string */
461
		tag = strstr (name, _(" ("));
462 463 464 465 466 467
		if (tag != NULL) {
			if (tag > *suffix) {
				/* handle case "foo. (22nd copy)" */
				*suffix = "";
			}
			*name_base = extract_string_until (name, tag);
468
			/* localizers: opening parentheses of the "th copy)" string */
469
			if (sscanf (tag, _(" (%d"), count) == 1) {
470 471 472 473
				if (*count < 1 || *count > 1000000) {
					/* keep the count within a reasonable range */
					*count = 0;
				}
474 475 476 477 478 479 480 481 482 483
				return;
			}
			*count = 0;
			return;
		}
	}

	
	*count = 0;
	if (**suffix != '\0') {
484 485
		*name_base = extract_string_until (name, *suffix);
	} else {
486
		*name_base = g_strdup (name);
487 488 489
	}
}

490
static char *
491
make_next_duplicate_name (const char *base, const char *suffix, int count)
492
{
493 494 495
	const char *format;
	char *result;

496 497

	if (count < 1) {
498
		g_warning ("bad count %d in get_duplicate_name", count);
499 500 501
		count = 1;
	}

502
	if (count <= 2) {
503

504 505 506 507
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
		 */
		switch (count) {
508 509 510
		default:
			g_assert_not_reached ();
			/* fall through */
511
		case 1:
512
			format = FIRST_COPY_DUPLICATE_FORMAT;
513 514
			break;
		case 2:
515
			format = SECOND_COPY_DUPLICATE_FORMAT;
516
			break;
517

518
		}
519
		result = g_strdup_printf (format, base, suffix);
520 521 522 523 524 525
	} 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.
		 */
526 527 528 529 530 531

		/* Handle special cases for x11th - x20th.
		 */
		switch (count % 100) {
		case 11:
			format = X11TH_COPY_DUPLICATE_FORMAT;
532
			break;
533 534
		case 12:
			format = X12TH_COPY_DUPLICATE_FORMAT;
535
			break;
536 537
		case 13:
			format = X13TH_COPY_DUPLICATE_FORMAT;
538 539
			break;
		default:
540
			format = NULL;
541 542 543
			break;
		}

544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
		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;
			}
		}

562
		result = g_strdup_printf (format, base, count, suffix);
563 564
	}

565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
	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;
}

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

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

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

607
	file = va_arg (va, GFile *);
608

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

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


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

646 647
	size = va_arg (va, goffset);
	return g_format_file_size_for_display (size);
648 649
}

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

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

661 662 663
	secs = va_arg (va, int);
	return format_time (secs);
}
664

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

671
static char *
672
custom_mount_to_string (char *format, va_list va)
673
{
674
	GMount *mount;
675

676 677
	mount = va_arg (va, GMount *);
	return g_mount_get_name (mount);
678 679 680
}

static void
681
custom_mount_skip (va_list *va)
682
{
683
	va_arg (*va, GMount *);
684 685 686
}


687 688 689 690 691
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 },
692
	{ 'V', custom_mount_to_string, custom_mount_skip },
693 694
	{ 0 }
};
695 696


697 698 699 700
static char *
f (const char *format, ...) {
	va_list va;
	char *res;
701
	
702 703 704
	va_start (va, format);
	res = eel_strdup_vprintf_with_custom (handlers, format, va);
	va_end (va);
705

706 707
	return res;
}
708

709 710 711 712 713 714 715 716 717 718
#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);
719 720 721 722

	if (parent_window) {
		common->parent_window = g_object_ref (parent_window);
	}
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
	common->progress = nautilus_progress_info_new ();
	common->cancellable = nautilus_progress_info_get_cancellable (common->progress);
	common->time = g_timer_new ();

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

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

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

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

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

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

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

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

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

801 802 803 804 805 806 807 808 809 810
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
811

Alexander Larsson's avatar
Alexander Larsson committed
812 813 814
static gboolean
can_delete_without_confirm (GFile *file)
{
815 816
	if (g_file_has_uri_scheme (file, "burn") ||
	    g_file_has_uri_scheme (file, "x-nautilus-desktop")) {
Alexander Larsson's avatar
Alexander Larsson committed
817
		return TRUE;
Alexander Larsson's avatar
Alexander Larsson committed
818 819
	}

Alexander Larsson's avatar
Alexander Larsson committed
820
	return FALSE;
821 822
}

Alexander Larsson's avatar
Alexander Larsson committed
823 824
static gboolean
can_delete_files_without_confirm (GList *files)
825
{
Alexander Larsson's avatar
Alexander Larsson committed
826
	g_assert (files != NULL);
827

Alexander Larsson's avatar
Alexander Larsson committed
828 829 830 831
	while (files != NULL) {
		if (!can_delete_without_confirm (files->data)) {
			return FALSE;
		}
Alexander Larsson's avatar
 
Alexander Larsson committed
832

Alexander Larsson's avatar
Alexander Larsson committed
833 834
		files = files->next;
	}
Pavel Cisler's avatar
Pavel Cisler committed
835

Alexander Larsson's avatar
Alexander Larsson committed
836 837
	return TRUE;
}
838

Alexander Larsson's avatar
Alexander Larsson committed
839 840 841 842 843 844 845 846 847 848 849
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;
850

851
static gboolean
Alexander Larsson's avatar
Alexander Larsson committed
852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
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);
	}
875

Alexander Larsson's avatar
Alexander Larsson committed
876 877 878 879 880 881 882 883 884 885 886 887 888 889
	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));
	}
	
890 891
	gtk_object_destroy (GTK_OBJECT (dialog));

Alexander Larsson's avatar
Alexander Larsson committed
892
	data->result = result;
893 894
	
	return FALSE;
Alexander Larsson's avatar
Alexander Larsson committed
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
}

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

931 932 933 934
	g_io_scheduler_job_send_to_mainloop (job->io_job,
					     do_run_simple_dialog,
					     data,
					     NULL);
Alexander Larsson's avatar
Alexander Larsson committed
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

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

1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
static int
run_question (CommonJob *job,
	      char *primary_text,
	      char *secondary_text,
	      const char *details_text,
	      ...)
{
	va_list varargs;
	int res;

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

1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
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
1054
static gboolean
1055
confirm_delete_from_trash (CommonJob *job,
Alexander Larsson's avatar
Alexander Larsson committed
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
			   GList *files)
{
	char *prompt;
	int file_count;
	int response;

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

	file_count = g_list_length (files);
	g_assert (file_count > 0);
	
	if (file_count == 1) {
1071 1072
		prompt = f (_("Are you sure you want to permanently delete \"%B\" "
					    "from the trash?"), files->data);
Alexander Larsson's avatar
Alexander Larsson committed
1073
	} else {
1074 1075 1076 1077 1078 1079
		prompt = f (ngettext("Are you sure you want to permanently delete "
				     "the %d selected item from the trash?",
				     "Are you sure you want to permanently delete "
				     "the %d selected items from the trash?",
				     file_count), 
			    file_count);
Alexander Larsson's avatar
Alexander Larsson committed
1080 1081
	}

1082 1083 1084 1085 1086 1087
	response = run_warning (job,
				prompt,
				f (_("If you delete an item, it will be permanently lost.")),
				NULL,
				GTK_STOCK_CANCEL, GTK_STOCK_DELETE,
				NULL);
Alexander Larsson's avatar
Alexander Larsson committed
1088 1089 1090 1091 1092
	
	return (response == 1);
}

static gboolean
1093
confirm_delete_directly (CommonJob *job,
Alexander Larsson's avatar
Alexander Larsson committed
1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112
			 GList *files)
{
	char *prompt;
	int file_count;
	int response;

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

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

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

	if (file_count == 1) {
1113 1114
		prompt = f (_("Are you sure you want to permanently delete \"%B\"?"), 
			    files->data);
Alexander Larsson's avatar
Alexander Larsson committed
1115
	} else {
1116 1117 1118 1119 1120
		prompt = f (ngettext("Are you sure you want to permanently delete "
				     "the %d selected item?",
				     "Are you sure you want to permanently delete "
				     "the %d selected items?", file_count),
			    file_count);
Alexander Larsson's avatar
Alexander Larsson committed
1121
	}
1122 1123 1124 1125 1126 1127