nautilus-file-operations.c 170 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.
Alexander Larsson's avatar
Alexander Larsson committed
7
   Copyright (C) 2007 Red Hat, Inc.
Ettore Perazzoli's avatar
Ettore Perazzoli committed
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

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

#include <config.h>
30
#include <string.h>
31
#include <stdio.h>
Alexander Larsson's avatar
Alexander Larsson committed
32
#include <stdarg.h>
33
#include <locale.h>
Alexander Larsson's avatar
Alexander Larsson committed
34
#include <math.h>
35
#include "nautilus-file-operations.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
36

Alexander Larsson's avatar
 
Alexander Larsson committed
37
#include "nautilus-debug-log.h"
38
#include "nautilus-file-operations-progress.h"
Alexander Larsson's avatar
Alexander Larsson committed
39
#include "nautilus-file-changes-queue.h"
40
#include "nautilus-lib-self-check-functions.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
41

Alexander Larsson's avatar
Alexander Larsson committed
42 43 44
#include "nautilus-progress-info.h"

#include <eel/eel-alert-dialog.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
45
#include <eel/eel-glib-extensions.h>
46
#include <eel/eel-pango-extensions.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
47 48
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-stock-dialogs.h>
Ramiro Estrugo's avatar
Ramiro Estrugo committed
49 50
#include <eel/eel-vfs-extensions.h>

51
#include <glib/gstdio.h>
Ettore Perazzoli's avatar
Ettore Perazzoli committed
52
#include <gnome.h>
53
#include <gdk/gdkdnd.h>
54
#include <gtk/gtklabel.h>
55
#include <gtk/gtkmessagedialog.h>
56
#include <gtk/gtkwidget.h>
57
#include <libgnomevfs/gnome-vfs-async-ops.h>
58
#include <libgnomevfs/gnome-vfs-find-directory.h>
59 60
#include <libgnomevfs/gnome-vfs-ops.h>
#include <libgnomevfs/gnome-vfs-result.h>
Pavel Cisler's avatar
Pavel Cisler committed
61
#include <libgnomevfs/gnome-vfs-uri.h>
62
#include <libgnomevfs/gnome-vfs-utils.h>
63 64
#include <libgnomevfs/gnome-vfs-volume.h>
#include <libgnomevfs/gnome-vfs-volume-monitor.h>
Alexander Larsson's avatar
Alexander Larsson committed
65 66 67
#include <gio/gfile.h>
#include <glib/gurifuncs.h>
#include <gio/gioscheduler.h>
68
#include "nautilus-file-changes-queue.h"
Alexander Larsson's avatar
Alexander Larsson committed
69 70 71
#include "nautilus-file-private.h"
#include "nautilus-desktop-icon-file.h"
#include "nautilus-desktop-link-monitor.h"
Ramiro Estrugo's avatar
Ramiro Estrugo committed
72 73 74
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
#include "nautilus-trash-monitor.h"
75
#include "nautilus-file-utilities.h"
Ettore Perazzoli's avatar
Ettore Perazzoli committed
76

Alexander Larsson's avatar
Alexander Larsson committed
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 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 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 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 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
static gboolean confirm_trash_auto_value;

/* TODO:
 *  Add cancellation
 *  Implement missing functions:
 *   duplicate, new file, new folder, empty trash, set_permissions recursive
 *  Make delete handle recursive deletes
 *  Use CommonJob in trash/delete code
 * TESTING!!!
 */

typedef struct {
	GIOJob *io_job;	
	GTimer *time;
	GtkWidget *parent_window;
	NautilusProgressInfo *progress;
	GCancellable *cancellable;
	gboolean aborted;
	GHashTable *skip_files;
	GHashTable *skip_readdir_error;
	gboolean skip_all_error;
	gboolean skip_all_conflict;
	gboolean merge_all;
	gboolean replace_all;
} CommonJob;

