nautilus-file-operations.c 83.4 KB
Newer Older
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2

3
/* nautilus-file-operations.c - Nautilus file operations.
Ettore Perazzoli's avatar
Ettore Perazzoli committed
4

Elliot Lee's avatar
Elliot Lee committed
5
   Copyright (C) 1999, 2000 Free Software Foundation
6
   Copyright (C) 2000, 2001 Eazel, Inc.
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 "nautilus-file-operations.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
30

31 32
#include "nautilus-file-operations-progress.h"
#include "nautilus-lib-self-check-functions.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
33 34

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

Ettore Perazzoli's avatar
Ettore Perazzoli committed
40
#include <gnome.h>
41
#include <gtk/gtklabel.h>
42
#include <libgnomevfs/gnome-vfs-async-ops.h>
43
#include <libgnomevfs/gnome-vfs-find-directory.h>
44 45
#include <libgnomevfs/gnome-vfs-ops.h>
#include <libgnomevfs/gnome-vfs-result.h>
Pavel Cisler's avatar
Pavel Cisler committed
46
#include <libgnomevfs/gnome-vfs-uri.h>
47
#include <libgnomevfs/gnome-vfs-utils.h>
48
#include "nautilus-file-changes-queue.h"
Alexander Larsson's avatar
Alexander Larsson committed
49 50 51
#include "nautilus-file-private.h"
#include "nautilus-desktop-icon-file.h"
#include "nautilus-desktop-link-monitor.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
52 53 54
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
#include "nautilus-trash-monitor.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
55

56
typedef enum {
57 58 59 60 61 62 63 64
	TRANSFER_MOVE,
	TRANSFER_COPY,
	TRANSFER_DUPLICATE,
	TRANSFER_MOVE_TO_TRASH,
	TRANSFER_EMPTY_TRASH,
	TRANSFER_DELETE,
	TRANSFER_LINK
} TransferKind;
65

66 67
/* Copy engine callback state */
typedef struct {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
68
	GnomeVFSAsyncHandle *handle;
69
	NautilusFileOperationsProgress *progress_dialog;
Pavel Cisler's avatar
Pavel Cisler committed
70
	const char *operation_title;	/* "Copying files" */
71
	const char *action_label;	/* "Files copied:" */
Pavel Cisler's avatar
Pavel Cisler committed
72 73 74
	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
75 76
	GnomeVFSXferErrorMode error_mode;
	GnomeVFSXferOverwriteMode overwrite_mode;
Pavel Cisler's avatar
Pavel Cisler committed
77
	GtkWidget *parent_view;
78
	TransferKind kind;
79
	void (* done_callback) (GHashTable *debuting_uris, gpointer data);
80
	gpointer done_callback_data;
81 82
	GHashTable *debuting_uris;
	gboolean cancelled;	
83
} TransferInfo;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
84

85 86 87 88 89 90 91 92
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
93
	eel_add_weak_pointer (&result->parent_view);
94 95 96 97 98 99 100
	
	return result;
}

static void
transfer_info_destroy (TransferInfo *transfer_info)
{
Darin Adler's avatar
Darin Adler committed
101
	eel_remove_weak_pointer (&transfer_info->parent_view);
102 103
	
	if (transfer_info->progress_dialog != NULL) {
104
		nautilus_file_operations_progress_done (transfer_info->progress_dialog);
105 106 107
	}
	
	if (transfer_info->debuting_uris != NULL) {
108
		g_hash_table_destroy (transfer_info->debuting_uris);
109 110 111 112 113
	}
	
	g_free (transfer_info);
}

114 115 116 117 118 119 120 121 122
/* Struct used to control applying icon positions to 
 * top level items during a copy, drag, new folder creation and
 * link creation
 */
typedef struct {
	GdkPoint *icon_positions;
	int last_icon_position_index;
	GList *uris;
	const GList *last_uri;
123
	int screen;
124 125 126
} IconPositionIterator;

static IconPositionIterator *
127 128
icon_position_iterator_new (GArray *icon_positions, const GList *uris,
			    int screen)
