summaryrefslogtreecommitdiff
path: root/libcaja-private/caja-file-operations.c
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-12-01 22:24:23 -0300
committerPerberos <[email protected]>2011-12-01 22:24:23 -0300
commit0e004c696b0e68b2cff37a4c3315b022a35eaf43 (patch)
tree43261e815529cb9518ed7be37af13b846af8b26b /libcaja-private/caja-file-operations.c
downloadcaja-0e004c696b0e68b2cff37a4c3315b022a35eaf43.tar.bz2
caja-0e004c696b0e68b2cff37a4c3315b022a35eaf43.tar.xz
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'libcaja-private/caja-file-operations.c')
-rw-r--r--libcaja-private/caja-file-operations.c6469
1 files changed, 6469 insertions, 0 deletions
diff --git a/libcaja-private/caja-file-operations.c b/libcaja-private/caja-file-operations.c
new file mode 100644
index 00000000..90205b83
--- /dev/null
+++ b/libcaja-private/caja-file-operations.c
@@ -0,0 +1,6469 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* caja-file-operations.c - Caja 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 <[email protected]>
+ Ettore Perazzoli <[email protected]>
+ Pavel Cisler <[email protected]>
+ */
+
+#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 "caja-file-operations.h"
+
+#include "caja-debug-log.h"
+#include "caja-file-changes-queue.h"
+#include "caja-lib-self-check-functions.h"
+
+#include "caja-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 "caja-file-changes-queue.h"
+#include "caja-file-private.h"
+#include "caja-desktop-icon-file.h"
+#include "caja-desktop-link-monitor.h"
+#include "caja-global-preferences.h"
+#include "caja-link.h"
+#include "caja-autorun.h"
+#include "caja-trash-monitor.h"
+#include "caja-file-utilities.h"
+#include "caja-file-conflict-dialog.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;
+ CajaProgressInfo *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;
+ CajaCopyCallback done_callback;
+ gpointer done_callback_data;
+} CopyMoveJob;
+
+typedef struct {
+ CommonJob common;
+ GList *files;
+ gboolean try_trash;
+ gboolean user_cancel;
+ CajaDeleteCallback 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;
+ CajaCreateCallback done_callback;
+ gpointer done_callback_data;
+} CreateJob;
+
+
+typedef struct {
+ CommonJob common;
+ GList *trash_dirs;
+ gboolean should_confirm;
+ CajaOpCallback done_callback;
+ gpointer done_callback_data;
+} EmptyTrashJob;
+
+typedef struct {
+ CommonJob common;
+ GFile *file;
+ gboolean interactive;
+ CajaOpCallback done_callback;
+ gpointer done_callback_data;
+} MarkTrustedJob;
+
+typedef struct {
+ CommonJob common;
+ GFile *file;
+ CajaOpCallback 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);
+
+ #if GLIB_CHECK_VERSION(2, 30, 0)
+ return g_format_size(size);
+ #else
+ return g_format_size_for_display(size);
+ #endif
+}
+
+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 = caja_progress_info_new ();
+ common->cancellable = caja_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)
+{
+ caja_progress_info_finish (common->progress);
+
+ if (common->inhibit_cookie != -1) {
+ caja_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 (CAJA_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-caja-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);
+
+ caja_progress_info_pause (job->progress);
+ g_io_scheduler_job_send_to_mainloop (job->io_job,
+ do_run_simple_dialog,
+ data,
+ NULL);
+ caja_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 = caja_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 items from Trash?"));
+
+ response = run_warning (job,
+ prompt,
+ f(_("All items in the Trash will be permanently deleted.")),
+ 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);
+
+ caja_progress_info_take_status (job->progress,
+ f (_("Deleting files")));
+
+ elapsed = g_timer_elapsed (job->time, NULL);
+ if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE) {
+
+ caja_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);
+ caja_progress_info_take_details (job->progress, details);
+
+ g_free (time_left_s);
+ }
+
+ g_free (files_left_s);
+
+ if (source_info->num_files != 0) {
+ caja_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 {
+ caja_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)) {
+ caja_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;
+
+ caja_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);
+ caja_progress_info_take_details (job->progress, s);
+
+ if (total_files != 0) {
+ caja_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 {
+ caja_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);
+
+ caja_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;
+
+ caja_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,
+ CajaDeleteCallback 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
+caja_file_operations_trash_or_delete (GList *files,
+ GtkWindow *parent_window,
+ CajaDeleteCallback done_callback,
+ gpointer done_callback_data)
+{
+ trash_or_delete_internal (files, parent_window,
+ TRUE,
+ done_callback, done_callback_data);
+}
+
+void
+caja_file_operations_delete (GList *files,
+ GtkWindow *parent_window,
+ CajaDeleteCallback 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;
+ CajaUnmountCallback 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",
+ "Caja");
+
+ /* Make transient for the window group */
+ gtk_widget_realize (dialog);
+ if (screen != NULL) {
+ gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (dialog)),
+ gdk_screen_get_root_window (screen));
+ }
+
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+ return result;
+}
+
+void
+caja_file_operations_unmount_mount_full (GtkWindow *parent_window,
+ GMount *mount,
+ gboolean eject,
+ gboolean check_trash,
+ CajaUnmountCallback 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 = (CajaOpCallback)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
+caja_file_operations_unmount_mount (GtkWindow *parent_window,
+ GMount *mount,
+ gboolean eject,
+ gboolean check_trash)
+{
+ caja_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)
+{
+ CajaMountCallback mount_callback;
+ GObject *mount_callback_data_object;
+ GMountOperation *mount_op = user_data;
+ GError *error;
+ char *primary;
+ char *name;
+
+ error = NULL;
+ caja_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 = (CajaMountCallback)
+ 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
+caja_file_operations_mount_volume (GtkWindow *parent_window,
+ GVolume *volume,
+ gboolean allow_autorun)
+{
+ caja_file_operations_mount_volume_full (parent_window, volume,
+ allow_autorun, NULL, NULL);
+}
+
+void
+caja_file_operations_mount_volume_full (GtkWindow *parent_window,
+ GVolume *volume,
+ gboolean allow_autorun,
+ CajaMountCallback mount_callback,
+ GObject *mount_callback_data_object)
+{
+ GMountOperation *mount_op;
+
+ mount_op = gtk_mount_operation_new (parent_window);
+ g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION);
+ 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)
+ caja_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;
+ }
+
+ caja_progress_info_take_details (job->progress, s);
+ caja_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) {
+ caja_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 {
+ caja_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) {
+ caja_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 {
+ caja_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) {
+ caja_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 {
+ caja_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);
+ caja_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);
+ caja_progress_info_take_details (job->progress, s);
+ }
+
+ caja_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;
+ }
+ caja_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;
+ }
+ caja_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) &&
+ caja_is_in_system_dir (file)) {
+ res = TRUE;
+ }
+ g_object_unref (info);
+
+ return res;
+}
+
+typedef struct {
+ int id;
+ char *new_name;
+ gboolean apply_to_all;
+} ConflictResponseData;
+
+typedef struct {
+ GFile *src;
+ GFile *dest;
+ GFile *dest_dir;
+ GtkWindow *parent;
+ ConflictResponseData *resp_data;
+} ConflictDialogData;
+
+static gboolean
+do_run_conflict_dialog (gpointer _data)
+{
+ ConflictDialogData *data = _data;
+ GtkWidget *dialog;
+ int response;
+
+ dialog = caja_file_conflict_dialog_new (data->parent,
+ data->src,
+ data->dest,
+ data->dest_dir);
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (response == CONFLICT_RESPONSE_RENAME) {
+ data->resp_data->new_name =
+ caja_file_conflict_dialog_get_new_name (CAJA_FILE_CONFLICT_DIALOG (dialog));
+ } else if (response != GTK_RESPONSE_CANCEL ||
+ response != GTK_RESPONSE_NONE) {
+ data->resp_data->apply_to_all =
+ caja_file_conflict_dialog_get_apply_to_all
+ (CAJA_FILE_CONFLICT_DIALOG (dialog));
+ }
+
+ data->resp_data->id = response;
+
+ gtk_widget_destroy (dialog);
+
+ return FALSE;
+}
+
+static ConflictResponseData *
+run_conflict_dialog (CommonJob *job,
+ GFile *src,
+ GFile *dest,
+ GFile *dest_dir)
+{
+ ConflictDialogData *data;
+ ConflictResponseData *resp_data;
+
+ g_timer_stop (job->time);
+
+ data = g_slice_new0 (ConflictDialogData);
+ data->parent = job->parent_window;
+ data->src = src;
+ data->dest = dest;
+ data->dest_dir = dest_dir;
+
+ resp_data = g_slice_new0 (ConflictResponseData);
+ resp_data->new_name = NULL;
+ data->resp_data = resp_data;
+
+ caja_progress_info_pause (job->progress);
+ g_io_scheduler_job_send_to_mainloop (job->io_job,
+ do_run_conflict_dialog,
+ data,
+ NULL);
+ caja_progress_info_resume (job->progress);
+
+ g_slice_free (ConflictDialogData, data);
+
+ g_timer_continue (job->time);
+
+ return resp_data;
+}
+
+static void
+conflict_response_data_free (ConflictResponseData *data)
+{
+ g_free (data->new_name);
+ g_slice_free (ConflictResponseData, data);
+}
+
+static GFile *
+get_target_file_for_display_name (GFile *dir,
+ char *name)
+{
+ GFile *dest;
+
+ dest = NULL;
+ dest = g_file_get_child_for_display_name (dir, name, NULL);
+
+ if (dest == NULL) {
+ dest = g_file_get_child (dir, name);
+ }
+
+ return dest;
+}
+
+/* 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) {
+ caja_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
+ } else {
+ caja_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) {
+ caja_file_changes_queue_file_moved (src, dest);
+ } else {
+ caja_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;
+ ConflictResponseData *response;
+
+ g_error_free (error);
+
+ if (unique_names) {
+ g_object_unref (dest);
+ dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr++);
+ goto retry;
+ }
+
+ is_merge = FALSE;
+
+ if (is_dir (dest) && is_dir (src)) {
+ is_merge = TRUE;
+ }
+
+ if ((is_merge && job->merge_all) ||
+ (!is_merge && job->replace_all)) {
+ overwrite = TRUE;
+ goto retry;
+ }
+
+ if (job->skip_all_conflict) {
+ goto out;
+ }
+
+ response = run_conflict_dialog (job, src, dest, dest_dir);
+
+ if (response->id == GTK_RESPONSE_CANCEL ||
+ response->id == GTK_RESPONSE_DELETE_EVENT) {
+ conflict_response_data_free (response);
+ abort_job (job);
+ } else if (response->id == CONFLICT_RESPONSE_SKIP) {
+ if (response->apply_to_all) {
+ job->skip_all_conflict = TRUE;
+ }
+ conflict_response_data_free (response);
+ } else if (response->id == CONFLICT_RESPONSE_REPLACE) { /* merge/replace */
+ if (response->apply_to_all) {
+ if (is_merge) {
+ job->merge_all = TRUE;
+ } else {
+ job->replace_all = TRUE;
+ }
+ }
+ overwrite = TRUE;
+ conflict_response_data_free (response);
+ goto retry;
+ } else if (response->id == CONFLICT_RESPONSE_RENAME) {
+ g_object_unref (dest);
+ dest = get_target_file_for_display_name (dest_dir,
+ response->new_name);
+ conflict_response_data_free (response);
+ 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;
+ }
+ caja_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);
+
+ caja_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;
+
+ caja_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
+caja_file_operations_copy (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ CajaCopyCallback done_callback,
+ gpointer done_callback_data)
+{
+ CopyMoveJob *job;
+
+ job = op_job_new (CopyMoveJob, parent_window);
+ job->desktop_location = caja_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;
+
+ caja_progress_info_take_status (job->progress,
+ f (_("Preparing to Move to \"%B\""),
+ move_job->destination));
+
+ caja_progress_info_take_details (job->progress,
+ f (ngettext ("Preparing to move %'d file",
+ "Preparing to move %'d files",
+ left), left));
+
+ caja_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, renamed;
+ char *primary, *secondary, *details;
+ int response;
+ GFileCopyFlags flags;
+ MoveFileCopyFallback *fallback;
+ gboolean handled_invalid_filename;
+
+ overwrite = FALSE;
+ renamed = 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));
+ }
+
+ caja_file_changes_queue_file_moved (src, dest);
+
+ if (position) {
+ caja_file_changes_queue_schedule_position_set (dest, *position, job->screen_num);
+ } else {
+ caja_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;
+ ConflictResponseData *response;
+
+ g_error_free (error);
+
+ is_merge = FALSE;
+ if (is_dir (dest) && is_dir (src)) {
+ is_merge = TRUE;
+ }
+
+ if ((is_merge && job->merge_all) ||
+ (!is_merge && job->replace_all)) {
+ overwrite = TRUE;
+ goto retry;
+ }
+
+ if (job->skip_all_conflict) {
+ goto out;
+ }
+
+ response = run_conflict_dialog (job, src, dest, dest_dir);
+
+ if (response->id == GTK_RESPONSE_CANCEL ||
+ response->id == GTK_RESPONSE_DELETE_EVENT) {
+ conflict_response_data_free (response);
+ abort_job (job);
+ } else if (response->id == CONFLICT_RESPONSE_SKIP) {
+ if (response->apply_to_all) {
+ job->skip_all_conflict = TRUE;
+ }
+ conflict_response_data_free (response);
+ } else if (response->id == CONFLICT_RESPONSE_REPLACE) { /* merge/replace */
+ if (response->apply_to_all) {
+ if (is_merge) {
+ job->merge_all = TRUE;
+ } else {
+ job->replace_all = TRUE;
+ }
+ }
+ overwrite = TRUE;
+ conflict_response_data_free (response);
+ goto retry;
+ } else if (response->id == CONFLICT_RESPONSE_RENAME) {
+ g_object_unref (dest);
+ dest = get_target_file_for_display_name (dest_dir,
+ response->new_name);
+ conflict_response_data_free (response);
+ goto retry;
+ } else if (response->id == CONFLICT_RESPONSE_RENAME) {
+ g_object_unref (dest);
+ dest = get_target_file_for_display_name (dest_dir,
+ response->new_name);
+ renamed = TRUE;
+ conflict_response_data_free (response);
+ 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);
+
+ caja_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;
+
+ caja_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
+caja_file_operations_move (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ CajaCopyCallback 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;
+
+ caja_progress_info_take_status (job->progress,
+ f (_("Creating links in \"%B\""),
+ link_job->destination));
+
+ caja_progress_info_take_details (job->progress,
+ f (ngettext ("Making link to %'d file",
+ "Making links to %'d files",
+ left), left));
+
+ caja_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));
+ }
+
+ caja_file_changes_queue_file_added (dest);
+ if (position) {
+ caja_file_changes_queue_schedule_position_set (dest, *position, common->screen_num);
+ } else {
+ caja_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);
+
+ caja_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;
+
+ caja_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
+caja_file_operations_link (GList *files,
+ GArray *relative_item_points,
+ GFile *target_dir,
+ GtkWindow *parent_window,
+ CajaCopyCallback 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
+caja_file_operations_duplicate (GList *files,
+ GArray *relative_item_points,
+ GtkWindow *parent_window,
+ CajaCopyCallback 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;
+
+ caja_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;
+
+ caja_progress_info_set_status (common->progress,
+ _("Setting permissions"));
+
+ caja_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
+caja_file_set_permissions_recursive (const char *directory,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 dir_permissions,
+ guint32 dir_mask,
+ CajaOpCallback 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 {
+ CajaCopyCallback 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
+caja_file_operations_copy_move (const GList *item_uris,
+ GArray *relative_item_points,
+ const char *target_dir,
+ GdkDragAction copy_action,
+ GtkWidget *parent_view,
+ CajaCopyCallback 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))) {
+ caja_file_operations_duplicate (locations,
+ relative_item_points,
+ parent_window,
+ done_callback, done_callback_data);
+ } else {
+ caja_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;
+ caja_file_operations_trash_or_delete (locations,
+ parent_window,
+ (CajaDeleteCallback) callback_for_move_to_trash,
+ cb_data);
+ } else {
+ caja_file_operations_move (locations,
+ relative_item_points,
+ dest,
+ parent_window,
+ done_callback, done_callback_data);
+ }
+ } else {
+ caja_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);
+
+ caja_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;
+
+ caja_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);
+ caja_file_changes_queue_file_added (dest);
+ if (job->has_position) {
+ caja_file_changes_queue_schedule_position_set (dest, job->position, common->screen_num);
+ } else {
+ caja_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
+caja_file_operations_new_folder (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ CajaCreateCallback 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
+caja_file_operations_new_file_from_template (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *template_uri,
+ CajaCreateCallback 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
+caja_file_operations_new_file (GtkWidget *parent_view,
+ GdkPoint *target_point,
+ const char *parent_dir,
+ const char *target_filename,
+ const char *initial_contents,
+ int length,
+ CajaCreateCallback 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;
+
+ caja_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
+caja_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;
+
+ caja_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
+caja_file_mark_desktop_file_trusted (GFile *file,
+ GtkWindow *parent_window,
+ gboolean interactive,
+ CajaOpCallback 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 (CAJA_OMIT_SELF_CHECK)
+
+void
+caja_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