nautilus-file-operations.c 72.2 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 "nautilus-file-operations.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
29

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

#include <eel/eel-gdk-font-extensions.h>
#include <eel/eel-glib-extensions.h>
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-stock-dialogs.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
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"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
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;
Pavel Cisler's avatar
Pavel Cisler committed
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
	/* get a nice length to ellipsize to, based on the font */
232
	font = get_label_font ();
233
	maximum_width = gdk_string_width (font, "MMMMMMMMMMMMMMMMMMMMMM");
234 235

	result = eel_string_ellipsize (str, font, maximum_width, EEL_ELLIPSIZE_MIDDLE);
236
	gdk_font_unref (font);
237 238 239
#else
	result = g_strdup (str);
#endif
240

241 242
	return result;
}
243

244 245 246 247
static char *
format_and_ellipsize_uri_for_dialog (const char *uri)
{
	char *unescaped, *result;
248

Ramiro Estrugo's avatar
Ramiro Estrugo committed
249
	unescaped = eel_format_uri_for_display (uri);
250
	result = ellipsize_string_for_dialog (unescaped);
251
	g_free (unescaped);
252

253 254 255 256
	return result;
}

static char *
257
extract_and_ellipsize_file_name_for_dialog (const char *uri)
258
{
259 260
	const char *last_part;
	char *unescaped, *result;
261
	
262
	last_part = g_basename (uri);
263
	g_return_val_if_fail (last_part != NULL, NULL);
264 265 266 267

	unescaped = gnome_vfs_unescape_string_for_display (last_part);
	result = ellipsize_string_for_dialog (unescaped);
	g_free (unescaped);
268

269 270 271
	return result;
}

272
static GtkWidget *
273
parent_for_error_dialog (TransferInfo *transfer_info)
274
{
275
	if (transfer_info->progress_dialog != NULL) {
276
		return GTK_WIDGET (transfer_info->progress_dialog);
277 278
	}

279
	return transfer_info->parent_view;
280 281
}

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 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 332 333 334
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);
}

static void
center_dialog_over_window (GtkWindow *window, GtkWindow *over)
{
	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);
}

