nautilus-file-operations.c 104 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.
Ettore Perazzoli's avatar
Ettore Perazzoli committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

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

#include <config.h>
28
#include <string.h>
29
#include <stdio.h>
30
#include <locale.h>
31
#include "nautilus-file-operations.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
32

Alexander Larsson's avatar
 
Alexander Larsson committed
33
#include "nautilus-debug-log.h"
34 35
#include "nautilus-file-operations-progress.h"
#include "nautilus-lib-self-check-functions.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
36 37

#include <eel/eel-glib-extensions.h>
38
#include <eel/eel-pango-extensions.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
39 40
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-stock-dialogs.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
41 42
#include <eel/eel-vfs-extensions.h>

43
#include <glib/gstdio.h>
Ettore Perazzoli's avatar
Ettore Perazzoli committed
44
#include <gnome.h>
45
#include <gdk/gdkdnd.h>
46
#include <gtk/gtklabel.h>
47
#include <gtk/gtkmessagedialog.h>
48
#include <gtk/gtkwidget.h>
49
#include <libgnomevfs/gnome-vfs-async-ops.h>
50
#include <libgnomevfs/gnome-vfs-find-directory.h>
51 52
#include <libgnomevfs/gnome-vfs-ops.h>
#include <libgnomevfs/gnome-vfs-result.h>
Pavel Cisler's avatar
Pavel Cisler committed
53
#include <libgnomevfs/gnome-vfs-uri.h>
54
#include <libgnomevfs/gnome-vfs-utils.h>
55 56
#include <libgnomevfs/gnome-vfs-volume.h>
#include <libgnomevfs/gnome-vfs-volume-monitor.h>
57
#include "nautilus-file-changes-queue.h"
Alexander Larsson's avatar
Alexander Larsson committed
58 59 60
#include "nautilus-file-private.h"
#include "nautilus-desktop-icon-file.h"
#include "nautilus-desktop-link-monitor.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
61 62 63
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
#include "nautilus-trash-monitor.h"
64
#include "nautilus-file-utilities.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
65

66 67 68 69 70
typedef enum   TransferKind         TransferKind;
typedef struct TransferInfo         TransferInfo;
typedef struct IconPositionIterator IconPositionIterator;

enum TransferKind {
71 72 73 74 75 76 77
	TRANSFER_MOVE,
	TRANSFER_COPY,
	TRANSFER_DUPLICATE,
	TRANSFER_MOVE_TO_TRASH,
	TRANSFER_EMPTY_TRASH,
	TRANSFER_DELETE,
	TRANSFER_LINK
78
};
79

80
/* Copy engine callback state */
81
struct TransferInfo {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
82
	GnomeVFSAsyncHandle *handle;
83
	NautilusFileOperationsProgress *progress_dialog;
Pavel Cisler's avatar
Pavel Cisler committed
84
	const char *operation_title;	/* "Copying files" */
85
	const char *action_label;	/* "Files copied:" */
Pavel Cisler's avatar
Pavel Cisler committed
86 87 88
	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
89 90
	GnomeVFSXferErrorMode error_mode;
	GnomeVFSXferOverwriteMode overwrite_mode;
Pavel Cisler's avatar
Pavel Cisler committed
91
	GtkWidget *parent_view;
92
	TransferKind kind;
93
	void (* done_callback) (GHashTable *debuting_uris, gpointer data);
94
	gpointer done_callback_data;
95 96
	GHashTable *debuting_uris;
	gboolean cancelled;	
97 98
	IconPositionIterator *iterator;
};
Ettore Perazzoli's avatar
Ettore Perazzoli committed
99

100 101 102 103 104 105 106 107
static TransferInfo *
transfer_info_new (GtkWidget *parent_view)
{
	TransferInfo *result;
	
	result = g_new0 (TransferInfo, 1);
	result->parent_view = parent_view;
	
Darin Adler's avatar
Darin Adler committed
108
	eel_add_weak_pointer (&result->parent_view);
109 110 111 112 113 114 115
	
	return result;
}

static void
transfer_info_destroy (TransferInfo *transfer_info)
{
Darin Adler's avatar
Darin Adler committed
116
	eel_remove_weak_pointer (&transfer_info->parent_view);
117 118
	
	if (transfer_info->progress_dialog != NULL) {
119
		nautilus_file_operations_progress_done (transfer_info->progress_dialog);
120 121 122
	}
	
	if (transfer_info->debuting_uris != NULL) {
123
		g_hash_table_destroy (transfer_info->debuting_uris);
124 125 126 127 128
	}
	
	g_free (transfer_info);
}