129 130
{
	IconPositionIterator *result;
131
	guint index;
132

133
	g_assert (icon_positions->len == g_list_length ((GList *)uris));
134 135
	result = g_new (IconPositionIterator, 1);
	
136
	/* make our own copy of the icon locations */
137
	result->icon_positions = g_new (GdkPoint, icon_positions->len);
138 139 140
	for (index = 0; index < icon_positions->len; index++) {
		result->icon_positions[index] = g_array_index (icon_positions, GdkPoint, index);
	}
141 142
	result->last_icon_position_index = 0;

Ramiro Estrugo's avatar
Ramiro Estrugo committed
143
	result->uris = eel_g_str_list_copy ((GList *)uris);
144
	result->last_uri = result->uris;
145
	result->screen = screen;
146 147 148 149 150 151 152 153 154 155 156 157

	return result;
}

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
158
	eel_g_list_free_deep (position_iterator->uris);
159 160 161
	g_free (position_iterator);
}

162 163 164 165 166 167 168 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 196 197 198 199 200 201 202 203
static gboolean
icon_position_iterator_get_next (IconPositionIterator *position_iterator,
				 const char *next_uri,
				 GdkPoint *point)
{
	if (position_iterator == NULL) {
		return FALSE;
	}
		
	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;
}

204 205
static char *
ellipsize_string_for_dialog (PangoContext *context, const char *str)
206
{
207 208 209 210
	int maximum_width;
	char *result;
	PangoLayout *layout;
	PangoFontMetrics *metrics;
211

212
	layout = pango_layout_new (context);
213

214 215
	metrics = pango_context_get_metrics (
		context, pango_context_get_font_description (context), NULL);
216

217
	maximum_width = pango_font_metrics_get_approximate_char_width (metrics) * 25 / PANGO_SCALE;
218

219
	pango_font_metrics_unref (metrics);
220

221 222
	eel_pango_layout_set_text_ellipsized (
		layout, str, maximum_width, EEL_ELLIPSIZE_MIDDLE);
223

224
	result = g_strdup (pango_layout_get_text (layout));
225

226
	g_object_unref (layout);
227

228 229
	return result;
}
230

231
static char *
232
format_and_ellipsize_uri_for_dialog (GtkWidget *context, const char *uri)
233 234
{
	char *unescaped, *result;
235

Ramiro Estrugo's avatar
Ramiro Estrugo committed
236
	unescaped = eel_format_uri_for_display (uri);
237 238
	result = ellipsize_string_for_dialog (
		gtk_widget_get_pango_context (context), unescaped);
239
	g_free (unescaped);
240

241 242 243 244
	return result;
}

static char *
245
extract_and_ellipsize_file_name_for_dialog (GtkWidget *context, const char *uri)
246
{
247
	char *basename;
248
	char *unescaped, *result;
249
	
250 251
	basename = g_path_get_basename (uri);
	g_return_val_if_fail (basename != NULL, NULL);
252

253
	unescaped = gnome_vfs_unescape_string_for_display (basename);
254 255
	result = ellipsize_string_for_dialog (
		gtk_widget_get_pango_context (context), unescaped);
256
	g_free (unescaped);
257
	g_free (basename);
258

259 260 261
	return result;
}

262
static GtkWidget *
263
parent_for_error_dialog (TransferInfo *transfer_info)
264
{
265
	if (transfer_info->progress_dialog != NULL) {
266
		return GTK_WIDGET (transfer_info->progress_dialog);
267 268
	}

269
	return transfer_info->parent_view;
270 271
}

272 273 274 275 276 277 278 279
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)
280
{
281
	transfer_info->cancelled = TRUE;
282 283
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
284
static void
285
create_transfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
286
			TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
287
{
288
	g_return_if_fail (transfer_info->progress_dialog == NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
289

290
	transfer_info->progress_dialog = nautilus_file_operations_progress_new 
291
		(transfer_info->operation_title, "", "", "", 0, 0, TRUE);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
292

293 294 295
	/* Treat clicking on the close box or use of the escape key
	 * the same as clicking cancel.
	 */
296
	g_signal_connect (transfer_info->progress_dialog,
297 298
			  "response",
			  G_CALLBACK (handle_response_callback),
299
			  transfer_info);
300
	g_signal_connect (transfer_info->progress_dialog,
301 302 303
			  "close",
			  G_CALLBACK (handle_close_callback),
			  transfer_info);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
304

305
	/* Make the progress dialog show up over the window we are copying into */
306
	if (transfer_info->parent_view != NULL) {
307 308 309 310 311 312 313 314 315 316
		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));
		}
317
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
318 319
}

