nautilus-file-operations.c 73.1 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 35 36

#include <eel/eel-glib-extensions.h>
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-stock-dialogs.h>
37 38
#include <eel/eel-vfs-extensions.h>

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

52
typedef enum {
53 54 55 56 57 58 59 60
	TRANSFER_MOVE,
	TRANSFER_COPY,
	TRANSFER_DUPLICATE,
	TRANSFER_MOVE_TO_TRASH,
	TRANSFER_EMPTY_TRASH,
	TRANSFER_DELETE,
	TRANSFER_LINK
} TransferKind;
61

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

82 83 84 85 86 87 88 89
static TransferInfo *
transfer_info_new (GtkWidget *parent_view)
{
	TransferInfo *result;
	
	result = g_new0 (TransferInfo, 1);
	result->parent_view = parent_view;
	
Ramiro Estrugo's avatar
Ramiro Estrugo committed
90
	eel_nullify_when_destroyed (&result->parent_view);
91 92 93 94 95 96 97
	
	return result;
}

static void
transfer_info_destroy (TransferInfo *transfer_info)
{
Ramiro Estrugo's avatar
Ramiro Estrugo committed
98
	eel_nullify_cancel (&transfer_info->parent_view);
99 100
	
	if (transfer_info->progress_dialog != NULL) {
101
		nautilus_file_operations_progress_done (transfer_info->progress_dialog);
102 103 104
	}
	
	if (transfer_info->debuting_uris != NULL) {
105
		g_hash_table_destroy (transfer_info->debuting_uris);
106 107 108 109 110
	}
	
	g_free (transfer_info);
}

111 112 113 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;
} IconPositionIterator;

static IconPositionIterator *
123
icon_position_iterator_new (GArray *icon_positions, const GList *uris)
124 125
{
	IconPositionIterator *result;
126
	guint index;
127

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

Ramiro Estrugo's avatar
Ramiro Estrugo committed
138
	result->uris = eel_g_str_list_copy ((GList *)uris);
139 140 141 142 143 144 145 146 147 148 149 150 151
	result->last_uri = result->uris;

	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
152
	eel_g_list_free_deep (position_iterator->uris);
153 154 155
	g_free (position_iterator);
}

156 157 158 159 160 161 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
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;
}

198 199
#if GNOME2_CONVERSION_COMPLETE

200 201
static GdkFont *
get_label_font (void)
202
{
203 204
	GtkWidget *label;
	GtkStyle *style;
205 206 207 208 209 210
	GdkFont *font;

	/* FIXME: Does this really pick up the correct font if the rc
	 * file has a special case for the particular dialog we are
	 * preparing?
	 */
211 212 213

	label = gtk_label_new ("");
	style = gtk_widget_get_style (label);
214 215 216
	font = style->font;
	gdk_font_ref (font);
	gtk_object_sink (GTK_OBJECT (label));
217

218
	return font;
219 220
}

221 222
#endif

223
static char *
224
ellipsize_string_for_dialog (const char *str)
225
{
226 227
	char *result;
#ifdef GNOME2_CONVERSION_COMPLETE
228
	GdkFont *font;
229
	int maximum_width;
230

231 232 233 234 235 236 237 238 239 240 241 242 243 244
	/* I believe the most appropriate fix here is to change it to be based on
	 * some number of characters, rather than on a pixel width.
	 * We just can't do anything sane with the pixel width since
	 * we don't have all the PangoLayout information.
	 *
	 * So in brief we want an ellipsize function in eel which
	 * takes a number of chars, computes log attrs for the string,
	 * and chops the string at one of the is_cursor_position points,
	 * such that the string has fewer chars than the given number.
	 *
	 * Code in nautilus-news.c implements this in a UTF-8-unsafe way,
	 * should be moved to Eel and used here as well.
	 */
	
245
	/* get a nice length to ellipsize to, based on the font */
246
	font = get_label_font ();
247
	maximum_width = gdk_string_width (font, "MMMMMMMMMMMMMMMMMMMMMM");
248 249

	result = eel_string_ellipsize (str, font, maximum_width, EEL_ELLIPSIZE_MIDDLE);
250
	gdk_font_unref (font);
251 252 253
#else
	result = g_strdup (str);
#endif
254

255 256
	return result;
}
257

258 259 260 261
static char *
format_and_ellipsize_uri_for_dialog (const char *uri)
{
	char *unescaped, *result;
262

263
	unescaped = eel_format_uri_for_display (uri);
264
	result = ellipsize_string_for_dialog (unescaped);
265
	g_free (unescaped);
266

267 268 269 270
	return result;
}

