nautilus-file-operations.c 32.6 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
/* xfer.c - Bonobo::Desktop::FileOperationService transfer service.
Ettore Perazzoli's avatar
Ettore Perazzoli committed
3

Elliot Lee's avatar
Elliot Lee committed
4
   Copyright (C) 1999, 2000 Free Software Foundation
Ettore Perazzoli's avatar
Ettore Perazzoli committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

   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.
   
Pavel Cisler's avatar
Pavel Cisler committed
21 22 23 24
   Authors: 
   Ettore Perazzoli <ettore@gnu.org> 
   Pavel Cisler <pavel@eazel.com> 
   */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
25 26 27 28

#include <config.h>

#include <gnome.h>
29
#include <libgnomevfs/gnome-vfs-find-directory.h>
Pavel Cisler's avatar
Pavel Cisler committed
30
#include <libgnomevfs/gnome-vfs-uri.h>
31

32 33
#include "nautilus-file-operations.h"
#include "nautilus-file-operations-progress.h"
34 35
#include <libnautilus-extensions/nautilus-file-changes-queue.h>
#include <libnautilus-extensions/nautilus-glib-extensions.h>
36
#include <libnautilus-extensions/nautilus-stock-dialogs.h>
Pavel Cisler's avatar
Pavel Cisler committed
37
#include "fm-directory-view.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
38

39 40 41
typedef enum {
	XFER_MOVE,
	XFER_COPY,
Pavel Cisler's avatar
Pavel Cisler committed
42
	XFER_DUPLICATE,
43
	XFER_MOVE_TO_TRASH,
44
	XFER_EMPTY_TRASH,
45 46
	XFER_DELETE,
	XFER_LINK
47 48
} XferKind;

Pavel Cisler's avatar
Pavel Cisler committed
49
typedef struct XferInfo {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
50 51
	GnomeVFSAsyncHandle *handle;
	GtkWidget *progress_dialog;
Pavel Cisler's avatar
Pavel Cisler committed
52 53 54 55 56
	const char *operation_title;	/* "Copying files" */
	const char *action_verb;	/* "copied" */
	const char *progress_verb;	/* "Copying" */
	const char *preparation_name;	/* "Preparing To Copy..." */
	const char *cleanup_name;	/* "Finishing Move..." */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
57 58
	GnomeVFSXferErrorMode error_mode;
	GnomeVFSXferOverwriteMode overwrite_mode;
Pavel Cisler's avatar
Pavel Cisler committed
59
	GtkWidget *parent_view;
60
	XferKind kind;
61
	gboolean show_progress_dialog;
Pavel Cisler's avatar
Pavel Cisler committed
62
} XferInfo;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
63

64 65 66 67 68 69 70 71 72 73 74 75 76
char *
nautilus_convert_to_unescaped_string_for_display  (char *escaped)
{
	char *result;
	if (!escaped) {
		return NULL;
	}
	result = gnome_vfs_unescape_string_for_display (escaped);
	g_free (escaped);
	return result;
}


Ettore Perazzoli's avatar
Ettore Perazzoli committed
77
static void
78
xfer_dialog_clicked_callback (NautilusFileOperationsProgress *dialog,
Pavel Cisler's avatar
Pavel Cisler committed
79
			      int button_number,
Ettore Perazzoli's avatar
Ettore Perazzoli committed
80 81 82 83 84 85 86 87 88 89 90 91 92 93
			      gpointer data)
{
	XferInfo *info;

	info = (XferInfo *) data;
	gnome_vfs_async_cancel (info->handle);

	gtk_widget_destroy (GTK_WIDGET (dialog));
}

static void
create_xfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
		    XferInfo *xfer_info)
{
94 95 96
	if (!xfer_info->show_progress_dialog)
		return;

Ettore Perazzoli's avatar
Ettore Perazzoli committed
97 98
	g_return_if_fail (xfer_info->progress_dialog == NULL);

99
	xfer_info->progress_dialog = nautilus_file_operations_progress_new 
Pavel Cisler's avatar
Pavel Cisler committed
100
		(xfer_info->operation_title, "", "", "", 1, 1);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
101 102 103 104 105 106 107 108 109

	gtk_signal_connect (GTK_OBJECT (xfer_info->progress_dialog),
			    "clicked",
			    GTK_SIGNAL_FUNC (xfer_dialog_clicked_callback),
			    xfer_info);

	gtk_widget_show (xfer_info->progress_dialog);
}

Pavel Cisler's avatar
Pavel Cisler committed
110
static void
111 112
progress_dialog_set_files_remaining_text ( NautilusFileOperationsProgress *dialog, 
	const char *action_verb)
Pavel Cisler's avatar
Pavel Cisler committed
113 114 115 116
{
	char *text;

	text = g_strdup_printf ("Files remaining to be %s:", action_verb);
117
	nautilus_file_operations_progress_set_operation_string (dialog, text);
Pavel Cisler's avatar
Pavel Cisler committed
118 119 120 121
	g_free (text);
}

