nautilus/libnautilus-private/nautilus-file-operations.c
Pavel Cisler 8caa91c163 reviewed by: Ramiro Estrugo <ramiro@eazel.com>
2001-01-19  Pavel Cisler  <pavel@eazel.com>

	reviewed by: Ramiro Estrugo <ramiro@eazel.com>

	Fixed 4348: trying to trash a file already deleted outside Nautilus crashes.
	* libnautilus-extensions/nautilus-file-operations.c:
	(nautilus_file_operations_copy_move):
	The problem was that the "find trash near" operation was failing because
	the trahsed file was non-existent.
	Added code that deals with a NULL target_directory_uri handle.
2001-01-19 11:36:52 +00:00

2288 lines
70 KiB
C

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* nautilus-file-operations.c - Nautilus file operations.
Copyright (C) 1999, 2000 Free Software Foundation
Copyright (C) 2000, 2001 Eazel, Inc.
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.
Authors:
Ettore Perazzoli <ettore@gnu.org>
Pavel Cisler <pavel@eazel.com>
*/
#include <config.h>
#include "nautilus-file-operations.h"
#include "nautilus-file-operations-progress.h"
#include "nautilus-lib-self-check-functions.h"
#include <gnome.h>
#include <gtk/gtklabel.h>
#include <libgnomevfs/gnome-vfs-async-ops.h>
#include <libgnomevfs/gnome-vfs-find-directory.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <libgnomevfs/gnome-vfs-result.h>
#include <libgnomevfs/gnome-vfs-uri.h>
#include <libgnomevfs/gnome-vfs-utils.h>
#include <libnautilus-extensions/nautilus-file-changes-queue.h>
#include <libnautilus-extensions/nautilus-file-utilities.h>
#include <libnautilus-extensions/nautilus-gdk-font-extensions.h>
#include <libnautilus-extensions/nautilus-glib-extensions.h>
#include <libnautilus-extensions/nautilus-global-preferences.h>
#include <libnautilus-extensions/nautilus-link.h>
#include <libnautilus-extensions/nautilus-stock-dialogs.h>
#include <libnautilus-extensions/nautilus-trash-monitor.h>
typedef enum {
TRANSFER_MOVE,
TRANSFER_COPY,
TRANSFER_DUPLICATE,
TRANSFER_MOVE_TO_TRASH,
TRANSFER_EMPTY_TRASH,
TRANSFER_DELETE,
TRANSFER_LINK
} TransferKind;
/* Copy engine callback state */
typedef struct {
GnomeVFSAsyncHandle *handle;
GtkWidget *progress_dialog;
const char *operation_title; /* "Copying files" */
const char *action_label; /* "Files copied:" */
const char *progress_verb; /* "Copying" */
const char *preparation_name; /* "Preparing To Copy..." */
const char *cleanup_name; /* "Finishing Move..." */
GnomeVFSXferErrorMode error_mode;
GnomeVFSXferOverwriteMode overwrite_mode;
GtkWidget *parent_view;
TransferKind kind;
gboolean show_progress_dialog;
void (*done_callback) (GHashTable *debuting_uris, gpointer data);
gpointer done_callback_data;
GHashTable *debuting_uris;
gboolean cancelled;
} TransferInfo;
/* 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 *
icon_position_iterator_new (GArray *icon_positions, const GList *uris)
{
IconPositionIterator *result;
guint index;
g_assert (icon_positions->len == g_list_length ((GList *)uris));
result = g_new (IconPositionIterator, 1);
result->icon_positions = g_new (GdkPoint, icon_positions->len);
/* make our own copy of the icon locations */
for (index = 0; index < icon_positions->len; index++) {
result->icon_positions[index] = g_array_index (icon_positions, GdkPoint, index);
}
result->last_icon_position_index = 0;
result->uris = nautilus_g_str_list_copy ((GList *)uris);
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);
nautilus_g_list_free_deep (position_iterator->uris);
g_free (position_iterator);
}
/* Hack to get the GdkFont used by a GtkLabel in an error dialog.
* We need to do this because the string truncation needs to be
* done before a dialog is instantiated.
*
* This is probably not super fast but it is not a problem in the
* context we are using it, truncating a string while displaying an
* error dialog.
*/
static GdkFont *
get_label_font (void)
{
GtkWidget *label;
GtkStyle *style;
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?
*/
label = gtk_label_new ("");
style = gtk_widget_get_style (label);
font = style->font;
gdk_font_ref (font);
gtk_object_sink (GTK_OBJECT (label));
return font;
}
static char *
ellipsize_string_for_dialog (const char *str)
{
GdkFont *font;
int maximum_width;
char *result;
/* get a nice length to ellipsize to, based on the font */
font = get_label_font ();
maximum_width = gdk_string_width (font, "MMMMMMMMMMMMMMMMMMMMMM");
/* FIXME: John Sullivan says we should ellipsize both URIs and
* file names in the middle, not the start.
*/
result = nautilus_string_ellipsize_start (str, font, maximum_width);
gdk_font_unref (font);
return result;
}
static char *
format_and_ellipsize_uri_for_dialog (const char *uri)
{
char *unescaped, *result;
unescaped = nautilus_format_uri_for_display (uri);
result = ellipsize_string_for_dialog (unescaped);
g_free (unescaped);
return result;
}
static char *
extract_and_ellipsize_file_name_for_dialog (const char *uri)
{
const char *last_part;
char *unescaped, *result;
last_part = g_basename (uri);
g_return_val_if_fail (last_part != NULL, NULL);
unescaped = gnome_vfs_unescape_string_for_display (last_part);
result = ellipsize_string_for_dialog (unescaped);
g_free (unescaped);
return result;
}
static GtkWidget *
parent_for_error_dialog (TransferInfo *transfer_info)
{
if (transfer_info->progress_dialog != NULL) {
return transfer_info->progress_dialog;
}
return transfer_info->parent_view;
}
static void
transfer_dialog_clicked_callback (NautilusFileOperationsProgress *dialog,
int button_number,
gpointer data)
{
TransferInfo *info;
info = (TransferInfo *) data;
info->cancelled = TRUE;
}
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);
}
static void
create_transfer_dialog (const GnomeVFSXferProgressInfo *progress_info,
TransferInfo *transfer_info)
{
if (!transfer_info->show_progress_dialog) {
return;
}
g_return_if_fail (transfer_info->progress_dialog == NULL);
transfer_info->progress_dialog = nautilus_file_operations_progress_new
(transfer_info->operation_title, "", "", "", 1, 1);
gtk_signal_connect (GTK_OBJECT (transfer_info->progress_dialog),
"clicked",
GTK_SIGNAL_FUNC (transfer_dialog_clicked_callback),
transfer_info);
gtk_widget_show (transfer_info->progress_dialog);
/* Make the progress dialog show up over the window we are copying into */
if (transfer_info->parent_view != NULL) {
center_dialog_over_window (GTK_WINDOW (transfer_info->progress_dialog),
GTK_WINDOW (gtk_widget_get_toplevel (transfer_info->parent_view)));
}
}
static void
progress_dialog_set_to_from_item_text (NautilusFileOperationsProgress *dialog,
const char *progress_verb,
const char *from_uri, const char *to_uri,
gulong index, gulong size)
{
char *item;
char *from_path;
char *to_path;
char *progress_label_text;
const char *from_prefix;
const char *to_prefix;
GnomeVFSURI *uri;
int length;
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);
/* remove the last '/' */
length = strlen (from_path);
if (from_path [length - 1] == '/') {
from_path [length - 1] = '\0';
}
gnome_vfs_uri_unref (uri);
g_assert (progress_verb);
progress_label_text = g_strdup_printf ("%s:", progress_verb);
/* "From" dialog label, source path gets placed next to it in the dialog */
from_prefix = _("From:");
}
if (to_uri != NULL) {
uri = gnome_vfs_uri_new (to_uri);
to_path = gnome_vfs_uri_extract_dirname (uri);
/* remove the last '/' */
length = strlen (to_path);
if (to_path [length - 1] == '/') {
to_path [length - 1] = '\0';
}
gnome_vfs_uri_unref (uri);
/* "To" dialog label, source path gets placed next to it in the dialog */
to_prefix = _("To:");
}
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);
g_free (progress_label_text);
g_free (item);
g_free (from_path);
g_free (to_path);
}
static int
handle_transfer_ok (const GnomeVFSXferProgressInfo *progress_info,
TransferInfo *transfer_info)
{
if (transfer_info->cancelled
&& progress_info->phase != GNOME_VFS_XFER_PHASE_COMPLETED) {
/* If cancelled, return right away, unless we are calling
* to shut down the progress dialog.
*/
return 0;
}
switch (progress_info->phase) {
case GNOME_VFS_XFER_PHASE_INITIAL:
create_transfer_dialog (progress_info, transfer_info);
return 1;
case GNOME_VFS_XFER_PHASE_COLLECTING:
if (transfer_info->progress_dialog != NULL) {
nautilus_file_operations_progress_set_operation_string
(NAUTILUS_FILE_OPERATIONS_PROGRESS
(transfer_info->progress_dialog),
transfer_info->preparation_name);
}
return 1;
case GNOME_VFS_XFER_PHASE_READYTOGO:
if (transfer_info->progress_dialog != NULL) {
nautilus_file_operations_progress_set_operation_string (
NAUTILUS_FILE_OPERATIONS_PROGRESS (transfer_info->progress_dialog),
transfer_info->action_label);
nautilus_file_operations_progress_set_total
(NAUTILUS_FILE_OPERATIONS_PROGRESS
(transfer_info->progress_dialog),
progress_info->files_total,
progress_info->bytes_total);
}
return 1;
case GNOME_VFS_XFER_PHASE_DELETESOURCE:
nautilus_file_changes_consume_changes (FALSE);
if (transfer_info->progress_dialog != NULL) {
progress_dialog_set_to_from_item_text (
NAUTILUS_FILE_OPERATIONS_PROGRESS (transfer_info->progress_dialog),
transfer_info->progress_verb,
progress_info->source_name,
NULL,
progress_info->file_index,
progress_info->file_size);
nautilus_file_operations_progress_update_sizes
(NAUTILUS_FILE_OPERATIONS_PROGRESS
(transfer_info->progress_dialog),
MIN (progress_info->bytes_copied,
progress_info->bytes_total),
MIN (progress_info->total_bytes_copied,
progress_info->bytes_total));
}
return 1;
case GNOME_VFS_XFER_PHASE_MOVING:
case GNOME_VFS_XFER_PHASE_OPENSOURCE:
case GNOME_VFS_XFER_PHASE_OPENTARGET:
/* fall through */
case GNOME_VFS_XFER_PHASE_COPYING:
if (transfer_info->progress_dialog != NULL) {
if (progress_info->bytes_copied == 0) {
progress_dialog_set_to_from_item_text (
NAUTILUS_FILE_OPERATIONS_PROGRESS (transfer_info->progress_dialog),
transfer_info->progress_verb,
progress_info->source_name,
progress_info->target_name,
progress_info->file_index,
progress_info->file_size);
} else {
nautilus_file_operations_progress_update_sizes
(NAUTILUS_FILE_OPERATIONS_PROGRESS
(transfer_info->progress_dialog),
MIN (progress_info->bytes_copied,
progress_info->bytes_total),
MIN (progress_info->total_bytes_copied,
progress_info->bytes_total));
}
}
return 1;
case GNOME_VFS_XFER_PHASE_CLEANUP:
if (transfer_info->progress_dialog != NULL) {
nautilus_file_operations_progress_clear(
NAUTILUS_FILE_OPERATIONS_PROGRESS
(transfer_info->progress_dialog));
nautilus_file_operations_progress_set_operation_string
(NAUTILUS_FILE_OPERATIONS_PROGRESS
(transfer_info->progress_dialog),
transfer_info->cleanup_name);
}
return 1;
case GNOME_VFS_XFER_PHASE_COMPLETED:
nautilus_file_changes_consume_changes (TRUE);
if (transfer_info->progress_dialog != NULL) {
gtk_widget_destroy (transfer_info->progress_dialog);
}
if (transfer_info->done_callback != NULL) {
transfer_info->done_callback (transfer_info->debuting_uris, transfer_info->done_callback_data);
/* done_callback now owns (will free) debuting_uris
*/
} else if (transfer_info->debuting_uris != NULL) {
nautilus_g_hash_table_destroy_deep (transfer_info->debuting_uris);
}
g_free (transfer_info);
return 1;
default:
return 1;
}
}
typedef enum {
ERROR_READ_ONLY,
ERROR_NOT_READABLE,
ERROR_NOT_WRITABLE,
ERROR_NOT_ENOUGH_PERMISSIONS,
ERROR_NO_SPACE,
ERROR_OTHER
} NautilusFileOperationsErrorKind;
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,
TransferKind operation_kind,
NautilusFileOperationsErrorKind error_kind,
NautilusFileOperationsErrorLocation error_location,
GnomeVFSResult error)
{
/* 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.
*/
const char *error_string;
char *result;
error_string = NULL;
result = NULL;
if (error_location == ERROR_LOCATION_SOURCE_PARENT) {
switch (operation_kind) {
case TRANSFER_MOVE:
case TRANSFER_MOVE_TO_TRASH:
if (error_kind == ERROR_READ_ONLY) {
error_string = _("Error while moving.\n\n"
"\"%s\" cannot be moved because it is on "
"a read-only disk.");
}
break;
case TRANSFER_DELETE:
case TRANSFER_EMPTY_TRASH:
switch (error_kind) {
case ERROR_NOT_ENOUGH_PERMISSIONS:
case ERROR_NOT_WRITABLE:
error_string = _("Error while deleting.\n\n"
"\"%s\" cannot be deleted because you do not have "
"permissions to modify its parent folder.");
break;
case ERROR_READ_ONLY:
error_string = _("Error while deleting.\n\n"
"\"%s\" cannot be deleted because it is on "
"a read-only disk.");
break;
default:
break;
}
break;
default:
g_assert_not_reached ();
break;
}
if (error_string != NULL) {
g_assert (source_name != NULL);
result = g_strdup_printf (error_string, source_name);
}
} else if (error_location == ERROR_LOCATION_SOURCE_OR_PARENT) {
g_assert (source_name != NULL);
/* 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.
*/
switch (operation_kind) {
case TRANSFER_MOVE:
if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
error_string = _("Error while moving.\n\n"
"\"%s\" cannot be moved because you do not have "
"permissions to change it or its parent folder.");
}
break;
case TRANSFER_MOVE_TO_TRASH:
if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
error_string = _("Error while moving.\n\n"
"\"%s\" cannot be moved to the trash because you do not have "
"permissions to change it or its parent folder.");
}
break;
default:
g_assert_not_reached ();
break;
}
if (error_string != NULL) {
g_assert (source_name != NULL);
result = g_strdup_printf (error_string, source_name);
}
} else if (error_location == ERROR_LOCATION_SOURCE) {
g_assert (source_name != NULL);
switch (operation_kind) {
case TRANSFER_COPY:
case TRANSFER_DUPLICATE:
if (error_kind == ERROR_NOT_READABLE) {
error_string = _("Error while copying.\n\n"
"\"%s\" cannot be copied because you do not have "
"permissions to read it.");
}
break;
default:
g_assert_not_reached ();
break;
}
if (error_string != NULL) {
g_assert (source_name != NULL);
result = g_strdup_printf (error_string, source_name);
}
} else if (error_location == ERROR_LOCATION_TARGET) {
if (error_kind == ERROR_NO_SPACE) {
switch (operation_kind) {
case TRANSFER_COPY:
case TRANSFER_DUPLICATE:
error_string = _("Error while copying to \"%s\".\n\n"
"There is not enough space on the destination.");
break;
case TRANSFER_MOVE_TO_TRASH:
case TRANSFER_MOVE:
error_string = _("Error while moving to \"%s\".\n\n"
"There is not enough space on the destination.");
break;
case TRANSFER_LINK:
error_string = _("Error while creating link in \"%s\".\n\n"
"There is not enough space on the destination.");
break;
default:
g_assert_not_reached ();
break;
}
} else {
switch (operation_kind) {
case TRANSFER_COPY:
case TRANSFER_DUPLICATE:
if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
error_string = _("Error while copying to \"%s\".\n\n"
"You do not have permissions to write to "
"this folder.");
} else if (error_kind == ERROR_NOT_WRITABLE) {
error_string = _("Error while copying to \"%s\".\n\n"
"The destination disk is read-only.");
}
break;
case TRANSFER_MOVE:
case TRANSFER_MOVE_TO_TRASH:
if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
error_string = _("Error while moving items to \"%s\".\n\n"
"You do not have permissions to write to "
"this folder.");
} else if (error_kind == ERROR_NOT_WRITABLE) {
error_string = _("Error while moving items to \"%s\".\n\n"
"The destination disk is read-only.");
}
break;
case TRANSFER_LINK:
if (error_kind == ERROR_NOT_ENOUGH_PERMISSIONS) {
error_string = _("Error while creating links in \"%s\".\n\n"
"You do not have permissions to write to "
"this folder.");
} else if (error_kind == ERROR_NOT_WRITABLE) {
error_string = _("Error while creating links in \"%s\".\n\n"
"The destination disk is read-only.");
}
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
*/
g_message ("Please tell pavel@eazel.com that you hit case %s while doing "
"a file operation.", gnome_vfs_result_to_string (error));
/* FIXMEs: we need to consider a single item
* move/copy and not offer to continue in that case
*/
if (source_name != NULL) {
switch (operation_kind) {
case TRANSFER_COPY:
case TRANSFER_DUPLICATE:
error_string = _("Error \"%s\" while copying \"%s\".\n\n"
"Would you like to continue?");
break;
case TRANSFER_MOVE:
error_string = _("Error \"%s\" while moving \"%s\".\n\n"
"Would you like to continue?");
break;
case TRANSFER_LINK:
error_string = _("Error \"%s\" while creating a link to \"%s\".\n\n"
"Would you like to continue?");
break;
case TRANSFER_DELETE:
case TRANSFER_EMPTY_TRASH:
case TRANSFER_MOVE_TO_TRASH:
error_string = _("Error \"%s\" while deleting \"%s\".\n\n"
"Would you like to continue?");
break;
default:
g_assert_not_reached ();
break;
}
result = g_strdup_printf (error_string,
gnome_vfs_result_to_string (error),
source_name);
} else {
switch (operation_kind) {
case TRANSFER_COPY:
case TRANSFER_DUPLICATE:
error_string = _("Error \"%s\" while copying.\n\n"
"Would you like to continue?");
break;
case TRANSFER_MOVE:
error_string = _("Error \"%s\" while moving.\n\n"
"Would you like to continue?");
break;
case TRANSFER_LINK:
error_string = _("Error \"%s\" while linking.\n\n"
"Would you like to continue?");
break;
case TRANSFER_DELETE:
case TRANSFER_EMPTY_TRASH:
case TRANSFER_MOVE_TO_TRASH:
error_string = _("Error \"%s\" while deleting.\n\n"
"Would you like to continue?");
break;
default:
g_assert_not_reached ();
break;
}
result = g_strdup_printf (error_string,
gnome_vfs_result_to_string (error));
}
}
return result;
}
static int
handle_transfer_vfs_error (const GnomeVFSXferProgressInfo *progress_info,
TransferInfo *transfer_info)
{
/* Notice that the error mode in `transfer_info' is the one we have been
* requested, but the transfer is always performed in mode
* `GNOME_VFS_XFER_ERROR_MODE_QUERY'.
*/
int error_dialog_button_pressed;
int error_dialog_result;
char *text;
char *formatted_source_name;
char *formatted_target_name;
const char *dialog_title;
NautilusFileOperationsErrorKind error_kind;
NautilusFileOperationsErrorLocation error_location;
switch (transfer_info->error_mode) {
case GNOME_VFS_XFER_ERROR_MODE_QUERY:
/* transfer error, prompt the user to continue or stop */
formatted_source_name = NULL;
formatted_target_name = NULL;
if (progress_info->source_name != NULL) {
formatted_source_name = format_and_ellipsize_uri_for_dialog
(progress_info->source_name);
}
if (progress_info->target_name != NULL) {
formatted_target_name = format_and_ellipsize_uri_for_dialog
(progress_info->target_name);
}
error_kind = ERROR_OTHER;
error_location = ERROR_LOCATION_UNKNOWN;
/* 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)
&& (transfer_info->kind == TRANSFER_DELETE
|| transfer_info->kind == TRANSFER_EMPTY_TRASH)) {
error_location = ERROR_LOCATION_SOURCE_PARENT;
error_kind = ERROR_READ_ONLY;
} else if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
&& (transfer_info->kind == TRANSFER_DELETE
|| transfer_info->kind == TRANSFER_EMPTY_TRASH)) {
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)
&& (transfer_info->kind == TRANSFER_MOVE
|| transfer_info->kind == TRANSFER_MOVE_TO_TRASH)
&& progress_info->phase != GNOME_VFS_XFER_CHECKING_DESTINATION) {
error_location = ERROR_LOCATION_SOURCE_PARENT;
error_kind = ERROR_READ_ONLY;
} 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;
} else if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED
&& (transfer_info->kind == TRANSFER_MOVE
|| transfer_info->kind == TRANSFER_MOVE_TO_TRASH)
&& 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
&& (transfer_info->kind == TRANSFER_COPY
|| transfer_info->kind == TRANSFER_DUPLICATE)
&& (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;
}
text = build_error_string (formatted_source_name, formatted_target_name,
transfer_info->kind,
error_kind, error_location,
progress_info->vfs_status);
switch (transfer_info->kind) {
case TRANSFER_COPY:
case TRANSFER_DUPLICATE:
dialog_title = _("Error while copying.");
break;
case TRANSFER_MOVE:
dialog_title = _("Error while moving.");
break;
case TRANSFER_LINK:
dialog_title = _("Error while linking.");
break;
case TRANSFER_DELETE:
case TRANSFER_EMPTY_TRASH:
case TRANSFER_MOVE_TO_TRASH:
dialog_title = _("Error while deleting.");
break;
default:
dialog_title = NULL;
break;
}
if (error_location == ERROR_LOCATION_TARGET) {
/* We can't continue, just tell the user. */
nautilus_simple_dialog (parent_for_error_dialog (transfer_info),
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.
*/
error_dialog_button_pressed = nautilus_simple_dialog
(parent_for_error_dialog (transfer_info), TRUE, text,
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. */
error_dialog_button_pressed = nautilus_simple_dialog
(parent_for_error_dialog (transfer_info), TRUE, text,
dialog_title,
_("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);
g_free (formatted_source_name);
g_free (formatted_target_name);
return error_dialog_result;
case GNOME_VFS_XFER_ERROR_MODE_ABORT:
default:
if (transfer_info->progress_dialog != NULL) {
nautilus_file_operations_progress_freeze
(NAUTILUS_FILE_OPERATIONS_PROGRESS (transfer_info->progress_dialog));
nautilus_file_operations_progress_thaw
(NAUTILUS_FILE_OPERATIONS_PROGRESS (transfer_info->progress_dialog));
gtk_widget_destroy (transfer_info->progress_dialog);
}
return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
}
}
/* 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;
}
static int
handle_transfer_overwrite (const GnomeVFSXferProgressInfo *progress_info,
TransferInfo *transfer_info)
{
int result;
char *text, *formatted_name;
/* Handle special case files such as Trash, mount links and home directory */
if (is_special_link (progress_info->target_name)) {
formatted_name = extract_and_ellipsize_file_name_for_dialog
(progress_info->target_name);
if (transfer_info->kind == TRANSFER_MOVE) {
text = g_strdup_printf (_("\"%s\" could not be moved to the new location, "
"because its name is already used for a special item that "
"cannot be removed or replaced.\n\n"
"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, "
"because its name is already used for a special item that "
"cannot be removed or replaced.\n\n"
"If you still want to copy \"%s\", rename it and try again."),
formatted_name, formatted_name);
}
nautilus_simple_dialog (parent_for_error_dialog (transfer_info), TRUE, text,
_("Unable to replace file."), _("OK"), NULL, NULL);
g_free (text);
g_free (formatted_name);
return GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP;
}
/* transfer conflict, prompt the user to replace or skip */
formatted_name = format_and_ellipsize_uri_for_dialog (progress_info->target_name);
text = g_strdup_printf (_("File \"%s\" already exists.\n\n"
"Would you like to replace it?"),
formatted_name);
g_free (formatted_name);
if (progress_info->duplicate_count == 1) {
/* we are going to only get one duplicate alert, don't offer
* Replace All
*/
result = nautilus_simple_dialog
(parent_for_error_dialog (transfer_info), TRUE, text,
_("Conflict while copying"),
_("Replace"), _("Skip"), NULL);
switch (result) {
case 0:
return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE;
default:
g_assert_not_reached ();
/* fall through */
case 1:
return GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP;
}
} else {
result = nautilus_simple_dialog
(parent_for_error_dialog (transfer_info), TRUE, text,
_("Conflict while copying"),
_("Replace All"), _("Replace"), _("Skip"), NULL);
switch (result) {
case 0:
return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE_ALL;
case 1:
return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE;
default:
g_assert_not_reached ();
/* fall through */
case 2:
return GNOME_VFS_XFER_OVERWRITE_ACTION_SKIP;
}
}
}
/* Note that we have these two separate functions with separate format
* strings for ease of localization.
*/
static char *
get_link_name (char *name, int count)
{
char *result;
char *unescaped_name;
char *unescaped_result;
const char *format;
g_assert (name != NULL);
unescaped_name = gnome_vfs_unescape_string (name, "/");
g_free (name);
if (count < 1) {
g_warning ("bad count in get_link_name");
count = 1;
}
if (count <= 2) {
/* Handle special cases for low numbers.
* Perhaps for some locales we will need to add more.
*/
switch (count) {
default:
g_assert_not_reached ();
/* fall through */
case 1:
/* appended to new link file */
format = _("link to %s");
break;
case 2:
/* appended to new link file */
format = _("another link to %s");
break;
}
unescaped_result = g_strdup_printf (format, unescaped_name);
} 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.
*/
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:
/* appended to new link file */
format = _("%dnd link to %s");
break;
case 3:
/* appended to new link file */
format = _("%drd link to %s");
break;
default:
/* appended to new link file */
format = _("%dth link to %s");
break;
}
unescaped_result = g_strdup_printf (format, count, unescaped_name);
}
result = gnome_vfs_escape_path_string (unescaped_result);
g_free (unescaped_name);
g_free (unescaped_result);
return result;
}
/* Localizers:
* Feel free to leave out the st, nd, rd and th suffix or
* make some or all of them match.
*/
/* localizers: tag used to detect the first copy of a file */
static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
/* localizers: tag used to detect the second copy of a file */
static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)");
/* localizers: tag used to detect the x1st copy of a file */
static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)");
/* localizers: tag used to detect the x2nd copy of a file */
static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)");
/* localizers: tag used to detect the x3rd copy of a file */
static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)");
/* localizers: tag used to detect the xxth copy of a file */
static const char untranslated_th_copy_duplicate_tag[] = N_("th copy)");
#define COPY_DUPLICATE_TAG _(untranslated_copy_duplicate_tag)
#define ANOTHER_COPY_DUPLICATE_TAG _(untranslated_another_copy_duplicate_tag)
#define ST_COPY_DUPLICATE_TAG _(untranslated_st_copy_duplicate_tag)
#define ND_COPY_DUPLICATE_TAG _(untranslated_nd_copy_duplicate_tag)
#define RD_COPY_DUPLICATE_TAG _(untranslated_rd_copy_duplicate_tag)
#define TH_COPY_DUPLICATE_TAG _(untranslated_th_copy_duplicate_tag)
/* localizers: appended to first file copy */
static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s");
/* localizers: appended to second file copy */
static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s");
/* localizers: appended to x1st file copy */
static const char untranslated_st_copy_duplicate_format[] = N_("%s (%dst copy)%s");
/* localizers: appended to x2nd file copy */
static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%dnd copy)%s");
/* localizers: appended to x3rd file copy */
static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%drd copy)%s");
/* localizers: appended to xxth file copy */
static const char untranslated_th_copy_duplicate_format[] = N_("%s (%dth copy)%s");
#define FIRST_COPY_DUPLICATE_FORMAT _(untranslated_first_copy_duplicate_format)
#define SECOND_COPY_DUPLICATE_FORMAT _(untranslated_second_copy_duplicate_format)
#define ST_COPY_DUPLICATE_FORMAT _(untranslated_st_copy_duplicate_format)
#define ND_COPY_DUPLICATE_FORMAT _(untranslated_nd_copy_duplicate_format)
#define RD_COPY_DUPLICATE_FORMAT _(untranslated_rd_copy_duplicate_format)
#define TH_COPY_DUPLICATE_FORMAT _(untranslated_th_copy_duplicate_format)
static char *
extract_string_until (const char *original, const char *until_substring)
{
char *result;
g_assert ((int) strlen (original) >= until_substring - original);
g_assert (until_substring - original >= 0);
result = g_malloc (until_substring - original + 1);
strncpy (result, original, until_substring - original);
result[until_substring - original] = '\0';
return result;
}
/* Dismantle a file name, separating the base name, the file suffix and removing any
* (xxxcopy), etc. string. Figure out the count that corresponds to the given
* (xxxcopy) substring.
*/
static void
parse_previous_duplicate_name (const char *name,
char **name_base,
const char **suffix,
int *count)
{
const char *tag;
g_assert (name[0] != '\0');
*suffix = strrchr (name + 1, '.');
if (*suffix == NULL || (*suffix)[1] == '\0') {
/* no suffix */
*suffix = "";
}
tag = strstr (name, COPY_DUPLICATE_TAG);
if (tag != NULL) {
if (tag > *suffix) {
/* handle case "foo. (copy)" */
*suffix = "";
}
*name_base = extract_string_until (name, tag);
*count = 1;
return;
}
tag = strstr (name, ANOTHER_COPY_DUPLICATE_TAG);
if (tag != NULL) {
if (tag > *suffix) {
/* handle case "foo. (another copy)" */
*suffix = "";
}
*name_base = extract_string_until (name, tag);
*count = 2;
return;
}
/* Check to see if we got one of st, nd, rd, th. */
tag = strstr (name, ST_COPY_DUPLICATE_TAG);
if (tag == NULL) {
tag = strstr (name, ND_COPY_DUPLICATE_TAG);
}
if (tag == NULL) {
tag = strstr (name, RD_COPY_DUPLICATE_TAG);
}
if (tag == NULL) {
tag = strstr (name, TH_COPY_DUPLICATE_TAG);
}
/* If we got one of st, nd, rd, th, fish out the duplicate number. */
if (tag != NULL) {
/* localizers: opening parentheses to match the "th copy)" string */
tag = strstr (name, _(" ("));
if (tag != NULL) {
if (tag > *suffix) {
/* handle case "foo. (22nd copy)" */
*suffix = "";
}
*name_base = extract_string_until (name, tag);
/* localizers: opening parentheses of the "th copy)" string */
if (sscanf (tag, _(" (%d"), count) == 1) {
if (*count < 1 || *count > 1000000) {
/* keep the count within a reasonable range */
*count = 0;
}
return;
}
*count = 0;
return;
}
}
*count = 0;
if (**suffix != '\0') {
*name_base = extract_string_until (name, *suffix);
} else {
*name_base = strdup (name);
}
}
static char *
make_next_duplicate_name (const char *base, const char *suffix, int count)
{
const char *format;
char *result;
if (count < 1) {
g_warning ("bad count %d in get_duplicate_name", count);
count = 1;
}
if (count <= 2) {
/* Handle special cases for low numbers.
* Perhaps for some locales we will need to add more.
*/
switch (count) {
default:
g_assert_not_reached ();
/* fall through */
case 1:
format = FIRST_COPY_DUPLICATE_FORMAT;
break;
case 2:
format = SECOND_COPY_DUPLICATE_FORMAT;
break;
}
result = g_strdup_printf (format, base, suffix);
} 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.
*/
switch (count % 10) {
case 1:
format = ST_COPY_DUPLICATE_FORMAT;
break;
case 2:
format = ND_COPY_DUPLICATE_FORMAT;
break;
case 3:
format = RD_COPY_DUPLICATE_FORMAT;
break;
default:
/* The general case. */
format = TH_COPY_DUPLICATE_FORMAT;
break;
}
result = g_strdup_printf (format, base, count, suffix);
}
return result;
}
static char *
get_duplicate_name (const char *name, int count_increment)
{
char *result;
char *name_base;
const char *suffix;
int count;
parse_previous_duplicate_name (name, &name_base, &suffix, &count);
result = make_next_duplicate_name (name_base, suffix, count + count_increment);
g_free (name_base);
return result;
}
static char *
get_next_duplicate_name (char *name, int count_increment)
{
char *unescaped_name;
char *unescaped_result;
char *result;
unescaped_name = gnome_vfs_unescape_string (name, "/");
g_free (name);
unescaped_result = get_duplicate_name (unescaped_name, count_increment);
g_free (unescaped_name);
result = gnome_vfs_escape_path_string (unescaped_result);
g_free (unescaped_result);
return result;
}
static int
handle_transfer_duplicate (GnomeVFSXferProgressInfo *progress_info,
TransferInfo *transfer_info)
{
switch (transfer_info->kind) {
case TRANSFER_LINK:
progress_info->duplicate_name = get_link_name
(progress_info->duplicate_name,
progress_info->duplicate_count);
break;
case TRANSFER_COPY:
case TRANSFER_MOVE_TO_TRASH:
progress_info->duplicate_name = get_next_duplicate_name
(progress_info->duplicate_name,
progress_info->duplicate_count);
break;
default:
/* For all other cases we use the name as-is. */
}
return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
}
static int
update_transfer_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSXferProgressInfo *progress_info,
gpointer data)
{
TransferInfo *transfer_info;
transfer_info = (TransferInfo *) data;
switch (progress_info->status) {
case GNOME_VFS_XFER_PROGRESS_STATUS_OK:
return handle_transfer_ok (progress_info, transfer_info);
case GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR:
return handle_transfer_vfs_error (progress_info, transfer_info);
case GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE:
return handle_transfer_overwrite (progress_info, transfer_info);
case GNOME_VFS_XFER_PROGRESS_STATUS_DUPLICATE:
return handle_transfer_duplicate (progress_info, transfer_info);
default:
g_warning (_("Unknown GnomeVFSXferProgressStatus %d"),
progress_info->status);
return 0;
}
}
static void
apply_one_position (IconPositionIterator *position_iterator,
const char *source_name, const char *target_name)
{
const char *item_uri;
if (position_iterator == NULL || position_iterator->last_uri == NULL) {
return;
}
for (;;) {
/* Scan for the next point that matches the source_name
* uri.
*/
if (strcmp ((const char *)position_iterator->last_uri->data,
source_name) == 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++;
if (position_iterator->last_uri == NULL) {
/* we are done, no more points left */
return;
}
}
item_uri = target_name != NULL ? target_name : source_name;
/* apply the location to the target file */
nautilus_file_changes_queue_schedule_position_setting (target_name,
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++;
}
typedef struct {
GHashTable *debuting_uris;
IconPositionIterator *iterator;
} SyncTransferInfo;
/* Low-level callback, called for every copy engine operation.
* Generates notifications about new, deleted and moved files.
*/
static int
sync_transfer_callback (GnomeVFSXferProgressInfo *progress_info, gpointer data)
{
GHashTable *debuting_uris;
IconPositionIterator *position_iterator;
if (data != NULL) {
debuting_uris = ((SyncTransferInfo *) data)->debuting_uris;
position_iterator = ((SyncTransferInfo *) data)->iterator;
} else {
debuting_uris = NULL;
position_iterator = NULL;
}
if (progress_info->status == GNOME_VFS_XFER_PROGRESS_STATUS_OK) {
switch (progress_info->phase) {
case GNOME_VFS_XFER_PHASE_OPENTARGET:
if (progress_info->top_level_item) {
/* this is one of the selected copied or moved items -- we need
* to make sure it's metadata gets copied over
*/
if (progress_info->source_name == NULL) {
/* remove any old metadata */
nautilus_file_changes_queue_schedule_metadata_remove
(progress_info->target_name);
} else {
nautilus_file_changes_queue_schedule_metadata_copy
(progress_info->source_name, progress_info->target_name);
apply_one_position (position_iterator, progress_info->source_name,
progress_info->target_name);
}
if (debuting_uris != NULL) {
g_hash_table_insert (debuting_uris, g_strdup (progress_info->target_name), NULL);
}
}
nautilus_file_changes_queue_file_added (progress_info->target_name);
break;
case GNOME_VFS_XFER_PHASE_MOVING:
if (progress_info->top_level_item) {
g_assert (progress_info->source_name != NULL);
nautilus_file_changes_queue_schedule_metadata_move
(progress_info->source_name, progress_info->target_name);
apply_one_position (position_iterator, progress_info->source_name,
progress_info->target_name);
if (debuting_uris != NULL) {
g_hash_table_insert (debuting_uris, g_strdup (progress_info->target_name), NULL);
}
}
nautilus_file_changes_queue_file_moved (progress_info->source_name,
progress_info->target_name);
break;
case GNOME_VFS_XFER_PHASE_DELETESOURCE:
if (progress_info->top_level_item) {
g_assert (progress_info->source_name != NULL);
nautilus_file_changes_queue_schedule_metadata_remove
(progress_info->source_name);
}
nautilus_file_changes_queue_file_removed (progress_info->source_name);
break;
case GNOME_VFS_XFER_PHASE_COMPLETED:
/* done, clean up */
icon_position_iterator_free (position_iterator);
/* SyncXferInfo doesn't own the debuting_uris hash table - don't free it here.
*/
g_free (data);
break;
default:
break;
}
}
return 1;
}
static gboolean
check_target_directory_is_or_in_trash (GnomeVFSURI *trash_dir_uri, GnomeVFSURI *target_dir_uri)
{
g_assert (target_dir_uri != NULL);
if (trash_dir_uri == NULL) {
return FALSE;
}
return gnome_vfs_uri_equal (trash_dir_uri, target_dir_uri)
|| gnome_vfs_uri_is_parent (trash_dir_uri, target_dir_uri, TRUE);
}
static GnomeVFSURI *
append_basename (const GnomeVFSURI *target_directory,
const GnomeVFSURI *source_directory)
{
const char *file_name;
file_name = gnome_vfs_uri_extract_short_name (source_directory);
if (file_name != NULL) {
return gnome_vfs_uri_append_file_name (target_directory, file_name);
}
return gnome_vfs_uri_dup (target_directory);
}
static gboolean
vfs_uri_is_special_link (GnomeVFSURI *vfs_uri)
{
char *uri;
gboolean is_special;
uri = gnome_vfs_uri_to_string (vfs_uri, GNOME_VFS_URI_HIDE_NONE);
is_special = is_special_link (uri);
g_free (uri);
return is_special;
}
void
nautilus_file_operations_copy_move (const GList *item_uris,
GArray *relative_item_points,
const char *target_dir,
int copy_action,
GtkWidget *view,
void (*done_callback) (GHashTable *debuting_uris, gpointer data),
gpointer done_callback_data)
{
const GList *p;
GnomeVFSXferOptions move_options;
GList *source_uri_list, *target_uri_list;
GnomeVFSURI *source_uri, *target_uri;
GnomeVFSURI *source_dir_uri, *target_dir_uri;
GnomeVFSURI *trash_dir_uri;
GnomeVFSURI *uri;
TransferInfo *transfer_info;
SyncTransferInfo *sync_transfer_info;
GnomeVFSResult result;
gboolean same_fs;
gboolean is_trash_move;
gboolean is_desktop_trash_link;
gboolean duplicate;
IconPositionIterator *icon_position_iterator;
g_assert (item_uris != NULL);
target_dir_uri = NULL;
trash_dir_uri = NULL;
icon_position_iterator = NULL;
result = GNOME_VFS_OK;
source_uri_list = NULL;
target_uri_list = NULL;
same_fs = TRUE;
is_trash_move = FALSE;
duplicate = copy_action != GDK_ACTION_MOVE;
move_options = GNOME_VFS_XFER_RECURSIVE;
if (target_dir != NULL) {
if (nautilus_uri_is_trash (target_dir)) {
is_trash_move = TRUE;
} else {
target_dir_uri = gnome_vfs_uri_new (target_dir);
}
}
/* build the source and target URI lists and figure out if all the files are on the
* same disk
*/
for (p = item_uris; p != NULL; p = p->next) {
/* Filter out special Nautilus link files */
/* FIXME bugzilla.eazel.com 5295:
* This is surprising behavior -- the user drags the Trash icon (say)
* to a folder, releases it, and nothing whatsoever happens. Don't we want
* a dialog in this case?
*/
if (is_special_link ((const char *) p->data)) {
continue;
}
source_uri = gnome_vfs_uri_new ((const char *) p->data);
source_dir_uri = gnome_vfs_uri_get_parent (source_uri);
target_uri = NULL;
if (target_dir != NULL) {
if (is_trash_move) {
gnome_vfs_find_directory (source_uri, GNOME_VFS_DIRECTORY_KIND_TRASH,
&target_dir_uri, FALSE, FALSE, 0777);
}
if (target_dir_uri != NULL) {
target_uri = append_basename (target_dir_uri, source_uri);
}
} else {
/* duplication */
target_uri = gnome_vfs_uri_ref (source_uri);
if (target_dir_uri == NULL) {
target_dir_uri = gnome_vfs_uri_ref (source_dir_uri);
}
}
if (target_uri != NULL) {
target_uri_list = g_list_prepend (target_uri_list, target_uri);
source_uri_list = g_list_prepend (source_uri_list, source_uri);
gnome_vfs_check_same_fs_uris (source_uri, target_uri, &same_fs);
g_assert (target_dir_uri != NULL);
duplicate &= same_fs;
duplicate &= gnome_vfs_uri_equal (source_dir_uri, target_dir_uri);
}
gnome_vfs_uri_unref (source_dir_uri);
}
if (duplicate) {
/* Copy operation, parents match -> duplicate operation. Ask gnome-vfs
* to generate unique names for target files
*/
move_options |= GNOME_VFS_XFER_USE_UNIQUE_NAMES;
}
/* List may be NULL if we filtered all items out */
if (source_uri_list == NULL) {
if (target_dir_uri != NULL) {
gnome_vfs_uri_unref (target_dir_uri);
}
if (target_uri_list != NULL) {
gnome_vfs_uri_list_free (target_uri_list);
}
return;
}
source_uri_list = g_list_reverse (source_uri_list);
target_uri_list = g_list_reverse (target_uri_list);
if (copy_action == GDK_ACTION_MOVE) {
move_options |= GNOME_VFS_XFER_REMOVESOURCE;
} else if (copy_action == GDK_ACTION_LINK) {
move_options |= GNOME_VFS_XFER_LINK_ITEMS;
}
/* set up the copy/move parameters */
transfer_info = g_new0 (TransferInfo, 1);
transfer_info->parent_view = view;
transfer_info->progress_dialog = NULL;
if (relative_item_points != NULL && relative_item_points->len > 0) {
/* FIXME: we probably don't need an icon_position_iterator
* here at all.
*/
icon_position_iterator = icon_position_iterator_new (relative_item_points, item_uris);
}
if ((move_options & GNOME_VFS_XFER_REMOVESOURCE) != 0) {
/* localizers: progress dialog title */
transfer_info->operation_title = _("Moving files");
/* localizers: label prepended to the progress count */
transfer_info->action_label =_("Files moved:");
/* localizers: label prepended to the name of the current file moved */
transfer_info->progress_verb =_("Moving");
transfer_info->preparation_name =_("Preparing To Move...");
transfer_info->cleanup_name = _("Finishing Move...");
transfer_info->kind = TRANSFER_MOVE;
/* Do an arbitrary guess that an operation will take very little
* time and the progress shouldn't be shown.
*/
transfer_info->show_progress_dialog =
!same_fs || g_list_length ((GList *)item_uris) > 20;
} else if ((move_options & GNOME_VFS_XFER_LINK_ITEMS) != 0) {
/* localizers: progress dialog title */
transfer_info->operation_title = _("Creating links to files");
/* localizers: label prepended to the progress count */
transfer_info->action_label =_("Files linked:");
/* localizers: label prepended to the name of the current file linked */
transfer_info->progress_verb =_("Linking");
transfer_info->preparation_name = _("Preparing to Create Links...");
transfer_info->cleanup_name = _("Finishing Creating Links...");
transfer_info->kind = TRANSFER_LINK;
transfer_info->show_progress_dialog =
g_list_length ((GList *)item_uris) > 20;
} else {
/* localizers: progress dialog title */
transfer_info->operation_title = _("Copying files");
/* localizers: label prepended to the progress count */
transfer_info->action_label =_("Files copied:");
/* localizers: label prepended to the name of the current file copied */
transfer_info->progress_verb =_("Copying");
transfer_info->preparation_name =_("Preparing To Copy...");
transfer_info->cleanup_name = "";
transfer_info->kind = TRANSFER_COPY;
/* always show progress during copy */
transfer_info->show_progress_dialog = TRUE;
}
/* we'll need to check for copy into Trash and for moving/copying the Trash itself */
gnome_vfs_find_directory (target_dir_uri, GNOME_VFS_DIRECTORY_KIND_TRASH,
&trash_dir_uri, FALSE, FALSE, 0777);
if ((move_options & GNOME_VFS_XFER_REMOVESOURCE) == 0) {
/* don't allow copying into Trash */
if (check_target_directory_is_or_in_trash (trash_dir_uri, target_dir_uri)) {
nautilus_simple_dialog
(view,
FALSE,
_("You cannot copy items into the Trash."),
_("Can't Copy to Trash"),
GNOME_STOCK_BUTTON_OK, NULL, NULL);
result = GNOME_VFS_ERROR_NOT_PERMITTED;
}
}
if (result == GNOME_VFS_OK) {
for (p = source_uri_list; p != NULL; p = p->next) {
uri = (GnomeVFSURI *)p->data;
/* Check that a trash folder is not being moved/copied (link is OK). */
if (trash_dir_uri != NULL
&& ((move_options & GNOME_VFS_XFER_LINK_ITEMS) == 0)
&& gnome_vfs_uri_equal (uri, trash_dir_uri)) {
/* Distinguish Trash file on desktop from other trash folders for
* message purposes.
*/
is_desktop_trash_link = vfs_uri_is_special_link (uri);
nautilus_simple_dialog
(view,
FALSE,
((move_options & GNOME_VFS_XFER_REMOVESOURCE) != 0)
? (is_desktop_trash_link
? _("The Trash must remain on the desktop.")
: _("You cannot move this trash folder."))
: (is_desktop_trash_link
? _("You cannot copy the Trash.")
: _("You cannot copy this trash folder.")),
((move_options & GNOME_VFS_XFER_REMOVESOURCE) != 0)
? _("Can't Change Trash Location")
: _("Can't Copy Trash"),
GNOME_STOCK_BUTTON_OK, NULL, NULL);
result = GNOME_VFS_ERROR_NOT_PERMITTED;
break;
}
/* Don't allow recursive move/copy into itself.
* (We would get a file system error if we proceeded but it is nicer to
* detect and report it at this level)
*/
if ((move_options & GNOME_VFS_XFER_LINK_ITEMS) == 0
&& (gnome_vfs_uri_equal (uri, target_dir_uri)
|| gnome_vfs_uri_is_parent (uri, target_dir_uri, TRUE))) {
nautilus_simple_dialog
(view,
FALSE,
((move_options & GNOME_VFS_XFER_REMOVESOURCE) != 0)
? _("You cannot move a folder into itself.")
: _("You cannot copy a folder into itself."),
_("Can't Move Into Self"),
GNOME_STOCK_BUTTON_OK, NULL, NULL);
result = GNOME_VFS_ERROR_NOT_PERMITTED;
break;
}
}
}
transfer_info->error_mode = GNOME_VFS_XFER_ERROR_MODE_QUERY;
transfer_info->overwrite_mode = GNOME_VFS_XFER_OVERWRITE_MODE_QUERY;
transfer_info->done_callback = done_callback;
transfer_info->done_callback_data = done_callback_data;
transfer_info->debuting_uris = g_hash_table_new (g_str_hash, g_str_equal);
sync_transfer_info = g_new (SyncTransferInfo, 1);
sync_transfer_info->iterator = icon_position_iterator;
sync_transfer_info->debuting_uris = transfer_info->debuting_uris;
if (result == GNOME_VFS_OK) {
gnome_vfs_async_xfer (&transfer_info->handle, source_uri_list, target_uri_list,
move_options, GNOME_VFS_XFER_ERROR_MODE_QUERY,
GNOME_VFS_XFER_OVERWRITE_MODE_QUERY,
update_transfer_callback, transfer_info,
sync_transfer_callback, sync_transfer_info);
}
gnome_vfs_uri_list_free (source_uri_list);
gnome_vfs_uri_list_free (target_uri_list);
if (trash_dir_uri != NULL) {
gnome_vfs_uri_unref (trash_dir_uri);
}
gnome_vfs_uri_unref (target_dir_uri);
}
typedef struct {
GnomeVFSAsyncHandle *handle;
void (* done_callback)(const char *new_folder_uri, gpointer data);
gpointer data;
GtkWidget *parent_view;
} NewFolderTransferState;
static int
handle_new_folder_vfs_error (const GnomeVFSXferProgressInfo *progress_info, NewFolderTransferState *state)
{
const char *error_string;
char *error_string_to_free;
error_string_to_free = NULL;
if (progress_info->vfs_status == GNOME_VFS_ERROR_ACCESS_DENIED) {
error_string = _("Error creating new folder.\n\n"
"You do not have permissions to write to the destination.");
} else if (progress_info->vfs_status == GNOME_VFS_ERROR_NO_SPACE) {
error_string = _("Error creating new folder.\n\n"
"There is no space on the destination.");
} else {
error_string = g_strdup_printf (_("Error \"%s\" creating new folder."),
gnome_vfs_result_to_string(progress_info->vfs_status));
}
nautilus_error_dialog (error_string, _("Error creating new folder"),
GTK_WINDOW (state->parent_view));
g_free (error_string_to_free);
return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
}
static int
new_folder_transfer_callback (GnomeVFSAsyncHandle *handle,
GnomeVFSXferProgressInfo *progress_info,
gpointer data)
{
NewFolderTransferState *state;
char *temp_string;
state = (NewFolderTransferState *) data;
switch (progress_info->status) {
case GNOME_VFS_XFER_PROGRESS_STATUS_OK:
nautilus_file_changes_consume_changes (TRUE);
(* state->done_callback) (progress_info->target_name, state->data);
g_free (state);
return 0;
case GNOME_VFS_XFER_PROGRESS_STATUS_DUPLICATE:
temp_string = progress_info->duplicate_name;
if (progress_info->vfs_status == GNOME_VFS_ERROR_NAME_TOO_LONG) {
/* special case an 8.3 file system */
progress_info->duplicate_name = g_strndup (temp_string, 8);
progress_info->duplicate_name[8] = '\0';
g_free (temp_string);
temp_string = progress_info->duplicate_name;
progress_info->duplicate_name = g_strdup_printf
("%s.%d",
progress_info->duplicate_name,
progress_info->duplicate_count);
} else {
progress_info->duplicate_name = g_strdup_printf
("%s%%20%d",
progress_info->duplicate_name,
progress_info->duplicate_count);
}
g_free (temp_string);
return GNOME_VFS_XFER_ERROR_ACTION_SKIP;
case GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR:
return handle_new_folder_vfs_error (progress_info, state);
default:
g_warning (_("Unknown GnomeVFSXferProgressStatus %d"),
progress_info->status);
return 0;
}
}
void
nautilus_file_operations_new_folder (GtkWidget *parent_view,
const char *parent_dir,
void (*done_callback) (const char *, gpointer),
gpointer data)
{
NewFolderTransferState *state;
GList *target_uri_list;
GnomeVFSURI *uri, *parent_uri;
state = g_new (NewFolderTransferState, 1);
state->done_callback = done_callback;
state->data = data;
state->parent_view = parent_view;
/* pass in the target directory and the new folder name as a destination URI */
parent_uri = gnome_vfs_uri_new (parent_dir);
/* localizers: the initial name of a new folder */
uri = gnome_vfs_uri_append_file_name (parent_uri, _("untitled folder"));
target_uri_list = g_list_append (NULL, uri);
gnome_vfs_async_xfer (&state->handle, NULL, target_uri_list,
GNOME_VFS_XFER_NEW_UNIQUE_DIRECTORY,
GNOME_VFS_XFER_ERROR_MODE_QUERY,
GNOME_VFS_XFER_OVERWRITE_MODE_QUERY,
new_folder_transfer_callback, state,
sync_transfer_callback, NULL);
gnome_vfs_uri_list_free (target_uri_list);
gnome_vfs_uri_unref (parent_uri);
}
void
nautilus_file_operations_move_to_trash (const GList *item_uris,
GtkWidget *parent_view)
{
const GList *p;
GnomeVFSURI *trash_dir_uri;
GnomeVFSURI *source_uri;
GList *source_uri_list, *target_uri_list;
GnomeVFSResult result;
TransferInfo *transfer_info;
gboolean bail;
char *text;
char *item_name;
const char *source_uri_text;
g_assert (item_uris != NULL);
trash_dir_uri = NULL;
source_uri_list = NULL;
target_uri_list = NULL;
result = GNOME_VFS_OK;
/* build the source and uri list, checking if any of the delete itmes are Trash */
for (p = item_uris; p != NULL; p = p->next) {
bail = FALSE;
source_uri_text = (const char *) p->data;
source_uri = gnome_vfs_uri_new (source_uri_text);
source_uri_list = g_list_prepend (source_uri_list, source_uri);
if (trash_dir_uri == NULL) {
GnomeVFSURI *source_dir_uri;
source_dir_uri = gnome_vfs_uri_get_parent (source_uri);
result = gnome_vfs_find_directory (source_dir_uri, GNOME_VFS_DIRECTORY_KIND_TRASH,
&trash_dir_uri, FALSE, FALSE, 0777);
gnome_vfs_uri_unref (source_dir_uri);
}
if (result != GNOME_VFS_OK) {
break;
}
g_assert (trash_dir_uri != NULL);
target_uri_list = g_list_prepend (target_uri_list, append_basename (trash_dir_uri, source_uri));
if (gnome_vfs_uri_equal (source_uri, trash_dir_uri)) {
nautilus_simple_dialog
(parent_view,
FALSE,
_("The Trash must remain on the desktop."),
_("Can't Change Trash Location"),
GNOME_STOCK_BUTTON_OK, NULL, NULL);
bail = TRUE;
} else if (gnome_vfs_uri_is_parent (source_uri, trash_dir_uri, TRUE)) {
item_name = extract_and_ellipsize_file_name_for_dialog (source_uri_text);
text = g_strdup_printf
(_("You cannot throw \"%s\" into the Trash."),
item_name);
nautilus_simple_dialog
(parent_view, FALSE, text,
_("Error Moving to Trash"),
GNOME_STOCK_BUTTON_OK, NULL, NULL);
bail = TRUE;
g_free (text);
g_free (item_name);
}
if (bail) {
result = GNOME_VFS_ERROR_NOT_PERMITTED;
break;
}
}
source_uri_list = g_list_reverse (source_uri_list);
target_uri_list = g_list_reverse (target_uri_list);
if (result == GNOME_VFS_OK) {
g_assert (trash_dir_uri != NULL);
/* set up the move parameters */
transfer_info = g_new0 (TransferInfo, 1);
transfer_info->parent_view = parent_view;
transfer_info->progress_dialog = NULL;
/* Do an arbitrary guess that an operation will take very little
* time and the progress shouldn't be shown.
*/
transfer_info->show_progress_dialog = g_list_length ((GList *)item_uris) > 20;
/* localizers: progress dialog title */
transfer_info->operation_title = _("Moving files to the Trash");
/* localizers: label prepended to the progress count */
transfer_info->action_label =_("Files thrown out:");
/* localizers: label prepended to the name of the current file moved */
transfer_info->progress_verb =_("Moving");
transfer_info->preparation_name =_("Preparing to Move to Trash...");
transfer_info->cleanup_name ="";
transfer_info->error_mode = GNOME_VFS_XFER_ERROR_MODE_QUERY;
transfer_info->overwrite_mode = GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE;
transfer_info->kind = TRANSFER_MOVE_TO_TRASH;
gnome_vfs_async_xfer (&transfer_info->handle, source_uri_list, target_uri_list,
GNOME_VFS_XFER_REMOVESOURCE | GNOME_VFS_XFER_USE_UNIQUE_NAMES,
GNOME_VFS_XFER_ERROR_MODE_QUERY,
GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE,
update_transfer_callback, transfer_info,
sync_transfer_callback, NULL);
}
gnome_vfs_uri_list_free (source_uri_list);
gnome_vfs_uri_list_free (target_uri_list);
gnome_vfs_uri_unref (trash_dir_uri);
}
void
nautilus_file_operations_delete (const GList *item_uris,
GtkWidget *parent_view)
{
GList *uri_list;
const GList *p;
TransferInfo *transfer_info;
uri_list = NULL;
for (p = item_uris; p != NULL; p = p->next) {
uri_list = g_list_prepend (uri_list,
gnome_vfs_uri_new ((const char *) p->data));
}
uri_list = g_list_reverse (uri_list);
transfer_info = g_new0 (TransferInfo, 1);
transfer_info->parent_view = parent_view;
transfer_info->progress_dialog = NULL;
transfer_info->show_progress_dialog = TRUE;
/* localizers: progress dialog title */
transfer_info->operation_title = _("Deleting files");
/* localizers: label prepended to the progress count */
transfer_info->action_label =_("Files deleted:");
/* localizers: label prepended to the name of the current file deleted */
transfer_info->progress_verb =_("Deleting");
transfer_info->preparation_name =_("Preparing to Delete files...");
transfer_info->cleanup_name ="";
transfer_info->error_mode = GNOME_VFS_XFER_ERROR_MODE_QUERY;
transfer_info->overwrite_mode = GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE;
transfer_info->kind = TRANSFER_DELETE;
gnome_vfs_async_xfer (&transfer_info->handle, uri_list, NULL,
GNOME_VFS_XFER_DELETE_ITEMS | GNOME_VFS_XFER_RECURSIVE,
GNOME_VFS_XFER_ERROR_MODE_QUERY,
GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE,
update_transfer_callback, transfer_info,
sync_transfer_callback, NULL);
gnome_vfs_uri_list_free (uri_list);
}
static void
do_empty_trash (GtkWidget *parent_view)
{
TransferInfo *transfer_info;
GList *trash_dir_list;
trash_dir_list = nautilus_trash_monitor_get_trash_directories ();
if (trash_dir_list != NULL) {
/* set up the move parameters */
transfer_info = g_new0 (TransferInfo, 1);
transfer_info->parent_view = parent_view;
transfer_info->progress_dialog = NULL;
transfer_info->show_progress_dialog = TRUE;
/* localizers: progress dialog title */
transfer_info->operation_title = _("Emptying the Trash");
/* localizers: label prepended to the progress count */
transfer_info->action_label =_("Files deleted:");
/* localizers: label prepended to the name of the current file deleted */
transfer_info->progress_verb =_("Deleting");
transfer_info->preparation_name =_("Preparing to Empty the Trash...");
transfer_info->cleanup_name ="";
transfer_info->error_mode = GNOME_VFS_XFER_ERROR_MODE_QUERY;
transfer_info->overwrite_mode = GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE;
transfer_info->kind = TRANSFER_EMPTY_TRASH;
gnome_vfs_async_xfer (&transfer_info->handle, trash_dir_list, NULL,
GNOME_VFS_XFER_EMPTY_DIRECTORIES,
GNOME_VFS_XFER_ERROR_MODE_QUERY,
GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE,
update_transfer_callback, transfer_info,
sync_transfer_callback, NULL);
}
gnome_vfs_uri_list_free (trash_dir_list);
}
static gboolean
confirm_empty_trash (GtkWidget *parent_view)
{
GnomeDialog *dialog;
GtkWindow *parent_window;
/* Just Say Yes if the preference says not to confirm. */
if (!nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_CONFIRM_TRASH)) {
return TRUE;
}
parent_window = GTK_WINDOW (gtk_widget_get_toplevel (parent_view));
dialog = nautilus_yes_no_dialog (
_("Are you sure you want to permanently delete "
"all of the items in the trash?"),
_("Delete Trash Contents?"),
_("Empty"),
GNOME_STOCK_BUTTON_CANCEL,
parent_window);
gnome_dialog_set_default (dialog, GNOME_CANCEL);
return gnome_dialog_run (dialog) == GNOME_OK;
}
void
nautilus_file_operations_empty_trash (GtkWidget *parent_view)
{
g_return_if_fail (parent_view != NULL);
/*
* I chose to use a modal confirmation dialog here.
* If we used a modeless dialog, we'd have to do work to
* make sure that no more than one appears on screen at
* a time. That one probably couldn't be parented, because
* otherwise you'd get into weird layer-shifting problems
* selecting "Empty Trash" from one window when there was
* already a modeless "Empty Trash" dialog parented on a
* different window. And if the dialog were not parented, it
* might show up in some weird place since window manager
* placement is unreliable (i.e., sucks). So modal it is.
*/
if (confirm_empty_trash (parent_view)) {
do_empty_trash (parent_view);
}
}
#if !defined (NAUTILUS_OMIT_SELF_CHECK)
void
nautilus_self_check_file_operations (void)
{
/* test the next duplicate name generator */
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_(" (copy)"), 1), _(" (another copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo"), 1), _("foo (copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_(".bashrc"), 1), _(".bashrc (copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_(".foo.txt"), 1), _(".foo (copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo foo"), 1), _("foo foo (copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo.txt"), 1), _("foo (copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo foo.txt"), 1), _("foo foo (copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo foo.txt txt"), 1), _("foo foo (copy).txt txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo...txt"), 1), _("foo.. (copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo..."), 1), _("foo... (copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo. (copy)"), 1), _("foo. (another copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (copy)"), 1), _("foo (another copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (copy).txt"), 1), _("foo (another copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (another copy)"), 1), _("foo (3rd copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (another copy).txt"), 1), _("foo (3rd copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo foo (another copy).txt"), 1), _("foo foo (3rd copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (21st copy)"), 1), _("foo (22nd copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (21st copy).txt"), 1), _("foo (22nd copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (22nd copy)"), 1), _("foo (23rd copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (22nd copy).txt"), 1), _("foo (23rd copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (23rd copy)"), 1), _("foo (24th copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (23rd copy).txt"), 1), _("foo (24th copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (24th copy)"), 1), _("foo (25th copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo (24th copy).txt"), 1), _("foo (25th copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo foo (24th copy)"), 1), _("foo foo (25th copy)"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo foo (24th copy).txt"), 1), _("foo foo (25th copy).txt"));
NAUTILUS_CHECK_STRING_RESULT (get_duplicate_name (_("foo foo (100000000000000th copy).txt"), 1), _("foo foo (copy).txt"));
}
#endif