static char *
271
extract_and_ellipsize_file_name_for_dialog (const char *uri)
272
{
273
	char *basename;
274
	char *unescaped, *result;
275
	
276 277
	basename = g_path_get_basename (uri);
	g_return_val_if_fail (basename != NULL, NULL);
278

279
	unescaped = gnome_vfs_unescape_string_for_display (basename);
280 281
	result = ellipsize_string_for_dialog (unescaped);
	g_free (unescaped);
282
	g_free (basename);
283

284 285 286
	return result;
}

287
static GtkWidget *
288
parent_for_error_dialog (TransferInfo *transfer_info)
289
{
290
	if (transfer_info->progress_dialog != NULL) {
291
		return GTK_WIDGET (transfer_info->progress_dialog);
292 293
	}

294
	return transfer_info->parent_view;
295 296
}

297 298
#ifdef GNOME2_CONVERSION_COMPLETE
/* This needs testing with a working WM - metacity ? */
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
static void
fit_rect_on_screen (GdkRectangle *rect)
{
	if (rect->x + rect->width > gdk_screen_width ()) {
		rect->x = gdk_screen_width () - rect->width;
	}

	if (rect->y + rect->height > gdk_screen_height ()) {
		rect->y = gdk_screen_height () - rect->height;
	}

	if (rect->x < 0) {
		rect->x = 0;
	}

	if (rect->y < 0) {
		rect->y = 0;
	}
}

static void
center_dialog_over_rect (GtkWindow *window, GdkRectangle rect)
{
	g_return_if_fail (GTK_WINDOW (window) != NULL);

	fit_rect_on_screen (&rect);

	gtk_widget_set_uposition (GTK_WIDGET (window), 
		rect.x + rect.width / 2 
			- GTK_WIDGET (window)->allocation.width / 2,
		rect.y + rect.height / 2
			- GTK_WIDGET (window)->allocation.height / 2);
}
332
#endif
333 334 335 336

static void
center_dialog_over_window (GtkWindow *window, GtkWindow *over)
{
337
#ifdef GNOME2_CONVERSION_COMPLETE
338 339 340 341 342 343 344 345 346 347 348 349 350 351
	GdkRectangle rect;
	int x, y, w, h;

	g_return_if_fail (GTK_WINDOW (window) != NULL);
	g_return_if_fail (GTK_WINDOW (over) != NULL);

	gdk_window_get_root_origin (GTK_WIDGET (over)->window, &x, &y);
	gdk_window_get_size (GTK_WIDGET (over)->window, &w, &h);
	rect.x = x;
	rect.y = y;
	rect.width = w;
	rect.height = h;

	center_dialog_over_rect (window, rect);
352 353 354 355 356 357 358 359 360
#endif
	GdkGeometry geometry = { 0 };

	geometry.win_gravity = GDK_GRAVITY_CENTER;

	gtk_window_set_geometry_hints (window,
				       GTK_WIDGET (over),
				       &geometry,
				       GDK_HINT_WIN_GRAVITY);
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
	if (!transfer_info->show_progress_dialog) {
380
		return;
381
	}
382

383
	g_return_if_fail (transfer_info->progress_dialog == NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
384

385
	transfer_info->progress_dialog = nautilus_file_operations_progress_new 
386
		(transfer_info->operation_title, "", "", "", 0, 0);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
387

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

400
	/* Make the progress dialog show up over the window we are copying into */
401 402
	if (transfer_info->parent_view != NULL) {
		center_dialog_over_window (GTK_WINDOW (transfer_info->progress_dialog), 
403
					   GTK_WINDOW (gtk_widget_get_toplevel (transfer_info->parent_view)));
404
	}
405 406

	gtk_widget_show (GTK_WIDGET (transfer_info->progress_dialog));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
407 408
}

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

	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);
435 436 437 438 439 440 441

		/* remove the last '/' */
		length = strlen (from_path);
		if (from_path [length - 1] == '/') {
			from_path [length - 1] = '\0';
		}
		
Pavel Cisler's avatar
Pavel Cisler committed
442 443 444
		gnome_vfs_uri_unref (uri);
		g_assert (progress_verb);
		progress_label_text = g_strdup_printf ("%s:", progress_verb);
445
		/* "From" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
446 447 448 449
		from_prefix = _("From:");
	}

	if (to_uri != NULL) {
450
		uri = gnome_vfs_uri_new (to_uri);
Pavel Cisler's avatar
Pavel Cisler committed
451
		to_path = gnome_vfs_uri_extract_dirname (uri);
452 453 454 455 456 457 458

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

Pavel Cisler's avatar
Pavel Cisler committed
459
		gnome_vfs_uri_unref (uri);
460
		/* "To" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
461 462 463
		to_prefix = _("To:");
	}

464 465 466 467 468 469 470
	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
471 472 473 474 475 476 477

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

478
static int
479
handle_transfer_ok (const GnomeVFSXferProgressInfo *progress_info,
480
		    TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
481
{
482 483
	if (transfer_info->cancelled
		&& progress_info->phase != GNOME_VFS_XFER_PHASE_COMPLETED) {
484 485
		/* If cancelled, delete any partially copied files that are laying
		 * around and return.
486
		 */
