Logo Search packages:      
Sourcecode: nautilus version File versions  Download package

nautilus-file-operations.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.
   Copyright (C) 2007 Red Hat, 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: Alexander Larsson <alexl@redhat.com>
            Ettore Perazzoli <ettore@gnu.org> 
            Pavel Cisler <pavel@eazel.com> 
 */

#include <config.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <locale.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

#include "nautilus-file-operations.h"

#include "nautilus-debug-log.h"
#include "nautilus-file-changes-queue.h"
#include "nautilus-lib-self-check-functions.h"

#include "nautilus-progress-info.h"

#include <eel/eel-alert-dialog.h>
#include <eel/eel-glib-extensions.h>
#include <eel/eel-pango-extensions.h>
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-stock-dialogs.h>
#include <eel/eel-vfs-extensions.h>

#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>
#include <gio/gio.h>
#include <glib.h>
#include "nautilus-file-changes-queue.h"
#include "nautilus-file-private.h"
#include "nautilus-desktop-icon-file.h"
#include "nautilus-desktop-link-monitor.h"
#include "nautilus-global-preferences.h"
#include "nautilus-link.h"
#include "nautilus-autorun.h"
#include "nautilus-trash-monitor.h"
#include "nautilus-file-utilities.h"

static gboolean confirm_trash_auto_value;

/* TODO: TESTING!!! */

typedef struct {
      GIOSchedulerJob *io_job;      
      GTimer *time;
      GtkWindow *parent_window;
      int screen_num;
      int inhibit_cookie;
      NautilusProgressInfo *progress;
      GCancellable *cancellable;
      GHashTable *skip_files;
      GHashTable *skip_readdir_error;
      gboolean skip_all_error;
      gboolean skip_all_conflict;
      gboolean merge_all;
      gboolean replace_all;
      gboolean delete_all;
} CommonJob;

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

typedef struct {
      CommonJob common;
      GList *files;
      gboolean try_trash;
      gboolean user_cancel;
      NautilusDeleteCallback done_callback;
      gpointer done_callback_data;
} DeleteJob;

typedef struct {
      CommonJob common;
      GFile *dest_dir;
      char *filename;
      gboolean make_dir;
      GFile *src;
      char *src_data;
      int length;
      GdkPoint position;
      gboolean has_position;
      GFile *created_file;
      NautilusCreateCallback done_callback;
      gpointer done_callback_data;
} CreateJob;


typedef struct {
      CommonJob common;
      GList *trash_dirs;
      gboolean should_confirm;
      NautilusOpCallback done_callback;
      gpointer done_callback_data;
} EmptyTrashJob;

typedef struct {
      CommonJob common;
      GFile *file;
      gboolean interactive;
      NautilusOpCallback done_callback;
      gpointer done_callback_data;
} MarkTrustedJob;

typedef struct {
      CommonJob common;
      GFile *file;
      NautilusOpCallback done_callback;
      gpointer done_callback_data;
      guint32 file_permissions;
      guint32 file_mask;
      guint32 dir_permissions;
      guint32 dir_mask;
} SetPermissionsJob;

typedef enum {
      OP_KIND_COPY,
      OP_KIND_MOVE,
      OP_KIND_DELETE,
      OP_KIND_TRASH
} OpKind;

typedef struct {
      int num_files;
      goffset num_bytes;
      int num_files_since_progress;
      OpKind op;
} SourceInfo;

typedef struct {
      int num_files;
      goffset num_bytes;
      OpKind op;
      guint64 last_report_time;
      int last_reported_files_left;
} TransferInfo;

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

#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50

#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 DELETE_ALL _("Delete _All")
#define REPLACE _("_Replace")
#define REPLACE_ALL _("Replace _All")
#define MERGE _("_Merge")
#define MERGE_ALL _("Merge _All")
#define COPY_FORCE _("Copy _Anyway")

static void
mark_desktop_file_trusted (CommonJob *common,
                     GCancellable *cancellable,
                     GFile *file,
                     gboolean interactive);

static gboolean
is_all_button_text (const char *button_text)
{
      g_assert (button_text != NULL);

      return !strcmp (button_text, SKIP_ALL) ||
             !strcmp (button_text, REPLACE_ALL) ||
             !strcmp (button_text, DELETE_ALL) ||
             !strcmp (button_text, MERGE_ALL);
}

static void scan_sources (GList *files,
                    SourceInfo *source_info,
                    CommonJob *job,
                    OpKind kind);


static gboolean empty_trash_job (GIOSchedulerJob *io_job,
                         GCancellable *cancellable,
                         gpointer user_data);

static char * query_fs_type (GFile *file,
                       GCancellable *cancellable);

/* keep in time with format_time()
 *
 * This counts and outputs the number of “time units”
 * formatted and displayed by format_time().
 * For instance, if format_time outputs “3 hours, 4 minutes”
 * it yields 7.
 */
static int
seconds_count_format_time_units (int seconds)
{
      int minutes;
      int hours;

      if (seconds < 0) {
            /* Just to make sure... */
            seconds = 0;
      }

      if (seconds < 60) {
            /* seconds */
            return seconds;
      }

      if (seconds < 60*60) {
            /* minutes */
            minutes = seconds / 60;
            return minutes;
      }

      hours = seconds / (60*60);

      if (seconds < 60*60*4) {
            /* minutes + hours */
            minutes = (seconds - hours * 60 * 60) / 60;
            return minutes + hours;
      }

      return hours;
}

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 / 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) / 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 (ngettext ("approximately %'d hour",
                                "approximately %'d hours",
                                hours), hours);
}

static char *
shorten_utf8_string (const char *base, int reduce_by_num_bytes)
{
      int len;
      char *ret;
      const char *p;
      
      len = strlen (base);
      len -= reduce_by_num_bytes;
      
      if (len <= 0) {
            return NULL;
      }

      ret = g_new (char, len + 1);

      p = base;
      while (len) {
            char *next;
            next = g_utf8_next_char (p);
            if (next - p > len || *next == '\0') {
                  break;
            }
            
            len -= next - p;
            p = next;
      }
      
      if (p - base == 0) {
            g_free (ret);
            return NULL;
      } else {
            memcpy (ret, base, p - base);
            ret[p - base] = '\0';
            return ret;
      }
}

/* Note that we have these two separate functions with separate format
 * strings for ease of localization.
 */

static char *
get_link_name (const char *name, int count, int max_length)
{
      const char *format;
      char *result;
      int unshortened_length;
      gboolean use_count;
      
      g_assert (name != NULL);

      if (count < 0) {
            g_warning ("bad count in get_link_name");
            count = 0;
      }

      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 0:
                  /* duplicate original file name */
                  format = "%s";
                  break;
            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;
            }

            use_count = FALSE;
      } 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;
            }

            use_count = TRUE;
      }

      if (use_count)
            result = g_strdup_printf (format, count, name);
      else
            result = g_strdup_printf (format, name);

      if (max_length > 0 && (unshortened_length = strlen (result)) > max_length) {
            char *new_name;

            new_name = shorten_utf8_string (name, unshortened_length - max_length);
            if (new_name) {
                  g_free (result);

                  if (use_count)
                        result = g_strdup_printf (format, count, new_name);
                  else
                        result = g_strdup_printf (format, new_name);

                  g_assert (strlen (result) <= max_length);
                  g_free (new_name);
            }
      }

      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 x11th copy of a file */
static const char untranslated_x11th_copy_duplicate_tag[] = N_("th copy)");
/* localizers: tag used to detect the x12th copy of a file */
static const char untranslated_x12th_copy_duplicate_tag[] = N_("th copy)");
/* localizers: tag used to detect the x13th copy of a file */
static const char untranslated_x13th_copy_duplicate_tag[] = N_("th 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 X11TH_COPY_DUPLICATE_TAG _(untranslated_x11th_copy_duplicate_tag)
#define X12TH_COPY_DUPLICATE_TAG _(untranslated_x12th_copy_duplicate_tag)
#define X13TH_COPY_DUPLICATE_TAG _(untranslated_x13th_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 x11th file copy */
static const char untranslated_x11th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
/* localizers: appended to x12th file copy */
static const char untranslated_x12th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");
/* localizers: appended to x13th file copy */
static const char untranslated_x13th_copy_duplicate_format[] = N_("%s (%'dth copy)%s");

/* localizers: if in your language there's no difference between 1st, 2nd, 3rd and nth
 * plurals, you can leave the st, nd, rd suffixes out and just make all the translated
 * strings look like "%s (copy %'d)%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 X11TH_COPY_DUPLICATE_FORMAT _(untranslated_x11th_copy_duplicate_format)
#define X12TH_COPY_DUPLICATE_FORMAT _(untranslated_x12th_copy_duplicate_format)
#define X13TH_COPY_DUPLICATE_FORMAT _(untranslated_x13th_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 = strchr (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, X11TH_COPY_DUPLICATE_TAG);

      if (tag == NULL) {
            tag = strstr (name, X12TH_COPY_DUPLICATE_TAG);
      }
      if (tag == NULL) {
            tag = strstr (name, X13TH_COPY_DUPLICATE_TAG);
      }

      if (tag == NULL) {
            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 = g_strdup (name);
      }
}

static char *
make_next_duplicate_name (const char *base, const char *suffix, int count, int max_length)
{
      const char *format;
      char *result;
      int unshortened_length;
      gboolean use_count;

      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;

            }

            use_count = FALSE;
      } 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.
             */

            /* Handle special cases for x11th - x20th.
             */
            switch (count % 100) {
            case 11:
                  format = X11TH_COPY_DUPLICATE_FORMAT;
                  break;
            case 12:
                  format = X12TH_COPY_DUPLICATE_FORMAT;
                  break;
            case 13:
                  format = X13TH_COPY_DUPLICATE_FORMAT;
                  break;
            default:
                  format = NULL;
                  break;
            }

            if (format == NULL) {
                  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;
                  }
            }

            use_count = TRUE;

      }

      if (use_count)
            result = g_strdup_printf (format, base, count, suffix);
      else
            result = g_strdup_printf (format, base, suffix);

      if (max_length > 0 && (unshortened_length = strlen (result)) > max_length) {
            char *new_base;

            new_base = shorten_utf8_string (base, unshortened_length - max_length);
            if (new_base) {
                  g_free (result);

                  if (use_count)
                        result = g_strdup_printf (format, new_base, count, suffix);
                  else
                        result = g_strdup_printf (format, new_base, suffix);

                  g_assert (strlen (result) <= max_length);
                  g_free (new_base);
            }
      }

      return result;
}

static char *
get_duplicate_name (const char *name, int count_increment, int max_length)
{
      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, max_length);

      g_free (name_base);

      return result;
}

static gboolean
has_invalid_xml_char (char *str)
{
      gunichar c;

      while (*str != 0) {
            c = g_utf8_get_char (str);
            /* characters XML permits */
            if (!(c == 0x9 ||
                  c == 0xA ||
                  c == 0xD ||
                  (c >= 0x20 && c <= 0xD7FF) ||
                  (c >= 0xE000 && c <= 0xFFFD) ||
                  (c >= 0x10000 && c <= 0x10FFFF))) {
                  return TRUE;
            }
            str = g_utf8_next_char (str);
      }
      return FALSE;
}


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)
{
      (void) va_arg (*va, GFile *);
}

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

      file = va_arg (va, GFile *);

      info = g_file_query_info (file,
                          G_FILE_ATTRIBUTE_STANDARD_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);
            }
      }

      /* Some chars can't be put in the markup we use for the dialogs... */
      if (has_invalid_xml_char (name)) {
            tmp = name;
            name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
            g_free (tmp);
      }

      /* Finally, if the string is too long, truncate it. */
      if (name != NULL) {
            tmp = name;
            name = eel_str_middle_truncate (tmp, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH);
            g_free (tmp);
      }

      
      return name;
}

static void
custom_basename_skip (va_list *va)
{
      (void) 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_size_for_display (size);
}

static void
custom_size_skip (va_list *va)
{
      (void) 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)
{
      (void) va_arg (*va, int);
}

static char *
custom_mount_to_string (char *format, va_list va)
{
      GMount *mount;

      mount = va_arg (va, GMount *);
      return g_mount_get_name (mount);
}

static void
custom_mount_skip (va_list *va)
{
      (void) va_arg (*va, GMount *);
}


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 },
      { 'V', custom_mount_to_string, custom_mount_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;
}

#define op_job_new(__type, parent_window) ((__type *)(init_common (sizeof(__type), parent_window)))

static gpointer
init_common (gsize job_size,
           GtkWindow *parent_window)
{
      CommonJob *common;
      GdkScreen *screen;

      common = g_malloc0 (job_size);

      if (parent_window) {
            common->parent_window = parent_window;
            eel_add_weak_pointer (&common->parent_window);
      }
      common->progress = nautilus_progress_info_new ();
      common->cancellable = nautilus_progress_info_get_cancellable (common->progress);
      common->time = g_timer_new ();
      common->inhibit_cookie = -1;
      common->screen_num = 0;
      if (parent_window) {
            screen = gtk_widget_get_screen (GTK_WIDGET (parent_window));
            common->screen_num = gdk_screen_get_number (screen);
      }
      
      return common;
}

static void
finalize_common (CommonJob *common)
{
      nautilus_progress_info_finish (common->progress);

      if (common->inhibit_cookie != -1) {
            nautilus_uninhibit_power_manager (common->inhibit_cookie);
      }

      common->inhibit_cookie = -1;
      g_timer_destroy (common->time);
      eel_remove_weak_pointer (&common->parent_window);
      if (common->skip_files) {
            g_hash_table_destroy (common->skip_files);
      }
      if (common->skip_readdir_error) {
            g_hash_table_destroy (common->skip_readdir_error);
      }
      g_object_unref (common->progress);
      g_object_unref (common->cancellable);
      g_free (common);
}

static void
skip_file (CommonJob *common,
         GFile *file)
{
      if (common->skip_files == NULL) {
            common->skip_files =
                  g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
      }

      g_hash_table_insert (common->skip_files, g_object_ref (file), file);
}

static void
skip_readdir_error (CommonJob *common,
                GFile *dir)
{
      if (common->skip_readdir_error == NULL) {
            common->skip_readdir_error =
                  g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
      }

      g_hash_table_insert (common->skip_readdir_error, g_object_ref (dir), dir);
}

static gboolean
should_skip_file (CommonJob *common,
              GFile *file)
{
      if (common->skip_files != NULL) {
            return g_hash_table_lookup (common->skip_files, file) != NULL;
      }
      return FALSE;
}

static gboolean
should_skip_readdir_error (CommonJob *common,
                     GFile *dir)
{
      if (common->skip_readdir_error != NULL) {
            return g_hash_table_lookup (common->skip_readdir_error, dir) != NULL;
      }
      return FALSE;
}

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

static gboolean
can_delete_without_confirm (GFile *file)
{
      if (g_file_has_uri_scheme (file, "burn") ||
          g_file_has_uri_scheme (file, "x-nautilus-desktop")) {
            return TRUE;
      }

      return FALSE;
}

static gboolean
can_delete_files_without_confirm (GList *files)
{
      g_assert (files != NULL);

      while (files != NULL) {
            if (!can_delete_without_confirm (files->data)) {
                  return FALSE;
            }

            files = files->next;
      }

      return TRUE;
}

typedef struct {
      GtkWindow **parent_window;
      gboolean ignore_close_box;
      GtkMessageType message_type;
      const char *primary_text;
      const char *secondary_text;
      const char *details_text;
      const char **button_titles;
      gboolean show_all;
      
      int result;
} RunSimpleDialogData;

static gboolean
do_run_simple_dialog (gpointer _data)
{
      RunSimpleDialogData *data = _data;
      const char *button_title;
        GtkWidget *dialog;
      int result;
      int response_id;

      /* Create the dialog. */
      dialog = eel_alert_dialog_new (*data->parent_window, 
                                     0,
                                     data->message_type,
                                     GTK_BUTTONS_NONE,
                                     data->primary_text,
                                     data->secondary_text);

      for (response_id = 0;
           data->button_titles[response_id] != NULL;
           response_id++) {
            button_title = data->button_titles[response_id];
            if (!data->show_all && is_all_button_text (button_title)) {
                  continue;
            }

            gtk_dialog_add_button (GTK_DIALOG (dialog), button_title, response_id);
            gtk_dialog_set_default_response (GTK_DIALOG (dialog), response_id);
      }

      if (data->details_text) {
            eel_alert_dialog_set_details_label (EEL_ALERT_DIALOG (dialog),
                                        data->details_text);
      }
      
      /* Run it. */
        gtk_widget_show (dialog);
        result = gtk_dialog_run (GTK_DIALOG (dialog));
      
      while ((result == GTK_RESPONSE_NONE || result == GTK_RESPONSE_DELETE_EVENT) && data->ignore_close_box) {
            gtk_widget_show (GTK_WIDGET (dialog));
            result = gtk_dialog_run (GTK_DIALOG (dialog));
      }
      
      gtk_object_destroy (GTK_OBJECT (dialog));

      data->result = result;
      
      return FALSE;
}

/* NOTE: This frees the primary / secondary strings, in order to
   avoid doing that everywhere. So, make sure they are strduped */

static int
run_simple_dialog_va (CommonJob *job,
                  gboolean ignore_close_box,
                  GtkMessageType message_type,
                  char *primary_text,
                  char *secondary_text,
                  const char *details_text,
                  gboolean show_all,
                  va_list varargs)
{
      RunSimpleDialogData *data;
      int res;
      const char *button_title;
      GPtrArray *ptr_array;

      g_timer_stop (job->time);
      
      data = g_new0 (RunSimpleDialogData, 1);
      data->parent_window = &job->parent_window;
      data->ignore_close_box = ignore_close_box;
      data->message_type = message_type;
      data->primary_text = primary_text;
      data->secondary_text = secondary_text;
      data->details_text = details_text;
      data->show_all = show_all;

      ptr_array = g_ptr_array_new ();
      while ((button_title = va_arg (varargs, const char *)) != NULL) {
            g_ptr_array_add (ptr_array, (char *)button_title);
      }
      g_ptr_array_add (ptr_array, NULL);
      data->button_titles = (const char **)g_ptr_array_free (ptr_array, FALSE);

      nautilus_progress_info_pause (job->progress);
      g_io_scheduler_job_send_to_mainloop (job->io_job,
                                   do_run_simple_dialog,
                                   data,
                                   NULL);
      nautilus_progress_info_resume (job->progress);
      res = data->result;

      g_free (data->button_titles);
      g_free (data);

      g_timer_continue (job->time);

      g_free (primary_text);
      g_free (secondary_text);
      
      return res;
}

#if 0 /* Not used at the moment */
static int
run_simple_dialog (CommonJob *job,
               gboolean ignore_close_box,
               GtkMessageType message_type,
               char *primary_text,
               char *secondary_text,
               const char *details_text,
               ...)
{
      va_list varargs;
      int res;

      va_start (varargs, details_text);
      res = run_simple_dialog_va (job,
                            ignore_close_box,
                            message_type,
                            primary_text,
                            secondary_text,
                            details_text,
                            varargs);
      va_end (varargs);
      return res;
}
#endif