335 336 337 338 339 340 341 342
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)
343
{
344
	transfer_info->cancelled = TRUE;
345 346
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
347
static void
348
create_transfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
349
			TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
350
{
351
	if (!transfer_info->show_progress_dialog) {
352
		return;
353
	}
354

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

357
	transfer_info->progress_dialog = nautilus_file_operations_progress_new 
358
		(transfer_info->operation_title, "", "", "", 0, 0);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
359

360 361 362
	/* Treat clicking on the close box or use of the escape key
	 * the same as clicking cancel.
	 */
363
	g_signal_connect (transfer_info->progress_dialog,
364 365 366
			  "response",
			  G_CALLBACK (handle_response_callback),
			  NULL);
367
	g_signal_connect (transfer_info->progress_dialog,
368 369 370
			  "close",
			  G_CALLBACK (handle_close_callback),
			  transfer_info);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
371

372
	/* Make the progress dialog show up over the window we are copying into */
373 374
	if (transfer_info->parent_view != NULL) {
		center_dialog_over_window (GTK_WINDOW (transfer_info->progress_dialog), 
375
					   GTK_WINDOW (gtk_widget_get_toplevel (transfer_info->parent_view)));
376
	}
377 378

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

Pavel Cisler's avatar
Pavel Cisler committed
381
static void
382
progress_dialog_set_to_from_item_text (NautilusFileOperationsProgress *dialog,
383 384 385
				       const char *progress_verb,
				       const char *from_uri, const char *to_uri, 
				       gulong index, gulong size)
Pavel Cisler's avatar
Pavel Cisler committed
386 387 388 389 390 391 392 393
{
	char *item;
	char *from_path;
	char *to_path;
	char *progress_label_text;
	const char *from_prefix;
	const char *to_prefix;
	GnomeVFSURI *uri;
394
	int length;
Pavel Cisler's avatar
Pavel Cisler committed
395 396 397 398 399 400 401 402 403 404 405 406

	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);
407 408 409 410 411 412 413

		/* remove the last '/' */
		length = strlen (from_path);
		if (from_path [length - 1] == '/') {
			from_path [length - 1] = '\0';
		}
		
Pavel Cisler's avatar
Pavel Cisler committed
414 415 416
		gnome_vfs_uri_unref (uri);
		g_assert (progress_verb);
		progress_label_text = g_strdup_printf ("%s:", progress_verb);
417
		/* "From" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
418 419 420 421
		from_prefix = _("From:");
	}

	if (to_uri != NULL) {
422
		uri = gnome_vfs_uri_new (to_uri);
Pavel Cisler's avatar
Pavel Cisler committed
423
		to_path = gnome_vfs_uri_extract_dirname (uri);
424 425 426 427 428 429 430

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

Pavel Cisler's avatar
Pavel Cisler committed
431
		gnome_vfs_uri_unref (uri);
432
		/* "To" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
433 434 435
		to_prefix = _("To:");
	}

436 437 438 439 440 441 442
	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
443 444 445 446 447 448 449

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

Pavel Cisler's avatar
Pavel Cisler committed
450
static int
451
handle_transfer_ok (const GnomeVFSXferProgressInfo *progress_info,
452
		    TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
453
{
454 455
	if (transfer_info->cancelled
		&& progress_info->phase != GNOME_VFS_XFER_PHASE_COMPLETED) {
456 457
		/* If cancelled, delete any partially copied files that are laying
		 * around and return.
458
		 */
459 460
		if (progress_info->target_name != NULL
		    && progress_info->bytes_total != progress_info->bytes_copied) {
461
			GList *delete_me;
462

463
			delete_me = g_list_prepend (NULL, progress_info->target_name);
464 465 466 467
			nautilus_file_operations_delete (delete_me, transfer_info->parent_view);
			g_list_free (delete_me);
		}

468 469 470
		return 0;
	}
	
Ettore Perazzoli's avatar
Ettore Perazzoli committed
471
	switch (progress_info->phase) {
Pavel Cisler's avatar
Pavel Cisler committed
472
	case GNOME_VFS_XFER_PHASE_INITIAL:
473
		create_transfer_dialog (progress_info, transfer_info);
474
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
475 476

	case GNOME_VFS_XFER_PHASE_COLLECTING:
477
		if (transfer_info->progress_dialog != NULL) {
478
			nautilus_file_operations_progress_set_operation_string
479 480
				(transfer_info->progress_dialog,
				 transfer_info->preparation_name);
481
		}
482
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
483 484

	case GNOME_VFS_XFER_PHASE_READYTOGO:
485
		if (transfer_info->progress_dialog != NULL) {
486 487 488
			nautilus_file_operations_progress_set_operation_string
				(transfer_info->progress_dialog,
				 transfer_info->action_label);
489
			nautilus_file_operations_progress_set_total
490 491 492
				(transfer_info->progress_dialog,
				 progress_info->files_total,
				 progress_info->bytes_total);
493
		}
494
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
495
				 
496
	case GNOME_VFS_XFER_PHASE_DELETESOURCE:
497
		nautilus_file_changes_consume_changes (FALSE);
498
		if (transfer_info->progress_dialog != NULL) {
499 500 501 502 503 504 505
			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
506

507
			nautilus_file_operations_progress_update_sizes
508
				(transfer_info->progress_dialog,
509
				 MIN (progress_info->bytes_copied, 
510
				      progress_info->bytes_total),
511
				 MIN (progress_info->total_bytes_copied,
512
				      progress_info->bytes_total));
513
		}
514
		return 1;
515 516

	case GNOME_VFS_XFER_PHASE_MOVING:
Pavel Cisler's avatar
Pavel Cisler committed
517 518
	case GNOME_VFS_XFER_PHASE_OPENSOURCE:
	case GNOME_VFS_XFER_PHASE_OPENTARGET:
519 520
		/* fall through */
	case GNOME_VFS_XFER_PHASE_COPYING:
521
		if (transfer_info->progress_dialog != NULL) {
522
			if (progress_info->bytes_copied == 0) {
523 524 525 526 527 528 529
				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);
530
			} else {
531
				nautilus_file_operations_progress_update_sizes
532
					(transfer_info->progress_dialog,
533
					 MIN (progress_info->bytes_copied, 
534
					      progress_info->bytes_total),
535
					 MIN (progress_info->total_bytes_copied,
536
					      progress_info->bytes_total));
537
			}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
538
		}
539
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
540

541
	case GNOME_VFS_XFER_PHASE_CLEANUP:
542
		if (transfer_info->progress_dialog != NULL) {
543 544
			nautilus_file_operations_progress_clear
				(transfer_info->progress_dialog);
545
			nautilus_file_operations_progress_set_operation_string
546 547
				(transfer_info->progress_dialog,
				 transfer_info->cleanup_name);
548
		}
549
		return 1;
550

Ettore Perazzoli's avatar
Ettore Perazzoli committed
551
	case GNOME_VFS_XFER_PHASE_COMPLETED:
552
		nautilus_file_changes_consume_changes (TRUE);
553
		if (transfer_info->done_callback != NULL) {
554 555 556 557
			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;
558
		}
559 560

		transfer_info_destroy (transfer_info);
561
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
562

Ettore Perazzoli's avatar
Ettore Perazzoli committed
563
	default:
564
		return 1;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
565 566 567
	}
}