487 488
		if (progress_info->target_name != NULL
		    && progress_info->bytes_total != progress_info->bytes_copied) {
489
			GList *delete_me;
490

491
			delete_me = g_list_prepend (NULL, progress_info->target_name);
492 493 494 495
			nautilus_file_operations_delete (delete_me, transfer_info->parent_view);
			g_list_free (delete_me);
		}

496 497 498
		return 0;
	}
	
Ettore Perazzoli's avatar
Ettore Perazzoli committed
499
	switch (progress_info->phase) {
500
	case GNOME_VFS_XFER_PHASE_INITIAL:
501
		create_transfer_dialog (progress_info, transfer_info);
502
		return 1;
503 504

	case GNOME_VFS_XFER_PHASE_COLLECTING:
505
		if (transfer_info->progress_dialog != NULL) {
506
			nautilus_file_operations_progress_set_operation_string
507 508
				(transfer_info->progress_dialog,
				 transfer_info->preparation_name);
509
		}
510
		return 1;
511 512

	case GNOME_VFS_XFER_PHASE_READYTOGO:
513
		if (transfer_info->progress_dialog != NULL) {
514 515 516
			nautilus_file_operations_progress_set_operation_string
				(transfer_info->progress_dialog,
				 transfer_info->action_label);
517
			nautilus_file_operations_progress_set_total
518 519 520
				(transfer_info->progress_dialog,
				 progress_info->files_total,
				 progress_info->bytes_total);
521
		}
522
		return 1;
523
				 
524
	case GNOME_VFS_XFER_PHASE_DELETESOURCE:
525
		nautilus_file_changes_consume_changes (FALSE);
526
		if (transfer_info->progress_dialog != NULL) {
527 528 529 530 531 532 533
			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
534

535
			nautilus_file_operations_progress_update_sizes
536
				(transfer_info->progress_dialog,
537
				 MIN (progress_info->bytes_copied, 
538
				      progress_info->bytes_total),
539
				 MIN (progress_info->total_bytes_copied,
540
				      progress_info->bytes_total));
541
		}
542
		return 1;
543 544

	case GNOME_VFS_XFER_PHASE_MOVING:
545 546
	case GNOME_VFS_XFER_PHASE_OPENSOURCE:
	case GNOME_VFS_XFER_PHASE_OPENTARGET:
547 548
		/* fall through */
	case GNOME_VFS_XFER_PHASE_COPYING:
549
		if (transfer_info->progress_dialog != NULL) {
550
			if (progress_info->bytes_copied == 0) {
551 552 553 554 555 556 557
				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);
558
			} else {
559
				nautilus_file_operations_progress_update_sizes
560
					(transfer_info->progress_dialog,
561
					 MIN (progress_info->bytes_copied, 
562
					      progress_info->bytes_total),
563
					 MIN (progress_info->total_bytes_copied,
564
					      progress_info->bytes_total));
565
			}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
566
		}
567
		return 1;
568

569
	case GNOME_VFS_XFER_PHASE_CLEANUP:
570
		if (transfer_info->progress_dialog != NULL) {
571 572
			nautilus_file_operations_progress_clear
				(transfer_info->progress_dialog);
573
			nautilus_file_operations_progress_set_operation_string
574 575
				(transfer_info->progress_dialog,
				 transfer_info->cleanup_name);
576
		}
577
		return 1;
578

Ettore Perazzoli's avatar
Ettore Perazzoli committed
579
	case GNOME_VFS_XFER_PHASE_COMPLETED:
580
		nautilus_file_changes_consume_changes (TRUE);
581
		if (transfer_info->done_callback != NULL) {
582 583 584 585
			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;
586
		}
587 588

		transfer_info_destroy (transfer_info);
589
		return 1;
590

Ettore Perazzoli's avatar
Ettore Perazzoli committed
591
	default:
592
		return 1;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
593 594 595
	}
}

596 597 598 599 600 601 602 603
typedef enum {
	ERROR_READ_ONLY,
	ERROR_NOT_READABLE,
	ERROR_NOT_WRITABLE,
	ERROR_NOT_ENOUGH_PERMISSIONS,
	ERROR_NO_SPACE,
	ERROR_OTHER
} NautilusFileOperationsErrorKind;
604

605 606 607 608 609 610 611 612 613 614 615
typedef enum {
	ERROR_LOCATION_UNKNOWN,
	ERROR_LOCATION_SOURCE,
	ERROR_LOCATION_SOURCE_PARENT,
	ERROR_LOCATION_SOURCE_OR_PARENT,
	ERROR_LOCATION_TARGET
} NautilusFileOperationsErrorLocation;