typedef struct {
	CommonJob common;
	gboolean is_move;
	GList *files;
	GFile *destination;
	GdkPoint *icon_positions;
	int n_icon_positions;
	int screen_num;
	GHashTable *debuting_files;
	NautilusCopyCallback  done_callback;
	gpointer done_callback_data;
} CopyMoveJob;

#define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 15
#define NSEC_PER_SEC 1000000000
#define NSEC_PER_MSEC 1000000

#define IS_IO_ERROR(__error, KIND) (((__error)->domain == G_IO_ERROR && (__error)->code == G_IO_ERROR_ ## KIND))

#define SKIP _("_Skip")
#define SKIP_ALL _("S_kip All")
#define RETRY _("_Retry")
#define REPLACE _("_Replace")
#define REPLACE_ALL _("Replace _All")
#define MERGE _("_Merge")
#define MERGE_ALL _("Merge _All")

static char *
format_time (int seconds)
{
	int minutes;
	int hours;
	char *res;

	if (seconds < 0) {
		/* Just to make sure... */
		seconds = 0;
	}
	
	if (seconds < 60) {
		return g_strdup_printf (ngettext ("%d second","%d seconds", (int) seconds), (int) seconds);
	}

	if (seconds < 60*60) {
		minutes = (seconds + 30) / 60;
		return g_strdup_printf (ngettext (_("%d minute"), _("%d minutes"), minutes), minutes);
	}

	hours = seconds / (60*60);
	
	if (seconds < 60*60*4) {
		char *h, *m;

		minutes = (seconds - hours * 60 * 60 + 30) / 60;
		
		h = g_strdup_printf (ngettext (_("%d hour"), _("%d hours"), hours), hours);
		m = g_strdup_printf (ngettext (_("%d minute"), _("%d minutes"), minutes), minutes);
		res = g_strconcat (h, ", ", m, NULL);
		g_free (h);
		g_free (m);
		return res;
	}
	
	return g_strdup_printf (_("about %d hours"), hours);
}

static char *
custom_full_name_to_string (char *format, va_list va)
{
	GFile *file;
	
	file = va_arg (va, GFile *);
	
	return g_file_get_parse_name (file);
}

static void
custom_full_name_skip (va_list *va)
{
	va_arg (*va, GFile *);
}

static char *
custom_basename_to_string (char *format, va_list va)
{
	GFile *file;
	GFileInfo *info;
	char *name, *basename;

	file = va_arg (va, GFile *);

	info = g_file_query_info (file,
				  G_FILE_ATTRIBUTE_STD_DISPLAY_NAME,
				  0,
				  g_cancellable_get_current (),
				  NULL);
	
	name = NULL;
	if (info) {
		name = g_strdup (g_file_info_get_display_name (info));
		g_object_unref (info);
	}
	
	if (name == NULL) {
		basename = g_file_get_basename (file);
		if (g_utf8_validate (basename, -1, NULL)) {
			name = basename;
		} else {
			name = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
			g_free (basename);
		}
	}
	
	return name;
}

static void
custom_basename_skip (va_list *va)
{
	va_arg (*va, GFile *);
}


static char *
custom_size_to_string (char *format, va_list va)
{
	goffset size;

	size = va_arg (va, goffset);
	return g_format_file_size_for_display (size);
}

static void
custom_size_skip (va_list *va)
{
	va_arg (*va, goffset);
}

static char *
custom_time_to_string (char *format, va_list va)
{
	int secs;

	secs = va_arg (va, int);
	return format_time (secs);
}

static void
custom_time_skip (va_list *va)
{
	va_arg (*va, int);
}

static EelPrintfHandler handlers[] = {
	{ 'F', custom_full_name_to_string, custom_full_name_skip },
	{ 'B', custom_basename_to_string, custom_basename_skip },
	{ 'S', custom_size_to_string, custom_size_skip },
	{ 'T', custom_time_to_string, custom_time_skip },
	{ 0 }
};