static int
run_error (CommonJob *job,
         char *primary_text,
         char *secondary_text,
         const char *details_text,
         gboolean show_all,
         ...)
{
      va_list varargs;
      int res;

      va_start (varargs, show_all);
      res = run_simple_dialog_va (job,
                            FALSE,
                            GTK_MESSAGE_ERROR,
                            primary_text,
                            secondary_text,
                            details_text,
                            show_all,
                            varargs);
      va_end (varargs);
      return res;
}

static int
run_warning (CommonJob *job,
           char *primary_text,
           char *secondary_text,
           const char *details_text,
           gboolean show_all,
           ...)
{
      va_list varargs;
      int res;

      va_start (varargs, show_all);
      res = run_simple_dialog_va (job,
                            FALSE,
                            GTK_MESSAGE_WARNING,
                            primary_text,
                            secondary_text,
                            details_text,
                            show_all,
                            varargs);
      va_end (varargs);
      return res;
}

static int
run_question (CommonJob *job,
            char *primary_text,
            char *secondary_text,
            const char *details_text,
            gboolean show_all,
            ...)
{
      va_list varargs;
      int res;

      va_start (varargs, show_all);
      res = run_simple_dialog_va (job,
                            FALSE,
                            GTK_MESSAGE_QUESTION,
                            primary_text,
                            secondary_text,
                            details_text,
                            show_all,
                            varargs);
      va_end (varargs);
      return res;
}

static void
inhibit_power_manager (CommonJob *job, const char *message)
{
      job->inhibit_cookie = nautilus_inhibit_power_manager (message);
}

static void
abort_job (CommonJob *job)
{
      g_cancellable_cancel (job->cancellable);
      
}

static gboolean
job_aborted (CommonJob *job)
{
      return g_cancellable_is_cancelled (job->cancellable);
}

static gboolean
confirm_delete_from_trash (CommonJob *job,
                     GList *files)
{
      char *prompt;
      int file_count;
      int response;

      /* Just Say Yes if the preference says not to confirm. */
      if (!confirm_trash_auto_value) {
            return TRUE;
      }

      file_count = g_list_length (files);
      g_assert (file_count > 0);
      
      if (file_count == 1) {
            prompt = f (_("Are you sure you want to permanently delete \"%B\" "
                                  "from the trash?"), files->data);
      } else {
            prompt = f (ngettext("Are you sure you want to permanently delete "
                             "the %'d selected item from the trash?",
                             "Are you sure you want to permanently delete "
                             "the %'d selected items from the trash?",
                             file_count), 
                      file_count);
      }

      response = run_warning (job,
                        prompt,
                        f (_("If you delete an item, it will be permanently lost.")),
                        NULL,
                        FALSE,
                        GTK_STOCK_CANCEL, GTK_STOCK_DELETE,
                        NULL);
      
      return (response == 1);
}

static gboolean
confirm_empty_trash (CommonJob *job)
{
      char *prompt;
      int response;

      /* Just Say Yes if the preference says not to confirm. */
      if (!confirm_trash_auto_value) {
            return TRUE;
      }

      prompt = f (_("Empty all of the items from the trash?"));

      response = run_warning (job,
                        prompt,
                        f(_("If you choose to empty the trash, all items "
                            "in it will be permanently lost. Please note "
                            "that you can also delete them separately.")),
                        NULL,
                        FALSE,
                        GTK_STOCK_CANCEL, _("Empty _Trash"),
                        NULL);

      return (response == 1);
}

static gboolean
confirm_delete_directly (CommonJob *job,
                   GList *files)
{
      char *prompt;
      int file_count;
      int response;

      /* Just Say Yes if the preference says not to confirm. */
      if (!confirm_trash_auto_value) {
            return TRUE;
      }

      file_count = g_list_length (files);
      g_assert (file_count > 0);

      if (can_delete_files_without_confirm (files)) {
            return TRUE;
      }

      if (file_count == 1) {
            prompt = f (_("Are you sure you want to permanently delete \"%B\"?"), 
                      files->data);
      } else {
            prompt = f (ngettext("Are you sure you want to permanently delete "
                             "the %'d selected item?",
                             "Are you sure you want to permanently delete "
                             "the %'d selected items?", file_count),
                      file_count);
      }
      
      response = run_warning (job, 
                        prompt,
                        f (_("If you delete an item, it will be permanently lost.")),
                        NULL,
                        FALSE,
                        GTK_STOCK_CANCEL, GTK_STOCK_DELETE,
                        NULL);

      return response == 1;
}

static void
report_delete_progress (CommonJob *job,
                  SourceInfo *source_info,
                  TransferInfo *transfer_info)
{
      int files_left;
      double elapsed, transfer_rate;
      int remaining_time;
      guint64 now;
      char *files_left_s;

      now = g_thread_gettime ();
      if (transfer_info->last_report_time != 0 &&
          ABS ((gint64)(transfer_info->last_report_time - now)) < 100 * NSEC_PER_MSEC) {
            return;
      }
      transfer_info->last_report_time = now;
      
      files_left = source_info->num_files - transfer_info->num_files;

      /* Races and whatnot could cause this to be negative... */
      if (files_left < 0) {
            files_left = 1;
      }

      files_left_s = f (ngettext ("%'d file left to delete",
                            "%'d files left to delete",
                            files_left),
                    files_left);

      nautilus_progress_info_take_status (job->progress,
                                  f (_("Deleting files")));

      elapsed = g_timer_elapsed (job->time, NULL);
      if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE) {

            nautilus_progress_info_set_details (job->progress, files_left_s);
      } else {
            char *details, *time_left_s;
            transfer_rate = transfer_info->num_files / elapsed;
            remaining_time = files_left / transfer_rate;

            /* To translators: %T will expand to a time like "2 minutes".
             * The singular/plural form will be used depending on the remaining time (i.e. the %T argument).
             */
            time_left_s = f (ngettext ("%T left",
                                 "%T left",
                                 seconds_count_format_time_units (remaining_time)),
                         remaining_time);

            details = g_strconcat (files_left_s, "\xE2\x80\x94", time_left_s, NULL);
            nautilus_progress_info_take_details (job->progress, details);

            g_free (time_left_s);
      }

      g_free (files_left_s);

      if (source_info->num_files != 0) {
            nautilus_progress_info_set_progress (job->progress, transfer_info->num_files, source_info->num_files);
      }
}

static void delete_file (CommonJob *job, GFile *file,
                   gboolean *skipped_file,
                   SourceInfo *source_info,
                   TransferInfo *transfer_info,
                   gboolean toplevel);

static void
delete_dir (CommonJob *job, GFile *dir,
          gboolean *skipped_file,
          SourceInfo *source_info,
          TransferInfo *transfer_info,
          gboolean toplevel)
{
      GFileInfo *info;
      GError *error;
      GFile *file;
      GFileEnumerator *enumerator;
      char *primary, *secondary, *details;
      int response;
      gboolean skip_error;
      gboolean local_skipped_file;

      local_skipped_file = FALSE;
      
      skip_error = should_skip_readdir_error (job, dir);
 retry:
      error = NULL;
      enumerator = g_file_enumerate_children (dir,
                                    G_FILE_ATTRIBUTE_STANDARD_NAME,
                                    G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                    job->cancellable,
                                    &error);
      if (enumerator) {
            error = NULL;
            
            while (!job_aborted (job) &&
                   (info = g_file_enumerator_next_file (enumerator, job->cancellable, skip_error?NULL:&error)) != NULL) {
                  file = g_file_get_child (dir,
                                     g_file_info_get_name (info));
                  delete_file (job, file, &local_skipped_file, source_info, transfer_info, FALSE);
                  g_object_unref (file);
                  g_object_unref (info);
            }
            g_file_enumerator_close (enumerator, job->cancellable, NULL);
            g_object_unref (enumerator);
            
            if (error && IS_IO_ERROR (error, CANCELLED)) {
                  g_error_free (error);
            } else if (error) {
                  primary = f (_("Error while deleting."));
                  details = NULL;
                  
                  if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
                        secondary = f (_("Files in the folder \"%B\" cannot be deleted because you do "
                                     "not have permissions to see them."), dir);
                  } else {
                        secondary = f (_("There was an error getting information about the files in the folder \"%B\"."), dir);
                        details = error->message;
                  }
                  
                  response = run_warning (job,
                                    primary,
                                    secondary,
                                    details,
                                    FALSE,
                                    GTK_STOCK_CANCEL, _("_Skip files"),
                                    NULL);
                  
                  g_error_free (error);
                  
                  if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                        abort_job (job);
                  } else if (response == 1) {
                        /* Skip: Do Nothing */
                        local_skipped_file = TRUE;
                  } else {
                        g_assert_not_reached ();
                  }
            }
            
      } else if (IS_IO_ERROR (error, CANCELLED)) {
            g_error_free (error);
      } else {
            primary = f (_("Error while deleting."));
            details = NULL;
            if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
                  secondary = f (_("The folder \"%B\" cannot be deleted because you do not have "
                               "permissions to read it."), dir);
            } else {
                  secondary = f (_("There was an error reading the folder \"%B\"."), dir);
                  details = error->message;
            }
            
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              FALSE,
                              GTK_STOCK_CANCEL, SKIP, RETRY,
                              NULL);

            g_error_free (error);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) {
                  /* Skip: Do Nothing  */
                  local_skipped_file = TRUE;
            } else if (response == 2) {
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }
      }

      if (!job_aborted (job) &&
          /* Don't delete dir if there was a skipped file */
          !local_skipped_file) {
            if (!g_file_delete (dir, job->cancellable, &error)) {
                  if (job->skip_all_error) {
                        goto skip;
                  }
                  primary = f (_("Error while deleting."));
                  secondary = f (_("Could not remove the folder %B."), dir);
                  details = error->message;
                  
                  response = run_warning (job,
                                    primary,
                                    secondary,
                                    details,
                                    (source_info->num_files - transfer_info->num_files) > 1,
                                    GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                                    NULL);
                  
                  if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                        abort_job (job);
                  } else if (response == 1) { /* skip all */
                        job->skip_all_error = TRUE;
                        local_skipped_file = TRUE;
                  } else if (response == 2) { /* skip */
                        local_skipped_file = TRUE;
                  } else {
                        g_assert_not_reached ();
                  }
                  
            skip:
                  g_error_free (error);
            } else {
                  nautilus_file_changes_queue_file_removed (dir);
                  transfer_info->num_files ++;
                  report_delete_progress (job, source_info, transfer_info);
                  return;
            }
      }

      if (local_skipped_file) {
            *skipped_file = TRUE;
      }
}

static void
delete_file (CommonJob *job, GFile *file,
           gboolean *skipped_file,
           SourceInfo *source_info,
           TransferInfo *transfer_info,
           gboolean toplevel)
{
      GError *error;
      char *primary, *secondary, *details;
      int response;

      if (should_skip_file (job, file)) {
            *skipped_file = TRUE;
            return;
      }
      
      error = NULL;
      if (g_file_delete (file, job->cancellable, &error)) {
            nautilus_file_changes_queue_file_removed (file);
            transfer_info->num_files ++;
            report_delete_progress (job, source_info, transfer_info);
            return;
      }

      if (IS_IO_ERROR (error, NOT_EMPTY)) {
            g_error_free (error);
            delete_dir (job, file,
                      skipped_file,
                      source_info, transfer_info,
                      toplevel);
            return;
            
      } else if (IS_IO_ERROR (error, CANCELLED)) {
            g_error_free (error);
            
      } else {
            if (job->skip_all_error) {
                  goto skip;
            }
            primary = f (_("Error while deleting."));
            secondary = f (_("There was an error deleting %B."), file);
            details = error->message;
            
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              (source_info->num_files - transfer_info->num_files) > 1,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                              NULL);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) { /* skip all */
                  job->skip_all_error = TRUE;
            } else if (response == 2) { /* skip */
                  /* do nothing */
            } else {
                  g_assert_not_reached ();
            }
      skip:
            g_error_free (error);
      }
      
      *skipped_file = TRUE;
}

static void
delete_files (CommonJob *job, GList *files, int *files_skipped)
{
      GList *l;
      GFile *file;
      SourceInfo source_info;
      TransferInfo transfer_info;
      gboolean skipped_file;
      
      if (job_aborted (job)) {
            return;
      }

      scan_sources (files,
                  &source_info,
                  job,
                  OP_KIND_DELETE);
      if (job_aborted (job)) {
            return;
      }

      g_timer_start (job->time);
      
      memset (&transfer_info, 0, sizeof (transfer_info));
      report_delete_progress (job, &source_info, &transfer_info);
      
      for (l = files;
           l != NULL && !job_aborted (job);
           l = l->next) {
            file = l->data;

            skipped_file = FALSE;
            delete_file (job, file,
                       &skipped_file,
                       &source_info, &transfer_info,
                       TRUE);
            if (skipped_file) {
                  (*files_skipped)++;
            }
      }
}

static void
report_trash_progress (CommonJob *job,
                   int files_trashed,
                   int total_files)
{
      int files_left;
      char *s;

      files_left = total_files - files_trashed;

      nautilus_progress_info_take_status (job->progress,
                                  f (_("Moving files to trash")));

      s = f (ngettext ("%'d file left to trash",
                   "%'d files left to trash",
                   files_left),
             files_left);
      nautilus_progress_info_take_details (job->progress, s);

      if (total_files != 0) {
            nautilus_progress_info_set_progress (job->progress, files_trashed, total_files);
      }
}


static void
trash_files (CommonJob *job, GList *files, int *files_skipped)
{
      GList *l;
      GFile *file;
      GList *to_delete;
      GError *error;
      int total_files, files_trashed;
      char *primary, *secondary, *details;
      int response;

      if (job_aborted (job)) {
            return;
      }

      total_files = g_list_length (files);
      files_trashed = 0;

      report_trash_progress (job, files_trashed, total_files);

      to_delete = NULL;
      for (l = files;
           l != NULL && !job_aborted (job);
           l = l->next) {
            file = l->data;

            error = NULL;
            if (!g_file_trash (file, job->cancellable, &error)) {
                  if (job->skip_all_error) {
                        (*files_skipped)++;
                        goto skip;
                  }

                  if (job->delete_all) {
                        to_delete = g_list_prepend (to_delete, file);
                        goto skip;
                  }

                  primary = f (_("Cannot move file to trash, do you want to delete immediately?"));
                  secondary = f (_("The file \"%B\" cannot be moved to the trash."), file);
                  details = NULL;
                  if (!IS_IO_ERROR (error, NOT_SUPPORTED)) {
                        details = error->message;
                  }

                  response = run_question (job,
                                     primary,
                                     secondary,
                                     details,
                                     (total_files - files_trashed) > 1,
                                     GTK_STOCK_CANCEL, SKIP_ALL, SKIP, DELETE_ALL, GTK_STOCK_DELETE,
                                     NULL);

                  if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                        ((DeleteJob *) job)->user_cancel = TRUE;                    
                        abort_job (job);
                  } else if (response == 1) { /* skip all */
                        (*files_skipped)++;
                        job->skip_all_error = TRUE;
                  } else if (response == 2) { /* skip */
                        (*files_skipped)++;
                  } else if (response == 3) { /* delete all */
                        to_delete = g_list_prepend (to_delete, file);
                        job->delete_all = TRUE;
                  } else if (response == 4) { /* delete */
                        to_delete = g_list_prepend (to_delete, file);
                  }

            skip:
                  g_error_free (error);
                  total_files--;
            } else {
                  nautilus_file_changes_queue_file_removed (file);
                  
                  files_trashed++;
                  report_trash_progress (job, files_trashed, total_files);
            }
      }

      if (to_delete) {
            to_delete = g_list_reverse (to_delete);
            delete_files (job, to_delete, files_skipped);
            g_list_free (to_delete);
      }
}

static gboolean
delete_job_done (gpointer user_data)
{
      DeleteJob *job;
      GHashTable *debuting_uris;

      job = user_data;
      
      eel_g_object_list_free (job->files);

      if (job->done_callback) {
            debuting_uris = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
            job->done_callback (debuting_uris, job->user_cancel, job->done_callback_data);
            g_hash_table_unref (debuting_uris);
      }
      
      finalize_common ((CommonJob *)job);

      nautilus_file_changes_consume_changes (TRUE);

      return FALSE;
}

static gboolean
delete_job (GIOSchedulerJob *io_job,
          GCancellable *cancellable,
          gpointer user_data)
{
      DeleteJob *job = user_data;
      GList *to_trash_files;
      GList *to_delete_files;
      GList *l;
      GFile *file;
      gboolean confirmed;
      CommonJob *common;
      gboolean must_confirm_delete_in_trash;
      gboolean must_confirm_delete;
      int files_skipped;

      common = (CommonJob *)job;
      common->io_job = io_job;

      nautilus_progress_info_start (job->common.progress);
      
      to_trash_files = NULL;
      to_delete_files = NULL;

      must_confirm_delete_in_trash = FALSE;
      must_confirm_delete = FALSE;
      files_skipped = 0;
      
      for (l = job->files; l != NULL; l = l->next) {
            file = l->data;
            
            if (job->try_trash &&
                g_file_has_uri_scheme (file, "trash")) {
                  must_confirm_delete_in_trash = TRUE;
                  to_delete_files = g_list_prepend (to_delete_files, file);
            } else if (can_delete_without_confirm (file)) {
                  to_delete_files = g_list_prepend (to_delete_files, file);
            } else {
                  if (job->try_trash) {
                        to_trash_files = g_list_prepend (to_trash_files, file);
                  } else {
                        must_confirm_delete = TRUE;
                        to_delete_files = g_list_prepend (to_delete_files, file);
                  }
            }
      }
      
      if (to_delete_files != NULL) {
            to_delete_files = g_list_reverse (to_delete_files);
            confirmed = TRUE;
            if (must_confirm_delete_in_trash) {
                  confirmed = confirm_delete_from_trash (common, to_delete_files);
            } else if (must_confirm_delete) {
                  confirmed = confirm_delete_directly (common, to_delete_files);
            }
            if (confirmed) {
                  delete_files (common, to_delete_files, &files_skipped);
            } else {
                  job->user_cancel = TRUE;
            }
      }
      
      if (to_trash_files != NULL) {
            to_trash_files = g_list_reverse (to_trash_files);
            
            trash_files (common, to_trash_files, &files_skipped);
      }
      
      g_list_free (to_trash_files);
      g_list_free (to_delete_files);
      
      if (files_skipped == g_list_length (job->files)) {
            /* User has skipped all files, report user cancel */
            job->user_cancel = TRUE;
      }

      g_io_scheduler_job_send_to_mainloop_async (io_job,
                                       delete_job_done,
                                       job,
                                       NULL);

      return FALSE;
}