568 569 570 571 572 573 574 575
typedef enum {
	ERROR_READ_ONLY,
	ERROR_NOT_READABLE,
	ERROR_NOT_WRITABLE,
	ERROR_NOT_ENOUGH_PERMISSIONS,
	ERROR_NO_SPACE,
	ERROR_OTHER
} NautilusFileOperationsErrorKind;
576

577 578 579 580 581 582 583 584 585 586 587
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,
588 589 590 591
		    TransferKind operation_kind,
		    NautilusFileOperationsErrorKind error_kind,
		    NautilusFileOperationsErrorLocation error_location,
		    GnomeVFSResult error)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
592
{
593 594 595 596 597 598
	/* 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
599

600
	const char *error_string;
601
	char *result;
Pavel Cisler's avatar
Pavel Cisler committed
602

603 604
	error_string = NULL;
	result = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
605

606
	if (error_location == ERROR_LOCATION_SOURCE_PARENT) {
607

608
		switch (operation_kind) {
609 610
		case TRANSFER_MOVE:
		case TRANSFER_MOVE_TO_TRASH:
611
			if (error_kind == ERROR_READ_ONLY) {
612
				error_string = _("Error while moving.\n\n"
613 614
						 "\"%s\" cannot be moved because it is on "
						 "a read-only disk.");
615 616
			}
			break;
617

618 619
		case TRANSFER_DELETE:
		case TRANSFER_EMPTY_TRASH:
620 621 622
			switch (error_kind) {
			case ERROR_NOT_ENOUGH_PERMISSIONS:
			case ERROR_NOT_WRITABLE:
623
				error_string = _("Error while deleting.\n\n"
624
						 "\"%s\" cannot be deleted because you do not have "
625 626 627 628
						 "permissions to modify its parent folder.");
				break;
			
			case ERROR_READ_ONLY:
629
				error_string = _("Error while deleting.\n\n"
630 631 632 633 634 635
						 "\"%s\" cannot be deleted because it is on "
						 "a read-only disk.");
				break;

			default:
				break;
636 637 638 639 640 641 642 643 644 645 646
			}
			break;

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

649
	} else if (error_location == ERROR_LOCATION_SOURCE_OR_PARENT) {
650

651 652
		g_assert (source_name != NULL);

653 654 655 656
		/* 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.
		 */
657
		switch (operation_kind) {
658
		case TRANSFER_MOVE:
659
			if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
660
				error_string = _("Error while moving.\n\n"
661 662 663
						 "\"%s\" cannot be moved because you do not have "
						 "permissions to change it or its parent folder.");
			}
664
			break;
665
		case TRANSFER_MOVE_TO_TRASH:
666
			if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
667
				error_string = _("Error while moving.\n\n"
668
						 "\"%s\" cannot be moved to the Trash because you do not have "
669 670
						 "permissions to change it or its parent folder.");
			}