static char *
f (const char *format, ...) {
	va_list va;
	char *res;
	
	va_start (va, format);
	res = eel_strdup_vprintf_with_custom (handlers, format, va);
	va_end (va);

	return res;
}



#ifdef GIO_CONVERSION_DONE

281 282 283 284 285
typedef enum   TransferKind         TransferKind;
typedef struct TransferInfo         TransferInfo;
typedef struct IconPositionIterator IconPositionIterator;

enum TransferKind {
286 287 288 289 290 291 292
	TRANSFER_MOVE,
	TRANSFER_COPY,
	TRANSFER_DUPLICATE,
	TRANSFER_MOVE_TO_TRASH,
	TRANSFER_EMPTY_TRASH,
	TRANSFER_DELETE,
	TRANSFER_LINK
293
};
294

295
/* Copy engine callback state */
296
struct TransferInfo {
Ettore Perazzoli's avatar
Ettore Perazzoli committed
297
	GnomeVFSAsyncHandle *handle;
298
	NautilusFileOperationsProgress *progress_dialog;
Pavel Cisler's avatar
Pavel Cisler committed
299
	const char *operation_title;	/* "Copying files" */
300
	const char *action_label;	/* "Files copied:" */
Pavel Cisler's avatar
Pavel Cisler committed
301 302 303
	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
304 305
	GnomeVFSXferErrorMode error_mode;
	GnomeVFSXferOverwriteMode overwrite_mode;
Pavel Cisler's avatar
Pavel Cisler committed
306
	GtkWidget *parent_view;
307
	TransferKind kind;
308
	void (* done_callback) (GHashTable *debuting_uris, gpointer data);
309
	gpointer done_callback_data;
310 311
	GHashTable *debuting_uris;
	gboolean cancelled;	
312 313
	IconPositionIterator *iterator;
};
Ettore Perazzoli's avatar
Ettore Perazzoli committed
314

315 316 317 318 319 320 321 322
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
323
	eel_add_weak_pointer (&result->parent_view);
324 325 326 327 328 329 330
	
	return result;
}

static void
transfer_info_destroy (TransferInfo *transfer_info)
{
Darin Adler's avatar
Darin Adler committed
331
	eel_remove_weak_pointer (&transfer_info->parent_view);
332 333
	
	if (transfer_info->progress_dialog != NULL) {
334
		nautilus_file_operations_progress_done (transfer_info->progress_dialog);
335 336 337
	}
	
	if (transfer_info->debuting_uris != NULL) {
338
		g_hash_table_destroy (transfer_info->debuting_uris);
339 340 341 342 343
	}
	
	g_free (transfer_info);
}

Alexander Larsson's avatar
Alexander Larsson committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
#endif /* GIO_CONVERSION_DONE */

static void
setup_autos (void)
{
	static gboolean setup_autos = FALSE;
	if (!setup_autos) {
		setup_autos = TRUE;
		eel_preferences_add_auto_boolean (NAUTILUS_PREFERENCES_CONFIRM_TRASH,
						  &confirm_trash_auto_value);
	}
}

#ifdef GIO_CONVERSION_DONE

359 360 361 362
/* Struct used to control applying icon positions to 
 * top level items during a copy, drag, new folder creation and
 * link creation
 */
363
struct IconPositionIterator {
364 365 366 367
	GdkPoint *icon_positions;
	int last_icon_position_index;
	GList *uris;
	const GList *last_uri;
368
	int screen;
369 370
	gboolean is_source_iterator;
};
371 372

static IconPositionIterator *
373 374 375 376
icon_position_iterator_new (GArray *icon_positions,
			    const GList *uris,
			    int screen,
			    gboolean is_source_iterator)