static void
trash_or_delete_internal (GList                  *files,
                    GtkWindow              *parent_window,
                    gboolean                try_trash,                    
                    NautilusDeleteCallback  done_callback,
                    gpointer                done_callback_data)
{
      DeleteJob *job;

      setup_autos ();

      /* TODO: special case desktop icon link files ... */

      job = op_job_new (DeleteJob, parent_window);
      job->files = eel_g_object_list_copy (files);
      job->try_trash = try_trash;
      job->user_cancel = FALSE;
      job->done_callback = done_callback;
      job->done_callback_data = done_callback_data;

      if (try_trash) {
            inhibit_power_manager ((CommonJob *)job, _("Trashing Files"));
      } else {
            inhibit_power_manager ((CommonJob *)job, _("Deleting Files"));
      }
      
      g_io_scheduler_push_job (delete_job,
                     job,
                     NULL,
                     0,
                     NULL);
}

void
nautilus_file_operations_trash_or_delete (GList                  *files,
                                GtkWindow              *parent_window,
                                NautilusDeleteCallback  done_callback,
                                gpointer                done_callback_data)
{
      trash_or_delete_internal (files, parent_window,
                          TRUE,
                          done_callback,  done_callback_data);
}

void
nautilus_file_operations_delete (GList                  *files, 
                         GtkWindow              *parent_window,
                         NautilusDeleteCallback  done_callback,
                         gpointer                done_callback_data)
{
      trash_or_delete_internal (files, parent_window,
                          FALSE,                  
                          done_callback,  done_callback_data);
}



typedef struct {
      gboolean eject;
      GMount *mount;
      GtkWindow *parent_window;
      NautilusUnmountCallback callback;
      gpointer callback_data;
} UnmountData;

static void
unmount_mount_callback (GObject *source_object,
                  GAsyncResult *res,
                  gpointer user_data)
{
      UnmountData *data = user_data;
      GError *error;
      char *primary;
      gboolean unmounted;

      error = NULL;
      if (data->eject) {
            unmounted = g_mount_eject_with_operation_finish (G_MOUNT (source_object),
                                                 res, &error);
      } else {
            unmounted = g_mount_unmount_with_operation_finish (G_MOUNT (source_object),
                                                   res, &error);
      }
      
      if (! unmounted) {
            if (error->code != G_IO_ERROR_FAILED_HANDLED) {
                  if (data->eject) {
                        primary = f (_("Unable to eject %V"), source_object);
                  } else {
                        primary = f (_("Unable to unmount %V"), source_object);
                  }
                  eel_show_error_dialog (primary,
                                     error->message,
                                     data->parent_window);
                  g_free (primary);
            }
      }

      if (data->callback) {
            data->callback (data->callback_data);
      }

      if (error != NULL) {
            g_error_free (error);
      }
      
      eel_remove_weak_pointer (&data->parent_window);
      g_object_unref (data->mount);
      g_free (data);
}

static void
do_unmount (UnmountData *data)
{
      GMountOperation *mount_op;

      mount_op = gtk_mount_operation_new (data->parent_window);
      if (data->eject) {
            g_mount_eject_with_operation (data->mount,
                                    0,
                                    mount_op,
                                    NULL,
                                    unmount_mount_callback,
                                    data);
      } else {
            g_mount_unmount_with_operation (data->mount,
                                    0,
                                    mount_op,
                                    NULL,
                                    unmount_mount_callback,
                                    data);
      }
      g_object_unref (mount_op);
}

static gboolean
dir_has_files (GFile *dir)
{
      GFileEnumerator *enumerator;
      gboolean res;
      GFileInfo *file_info;

      res = FALSE;
      
      enumerator = g_file_enumerate_children (dir,
                                    G_FILE_ATTRIBUTE_STANDARD_NAME,
                                    0,
                                    NULL, NULL);
      if (enumerator) {
            file_info = g_file_enumerator_next_file (enumerator, NULL, NULL);
            if (file_info != NULL) {
                  res = TRUE;
                  g_object_unref (file_info);
            }
            
            g_file_enumerator_close (enumerator, NULL, NULL);
            g_object_unref (enumerator);
      }
      

      return res;
}

static GList *
get_trash_dirs_for_mount (GMount *mount)
{
      GFile *root;
      GFile *trash;
      char *relpath;
      GList *list;

      root = g_mount_get_root (mount);
      if (root == NULL) {
            return NULL;
      }

      list = NULL;
      
      if (g_file_is_native (root)) {
            relpath = g_strdup_printf (".Trash/%d", getuid ());
            trash = g_file_resolve_relative_path (root, relpath);
            g_free (relpath);

            list = g_list_prepend (list, g_file_get_child (trash, "files"));
            list = g_list_prepend (list, g_file_get_child (trash, "info"));
            
            g_object_unref (trash);
            
            relpath = g_strdup_printf (".Trash-%d", getuid ());
            trash = g_file_get_child (root, relpath);
            g_free (relpath);

            list = g_list_prepend (list, g_file_get_child (trash, "files"));
            list = g_list_prepend (list, g_file_get_child (trash, "info"));
            
            g_object_unref (trash);
      }
      
      g_object_unref (root);
      
      return list;
}

static gboolean
has_trash_files (GMount *mount)
{
      GList *dirs, *l;
      GFile *dir;
      gboolean res;

      dirs = get_trash_dirs_for_mount (mount);

      res = FALSE;

      for (l = dirs; l != NULL; l = l->next) {
            dir = l->data;

            if (dir_has_files (dir)) {
                  res = TRUE;
                  break;
            }
      }

      eel_g_object_list_free (dirs);
      
      return res;
}


static gint
prompt_empty_trash (GtkWindow *parent_window)
{
      gint                    result;
      GtkWidget               *dialog;
      GdkScreen               *screen;

      screen = NULL;
      if (parent_window != NULL) {
            screen = gtk_widget_get_screen (GTK_WIDGET (parent_window));
      }

      /* Do we need to be modal ? */
      dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
                               GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
                               _("Do you want to empty the trash before you unmount?"));
      gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
                                      _("In order to regain the "
                                        "free space on this volume "
                                        "the trash must be emptied. "
                                        "All trashed items on the volume "
                                        "will be permanently lost."));
      gtk_dialog_add_buttons (GTK_DIALOG (dialog), 
                              _("Do _not Empty Trash"), GTK_RESPONSE_REJECT, 
                              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, 
                              _("Empty _Trash"), GTK_RESPONSE_ACCEPT, NULL);
      gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
      gtk_window_set_title (GTK_WINDOW (dialog), ""); /* as per HIG */
      gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), TRUE);
      if (screen) {
            gtk_window_set_screen (GTK_WINDOW (dialog), screen);
      }
      atk_object_set_role (gtk_widget_get_accessible (dialog), ATK_ROLE_ALERT);
      gtk_window_set_wmclass (GTK_WINDOW (dialog), "empty_trash",
                        "Nautilus");
      
      /* Make transient for the window group */
      gtk_widget_realize (dialog);
      if (screen != NULL) {
            gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
                                          gdk_screen_get_root_window (screen));
      }
      
      result = gtk_dialog_run (GTK_DIALOG (dialog));
      gtk_widget_destroy (dialog);
      return result;
}

void
nautilus_file_operations_unmount_mount_full (GtkWindow                      *parent_window,
                                   GMount                         *mount,
                                   gboolean                        eject,
                                   gboolean                        check_trash,
                                   NautilusUnmountCallback         callback,
                                   gpointer                        callback_data)
{
      UnmountData *data;
      int response;

      data = g_new0 (UnmountData, 1);
      data->callback = callback;
      data->callback_data = callback_data;
      if (parent_window) {
            data->parent_window = parent_window;
            eel_add_weak_pointer (&data->parent_window);
            
      }
      data->eject = eject;
      data->mount = g_object_ref (mount);

      if (check_trash && has_trash_files (mount)) {
            response = prompt_empty_trash (parent_window);

            if (response == GTK_RESPONSE_ACCEPT) {
                  EmptyTrashJob *job;
                  
                  job = op_job_new (EmptyTrashJob, parent_window);
                  job->should_confirm = FALSE;
                  job->trash_dirs = get_trash_dirs_for_mount (mount);
                  job->done_callback = (NautilusOpCallback)do_unmount;
                  job->done_callback_data = data;
                  g_io_scheduler_push_job (empty_trash_job,
                                 job,
                                 NULL,
                                 0,
                                 NULL);
                  return;
            } else if (response == GTK_RESPONSE_CANCEL) {
                  if (callback) {
                        callback (callback_data);
                  }
                  eel_remove_weak_pointer (&data->parent_window);
                  g_object_unref (data->mount);
                  g_free (data);
                  return;
            }
      }
      
      do_unmount (data);
}

void
nautilus_file_operations_unmount_mount (GtkWindow                      *parent_window,
                              GMount                         *mount,
                              gboolean                        eject,
                              gboolean                        check_trash)
{
      nautilus_file_operations_unmount_mount_full (parent_window, mount, eject,
                                         check_trash, NULL, NULL);
}

static void
mount_callback_data_notify (gpointer data,
                      GObject *object)
{
      GMountOperation *mount_op;

      mount_op = G_MOUNT_OPERATION (data);
      g_object_set_data (G_OBJECT (mount_op), "mount-callback", NULL);
      g_object_set_data (G_OBJECT (mount_op), "mount-callback-data", NULL);
}

static void
volume_mount_cb (GObject *source_object,
             GAsyncResult *res,
             gpointer user_data)
{
      NautilusMountCallback mount_callback;
      GObject *mount_callback_data_object;
      GMountOperation *mount_op = user_data;
      GError *error;
      char *primary;
      char *name;

      error = NULL;
      nautilus_allow_autorun_for_volume_finish (G_VOLUME (source_object));
      if (!g_volume_mount_finish (G_VOLUME (source_object), res, &error)) {
            if (error->code != G_IO_ERROR_FAILED_HANDLED) {
                  name = g_volume_get_name (G_VOLUME (source_object));
                  primary = g_strdup_printf (_("Unable to mount %s"), name);
                  g_free (name);
                  eel_show_error_dialog (primary,
                                     error->message,
                                     NULL);
                  g_free (primary);
            }
            g_error_free (error);
      }

      mount_callback = (NautilusMountCallback)
            g_object_get_data (G_OBJECT (mount_op), "mount-callback");
      mount_callback_data_object =
            g_object_get_data (G_OBJECT (mount_op), "mount-callback-data");

      if (mount_callback != NULL) {
            (* mount_callback) (G_VOLUME (source_object),
                            mount_callback_data_object);

            if (mount_callback_data_object != NULL) {
                  g_object_weak_unref (mount_callback_data_object,
                                   mount_callback_data_notify,
                                   mount_op);
            }
      }

      g_object_unref (mount_op);
}


void
nautilus_file_operations_mount_volume (GtkWindow *parent_window,
                               GVolume *volume,
                               gboolean allow_autorun)
{
      nautilus_file_operations_mount_volume_full (parent_window, volume,
                                        allow_autorun, NULL, NULL);
}

void
nautilus_file_operations_mount_volume_full (GtkWindow *parent_window,
                                  GVolume *volume,
                                  gboolean allow_autorun,
                                  NautilusMountCallback mount_callback,
                                  GObject *mount_callback_data_object)
{
      GMountOperation *mount_op;

      mount_op = gtk_mount_operation_new (parent_window);
      g_object_set_data (G_OBJECT (mount_op),
                     "mount-callback",
                     mount_callback);

      if (mount_callback != NULL &&
          mount_callback_data_object != NULL) {
            g_object_weak_ref (mount_callback_data_object,
                           mount_callback_data_notify,
                           mount_op);
      }
      g_object_set_data (G_OBJECT (mount_op),
                     "mount-callback-data",
                     mount_callback_data_object);

      if (allow_autorun)
            nautilus_allow_autorun_for_volume (volume);
      g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, mount_op);
}

static void
report_count_progress (CommonJob *job,
                   SourceInfo *source_info)
{
      char *s;

      switch (source_info->op) {
      default:
      case OP_KIND_COPY:
            s = f (ngettext("Preparing to copy %'d file (%S)",
                            "Preparing to copy %'d files (%S)",
                            source_info->num_files),
                   source_info->num_files, source_info->num_bytes);
            break;
      case OP_KIND_MOVE:
            s = f (ngettext("Preparing to move %'d file (%S)",
                            "Preparing to move %'d files (%S)",
                            source_info->num_files),
                   source_info->num_files, source_info->num_bytes);
            break;
      case OP_KIND_DELETE:
            s = f (ngettext("Preparing to delete %'d file (%S)",
                            "Preparing to delete %'d files (%S)",
                            source_info->num_files),
                   source_info->num_files, source_info->num_bytes);
            break;
      case OP_KIND_TRASH:
            s = f (ngettext("Preparing to trash %'d file",
                            "Preparing to trash %'d files",
                            source_info->num_files),
                   source_info->num_files);
            break;
      } 

      nautilus_progress_info_take_details (job->progress, s);
      nautilus_progress_info_pulse_progress (job->progress);
}

static void
count_file (GFileInfo *info,
          CommonJob *job,
          SourceInfo *source_info)
{
      source_info->num_files += 1;
      source_info->num_bytes += g_file_info_get_size (info);

      if (source_info->num_files_since_progress++ > 100) {
            report_count_progress (job, source_info);
            source_info->num_files_since_progress = 0;
      }
}

static char *
get_scan_primary (OpKind kind)
{
      switch (kind) {
      default:
      case OP_KIND_COPY:
            return f (_("Error while copying."));
      case OP_KIND_MOVE:
            return f (_("Error while moving."));
      case OP_KIND_DELETE:
            return f (_("Error while deleting."));
      case OP_KIND_TRASH:
            return f (_("Error while moving files to trash."));
      }
}

static void
scan_dir (GFile *dir,
        SourceInfo *source_info,
        CommonJob *job,
        GQueue *dirs)
{
      GFileInfo *info;
      GError *error;
      GFile *subdir;
      GFileEnumerator *enumerator;
      char *primary, *secondary, *details;
      int response;
      SourceInfo saved_info;

      saved_info = *source_info;

 retry:
      error = NULL;
      enumerator = g_file_enumerate_children (dir,
                                    G_FILE_ATTRIBUTE_STANDARD_NAME","
                                    G_FILE_ATTRIBUTE_STANDARD_TYPE","
                                    G_FILE_ATTRIBUTE_STANDARD_SIZE,
                                    G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                    job->cancellable,
                                    &error);
      if (enumerator) {
            error = NULL;
            while ((info = g_file_enumerator_next_file (enumerator, job->cancellable, &error)) != NULL) {
                  count_file (info, job, source_info);

                  if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
                        subdir = g_file_get_child (dir,
                                             g_file_info_get_name (info));
                        
                        /* Push to head, since we want depth-first */
                        g_queue_push_head (dirs, subdir);
                  }

                  g_object_unref (info);
            }
            g_file_enumerator_close (enumerator, job->cancellable, NULL);
            g_object_unref (enumerator);
            
            if (error && IS_IO_ERROR (error, CANCELLED)) {
                  g_error_free (error);
            } else if (error) {
                  primary = get_scan_primary (source_info->op);
                  details = NULL;
                  
                  if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
                        secondary = f (_("Files in the folder \"%B\" cannot be handled because you do "
                                     "not have permissions to see them."), dir);
                  } else {
                        secondary = f (_("There was an error getting information about the files in the folder \"%B\"."), dir);
                        details = error->message;
                  }
                  
                  response = run_warning (job,
                                    primary,
                                    secondary,
                                    details,
                                    FALSE,
                                    GTK_STOCK_CANCEL, RETRY, SKIP,
                                    NULL);

                  g_error_free (error);
                  
                  if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                        abort_job (job);
                  } else if (response == 1) {
                        *source_info = saved_info;
                        goto retry;
                  } else if (response == 2) {
                        skip_readdir_error (job, dir);
                  } else {
                        g_assert_not_reached ();
                  }
            }
            
      } else if (job->skip_all_error) {
            g_error_free (error);
            skip_file (job, dir);
      } else if (IS_IO_ERROR (error, CANCELLED)) {
            g_error_free (error);
      } else {    
            primary = get_scan_primary (source_info->op);
            details = NULL;
            
            if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
                  secondary = f (_("The folder \"%B\" cannot be handled because you do not have "
                               "permissions to read it."), dir);
            } else {
                  secondary = f (_("There was an error reading the folder \"%B\"."), dir);
                  details = error->message;
            }
            /* set show_all to TRUE here, as we don't know how many
             * files we'll end up processing yet.
             */
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              TRUE,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP, RETRY,
                              NULL);
            
            g_error_free (error);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1 || response == 2) {
                  if (response == 1) {
                        job->skip_all_error = TRUE;
                  }
                  skip_file (job, dir);
            } else if (response == 3) {
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }
      }
}     

static void
scan_file (GFile *file,
         SourceInfo *source_info,
         CommonJob *job)
{
      GFileInfo *info;
      GError *error;
      GQueue *dirs;
      GFile *dir;
      char *primary;
      char *secondary;
      char *details;
      int response;

      dirs = g_queue_new ();
      
 retry:
      error = NULL;
      info = g_file_query_info (file, 
                          G_FILE_ATTRIBUTE_STANDARD_TYPE","
                          G_FILE_ATTRIBUTE_STANDARD_SIZE,
                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                          job->cancellable,
                          &error);

      if (info) {
            count_file (info, job, source_info);

            if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
                  g_queue_push_head (dirs, g_object_ref (file));
            }
            
            g_object_unref (info);
      } else if (job->skip_all_error) {
            g_error_free (error);
            skip_file (job, file);
      } else if (IS_IO_ERROR (error, CANCELLED)) {
            g_error_free (error);
      } else {
            primary = get_scan_primary (source_info->op);
            details = NULL;
            
            if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
                  secondary = f (_("The file \"%B\" cannot be handled because you do not have "
                               "permissions to read it."), file);
            } else {
                  secondary = f (_("There was an error getting information about \"%B\"."), file);
                  details = error->message;
            }
            /* set show_all to TRUE here, as we don't know how many
             * files we'll end up processing yet.
             */
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              TRUE,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP, RETRY,
                              NULL);
            
            g_error_free (error);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1 || response == 2) {
                  if (response == 1) {
                        job->skip_all_error = TRUE;
                  }
                  skip_file (job, file);
            } else if (response == 3) {
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }
      }
            
      while (!job_aborted (job) && 
             (dir = g_queue_pop_head (dirs)) != NULL) {
            scan_dir (dir, source_info, job, dirs);
            g_object_unref (dir);
      }

      /* Free all from queue if we exited early */
      g_queue_foreach (dirs, (GFunc)g_object_unref, NULL);
      g_queue_free (dirs);
}

static void
scan_sources (GList *files,
            SourceInfo *source_info,
            CommonJob *job,
            OpKind kind)
{
      GList *l;
      GFile *file;

      memset (source_info, 0, sizeof (SourceInfo));
      source_info->op = kind;

      report_count_progress (job, source_info);
      
      for (l = files; l != NULL && !job_aborted (job); l = l->next) {
            file = l->data;

            scan_file (file,
                     source_info,
                     job);
      }

      /* Make sure we report the final count */
      report_count_progress (job, source_info);
}