static char *
build_error_string (const char *source_name, const char *target_name,
616 617 618 619
		    TransferKind operation_kind,
		    NautilusFileOperationsErrorKind error_kind,
		    NautilusFileOperationsErrorLocation error_location,
		    GnomeVFSResult error)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
620
{
621 622 623 624 625 626
	/* 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
627

628
	const char *error_string;
629
	char *result;
630

631 632
	error_string = NULL;
	result = NULL;
633

634
	if (error_location == ERROR_LOCATION_SOURCE_PARENT) {
635

636
		switch (operation_kind) {
637 638
		case TRANSFER_MOVE:
		case TRANSFER_MOVE_TO_TRASH:
639
			if (error_kind == ERROR_READ_ONLY) {
640
				error_string = _("Error while moving.\n\n"
641 642
						 "\"%s\" cannot be moved because it is on "
						 "a read-only disk.");
643 644
			}
			break;
645

646 647
		case TRANSFER_DELETE:
		case TRANSFER_EMPTY_TRASH:
648 649 650
			switch (error_kind) {
			case ERROR_NOT_ENOUGH_PERMISSIONS:
			case ERROR_NOT_WRITABLE:
651
				error_string = _("Error while deleting.\n\n"
652
						 "\"%s\" cannot be deleted because you do not have "
653 654 655 656
						 "permissions to modify its parent folder.");
				break;
			
			case ERROR_READ_ONLY:
657
				error_string = _("Error while deleting.\n\n"
658 659 660 661 662 663
						 "\"%s\" cannot be deleted because it is on "
						 "a read-only disk.");
				break;

			default:
				break;
664 665 666 667 668 669 670 671 672 673 674
			}
			break;

		default:
			g_assert_not_reached ();
			break;
		}
		
		if (error_string != NULL) {
			g_assert (source_name != NULL);
			result = g_strdup_printf (error_string, source_name);
675
		}
676

677
	} else if (error_location == ERROR_LOCATION_SOURCE_OR_PARENT) {
678

679 680
		g_assert (source_name != NULL);

681 682 683 684
		/* 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.
		 */
685
		switch (operation_kind) {
686
		case TRANSFER_MOVE:
687
			if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
688
				error_string = _("Error while moving.\n\n"
689 690 691
						 "\"%s\" cannot be moved because you do not have "
						 "permissions to change it or its parent folder.");
			}
692
			break;
693
		case TRANSFER_MOVE_TO_TRASH:
694
			if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
695
				error_string = _("Error while moving.\n\n"
696
						 "\"%s\" cannot be moved to the Trash because you do not have "
697 698
						 "permissions to change it or its parent folder.");
			}
699
			break;
700

701
		default:
702
			g_assert_not_reached ();
703 704
			break;
		}
705

706 707 708 709
		if (error_string != NULL) {
			g_assert (source_name != NULL);
			result = g_strdup_printf (error_string, source_name);
		}
710