377 378
{
	IconPositionIterator *result;
379
	guint index;
380

381
	g_assert (icon_positions->len == g_list_length ((GList *)uris));
382 383
	result = g_new (IconPositionIterator, 1);
	
384
	/* make our own copy of the icon locations */
385
	result->icon_positions = g_new (GdkPoint, icon_positions->len);
386 387 388
	for (index = 0; index < icon_positions->len; index++) {
		result->icon_positions[index] = g_array_index (icon_positions, GdkPoint, index);
	}
389 390
	result->last_icon_position_index = 0;

Ramiro Estrugo's avatar
Ramiro Estrugo committed
391
	result->uris = eel_g_str_list_copy ((GList *)uris);
392
	result->last_uri = result->uris;
393
	result->screen = screen;
394
	result->is_source_iterator = is_source_iterator;
395 396 397 398

	return result;
}

399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
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;
}

426 427 428 429 430 431 432 433
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
434
	eel_g_list_free_deep (position_iterator->uris);
435 436 437
	g_free (position_iterator);
}

438 439
static gboolean
icon_position_iterator_get_next (IconPositionIterator *position_iterator,
440 441
				 const char *next_source_uri,
				 const char *next_target_uri,
442 443
				 GdkPoint *point)
{
444 445
	const char *next_uri;

446 447 448
	if (position_iterator == NULL) {
		return FALSE;
	}
449 450 451 452 453 454 455

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

456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
	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;
}

489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
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
516
	uri = gnome_vfs_uri_append_string (parent_uri, new_uri_fragment);
517 518 519 520 521 522 523 524

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

525 526
static char *
ellipsize_string_for_dialog (PangoContext *context, const char *str)
527
{
528 529 530 531
	int maximum_width;
	char *result;
	PangoLayout *layout;
	PangoFontMetrics *metrics;
532

533
	layout = pango_layout_new (context);
534

535 536
	metrics = pango_context_get_metrics (
		context, pango_context_get_font_description (context), NULL);
537

538
	maximum_width = pango_font_metrics_get_approximate_char_width (metrics) * 25 / PANGO_SCALE;
539

540
	pango_font_metrics_unref (metrics);
541

542 543
	eel_pango_layout_set_text_ellipsized (
		layout, str, maximum_width, EEL_ELLIPSIZE_MIDDLE);
544

545
	result = g_strdup (pango_layout_get_text (layout));
546

547
	g_object_unref (layout);
548

549 550
	return result;
}
551

552
static char *
553
format_and_ellipsize_uri_for_dialog (GtkWidget *context, const char *uri)
554 555
{
	char *unescaped, *result;
556

Ramiro Estrugo's avatar
Ramiro Estrugo committed
557
	unescaped = eel_format_uri_for_display (uri);
558 559
	result = ellipsize_string_for_dialog (
		gtk_widget_get_pango_context (context), unescaped);
560
	g_free (unescaped);
561

562 563 564 565
	return result;
}

static char *
566
extract_and_ellipsize_file_name_for_dialog (GtkWidget *context, const char *uri)
567
{
568
	char *basename;
569
	char *unescaped, *result;
570
	
571 572
	basename = g_path_get_basename (uri);
	g_return_val_if_fail (basename != NULL, NULL);
573

574
	unescaped = gnome_vfs_unescape_string_for_display (basename);
575 576
	result = ellipsize_string_for_dialog (
		gtk_widget_get_pango_context (context), unescaped);
577
	g_free (unescaped);
578
	g_free (basename);
579

580 581 582
	return result;
}

583
static GtkWidget *
584
parent_for_error_dialog (TransferInfo *transfer_info)
585
{
586
	if (transfer_info->progress_dialog != NULL) {
587
		return GTK_WIDGET (transfer_info->progress_dialog);
588 589
	}

590
	return transfer_info->parent_view;
591 592
}

593 594 595 596 597 598 599 600
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)
601
{
602
	transfer_info->cancelled = TRUE;
603 604
}

Ettore Perazzoli's avatar
Ettore Perazzoli committed
605
static void
606
create_transfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
607
			TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