static void
verify_destination (CommonJob *job,
                GFile *dest,
                char **dest_fs_id,
                goffset required_size)
{
      GFileInfo *info, *fsinfo;
      GError *error;
      guint64 free_size;
      char *primary, *secondary, *details;
      int response;
      GFileType file_type;

      if (dest_fs_id) {
            *dest_fs_id = NULL;
      }

 retry:
      
      error = NULL;
      info = g_file_query_info (dest, 
                          G_FILE_ATTRIBUTE_STANDARD_TYPE","
                          G_FILE_ATTRIBUTE_ID_FILESYSTEM,
                          0,
                          job->cancellable,
                          &error);

      if (info == NULL) {
            if (IS_IO_ERROR (error, CANCELLED)) {
                  g_error_free (error);
                  return;
            }
            
            primary = f (_("Error while copying to \"%B\"."), dest);
            details = NULL;
            
            if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
                  secondary = f (_("You do not have permissions to access the destination folder."));
            } else {
                  secondary = f (_("There was an error getting information about the destination."));
                  details = error->message;
            }

            response = run_error (job,
                              primary,
                              secondary,
                              details,
                              FALSE,
                              GTK_STOCK_CANCEL, RETRY,
                              NULL);
            
            g_error_free (error);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) {
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }

            return;
      }

      file_type = g_file_info_get_file_type (info);

      if (dest_fs_id) {
            *dest_fs_id =
                  g_strdup (g_file_info_get_attribute_string (info,
                                                    G_FILE_ATTRIBUTE_ID_FILESYSTEM));
      }
      
      g_object_unref (info);
      
      if (file_type != G_FILE_TYPE_DIRECTORY) {
            primary = f (_("Error while copying to \"%B\"."), dest);
            secondary = f (_("The destination is not a folder."));

            response = run_error (job,
                              primary,
                              secondary,
                              NULL,
                              FALSE,
                              GTK_STOCK_CANCEL,
                              NULL);
            
            abort_job (job);
            return;
      }
      
      fsinfo = g_file_query_filesystem_info (dest,
                                     G_FILE_ATTRIBUTE_FILESYSTEM_FREE","
                                     G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
                                     job->cancellable,
                                     NULL);
      if (fsinfo == NULL) {
            /* All sorts of things can go wrong getting the fs info (like not supported)
             * only check these things if the fs returns them
             */
            return;
      }
      
      if (required_size > 0 &&
          g_file_info_has_attribute (fsinfo, G_FILE_ATTRIBUTE_FILESYSTEM_FREE)) {
            free_size = g_file_info_get_attribute_uint64 (fsinfo,
                                                G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
            
            if (free_size < required_size) {
                  primary = f (_("Error while copying to \"%B\"."), dest);
                  secondary = f(_("There is not enough space on the destination. Try to remove files to make space."));
                  
                  details = f (_("There is %S available, but %S is required."), free_size, required_size);
                  
                  response = run_warning (job,
                                    primary,
                                    secondary,
                                    details,
                                    FALSE,
                                    GTK_STOCK_CANCEL,
                                    COPY_FORCE,
                                    RETRY,
                                    NULL);
                  
                  if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                        abort_job (job);
                  } else if (response == 2) {
                        goto retry;
                  } else if (response == 1) {
                        /* We are forced to copy - just fall through ... */
                  } else {
                        g_assert_not_reached ();
                  }
            }
      }
      
      if (!job_aborted (job) &&
          g_file_info_get_attribute_boolean (fsinfo,
                                     G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) {
            primary = f (_("Error while copying to \"%B\"."), dest);
            secondary = f (_("The destination is read-only."));

            response = run_error (job,
                              primary,
                              secondary,
                              NULL,
                              FALSE,
                              GTK_STOCK_CANCEL,
                              NULL);
            
            g_error_free (error);

            abort_job (job);
      }
      
      g_object_unref (fsinfo);
}

static void
report_copy_progress (CopyMoveJob *copy_job,
                  SourceInfo *source_info,
                  TransferInfo *transfer_info)
{
      int files_left;
      goffset total_size;
      double elapsed, transfer_rate;
      int remaining_time;
      guint64 now;
      CommonJob *job;
      gboolean is_move;

      job = (CommonJob *)copy_job;

      is_move = copy_job->is_move;
      
      now = g_thread_gettime ();
      
      if (transfer_info->last_report_time != 0 &&
          ABS ((gint64)(transfer_info->last_report_time - now)) < 100 * NSEC_PER_MSEC) {
            return;
      }
      transfer_info->last_report_time = now;
      
      files_left = source_info->num_files - transfer_info->num_files;

      /* Races and whatnot could cause this to be negative... */
      if (files_left < 0) {
            files_left = 1;
      }

      if (files_left != transfer_info->last_reported_files_left ||
          transfer_info->last_reported_files_left == 0) {
            /* Avoid changing this unless files_left changed since last time */
            transfer_info->last_reported_files_left = files_left;
            
            if (source_info->num_files == 1) {
                  if (copy_job->destination != NULL) {
                        nautilus_progress_info_take_status (job->progress,
                                                    f (is_move ?
                                                       _("Moving \"%B\" to \"%B\""):
                                                       _("Copying \"%B\" to \"%B\""),
                                                       (GFile *)copy_job->files->data,
                                                       copy_job->destination));
                  } else {
                        nautilus_progress_info_take_status (job->progress,
                                                    f (_("Duplicating \"%B\""),
                                                       (GFile *)copy_job->files->data));
                  }
            } else if (copy_job->files != NULL &&
                     copy_job->files->next == NULL) {
                  if (copy_job->destination != NULL) {
                        nautilus_progress_info_take_status (job->progress,
                                                    f (is_move?
                                                       ngettext ("Moving %'d file (in \"%B\") to \"%B\"",
                                                             "Moving %'d files (in \"%B\") to \"%B\"",
                                                             files_left)
                                                       :
                                                       ngettext ("Copying %'d file (in \"%B\") to \"%B\"",
                                                             "Copying %'d files (in \"%B\") to \"%B\"",
                                                             files_left),
                                                       files_left,
                                                       (GFile *)copy_job->files->data,
                                                       copy_job->destination));
                  } else {
                        nautilus_progress_info_take_status (job->progress,
                                                    f (ngettext ("Duplicating %'d file (in \"%B\")",
                                                             "Duplicating %'d files (in \"%B\")",
                                                             files_left),
                                                       files_left,
                                                       (GFile *)copy_job->files->data));
                  }
            } else {
                  if (copy_job->destination != NULL) {
                        nautilus_progress_info_take_status (job->progress,
                                                    f (is_move?
                                                       ngettext ("Moving %'d file to \"%B\"",
                                                             "Moving %'d files to \"%B\"",
                                                             files_left)
                                                       :
                                                       ngettext ("Copying %'d file to \"%B\"",
                                                             "Copying %'d files to \"%B\"",
                                                             files_left),
                                                       files_left, copy_job->destination));
                  } else {
                        nautilus_progress_info_take_status (job->progress,
                                                    f (ngettext ("Duplicating %'d file",
                                                             "Duplicating %'d files",
                                                             files_left),
                                                       files_left));
                  }
            }
      }
      
      total_size = MAX (source_info->num_bytes, transfer_info->num_bytes);
      
      elapsed = g_timer_elapsed (job->time, NULL);
      transfer_rate = 0;
      if (elapsed > 0) {
            transfer_rate = transfer_info->num_bytes / elapsed;
      }

      if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE &&
          transfer_rate > 0) {
            char *s;
            /* To translators: %S will expand to a size like "2 bytes" or "3 MB", so something like "4 kb of 4 MB" */         
            s = f (_("%S of %S"), transfer_info->num_bytes, total_size);
            nautilus_progress_info_take_details (job->progress, s);
      } else {
            char *s;
            remaining_time = (total_size - transfer_info->num_bytes) / transfer_rate;

            /* To translators: %S will expand to a size like "2 bytes" or "3 MB", %T to a time duration like
             * "2 minutes". So the whole thing will be something like "2 kb of 4 MB -- 2 hours left (4kb/sec)"
             *
             * The singular/plural form will be used depending on the remaining time (i.e. the %T argument).
             */         
            s = f (ngettext ("%S of %S \xE2\x80\x94 %T left (%S/sec)",
                         "%S of %S \xE2\x80\x94 %T left (%S/sec)",
                         seconds_count_format_time_units (remaining_time)),
                   transfer_info->num_bytes, total_size,
                   remaining_time,
                   (goffset)transfer_rate);
            nautilus_progress_info_take_details (job->progress, s);
      }

      nautilus_progress_info_set_progress (job->progress, transfer_info->num_bytes, total_size);
}

static int
get_max_name_length (GFile *file_dir)
{
      int max_length;
      char *dir;
      long max_path;
      long max_name;

      max_length = -1;

      if (!g_file_has_uri_scheme (file_dir, "file"))
            return max_length;

      dir = g_file_get_path (file_dir);
      if (!dir)
            return max_length;

      max_path = pathconf (dir, _PC_PATH_MAX);
      max_name = pathconf (dir, _PC_NAME_MAX);

      if (max_name == -1 && max_path == -1) {
            max_length = -1;
      } else if (max_name == -1 && max_path != -1) {
            max_length = max_path - (strlen (dir) + 1);
      } else if (max_name != -1 && max_path == -1) {
            max_length = max_name;
      } else {
            int leftover;

            leftover = max_path - (strlen (dir) + 1);

            max_length = MIN (leftover, max_name);
      }

      g_free (dir);

      return max_length;
}

#define FAT_FORBIDDEN_CHARACTERS "/:;*?\"<>"

static gboolean
str_replace (char *str,
           const char *chars_to_replace,
           char replacement)
{
      gboolean success;
      int i;

      success = FALSE;
      for (i = 0; str[i] != '\0'; i++) {
            if (strchr (chars_to_replace, str[i])) {
                  success = TRUE;
                  str[i] = replacement;
            }
      }

      return success;
}

static gboolean
make_file_name_valid_for_dest_fs (char *filename,
                         const char *dest_fs_type)
{
      if (dest_fs_type != NULL && filename != NULL) {
            if (!strcmp (dest_fs_type, "fat")  ||
                !strcmp (dest_fs_type, "vfat") ||
                !strcmp (dest_fs_type, "msdos") ||
                !strcmp (dest_fs_type, "msdosfs")) {
                  gboolean ret;
                  int i, old_len;

                  ret = str_replace (filename, FAT_FORBIDDEN_CHARACTERS, '_');

                  old_len = strlen (filename);
                  for (i = 0; i < old_len; i++) {
                        if (filename[i] != ' ') {
                              g_strchomp (filename);
                              ret |= (old_len != strlen (filename));
                              break;
                        }
                  }

                  return ret;
            }
      }

      return FALSE;
}

static GFile *
get_unique_target_file (GFile *src,
                  GFile *dest_dir,
                  gboolean same_fs,
                  const char *dest_fs_type,
                  int count)
{
      const char *editname, *end;
      char *basename, *new_name;
      GFileInfo *info;
      GFile *dest;
      int max_length;

      max_length = get_max_name_length (dest_dir);
      
      dest = NULL;
      info = g_file_query_info (src,
                          G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
                          0, NULL, NULL);
      if (info != NULL) {
            editname = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME);
            
            if (editname != NULL) {
                  new_name = get_duplicate_name (editname, count, max_length);
                  make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
                  dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
                  g_free (new_name);
            }
            
            g_object_unref (info);
      }

      if (dest == NULL) {
            basename = g_file_get_basename (src);

            if (g_utf8_validate (basename, -1, NULL)) {
                  new_name = get_duplicate_name (basename, count, max_length);
                  make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
                  dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
                  g_free (new_name);
            } 

            if (dest == NULL) {
                  end = strrchr (basename, '.');
                  if (end != NULL) {
                        count += atoi (end + 1);
                  }
                  new_name = g_strdup_printf ("%s.%d", basename, count);
                  make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
                  dest = g_file_get_child (dest_dir, new_name);
                  g_free (new_name);
            }
            
            g_free (basename);
      }

      return dest;
}

static GFile *
get_target_file_for_link (GFile *src,
                    GFile *dest_dir,
                    const char *dest_fs_type,
                    int count)
{
      const char *editname;
      char *basename, *new_name;
      GFileInfo *info;
      GFile *dest;
      int max_length;

      max_length = get_max_name_length (dest_dir);

      dest = NULL;
      info = g_file_query_info (src,
                          G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
                          0, NULL, NULL);
      if (info != NULL) {
            editname = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME);
            
            if (editname != NULL) {
                  new_name = get_link_name (editname, count, max_length);
                  make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
                  dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
                  g_free (new_name);
            }
            
            g_object_unref (info);
      }

      if (dest == NULL) {
            basename = g_file_get_basename (src);
            make_file_name_valid_for_dest_fs (basename, dest_fs_type);

            if (g_utf8_validate (basename, -1, NULL)) {
                  new_name = get_link_name (basename, count, max_length);
                  make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
                  dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL);
                  g_free (new_name);
            } 

            if (dest == NULL) {
                  if (count == 1) {
                        new_name = g_strdup_printf ("%s.lnk", basename);
                  } else {
                        new_name = g_strdup_printf ("%s.lnk%d", basename, count);
                  }
                  make_file_name_valid_for_dest_fs (new_name, dest_fs_type);
                  dest = g_file_get_child (dest_dir, new_name);
                  g_free (new_name);
            }
            
            g_free (basename);
      }

      return dest;
}

static GFile *
get_target_file (GFile *src,
             GFile *dest_dir,
             const char *dest_fs_type,
             gboolean same_fs)
{
      char *basename;
      GFile *dest;
      GFileInfo *info;
      char *copyname;

      dest = NULL;
      if (!same_fs) {
            info = g_file_query_info (src,
                                G_FILE_ATTRIBUTE_STANDARD_COPY_NAME,
                                0, NULL, NULL);
            
            if (info) {
                  copyname = g_strdup (g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME));

                  if (copyname) {
                        make_file_name_valid_for_dest_fs (copyname, dest_fs_type);
                        dest = g_file_get_child_for_display_name (dest_dir, copyname, NULL);
                        g_free (copyname);
                  }
                  
                  g_object_unref (info);
            }
      }

      if (dest == NULL) {
            basename = g_file_get_basename (src);
            make_file_name_valid_for_dest_fs (basename, dest_fs_type);
            dest = g_file_get_child (dest_dir, basename);
            g_free (basename);
      }
      
      return dest;
}

static gboolean
has_fs_id (GFile *file, const char *fs_id)
{
      const char *id;
      GFileInfo *info;
      gboolean res;

      res = FALSE;
      info = g_file_query_info (file,
                          G_FILE_ATTRIBUTE_ID_FILESYSTEM,
                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                          NULL, NULL);

      if (info) {
            id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM);
            
            if (id && strcmp (id, fs_id) == 0) {
                  res = TRUE;
            }
            
            g_object_unref (info);
      }
      
      return res;
}

static gboolean
is_dir (GFile *file)
{
      GFileInfo *info;
      gboolean res;

      res = FALSE;
      info = g_file_query_info (file,
                          G_FILE_ATTRIBUTE_STANDARD_TYPE,
                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                          NULL, NULL);
      if (info) {
            res = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
            g_object_unref (info);
      }
      
      return res;
}

static void copy_move_file (CopyMoveJob *job,
                      GFile *src,
                      GFile *dest_dir,
                      gboolean same_fs,
                      gboolean unique_names,
                      char **dest_fs_type,
                      SourceInfo *source_info,
                      TransferInfo *transfer_info,
                      GHashTable *debuting_files,
                      GdkPoint *point,
                      gboolean overwrite,
                      gboolean *skipped_file,
                      gboolean readonly_source_fs);

typedef enum {
      CREATE_DEST_DIR_RETRY,
      CREATE_DEST_DIR_FAILED,
      CREATE_DEST_DIR_SUCCESS
} CreateDestDirResult;

static CreateDestDirResult
create_dest_dir (CommonJob *job,
             GFile *src,
             GFile **dest,
             gboolean same_fs,
             char **dest_fs_type)
{
      GError *error;
      GFile *new_dest, *dest_dir;
      char *primary, *secondary, *details;
      int response;
      gboolean handled_invalid_filename;

      handled_invalid_filename = *dest_fs_type != NULL;

 retry:
      /* First create the directory, then copy stuff to it before
         copying the attributes, because we need to be sure we can write to it */
      
      error = NULL;
      if (!g_file_make_directory (*dest, job->cancellable, &error)) {
            if (IS_IO_ERROR (error, CANCELLED)) {
                  g_error_free (error);
                  return CREATE_DEST_DIR_FAILED;
            } else if (IS_IO_ERROR (error, INVALID_FILENAME) &&
                     !handled_invalid_filename) {
                  handled_invalid_filename = TRUE;

                  g_assert (*dest_fs_type == NULL);

                  dest_dir = g_file_get_parent (*dest);

                  if (dest_dir != NULL) {
                        *dest_fs_type = query_fs_type (dest_dir, job->cancellable);

                        new_dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);
                        g_object_unref (dest_dir);

                        if (!g_file_equal (*dest, new_dest)) {
                              g_object_unref (*dest);
                              *dest = new_dest;
                              g_error_free (error);
                              return CREATE_DEST_DIR_RETRY;
                        } else {
                              g_object_unref (new_dest);
                        }
                  }
            }

            primary = f (_("Error while copying."));
            details = NULL;
            
            if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
                  secondary = f (_("The folder \"%B\" cannot be copied because you do not have "
                               "permissions to create it in the destination."), src);
            } else {
                  secondary = f (_("There was an error creating the folder \"%B\"."), src);
                  details = error->message;
            }
            
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              FALSE,
                              GTK_STOCK_CANCEL, SKIP, RETRY,
                              NULL);

            g_error_free (error);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) {
                  /* Skip: Do Nothing  */
            } else if (response == 2) {
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }
            return CREATE_DEST_DIR_FAILED;
      }
      nautilus_file_changes_queue_file_added (*dest);
      return CREATE_DEST_DIR_SUCCESS;
}

/* a return value of FALSE means retry, i.e.
 * the destination has changed and the source
 * is expected to re-try the preceeding
 * g_file_move() or g_file_copy() call with
 * the new destination.
 */