static void
122
progress_dialog_set_to_from_item_text (NautilusFileOperationsProgress *dialog,
Pavel Cisler's avatar
Pavel Cisler committed
123 124 125 126 127 128 129 130 131 132
	const char *progress_verb, const char *from_uri, const char *to_uri, 
	gulong index, gulong size)
{
	char *item;
	char *from_path;
	char *to_path;
	char *progress_label_text;
	const char *from_prefix;
	const char *to_prefix;
	GnomeVFSURI *uri;
133
	int length;
Pavel Cisler's avatar
Pavel Cisler committed
134 135 136 137 138 139 140 141 142 143 144 145

	item = NULL;
	from_path = NULL;
	to_path = NULL;
	from_prefix = "";
	to_prefix = "";
	progress_label_text = NULL;

	if (from_uri != NULL) {
		uri = gnome_vfs_uri_new (from_uri);
		item = gnome_vfs_uri_extract_short_name (uri);
		from_path = gnome_vfs_uri_extract_dirname (uri);
146 147 148 149 150 151 152

		/* remove the last '/' */
		length = strlen (from_path);
		if (from_path [length - 1] == '/') {
			from_path [length - 1] = '\0';
		}
		
Pavel Cisler's avatar
Pavel Cisler committed
153 154 155 156 157 158 159 160 161
		gnome_vfs_uri_unref (uri);
		g_assert (progress_verb);
		progress_label_text = g_strdup_printf ("%s:", progress_verb);
		from_prefix = _("From:");
	}

	if (to_uri != NULL) {
		uri = gnome_vfs_uri_new (from_uri);
		to_path = gnome_vfs_uri_extract_dirname (uri);
162 163 164 165 166 167 168

		/* remove the last '/' */
		length = strlen (to_path);
		if (to_path [length - 1] == '/') {
			to_path [length - 1] = '\0';
		}

Pavel Cisler's avatar
Pavel Cisler committed
169 170 171 172
		gnome_vfs_uri_unref (uri);
		to_prefix = _("To:");
	}

173
	nautilus_file_operations_progress_new_file (dialog,
Pavel Cisler's avatar
Pavel Cisler committed
174 175 176 177 178 179 180 181 182 183 184 185
		progress_label_text ? progress_label_text : "",
		item ? item : "",
		from_path ? from_path : "",
		to_path ? to_path : "",
		from_prefix, to_prefix, index, size);

	g_free (progress_label_text);
	g_free (item);
	g_free (from_path);
	g_free (to_path);
}

Pavel Cisler's avatar
Pavel Cisler committed
186
static int
Ettore Perazzoli's avatar
Ettore Perazzoli committed
187 188 189 190
handle_xfer_ok (const GnomeVFSXferProgressInfo *progress_info,
		XferInfo *xfer_info)
{
	switch (progress_info->phase) {
Pavel Cisler's avatar
Pavel Cisler committed
191
	case GNOME_VFS_XFER_PHASE_INITIAL:
Ettore Perazzoli's avatar
Ettore Perazzoli committed
192 193
		create_xfer_dialog (progress_info, xfer_info);
		return TRUE;
Pavel Cisler's avatar
Pavel Cisler committed
194 195

	case GNOME_VFS_XFER_PHASE_COLLECTING:
196
		if (xfer_info->progress_dialog != NULL) {
197 198
			nautilus_file_operations_progress_set_operation_string
				(NAUTILUS_FILE_OPERATIONS_PROGRESS
199 200 201
					 (xfer_info->progress_dialog),
					 xfer_info->preparation_name);
		}
Pavel Cisler's avatar
Pavel Cisler committed
202 203 204
		return TRUE;

	case GNOME_VFS_XFER_PHASE_READYTOGO:
205
		if (xfer_info->progress_dialog != NULL) {
Pavel Cisler's avatar
Pavel Cisler committed
206
			progress_dialog_set_files_remaining_text (
207
				NAUTILUS_FILE_OPERATIONS_PROGRESS (xfer_info->progress_dialog),
Pavel Cisler's avatar
Pavel Cisler committed
208
					 xfer_info->action_verb);
209 210
			nautilus_file_operations_progress_set_total
				(NAUTILUS_FILE_OPERATIONS_PROGRESS
211 212 213 214
					 (xfer_info->progress_dialog),
					 progress_info->files_total,
					 progress_info->bytes_total);
		}
Pavel Cisler's avatar
Pavel Cisler committed
215 216
		return TRUE;
				 
217
	case GNOME_VFS_XFER_PHASE_DELETESOURCE:
218 219
		nautilus_file_changes_consume_changes (FALSE);
		if (xfer_info->progress_dialog != NULL) {
Pavel Cisler's avatar
Pavel Cisler committed
220
			progress_dialog_set_to_from_item_text (
221
				NAUTILUS_FILE_OPERATIONS_PROGRESS (xfer_info->progress_dialog),
Pavel Cisler's avatar
Pavel Cisler committed
222 223 224 225 226 227
				xfer_info->progress_verb,
				progress_info->source_name,
				NULL,
				progress_info->file_index,
				progress_info->file_size);

228 229
			nautilus_file_operations_progress_update_sizes
				(NAUTILUS_FILE_OPERATIONS_PROGRESS
230 231 232 233 234 235 236 237 238
				 (xfer_info->progress_dialog),
				 MIN (progress_info->bytes_copied, 
				 	progress_info->bytes_total),
				 MIN (progress_info->total_bytes_copied,
				 	progress_info->bytes_total));
		}
		return TRUE;

	case GNOME_VFS_XFER_PHASE_MOVING:
Pavel Cisler's avatar
Pavel Cisler committed
239 240
	case GNOME_VFS_XFER_PHASE_OPENSOURCE:
	case GNOME_VFS_XFER_PHASE_OPENTARGET:
241 242 243
		/* fall through */

	case GNOME_VFS_XFER_PHASE_COPYING:
244
		if (xfer_info->progress_dialog != NULL) {
245
				
246
			if (progress_info->bytes_copied == 0) {
Pavel Cisler's avatar
Pavel Cisler committed
247
				progress_dialog_set_to_from_item_text (
248
					NAUTILUS_FILE_OPERATIONS_PROGRESS (xfer_info->progress_dialog),
Pavel Cisler's avatar
Pavel Cisler committed
249 250 251 252 253
					xfer_info->progress_verb,
					progress_info->source_name,
					progress_info->target_name,
					progress_info->file_index,
					progress_info->file_size);
254
			} else {
255 256
				nautilus_file_operations_progress_update_sizes
					(NAUTILUS_FILE_OPERATIONS_PROGRESS
257 258 259 260 261 262
					 (xfer_info->progress_dialog),
					 MIN (progress_info->bytes_copied, 
					 	progress_info->bytes_total),
					 MIN (progress_info->total_bytes_copied,
					 	progress_info->bytes_total));
			}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
263 264
		}
		return TRUE;
Pavel Cisler's avatar
Pavel Cisler committed
265

266 267
	case GNOME_VFS_XFER_PHASE_CLEANUP:
		if (xfer_info->progress_dialog != NULL) {
268 269
			nautilus_file_operations_progress_clear(
				NAUTILUS_FILE_OPERATIONS_PROGRESS
270
					 (xfer_info->progress_dialog));
271 272
			nautilus_file_operations_progress_set_operation_string
				(NAUTILUS_FILE_OPERATIONS_PROGRESS
273 274 275 276 277
					 (xfer_info->progress_dialog),
					 xfer_info->cleanup_name);
		}
		return TRUE;

Ettore Perazzoli's avatar
Ettore Perazzoli committed
278
	case GNOME_VFS_XFER_PHASE_COMPLETED:
279
		nautilus_file_changes_consume_changes (TRUE);
280 281 282
		if (xfer_info->progress_dialog != NULL) {
			gtk_widget_destroy (xfer_info->progress_dialog);
		}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
283 284
		g_free (xfer_info);
		return TRUE;
Pavel Cisler's avatar
Pavel Cisler committed
285

Ettore Perazzoli's avatar
Ettore Perazzoli committed
286 287 288 289 290
	default:
		return TRUE;
	}
}