608
{
609
	g_return_if_fail (transfer_info->progress_dialog == NULL);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
610

611
	transfer_info->progress_dialog = nautilus_file_operations_progress_new 
612
		(transfer_info->operation_title, "", "", "", 0, 0, TRUE);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
613

614 615 616
	/* Treat clicking on the close box or use of the escape key
	 * the same as clicking cancel.
	 */
617
	g_signal_connect (transfer_info->progress_dialog,
618 619
			  "response",
			  G_CALLBACK (handle_response_callback),
620
			  transfer_info);
621
	g_signal_connect (transfer_info->progress_dialog,
622 623 624
			  "close",
			  G_CALLBACK (handle_close_callback),
			  transfer_info);
Ettore Perazzoli's avatar
Ettore Perazzoli committed
625

626
	/* Make the progress dialog show up over the window we are copying into */
627
	if (transfer_info->parent_view != NULL) {
628 629 630 631 632 633 634 635 636 637
		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));
		}
638
	}
Ettore Perazzoli's avatar
Ettore Perazzoli committed
639 640
}

Alexander Larsson's avatar
Alexander Larsson committed
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
/* TODO: This should really use the gio display name */
static const char *
get_vfs_method_display_name (char *method)
{
	if (g_ascii_strcasecmp (method, "computer") == 0 ) {
		return _("Computer");
	} else if (g_ascii_strcasecmp (method, "network") == 0 ) {
		return _("Network");
	} else if (g_ascii_strcasecmp (method, "fonts") == 0 ) {
		return _("Fonts");
	} else if (g_ascii_strcasecmp (method, "themes") == 0 ) {
		return _("Themes");
	} else if (g_ascii_strcasecmp (method, "burn") == 0 ) {
		return _("CD/DVD Creator");
	} else if (g_ascii_strcasecmp (method, "smb") == 0 ) {
		return _("Windows Network");
	} else if (g_ascii_strcasecmp (method, "dns-sd") == 0 ) {
		/* translators: this is the title of the "dns-sd:///" location */
		return _("Services in");
	}
	return NULL;
}

/* TODO: This should really use the gio display name */
static char *
get_uri_shortname_for_display (GnomeVFSURI *uri)
{
	char *utf8_name, *name, *tmp;
	char *text_uri, *local_file;
	gboolean validated;
	const char *method;

	
	validated = FALSE;
	name = gnome_vfs_uri_extract_short_name (uri);
	if (name == NULL) {
		name = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_PASSWORD);
	} else if (g_ascii_strcasecmp (uri->method_string, "file") == 0) {
		text_uri = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_PASSWORD);
		local_file = g_filename_from_uri (text_uri, NULL, NULL);
		g_free (name);
		if (local_file == NULL) { /* Happens for e.g. file:///# */
			local_file = g_strdup ("/");
		}
		name = g_filename_display_basename (local_file);
		g_free (local_file);
		g_free (text_uri);
		validated = TRUE;
	} else if (!gnome_vfs_uri_has_parent (uri)) {
		/* Special-case the display name for roots that are not local files */
		method = get_vfs_method_display_name (uri->method_string);
		if (method == NULL) {
			method = uri->method_string;
		}
		
		if (name == NULL ||
		    strcmp (name, GNOME_VFS_URI_PATH_STR) == 0) {
			g_free (name);
			name = g_strdup (method);
		} else {
			tmp = name;
			name = g_strdup_printf ("%s: %s", method, name);
			g_free (tmp);
		}
	}

	if (!validated && !g_utf8_validate (name, -1, NULL)) {
		utf8_name = eel_make_valid_utf8 (name);
		g_free (name);
		name = utf8_name;
	}

	return name;
}

Pavel Cisler's avatar
Pavel Cisler committed
716
static void
717
progress_dialog_set_to_from_item_text (NautilusFileOperationsProgress *dialog,
718 719 720
				       const char *progress_verb,
				       const char *from_uri, const char *to_uri, 
				       gulong index, gulong size)