671
			break;
672

673
		default:
674
			g_assert_not_reached ();
675 676
			break;
		}
677

678 679 680 681
		if (error_string != NULL) {
			g_assert (source_name != NULL);
			result = g_strdup_printf (error_string, source_name);
		}
682

683
	} else if (error_location == ERROR_LOCATION_SOURCE) {
684

685 686 687
		g_assert (source_name != NULL);

		switch (operation_kind) {
688 689
		case TRANSFER_COPY:
		case TRANSFER_DUPLICATE:
690
			if (error_kind == ERROR_NOT_READABLE) {
691
				error_string = _("Error while copying.\n\n"
692 693
						 "\"%s\" cannot be copied because you do not have "
						 "permissions to read it.");
694 695
			}
			break;
696

697 698 699
		default:
			g_assert_not_reached ();
			break;
700
		}
701

702 703 704
		if (error_string != NULL) {
			g_assert (source_name != NULL);
			result = g_strdup_printf (error_string, source_name);
705
		}
Pavel Cisler's avatar
Pavel Cisler committed
706

707 708 709 710
	} else if (error_location == ERROR_LOCATION_TARGET) {

		if (error_kind == ERROR_NO_SPACE) {
			switch (operation_kind) {
711 712
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
713
				error_string = _("Error while copying to \"%s\".\n\n"
714
				   		 "There is not enough space on the destination.");
715
				break;
716 717
			case TRANSFER_MOVE_TO_TRASH:
			case TRANSFER_MOVE:
718
				error_string = _("Error while moving to \"%s\".\n\n"
719
				   		 "There is not enough space on the destination.");
720
				break;
721
			case TRANSFER_LINK:
722
				error_string = _("Error while creating link in \"%s\".\n\n"
723
				   		 "There is not enough space on the destination.");
724 725 726 727 728 729 730
				break;
			default:
				g_assert_not_reached ();
				break;
			}
		} else {
			switch (operation_kind) {
731 732
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
733
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
734
					error_string = _("Error while copying to \"%s\".\n\n"
735
					   		 "You do not have permissions to write to "
736
					   		 "this folder.");
737
				} else if (error_kind == ERROR_NOT_WRITABLE) {
738
					error_string = _("Error while copying to \"%s\".\n\n"
739
					   		 "The destination disk is read-only.");
740 741
				} 
				break;
742 743
			case TRANSFER_MOVE:
			case TRANSFER_MOVE_TO_TRASH:
744
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
745
					error_string = _("Error while moving items to \"%s\".\n\n"
746
					   		 "You do not have permissions to write to "
747
					   		 "this folder.");
748
				} else if (error_kind == ERROR_NOT_WRITABLE) {
749
					error_string = _("Error while moving items to \"%s\".\n\n"
750
					   		 "The destination disk is read-only.");
751 752 753
				} 

				break;
754
			case TRANSFER_LINK:
755
				if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
756
					error_string = _("Error while creating links in \"%s\".\n\n"
757
					   		 "You do not have permissions to write to "
758
					   		 "this folder.");
759
				} else if (error_kind == ERROR_NOT_WRITABLE) {
760
					error_string = _("Error while creating links in \"%s\".\n\n"
761
					   		 "The destination disk is read-only.");
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
				} 
				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
		 */