Pavel Cisler's avatar
Pavel Cisler committed
291
static int
Ettore Perazzoli's avatar
Ettore Perazzoli committed
292 293 294 295
handle_xfer_vfs_error (const GnomeVFSXferProgressInfo *progress_info,
		       XferInfo *xfer_info)
{
	/* Notice that the error mode in `xfer_info' is the one we have been
296 297 298
         * requested, but the transfer is always performed in mode
         * `GNOME_VFS_XFER_ERROR_MODE_QUERY'.
         */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
299

Pavel Cisler's avatar
Pavel Cisler committed
300 301
	int result;
	char *text;
302
	char *unescaped_name;
303 304 305 306
	char *current_operation;
	const char *read_only_reason;

	current_operation = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
307

Ettore Perazzoli's avatar
Ettore Perazzoli committed
308
	switch (xfer_info->error_mode) {
Pavel Cisler's avatar
Pavel Cisler committed
309 310
	case GNOME_VFS_XFER_ERROR_MODE_QUERY:

311
		unescaped_name = gnome_vfs_unescape_string_for_display (progress_info->source_name);
Pavel Cisler's avatar
Pavel Cisler committed
312
		/* transfer error, prompt the user to continue or stop */
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 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365

		current_operation = g_strdup (xfer_info->progress_verb);

		g_strdown (current_operation);
		if (progress_info->vfs_status == GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM
			|| progress_info->vfs_status == GNOME_VFS_ERROR_READ_ONLY
			|| progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED) {

			read_only_reason = progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
				? "You do not have the required permissions to do that"
				: "The destination is read-only";

			text = g_strdup_printf ( _("Error while %s \"%s\".\n"
				"%s. "
				"Would you like to continue?"),
				current_operation,
				unescaped_name,
				read_only_reason);

			result = nautilus_simple_dialog
				(xfer_info->parent_view, text, 
				 _("File copy error"),
				 _("Skip"), _("Stop"), NULL);

			g_free (current_operation);

			switch (result) {
			case 0:
				return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
			case 2:
				return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
			}						
		} else {

			text = g_strdup_printf ( _("Error \"%s\" copying file %s.\n"
				"Would you like to continue?"), 
				gnome_vfs_result_to_string (progress_info->vfs_status),
				unescaped_name);
			g_free (unescaped_name);
			result = nautilus_simple_dialog
				(xfer_info->parent_view, text, 
				 _("File copy error"),
				 _("Skip"), _("Retry"), _("Stop"), NULL);

			switch (result) {
			case 0:
				return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
			case 1:
				return GNOME_VFS_XFER_ERROR_ACTION_RETRY;
			case 2:
				return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
			}						
		}
Pavel Cisler's avatar
Pavel Cisler committed
366

Ettore Perazzoli's avatar
Ettore Perazzoli committed
367 368
	case GNOME_VFS_XFER_ERROR_MODE_ABORT:
	default:
369
		if (xfer_info->progress_dialog != NULL) {
370
			nautilus_file_operations_progress_freeze (NAUTILUS_FILE_OPERATIONS_PROGRESS
371
							  (xfer_info->progress_dialog));
372
			nautilus_file_operations_progress_thaw (NAUTILUS_FILE_OPERATIONS_PROGRESS
373 374 375
							(xfer_info->progress_dialog));
			gtk_widget_destroy (xfer_info->progress_dialog);
		}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
376 377 378 379
		return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
	}
}