static gboolean
copy_move_directory (CopyMoveJob *copy_job,
                 GFile *src,
                 GFile **dest,
                 gboolean same_fs,
                 gboolean create_dest,
                 char **parent_dest_fs_type,
                 SourceInfo *source_info,
                 TransferInfo *transfer_info,
                 GHashTable *debuting_files,
                 gboolean *skipped_file,
                 gboolean readonly_source_fs)
{
      GFileInfo *info;
      GError *error;
      GFile *src_file;
      GFileEnumerator *enumerator;
      char *primary, *secondary, *details;
      char *dest_fs_type;
      int response;
      gboolean skip_error;
      gboolean local_skipped_file;
      CommonJob *job;
      GFileCopyFlags flags;

      job = (CommonJob *)copy_job;
      
      if (create_dest) {
            switch (create_dest_dir (job, src, dest, same_fs, parent_dest_fs_type)) {
                  case CREATE_DEST_DIR_RETRY:
                        /* next time copy_move_directory() is called,
                         * create_dest will be FALSE if a directory already
                         * exists under the new name (i.e. WOULD_RECURSE)
                         */
                        return FALSE;

                  case CREATE_DEST_DIR_FAILED:
                        *skipped_file = TRUE;
                        return TRUE;

                  case CREATE_DEST_DIR_SUCCESS:
                  default:
                        break;
            }

            if (debuting_files) {
                  g_hash_table_replace (debuting_files, g_object_ref (*dest), GINT_TO_POINTER (TRUE));
            }

      }

      local_skipped_file = FALSE;
      dest_fs_type = NULL;
      
      skip_error = should_skip_readdir_error (job, src);
 retry:
      error = NULL;
      enumerator = g_file_enumerate_children (src,
                                    G_FILE_ATTRIBUTE_STANDARD_NAME,
                                    G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                    job->cancellable,
                                    &error);
      if (enumerator) {
            error = NULL;

            while (!job_aborted (job) &&
                   (info = g_file_enumerator_next_file (enumerator, job->cancellable, skip_error?NULL:&error)) != NULL) {
                  src_file = g_file_get_child (src,
                                         g_file_info_get_name (info));
                  copy_move_file (copy_job, src_file, *dest, same_fs, FALSE, &dest_fs_type,
                              source_info, transfer_info, NULL, NULL, FALSE, &local_skipped_file,
                              readonly_source_fs);
                  g_object_unref (src_file);
                  g_object_unref (info);
            }
            g_file_enumerator_close (enumerator, job->cancellable, NULL);
            g_object_unref (enumerator);
            
            if (error && IS_IO_ERROR (error, CANCELLED)) {
                  g_error_free (error);
            } else if (error) {
                  if (copy_job->is_move) {
                        primary = f (_("Error while moving."));
                  } else {
                        primary = f (_("Error while copying."));
                  }
                  details = NULL;
                  
                  if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
                        secondary = f (_("Files in the folder \"%B\" cannot be copied because you do "
                                     "not have permissions to see them."), src);
                  } else {
                        secondary = f (_("There was an error getting information about the files in the folder \"%B\"."), src);
                        details = error->message;
                  }
                  
                  response = run_warning (job,
                                    primary,
                                    secondary,
                                    details,
                                    FALSE,
                                    GTK_STOCK_CANCEL, _("_Skip files"),
                                    NULL);
                  
                  g_error_free (error);
                  
                  if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                        abort_job (job);
                  } else if (response == 1) {
                        /* Skip: Do Nothing */
                        local_skipped_file = TRUE;
                  } else {
                        g_assert_not_reached ();
                  }
            }

            /* Count the copied directory as a file */
            transfer_info->num_files ++;
            report_copy_progress (copy_job, source_info, transfer_info);

            if (debuting_files) {
                  g_hash_table_replace (debuting_files, g_object_ref (*dest), GINT_TO_POINTER (create_dest));
            }
      } else if (IS_IO_ERROR (error, CANCELLED)) {
            g_error_free (error);
      } else {
            if (copy_job->is_move) {
                  primary = f (_("Error while moving."));
            } else {
                  primary = f (_("Error while copying."));
            }
            details = NULL;
            
            if (IS_IO_ERROR (error, PERMISSION_DENIED)) {
                  secondary = f (_("The folder \"%B\" cannot be copied because you do not have "
                               "permissions to read it."), src);
            } else {
                  secondary = f (_("There was an error reading the folder \"%B\"."), src);
                  details = error->message;
            }
            
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              FALSE,
                              GTK_STOCK_CANCEL, SKIP, RETRY,
                              NULL);

            g_error_free (error);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) {
                  /* Skip: Do Nothing  */
                  local_skipped_file = TRUE;
            } else if (response == 2) {
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }
      }

      if (create_dest) {
            flags = (readonly_source_fs) ? G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_TARGET_DEFAULT_PERMS 
                                   : G_FILE_COPY_NOFOLLOW_SYMLINKS;
            /* Ignore errors here. Failure to copy metadata is not a hard error */
            g_file_copy_attributes (src, *dest,
                              flags,
                              job->cancellable, NULL);
      }

      if (!job_aborted (job) && copy_job->is_move &&
          /* Don't delete source if there was a skipped file */
          !local_skipped_file) {
            if (!g_file_delete (src, job->cancellable, &error)) {
                  if (job->skip_all_error) {
                        goto skip;
                  }
                  primary = f (_("Error while moving \"%B\"."), src);
                  secondary = f (_("Could not remove the source folder."));
                  details = error->message;
                  
                  response = run_warning (job,
                                    primary,
                                    secondary,
                                    details,
                                    (source_info->num_files - transfer_info->num_files) > 1,
                                    GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                                    NULL);
                  
                  if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                        abort_job (job);
                  } else if (response == 1) { /* skip all */
                        job->skip_all_error = TRUE;
                        local_skipped_file = TRUE;
                  } else if (response == 2) { /* skip */
                        local_skipped_file = TRUE;
                  } else {
                        g_assert_not_reached ();
                  }
                  
            skip:
                  g_error_free (error);
            }
      }

      if (local_skipped_file) {
            *skipped_file = TRUE;
      }

      g_free (dest_fs_type);
      return TRUE;
}

static gboolean
remove_target_recursively (CommonJob *job,
                     GFile *src,
                     GFile *toplevel_dest,
                     GFile *file)
{
      GFileEnumerator *enumerator;
      GError *error;
      GFile *child;
      gboolean stop;
      char *primary, *secondary, *details;
      int response;
      GFileInfo *info;

      stop = FALSE;
      
      error = NULL;
      enumerator = g_file_enumerate_children (file,
                                    G_FILE_ATTRIBUTE_STANDARD_NAME,
                                    G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                    job->cancellable,
                                    &error);
      if (enumerator) {
            error = NULL;
            
            while (!job_aborted (job) &&
                   (info = g_file_enumerator_next_file (enumerator, job->cancellable, &error)) != NULL) {
                  child = g_file_get_child (file,
                                      g_file_info_get_name (info));
                  if (!remove_target_recursively (job, src, toplevel_dest, child)) {
                        stop = TRUE;
                        break;
                  }
                  g_object_unref (child);
                  g_object_unref (info);
            }
            g_file_enumerator_close (enumerator, job->cancellable, NULL);
            g_object_unref (enumerator);
            
      } else if (IS_IO_ERROR (error, NOT_DIRECTORY)) {
            /* Not a dir, continue */
            g_error_free (error);
            
      } else if (IS_IO_ERROR (error, CANCELLED)) {
            g_error_free (error);
      } else {
            if (job->skip_all_error) {
                  goto skip1;
            }
            
            primary = f (_("Error while copying \"%B\"."), src);
            secondary = f (_("Could not remove files from the already existing folder %F."), file);
            details = error->message;

            /* set show_all to TRUE here, as we don't know how many
             * files we'll end up processing yet.
             */
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              TRUE,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                              NULL);
            
            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) { /* skip all */
                  job->skip_all_error = TRUE;
            } else if (response == 2) { /* skip */
                  /* do nothing */
            } else {
                  g_assert_not_reached ();
            }
      skip1:
            g_error_free (error);
            
            stop = TRUE;
      }

      if (stop) {
            return FALSE;
      }

      error = NULL;
      
      if (!g_file_delete (file, job->cancellable, &error)) {
            if (job->skip_all_error ||
                IS_IO_ERROR (error, CANCELLED)) {
                  goto skip2;
            }
            primary = f (_("Error while copying \"%B\"."), src);
            secondary = f (_("Could not remove the already existing file %F."), file);
            details = error->message;

            /* set show_all to TRUE here, as we don't know how many
             * files we'll end up processing yet.
             */
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              TRUE,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                              NULL);
            
            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) { /* skip all */
                  job->skip_all_error = TRUE;
            } else if (response == 2) { /* skip */
                  /* do nothing */
            } else {
                  g_assert_not_reached ();
            }

      skip2:
            g_error_free (error);
            
            return FALSE;
      }
      nautilus_file_changes_queue_file_removed (file);
      
      return TRUE;
      
}

typedef struct {
      CopyMoveJob *job;
      goffset last_size;
      SourceInfo *source_info;
      TransferInfo *transfer_info;
} ProgressData;

static void
copy_file_progress_callback (goffset current_num_bytes,
                       goffset total_num_bytes,
                       gpointer user_data)
{
      ProgressData *pdata;
      goffset new_size;

      pdata = user_data;
      
      new_size = current_num_bytes - pdata->last_size;

      if (new_size > 0) {
            pdata->transfer_info->num_bytes += new_size;
            pdata->last_size = current_num_bytes;
            report_copy_progress (pdata->job,
                              pdata->source_info,
                              pdata->transfer_info);
      }
}

static gboolean
test_dir_is_parent (GFile *child, GFile *root)
{
      GFile *f;

      f = g_file_dup (child);
      while (f) {
            if (g_file_equal (f, root)) {
                  g_object_unref (f);
                  return TRUE;
            }
            f = g_file_get_parent (f);
      }

      if (f) {
            g_object_unref (f);
      }
      return FALSE;
}

static char *
query_fs_type (GFile *file,
             GCancellable *cancellable)
{
      GFileInfo *fsinfo;
      char *ret;

      ret = NULL;

      fsinfo = g_file_query_filesystem_info (file,
                                     G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
                                     cancellable,
                                     NULL);
      if (fsinfo != NULL) {
            ret = g_strdup (g_file_info_get_attribute_string (fsinfo, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE));
            g_object_unref (fsinfo);
      }

      if (ret == NULL) {
            /* ensure that we don't attempt to query
             * the FS type for each file in a given
             * directory, if it can't be queried. */
            ret = g_strdup ("");
      }

      return ret;
}

static gboolean
is_trusted_desktop_file (GFile *file,
                   GCancellable *cancellable)
{
      char *basename;
      gboolean res;
      GFileInfo *info;

      /* Don't trust non-local files */
      if (!g_file_is_native (file)) {
            return FALSE;
      }
      
      basename = g_file_get_basename (file);
      if (!g_str_has_suffix (basename, ".desktop")) {
            g_free (basename);
            return FALSE;
      }
      g_free (basename);

      info = g_file_query_info (file, 
                          G_FILE_ATTRIBUTE_STANDARD_TYPE ","
                          G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                          cancellable,
                          NULL);

      if (info == NULL) {
            return FALSE;
      }

      res = FALSE;
      
      /* Weird file => not trusted,
         Already executable => no need to mark trusted */
      if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR &&
          !g_file_info_get_attribute_boolean (info,
                                    G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) &&
          nautilus_is_in_system_dir (file)) {
            res = TRUE;
      }
      g_object_unref (info);
      
      return res;
}

/* Debuting files is non-NULL only for toplevel items */
static void
copy_move_file (CopyMoveJob *copy_job,
            GFile *src,
            GFile *dest_dir,
            gboolean same_fs,
            gboolean unique_names,
            char **dest_fs_type,
            SourceInfo *source_info,
            TransferInfo *transfer_info,
            GHashTable *debuting_files,
            GdkPoint *position,
            gboolean overwrite,
            gboolean *skipped_file,
            gboolean readonly_source_fs)
{
      GFile *dest, *new_dest;
      GError *error;
      GFileCopyFlags flags;
      char *primary, *secondary, *details;
      int response;
      ProgressData pdata;
      gboolean would_recurse, is_merge;
      CommonJob *job;
      gboolean res;
      int unique_name_nr;
      gboolean handled_invalid_filename;

      job = (CommonJob *)copy_job;
      
      if (should_skip_file (job, src)) {
            *skipped_file = TRUE;
            return;
      }

      unique_name_nr = 1;

      /* another file in the same directory might have handled the invalid
       * filename condition for us
       */
      handled_invalid_filename = *dest_fs_type != NULL;

      if (unique_names) {
            dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr++);
      } else {
            dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);
      }


      /* 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 (test_dir_is_parent (dest_dir, src)) {
            if (job->skip_all_error) {
                  goto out;
            }
            
            /*  the run_warning() frees all strings passed in automatically  */
            primary = copy_job->is_move ? g_strdup (_("You cannot move a folder into itself."))
                                  : g_strdup (_("You cannot copy a folder into itself."));
            secondary = g_strdup (_("The destination folder is inside the source folder."));
            
            response = run_warning (job,
                              primary,
                              secondary,
                              NULL,
                              (source_info->num_files - transfer_info->num_files) > 1,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                              NULL);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) { /* skip all */
                  job->skip_all_error = TRUE;
            } else if (response == 2) { /* skip */
                  /* do nothing */
            } else {
                  g_assert_not_reached ();
            }

            goto out;
      }

      /* Don't allow copying over the source or one of the parents of the source.
       */
      if (test_dir_is_parent (src, dest)) {
            if (job->skip_all_error) {
                  goto out;
            }
            
            /*  the run_warning() frees all strings passed in automatically  */
            primary = copy_job->is_move ? g_strdup (_("You cannot move a file over itself."))
                                  : g_strdup (_("You cannot copy a file over itself."));
            secondary = g_strdup (_("The source file would be overwritten by the destination."));
            
            response = run_warning (job,
                              primary,
                              secondary,
                              NULL,
                              (source_info->num_files - transfer_info->num_files) > 1,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                              NULL);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) { /* skip all */
                  job->skip_all_error = TRUE;
            } else if (response == 2) { /* skip */
                  /* do nothing */
            } else {
                  g_assert_not_reached ();
            }

            goto out;
      }

      
 retry:
      
      error = NULL;
      flags = G_FILE_COPY_NOFOLLOW_SYMLINKS;
      if (overwrite) {
            flags |= G_FILE_COPY_OVERWRITE;
      }
      if (readonly_source_fs) {
            flags |= G_FILE_COPY_TARGET_DEFAULT_PERMS;
      }

      pdata.job = copy_job;
      pdata.last_size = 0;
      pdata.source_info = source_info;
      pdata.transfer_info = transfer_info;

      if (copy_job->is_move) {
            res = g_file_move (src, dest,
                           flags,
                           job->cancellable,
                           copy_file_progress_callback,
                           &pdata,
                           &error);
      } else {
            res = g_file_copy (src, dest,
                           flags,
                           job->cancellable,
                           copy_file_progress_callback,
                           &pdata,
                           &error);
      }
      
      if (res) {
            transfer_info->num_files ++;
            report_copy_progress (copy_job, source_info, transfer_info);

            if (debuting_files) {
                  if (position) {
                        nautilus_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
                  } else {
                        nautilus_file_changes_queue_schedule_position_remove (dest);
                  }
                  
                  g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
            }
            if (copy_job->is_move) {
                  nautilus_file_changes_queue_file_moved (src, dest);
            } else {
                  nautilus_file_changes_queue_file_added (dest);
            }

            /* If copying a trusted desktop file to the desktop,
               mark it as trusted. */
            if (copy_job->desktop_location != NULL &&
                g_file_equal (copy_job->desktop_location, dest_dir) &&
                is_trusted_desktop_file (src, job->cancellable)) {
                  mark_desktop_file_trusted (job,
                                       job->cancellable,
                                       dest,
                                       FALSE);
            }
                  
            g_object_unref (dest);
            return;
      }

      if (!handled_invalid_filename &&
          IS_IO_ERROR (error, INVALID_FILENAME)) {
            handled_invalid_filename = TRUE;

            g_assert (*dest_fs_type == NULL);
            *dest_fs_type = query_fs_type (dest_dir, job->cancellable);

            if (unique_names) {
                  new_dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr);
            } else {
                  new_dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);
            }

            if (!g_file_equal (dest, new_dest)) {
                  g_object_unref (dest);
                  dest = new_dest;

                  g_error_free (error);
                  goto retry;
            } else {
                  g_object_unref (new_dest);
            }
      }

      /* Conflict */
      if (!overwrite &&
          IS_IO_ERROR (error, EXISTS)) {
            gboolean is_merge;

            if (unique_names) {
                  g_object_unref (dest);
                  dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr++);
                  g_error_free (error);
                  goto retry;
            }
            
            is_merge = FALSE;
            if (is_dir (dest)) {
                  if (is_dir (src)) {
                        is_merge = TRUE;
                        primary = f (_("A folder named \"%B\" already exists.  Do you want to merge the source folder?"), 
                                   dest);
                        secondary = f (_("The source folder already exists in \"%B\".  "
                                     "Merging will ask for confirmation before replacing any files in the folder that conflict with the files being copied."), 
                                     dest_dir);
                        
                  } else {
                        primary = f (_("A folder named \"%B\" already exists.  Do you want to replace it?"), 
                                              dest);
                        secondary = f (_("The folder already exists in \"%F\".  "
                                     "Replacing it will remove all files in the folder."), 
                                     dest_dir);
                  }
            } else {
                  primary = f (_("A file named \"%B\" already exists.  Do you want to replace it?"), 
                             dest);
                  secondary = f (_("The file already exists in \"%F\".  "
                               "Replacing it will overwrite its content."), 
                               dest_dir);
            }

            if ((is_merge && job->merge_all) ||
                (!is_merge && job->replace_all)) {
                  g_free (primary);
                  g_free (secondary);
                  g_error_free (error);
                  
                  overwrite = TRUE;
                  goto retry;
            }

            if (job->skip_all_conflict) {
                  g_free (primary);
                  g_free (secondary);
                  g_error_free (error);
                  
                  goto out;
            }
            
            response = run_warning (job,
                              primary,
                              secondary,
                              NULL,
                              (source_info->num_files - transfer_info->num_files) > 1,
                              GTK_STOCK_CANCEL,
                              SKIP_ALL,
                              is_merge?MERGE_ALL:REPLACE_ALL,
                              SKIP,
                              is_merge?MERGE:REPLACE,
                              NULL);
            
            g_error_free (error);
            
            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1 || response == 3) { /* skip all / skip */
                  if (response == 1) {
                        job->skip_all_conflict = TRUE;
                  }
            } else if (response == 2 || response == 4) { /* merge/replace all  / merge/replace*/
                  if (response == 2) {
                        if (is_merge) {
                              job->merge_all = TRUE;
                        } else {
                              job->replace_all = TRUE;
                        }
                  }
                  overwrite = TRUE;
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }
      }
      
      else if (overwrite &&
             IS_IO_ERROR (error, IS_DIRECTORY)) {

            g_error_free (error);
            
            if (remove_target_recursively (job, src, dest, dest)) {
                  goto retry;
            }
      }
      
      /* Needs to recurse */
      else if (IS_IO_ERROR (error, WOULD_RECURSE) ||
             IS_IO_ERROR (error, WOULD_MERGE)) {
            is_merge = error->code == G_IO_ERROR_WOULD_MERGE;
            would_recurse = error->code == G_IO_ERROR_WOULD_RECURSE;
            g_error_free (error);

            if (overwrite && would_recurse) {
                  error = NULL;
                  
                  /* Copying a dir onto file, first remove the file */
                  if (!g_file_delete (dest, job->cancellable, &error) &&
                      !IS_IO_ERROR (error, NOT_FOUND)) {
                        if (job->skip_all_error) {
                              g_error_free (error);
                              goto out;
                        }
                        if (copy_job->is_move) {
                              primary = f (_("Error while moving \"%B\"."), src);
                        } else {
                              primary = f (_("Error while copying \"%B\"."), src);
                        }
                        secondary = f (_("Could not remove the already existing file with the same name in %F."), dest_dir);
                        details = error->message;

                        /* setting TRUE on show_all here, as we could have
                         * another error on the same file later.
                         */
                        response = run_warning (job,
                                          primary,
                                          secondary,
                                          details,
                                          TRUE,
                                          GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                                          NULL);
                        
                        g_error_free (error);
                        
                        if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                              abort_job (job);
                        } else if (response == 1) { /* skip all */
                              job->skip_all_error = TRUE;
                        } else if (response == 2) { /* skip */
                              /* do nothing */
                        } else {
                              g_assert_not_reached ();
                        }
                        goto out;
                        
                  }
                  if (error) {
                        g_error_free (error);
                        error = NULL;
                  }
                  nautilus_file_changes_queue_file_removed (dest);
            }

            if (is_merge) {
                  /* On merge we now write in the target directory, which may not
                     be in the same directory as the source, even if the parent is
                     (if the merged directory is a mountpoint). This could cause
                     problems as we then don't transcode filenames.
                     We just set same_fs to FALSE which is safe but a bit slower. */
                  same_fs = FALSE;
            }
            
            if (!copy_move_directory (copy_job, src, &dest, same_fs,
                                would_recurse, dest_fs_type,
                                source_info, transfer_info,
                                debuting_files, skipped_file,
                                readonly_source_fs)) {
                  /* destination changed, since it was an invalid file name */
                  g_assert (*dest_fs_type != NULL);
                  handled_invalid_filename = TRUE;
                  goto retry;
            }

            g_object_unref (dest);
            return;
      }
      
      else if (IS_IO_ERROR (error, CANCELLED)) {
            g_error_free (error);
      }
      
      /* Other error */
      else {
            if (job->skip_all_error) {
                  g_error_free (error);
                  goto out;
            }
            primary = f (_("Error while copying \"%B\"."), src);
            secondary = f (_("There was an error copying the file into %F."), dest_dir);
            details = error->message;
            
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              (source_info->num_files - transfer_info->num_files) > 1,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                              NULL);

            g_error_free (error);
            
            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) { /* skip all */
                  job->skip_all_error = TRUE;
            } else if (response == 2) { /* skip */
                  /* do nothing */
            } else {
                  g_assert_not_reached ();
            }
      }
 out:
      *skipped_file = TRUE; /* Or aborted, but same-same */
      g_object_unref (dest);
}