129 130 131 132
/* Struct used to control applying icon positions to 
 * top level items during a copy, drag, new folder creation and
 * link creation
 */
133
struct IconPositionIterator {
134 135 136 137
	GdkPoint *icon_positions;
	int last_icon_position_index;
	GList *uris;
	const GList *last_uri;
138
	int screen;
139 140
	gboolean is_source_iterator;
};
141 142

static IconPositionIterator *
143 144 145 146
icon_position_iterator_new (GArray *icon_positions,
			    const GList *uris,
			    int screen,
			    gboolean is_source_iterator)
147 148
{
	IconPositionIterator *result;
149
	guint index;
150

151
	g_assert (icon_positions->len == g_list_length ((GList *)uris));
152 153
	result = g_new (IconPositionIterator, 1);
	
154
	/* make our own copy of the icon locations */
155
	result->icon_positions = g_new (GdkPoint, icon_positions->len);
156 157 158
	for (index = 0; index < icon_positions->len; index++) {
		result->icon_positions[index] = g_array_index (icon_positions, GdkPoint, index);
	}
159 160
	result->last_icon_position_index = 0;

Ramiro Estrugo's avatar
Ramiro Estrugo committed
161
	result->uris = eel_g_str_list_copy ((GList *)uris);
162
	result->last_uri = result->uris;
163
	result->screen = screen;
164
	result->is_source_iterator = is_source_iterator;
165 166 167 168

	return result;
}

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
static IconPositionIterator *
icon_position_iterator_new_single (GdkPoint *icon_position,
				   const char *uri,
				   int screen,
				   gboolean is_source_iterator)
{
	IconPositionIterator *iterator;
	GArray *icon_positions;
	GList *uris;

	if (icon_position == NULL || uri == NULL) {
		return NULL;
	}

	icon_positions = g_array_sized_new (FALSE, FALSE, sizeof (GdkPoint), 1);
	g_array_insert_val (icon_positions, 0, *icon_position);

	uris = g_list_append (NULL, (char *) uri);

	iterator = icon_position_iterator_new (icon_positions, uris, screen, is_source_iterator);

	g_list_free (uris);
	g_array_free (icon_positions, TRUE);

	return iterator;
}

196 197 198 199 200 201 202 203
static void
icon_position_iterator_free (IconPositionIterator *position_iterator)
{
	if (position_iterator == NULL) {
		return;
	}
	
	g_free (position_iterator->icon_positions);
Ramiro Estrugo's avatar
Ramiro Estrugo committed
204
	eel_g_list_free_deep (position_iterator->uris);
205 206 207
	g_free (position_iterator);
}

208 209
static gboolean
icon_position_iterator_get_next (IconPositionIterator *position_iterator,
210 211
				 const char *next_source_uri,
				 const char *next_target_uri,
212 213
				 GdkPoint *point)
{
214 215
	const char *next_uri;

216 217 218
	if (position_iterator == NULL) {
		return FALSE;
	}
219 220 221 222 223 224 225

	if (position_iterator->is_source_iterator) {
		next_uri = next_source_uri;
	} else {
		next_uri = next_target_uri;
	}

226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
	for (;;) {
		if (position_iterator->last_uri == NULL) {
			/* we are done, no more points left */
			return FALSE;
		}

		/* Scan for the next point that matches the source_name
		 * uri.
		 */
		if (strcmp ((const char *) position_iterator->last_uri->data, 
			    next_uri) == 0) {
			break;
		}
		
		/* Didn't match -- a uri must have been skipped by the copy 
		 * engine because of a name conflict. All we need to do is 
		 * skip ahead too.
		 */
		position_iterator->last_uri = position_iterator->last_uri->next;
		position_iterator->last_icon_position_index++; 
	}

	/* apply the location to the target file */
	*point = position_iterator->icon_positions
		[position_iterator->last_icon_position_index];

	/* advance to the next point for next time */
	position_iterator->last_uri = position_iterator->last_uri->next;
	position_iterator->last_icon_position_index++; 

	return TRUE;
}

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
static void
icon_position_iterator_update_uri (IconPositionIterator *position_iterator,
				   const char *old_uri,
				   const char *new_uri_fragment)
{
	GnomeVFSURI *uri, *parent_uri;
	GList *l;

	if (position_iterator == NULL) {
		return;
	}

	l = g_list_find_custom (position_iterator->uris,
			        old_uri,
				(GCompareFunc) strcmp);
	if (l == NULL) {
		return;
	}

	uri = gnome_vfs_uri_new (old_uri);
	parent_uri = gnome_vfs_uri_get_parent (uri);
	gnome_vfs_uri_unref (uri);

	if (parent_uri == NULL) {
		return;
	}
	
Alexander Larsson's avatar
Alexander Larsson committed
286
	uri = gnome_vfs_uri_append_string (parent_uri, new_uri_fragment);
287 288 289 290 291 292 293 294

	g_free (l->data);
	l->data = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);

	gnome_vfs_uri_unref (uri);
	gnome_vfs_uri_unref (parent_uri);
}