Pavel Cisler's avatar
Pavel Cisler committed
380
static int
Ettore Perazzoli's avatar
Ettore Perazzoli committed
381 382 383
handle_xfer_overwrite (const GnomeVFSXferProgressInfo *progress_info,
		       XferInfo *xfer_info)
{
Pavel Cisler's avatar
Pavel Cisler committed
384 385 386
	/* transfer conflict, prompt the user to replace or skip */
	int result;
	char *text;
387
	char *unescaped_name;
Pavel Cisler's avatar
Pavel Cisler committed
388

389
	unescaped_name = gnome_vfs_unescape_string_for_display (progress_info->target_name);
Darin Adler's avatar
Darin Adler committed
390
	text = g_strdup_printf ( _("File %s already exists.\n"
Pavel Cisler's avatar
Pavel Cisler committed
391
		"Would you like to replace it?"), 
392 393
		unescaped_name);
	g_free (unescaped_name);
394 395 396 397 398

	if (progress_info->duplicate_count == 1) {
		/* we are going to only get one duplicate alert, don't offer
		 * Replace All
		 */
399 400 401 402
		result = nautilus_simple_dialog
			(xfer_info->parent_view, text, 
			 _("File copy conflict"),
			 _("Replace"), _("Skip"), NULL);
403 404 405 406 407 408 409
		switch (result) {
		case 0:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE;
		case 1:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP;
		}
	} else {
410 411 412 413
		result = nautilus_simple_dialog
			(xfer_info->parent_view, text, 
			 _("File copy conflict"),
			 _("Replace All"), _("Replace"), _("Skip"), NULL);
414 415 416 417 418 419 420 421 422

		switch (result) {
		case 0:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE_ALL;
		case 1:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE;
		case 2:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP;
		}
Pavel Cisler's avatar
Pavel Cisler committed
423 424 425 426 427
	}

	return 0;					
}

428
/* Note that we have these two separate functions with separate format
429
 * strings for ease of localization. Resist the urge to "optimize" them
430 431 432 433 434
 * for English.
 */

static char *
get_link_name (const char *name, int count) 
435
{
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
	g_assert (name != NULL);

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

	/* Handle special cases for low numbers.
	 * Perhaps for some locales we will need to add more.
	 */
	switch (count) {
	case 1:
		return g_strdup_printf (_("link to %s"), name);
	case 2:
		return g_strdup_printf (_("another link to %s"), name);
	}

	/* 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.
	 */
	switch (count % 10) {
	case 1:
		return g_strdup_printf (_("%dst link to %s"), count, name);
	case 2:
		return g_strdup_printf (_("%dnd link to %s"), count, name);
	case 3:
		return g_strdup_printf (_("%drd link to %s"), count, name);
	}

	/* The general case. */
	return g_strdup_printf (_("%dth link to %s"), count, name);
}

static char *
get_duplicate_name (const char *name, int count) 
{
	g_assert (name != NULL);

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

	/* Handle special cases for low numbers.
	 * Perhaps for some locales we will need to add more.
	 */
	switch (count) {
	case 1:
		return g_strdup_printf (_("%s (copy)"), name);
	case 2:
		return g_strdup_printf (_("%s (another copy)"), name);
488
	}
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504

	/* 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.
	 */
	switch (count % 10) {
	case 1:
		return g_strdup_printf (_("%s (%dst copy)"), name, count);
	case 2:
		return g_strdup_printf (_("%s (%dnd copy)"), name, count);
	case 3:
		return g_strdup_printf (_("%s (%drd copy)"), name, count);
	}

	/* The general case. */
	return g_strdup_printf (_("%s (%dth copy)"), name, count);
505 506
}

Pavel Cisler's avatar
Pavel Cisler committed
507 508 509 510
static int
handle_xfer_duplicate (GnomeVFSXferProgressInfo *progress_info,
		       XferInfo *xfer_info)
{
511 512
	switch (xfer_info->kind) {
	case XFER_LINK:
513 514 515
		/* FIXME bugzilla.eazel.com 2556: We overwrite the old name here. 
		 * Is this a storage leak?
		 */
516 517 518
		progress_info->duplicate_name = get_link_name
			(progress_info->duplicate_name,
			 progress_info->duplicate_count);
519 520 521
		break;

	case XFER_COPY:
522 523 524
		/* FIXME bugzilla.eazel.com 2556: We overwrite the old name here. 
		 * Is this a storage leak? 
		 */
525 526 527
		progress_info->duplicate_name = get_duplicate_name
			(progress_info->duplicate_name,
			 progress_info->duplicate_count);
528
		break;
529

530
	default:
531
		/* For all other cases we use the name as-is. */
Pavel Cisler's avatar
Pavel Cisler committed
532
	}
533

Pavel Cisler's avatar
Pavel Cisler committed
534
	return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
535 536
}

Pavel Cisler's avatar
Pavel Cisler committed
537
static int
538
update_xfer_callback (GnomeVFSAsyncHandle *handle,
Pavel Cisler's avatar
Pavel Cisler committed
539
	       GnomeVFSXferProgressInfo *progress_info,
Ettore Perazzoli's avatar
Ettore Perazzoli committed
540 541 542 543 544 545 546 547 548 549 550 551 552
	       gpointer data)
{
	XferInfo *xfer_info;

	xfer_info = (XferInfo *) data;

	switch (progress_info->status) {
	case GNOME_VFS_XFER_PROGRESS_STATUS_OK:
		return handle_xfer_ok (progress_info, xfer_info);
	case GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR:
		return handle_xfer_vfs_error (progress_info, xfer_info);
	case GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE:
		return handle_xfer_overwrite (progress_info, xfer_info);
Pavel Cisler's avatar
Pavel Cisler committed
553 554
	case GNOME_VFS_XFER_PROGRESS_STATUS_DUPLICATE:
		return handle_xfer_duplicate (progress_info, xfer_info);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
555 556 557
	default:
		g_warning (_("Unknown GnomeVFSXferProgressStatus %d"),
			   progress_info->status);
558
		return 0;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
559 560 561
	}
}