static void
copy_files (CopyMoveJob *job,
          const char *dest_fs_id,
          SourceInfo *source_info,
          TransferInfo *transfer_info)
{
      CommonJob *common;
      GList *l;
      GFile *src;
      gboolean same_fs;
      int i;
      GdkPoint *point;
      gboolean skipped_file;
      gboolean unique_names;
      GFile *dest;
      GFile *source_dir;
      char *dest_fs_type;
      GFileInfo *inf;
      gboolean readonly_source_fs;

      dest_fs_type = NULL;
      readonly_source_fs = FALSE;

      common = &job->common;

      report_copy_progress (job, source_info, transfer_info);

      /* Query the source dir, not the file because if its a symlink we'll follow it */
      source_dir = g_file_get_parent ((GFile *) job->files->data);
      if (source_dir) {
            inf = g_file_query_filesystem_info (source_dir, "filesystem::readonly", NULL, NULL);
            if (inf != NULL) {
                  readonly_source_fs = g_file_info_get_attribute_boolean (inf, "filesystem::readonly");
                  g_object_unref (inf);
            }
            g_object_unref (source_dir);
      }

      unique_names = (job->destination == NULL);
      i = 0;
      for (l = job->files;
           l != NULL && !job_aborted (common);
           l = l->next) {
            src = l->data;

            if (i < job->n_icon_positions) {
                  point = &job->icon_positions[i];
            } else {
                  point = NULL;
            }

            
            same_fs = FALSE;
            if (dest_fs_id) {
                  same_fs = has_fs_id (src, dest_fs_id);
            }

            if (job->destination) {
                  dest = g_object_ref (job->destination);
            } else {
                  dest = g_file_get_parent (src);
                  
            }
            if (dest) {
                  skipped_file = FALSE;
                  copy_move_file (job, src, dest,
                              same_fs, unique_names,
                              &dest_fs_type,
                              source_info, transfer_info,
                              job->debuting_files,
                              point, FALSE, &skipped_file,
                              readonly_source_fs);
                  g_object_unref (dest);
            }
            i++;
      }

      g_free (dest_fs_type);
}

static gboolean
copy_job_done (gpointer user_data)
{
      CopyMoveJob *job;

      job = user_data;
      if (job->done_callback) {
            job->done_callback (job->debuting_files, job->done_callback_data);
      }

      eel_g_object_list_free (job->files);
      if (job->destination) {
            g_object_unref (job->destination);
      }
      if (job->desktop_location) {
            g_object_unref (job->desktop_location);
      }
      g_hash_table_unref (job->debuting_files);
      g_free (job->icon_positions);
      
      finalize_common ((CommonJob *)job);

      nautilus_file_changes_consume_changes (TRUE);
      return FALSE;
}

static gboolean
copy_job (GIOSchedulerJob *io_job,
        GCancellable *cancellable,
        gpointer user_data)
{
      CopyMoveJob *job;
      CommonJob *common;
      SourceInfo source_info;
      TransferInfo transfer_info;
      char *dest_fs_id;
      GFile *dest;

      job = user_data;
      common = &job->common;
      common->io_job = io_job;

      dest_fs_id = NULL;
      
      nautilus_progress_info_start (job->common.progress);
      
      scan_sources (job->files,
                  &source_info,
                  common,
                  OP_KIND_COPY);
      if (job_aborted (common)) {
            goto aborted;
      }

      if (job->destination) {
            dest = g_object_ref (job->destination);
      } else {
            /* Duplication, no dest,
             * use source for free size, etc
             */
            dest = g_file_get_parent (job->files->data);
      }
      
      verify_destination (&job->common,
                      dest,
                      &dest_fs_id,
                      source_info.num_bytes);
      g_object_unref (dest);
      if (job_aborted (common)) {
            goto aborted;
      }

      g_timer_start (job->common.time);
      
      memset (&transfer_info, 0, sizeof (transfer_info));
      copy_files (job,
                dest_fs_id,
                &source_info, &transfer_info);

 aborted:
      
      g_free (dest_fs_id);
      
      g_io_scheduler_job_send_to_mainloop_async (io_job,
                                       copy_job_done,
                                       job,
                                       NULL);

      return FALSE;
}

void
nautilus_file_operations_copy (GList *files,
                         GArray *relative_item_points,
                         GFile *target_dir,
                         GtkWindow *parent_window,
                         NautilusCopyCallback  done_callback,
                         gpointer done_callback_data)
{
      CopyMoveJob *job;

      job = op_job_new (CopyMoveJob, parent_window);
      job->desktop_location = nautilus_get_desktop_location ();
      job->done_callback = done_callback;
      job->done_callback_data = done_callback_data;
      job->files = eel_g_object_list_copy (files);
      job->destination = g_object_ref (target_dir);
      if (relative_item_points != NULL &&
          relative_item_points->len > 0) {
            job->icon_positions =
                  g_memdup (relative_item_points->data,
                          sizeof (GdkPoint) * relative_item_points->len);
            job->n_icon_positions = relative_item_points->len;
      }
      job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);

      inhibit_power_manager ((CommonJob *)job, _("Copying Files"));

      g_io_scheduler_push_job (copy_job,
                     job,
                     NULL, /* destroy notify */
                     0,
                     job->common.cancellable);
}

static void
report_move_progress (CopyMoveJob *move_job, int total, int left)
{
      CommonJob *job;

      job = (CommonJob *)move_job;
      
      nautilus_progress_info_take_status (job->progress,
                                  f (_("Preparing to Move to \"%B\""),
                                     move_job->destination));

      nautilus_progress_info_take_details (job->progress,
                                   f (ngettext ("Preparing to move %'d file",
                                            "Preparing to move %'d files",
                                            left), left));

      nautilus_progress_info_pulse_progress (job->progress);
}

typedef struct {
      GFile *file;
      gboolean overwrite;
      gboolean has_position;
      GdkPoint position;
} MoveFileCopyFallback;

static MoveFileCopyFallback *
move_copy_file_callback_new (GFile *file,
                       gboolean overwrite,
                       GdkPoint *position)
{
      MoveFileCopyFallback *fallback;

      fallback = g_new (MoveFileCopyFallback, 1);
      fallback->file = file;
      fallback->overwrite = overwrite;
      if (position) {
            fallback->has_position = TRUE;
            fallback->position = *position;
      } else {
            fallback->has_position = FALSE;
      }

      return fallback;
}

static GList *
get_files_from_fallbacks (GList *fallbacks)
{
      MoveFileCopyFallback *fallback;
      GList *res, *l;

      res = NULL;
      for (l = fallbacks; l != NULL; l = l->next) {
            fallback = l->data;
            res = g_list_prepend (res, fallback->file);
      }
      return g_list_reverse (res);
}

static void
move_file_prepare (CopyMoveJob *move_job,
               GFile *src,
               GFile *dest_dir,
               gboolean same_fs,
               char **dest_fs_type,
               GHashTable *debuting_files,
               GdkPoint *position,
               GList **fallback_files,
               int files_left)
{
      GFile *dest, *new_dest;
      GError *error;
      CommonJob *job;
      gboolean overwrite;
      char *primary, *secondary, *details;
      int response;
      GFileCopyFlags flags;
      MoveFileCopyFallback *fallback;
      gboolean handled_invalid_filename;

      overwrite = FALSE;
      handled_invalid_filename = *dest_fs_type != NULL;

      job = (CommonJob *)move_job;
      
      dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);


      /* 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 (test_dir_is_parent (dest_dir, src)) {
            if (job->skip_all_error) {
                  goto out;
            }
            
            /*  the run_warning() frees all strings passed in automatically  */
            primary = move_job->is_move ? g_strdup (_("You cannot move a folder into itself."))
                                  : g_strdup (_("You cannot copy a folder into itself."));
            secondary = g_strdup (_("The destination folder is inside the source folder."));
            
            response = run_warning (job,
                              primary,
                              secondary,
                              NULL,
                              files_left > 1,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                              NULL);
            
            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) { /* skip all */
                  job->skip_all_error = TRUE;
            } else if (response == 2) { /* skip */
                  /* do nothing */
            } else {
                  g_assert_not_reached ();
            }

            goto out;
      }

 retry:
      
      flags = G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_NO_FALLBACK_FOR_MOVE;
      if (overwrite) {
            flags |= G_FILE_COPY_OVERWRITE;
      }
      
      error = NULL;
      if (g_file_move (src, dest,
                   flags,
                   job->cancellable,
                   NULL,
                   NULL,
                   &error)) {
            
            if (debuting_files) {
                  g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
            }
            
            nautilus_file_changes_queue_file_moved (src, dest);
            if (position) {
                  nautilus_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
            } else {
                  nautilus_file_changes_queue_schedule_position_remove (dest);
            }
            
            return;
      }

      if (IS_IO_ERROR (error, INVALID_FILENAME) &&
          !handled_invalid_filename) {
            handled_invalid_filename = TRUE;

            g_assert (*dest_fs_type == NULL);
            *dest_fs_type = query_fs_type (dest_dir, job->cancellable);

            new_dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs);
            if (!g_file_equal (dest, new_dest)) {
                  g_object_unref (dest);
                  dest = new_dest;
                  goto retry;
            } else {
                  g_object_unref (new_dest);
            }
      }

      /* Conflict */
      else if (!overwrite &&
             IS_IO_ERROR (error, EXISTS)) {
            gboolean is_merge;
            
            g_error_free (error);
            
            is_merge = FALSE;
            if (is_dir (dest)) {
                  if (is_dir (src)) {
                        is_merge = TRUE;
                        primary = f (_("A folder named \"%B\" already exists.  Do you want to merge the source folder?"), 
                                   dest);
                        secondary = f (_("The source folder already exists in \"%B\".  "
                                     "Merging will ask for confirmation before replacing any files in the folder that conflict with the files being moved."), 
                                     dest_dir);
                        
                  } else {
                        primary = f (_("A folder named \"%B\" already exists.  Do you want to replace it?"), 
                                              dest);
                        secondary = f (_("The folder already exists in \"%F\".  "
                                     "Replacing it will remove all files in the folder."), 
                                     dest_dir);
                  }
            } else {
                  primary = f (_("A file named \"%B\" already exists.  Do you want to replace it?"), 
                             dest);
                  secondary = f (_("The file already exists in \"%F\".  "
                               "Replacing it will overwrite its content."), 
                               dest_dir);
            }

            if ((is_merge && job->merge_all) ||
                (!is_merge && job->replace_all)) {
                  g_free (primary);
                  g_free (secondary);
                  
                  overwrite = TRUE;
                  goto retry;
            }

            if (job->skip_all_conflict) {
                  g_free (primary);
                  g_free (secondary);
                  
                  goto out;
            }
            
            response = run_warning (job,
                              primary,
                              secondary,
                              NULL,
                              files_left > 1,
                              GTK_STOCK_CANCEL,
                              SKIP_ALL,
                              is_merge?MERGE_ALL:REPLACE_ALL,
                              SKIP,
                              is_merge?MERGE:REPLACE,
                              NULL);
            
            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1 || response == 3) { /* skip all / skip */
                  if (response == 1) {
                        job->skip_all_conflict = TRUE;
                  }
            } else if (response == 2 || response == 4) { /* merge/replace all  / merge/replace*/
                  if (response == 2) {
                        if (is_merge) {
                              job->merge_all = TRUE;
                        } else {
                              job->replace_all = TRUE;
                        }
                  }
                  overwrite = TRUE;
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }
      }

      else if (IS_IO_ERROR (error, WOULD_RECURSE) ||
             IS_IO_ERROR (error, WOULD_MERGE) ||
             IS_IO_ERROR (error, NOT_SUPPORTED) ||
             (overwrite && IS_IO_ERROR (error, IS_DIRECTORY))) {
            g_error_free (error);
            
            fallback = move_copy_file_callback_new (src,
                                          overwrite, 
                                          position);
            *fallback_files = g_list_prepend (*fallback_files, fallback);
      }
      
      else if (IS_IO_ERROR (error, CANCELLED)) {
            g_error_free (error);
      }
      
      /* Other error */
      else {
            if (job->skip_all_error) {
                  goto out;
            }
            primary = f (_("Error while moving \"%B\"."), src);
            secondary = f (_("There was an error moving the file into %F."), dest_dir);
            details = error->message;
            
            response = run_warning (job,
                              primary,
                              secondary,
                              details,
                              files_left > 1,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                              NULL);

            g_error_free (error);
            
            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (job);
            } else if (response == 1) { /* skip all */
                  job->skip_all_error = TRUE;
            } else if (response == 2) { /* skip */
                  /* do nothing */
            } else {
                  g_assert_not_reached ();
            }
      }
      
 out:
      g_object_unref (dest);
}

static void
move_files_prepare (CopyMoveJob *job,
                const char *dest_fs_id,
                char **dest_fs_type,
                GList **fallbacks)
{
      CommonJob *common;
      GList *l;
      GFile *src;
      gboolean same_fs;
      int i;
      GdkPoint *point;
      int total, left;

      common = &job->common;

      total = left = g_list_length (job->files);

      report_move_progress (job, total, left);

      i = 0;
      for (l = job->files;
           l != NULL && !job_aborted (common);
           l = l->next) {
            src = l->data;

            if (i < job->n_icon_positions) {
                  point = &job->icon_positions[i];
            } else {
                  point = NULL;
            }

            
            same_fs = FALSE;
            if (dest_fs_id) {
                  same_fs = has_fs_id (src, dest_fs_id);
            }
            
            move_file_prepare (job, src, job->destination,
                           same_fs, dest_fs_type,
                           job->debuting_files,
                           point,
                           fallbacks,
                           left);
            report_move_progress (job, total, --left);
            i++;
      }

      *fallbacks = g_list_reverse (*fallbacks);

      
}

static void
move_files (CopyMoveJob *job,
          GList *fallbacks,
          const char *dest_fs_id,
          char **dest_fs_type,
          SourceInfo *source_info,
          TransferInfo *transfer_info)
{
      CommonJob *common;
      GList *l;
      GFile *src;
      gboolean same_fs;
      int i;
      GdkPoint *point;
      gboolean skipped_file;
      MoveFileCopyFallback *fallback;
common = &job->common;

      report_copy_progress (job, source_info, transfer_info);
      
      i = 0;
      for (l = fallbacks;
           l != NULL && !job_aborted (common);
           l = l->next) {
            fallback = l->data;
            src = fallback->file;

            if (fallback->has_position) {
                  point = &fallback->position;
            } else {
                  point = NULL;
            }
            
            same_fs = FALSE;
            if (dest_fs_id) {
                  same_fs = has_fs_id (src, dest_fs_id);
            }

            /* Set overwrite to true, as the user has
               selected overwrite on all toplevel items */
            skipped_file = FALSE;
            copy_move_file (job, src, job->destination,
                        same_fs, FALSE, dest_fs_type,
                        source_info, transfer_info,
                        job->debuting_files,
                        point, fallback->overwrite, &skipped_file, FALSE);
            i++;
      }
}


static gboolean
move_job_done (gpointer user_data)
{
      CopyMoveJob *job;

      job = user_data;
      if (job->done_callback) {
            job->done_callback (job->debuting_files, job->done_callback_data);
      }

      eel_g_object_list_free (job->files);
      g_object_unref (job->destination);
      g_hash_table_unref (job->debuting_files);
      g_free (job->icon_positions);
      
      finalize_common ((CommonJob *)job);

      nautilus_file_changes_consume_changes (TRUE);
      return FALSE;
}

static gboolean
move_job (GIOSchedulerJob *io_job,
        GCancellable *cancellable,
        gpointer user_data)
{
      CopyMoveJob *job;
      CommonJob *common;
      GList *fallbacks;
      SourceInfo source_info;
      TransferInfo transfer_info;
      char *dest_fs_id;
      char *dest_fs_type;
      GList *fallback_files;

      job = user_data;
      common = &job->common;
      common->io_job = io_job;

      dest_fs_id = NULL;
      dest_fs_type = NULL;

      fallbacks = NULL;
      
      nautilus_progress_info_start (job->common.progress);
      
      verify_destination (&job->common,
                      job->destination,
                      &dest_fs_id,
                      -1);
      if (job_aborted (common)) {
            goto aborted;
      }

      /* This moves all files that we can do without copy + delete */
      move_files_prepare (job, dest_fs_id, &dest_fs_type, &fallbacks);
      if (job_aborted (common)) {
            goto aborted;
      }

      /* The rest we need to do deep copy + delete behind on,
         so scan for size */

      fallback_files = get_files_from_fallbacks (fallbacks);
      scan_sources (fallback_files,
                  &source_info,
                  common,
                  OP_KIND_MOVE);
      
      g_list_free (fallback_files);
      
      if (job_aborted (common)) {
            goto aborted;
      }

      verify_destination (&job->common,
                      job->destination,
                      NULL,
                      source_info.num_bytes);
      if (job_aborted (common)) {
            goto aborted;
      }

      memset (&transfer_info, 0, sizeof (transfer_info));
      move_files (job,
                fallbacks,
                dest_fs_id, &dest_fs_type,
                &source_info, &transfer_info);

 aborted:
      eel_g_list_free_deep (fallbacks);

      g_free (dest_fs_id);
      g_free (dest_fs_type);
      
      g_io_scheduler_job_send_to_mainloop (io_job,
                                   move_job_done,
                                   job,
                                   NULL);

      return FALSE;
}