Pavel Cisler's avatar
Pavel Cisler committed
721 722 723
{
	char *item;
	char *from_path;
724
	char *from_text;
Pavel Cisler's avatar
Pavel Cisler committed
725
	char *to_path;
726
	char *to_text;
Pavel Cisler's avatar
Pavel Cisler committed
727
	char *progress_label_text;
728
	const char *hostname;
Pavel Cisler's avatar
Pavel Cisler committed
729 730 731
	const char *from_prefix;
	const char *to_prefix;
	GnomeVFSURI *uri;
732
	int length;
Pavel Cisler's avatar
Pavel Cisler committed
733 734

	item = NULL;
735 736
	from_text = NULL;
	to_text = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
737 738 739 740 741 742
	from_prefix = "";
	to_prefix = "";
	progress_label_text = NULL;

	if (from_uri != NULL) {
		uri = gnome_vfs_uri_new (from_uri);
Alexander Larsson's avatar
Alexander Larsson committed
743
		item = get_uri_shortname_for_display (uri);
Pavel Cisler's avatar
Pavel Cisler committed
744
		from_path = gnome_vfs_uri_extract_dirname (uri);
745
		hostname = NULL;
746 747 748 749 750 751

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

753
		if (strcmp (uri->method_string, "file") != 0) {
754 755 756
			hostname = gnome_vfs_uri_get_host_name (uri);
		}
		if (hostname) {
757
			from_text = g_strdup_printf (_("%s on %s"),
758
				from_path, hostname);
759
			g_free (from_path);
760 761
		} else {
			from_text = from_path;
762
		}
763
		
Pavel Cisler's avatar
Pavel Cisler committed
764 765
		gnome_vfs_uri_unref (uri);
		g_assert (progress_verb);
766
		progress_label_text = g_strdup_printf ("%s", progress_verb);
767
		/* "From" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
768 769 770 771
		from_prefix = _("From:");
	}

	if (to_uri != NULL) {
772
		uri = gnome_vfs_uri_new (to_uri);
Pavel Cisler's avatar
Pavel Cisler committed
773
		to_path = gnome_vfs_uri_extract_dirname (uri);
774
		hostname = NULL;
775 776 777 778 779 780 781

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

782
		if (strcmp (uri->method_string, "file") != 0) {
783 784 785
			hostname = gnome_vfs_uri_get_host_name (uri);
		}
		if (hostname) {
786
			to_text = g_strdup_printf (_("%s on %s"),
787
				to_path, hostname);
788
			g_free (to_path);
789 790
		} else {
			to_text = to_path;
791 792
		}

Pavel Cisler's avatar
Pavel Cisler committed
793
		gnome_vfs_uri_unref (uri);
794
		/* "To" dialog label, source path gets placed next to it in the dialog */
Pavel Cisler's avatar
Pavel Cisler committed
795 796 797
		to_prefix = _("To:");
	}

798 799 800 801
	nautilus_file_operations_progress_new_file
		(dialog,
		 progress_label_text != NULL ? progress_label_text : "",
		 item != NULL ? item : "",
802 803
		 from_text != NULL ? from_text : "",
		 to_text != NULL ? to_text : "",
804
		 from_prefix, to_prefix, index, size);
Pavel Cisler's avatar
Pavel Cisler committed
805 806 807

	g_free (progress_label_text);
	g_free (item);
808 809
	g_free (from_text);
	g_free (to_text);
Pavel Cisler's avatar
Pavel Cisler committed
810 811
}

Pavel Cisler's avatar
Pavel Cisler committed
812
static int
813
handle_transfer_ok (const GnomeVFSXferProgressInfo *progress_info,
814
		    TransferInfo *transfer_info)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