295 296
static char *
ellipsize_string_for_dialog (PangoContext *context, const char *str)
297
{
298 299 300 301
	int maximum_width;
	char *result;
	PangoLayout *layout;
	PangoFontMetrics *metrics;
302

303
	layout = pango_layout_new (context);
304

305 306
	metrics = pango_context_get_metrics (
		context, pango_context_get_font_description (context), NULL);
307

308
	maximum_width = pango_font_metrics_get_approximate_char_width (metrics) * 25 / PANGO_SCALE;
309

310
	pango_font_metrics_unref (metrics);
311

312 313
	eel_pango_layout_set_text_ellipsized (
		layout, str, maximum_width, EEL_ELLIPSIZE_MIDDLE);
314

315
	result = g_strdup (pango_layout_get_text (layout));
316

317
	g_object_unref (layout);
318

319 320
	return result;
}
321

322
static char *
323
format_and_ellipsize_uri_for_dialog (GtkWidget *context, const char *uri)
324 325
{
	char *unescaped, *result;
326

Ramiro Estrugo's avatar
Ramiro Estrugo committed
327
	unescaped = eel_format_uri_for_display (uri);
328 329
	result = ellipsize_string_for_dialog (
		gtk_widget_get_pango_context (context), unescaped);
330
	g_free (unescaped);
331

332 333 334 335
	return result;
}

static char *
336
extract_and_ellipsize_file_name_for_dialog (GtkWidget *context, const char *uri)
337
{
338
	char *basename;
339
	char *unescaped, *result;
340
	
341 342
	basename = g_path_get_basename (uri);
	g_return_val_if_fail (basename != NULL, NULL);
343

344
	unescaped = gnome_vfs_unescape_string_for_display (basename);
345 346
	result = ellipsize_string_for_dialog (
		gtk_widget_get_pango_context (context), unescaped);
347
	g_free (unescaped);
348
	g_free (basename);
349

350 351 352
	return result;
}

353
static GtkWidget *
354
parent_for_error_dialog (TransferInfo *transfer_info)
355
{
356
	if (transfer_info->progress_dialog != NULL) {
357
		return GTK_WIDGET (transfer_info->progress_dialog);
358 359
	}

360
	return transfer_info->parent_view;
361 362
}

363 364 365 366 367 368 369 370
static void
handle_response_callback (GtkDialog *dialog, int response, TransferInfo *transfer_info)
{
	transfer_info->cancelled = TRUE;
}