562 563 564
/* Low-level callback, called for every copy engine operation.
 * Generates notifications about new, deleted and moved files.
 */
Pavel Cisler's avatar
Pavel Cisler committed
565
static int
566
sync_xfer_callback (GnomeVFSXferProgressInfo *progress_info, gpointer data)
Pavel Cisler's avatar
Pavel Cisler committed
567
{
568 569 570
	if (progress_info->status == GNOME_VFS_XFER_PROGRESS_STATUS_OK) {
		switch (progress_info->phase) {
		case GNOME_VFS_XFER_PHASE_OPENTARGET:
571 572 573 574 575 576 577 578
			if (progress_info->top_level_item) {
				/* this is one of the selected copied or moved items -- we need
				 * to make sure it's metadata gets copied over
				 */
				g_assert (progress_info->source_name != NULL);
				nautilus_file_changes_queue_schedule_metadata_copy 
					(progress_info->source_name, progress_info->target_name);
			}
579 580 581 582
			nautilus_file_changes_queue_file_added (progress_info->target_name);
			break;

		case GNOME_VFS_XFER_PHASE_MOVING:
583 584 585 586 587
			if (progress_info->top_level_item) {
				g_assert (progress_info->source_name != NULL);
				nautilus_file_changes_queue_schedule_metadata_move 
					(progress_info->source_name, progress_info->target_name);
			}
588 589 590 591 592 593 594 595 596 597 598 599 600
			nautilus_file_changes_queue_file_moved (progress_info->source_name,
				progress_info->target_name);
			break;

		case GNOME_VFS_XFER_PHASE_DELETESOURCE:
			nautilus_file_changes_queue_file_removed (progress_info->source_name);
			break;

		default:
			break;
		}
	}
	return 1;
Pavel Cisler's avatar
Pavel Cisler committed
601 602
}

603 604
static gboolean
check_target_directory_is_or_in_trash (GnomeVFSURI *trash_dir_uri, GnomeVFSURI *target_dir_uri)
605
{
606
	g_assert (target_dir_uri != NULL);
607

608 609 610
	if (trash_dir_uri == NULL) {
		return FALSE;
	}
611

612 613 614
	return gnome_vfs_uri_equal (trash_dir_uri, target_dir_uri)
		|| gnome_vfs_uri_is_parent (trash_dir_uri, target_dir_uri, TRUE);
}
615

616 617 618 619 620 621 622 623 624
static GnomeVFSURI *
new_uri_from_escaped_string (const char *string)
{
	char *unescaped_string;
	GnomeVFSURI *result;
	
	unescaped_string = gnome_vfs_unescape_string (string, "/");
	result = gnome_vfs_uri_new (unescaped_string);
	g_free (unescaped_string);
625

626 627
	return result;
}
628

629 630 631 632 633
static GnomeVFSURI *
append_basename_unescaped (const GnomeVFSURI *target_directory, const GnomeVFSURI *source_directory)
{
	return gnome_vfs_uri_append_path (target_directory, 
		gnome_vfs_uri_get_basename (source_directory));
634 635
}

636

Pavel Cisler's avatar
Pavel Cisler committed
637
void
638 639 640 641 642
nautilus_file_operations_copy_move (const GList *item_uris,
				    const GdkPoint *relative_item_points,
				    const char *target_dir,
				    int copy_action,
				    GtkWidget *view)