815
{
816 817
	if (transfer_info->cancelled
		&& progress_info->phase != GNOME_VFS_XFER_PHASE_COMPLETED) {
818
		/* If cancelled, delete any partially copied files that are laying
819
		 * around and return. Don't delete the source though..
820
		 */
821
		if (progress_info->target_name != NULL
822 823
		    && progress_info->source_name != NULL
		    && strcmp (progress_info->source_name, progress_info->target_name) != 0
824
		    && progress_info->bytes_total != progress_info->bytes_copied) {
825
			GList *delete_me;
Alexander Larsson's avatar
Alexander Larsson committed
826
			GtkWidget *toplevel;
827

Alexander Larsson's avatar
Alexander Larsson committed
828 829 830 831
			delete_me = g_list_prepend (NULL, g_file_new_for_uri (progress_info->target_name));
			toplevel = gtk_widget_get_toplevel (transfer_info->parent_view);
			nautilus_file_operations_delete (delete_me, GTK_WINDOW (toplevel), NULL, NULL);
			eel_g_object_list_free (delete_me);
832 833
		}

834 835 836
		return 0;
	}
	
Ettore Perazzoli's avatar
Ettore Perazzoli committed
837
	switch (progress_info->phase) {
Pavel Cisler's avatar
Pavel Cisler committed
838
	case GNOME_VFS_XFER_PHASE_INITIAL:
839
		create_transfer_dialog (progress_info, transfer_info);
840
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
841 842

	case GNOME_VFS_XFER_PHASE_COLLECTING:
843
		if (transfer_info->progress_dialog != NULL) {
844
			nautilus_file_operations_progress_set_operation_string
845 846
				(transfer_info->progress_dialog,
				 transfer_info->preparation_name);
847
		}
848
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
849 850

	case GNOME_VFS_XFER_PHASE_READYTOGO:
851
		if (transfer_info->progress_dialog != NULL) {
852 853 854
			nautilus_file_operations_progress_set_operation_string
				(transfer_info->progress_dialog,
				 transfer_info->action_label);
855
			nautilus_file_operations_progress_set_total
856 857 858
				(transfer_info->progress_dialog,
				 progress_info->files_total,
				 progress_info->bytes_total);
859
		}
860
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
861
				 
862
	case GNOME_VFS_XFER_PHASE_DELETESOURCE:
863
		nautilus_file_changes_consume_changes (FALSE);
864
		if (transfer_info->progress_dialog != NULL) {
865 866 867 868 869 870 871
			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
872

873
			nautilus_file_operations_progress_update_sizes
874
				(transfer_info->progress_dialog,
875
				 MIN (progress_info->bytes_copied, 
876
				      progress_info->bytes_total),
877
				 MIN (progress_info->total_bytes_copied,
878
				      progress_info->bytes_total));
879
		}
880
		return 1;
881 882

	case GNOME_VFS_XFER_PHASE_MOVING:
Pavel Cisler's avatar
Pavel Cisler committed
883 884
	case GNOME_VFS_XFER_PHASE_OPENSOURCE:
	case GNOME_VFS_XFER_PHASE_OPENTARGET:
885 886
		/* fall through */
	case GNOME_VFS_XFER_PHASE_COPYING:
887
		if (transfer_info->progress_dialog != NULL) {
888 889 890 891 892 893 894 895 896 897 898
			progress_dialog_set_to_from_item_text (transfer_info->progress_dialog,
							       transfer_info->progress_verb,
							       progress_info->source_name,
							       progress_info->target_name,
							       progress_info->file_index,
							       progress_info->file_size);
			nautilus_file_operations_progress_update_sizes (transfer_info->progress_dialog,
									MIN (progress_info->bytes_copied, 
									     progress_info->bytes_total),
									MIN (progress_info->total_bytes_copied,
									     progress_info->bytes_total));
Ettore Perazzoli's avatar
Ettore Perazzoli committed
899
		}
900
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
901

902
	case GNOME_VFS_XFER_PHASE_CLEANUP:
903
		if (transfer_info->progress_dialog != NULL) {
904 905
			nautilus_file_operations_progress_clear
				(transfer_info->progress_dialog);
906
			nautilus_file_operations_progress_set_operation_string
907 908
				(transfer_info->progress_dialog,
				 transfer_info->cleanup_name);
909
		}
910
		return 1;
911

Ettore Perazzoli's avatar
Ettore Perazzoli committed
912
	case GNOME_VFS_XFER_PHASE_COMPLETED:
913
		nautilus_file_changes_consume_changes (TRUE);
914
		if (transfer_info->done_callback != NULL) {
915 916 917 918
			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;
919
		}
920 921

		transfer_info_destroy (transfer_info);
922
		return 1;
Pavel Cisler's avatar
Pavel Cisler committed
923

Ettore Perazzoli's avatar
Ettore Perazzoli committed
924
	default:
925
		return 1;
Ettore Perazzoli's avatar
Ettore Perazzoli committed
926 927 928
	}
}