711
	} else if (error_location == ERROR_LOCATION_SOURCE) {
712

713 714 715
		g_assert (source_name != NULL);

		switch (operation_kind) {
716 717
		case TRANSFER_COPY:
		case TRANSFER_DUPLICATE:
718
			if (error_kind == ERROR_NOT_READABLE) {
719
				error_string = _("Error while copying.\n\n"
720 721
						 "\"%s\" cannot be copied because you do not have "
						 "permissions to read it.");
722 723
			}
			break;
724

725 726 727
		default:
			g_assert_not_reached ();
			break;
728
		}
729

730 731 732
		if (error_string != NULL) {
			g_assert (source_name != NULL);
			result = g_strdup_printf (error_string, source_name);
733
		}
734

735 736 737 738
	} else if (error_location == ERROR_LOCATION_TARGET) {

		if (error_kind == ERROR_NO_SPACE) {
			switch (operation_kind) {
739 740
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
741
				error_string = _("Error while copying to \"%s\".\n\n"
742
				   		 "There is not enough space on the destination.");
743
				break;
744 745
			case TRANSFER_MOVE_TO_TRASH:
			case TRANSFER_MOVE:
746
				error_string = _("Error while moving to \"%s\".\n\n"
747
				   		 "There is not enough space on the destination.");
748
				break;
749
			case TRANSFER_LINK:
750
				error_string = _("Error while creating link in \"%s\".\n\n"
751
				   		 "There is not enough space on the destination.");
752 753 754 755 756 757 758
				break;
			default:
				g_assert_not_reached ();
				break;
			}
		} else {
			switch (operation_kind) {
759 760
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
761
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
762
					error_string = _("Error while copying to \"%s\".\n\n"
763
					   		 "You do not have permissions to write to "
764
					   		 "this folder.");
765
				} else if (error_kind == ERROR_NOT_WRITABLE) {
766
					error_string = _("Error while copying to \"%s\".\n\n"
767
					   		 "The destination disk is read-only.");
768 769
				} 
				break;
770 771
			case TRANSFER_MOVE:
			case TRANSFER_MOVE_TO_TRASH:
772
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
773
					error_string = _("Error while moving items to \"%s\".\n\n"
774
					   		 "You do not have permissions to write to "
775
					   		 "this folder.");
776
				} else if (error_kind == ERROR_NOT_WRITABLE) {
777
					error_string = _("Error while moving items to \"%s\".\n\n"
778
					   		 "The destination disk is read-only.");
779 780 781
				} 

				break;
782
			case TRANSFER_LINK:
783
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
784
					error_string = _("Error while creating links in \"%s\".\n\n"
785
					   		 "You do not have permissions to write to "
786
					   		 "this folder.");
787
				} else if (error_kind == ERROR_NOT_WRITABLE) {
788
					error_string = _("Error while creating links in \"%s\".\n\n"
789
					   		 "The destination disk is read-only.");
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
				} 
				break;
			default:
				g_assert_not_reached ();
				break;
			}
		}
		if (error_string != NULL) {
			g_assert (target_name != NULL);
			result = g_strdup_printf (error_string, target_name);
		}
	}
	
	if (result == NULL) {
		/* None of the specific error messages apply, use a catch-all
		 * generic error
		 */
807 808
		g_message ("Hit unexpected error \"%s\" while doing a file operation.",
			   gnome_vfs_result_to_string (error));
809 810 811 812

		/* FIXMEs: we need to consider a single item
		 * move/copy and not offer to continue in that case
		 */
813 814
		if (source_name != NULL) {
			switch (operation_kind) {
815 816
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
817
				error_string = _("Error \"%s\" while copying \"%s\".\n\n"
818 819
						 "Would you like to continue?");
				break;
820
			case TRANSFER_MOVE:
821
				error_string = _("Error \"%s\" while moving \"%s\".\n\n"
822 823
						 "Would you like to continue?");
				break;
824
			case TRANSFER_LINK:
825
				error_string = _("Error \"%s\" while creating a link to \"%s\".\n\n"
826 827
						 "Would you like to continue?");
				break;
828 829 830
			case TRANSFER_DELETE:
			case TRANSFER_EMPTY_TRASH:
			case TRANSFER_MOVE_TO_TRASH:
831
				error_string = _("Error \"%s\" while deleting \"%s\".\n\n"
832 833 834
						 "Would you like to continue?");
				break;
			default:
835
				g_assert_not_reached ();
836 837 838
				break;
			}
	
839
			result = g_strdup_printf (error_string, 
840 841
						  gnome_vfs_result_to_string (error),
						  source_name);
842
		} else {
843
			switch (operation_kind) {
844 845
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
846
				error_string = _("Error \"%s\" while copying.\n\n"
847 848
						 "Would you like to continue?");
				break;
849
			case TRANSFER_MOVE:
850
				error_string = _("Error \"%s\" while moving.\n\n"
851 852
						 "Would you like to continue?");
				break;
853
			case TRANSFER_LINK:
854
				error_string = _("Error \"%s\" while linking.\n\n"
855 856
						 "Would you like to continue?");
				break;
857 858 859
			case TRANSFER_DELETE:
			case TRANSFER_EMPTY_TRASH:
			case TRANSFER_MOVE_TO_TRASH:
860
				error_string = _("Error \"%s\" while deleting.\n\n"
861 862 863
						 "Would you like to continue?");
				break;
			default:
864
				g_assert_not_reached ();
865 866 867
				break;
			}
	
868
			result = g_strdup_printf (error_string, 
869
						  gnome_vfs_result_to_string (error));
870
		}
871 872 873
	}
	return result;
}
874

875
static int
876
handle_transfer_vfs_error (const GnomeVFSXferProgressInfo *progress_info,
877
			   TransferInfo *transfer_info)