Pavel Cisler's avatar
Pavel Cisler committed
320
static void
321
progress_dialog_set_to_from_item_text (NautilusFileOperationsProgress *dialog,
322 323 324
				       const char *progress_verb,
				       const char *from_uri, const char *to_uri, 
				       gulong index, gulong size)
Pavel Cisler's avatar
Pavel Cisler committed
325 326 327 328 329 330 331 332
{
	char *item;
	char *from_path;
	char *to_path;
	char *progress_label_text;
	const char *from_prefix;
	const char *to_prefix;
	GnomeVFSURI *uri;
333
	int length;
Pavel Cisler's avatar
Pavel Cisler committed
334 335 336 337 338 339 340 341 342 343 344 345

	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);
346 347 348 349 350 351 352

		/* remove the last '/' */
		length = strlen (from_path);
		if (from_path [length - 1] == '/') {
			from_path [length - 1] = '\0';
		}
		
Pavel Cisler's avatar
Pavel Cisler committed
353 354 355
		gnome_vfs_uri_unref (uri);
		g_assert (progress_verb);
		progress_label_text = g_strdup_printf ("%s:", progress_verb);
356
		/* "From" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
357 358 359 360
		from_prefix = _("From:");
	}

	if (to_uri != NULL) {
361
		uri = gnome_vfs_uri_new (to_uri);
Pavel Cisler's avatar
Pavel Cisler committed
362
		to_path = gnome_vfs_uri_extract_dirname (uri);
363 364 365 366 367 368 369

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

Pavel Cisler's avatar
Pavel Cisler committed
370
		gnome_vfs_uri_unref (uri);
371
		/* "To" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
372 373 374
		to_prefix = _("To:");
	}

375 376 377 378 379 380 381
	nautilus_file_operations_progress_new_file
		(dialog,
		 progress_label_text != NULL ? progress_label_text : "",
		 item != NULL ? item : "",
		 from_path != NULL ? from_path : "",
		 to_path != NULL ? to_path : "",
		 from_prefix, to_prefix, index, size);
Pavel Cisler's avatar
Pavel Cisler committed
382 383 384 385 386 387 388

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

Pavel Cisler's avatar
Pavel Cisler committed
389
static int
390
handle_transfer_ok (const GnomeVFSXferProgressInfo *progress_info,
391
		    TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
392
{
393 394
	if (transfer_info->cancelled
		&& progress_info->phase != GNOME_VFS_XFER_PHASE_COMPLETED) {
395
		/* If cancelled, delete any partially copied files that are laying
396
		 * around and return. Don't delete the source though..
397
		 */
398
		if (progress_info->target_name != NULL
399 400
		    && progress_info->source_name != NULL
		    && strcmp (progress_info->source_name, progress_info->target_name) != 0
401
		    && progress_info->bytes_total != progress_info->bytes_copied) {
402
			GList *delete_me;
403

404
			delete_me = g_list_prepend (NULL, progress_info->target_name);
405 406 407 408
			nautilus_file_operations_delete (delete_me, transfer_info->parent_view);
			g_list_free (delete_me);
		}

409 410 411
		return 0;
	}
	
Ettore Perazzoli's avatar
Ettore Perazzoli committed
412
	switch (progress_info->phase) {
Pavel Cisler's avatar
Pavel Cisler committed
413
	case GNOME_VFS_XFER_PHASE_INITIAL:
414
		create_transfer_dialog (progress_info, transfer_info);
415
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
416 417

	case GNOME_VFS_XFER_PHASE_COLLECTING:
418
		if (transfer_info->progress_dialog != NULL) {
419
			nautilus_file_operations_progress_set_operation_string
420 421
				(transfer_info->progress_dialog,
				 transfer_info->preparation_name);
422
		}
423
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
424 425

	case GNOME_VFS_XFER_PHASE_READYTOGO:
426
		if (transfer_info->progress_dialog != NULL) {
427 428 429
			nautilus_file_operations_progress_set_operation_string
				(transfer_info->progress_dialog,
				 transfer_info->action_label);
430
			nautilus_file_operations_progress_set_total
431 432 433
				(transfer_info->progress_dialog,
				 progress_info->files_total,
				 progress_info->bytes_total);
434
		}
435
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
436
				 
437
	case GNOME_VFS_XFER_PHASE_DELETESOURCE:
438
		nautilus_file_changes_consume_changes (FALSE);
439
		if (transfer_info->progress_dialog != NULL) {
440 441 442 443 444 445 446
			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
447

448
			nautilus_file_operations_progress_update_sizes
449
				(transfer_info->progress_dialog,
450
				 MIN (progress_info->bytes_copied, 
451
				      progress_info->bytes_total),
452
				 MIN (progress_info->total_bytes_copied,
453
				      progress_info->bytes_total));
454
		}
455
		return 1;
456 457

	case GNOME_VFS_XFER_PHASE_MOVING:
Pavel Cisler's avatar
Pavel Cisler committed
458 459
	case GNOME_VFS_XFER_PHASE_OPENSOURCE:
	case GNOME_VFS_XFER_PHASE_OPENTARGET:
460 461
		/* fall through */
	case GNOME_VFS_XFER_PHASE_COPYING:
462
		if (transfer_info->progress_dialog != NULL) {
463
			if (progress_info->bytes_copied == 0) {
464 465 466 467 468 469 470
				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);
471
			} else {
472
				nautilus_file_operations_progress_update_sizes
473
					(transfer_info->progress_dialog,
474
					 MIN (progress_info->bytes_copied, 
475
					      progress_info->bytes_total),
476
					 MIN (progress_info->total_bytes_copied,
477
					      progress_info->bytes_total));
478
			}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
479
		}