779 780
		g_message ("Hit unexpected error \"%s\" while doing a file operation.",
			   gnome_vfs_result_to_string (error));
781 782 783 784

		/* FIXMEs: we need to consider a single item
		 * move/copy and not offer to continue in that case
		 */
785 786
		if (source_name != NULL) {
			switch (operation_kind) {
787 788
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
789
				error_string = _("Error \"%s\" while copying \"%s\".\n\n"
790 791
						 "Would you like to continue?");
				break;
792
			case TRANSFER_MOVE:
793
				error_string = _("Error \"%s\" while moving \"%s\".\n\n"
794 795
						 "Would you like to continue?");
				break;
796
			case TRANSFER_LINK:
797
				error_string = _("Error \"%s\" while creating a link to \"%s\".\n\n"
798 799
						 "Would you like to continue?");
				break;
800 801 802
			case TRANSFER_DELETE:
			case TRANSFER_EMPTY_TRASH:
			case TRANSFER_MOVE_TO_TRASH:
803
				error_string = _("Error \"%s\" while deleting \"%s\".\n\n"
804 805 806
						 "Would you like to continue?");
				break;
			default:
807
				g_assert_not_reached ();
808 809 810
				break;
			}
	
811
			result = g_strdup_printf (error_string, 
812 813
						  gnome_vfs_result_to_string (error),
						  source_name);
814
		} else {
815
			switch (operation_kind) {
816 817
			case TRANSFER_COPY:
			case TRANSFER_DUPLICATE:
818
				error_string = _("Error \"%s\" while copying.\n\n"
819 820
						 "Would you like to continue?");
				break;
821
			case TRANSFER_MOVE:
822
				error_string = _("Error \"%s\" while moving.\n\n"
823 824
						 "Would you like to continue?");
				break;
825
			case TRANSFER_LINK:
826
				error_string = _("Error \"%s\" while linking.\n\n"
827 828
						 "Would you like to continue?");
				break;
829 830 831
			case TRANSFER_DELETE:
			case TRANSFER_EMPTY_TRASH:
			case TRANSFER_MOVE_TO_TRASH:
832
				error_string = _("Error \"%s\" while deleting.\n\n"
833 834 835
						 "Would you like to continue?");
				break;
			default:
836
				g_assert_not_reached ();
837 838 839
				break;
			}
	
840
			result = g_strdup_printf (error_string, 
841
						  gnome_vfs_result_to_string (error));
842
		}
843 844 845
	}
	return result;
}
846

847
static int
848
handle_transfer_vfs_error (const GnomeVFSXferProgressInfo *progress_info,
849
			   TransferInfo *transfer_info)