878
{
879
	/* Notice that the error mode in `transfer_info' is the one we have been
880 881 882
         * requested, but the transfer is always performed in mode
         * `GNOME_VFS_XFER_ERROR_MODE_QUERY'.
         */
883

884 885 886
	int error_dialog_button_pressed;
	int error_dialog_result;
	char *text;
887 888
	char *formatted_source_name;
	char *formatted_target_name;
889 890 891 892
	const char *dialog_title;
	NautilusFileOperationsErrorKind error_kind;
	NautilusFileOperationsErrorLocation error_location;
	
893
	switch (transfer_info->error_mode) {
894 895 896 897
	case GNOME_VFS_XFER_ERROR_MODE_QUERY:

		/* transfer error, prompt the user to continue or stop */

898 899
		formatted_source_name = NULL;
		formatted_target_name = NULL;
900 901

		if (progress_info->source_name != NULL) {
902
			formatted_source_name = format_and_ellipsize_uri_for_dialog
903 904 905 906
				(progress_info->source_name);
		}

		if (progress_info->target_name != NULL) {
907
			formatted_target_name = format_and_ellipsize_uri_for_dialog
908 909 910 911 912
				(progress_info->target_name);
		}

		error_kind = ERROR_OTHER;
		error_location = ERROR_LOCATION_UNKNOWN;
913
		
914 915 916 917 918
		/* Single out a few common error conditions for which we have
		 * custom-taylored error messages.
		 */
		if ((progress_info->vfs_status == GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM
				|| progress_info->vfs_status == GNOME_VFS_ERROR_READ_ONLY)
919 920
			&& (transfer_info->kind == TRANSFER_DELETE
				|| transfer_info->kind == TRANSFER_EMPTY_TRASH)) {
921 922 923
			error_location = ERROR_LOCATION_SOURCE_PARENT;
			error_kind = ERROR_READ_ONLY;
		} else if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
924 925
			&& (transfer_info->kind == TRANSFER_DELETE
				|| transfer_info->kind == TRANSFER_EMPTY_TRASH)) {
926 927 928 929
			error_location = ERROR_LOCATION_SOURCE_PARENT;
			error_kind = ERROR_NOT_ENOUGH_PERMISSIONS;
		} else if ((progress_info->vfs_status == GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM
				|| progress_info->vfs_status == GNOME_VFS_ERROR_READ_ONLY)
930 931
			&& (transfer_info->kind == TRANSFER_MOVE
				|| transfer_info->kind == TRANSFER_MOVE_TO_TRASH)
932 933 934
			&& progress_info->phase != GNOME_VFS_XFER_CHECKING_DESTINATION) {
			error_location = ERROR_LOCATION_SOURCE_PARENT;
			error_kind = ERROR_READ_ONLY;
935 936 937 938 939
		} else if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
			&& transfer_info->kind == TRANSFER_MOVE
			&& progress_info->phase == GNOME_VFS_XFER_PHASE_OPENTARGET) {
			error_location = ERROR_LOCATION_TARGET;
			error_kind = ERROR_NOT_ENOUGH_PERMISSIONS;
940
		} else if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
941 942
			&& (transfer_info->kind == TRANSFER_MOVE
				|| transfer_info->kind == TRANSFER_MOVE_TO_TRASH)
943 944 945 946
			&& progress_info->phase != GNOME_VFS_XFER_CHECKING_DESTINATION) {
			error_location = ERROR_LOCATION_SOURCE_OR_PARENT;
			error_kind = ERROR_NOT_ENOUGH_PERMISSIONS;
		} else if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
947 948
			&& (transfer_info->kind == TRANSFER_COPY
				|| transfer_info->kind == TRANSFER_DUPLICATE)
949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967
			&& (progress_info->phase == GNOME_VFS_XFER_PHASE_OPENSOURCE
				|| progress_info->phase == GNOME_VFS_XFER_PHASE_COLLECTING
				|| progress_info->phase == GNOME_VFS_XFER_PHASE_INITIAL)) {
			error_location = ERROR_LOCATION_SOURCE;
			error_kind = ERROR_NOT_READABLE;
		} else if ((progress_info->vfs_status == GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM
				|| progress_info->vfs_status == GNOME_VFS_ERROR_READ_ONLY)
			&& progress_info->phase == GNOME_VFS_XFER_CHECKING_DESTINATION) {
			error_location = ERROR_LOCATION_TARGET;
			error_kind = ERROR_NOT_WRITABLE;
		} else if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
			&& progress_info->phase == GNOME_VFS_XFER_CHECKING_DESTINATION) {
			error_location = ERROR_LOCATION_TARGET;
			error_kind = ERROR_NOT_ENOUGH_PERMISSIONS;
		} else if (progress_info->vfs_status == GNOME_VFS_ERROR_NO_SPACE) {
			error_location = ERROR_LOCATION_TARGET;
			error_kind = ERROR_NO_SPACE;
		}

968 969 970 971
		text = build_error_string (formatted_source_name, formatted_target_name,
					   transfer_info->kind,
					   error_kind, error_location,
					   progress_info->vfs_status);
972

973 974 975
		switch (transfer_info->kind) {
		case TRANSFER_COPY:
		case TRANSFER_DUPLICATE:
976 977
			dialog_title = _("Error while copying.");
			break;
978
		case TRANSFER_MOVE:
979 980
			dialog_title = _("Error while moving.");
			break;
981
		case TRANSFER_LINK:
982 983
			dialog_title = _("Error while linking.");
			break;
984 985 986
		case TRANSFER_DELETE:
		case TRANSFER_EMPTY_TRASH:
		case TRANSFER_MOVE_TO_TRASH:
987 988
			dialog_title = _("Error while deleting.");
			break;
989
		default:
990 991 992
			dialog_title = NULL;
			break;
		}
993