480
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
481

482
	case GNOME_VFS_XFER_PHASE_CLEANUP:
483
		if (transfer_info->progress_dialog != NULL) {
484 485
			nautilus_file_operations_progress_clear
				(transfer_info->progress_dialog);
486
			nautilus_file_operations_progress_set_operation_string
487 488
				(transfer_info->progress_dialog,
				 transfer_info->cleanup_name);
489
		}
490
		return 1;
491

Ettore Perazzoli's avatar
Ettore Perazzoli committed
492
	case GNOME_VFS_XFER_PHASE_COMPLETED:
493
		nautilus_file_changes_consume_changes (TRUE);
494
		if (transfer_info->done_callback != NULL) {
495 496 497 498
			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;
499
		}
500 501

		transfer_info_destroy (transfer_info);
502
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
503

Ettore Perazzoli's avatar
Ettore Perazzoli committed
504
	default:
505
		return 1;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
506 507 508
	}
}

509 510 511 512 513 514
typedef enum {
	ERROR_READ_ONLY,
	ERROR_NOT_READABLE,
	ERROR_NOT_WRITABLE,
	ERROR_NOT_ENOUGH_PERMISSIONS,
	ERROR_NO_SPACE,
515
	ERROR_SOURCE_IN_TARGET,
516 517
	ERROR_OTHER
} NautilusFileOperationsErrorKind;
518

519 520 521 522 523 524 525 526 527
typedef enum {
	ERROR_LOCATION_UNKNOWN,
	ERROR_LOCATION_SOURCE,
	ERROR_LOCATION_SOURCE_PARENT,
	ERROR_LOCATION_SOURCE_OR_PARENT,
	ERROR_LOCATION_TARGET
} NautilusFileOperationsErrorLocation;


528
static void
529
build_error_string (const char *source_name, const char *target_name,
530 531 532
		    TransferKind operation_kind,
		    NautilusFileOperationsErrorKind error_kind,
		    NautilusFileOperationsErrorLocation error_location,
533 534
		    GnomeVFSResult error,
		    char **error_string, char **detail_string)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
