summaryrefslogtreecommitdiff
path: root/capplets/common/file-transfer-dialog.c
diff options
context:
space:
mode:
Diffstat (limited to 'capplets/common/file-transfer-dialog.c')
-rw-r--r--capplets/common/file-transfer-dialog.c608
1 files changed, 608 insertions, 0 deletions
diff --git a/capplets/common/file-transfer-dialog.c b/capplets/common/file-transfer-dialog.c
new file mode 100644
index 00000000..a698520f
--- /dev/null
+++ b/capplets/common/file-transfer-dialog.c
@@ -0,0 +1,608 @@
+/* file-transfer-dialog.c
+ * Copyright (C) 2002 Ximian, Inc.
+ *
+ * Written by Rachel Hestilow <[email protected]>
+ * Jens Granseuer <[email protected]>
+ *
+ * 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, 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <limits.h>
+
+#include "file-transfer-dialog.h"
+
+enum
+{
+ PROP_0,
+ PROP_FROM_URI,
+ PROP_TO_URI,
+ PROP_FRACTION_COMPLETE,
+ PROP_NTH_URI,
+ PROP_TOTAL_URIS,
+ PROP_PARENT
+};
+
+enum
+{
+ CANCEL,
+ DONE,
+ LAST_SIGNAL
+};
+
+guint file_transfer_dialog_signals[LAST_SIGNAL] = {0, };
+
+struct _FileTransferDialogPrivate
+{
+ GtkWidget *progress;
+ GtkWidget *status;
+ guint nth;
+ guint total;
+ GCancellable *cancellable;
+};
+
+#define FILE_TRANSFER_DIALOG_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), file_transfer_dialog_get_type (), FileTransferDialogPrivate))
+
+typedef struct _FileTransferJob
+{
+ FileTransferDialog *dialog;
+ GtkDialog *overwrite_dialog;
+ GSList *source_files;
+ GSList *target_files;
+ FileTransferDialogOptions options;
+} FileTransferJob;
+
+/* structure passed to the various callbacks */
+typedef struct {
+ FileTransferDialog *dialog;
+ gchar *source;
+ gchar *target;
+ guint current_file;
+ guint total_files;
+ goffset current_bytes;
+ goffset total_bytes;
+ gint response;
+ GtkDialog *overwrite_dialog;
+} FileTransferData;
+
+G_DEFINE_TYPE (FileTransferDialog, file_transfer_dialog, GTK_TYPE_DIALOG)
+
+static void
+file_transfer_dialog_update_num_files (FileTransferDialog *dlg)
+{
+ gchar *str;
+
+ if (dlg->priv->total <= 1)
+ return;
+
+ str = g_strdup_printf (_("Copying file: %u of %u"),
+ dlg->priv->nth, dlg->priv->total);
+ gtk_progress_bar_set_text (GTK_PROGRESS_BAR (dlg->priv->progress), str);
+ g_free (str);
+}
+
+static void
+file_transfer_dialog_response (GtkDialog *dlg, gint response_id)
+{
+ FileTransferDialog *dialog = FILE_TRANSFER_DIALOG (dlg);
+
+ g_cancellable_cancel (dialog->priv->cancellable);
+}
+
+static void
+file_transfer_dialog_finalize (GObject *object)
+{
+ FileTransferDialog *dlg = FILE_TRANSFER_DIALOG (object);
+
+ if (dlg->priv->cancellable)
+ {
+ g_object_unref (dlg->priv->cancellable);
+ dlg->priv->cancellable = NULL;
+ }
+
+ G_OBJECT_CLASS (file_transfer_dialog_parent_class)->finalize (object);
+}
+
+static void
+file_transfer_dialog_set_prop (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ FileTransferDialog *dlg = FILE_TRANSFER_DIALOG (object);
+ GFile *file;
+ gchar *str;
+ gchar *str2;
+ gchar *base;
+ gchar *escaped;
+ GtkWindow *parent;
+ guint n;
+
+ switch (prop_id)
+ {
+ case PROP_FROM_URI:
+ file = g_file_new_for_uri (g_value_get_string (value));
+ base = g_file_get_basename (file);
+ escaped = g_uri_escape_string (base, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
+
+ str = g_strdup_printf (_("Copying '%s'"), escaped);
+ str2 = g_strdup_printf ("<big><b>%s</b></big>", str);
+ gtk_label_set_markup (GTK_LABEL (dlg->priv->status), str2);
+
+ g_free (base);
+ g_free (escaped);
+ g_free (str);
+ g_free (str2);
+ g_object_unref (file);
+ break;
+ case PROP_TO_URI:
+ break;
+ case PROP_FRACTION_COMPLETE:
+ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dlg->priv->progress), g_value_get_double (value));
+ break;
+ case PROP_NTH_URI:
+ n = g_value_get_uint (value);
+ if (n != dlg->priv->nth)
+ {
+ dlg->priv->nth = g_value_get_uint (value);
+ file_transfer_dialog_update_num_files (dlg);
+ }
+ break;
+ case PROP_TOTAL_URIS:
+ n = g_value_get_uint (value);
+ if (n != dlg->priv->nth)
+ {
+ dlg->priv->total = g_value_get_uint (value);
+ file_transfer_dialog_update_num_files (dlg);
+ }
+ break;
+ case PROP_PARENT:
+ parent = g_value_get_pointer (value);
+ if (parent)
+ {
+ gtk_window_set_title (GTK_WINDOW (dlg), gtk_window_get_title (parent));
+ gtk_window_set_transient_for (GTK_WINDOW (dlg), parent);
+ }
+ else
+ gtk_window_set_title (GTK_WINDOW (dlg),
+ _("Copying files"));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+file_transfer_dialog_get_prop (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ FileTransferDialog *dlg = FILE_TRANSFER_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_NTH_URI:
+ g_value_set_uint (value, dlg->priv->nth);
+ break;
+ case PROP_TOTAL_URIS:
+ g_value_set_uint (value, dlg->priv->total);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+file_transfer_dialog_class_init (FileTransferDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = file_transfer_dialog_finalize;
+ object_class->get_property = file_transfer_dialog_get_prop;
+ object_class->set_property = file_transfer_dialog_set_prop;
+
+ GTK_DIALOG_CLASS (klass)->response = file_transfer_dialog_response;
+
+ g_object_class_install_property
+ (object_class, PROP_PARENT,
+ g_param_spec_pointer ("parent",
+ _("Parent Window"),
+ _("Parent window of the dialog"),
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_FROM_URI,
+ g_param_spec_string ("from_uri",
+ _("From URI"),
+ _("URI currently transferring from"),
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_TO_URI,
+ g_param_spec_string ("to_uri",
+ _("To URI"),
+ _("URI currently transferring to"),
+ NULL,
+ G_PARAM_WRITABLE));
+
+ g_object_class_install_property
+ (object_class, PROP_FRACTION_COMPLETE,
+ g_param_spec_double ("fraction_complete",
+ _("Fraction completed"),
+ _("Fraction of transfer currently completed"),
+ 0, 1, 0,
+ G_PARAM_WRITABLE));
+
+ g_object_class_install_property
+ (object_class, PROP_NTH_URI,
+ g_param_spec_uint ("nth_uri",
+ _("Current URI index"),
+ _("Current URI index - starts from 1"),
+ 1, INT_MAX, 1,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property
+ (object_class, PROP_TOTAL_URIS,
+ g_param_spec_uint ("total_uris",
+ _("Total URIs"),
+ _("Total number of URIs"),
+ 1, INT_MAX, 1,
+ G_PARAM_READWRITE));
+
+ file_transfer_dialog_signals[CANCEL] =
+ g_signal_new ("cancel",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ file_transfer_dialog_signals[DONE] =
+ g_signal_new ("done",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (FileTransferDialogPrivate));
+}
+
+static void
+file_transfer_dialog_init (FileTransferDialog *dlg)
+{
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *progress_vbox;
+ GtkWidget *table;
+ char *markup;
+ GtkWidget *content_area;
+
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dlg));
+ dlg->priv = FILE_TRANSFER_DIALOG_GET_PRIVATE (dlg);
+ dlg->priv->cancellable = g_cancellable_new ();
+
+ gtk_container_set_border_width (GTK_CONTAINER (content_area), 4);
+ gtk_box_set_spacing (GTK_BOX (content_area), 4);
+
+ gtk_widget_set_size_request (GTK_WIDGET (dlg), 350, -1);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
+ gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0);
+
+ dlg->priv->status = gtk_label_new (NULL);
+ markup = g_strconcat ("<big><b>", _("Copying files"), "</b></big>", NULL);
+ gtk_label_set_markup (GTK_LABEL (dlg->priv->status), markup);
+ g_free (markup);
+
+ gtk_misc_set_alignment (GTK_MISC (dlg->priv->status), 0.0, 0.0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), dlg->priv->status, FALSE, FALSE, 0);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 4);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+
+ gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (table), FALSE, FALSE, 0);
+
+ progress_vbox = gtk_vbox_new (TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), progress_vbox, FALSE, FALSE, 0);
+
+ dlg->priv->progress = gtk_progress_bar_new ();
+ gtk_box_pack_start (GTK_BOX (progress_vbox),
+ dlg->priv->progress, FALSE, FALSE, 0);
+
+ gtk_dialog_add_button (GTK_DIALOG (dlg),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (dlg), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (dlg), 6);
+
+ gtk_widget_show_all (content_area);
+}
+
+GtkWidget*
+file_transfer_dialog_new (void)
+{
+ return GTK_WIDGET (g_object_new (file_transfer_dialog_get_type (),
+ NULL));
+}
+
+GtkWidget*
+file_transfer_dialog_new_with_parent (GtkWindow *parent)
+{
+ return GTK_WIDGET (g_object_new (file_transfer_dialog_get_type (),
+ "parent", parent, NULL));
+}
+
+static gboolean
+file_transfer_job_update (gpointer user_data)
+{
+ FileTransferData *data = user_data;
+ gdouble fraction;
+ gdouble current_fraction;
+
+ if (data->total_bytes == 0)
+ current_fraction = 0.0;
+ else
+ current_fraction = ((gdouble) data->current_bytes) / data->total_bytes;
+
+ fraction = ((gdouble) data->current_file - 1) / data->total_files +
+ (1.0 / data->total_files) * current_fraction;
+
+ g_object_set (data->dialog,
+ "from_uri", data->source,
+ "to_uri", data->target,
+ "nth_uri", data->current_file,
+ "fraction_complete", fraction,
+ NULL);
+ return FALSE;
+}
+
+static void
+file_transfer_job_progress (goffset current_bytes,
+ goffset total_bytes,
+ gpointer user_data)
+{
+ FileTransferData *data = user_data;
+
+ data->current_bytes = current_bytes;
+ data->total_bytes = total_bytes;
+
+ gdk_threads_enter ();
+ file_transfer_job_update (data);
+ gdk_threads_leave ();
+}
+
+static void
+file_transfer_job_destroy (FileTransferJob *job)
+{
+ g_object_unref (job->dialog);
+ g_slist_foreach (job->source_files, (GFunc) g_object_unref, NULL);
+ g_slist_foreach (job->target_files, (GFunc) g_object_unref, NULL);
+ g_slist_free (job->source_files);
+ g_slist_free (job->target_files);
+ if (job->overwrite_dialog != NULL)
+ gtk_widget_destroy (GTK_WIDGET (job->overwrite_dialog));
+ g_free (job);
+}
+
+static gboolean
+file_transfer_dialog_done (FileTransferDialog *dialog)
+{
+ g_signal_emit (dialog,
+ file_transfer_dialog_signals[DONE],
+ 0, NULL);
+ return FALSE;
+}
+
+static gboolean
+file_transfer_dialog_cancel (FileTransferDialog *dialog)
+{
+ g_signal_emit (dialog,
+ file_transfer_dialog_signals[CANCEL],
+ 0, NULL);
+ return FALSE;
+}
+
+static gboolean
+file_transfer_dialog_overwrite (gpointer user_data)
+{
+ FileTransferData *data = user_data;
+ GtkDialog *dialog;
+
+ dialog = data->overwrite_dialog;
+
+ if (dialog != NULL) {
+ } else {
+ GtkWidget *button;
+
+ dialog = GTK_DIALOG (gtk_message_dialog_new (GTK_WINDOW (data->dialog),
+ GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("File '%s' already exists. Do you want to overwrite it?"),
+ data->target));
+
+ gtk_dialog_add_button (dialog, _("_Skip"), GTK_RESPONSE_NO);
+ gtk_dialog_add_button (dialog, _("Overwrite _All"), GTK_RESPONSE_APPLY);
+
+ button = gtk_button_new_with_label (_("_Overwrite"));
+ gtk_button_set_image (GTK_BUTTON (button),
+ gtk_image_new_from_stock (GTK_STOCK_APPLY,
+ GTK_ICON_SIZE_BUTTON));
+ gtk_dialog_add_action_widget (dialog, button, GTK_RESPONSE_YES);
+ gtk_widget_show (button);
+
+ data->overwrite_dialog = dialog;
+ }
+
+ data->response = gtk_dialog_run (dialog);
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ return FALSE;
+}
+
+/* TODO: support transferring directories recursively? */
+static gboolean
+file_transfer_job_schedule (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ FileTransferJob *job)
+{
+ GFile *source, *target;
+ gboolean success;
+ GFileCopyFlags copy_flags = G_FILE_COPY_NONE;
+ FileTransferData data;
+ GError *error;
+ gboolean retry;
+
+ /* take the first file from the list and copy it */
+ source = job->source_files->data;
+ job->source_files = g_slist_delete_link (job->source_files, job->source_files);
+
+ target = job->target_files->data;
+ job->target_files = g_slist_delete_link (job->target_files, job->target_files);
+
+ data.dialog = job->dialog;
+ data.overwrite_dialog = job->overwrite_dialog;
+ data.current_file = job->dialog->priv->nth + 1;
+ data.total_files = job->dialog->priv->total;
+ data.current_bytes = data.total_bytes = 0;
+ data.source = g_file_get_basename (source);
+ data.target = g_file_get_basename (target);
+
+ g_io_scheduler_job_send_to_mainloop (io_job,
+ file_transfer_job_update,
+ &data,
+ NULL);
+
+ if (job->options & FILE_TRANSFER_DIALOG_OVERWRITE)
+ copy_flags |= G_FILE_COPY_OVERWRITE;
+
+ do {
+ retry = FALSE;
+ error = NULL;
+ success = g_file_copy (source, target,
+ copy_flags,
+ job->dialog->priv->cancellable,
+ file_transfer_job_progress,
+ &data,
+ &error);
+
+ if (error != NULL)
+ {
+ if (error->domain == G_IO_ERROR &&
+ error->code == G_IO_ERROR_EXISTS)
+ {
+ /* since the job is run in a thread, we cannot simply run
+ * a dialog here and need to defer it to the mainloop */
+ data.response = GTK_RESPONSE_NONE;
+ g_io_scheduler_job_send_to_mainloop (io_job,
+ file_transfer_dialog_overwrite,
+ &data,
+ NULL);
+
+ if (data.response == GTK_RESPONSE_YES) {
+ retry = TRUE;
+ copy_flags |= G_FILE_COPY_OVERWRITE;
+ } else if (data.response == GTK_RESPONSE_APPLY) {
+ retry = TRUE;
+ job->options |= FILE_TRANSFER_DIALOG_OVERWRITE;
+ copy_flags |= G_FILE_COPY_OVERWRITE;
+ } else {
+ success = TRUE;
+ }
+
+ job->overwrite_dialog = data.overwrite_dialog;
+ }
+ g_error_free (error);
+ }
+ } while (retry);
+
+ g_object_unref (source);
+ g_object_unref (target);
+
+ g_free (data.source);
+ g_free (data.target);
+
+ if (success)
+ {
+ if (job->source_files == NULL)
+ {
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ (GSourceFunc) file_transfer_dialog_done,
+ g_object_ref (job->dialog),
+ g_object_unref);
+ return FALSE;
+ }
+ }
+ else /* error on copy or cancelled */
+ {
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ (GSourceFunc) file_transfer_dialog_cancel,
+ g_object_ref (job->dialog),
+ g_object_unref);
+ return FALSE;
+ }
+
+ /* more work to do... */
+ return TRUE;
+}
+
+void
+file_transfer_dialog_copy_async (FileTransferDialog *dlg,
+ GList *source_files,
+ GList *target_files,
+ FileTransferDialogOptions options,
+ int priority)
+{
+ FileTransferJob *job;
+ GList *l;
+ guint n;
+
+ job = g_new0 (FileTransferJob, 1);
+ job->dialog = g_object_ref (dlg);
+ job->options = options;
+
+ /* we need to copy the list contents for private use */
+ n = 0;
+ for (l = g_list_last (source_files); l; l = l->prev, ++n)
+ {
+ job->source_files = g_slist_prepend (job->source_files,
+ g_object_ref (l->data));
+ }
+ for (l = g_list_last (target_files); l; l = l->prev)
+ {
+ job->target_files = g_slist_prepend (job->target_files,
+ g_object_ref (l->data));
+ }
+
+ g_object_set (dlg, "total_uris", n, NULL);
+
+ g_io_scheduler_push_job ((GIOSchedulerJobFunc) file_transfer_job_schedule,
+ job,
+ (GDestroyNotify) file_transfer_job_destroy,
+ priority,
+ dlg->priv->cancellable);
+}