Pavel Cisler's avatar
Pavel Cisler committed
643
{
644
	const GList *p;
Pavel Cisler's avatar
Pavel Cisler committed
645
	GnomeVFSXferOptions move_options;
646 647
	GList *source_uri_list, *target_uri_list;
	GnomeVFSURI *source_uri, *target_uri;
Pavel Cisler's avatar
Pavel Cisler committed
648
	GnomeVFSURI *target_dir_uri;
649 650
	GnomeVFSURI *trash_dir_uri;
	GnomeVFSURI *uri;
651 652

	
Pavel Cisler's avatar
Pavel Cisler committed
653
	XferInfo *xfer_info;
654
	GnomeVFSResult result;
655
	gboolean same_fs;
Pavel Cisler's avatar
Pavel Cisler committed
656 657
	
	g_assert (item_uris != NULL);
658

659
	target_dir_uri = NULL;
660 661
	trash_dir_uri = NULL;
	result = GNOME_VFS_OK;
Pavel Cisler's avatar
Pavel Cisler committed
662

663 664 665
	source_uri_list = NULL;
	target_uri_list = NULL;
	same_fs = TRUE;
Pavel Cisler's avatar
Pavel Cisler committed
666 667 668

	move_options = GNOME_VFS_XFER_RECURSIVE;

669 670
	if (target_dir == NULL) {
		/* assume duplication */
671
		g_assert (copy_action != GDK_ACTION_MOVE);
Pavel Cisler's avatar
Pavel Cisler committed
672
		move_options |= GNOME_VFS_XFER_USE_UNIQUE_NAMES;
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
	} else {
		target_dir_uri = new_uri_from_escaped_string (target_dir);
	}


	/* build the source and target URI lists and figure out if all the files are on the
	 * same disk
	 */
	for (p = item_uris; p != NULL; p = p->next) {
		source_uri = new_uri_from_escaped_string ((const char *)p->data);
		source_uri_list = g_list_prepend (source_uri_list, gnome_vfs_uri_ref (source_uri));

		if (target_dir != NULL) {
			target_uri = append_basename_unescaped (target_dir_uri, source_uri);
		} else {
			/* duplication */
			target_uri = gnome_vfs_uri_ref (source_uri);
			if (target_dir_uri == NULL) {
				target_dir_uri = gnome_vfs_uri_get_parent (source_uri);
			}
		}
		target_uri_list = g_list_prepend (target_uri_list, target_uri);
		gnome_vfs_check_same_fs_uris (source_uri, target_uri, &same_fs);
696 697
	}

698 699 700 701
	source_uri_list = g_list_reverse (source_uri_list);
	target_uri_list = g_list_reverse (target_uri_list);


702
	if (copy_action == GDK_ACTION_MOVE) {
Pavel Cisler's avatar
Pavel Cisler committed
703
		move_options |= GNOME_VFS_XFER_REMOVESOURCE;
704 705
	} else if (copy_action == GDK_ACTION_LINK) {
		move_options |= GNOME_VFS_XFER_LINK_ITEMS;
Pavel Cisler's avatar
Pavel Cisler committed
706
	}
707
	
Pavel Cisler's avatar
Pavel Cisler committed
708 709 710 711 712
	/* set up the copy/move parameters */
	xfer_info = g_new (XferInfo, 1);
	xfer_info->parent_view = view;
	xfer_info->progress_dialog = NULL;

713
	if ((move_options & GNOME_VFS_XFER_REMOVESOURCE) != 0) {
Pavel Cisler's avatar
Pavel Cisler committed
714 715 716
		xfer_info->operation_title = _("Moving files");
		xfer_info->action_verb =_("moved");
		xfer_info->progress_verb =_("Moving");
717
		xfer_info->preparation_name =_("Preparing To Move...");
Pavel Cisler's avatar
Pavel Cisler committed
718 719
		xfer_info->cleanup_name = _("Finishing Move...");

720
		xfer_info->kind = XFER_MOVE;
721 722 723
		/* Do an arbitrary guess that an operation will take very little
		 * time and the progress shouldn't be shown.
		 */
724 725
		xfer_info->show_progress_dialog = 
			!same_fs || g_list_length ((GList *)item_uris) > 20;
726
	} else if ((move_options & GNOME_VFS_XFER_LINK_ITEMS) != 0) {
727
		xfer_info->operation_title = _("Creating links to files");
728
		xfer_info->action_verb =_("linked");
729
		xfer_info->progress_verb =_("Linking");
730
		xfer_info->preparation_name = _("Preparing to Create Links...");
731
		xfer_info->cleanup_name = _("Finishing Creating Links...");
732 733 734 735

		xfer_info->kind = XFER_LINK;
		xfer_info->show_progress_dialog =
			g_list_length ((GList *)item_uris) > 20;
736
	} else {
Pavel Cisler's avatar
Pavel Cisler committed
737 738 739
		xfer_info->operation_title = _("Copying files");
		xfer_info->action_verb =_("copied");
		xfer_info->progress_verb =_("Copying");
740
		xfer_info->preparation_name =_("Preparing To Copy...");
Pavel Cisler's avatar
Pavel Cisler committed
741 742
		xfer_info->cleanup_name = "";

743
		xfer_info->kind = XFER_COPY;
744 745
		/* always show progress during copy */
		xfer_info->show_progress_dialog = TRUE;
746
	}
Pavel Cisler's avatar
Pavel Cisler committed
747

748

749 750
	/* we'll need to check for copy into Trash and for moving/copying the Trash itself */
	gnome_vfs_find_directory (target_dir_uri, GNOME_VFS_DIRECTORY_KIND_TRASH,
751
		&trash_dir_uri, FALSE, FALSE, 0777);
752 753

	if ((move_options & GNOME_VFS_XFER_REMOVESOURCE) == 0) {
754 755
		/* don't allow copying into Trash */
		if (check_target_directory_is_or_in_trash (trash_dir_uri, target_dir_uri)) {
756 757 758 759
			nautilus_simple_dialog (view, 
				_("You cannot copy items into the Trash."), 
				_("Error copying"),
				_("OK"), NULL, NULL);			
760
			result = GNOME_VFS_ERROR_NOT_PERMITTED;
761 762 763 764
		}
	}

	if (result == GNOME_VFS_OK) {
765 766
		for (p = source_uri_list; p != NULL; p = p->next) {
			uri = (GnomeVFSURI *)p->data;
767 768 769 770 771 772 773 774 775

			/* Check that the Trash is not being moved/copied */
			if (trash_dir_uri != NULL && gnome_vfs_uri_equal (uri, trash_dir_uri)) {
				nautilus_simple_dialog (view, 
					((move_options & GNOME_VFS_XFER_REMOVESOURCE) != 0) 
					? _("You cannot move the Trash.")
					: _("You cannot copy the Trash."), 
					_("Error moving to Trash"),
					_("OK"), NULL, NULL);			
776 777 778

				result = GNOME_VFS_ERROR_NOT_PERMITTED;
				break;
779 780 781 782 783 784
			}

			/* Don't allow recursive move/copy into itself. 
			 * (We would get a file system error if we proceeded but it is nicer to
			 * detect and report it at this level)
			 */
785 786 787
			if ((move_options & GNOME_VFS_XFER_LINK_ITEMS) == 0
				&& (gnome_vfs_uri_equal (uri, target_dir_uri)
					|| gnome_vfs_uri_is_parent (uri, target_dir_uri, TRUE))) {
788 789 790 791 792 793
				nautilus_simple_dialog (view, 
					((move_options & GNOME_VFS_XFER_REMOVESOURCE) != 0) 
					? _("You cannot move an item into itself.")
					: _("You cannot copy an item into itself."), 
					_("Error moving to Trash"),
					_("OK"), NULL, NULL);			
794

795
				result = GNOME_VFS_ERROR_NOT_PERMITTED;
796 797 798 799 800
				break;
			}
		}
	}

Pavel Cisler's avatar
Pavel Cisler committed
801 802 803
	xfer_info->error_mode = GNOME_VFS_XFER_ERROR_MODE_QUERY;
	xfer_info->overwrite_mode = GNOME_VFS_XFER_OVERWRITE_MODE_QUERY;
	
804
	if (result == GNOME_VFS_OK) {
805
		gnome_vfs_async_xfer (&xfer_info->handle, source_uri_list, target_uri_list,
806 807 808
		      		      move_options, GNOME_VFS_XFER_ERROR_MODE_QUERY, 
		      		      GNOME_VFS_XFER_OVERWRITE_MODE_QUERY,
		      		      &update_xfer_callback, xfer_info,
809
		      		      &sync_xfer_callback, NULL);
810
	}
Pavel Cisler's avatar
Pavel Cisler committed
811

812 813 814
	if (trash_dir_uri != NULL) {
		gnome_vfs_uri_unref (trash_dir_uri);
	}
815
	gnome_vfs_uri_unref (target_dir_uri);
Pavel Cisler's avatar
Pavel Cisler committed
816
}
817