535
{
536 537 538 539 540 541
	/* 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
542

543 544 545 546 547
	char *error_format;
	char *detail_format;
	
	error_format = NULL;
	detail_format = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
548

549 550
	*error_string = NULL;
	*detail_string = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
551

552
	if (error_location == ERROR_LOCATION_SOURCE_PARENT) {
553

554
		switch (operation_kind) {
555 556
		case TRANSFER_MOVE:
		case TRANSFER_MOVE_TO_TRASH:
557
			if (error_kind == ERROR_READ_ONLY) {
558 559 560
				*error_string = g_strdup (_("Error while moving."));
				detail_format = _("\"%s\" cannot be moved because it is on "
						  "a read-only disk.");
561 562
			}
			break;
563

564 565
		case TRANSFER_DELETE:
		case TRANSFER_EMPTY_TRASH:
566 567 568
			switch (error_kind) {
			case ERROR_NOT_ENOUGH_PERMISSIONS:
			case ERROR_NOT_WRITABLE:
569 570 571
				*error_string = g_strdup (_("Error while deleting."));
				detail_format = _("\"%s\" cannot be deleted because you do not have "
						  "permissions to modify its parent folder.");
572 573 574
				break;
			
			case ERROR_READ_ONLY:
575 576 577
				*error_string = g_strdup (_("Error while deleting."));
				detail_format = _("\"%s\" cannot be deleted because it is on "
						  "a read-only disk.");
578 579 580 581
				break;

			default:
				break;
582 583 584 585 586 587 588 589
			}
			break;

		default:
			g_assert_not_reached ();
			break;
		}
		
590 591
		if (detail_format != NULL && source_name != NULL) {
			*detail_string = g_strdup_printf (detail_format, source_name);
592
		}
593

594
	} else if (error_location == ERROR_LOCATION_SOURCE_OR_PARENT) {
595

596 597
		g_assert (source_name != NULL);

598 599 600 601
		/* 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.
		 */
602
		switch (operation_kind) {
603
		case TRANSFER_MOVE:
604 605
			switch (error_kind) {
			case ERROR_NOT_ENOUGH_PERMISSIONS:
606 607 608
				*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.");
609 610
				break;
			case ERROR_SOURCE_IN_TARGET:
Alexander Winston's avatar
Alexander Winston committed
611
				*error_string = g_strdup (_("Error while moving."));
612 613
				detail_format = _("Cannot move \"%s\" because it or its parent folder "
						  "are contained in the destination.");
614 615 616
				break;
			default:
				break;
617
			}
618
			break;
619
		case TRANSFER_MOVE_TO_TRASH:
620
			if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
621 622 623
				*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.");
624
			}
625
			break;
626

627
		default:
628
			g_assert_not_reached ();
629 630
			break;
		}
631

632 633
		if (detail_format != NULL && source_name != NULL) {
			*detail_string = g_strdup_printf (detail_format, source_name);
634
		}
635

636
	} else if (error_location == ERROR_LOCATION_SOURCE) {
637

638 639 640
		g_assert (source_name != NULL);

		switch (operation_kind) {
641 642
		case TRANSFER_COPY:
		case TRANSFER_DUPLICATE:
643
			if (error_kind == ERROR_NOT_READABLE) {
644 645 646
				*error_string = g_strdup (_("Error while copying."));
				detail_format = _("\"%s\" cannot be copied because you do not have "
						  "permissions to read it.");
647 648
			}
			break;
649

650 651 652
		default:
			g_assert_not_reached ();
			break;
653
		}
654

655 656
		if (detail_format != NULL && source_name != NULL) {
			*detail_string = g_strdup_printf (detail_format, source_name);
657
		}
Pavel Cisler's avatar
Pavel Cisler committed
658

659 660 661 662
	} else if (error_location == ERROR_LOCATION_TARGET) {

		if (error_kind == ERROR_NO_SPACE) {
			switch (operation_kind) {
663 664
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
665 666
				error_format = _("Error while copying to \"%s\".");
				*detail_string = g_strdup (_("There is not enough space on the destination."));
667
				break;
668 669
			case TRANSFER_MOVE_TO_TRASH:
			case TRANSFER_MOVE:
670 671
				error_format = _("Error while moving to \"%s\".");
				*detail_string = g_strdup (_("There is not enough space on the destination."));
672
				break;
673
			case TRANSFER_LINK:
674 675
				error_format = _("Error while creating link in \"%s\".");
				*detail_string = g_strdup (_("There is not enough space on the destination."));
676 677 678 679 680 681 682
				break;
			default:
				g_assert_not_reached ();
				break;
			}
		} else {
			switch (operation_kind) {
683 684
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
685
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
686 687 688
					error_format = _("Error while copying to \"%s\".");
					*detail_string = g_strdup (_("You do not have permissions to write to "
					   		            "this folder."));
689
				} else if (error_kind == ERROR_NOT_WRITABLE) {
690 691
					error_format = _("Error while copying to \"%s\".");
					*detail_string = g_strdup (_("The destination disk is read-only."));
692 693
				} 
				break;
694 695
			case TRANSFER_MOVE:
			case TRANSFER_MOVE_TO_TRASH:
696
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
697 698 699
					error_format = _("Error while moving items to \"%s\".");
					*detail_string = g_strdup (_("You do not have permissions to write to "
					   		            "this folder."));
700
				} else if (error_kind == ERROR_NOT_WRITABLE) {
701 702
					error_format = _("Error while moving items to \"%s\".");
					*detail_string = g_strdup (_("The destination disk is read-only."));
703 704 705
				} 

				break;
706
			case TRANSFER_LINK:
707
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
708 709 710
					error_format = _("Error while creating links in \"%s\".");
					*detail_string = g_strdup (_("You do not have permissions to write to "
					   		             "this folder."));
711
				} else if (error_kind == ERROR_NOT_WRITABLE) {
712 713
					error_format = _("Error while creating links in \"%s\".");
					*detail_string = g_strdup (_("The destination disk is read-only."));
714 715 716 717 718 719 720
				} 
				break;
			default:
				g_assert_not_reached ();
				break;
			}
		}