994 995
		if (error_location == ERROR_LOCATION_TARGET) {
			/* We can't continue, just tell the user. */
Ramiro Estrugo's avatar
Ramiro Estrugo committed
996
			eel_run_simple_dialog (parent_for_error_dialog (transfer_info),
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
				TRUE, text, dialog_title, _("Stop"), NULL);
			error_dialog_result = GNOME_VFS_XFER_ERROR_ACTION_ABORT;

		} else if ((error_location == ERROR_LOCATION_SOURCE
				|| error_location == ERROR_LOCATION_SOURCE_PARENT
				|| error_location == ERROR_LOCATION_SOURCE_OR_PARENT)
			&& (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS
				|| error_kind == ERROR_NOT_READABLE)) {
			/* The error could have happened on any of the files
			 * in the moved/copied/deleted hierarchy, we can probably
			 * continue. Allow the user to skip.
			 */
Ramiro Estrugo's avatar
Ramiro Estrugo committed
1009
			error_dialog_button_pressed = eel_run_simple_dialog
1010
				(parent_for_error_dialog (transfer_info), TRUE, text, 
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
				dialog_title,
				 _("Skip"), _("Stop"), NULL);
				 
			switch (error_dialog_button_pressed) {
			case 0:
				error_dialog_result = GNOME_VFS_XFER_ERROR_ACTION_SKIP;
				break;
			case 1:
				error_dialog_result = GNOME_VFS_XFER_ERROR_ACTION_ABORT;
				break;
			default:
				g_assert_not_reached ();
				error_dialog_result = GNOME_VFS_XFER_ERROR_ACTION_ABORT;
			}
								
		} else {
			/* Generic error, offer to retry and skip. */
Ramiro Estrugo's avatar
Ramiro Estrugo committed
1028
			error_dialog_button_pressed = eel_run_simple_dialog
1029
				(parent_for_error_dialog (transfer_info), TRUE, text, 
1030
				 dialog_title,
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
				 _("Skip"), _("Retry"), _("Stop"), NULL);

			switch (error_dialog_button_pressed) {
			case 0:
				error_dialog_result = GNOME_VFS_XFER_ERROR_ACTION_SKIP;
				break;
			case 1:
				error_dialog_result = GNOME_VFS_XFER_ERROR_ACTION_RETRY;
				break;
			case 2:
				error_dialog_result = GNOME_VFS_XFER_ERROR_ACTION_ABORT;
				break;
			default:
				g_assert_not_reached ();
				error_dialog_result = GNOME_VFS_XFER_ERROR_ACTION_ABORT;
			}
		}
			
		g_free (text);
1050 1051 1052
		g_free (formatted_source_name);
		g_free (formatted_target_name);

1053
		return error_dialog_result;
1054

Ettore Perazzoli's avatar
Ettore Perazzoli committed
1055 1056
	case GNOME_VFS_XFER_ERROR_MODE_ABORT:
	default:
1057
		if (transfer_info->progress_dialog != NULL) {
1058 1059
			nautilus_file_operations_progress_done
				(transfer_info->progress_dialog);
1060
		}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1061 1062 1063 1064
		return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
	}
}

1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
/* is_special_link
 *
 * Check and see if file is one of our special links.
 * A special link ould be one of the following:
 * 	trash, home, volume
 */
static gboolean
is_special_link (const char *uri)
{
	char *local_path;
	gboolean is_special;

	local_path = gnome_vfs_get_local_path_from_uri (uri);

	/* FIXME: This should use some API to check if the file is a
	 * link. Normally we use the MIME type. As things stand, this
	 * will read files and try to parse them as XML, which could
	 * result in a lot of output to the console, since the XML
	 * parser reports errors directly there.
	 */
	is_special = local_path != NULL
		&& nautilus_link_local_get_link_type (local_path) != NAUTILUS_LINK_GENERIC;
	
	g_free (local_path);
	
	return is_special;
}

1093
static int
1094
handle_transfer_overwrite (const GnomeVFSXferProgressInfo *progress_info,
1095
		           TransferInfo *transfer_info)