818 819 820 821 822 823 824 825 826 827 828
typedef struct {
	GnomeVFSAsyncHandle *handle;
	void (* done_callback)(const char *new_folder_uri, gpointer data);
	gpointer data;
} NewFolderXferState;

static int
new_folder_xfer_callback (GnomeVFSAsyncHandle *handle,
	GnomeVFSXferProgressInfo *progress_info, gpointer data)
{
	NewFolderXferState *state;
829
	char *temp_string;
830 831 832 833 834 835 836 837 838 839 840
	
	state = (NewFolderXferState *) data;

	switch (progress_info->status) {
	case GNOME_VFS_XFER_PROGRESS_STATUS_OK:
		nautilus_file_changes_consume_changes (TRUE);
		(state->done_callback) (progress_info->target_name, state->data);
		g_free (state);
		return 0;

	case GNOME_VFS_XFER_PROGRESS_STATUS_DUPLICATE:
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858

		temp_string = progress_info->duplicate_name;

		if (progress_info->vfs_status == GNOME_VFS_ERROR_NAME_TOO_LONG) {
			/* special case an 8.3 file system */
			progress_info->duplicate_name = g_strndup (temp_string, 8);
			progress_info->duplicate_name[8] = '\0';
			g_free (temp_string);
			temp_string = progress_info->duplicate_name;
			progress_info->duplicate_name = g_strdup_printf ("%s.%d", 
				progress_info->duplicate_name,
				progress_info->duplicate_count);
		} else {
			progress_info->duplicate_name = g_strdup_printf ("%s %d", 
				progress_info->duplicate_name,
				progress_info->duplicate_count);
		}
		g_free (temp_string);
859 860 861 862 863 864 865 866 867 868 869
		return GNOME_VFS_XFER_ERROR_ACTION_SKIP;

	default:
		g_warning (_("Unknown GnomeVFSXferProgressStatus %d"),
			   progress_info->status);
		return 0;
	}
}


void 
870 871 872 873
nautilus_file_operations_new_folder (GtkWidget *parent_view, 
				     const char *parent_dir,
				     void (*done_callback)(const char *, gpointer),
				     gpointer data)
874 875
{
	NewFolderXferState *state;
876 877
	GList *target_uri_list;
	GnomeVFSURI *uri, *parent_uri;
878 879 880 881 882

	state = g_new (NewFolderXferState, 1);
	state->done_callback = done_callback;
	state->data = data;

883 884 885 886 887 888
	/* pass in the target directory and the new folder name as a destination URI */
	parent_uri = new_uri_from_escaped_string (parent_dir);
	uri = gnome_vfs_uri_append_path (parent_uri, _("untitled folder"));
	target_uri_list = g_list_append (NULL, uri);
	
	gnome_vfs_async_xfer (&state->handle, NULL, target_uri_list,
889
	      		      GNOME_VFS_XFER_NEW_UNIQUE_DIRECTORY,
890 891 892 893
	      		      GNOME_VFS_XFER_ERROR_MODE_QUERY, 
	      		      GNOME_VFS_XFER_OVERWRITE_MODE_QUERY,
	      		      &new_folder_xfer_callback, state,
	      		      &sync_xfer_callback, NULL);
894 895 896

	gnome_vfs_uri_list_free (target_uri_list);
	gnome_vfs_uri_unref (parent_uri);
897 898
}

899
void 
900 901
nautilus_file_operations_move_to_trash (const GList *item_uris, 
					GtkWidget *parent_view)