721 722
		if (error_format != NULL && target_name != NULL) {
			*error_string = g_strdup_printf (error_format, target_name);
723 724 725
		}
	}
	
726
	if (*error_string == NULL) {
727 728 729
		/* None of the specific error messages apply, use a catch-all
		 * generic error
		 */
730 731
		g_message ("Hit unexpected error \"%s\" while doing a file operation.",
			   gnome_vfs_result_to_string (error));
732 733 734 735

		/* FIXMEs: we need to consider a single item
		 * move/copy and not offer to continue in that case
		 */
736 737
		if (source_name != NULL) {
			switch (operation_kind) {
738 739
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
740 741
				error_format = _("Error \"%s\" while copying \"%s\".");
				*detail_string = g_strdup (_("Would you like to continue?"));
742
				break;
743
			case TRANSFER_MOVE:
744 745
				error_format = _("Error \"%s\" while moving \"%s\".");
				*detail_string = g_strdup (_("Would you like to continue?"));
746
				break;
747
			case TRANSFER_LINK:
748 749
				error_format = _("Error \"%s\" while creating a link to \"%s\".");
				*detail_string = g_strdup (_("Would you like to continue?"));
750
				break;
751 752 753
			case TRANSFER_DELETE:
			case TRANSFER_EMPTY_TRASH:
			case TRANSFER_MOVE_TO_TRASH:
754 755
				error_format = _("Error \"%s\" while deleting \"%s\".");
				*detail_string = g_strdup (_("Would you like to continue?"));
756 757
				break;
			default:
758
				g_assert_not_reached ();
759 760 761
				break;
			}
	
762 763 764
			*error_string = g_strdup_printf (error_format, 
							 gnome_vfs_result_to_string (error),
							 source_name);
765
		} else {
766
			switch (operation_kind) {
767 768
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
769 770
				error_format = _("Error \"%s\" while copying.");
				*detail_string = g_strdup (_("Would you like to continue?"));
771
				break;
772
			case TRANSFER_MOVE:
773 774
				error_format = _("Error \"%s\" while moving.");
				*detail_string = g_strdup (_("Would you like to continue?"));
775
				break;
776
			case TRANSFER_LINK:
777 778
				error_format = _("Error \"%s\" while linking.");
				*detail_string = g_strdup (_("Would you like to continue?"));
779
				break;
780 781 782
			case TRANSFER_DELETE:
			case TRANSFER_EMPTY_TRASH:
			case TRANSFER_MOVE_TO_TRASH:
783 784
				error_format = _("Error \"%s\" while deleting.");
				*detail_string = g_strdup (_("Would you like to continue?"));
785 786
				break;
			default:
787
				g_assert_not_reached ();
788 789 790
				break;
			}
	
791 792
			*error_string = g_strdup_printf (error_format, 
						         gnome_vfs_result_to_string (error));
793
		}
794 795
	}
}
796

797
static int
798
handle_transfer_vfs_error (const GnomeVFSXferProgressInfo *progress_info,
799
			   TransferInfo *transfer_info)
800
{
801
	/* Notice that the error mode in `transfer_info' is the one we have been
802 803 804
         * requested, but the transfer is always performed in mode
         * `GNOME_VFS_XFER_ERROR_MODE_QUERY'.
         */
805

806 807 808
	int error_dialog_button_pressed;
	int error_dialog_result;
	char *text;
809
	char *detail;
810 811
	char *formatted_source_name;
	char *formatted_target_name;
812 813 814 815
	const char *dialog_title;
	NautilusFileOperationsErrorKind error_kind;
	NautilusFileOperationsErrorLocation error_location;