850
{
851
	/* Notice that the error mode in `transfer_info' is the one we have been
852 853 854
         * requested, but the transfer is always performed in mode
         * `GNOME_VFS_XFER_ERROR_MODE_QUERY'.
         */
855

856 857 858
	int error_dialog_button_pressed;
	int error_dialog_result;
	char *text;
859 860
	char *formatted_source_name;
	char *formatted_target_name;
861 862 863 864
	const char *dialog_title;
	NautilusFileOperationsErrorKind error_kind;
	NautilusFileOperationsErrorLocation error_location;
	
865
	switch (transfer_info->error_mode) {
866 867 868 869
	case GNOME_VFS_XFER_ERROR_MODE_QUERY:

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

870 871
		formatted_source_name = NULL;
		formatted_target_name = NULL;
872 873

		if (progress_info->source_name != NULL) {
874
			formatted_source_name = format_and_ellipsize_uri_for_dialog
875 876 877 878
				(progress_info->source_name);
		}

		if (progress_info->target_name != NULL) {
879
			formatted_target_name = format_and_ellipsize_uri_for_dialog
880 881 882 883 884
				(progress_info->target_name);
		}

		error_kind = ERROR_OTHER;
		error_location = ERROR_LOCATION_UNKNOWN;
885
		
886 887 888 889 890
		/* 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)
891 892
			&& (transfer_info->kind == TRANSFER_DELETE
				|| transfer_info->kind == TRANSFER_EMPTY_TRASH)) {
893 894 895
			error_location = ERROR_LOCATION_SOURCE_PARENT;
			error_kind = ERROR_READ_ONLY;
		} else if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
896 897
			&& (transfer_info->kind == TRANSFER_DELETE
				|| transfer_info->kind == TRANSFER_EMPTY_TRASH)) {
898 899 900 901
			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)
902 903
			&& (transfer_info->kind == TRANSFER_MOVE
				|| transfer_info->kind == TRANSFER_MOVE_TO_TRASH)
904 905 906
			&& progress_info->phase != GNOME_VFS_XFER_CHECKING_DESTINATION) {
			error_location = ERROR_LOCATION_SOURCE_PARENT;
			error_kind = ERROR_READ_ONLY;
907 908 909 910 911
		} 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;
912
		} else if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
913 914
			&& (transfer_info->kind == TRANSFER_MOVE
				|| transfer_info->kind == TRANSFER_MOVE_TO_TRASH)
915 916 917 918
			&& 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
919 920
			&& (transfer_info->kind == TRANSFER_COPY
				|| transfer_info->kind == TRANSFER_DUPLICATE)
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939
			&& (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;
		}

940 941 942 943
		text = build_error_string (formatted_source_name, formatted_target_name,
					   transfer_info->kind,
					   error_kind, error_location,
					   progress_info->vfs_status);
944

945 946 947
		switch (transfer_info->kind) {
		case TRANSFER_COPY:
		case TRANSFER_DUPLICATE:
948 949
			dialog_title = _("Error while copying.");
			break;
950
		case TRANSFER_MOVE:
951 952
			dialog_title = _("Error while moving.");
			break;
953
		case TRANSFER_LINK:
954 955
			dialog_title = _("Error while linking.");
			break;
956 957 958
		case TRANSFER_DELETE:
		case TRANSFER_EMPTY_TRASH:
		case TRANSFER_MOVE_TO_TRASH:
959 960
			dialog_title = _("Error while deleting.");
			break;
961
		default:
962 963 964
			dialog_title = NULL;
			break;
		}
965

966 967
		if (error_location == ERROR_LOCATION_TARGET) {
			/* We can't continue, just tell the user. */
Ramiro Estrugo's avatar
Ramiro Estrugo committed
968
			eel_run_simple_dialog (parent_for_error_dialog (transfer_info),
969 970 971 972 973 974 975 976 977 978 979 980
				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
981
			error_dialog_button_pressed = eel_run_simple_dialog
982
				(parent_for_error_dialog (transfer_info), TRUE, text, 
983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999
				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
1000
			error_dialog_button_pressed = eel_run_simple_dialog
1001
				(parent_for_error_dialog (transfer_info), TRUE, text, 
1002
				 dialog_title,
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
				 _("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);
1022 1023 1024
		g_free (formatted_source_name);
		g_free (formatted_target_name);

1025
		return error_dialog_result;
1026

Ettore Perazzoli's avatar
Ettore Perazzoli committed
1027 1028
	case GNOME_VFS_XFER_ERROR_MODE_ABORT:
	default:
1029
		if (transfer_info->progress_dialog != NULL) {
1030 1031
			nautilus_file_operations_progress_done
				(transfer_info->progress_dialog);
1032
		}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
1033 1034 1035 1036
		return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
	}
}

1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
/* 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;
}

Pavel Cisler's avatar
Pavel Cisler committed
1065
static int
1066
handle_transfer_overwrite (const GnomeVFSXferProgressInfo *progress_info,
1067
		           TransferInfo *transfer_info)
1068
{
Pavel Cisler's avatar
Pavel Cisler committed
1069
	int result;
1070
	char *text, *formatted_name;
1071