void
nautilus_file_operations_move (GList *files,
                         GArray *relative_item_points,
                         GFile *target_dir,
                         GtkWindow *parent_window,
                         NautilusCopyCallback  done_callback,
                         gpointer done_callback_data)
{
      CopyMoveJob *job;

      job = op_job_new (CopyMoveJob, parent_window);
      job->is_move = TRUE;
      job->done_callback = done_callback;
      job->done_callback_data = done_callback_data;
      job->files = eel_g_object_list_copy (files);
      job->destination = g_object_ref (target_dir);
      if (relative_item_points != NULL &&
          relative_item_points->len > 0) {
            job->icon_positions =
                  g_memdup (relative_item_points->data,
                          sizeof (GdkPoint) * relative_item_points->len);
            job->n_icon_positions = relative_item_points->len;
      }
      job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);

      inhibit_power_manager ((CommonJob *)job, _("Moving Files"));

      g_io_scheduler_push_job (move_job,
                         job,
                         NULL, /* destroy notify */
                         0,
                         job->common.cancellable);
}

static void
report_link_progress (CopyMoveJob *link_job, int total, int left)
{
      CommonJob *job;

      job = (CommonJob *)link_job;
      
      nautilus_progress_info_take_status (job->progress,
                                  f (_("Creating links in \"%B\""),
                                     link_job->destination));

      nautilus_progress_info_take_details (job->progress,
                                   f (ngettext ("Making link to %'d file",
                                            "Making links to %'d files",
                                            left), left));

      nautilus_progress_info_set_progress (job->progress, left, total);
}

static char *
get_abs_path_for_symlink (GFile *file)
{
      GFile *root, *parent;
      char *relative, *abs;
      
      if (g_file_is_native (file)) {
            return g_file_get_path (file);
      }

      root = g_object_ref (file);
      while ((parent = g_file_get_parent (root)) != NULL) {
            g_object_unref (root);
            root = parent;
      }
      
      relative = g_file_get_relative_path (root, file);
      g_object_unref (root);
      abs = g_strconcat ("/", relative, NULL);
      g_free (relative);
      return abs;
}


static void
link_file (CopyMoveJob *job,
         GFile *src, GFile *dest_dir,
         char **dest_fs_type,
         GHashTable *debuting_files,
         GdkPoint *position,
         int files_left)
{
      GFile *src_dir, *dest, *new_dest;
      int count;
      char *path;
      gboolean not_local;
      GError *error;
      CommonJob *common;
      char *primary, *secondary, *details;
      int response;
      gboolean handled_invalid_filename;

      common = (CommonJob *)job;

      count = 0;

      src_dir = g_file_get_parent (src);
      if (g_file_equal (src_dir, dest_dir)) {
            count = 1;
      }
      g_object_unref (src_dir);

      handled_invalid_filename = *dest_fs_type != NULL;

      dest = get_target_file_for_link (src, dest_dir, *dest_fs_type, count);

 retry:
      error = NULL;
      not_local = FALSE;
      
      path = get_abs_path_for_symlink (src);
      if (path == NULL) {
            not_local = TRUE;
      } else if (g_file_make_symbolic_link (dest,
                                    path, 
                                    common->cancellable,
                                    &error)) {
            g_free (path);
            if (debuting_files) {
                  g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
            }
            
            nautilus_file_changes_queue_file_added (dest);
            if (position) {
                  nautilus_file_changes_queue_schedule_position_set (dest, *position, common->screen_num);
            } else {
                  nautilus_file_changes_queue_schedule_position_remove (dest);
            }

            g_object_unref (dest);
            
            return;
      }
      g_free (path);

      if (error != NULL &&
          IS_IO_ERROR (error, INVALID_FILENAME) &&
          !handled_invalid_filename) {
            handled_invalid_filename = TRUE;

            g_assert (*dest_fs_type == NULL);
            *dest_fs_type = query_fs_type (dest_dir, common->cancellable);

            new_dest = get_target_file_for_link (src, dest_dir, *dest_fs_type, count);

            if (!g_file_equal (dest, new_dest)) {
                  g_object_unref (dest);
                  dest = new_dest;
                  g_error_free (error);

                  goto retry;
            } else {
                  g_object_unref (new_dest);
            }
      }
      /* Conflict */
      if (error != NULL && IS_IO_ERROR (error, EXISTS)) {
            g_object_unref (dest);
            dest = get_target_file_for_link (src, dest_dir, *dest_fs_type, count++);
            g_error_free (error);
            goto retry;
      }

      else if (error != NULL && IS_IO_ERROR (error, CANCELLED)) {
            g_error_free (error);
      }
      
      /* Other error */
      else {
            if (common->skip_all_error) {
                  goto out;
            }
            primary = f (_("Error while creating link to %B."), src);
            if (not_local) {
                  secondary = f (_("Symbolic links only supported for local files"));
                  details = NULL;
            } else if (IS_IO_ERROR (error, NOT_SUPPORTED)) {
                  secondary = f (_("The target doesn't support symbolic links."));
                  details = NULL;
            } else {
                  secondary = f (_("There was an error creating the symlink in %F."), dest_dir);
                  details = error->message;
            }
            
            response = run_warning (common,
                              primary,
                              secondary,
                              details,
                              files_left > 1,
                              GTK_STOCK_CANCEL, SKIP_ALL, SKIP,
                              NULL);

            if (error) {
                  g_error_free (error);
            }
            
            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (common);
            } else if (response == 1) { /* skip all */
                  common->skip_all_error = TRUE;
            } else if (response == 2) { /* skip */
                  /* do nothing */
            } else {
                  g_assert_not_reached ();
            }
      }
      
 out:
      g_object_unref (dest);
}

static gboolean
link_job_done (gpointer user_data)
{
      CopyMoveJob *job;

      job = user_data;
      if (job->done_callback) {
            job->done_callback (job->debuting_files, job->done_callback_data);
      }

      eel_g_object_list_free (job->files);
      g_object_unref (job->destination);
      g_hash_table_unref (job->debuting_files);
      g_free (job->icon_positions);
      
      finalize_common ((CommonJob *)job);

      nautilus_file_changes_consume_changes (TRUE);
      return FALSE;
}

static gboolean
link_job (GIOSchedulerJob *io_job,
        GCancellable *cancellable,
        gpointer user_data)
{
      CopyMoveJob *job;
      CommonJob *common;
      GList *copy_files;
      GArray *copy_positions;
      GFile *src;
      GdkPoint *point;
      char *dest_fs_type;
      int total, left;
      int i;
      GList *l;

      job = user_data;
      common = &job->common;
      common->io_job = io_job;

      copy_files = NULL;
      copy_positions = NULL;

      dest_fs_type = NULL;
      
      nautilus_progress_info_start (job->common.progress);
      
      verify_destination (&job->common,
                      job->destination,
                      NULL,
                      -1);
      if (job_aborted (common)) {
            goto aborted;
      }

      total = left = g_list_length (job->files);
      
      report_link_progress (job, total, left);

      i = 0;
      for (l = job->files;
           l != NULL && !job_aborted (common);
           l = l->next) {
            src = l->data;

            if (i < job->n_icon_positions) {
                  point = &job->icon_positions[i];
            } else {
                  point = NULL;
            }

            
            link_file (job, src, job->destination,
                     &dest_fs_type, job->debuting_files,
                     point, left);
            report_link_progress (job, total, --left);
            i++;
            
      }

 aborted:
      g_free (dest_fs_type);
      
      g_io_scheduler_job_send_to_mainloop (io_job,
                                   link_job_done,
                                   job,
                                   NULL);

      return FALSE;
}

void
nautilus_file_operations_link (GList *files,
                         GArray *relative_item_points,
                         GFile *target_dir,
                         GtkWindow *parent_window,
                         NautilusCopyCallback  done_callback,
                         gpointer done_callback_data)
{
      CopyMoveJob *job;

      job = op_job_new (CopyMoveJob, parent_window);
      job->done_callback = done_callback;
      job->done_callback_data = done_callback_data;
      job->files = eel_g_object_list_copy (files);
      job->destination = g_object_ref (target_dir);
      if (relative_item_points != NULL &&
          relative_item_points->len > 0) {
            job->icon_positions =
                  g_memdup (relative_item_points->data,
                          sizeof (GdkPoint) * relative_item_points->len);
            job->n_icon_positions = relative_item_points->len;
      }
      job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);

      g_io_scheduler_push_job (link_job,
                     job,
                     NULL, /* destroy notify */
                     0,
                     job->common.cancellable);
}


void
nautilus_file_operations_duplicate (GList *files,
                            GArray *relative_item_points,
                            GtkWindow *parent_window,
                            NautilusCopyCallback  done_callback,
                            gpointer done_callback_data)
{
      CopyMoveJob *job;

      job = op_job_new (CopyMoveJob, parent_window);
      job->done_callback = done_callback;
      job->done_callback_data = done_callback_data;
      job->files = eel_g_object_list_copy (files);
      job->destination = NULL;
      if (relative_item_points != NULL &&
          relative_item_points->len > 0) {
            job->icon_positions =
                  g_memdup (relative_item_points->data,
                          sizeof (GdkPoint) * relative_item_points->len);
            job->n_icon_positions = relative_item_points->len;
      }
      job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);

      g_io_scheduler_push_job (copy_job,
                     job,
                     NULL, /* destroy notify */
                     0,
                     job->common.cancellable);
}

static gboolean
set_permissions_job_done (gpointer user_data)
{
      SetPermissionsJob *job;

      job = user_data;
      
      g_object_unref (job->file);

      if (job->done_callback) {
            job->done_callback (job->done_callback_data);
      }
      
      finalize_common ((CommonJob *)job);
      return FALSE;
}

static void
set_permissions_file (SetPermissionsJob *job,
                  GFile *file,
                  GFileInfo *info)
{
      CommonJob *common;
      GFileInfo *child_info;
      gboolean free_info;
      guint32 current;
      guint32 value;
      guint32 mask;
      GFileEnumerator *enumerator;
      GFile *child;
      
      common = (CommonJob *)job;

      nautilus_progress_info_pulse_progress (common->progress);
      
      free_info = FALSE;
      if (info == NULL) {
            free_info = TRUE;
            info = g_file_query_info (file,
                                G_FILE_ATTRIBUTE_STANDARD_TYPE","
                                G_FILE_ATTRIBUTE_UNIX_MODE,
                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                common->cancellable,
                                NULL);
            /* Ignore errors */
            if (info == NULL) {
                  return;
            }
      }

      if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
            value = job->dir_permissions;
            mask = job->dir_mask;
      } else {
            value = job->file_permissions;
            mask = job->file_mask;
      }

      
      if (!job_aborted (common) &&
          g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE)) {
            current = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
            current = (current & ~mask) | value;

            g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE,
                                   current, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                   common->cancellable, NULL);
      }
      
      if (!job_aborted (common) &&
          g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
            enumerator = g_file_enumerate_children (file,
                                          G_FILE_ATTRIBUTE_STANDARD_NAME","
                                          G_FILE_ATTRIBUTE_STANDARD_TYPE","
                                          G_FILE_ATTRIBUTE_UNIX_MODE,
                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                          common->cancellable,
                                          NULL);
            if (enumerator) {
                  while (!job_aborted (common) &&
                         (child_info = g_file_enumerator_next_file (enumerator, common->cancellable, NULL)) != NULL) {
                        child = g_file_get_child (file,
                                            g_file_info_get_name (child_info));
                        set_permissions_file (job, child, child_info);
                        g_object_unref (child);
                        g_object_unref (child_info);
                  }
                  g_file_enumerator_close (enumerator, common->cancellable, NULL);
                  g_object_unref (enumerator);
            } 
      }
      if (free_info) {
            g_object_unref (info);
      }
}


static gboolean
set_permissions_job (GIOSchedulerJob *io_job,
                 GCancellable *cancellable,
                 gpointer user_data)
{
      SetPermissionsJob *job = user_data;
      CommonJob *common;
      
      common = (CommonJob *)job;
      common->io_job = io_job;
      
      nautilus_progress_info_set_status (common->progress,
                                 _("Setting permissions"));

      nautilus_progress_info_start (job->common.progress);

      set_permissions_file (job, job->file, NULL);

      g_io_scheduler_job_send_to_mainloop_async (io_job,
                                       set_permissions_job_done,
                                       job,
                                       NULL);

      return FALSE;
}



void
nautilus_file_set_permissions_recursive (const char *directory,
                               guint32         file_permissions,
                               guint32         file_mask,
                               guint32         dir_permissions,
                               guint32         dir_mask,
                               NautilusOpCallback  callback,
                               gpointer  callback_data)
{
      SetPermissionsJob *job;
      
      job = op_job_new (SetPermissionsJob, NULL);
      job->file = g_file_new_for_uri (directory);
      job->file_permissions = file_permissions;
      job->file_mask = file_mask;
      job->dir_permissions = dir_permissions;
      job->dir_mask = dir_mask;
      job->done_callback = callback;
      job->done_callback_data = callback_data;
      
      g_io_scheduler_push_job (set_permissions_job,
                     job,
                     NULL,
                     0,
                     NULL);
}

static GList *
location_list_from_uri_list (const GList *uris)
{
      const GList *l;
      GList *files;
      GFile *f;

      files = NULL;
      for (l = uris; l != NULL; l = l->next) {
            f = g_file_new_for_uri (l->data);
            files = g_list_prepend (files, f);
      }

      return g_list_reverse (files);
}

typedef struct {
      NautilusCopyCallback real_callback;
      gpointer real_data;
} MoveTrashCBData;

static void
callback_for_move_to_trash (GHashTable *debuting_uris,
                      gboolean user_cancelled,
                      MoveTrashCBData *data)
{
      if (data->real_callback)
            data->real_callback (debuting_uris, data->real_data);
      g_slice_free (MoveTrashCBData, data);
}

void
nautilus_file_operations_copy_move (const GList *item_uris,
                            GArray *relative_item_points,
                            const char *target_dir,
                            GdkDragAction copy_action,
                            GtkWidget *parent_view,
                            NautilusCopyCallback  done_callback,
                            gpointer done_callback_data)
{
      GList *locations;
      GList *p;
      GFile *dest, *src_dir;
      GtkWindow *parent_window;
      gboolean target_is_mapping;
      gboolean have_nonmapping_source;
                                    
      dest = NULL;
      target_is_mapping = FALSE;
      have_nonmapping_source = FALSE;
                
      if (target_dir) {
            dest = g_file_new_for_uri (target_dir);
            if (g_file_has_uri_scheme (dest, "burn")) {
                  target_is_mapping = TRUE;
                }
      }

      locations = location_list_from_uri_list (item_uris);
      
      for (p = location_list_from_uri_list (item_uris); p != NULL; p = p->next) {
            if (!g_file_has_uri_scheme ((GFile* )p->data, "burn")) {                
                  have_nonmapping_source = TRUE;
            }
      }
      
      if (target_is_mapping && have_nonmapping_source && copy_action == GDK_ACTION_MOVE) {
            /* never move to "burn:///", but fall back to copy.
             * This is a workaround, because otherwise the source files would be removed.
             */
            copy_action = GDK_ACTION_COPY;
      }
      
      parent_window = NULL;
      if (parent_view) {
            parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
      }
      
      if (copy_action == GDK_ACTION_COPY) {
            src_dir = g_file_get_parent (locations->data);
            if (target_dir == NULL ||
                (src_dir != NULL &&
                 g_file_equal (src_dir, dest))) {
                  nautilus_file_operations_duplicate (locations,
                                              relative_item_points,
                                              parent_window,
                                              done_callback, done_callback_data);
            } else {
                  nautilus_file_operations_copy (locations,
                                           relative_item_points,
                                           dest,
                                           parent_window,
                                           done_callback, done_callback_data);
            }
            if (src_dir) {
                  g_object_unref (src_dir);
            }
            
      } else if (copy_action == GDK_ACTION_MOVE) {
            if (g_file_has_uri_scheme (dest, "trash")) {
                  MoveTrashCBData *cb_data;
                  
                  cb_data = g_slice_new0 (MoveTrashCBData);
                  cb_data->real_callback = done_callback;
                  cb_data->real_data = done_callback_data;
                  nautilus_file_operations_trash_or_delete (locations,
                                                  parent_window,
                                                  (NautilusDeleteCallback) callback_for_move_to_trash,
                                                  cb_data);
            } else {
                  nautilus_file_operations_move (locations,
                                           relative_item_points,
                                           dest,
                                           parent_window,
                                           done_callback, done_callback_data);
            }
      } else {
            nautilus_file_operations_link (locations,
                                     relative_item_points,
                                     dest,
                                     parent_window,
                                     done_callback, done_callback_data);
      }
      
      eel_g_object_list_free (locations);
      if (dest) {
            g_object_unref (dest);
      }
}

static gboolean
create_job_done (gpointer user_data)
{
      CreateJob *job;

      job = user_data;
      if (job->done_callback) {
            job->done_callback (job->created_file, job->done_callback_data);
      }

      g_object_unref (job->dest_dir);
      if (job->src) {
            g_object_unref (job->src);
      }
      g_free (job->src_data);
      g_free (job->filename);
      if (job->created_file) {
            g_object_unref (job->created_file);
      }
      
      finalize_common ((CommonJob *)job);

      nautilus_file_changes_consume_changes (TRUE);
      return FALSE;
}

