nautilus-file-operations.c 86.5 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

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

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

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

62 63 64 65 66
typedef enum   TransferKind         TransferKind;
typedef struct TransferInfo         TransferInfo;
typedef struct IconPositionIterator IconPositionIterator;

enum TransferKind {
67 68 69 70 71 72 73
	TRANSFER_MOVE,
	TRANSFER_COPY,
	TRANSFER_DUPLICATE,
	TRANSFER_MOVE_TO_TRASH,
	TRANSFER_EMPTY_TRASH,
	TRANSFER_DELETE,
	TRANSFER_LINK
74
};
75

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

96 97 98 99 100 101 102 103
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
104
	eel_add_weak_pointer (&result->parent_view);
105 106 107 108 109 110 111
	
	return result;
}

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

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

static IconPositionIterator *
139 140 141 142
icon_position_iterator_new (GArray *icon_positions,
			    const GList *uris,
			    int screen,
			    gboolean is_source_iterator)
143 144
{
	IconPositionIterator *result;
145
	guint index;
146

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

Ramiro Estrugo's avatar
Ramiro Estrugo committed
157
	result->uris = eel_g_str_list_copy ((GList *)uris);
158
	result->last_uri = result->uris;
159
	result->screen = screen;
160
	result->is_source_iterator = is_source_iterator;
161 162 163 164

	return result;
}

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

192 193 194 195 196 197 198 199
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
200
	eel_g_list_free_deep (position_iterator->uris);
201 202 203
	g_free (position_iterator);
}

204 205
static gboolean
icon_position_iterator_get_next (IconPositionIterator *position_iterator,
206 207
				 const char *next_source_uri,
				 const char *next_target_uri,
208 209
				 GdkPoint *point)
{
210 211
	const char *next_uri;

212 213 214
	if (position_iterator == NULL) {
		return FALSE;
	}
215 216 217 218 219 220 221

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

222 223 224 225 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
	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;
}

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
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
282
	uri = gnome_vfs_uri_append_string (parent_uri, new_uri_fragment);
283 284 285 286 287 288 289 290

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

291 292
static char *
ellipsize_string_for_dialog (PangoContext *context, const char *str)
293
{
294 295 296 297
	int maximum_width;
	char *result;
	PangoLayout *layout;
	PangoFontMetrics *metrics;
298

299
	layout = pango_layout_new (context);
300

301 302
	metrics = pango_context_get_metrics (
		context, pango_context_get_font_description (context), NULL);
303

304
	maximum_width = pango_font_metrics_get_approximate_char_width (metrics) * 25 / PANGO_SCALE;
305

306
	pango_font_metrics_unref (metrics);
307

308 309
	eel_pango_layout_set_text_ellipsized (
		layout, str, maximum_width, EEL_ELLIPSIZE_MIDDLE);
310

311
	result = g_strdup (pango_layout_get_text (layout));
312

313
	g_object_unref (layout);
314

315 316
	return result;
}
317

318
static char *
319
format_and_ellipsize_uri_for_dialog (GtkWidget *context, const char *uri)
320 321
{
	char *unescaped, *result;
322

Ramiro Estrugo's avatar
Ramiro Estrugo committed
323
	unescaped = eel_format_uri_for_display (uri);
324 325
	result = ellipsize_string_for_dialog (
		gtk_widget_get_pango_context (context), unescaped);
326
	g_free (unescaped);
327

328 329 330 331
	return result;
}

static char *
332
extract_and_ellipsize_file_name_for_dialog (GtkWidget *context, const char *uri)
333
{
334
	char *basename;
335
	char *unescaped, *result;
336
	
337 338
	basename = g_path_get_basename (uri);
	g_return_val_if_fail (basename != NULL, NULL);
339

340
	unescaped = gnome_vfs_unescape_string_for_display (basename);
341 342
	result = ellipsize_string_for_dialog (
		gtk_widget_get_pango_context (context), unescaped);
343
	g_free (unescaped);
344
	g_free (basename);
345

346 347 348
	return result;
}

349
static GtkWidget *
350
parent_for_error_dialog (TransferInfo *transfer_info)
351
{
352
	if (transfer_info->progress_dialog != NULL) {
353
		return GTK_WIDGET (transfer_info->progress_dialog);
354 355
	}

356
	return transfer_info->parent_view;
357 358
}

359 360 361 362 363 364 365 366
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)
367
{
368
	transfer_info->cancelled = TRUE;
369 370
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
371
static void
372
create_transfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
373
			TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
374
{
375
	g_return_if_fail (transfer_info->progress_dialog == NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
376

377
	transfer_info->progress_dialog = nautilus_file_operations_progress_new 
378
		(transfer_info->operation_title, "", "", "", 0, 0, TRUE);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
379

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

392
	/* Make the progress dialog show up over the window we are copying into */
393
	if (transfer_info->parent_view != NULL) {
394 395 396 397 398 399 400 401 402 403
		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));
		}
404
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
405 406
}

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

	item = NULL;
426 427
	from_text = NULL;
	to_text = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
428 429 430 431 432 433 434 435
	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);
436
		hostname = NULL;