902
{
903
	const GList *p;
904
	GnomeVFSURI *trash_dir_uri;
905 906
	GnomeVFSURI *source_uri;
	GList *source_uri_list, *target_uri_list;
907 908
	GnomeVFSResult result;
	XferInfo *xfer_info;
909 910 911
	gboolean bail;
	char *text;
	char *item_name;
912 913 914 915

	g_assert (item_uris != NULL);
	
	trash_dir_uri = NULL;
916 917
	source_uri_list = NULL;
	target_uri_list = NULL;
918

919 920
	result = GNOME_VFS_OK;

921
	/* build the source and uri list, checking if any of the delete itmes are Trash */
922 923
	for (p = item_uris; p != NULL; p = p->next) {
		bail = FALSE;
924 925 926 927 928 929 930 931 932 933 934
		source_uri = new_uri_from_escaped_string ((const char *)p->data);
		source_uri_list = g_list_prepend (source_uri_list, source_uri);

		if (trash_dir_uri == NULL) {
			GnomeVFSURI *source_dir_uri;
			
			source_dir_uri = gnome_vfs_uri_get_parent (source_uri);
			result = gnome_vfs_find_directory (source_dir_uri, GNOME_VFS_DIRECTORY_KIND_TRASH,
				&trash_dir_uri, FALSE, FALSE, 0777);
			gnome_vfs_uri_unref (source_dir_uri);
		}
935

936 937 938 939 940 941 942 943 944
		if (result != GNOME_VFS_OK) {
			break;
		}

		g_assert (trash_dir_uri != NULL);
		target_uri_list = g_list_prepend (target_uri_list,
			append_basename_unescaped (trash_dir_uri, source_uri));
		
		if (gnome_vfs_uri_equal (source_uri, trash_dir_uri)) {
945 946 947 948 949
			nautilus_simple_dialog (parent_view, 
				_("You cannot throw away the Trash."), 
				_("Error moving to Trash"),
				_("OK"), NULL, NULL);			
			bail = TRUE;
950
		} else if (gnome_vfs_uri_is_parent (source_uri, trash_dir_uri, TRUE)) {
951
			item_name = nautilus_convert_to_unescaped_string_for_display 
952
				(gnome_vfs_uri_extract_short_name (source_uri));
953 954 955 956 957 958 959 960 961 962
			text = g_strdup_printf ( _("You cannot throw \"%s\" "
				"into the Trash."), item_name);

			nautilus_simple_dialog (parent_view, text, 
				_("Error moving to Trash"),
				_("OK"), NULL, NULL);			
			bail = TRUE;
			g_free (text);
			g_free (item_name);
		}
963

964
		if (bail) {
965
			result = GNOME_VFS_ERROR_NOT_PERMITTED;
966 967 968
			break;
		}
	}
969 970
	source_uri_list = g_list_reverse (source_uri_list);
	target_uri_list = g_list_reverse (target_uri_list);
971

972 973 974 975 976 977 978 979
	if (result == GNOME_VFS_OK) {
		g_assert (trash_dir_uri != NULL);

		/* set up the move parameters */
		xfer_info = g_new (XferInfo, 1);
		xfer_info->parent_view = parent_view;
		xfer_info->progress_dialog = NULL;

980 981 982 983 984
		/* Do an arbitrary guess that an operation will take very little
		 * time and the progress shouldn't be shown.
		 */
		xfer_info->show_progress_dialog = g_list_length ((GList *)item_uris) > 20;

Pavel Cisler's avatar
Pavel Cisler committed
985 986 987
		xfer_info->operation_title = _("Moving files to the Trash");
		xfer_info->action_verb =_("thrown out");
		xfer_info->progress_verb =_("Moving");
988
		xfer_info->preparation_name =_("Preparing to Move to Trash...");
Pavel Cisler's avatar
Pavel Cisler committed
989 990
		xfer_info->cleanup_name ="";

991 992
		xfer_info->error_mode = GNOME_VFS_XFER_ERROR_MODE_QUERY;
		xfer_info->overwrite_mode = GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE;
993
		xfer_info->kind = XFER_MOVE_TO_TRASH;
994
		
995
		gnome_vfs_async_xfer (&xfer_info->handle, source_uri_list, target_uri_list,
996 997 998 999
		      		      GNOME_VFS_XFER_REMOVESOURCE | GNOME_VFS_XFER_USE_UNIQUE_NAMES,
		      		      GNOME_VFS_XFER_ERROR_MODE_QUERY, 
		      		      GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE,
		      		      &update_xfer_callback, xfer_info,
1000
		      		      &sync_xfer_callback, NULL);
1001 1002 1003

	}

1004 1005
	gnome_vfs_uri_list_free (source_uri_list);
	gnome_vfs_uri_list_free (target_uri_list);
1006
	gnome_vfs_uri_unref (trash_dir_uri);
1007 1008 1009
}

void 
1010 1011
nautilus_file_operations_delete (const GList *item_uris, 
				 GtkWidget *parent_view)
1012
{
1013 1014
	GList *uri_list;
	const GList *p;
1015 1016
	XferInfo *xfer_info;

1017 1018 1019 1020 1021 1022
	uri_list = NULL;
	for (p = item_uris; p != NULL; p = p->next) {
		uri_list = g_list_prepend (uri_list, 
			new_uri_from_escaped_string ((const char *)p->data));
	}
	uri_list = g_list_reverse (uri_list);
1023 1024 1025 1026 1027

	xfer_info = g_new (XferInfo, 1);
	xfer_info->parent_view = parent_view;
	xfer_info->progress_dialog = NULL;
	xfer_info->show_progress_dialog = TRUE;
Pavel Cisler's avatar
Pavel Cisler committed
1028 1029 1030 1031 1032 1033 1034

	xfer_info->operation_title = _("Deleting files");
	xfer_info->action_verb =_("deleted");
	xfer_info->progress_verb =_("Deleting");
	xfer_info->preparation_name =_("Preparing to Delete files...");
	xfer_info->cleanup_name ="";

1035 1036 1037