static void
handle_close_callback (GtkDialog *dialog, TransferInfo *transfer_info)
371
{
372
	transfer_info->cancelled = TRUE;
373 374
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
375
static void
376
create_transfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
377
			TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
378
{
379
	g_return_if_fail (transfer_info->progress_dialog == NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
380

381
	transfer_info->progress_dialog = nautilus_file_operations_progress_new 
382
		(transfer_info->operation_title, "", "", "", 0, 0, TRUE);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
383

384 385 386
	/* Treat clicking on the close box or use of the escape key
	 * the same as clicking cancel.
	 */
387
	g_signal_connect (transfer_info->progress_dialog,
388 389
			  "response",
			  G_CALLBACK (handle_response_callback),
390
			  transfer_info);
391
	g_signal_connect (transfer_info->progress_dialog,
392 393 394
			  "close",
			  G_CALLBACK (handle_close_callback),
			  transfer_info);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
395

396
	/* Make the progress dialog show up over the window we are copying into */
397
	if (transfer_info->parent_view != NULL) {
398 399 400 401 402 403 404 405 406 407
		GtkWidget *toplevel;

		/* Transient-for-desktop are visible on all desktops, we don't want
		   that. */
		toplevel = gtk_widget_get_toplevel (transfer_info->parent_view);
		if (toplevel != NULL &&
		    g_object_get_data (G_OBJECT (toplevel), "is_desktop_window") == NULL) {
			gtk_window_set_transient_for (GTK_WINDOW (transfer_info->progress_dialog), 
						      GTK_WINDOW (toplevel));
		}
408
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
409 410
}

Pavel Cisler's avatar
Pavel Cisler committed
411
static void
412
progress_dialog_set_to_from_item_text (NautilusFileOperationsProgress *dialog,
413 414 415
				       const char *progress_verb,
				       const char *from_uri, const char *to_uri, 
				       gulong index, gulong size)
Pavel Cisler's avatar
Pavel Cisler committed
416 417 418
{
	char *item;
	char *from_path;
419
	char *from_text;
Pavel Cisler's avatar
Pavel Cisler committed
420
	char *to_path;
421
	char *to_text;
Pavel Cisler's avatar
Pavel Cisler committed
422
	char *progress_label_text;
423
	const char *hostname;
Pavel Cisler's avatar
Pavel Cisler committed
424 425 426
	const char *from_prefix;
	const char *to_prefix;
	GnomeVFSURI *uri;
427
	int length;
Pavel Cisler's avatar
Pavel Cisler committed
428 429

	item = NULL;
430 431
	from_text = NULL;
	to_text = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
432 433 434 435 436 437
	from_prefix = "";
	to_prefix = "";
	progress_label_text = NULL;

	if (from_uri != NULL) {
		uri = gnome_vfs_uri_new (from_uri);
438
		item = nautilus_get_uri_shortname_for_display (uri);
Pavel Cisler's avatar
Pavel Cisler committed
439
		from_path = gnome_vfs_uri_extract_dirname (uri);
440
		hostname = NULL;
441 442 443 444 445 446

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

448
		if (strcmp (uri->method_string, "file") != 0) {
449 450 451
			hostname = gnome_vfs_uri_get_host_name (uri);
		}
		if (hostname) {
452
			from_text = g_strdup_printf (_("%s on %s"),
453
				from_path, hostname);
454
			g_free (from_path);
455 456
		} else {
			from_text = from_path;
457
		}
458
		
Pavel Cisler's avatar
Pavel Cisler committed
459 460
		gnome_vfs_uri_unref (uri);
		g_assert (progress_verb);
461
		progress_label_text = g_strdup_printf ("%s", progress_verb);
462
		/* "From" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
463 464 465 466
		from_prefix = _("From:");
	}

	if (to_uri != NULL) {
467
		uri = gnome_vfs_uri_new (to_uri);
Pavel Cisler's avatar
Pavel Cisler committed
468
		to_path = gnome_vfs_uri_extract_dirname (uri);
469
		hostname = NULL;
470 471 472 473 474 475 476

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

477
		if (strcmp (uri->method_string, "file") != 0) {
478 479 480
			hostname = gnome_vfs_uri_get_host_name (uri);
		}
		if (hostname) {
481
			to_text = g_strdup_printf (_("%s on %s"),
482
				to_path, hostname);
483
			g_free (to_path);
484 485
		} else {
			to_text = to_path;
486 487
		}

Pavel Cisler's avatar
Pavel Cisler committed
488
		gnome_vfs_uri_unref (uri);
489
		/* "To" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
490 491 492
		to_prefix = _("To:");
	}

493 494 495 496
	nautilus_file_operations_progress_new_file
		(dialog,
		 progress_label_text != NULL ? progress_label_text : "",
		 item != NULL ? item : "",
497 498
		 from_text != NULL ? from_text : "",
		 to_text != NULL ? to_text : "",
499
		 from_prefix, to_prefix, index, size);
Pavel Cisler's avatar
Pavel Cisler committed
500 501 502

	g_free (progress_label_text);
	g_free (item);
503 504
	g_free (from_text);
	g_free (to_text);
Pavel Cisler's avatar
Pavel Cisler committed
505 506
}

Pavel Cisler's avatar
Pavel Cisler committed
507
static int
508
handle_transfer_ok (const GnomeVFSXferProgressInfo *progress_info,
509
		    TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
510
{
511 512
	if (transfer_info->cancelled
		&& progress_info->phase != GNOME_VFS_XFER_PHASE_COMPLETED) {
513
		/* If cancelled, delete any partially copied files that are laying
514
		 * around and return. Don't delete the source though..
515
		 */
516
		if (progress_info->target_name != NULL
517 518
		    && progress_info->source_name != NULL
		    && strcmp (progress_info->source_name, progress_info->target_name) != 0
519
		    && progress_info->bytes_total != progress_info->bytes_copied) {
520
			GList *delete_me;
521

522
			delete_me = g_list_prepend (NULL, progress_info->target_name);
523
			nautilus_file_operations_delete (delete_me, transfer_info->parent_view, NULL, NULL);
524 525 526
			g_list_free (delete_me);
		}

527 528 529
		return 0;
	}
	
Ettore Perazzoli's avatar
Ettore Perazzoli committed
530
	switch (progress_info->phase) {
Pavel Cisler's avatar
Pavel Cisler committed
531
	case GNOME_VFS_XFER_PHASE_INITIAL:
532
		create_transfer_dialog (progress_info, transfer_info);
533
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
534 535

	case GNOME_VFS_XFER_PHASE_COLLECTING:
536
		if (transfer_info->progress_dialog != NULL) {
537
			nautilus_file_operations_progress_set_operation_string
538 539
				(transfer_info->progress_dialog,
				 transfer_info->preparation_name);
540
		}
541
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
542 543

	case GNOME_VFS_XFER_PHASE_READYTOGO:
544
		if (transfer_info->progress_dialog != NULL) {
545 546 547
			nautilus_file_operations_progress_set_operation_string
				(transfer_info->progress_dialog,
				 transfer_info->action_label);
548
			nautilus_file_operations_progress_set_total
549 550 551
				(transfer_info->progress_dialog,
				 progress_info->files_total,
				 progress_info->bytes_total);
552
		}
553
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
554
				 
555
	case GNOME_VFS_XFER_PHASE_DELETESOURCE:
556
		nautilus_file_changes_consume_changes (FALSE);
557
		if (transfer_info->progress_dialog != NULL) {
558 559 560 561 562 563 564
			progress_dialog_set_to_from_item_text
				(transfer_info->progress_dialog,
				 transfer_info->progress_verb,
				 progress_info->source_name,
				 NULL,
				 progress_info->file_index,
				 progress_info->file_size);
Pavel Cisler's avatar
Pavel Cisler committed
565

566
			nautilus_file_operations_progress_update_sizes
567
				(transfer_info->progress_dialog,
568
				 MIN (progress_info->bytes_copied, 
569
				      progress_info->bytes_total),
570
				 MIN (progress_info->total_bytes_copied,
571
				      progress_info->bytes_total));
572
		}
573
		return 1;
574 575

	case GNOME_VFS_XFER_PHASE_MOVING:
Pavel Cisler's avatar
Pavel Cisler committed
576 577
	case GNOME_VFS_XFER_PHASE_OPENSOURCE:
	case GNOME_VFS_XFER_PHASE_OPENTARGET:
578 579
		/* fall through */
	case GNOME_VFS_XFER_PHASE_COPYING:
580
		if (transfer_info->progress_dialog != NULL) {
581 582 583 584 585 586 587 588 589 590 591
			progress_dialog_set_to_from_item_text (transfer_info->progress_dialog,
							       transfer_info->progress_verb,
							       progress_info->source_name,
							       progress_info->target_name,
							       progress_info->file_index,
							       progress_info->file_size);
			nautilus_file_operations_progress_update_sizes (transfer_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
592
		}
593
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
594

595
	case GNOME_VFS_XFER_PHASE_CLEANUP:
596
		if (transfer_info->progress_dialog != NULL) {
597 598
			nautilus_file_operations_progress_clear
				(transfer_info->progress_dialog);
599
			nautilus_file_operations_progress_set_operation_string
600 601
				(transfer_info->progress_dialog,
				 transfer_info->cleanup_name);
602
		}
603
		return 1;
604

Ettore Perazzoli's avatar
Ettore Perazzoli committed
605
	case GNOME_VFS_XFER_PHASE_COMPLETED:
606
		nautilus_file_changes_consume_changes (TRUE);
607
		if (transfer_info->done_callback != NULL) {
608 609 610 611
			transfer_info->done_callback (transfer_info->debuting_uris,
						      transfer_info->done_callback_data);
			/* done_callback now owns (will free) debuting_uris */
			transfer_info->debuting_uris = NULL;
612
		}
613 614

		transfer_info_destroy (transfer_info);
615
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
616

Ettore Perazzoli's avatar
Ettore Perazzoli committed
617
	default:
618
		return 1;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
619 620 621
	}
}

622 623 624 625 626 627
typedef enum {
	ERROR_READ_ONLY,
	ERROR_NOT_READABLE,
	ERROR_NOT_WRITABLE,
	ERROR_NOT_ENOUGH_PERMISSIONS,
	ERROR_NO_SPACE,
628
	ERROR_SOURCE_IN_TARGET,
629 630
	ERROR_OTHER
} NautilusFileOperationsErrorKind;
631

632 633 634 635 636 637 638 639 640
typedef enum {
	ERROR_LOCATION_UNKNOWN,
	ERROR_LOCATION_SOURCE,
	ERROR_LOCATION_SOURCE_PARENT,
	ERROR_LOCATION_SOURCE_OR_PARENT,
	ERROR_LOCATION_TARGET
} NautilusFileOperationsErrorLocation;


641
static void
642
build_error_string (const char *source_name, const char *target_name,
643 644 645
		    TransferKind operation_kind,
		    NautilusFileOperationsErrorKind error_kind,
		    NautilusFileOperationsErrorLocation error_location,
646 647
		    GnomeVFSResult error,
		    char **error_string, char **detail_string)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
648
{
649 650 651 652 653 654
	/* Avoid clever message composing here, just use brute force and
	 * duplicate the different flavors of error messages for all the
	 * possible permutations.
	 * That way localizers have an easier time and can even rearrange the
	 * order of the words in the messages easily.
	 */
Ettore Perazzoli's avatar
Ettore Perazzoli committed
655

656 657 658 659 660
	char *error_format;
	char *detail_format;
	
	error_format = NULL;
	detail_format = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
661

662 663
	*error_string = NULL;
	*detail_string = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
664

665
	if (error_location == ERROR_LOCATION_SOURCE_PARENT) {
666

667
		switch (operation_kind) {
668 669
		case TRANSFER_MOVE:
		case TRANSFER_MOVE_TO_TRASH:
670
			if (error_kind == ERROR_READ_ONLY) {
671 672 673
				*error_string = g_strdup (_("Error while moving."));
				detail_format = _("\"%s\" cannot be moved because it is on "
						  "a read-only disk.");
674 675
			}
			break;
676

677 678
		case TRANSFER_DELETE:
		case TRANSFER_EMPTY_TRASH:
679 680 681
			switch (error_kind) {
			case ERROR_NOT_ENOUGH_PERMISSIONS:
			case ERROR_NOT_WRITABLE:
682 683 684
				*error_string = g_strdup (_("Error while deleting."));
				detail_format = _("\"%s\" cannot be deleted because you do not have "
						  "permissions to modify its parent folder.");
685 686 687
				break;
			
			case ERROR_READ_ONLY:
688 689 690
				*error_string = g_strdup (_("Error while deleting."));
				detail_format = _("\"%s\" cannot be deleted because it is on "
						  "a read-only disk.");
691 692 693 694
				break;

			default:
				break;
695 696 697 698 699 700 701 702
			}
			break;

		default:
			g_assert_not_reached ();
			break;
		}
		
703 704
		if (detail_format != NULL && source_name != NULL) {
			*detail_string = g_strdup_printf (detail_format, source_name);
705
		}
706

707
	} else if (error_location == ERROR_LOCATION_SOURCE_OR_PARENT) {
708

709 710
		g_assert (source_name != NULL);

711 712 713 714
		/* FIXME: Would be better if we could distinguish source vs parent permissions
		 * better somehow. The GnomeVFS copy engine would have to do some snooping
		 * after the failure in this case.
		 */
715
		switch (operation_kind) {
716
		case TRANSFER_MOVE:
717 718
			switch (error_kind) {
			case ERROR_NOT_ENOUGH_PERMISSIONS:
719 720 721
				*error_string = g_strdup (_("Error while moving."));
				detail_format = _("\"%s\" cannot be moved because you do not have "
						  "permissions to change it or its parent folder.");
722 723
				break;
			case ERROR_SOURCE_IN_TARGET:
Alexander Winston's avatar
Alexander Winston committed
724
				*error_string = g_strdup (_("Error while moving."));
725 726
				detail_format = _("Cannot move \"%s\" because it or its parent folder "
						  "are contained in the destination.");
727 728 729
				break;
			default:
				break;
730
			}
731
			break;
732
		case TRANSFER_MOVE_TO_TRASH:
733
			if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
734 735 736
				*error_string = g_strdup (_("Error while moving."));
				detail_format = _("Cannot move \"%s\" to the trash because you do not have "
						  "permissions to change it or its parent folder.");
737
			}
738
			break;
739

740
		default:
741
			g_assert_not_reached ();
742 743
			break;
		}
744

745 746
		if (detail_format != NULL && source_name != NULL) {
			*detail_string = g_strdup_printf (detail_format, source_name);
747
		}
748

749
	} else if (error_location == ERROR_LOCATION_SOURCE) {
750

751 752 753
		g_assert (source_name != NULL);

		switch (operation_kind) {
754 755
		case TRANSFER_COPY:
		case TRANSFER_DUPLICATE:
756
			if (error_kind == ERROR_NOT_READABLE) {
757 758 759
				*error_string = g_strdup (_("Error while copying."));
				detail_format = _("\"%s\" cannot be copied because you do not have "
						  "permissions to read it.");
760 761
			}
			break;
762

763 764 765
		default:
			g_assert_not_reached ();
			break;
766
		}
767

768 769
		if (detail_format != NULL && source_name != NULL) {
			*detail_string = g_strdup_printf (detail_format, source_name);
770
		}
Pavel Cisler's avatar
Pavel Cisler committed
771

772 773 774 775
	} else if (error_location == ERROR_LOCATION_TARGET) {

		if (error_kind == ERROR_NO_SPACE) {
			switch (operation_kind) {
776 777
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
778 779
				error_format = _("Error while copying to \"%s\".");
				*detail_string = g_strdup (_("There is not enough space on the destination."));
780
				break;
781 782
			case TRANSFER_MOVE_TO_TRASH:
			case TRANSFER_MOVE:
783 784
				error_format = _("Error while moving to \"%s\".");
				*detail_string = g_strdup (_("There is not enough space on the destination."));
785
				break;
786
			case TRANSFER_LINK:
787 788
				error_format = _("Error while creating link in \"%s\".");
				*detail_string = g_strdup (_("There is not enough space on the destination."));
789 790 791 792 793 794 795
				break;
			default:
				g_assert_not_reached ();
				break;
			}
		} else {
			switch (operation_kind) {
796 797
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
798
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
799 800 801
					error_format = _("Error while copying to \"%s\".");
					*detail_string = g_strdup (_("You do not have permissions to write to "
					   		            "this folder."));
802
				} else if (error_kind == ERROR_NOT_WRITABLE) {
803 804
					error_format = _("Error while copying to \"%s\".");
					*detail_string = g_strdup (_("The destination disk is read-only."));
805 806
				} 
				break;
807 808
			case TRANSFER_MOVE:
			case TRANSFER_MOVE_TO_TRASH:
809
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
810 811 812
					error_format = _("Error while moving items to \"%s\".");
					*detail_string = g_strdup (_("You do not have permissions to write to "
					   		            "this folder."));
813
				} else if (error_kind == ERROR_NOT_WRITABLE) {
814 815
					error_format = _("Error while moving items to \"%s\".");
					*detail_string = g_strdup (_("The destination disk is read-only."));
816 817 818
				} 

				break;
819
			case TRANSFER_LINK:
820
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
821 822 823
					error_format = _("Error while creating links in \"%s\".");
					*detail_string = g_strdup (_("You do not have permissions to write to "
					   		             "this folder."));
824
				} else if (error_kind == ERROR_NOT_WRITABLE) {
825 826
					error_format = _("Error while creating links in \"%s\".");
					*detail_string = g_strdup (_("The destination disk is read-only."));