437 438 439 440 441 442

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

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

	if (to_uri != NULL) {
463
		uri = gnome_vfs_uri_new (to_uri);
Pavel Cisler's avatar
Pavel Cisler committed
464
		to_path = gnome_vfs_uri_extract_dirname (uri);
465
		hostname = NULL;
466 467 468 469 470 471 472

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

473 474 475 476
		if (!gnome_vfs_uri_is_local (uri)) {
			hostname = gnome_vfs_uri_get_host_name (uri);
		}
		if (hostname) {
477
			to_text = g_strdup_printf (_("%s on %s"),
478
				to_path, hostname);
479
			g_free (to_path);
480 481
		} else {
			to_text = to_path;
482 483
		}

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

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

	g_free (progress_label_text);
	g_free (item);
499 500
	g_free (from_text);
	g_free (to_text);
Pavel Cisler's avatar
Pavel Cisler committed
501 502
}

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

518
			delete_me = g_list_prepend (NULL, progress_info->target_name);
519 520 521 522
			nautilus_file_operations_delete (delete_me, transfer_info->parent_view);
			g_list_free (delete_me);
		}

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

	case GNOME_VFS_XFER_PHASE_COLLECTING:
532
		if (transfer_info->progress_dialog != NULL) {
533
			nautilus_file_operations_progress_set_operation_string
534 535
				(transfer_info->progress_dialog,
				 transfer_info->preparation_name);
536
		}
537
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
538 539

	case GNOME_VFS_XFER_PHASE_READYTOGO:
540
		if (transfer_info->progress_dialog != NULL) {
541 542 543
			nautilus_file_operations_progress_set_operation_string
				(transfer_info->progress_dialog,
				 transfer_info->action_label);
544
			nautilus_file_operations_progress_set_total
545 546 547
				(transfer_info->progress_dialog,
				 progress_info->files_total,
				 progress_info->bytes_total);
548
		}
549
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
550
				 
551
	case GNOME_VFS_XFER_PHASE_DELETESOURCE:
552
		nautilus_file_changes_consume_changes (FALSE);
553
		if (transfer_info->progress_dialog != NULL) {
554 555 556 557 558 559 560
			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
561

562
			nautilus_file_operations_progress_update_sizes
563
				(transfer_info->progress_dialog,
564
				 MIN (progress_info->bytes_copied, 
565
				      progress_info->bytes_total),
566
				 MIN (progress_info->total_bytes_copied,
567
				      progress_info->bytes_total));
568
		}
569
		return 1;
570 571

	case GNOME_VFS_XFER_PHASE_MOVING:
Pavel Cisler's avatar
Pavel Cisler committed
572 573
	case GNOME_VFS_XFER_PHASE_OPENSOURCE:
	case GNOME_VFS_XFER_PHASE_OPENTARGET:
574 575
		/* fall through */
	case GNOME_VFS_XFER_PHASE_COPYING:
576
		if (transfer_info->progress_dialog != NULL) {
577
			if (progress_info->bytes_copied == 0) {
578 579 580 581 582 583 584
				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);
585
			} else {
586
				nautilus_file_operations_progress_update_sizes
587
					(transfer_info->progress_dialog,
588
					 MIN (progress_info->bytes_copied, 
589
					      progress_info->bytes_total),
590
					 MIN (progress_info->total_bytes_copied,
591
					      progress_info->bytes_total));
592
			}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
593
		}
594
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
595

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

Ettore Perazzoli's avatar
Ettore Perazzoli committed
606
	case GNOME_VFS_XFER_PHASE_COMPLETED:
607
		nautilus_file_changes_consume_changes (TRUE);
608
		if (transfer_info->done_callback != NULL) {
609 610 611 612
			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;
613
		}
614 615

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

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

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

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


642
static void
643
build_error_string (const char *source_name, const char *target_name,
644 645 646
		    TransferKind operation_kind,
		    NautilusFileOperationsErrorKind error_kind,
		    NautilusFileOperationsErrorLocation error_location,
647 648
		    GnomeVFSResult error,
		    char **error_string, char **detail_string)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
649
{
650 651 652 653 654 655
	/* 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
656

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

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

666
	if (error_location == ERROR_LOCATION_SOURCE_PARENT) {
667

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

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

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

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

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

710 711
		g_assert (source_name != NULL);

712 713 714 715
		/* 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.
		 */
716
		switch (operation_kind) {
717
		case TRANSFER_MOVE:
718 719
			switch (error_kind) {
			case ERROR_NOT_ENOUGH_PERMISSIONS:
720 721 722
				*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.");
723 724
				break;
			case ERROR_SOURCE_IN_TARGET:
Alexander Winston's avatar
Alexander Winston committed
725
				*error_string = g_strdup (_("Error while moving."));
726 727
				detail_format = _("Cannot move \"%s\" because it or its parent folder "
						  "are contained in the destination.");
728 729 730
				break;
			default:
				break;
731
			}
732
			break;
733
		case TRANSFER_MOVE_TO_TRASH:
734
			if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
735 736 737
				*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.");
738
			}
739
			break;
740

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

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

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

752 753 754
		g_assert (source_name != NULL);

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

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

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

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

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

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