1096
{
1097
	int result;
1098
	char *text, *formatted_name;
1099

1100
	/* Handle special case files such as Trash, mount links and home directory */	
1101
	if (is_special_link (progress_info->target_name)) {
1102 1103
		formatted_name = extract_and_ellipsize_file_name_for_dialog
			(progress_info->target_name);
1104 1105 1106
		
		if (transfer_info->kind == TRANSFER_MOVE) {
			text = g_strdup_printf (_("\"%s\" could not be moved to the new location, "
1107 1108
						  "because its name is already used for a special item that "
						  "cannot be removed or replaced.\n\n"
1109 1110 1111 1112
						  "If you still want to move \"%s\", rename it and try again."),
						formatted_name, formatted_name);
		} else {
			text = g_strdup_printf (_("\"%s\" could not be copied to the new location, "
1113 1114
						  "because its name is already used for a special item that "
						  "cannot be removed or replaced.\n\n"
1115 1116
						  "If you still want to copy \"%s\", rename it and try again."),
						formatted_name, formatted_name);
1117
		}
1118
		
Ramiro Estrugo's avatar
Ramiro Estrugo committed
1119
		eel_run_simple_dialog (parent_for_error_dialog (transfer_info), TRUE, text,
1120
				       _("Unable to replace file."), GTK_STOCK_OK, NULL, NULL);
1121 1122 1123 1124

		g_free (text);
		g_free (formatted_name);

1125 1126 1127 1128
		return GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP;
	}
	
	/* transfer conflict, prompt the user to replace or skip */
1129
	formatted_name = format_and_ellipsize_uri_for_dialog (progress_info->target_name);
1130
	text = g_strdup_printf (_("File \"%s\" already exists.\n\n"
1131
				  "Would you like to replace it?"), 
1132 1133
				formatted_name);
	g_free (formatted_name);
1134 1135 1136 1137 1138

	if (progress_info->duplicate_count == 1) {
		/* we are going to only get one duplicate alert, don't offer
		 * Replace All
		 */
Ramiro Estrugo's avatar
Ramiro Estrugo committed
1139
		result = eel_run_simple_dialog
1140
			(parent_for_error_dialog (transfer_info), TRUE, text, 
1141
			 _("Conflict while copying"),
1142
			 _("Replace"), _("Skip"), NULL);
1143 1144 1145
		switch (result) {
		case 0:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE;
1146 1147 1148
		default:
			g_assert_not_reached ();
			/* fall through */
1149 1150 1151 1152
		case 1:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP;
		}
	} else {
Ramiro Estrugo's avatar
Ramiro Estrugo committed
1153
		result = eel_run_simple_dialog
1154
			(parent_for_error_dialog (transfer_info), TRUE, text, 
1155
			 _("Conflict while copying"),
1156
			 _("Replace All"), _("Replace"), _("Skip"), NULL);
1157 1158 1159 1160 1161 1162

		switch (result) {
		case 0:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE_ALL;
		case 1:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE;
1163 1164 1165
		default:
			g_assert_not_reached ();
			/* fall through */
1166 1167 1168
		case 2:
			return GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP;
		}
1169 1170 1171
	}
}

1172
/* Note that we have these two separate functions with separate format
1173
 * strings for ease of localization.
1174 1175 1176
 */

static char *
1177
get_link_name (char *name, int count) 
1178
{
1179
	char *result;
1180 1181 1182 1183
	char *unescaped_name;
	char *unescaped_result;

	const char *format;
1184
	
1185 1186
	g_assert (name != NULL);

1187 1188 1189
	unescaped_name = gnome_vfs_unescape_string (name, "/");
	g_free (name);

1190 1191 1192 1193 1194
	if (count < 1) {
		g_warning ("bad count in get_link_name");
		count = 1;
	}

1195 1196 1197
	if (count <= 2) {
		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
1198
		 */
1199
		switch (count) {
1200 1201 1202
		default:
			g_assert_not_reached ();
			/* fall through */
1203
		case 1:
1204
			/* appended to new link file */
1205 1206 1207
			format = _("link to %s");
			break;
		case 2:
1208
			/* appended to new link file */
1209 1210 1211
			format = _("another link to %s");
			break;
		}
1212
		unescaped_result = g_strdup_printf (format, unescaped_name);
1213 1214 1215 1216 1217

	} else {
		/* Handle special cases for the first few numbers of each ten.
		 * For locales where getting this exactly right is difficult,
		 * these can just be made all the same as the general case below.
1218
		 */
1219 1220 1221 1222 1223 1224 1225 1226 1227
		switch (count % 10) {
		case 1:
			/* Localizers: Feel free to leave out the "st" suffix
			 * if there's no way to do that nicely for a
			 * particular language.
			 */
			format = _("%dst link to %s");
			break;
		case 2:
1228
			/* appended to new link file */
1229 1230 1231
			format = _("%dnd link to %s");
			break;
		case 3:
1232
			/* appended to new link file */
1233 1234 1235
			format = _("%drd link to %s");
			break;
		default:
1236
			/* appended to new link file */
1237 1238 1239
			format = _("%dth link to %s");
			break;
		}
1240
		unescaped_result = g_strdup_printf (format, count, unescaped_name);
1241
	}
1242

1243 1244 1245 1246 1247
	result = gnome_vfs_escape_path_string (unescaped_result);
	
	g_free (unescaped_name);
	g_free (unescaped_result);

1248
	return result;
1249 1250
}

1251 1252 1253 1254 1255
/* Localizers: 
 * Feel free to leave out the st, nd, rd and th suffix or
 * make some or all of them match.
 */

1256
/* localizers: tag used to detect the first copy of a file */
1257
static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
1258
/* localizers: tag used to detect the second copy of a file */