929 930 931 932 933 934
typedef enum {
	ERROR_READ_ONLY,
	ERROR_NOT_READABLE,
	ERROR_NOT_WRITABLE,
	ERROR_NOT_ENOUGH_PERMISSIONS,
	ERROR_NO_SPACE,
935
	ERROR_SOURCE_IN_TARGET,
936 937
	ERROR_OTHER
} NautilusFileOperationsErrorKind;
938

939 940 941 942 943 944 945 946 947
typedef enum {
	ERROR_LOCATION_UNKNOWN,
	ERROR_LOCATION_SOURCE,
	ERROR_LOCATION_SOURCE_PARENT,
	ERROR_LOCATION_SOURCE_OR_PARENT,
	ERROR_LOCATION_TARGET
} NautilusFileOperationsErrorLocation;


948
static void
949
build_error_string (const char *source_name, const char *target_name,
950 951 952
		    TransferKind operation_kind,
		    NautilusFileOperationsErrorKind error_kind,
		    NautilusFileOperationsErrorLocation error_location,
953 954
		    GnomeVFSResult error,
		    char **error_string, char **detail_string)
Ettore Perazzoli's avatar
Ettore Perazzoli committed
955
{
956 957 958 959 960 961
	/* 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
962

963 964 965 966 967
	char *error_format;
	char *detail_format;
	
	error_format = NULL;
	detail_format = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
968

969 970
	*error_string = NULL;
	*detail_string = NULL;
Pavel Cisler's avatar
Pavel Cisler committed
971

972
	if (error_location == ERROR_LOCATION_SOURCE_PARENT) {
973

974
		switch (operation_kind) {
975 976
		case TRANSFER_MOVE:
		case TRANSFER_MOVE_TO_TRASH:
977
			if (error_kind == ERROR_READ_ONLY) {
978 979 980
				*error_string = g_strdup (_("Error while moving."));
				detail_format = _("\"%s\" cannot be moved because it is on "
						  "a read-only disk.");
981 982
			}
			break;
983

984 985
		case TRANSFER_DELETE:
		case TRANSFER_EMPTY_TRASH:
986 987 988
			switch (error_kind) {
			case ERROR_NOT_ENOUGH_PERMISSIONS:
			case ERROR_NOT_WRITABLE:
989 990 991
				*error_string = g_strdup (_("Error while deleting."));
				detail_format = _("\"%s\" cannot be deleted because you do not have "
						  "permissions to modify its parent folder.");
992 993 994
				break;
			
			case ERROR_READ_ONLY:
995 996 997
				*error_string = g_strdup (_("Error while deleting."));
				detail_format = _("\"%s\" cannot be deleted because it is on "
						  "a read-only disk.");
998 999 1000 1001
				break;

			default:
				break;
1002 1003 1004 1005 1006 1007 1008 1009
			}
			break;

		default:
			g_assert_not_reached ();
			break;
		}
		
1010 1011
		if (detail_format != NULL && source_name != NULL) {
			*detail_string = g_strdup_printf (detail_format, source_name);
1012
		}
1013

1014
	} else if (error_location == ERROR_LOCATION_SOURCE_OR_PARENT) {
1015

1016 1017
		g_assert (source_name != NULL);

1018 1019 1020