static gboolean
create_job (GIOSchedulerJob *io_job,
          GCancellable *cancellable,
          gpointer user_data)
{
      CreateJob *job;
      CommonJob *common;
      int count;
      GFile *dest;
      char *filename, *filename2, *new_filename;
      char *dest_fs_type;
      GError *error;
      gboolean res;
      gboolean filename_is_utf8;
      char *primary, *secondary, *details;
      int response;
      char *data;
      int length;
      GFileOutputStream *out;
      gboolean handled_invalid_filename;
      int max_length;

      job = user_data;
      common = &job->common;
      common->io_job = io_job;

      nautilus_progress_info_start (job->common.progress);

      handled_invalid_filename = FALSE;

      dest_fs_type = NULL;
      filename = NULL;
      dest = NULL;

      max_length = get_max_name_length (job->dest_dir);

      verify_destination (common,
                      job->dest_dir,
                      NULL, -1);
      if (job_aborted (common)) {
            goto aborted;
      }

      filename = g_strdup (job->filename);
      filename_is_utf8 = FALSE;
      if (filename) {
            filename_is_utf8 = g_utf8_validate (filename, -1, NULL);          
      }
      if (filename == NULL) {
            if (job->make_dir) {
                  /* localizers: the initial name of a new folder  */
                  filename = g_strdup (_("untitled folder"));
                  filename_is_utf8 = TRUE; /* Pass in utf8 */
            } else {
                  if (job->src != NULL) {
                        filename = g_file_get_basename (job->src);
                  }
                  if (filename == NULL) {
                        /* localizers: the initial name of a new empty file */
                        filename = g_strdup (_("new file"));
                        filename_is_utf8 = TRUE; /* Pass in utf8 */
                  }
            }
      } 

      make_file_name_valid_for_dest_fs (filename, dest_fs_type);
      if (filename_is_utf8) {
            dest = g_file_get_child_for_display_name (job->dest_dir, filename, NULL);
      }
      if (dest == NULL) {
            dest = g_file_get_child (job->dest_dir, filename);
      }
      count = 1;

 retry:

      error = NULL;
      if (job->make_dir) {
            res = g_file_make_directory (dest,
                                   common->cancellable,
                                   &error);
      } else {
            if (job->src) {
                  res = g_file_copy (job->src,
                                 dest,
                                 G_FILE_COPY_NONE,
                                 common->cancellable,
                                 NULL, NULL,
                                 &error);
            } else {
                  data = "";
                  length = 0;
                  if (job->src_data) {
                        data = job->src_data;
                        length = job->length;
                  }

                  out = g_file_create (dest,
                                   G_FILE_CREATE_NONE,
                                   common->cancellable,
                                   &error);
                  if (out) {
                        res = g_output_stream_write_all (G_OUTPUT_STREAM (out),
                                                 data, length,
                                                 NULL,
                                                 common->cancellable,
                                                 &error);
                        if (res) {
                              res = g_output_stream_close (G_OUTPUT_STREAM (out),
                                                     common->cancellable,
                                                     &error);
                        }

                        /* This will close if the write failed and we didn't close */
                        g_object_unref (out);
                  } else {
                        res = FALSE;
                  }
            }
      }

      if (res) {
            job->created_file = g_object_ref (dest);
            nautilus_file_changes_queue_file_added (dest);
            if (job->has_position) {
                  nautilus_file_changes_queue_schedule_position_set (dest, job->position, common->screen_num);
            } else {
                  nautilus_file_changes_queue_schedule_position_remove (dest);
            }
      } else {
            g_assert (error != NULL);

            if (IS_IO_ERROR (error, INVALID_FILENAME) &&
                !handled_invalid_filename) {
                  handled_invalid_filename = TRUE;

                  g_assert (dest_fs_type == NULL);
                  dest_fs_type = query_fs_type (job->dest_dir, common->cancellable);

                  g_object_unref (dest);

                  if (count == 1) {
                        new_filename = g_strdup (filename);
                  } else if (job->make_dir) {
                        filename2 = g_strdup_printf ("%s %d", filename, count);

                        new_filename = NULL;
                        if (max_length > 0 && strlen (filename2) > max_length) {
                              new_filename = shorten_utf8_string (filename2, strlen (filename2) - max_length);
                        }

                        if (new_filename == NULL) {
                              new_filename = g_strdup (filename2);
                        }

                        g_free (filename2);
                  } else {
                        new_filename = get_duplicate_name (filename, count, max_length);
                  }

                  if (make_file_name_valid_for_dest_fs (new_filename, dest_fs_type)) {
                        g_object_unref (dest);

                        if (filename_is_utf8) {
                              dest = g_file_get_child_for_display_name (job->dest_dir, new_filename, NULL);
                        }
                        if (dest == NULL) {
                              dest = g_file_get_child (job->dest_dir, new_filename);
                        }

                        g_free (new_filename);
                        g_error_free (error);
                        goto retry;
                  }
                  g_free (new_filename);
            } else if (IS_IO_ERROR (error, EXISTS)) {
                  g_object_unref (dest);
                  dest = NULL;
                  if (job->make_dir) {
                        filename2 = g_strdup_printf ("%s %d", filename, ++count);
                        if (max_length > 0 && strlen (filename2) > max_length) {
                              new_filename = shorten_utf8_string (filename2, strlen (filename2) - max_length);
                              if (new_filename != NULL) {
                                    g_free (filename2);
                                    filename2 = new_filename;
                              }
                        }
                  } else {
                        filename2 = get_duplicate_name (filename, count++, max_length);
                  }
                  make_file_name_valid_for_dest_fs (filename2, dest_fs_type);
                  if (filename_is_utf8) {
                        dest = g_file_get_child_for_display_name (job->dest_dir, filename2, NULL);
                  }
                  if (dest == NULL) {
                        dest = g_file_get_child (job->dest_dir, filename2);
                  }
                  g_free (filename2);
                  g_error_free (error);
                  goto retry;
            }
            
            else if (IS_IO_ERROR (error, CANCELLED)) {
                  g_error_free (error);
            }
            
            /* Other error */
            else {
                  if (job->make_dir) {
                        primary = f (_("Error while creating directory %B."), dest);
                  } else {
                        primary = f (_("Error while creating file %B."), dest);
                  }
                  secondary = f (_("There was an error creating the directory in %F."), job->dest_dir);
                  details = error->message;
            
                  response = run_warning (common,
                                    primary,
                                    secondary,
                                    details,
                                    FALSE,
                                    GTK_STOCK_CANCEL, SKIP,
                                    NULL);
                  
                  g_error_free (error);
            
                  if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                        abort_job (common);
                  } else if (response == 1) { /* skip */
                        /* do nothing */
                  } else {
                        g_assert_not_reached ();
                  }
            }
      }

 aborted:
      if (dest) {
            g_object_unref (dest);
      }
      g_free (filename);
      g_free (dest_fs_type);
      g_io_scheduler_job_send_to_mainloop_async (io_job,
                                       create_job_done,
                                       job,
                                       NULL);

      return FALSE;
}

void 
nautilus_file_operations_new_folder (GtkWidget *parent_view, 
                             GdkPoint *target_point,
                             const char *parent_dir,
                             NautilusCreateCallback done_callback,
                             gpointer done_callback_data)
{
      CreateJob *job;
      GtkWindow *parent_window;

      parent_window = NULL;
      if (parent_view) {
            parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
      }

      job = op_job_new (CreateJob, parent_window);
      job->done_callback = done_callback;
      job->done_callback_data = done_callback_data;
      job->dest_dir = g_file_new_for_uri (parent_dir);
      job->make_dir = TRUE;
      if (target_point != NULL) {
            job->position = *target_point;
            job->has_position = TRUE;
      }

      g_io_scheduler_push_job (create_job,
                     job,
                     NULL, /* destroy notify */
                     0,
                     job->common.cancellable);
}

void 
nautilus_file_operations_new_file_from_template (GtkWidget *parent_view, 
                                     GdkPoint *target_point,
                                     const char *parent_dir,
                                     const char *target_filename,
                                     const char *template_uri,
                                     NautilusCreateCallback done_callback,
                                     gpointer done_callback_data)
{
      CreateJob *job;
      GtkWindow *parent_window;

      parent_window = NULL;
      if (parent_view) {
            parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
      }

      job = op_job_new (CreateJob, parent_window);
      job->done_callback = done_callback;
      job->done_callback_data = done_callback_data;
      job->dest_dir = g_file_new_for_uri (parent_dir);
      if (target_point != NULL) {
            job->position = *target_point;
            job->has_position = TRUE;
      }
      job->filename = g_strdup (target_filename);

      if (template_uri) {
            job->src = g_file_new_for_uri (template_uri);
      }

      g_io_scheduler_push_job (create_job,
                     job,
                     NULL, /* destroy notify */
                     0,
                     job->common.cancellable);
}

void 
nautilus_file_operations_new_file (GtkWidget *parent_view, 
                           GdkPoint *target_point,
                           const char *parent_dir,
                           const char *target_filename,
                           const char *initial_contents,
                           int length,
                           NautilusCreateCallback done_callback,
                           gpointer done_callback_data)
{
      CreateJob *job;
      GtkWindow *parent_window;

      parent_window = NULL;
      if (parent_view) {
            parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
      }

      job = op_job_new (CreateJob, parent_window);
      job->done_callback = done_callback;
      job->done_callback_data = done_callback_data;
      job->dest_dir = g_file_new_for_uri (parent_dir);
      if (target_point != NULL) {
            job->position = *target_point;
            job->has_position = TRUE;
      }
      job->src_data = g_memdup (initial_contents, length);
      job->length = length;
      job->filename = g_strdup (target_filename);

      g_io_scheduler_push_job (create_job,
                     job,
                     NULL, /* destroy notify */
                     0,
                     job->common.cancellable);
}



static void
delete_trash_file (CommonJob *job,
               GFile *file,
               gboolean del_file,
               gboolean del_children)
{
      GFileInfo *info;
      GFile *child;
      GFileEnumerator *enumerator;

      if (job_aborted (job)) {
            return;
      }

      if (del_children) {
            enumerator = g_file_enumerate_children (file,
                                          G_FILE_ATTRIBUTE_STANDARD_NAME ","
                                          G_FILE_ATTRIBUTE_STANDARD_TYPE,
                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                          job->cancellable,
                                          NULL);
            if (enumerator) {
                  while (!job_aborted (job) &&
                         (info = g_file_enumerator_next_file (enumerator, job->cancellable, NULL)) != NULL) {
                        child = g_file_get_child (file,
                                            g_file_info_get_name (info));
                        delete_trash_file (job, child, TRUE,
                                       g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY);
                        g_object_unref (child);
                        g_object_unref (info);
                  }
                  g_file_enumerator_close (enumerator, job->cancellable, NULL);
                  g_object_unref (enumerator);
            }
      }
      
      if (!job_aborted (job) && del_file) {
            g_file_delete (file, job->cancellable, NULL);
      }
}

static gboolean
empty_trash_job_done (gpointer user_data)
{
      EmptyTrashJob *job;

      job = user_data;
      
      eel_g_object_list_free (job->trash_dirs);

      if (job->done_callback) {
            job->done_callback (job->done_callback_data);
      }
      
      finalize_common ((CommonJob *)job);
      return FALSE;
}

static gboolean
empty_trash_job (GIOSchedulerJob *io_job,
             GCancellable *cancellable,
             gpointer user_data)
{
      EmptyTrashJob *job = user_data;
      CommonJob *common;
      GList *l;
      gboolean confirmed;
      
      common = (CommonJob *)job;
      common->io_job = io_job;
      
      nautilus_progress_info_start (job->common.progress);

      if (job->should_confirm) {
            confirmed = confirm_empty_trash (common);
      } else {
            confirmed = TRUE;
      }
      if (confirmed) {
            for (l = job->trash_dirs;
                 l != NULL && !job_aborted (common);
                 l = l->next) {
                  delete_trash_file (common, l->data, FALSE, TRUE);
            }
      }

      g_io_scheduler_job_send_to_mainloop_async (io_job,
                                       empty_trash_job_done,
                                       job,
                                       NULL);

      return FALSE;
}

void 
nautilus_file_operations_empty_trash (GtkWidget *parent_view)
{
      EmptyTrashJob *job;
      GtkWindow *parent_window;

      parent_window = NULL;
      if (parent_view) {
            parent_window = (GtkWindow *)gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW);
      }
      
      setup_autos ();
      
      job = op_job_new (EmptyTrashJob, parent_window);
      job->trash_dirs = g_list_prepend (job->trash_dirs,
                                g_file_new_for_uri ("trash:"));
      job->should_confirm = TRUE;

      inhibit_power_manager ((CommonJob *)job, _("Emptying Trash"));
      
      g_io_scheduler_push_job (empty_trash_job,
                     job,
                     NULL,
                     0,
                     NULL);
}

static gboolean
mark_trusted_job_done (gpointer user_data)
{
      MarkTrustedJob *job = user_data;
      
      g_object_unref (job->file);

      if (job->done_callback) {
            job->done_callback (job->done_callback_data);
      }
      
      finalize_common ((CommonJob *)job);
      return FALSE;
}

#define TRUSTED_SHEBANG "#!/usr/bin/env xdg-open\n"

static void
mark_desktop_file_trusted (CommonJob *common,
                     GCancellable *cancellable,
                     GFile *file,
                     gboolean interactive)
{
      char *contents, *new_contents;
      gsize length, new_length;
      GError *error;
      guint32 current_perms, new_perms;
      int response;
      GFileInfo *info;
      
 retry:
      error = NULL;
      if (!g_file_load_contents (file,
                          cancellable,
                          &contents, &length,
                          NULL, &error)) {
            if (interactive) {
                  response = run_error (common,
                                    g_strdup (_("Unable to mark launcher trusted (executable)")),
                                    error->message,
                                    NULL,
                                    FALSE,
                                    GTK_STOCK_CANCEL, RETRY,
                                    NULL);
            } else {
                  response = 0;
            }
            

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (common);
            } else if (response == 1) {
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }

            goto out;
      }

      if (!g_str_has_prefix (contents, "#!")) {
            new_length = length + strlen (TRUSTED_SHEBANG);
            new_contents = g_malloc (new_length);
            
            strcpy (new_contents, TRUSTED_SHEBANG);
            memcpy (new_contents + strlen (TRUSTED_SHEBANG),
                  contents, length);
            
            if (!g_file_replace_contents (file,
                                    new_contents,
                                    new_length,
                                    NULL,
                                    FALSE, 0,
                                    NULL, cancellable, &error)) {
                  g_free (contents);
                  g_free (new_contents);
                  
                  if (interactive) {
                        response = run_error (common,
                                          g_strdup (_("Unable to mark launcher trusted (executable)")),
                                          error->message,
                                          NULL,
                                          FALSE,
                                          GTK_STOCK_CANCEL, RETRY,
                                          NULL);
                  } else {
                        response = 0;
                  }

                  if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                        abort_job (common);
                  } else if (response == 1) {
                        goto retry;
                  } else {
                        g_assert_not_reached ();
                  }
                  
                  goto out;
            }
            g_free (new_contents);
            
      }
      g_free (contents);
      
      info = g_file_query_info (file,
                          G_FILE_ATTRIBUTE_STANDARD_TYPE","
                          G_FILE_ATTRIBUTE_UNIX_MODE,
                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                          common->cancellable,
                          &error);

      if (info == NULL) {
            if (interactive) {
                  response = run_error (common,
                                    g_strdup (_("Unable to mark launcher trusted (executable)")),
                                    error->message,
                                    NULL,
                                    FALSE,
                                    GTK_STOCK_CANCEL, RETRY,
                                    NULL);
            } else {
                  response = 0;
            }
            
            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                  abort_job (common);
            } else if (response == 1) {
                  goto retry;
            } else {
                  g_assert_not_reached ();
            }
            
            goto out;
      }
      
      
      if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE)) {
            current_perms = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
            new_perms = current_perms | S_IXGRP | S_IXUSR | S_IXOTH;

            if ((current_perms != new_perms) &&
                !g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE,
                                      new_perms, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                      common->cancellable, &error))
                  {
                        g_object_unref (info);
                        
                        if (interactive) {
                              response = run_error (common,
                                                g_strdup (_("Unable to mark launcher trusted (executable)")),
                                                error->message,
                                                NULL,
                                                FALSE,
                                                GTK_STOCK_CANCEL, RETRY,
                                                NULL);
                        } else {
                              response = 0;
                        }
                        
                        if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) {
                              abort_job (common);
                        } else if (response == 1) {
                              goto retry;
                        } else {
                              g_assert_not_reached ();
                        }
                        
                        goto out;
                  }
      } 
      g_object_unref (info);
 out:
      ;
}

static gboolean
mark_trusted_job (GIOSchedulerJob *io_job,
              GCancellable *cancellable,
              gpointer user_data)
{
      MarkTrustedJob *job = user_data;
      CommonJob *common;
      
      common = (CommonJob *)job;
      common->io_job = io_job;
      
      nautilus_progress_info_start (job->common.progress);

      mark_desktop_file_trusted (common,
                           cancellable,
                           job->file,
                           job->interactive);
      
      g_io_scheduler_job_send_to_mainloop_async (io_job,
                                       mark_trusted_job_done,
                                       job,
                                       NULL);

      return FALSE;
}

void
nautilus_file_mark_desktop_file_trusted (GFile *file,
                               GtkWindow *parent_window,
                               gboolean interactive,
                               NautilusOpCallback done_callback,
                               gpointer done_callback_data)
{
      MarkTrustedJob *job;
      
      job = op_job_new (MarkTrustedJob, parent_window);
      job->file = g_object_ref (file);
      job->interactive = interactive;
      job->done_callback = done_callback;
      job->done_callback_data = done_callback_data;
      
      g_io_scheduler_push_job (mark_trusted_job,
                         job,
                         NULL,
                         0,
                         NULL);
}

#if !defined (NAUTILUS_OMIT_SELF_CHECK)

void
nautilus_self_check_file_operations (void)
{
      setlocale (LC_MESSAGES, "C");

      
      /* test the next duplicate name generator */
      EEL_CHECK_STRING_RESULT (get_duplicate_name (" (copy)", 1, -1), " (another copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo", 1, -1), "foo (copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name (".bashrc", 1, -1), ".bashrc (copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name (".foo.txt", 1, -1), ".foo (copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo", 1, -1), "foo foo (copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo.txt", 1, -1), "foo (copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt", 1, -1), "foo foo (copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt txt", 1, -1), "foo foo (copy).txt txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...txt", 1, -1), "foo (copy)...txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...", 1, -1), "foo (copy)...");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo. (copy)", 1, -1), "foo. (another copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy)", 1, -1), "foo (another copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy).txt", 1, -1), "foo (another copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy)", 1, -1), "foo (3rd copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy).txt", 1, -1), "foo (3rd copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (another copy).txt", 1, -1), "foo foo (3rd copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy)", 1, -1), "foo (14th copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy).txt", 1, -1), "foo (14th copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy)", 1, -1), "foo (22nd copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy).txt", 1, -1), "foo (22nd copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy)", 1, -1), "foo (23rd copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy).txt", 1, -1), "foo (23rd copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy)", 1, -1), "foo (24th copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy).txt", 1, -1), "foo (24th copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy)", 1, -1), "foo (25th copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy).txt", 1, -1), "foo (25th copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy)", 1, -1), "foo foo (25th copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy).txt", 1, -1), "foo foo (25th copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (100000000000000th copy).txt", 1, -1), "foo foo (copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy)", 1, -1), "foo (11th copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy).txt", 1, -1), "foo (11th copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy)", 1, -1), "foo (12th copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy).txt", 1, -1), "foo (12th copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy)", 1, -1), "foo (13th copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy).txt", 1, -1), "foo (13th copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy)", 1, -1), "foo (111th copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy).txt", 1, -1), "foo (111th copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy)", 1, -1), "foo (123rd copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy).txt", 1, -1), "foo (123rd copy).txt");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy)", 1, -1), "foo (124th copy)");
      EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy).txt", 1, -1), "foo (124th copy).txt");

      setlocale (LC_MESSAGES, "");
}

#endif

Generated by  Doxygen 1.6.0   Back to index