diff --git a/docs/reference/thunar/thunar-docs.xml b/docs/reference/thunar/thunar-docs.xml index b644f65722d4b5e14531491e6b7a5d3d6f1577bb..a0d106be49ae02531d180c5aaaa1aac1cc700e13 100644 --- a/docs/reference/thunar/thunar-docs.xml +++ b/docs/reference/thunar/thunar-docs.xml @@ -55,6 +55,7 @@ <!ENTITY ThunarSessionClient SYSTEM "xml/thunar-session-client.xml"> <!ENTITY ThunarShortcutsPane SYSTEM "xml/thunar-shortcuts-pane.xml"> <!ENTITY ThunarJob SYSTEM "xml/thunar-job.xml"> + <!ENTITY ThunarJobOperation SYSTEM "xml/thunar-job-operation.xml"> <!ENTITY ThunarPangoExtensions SYSTEM "xml/thunar-pango-extensions.xml"> <!ENTITY ThunarStatusbar SYSTEM "xml/thunar-statusbar.xml"> <!ENTITY ThunarLocationBar SYSTEM "xml/thunar-location-bar.xml"> @@ -149,6 +150,7 @@ <xi:include href="xml/thunar-enum-types.xml"/> <xi:include href="xml/thunar-preferences.xml"/> <xi:include href="xml/thunar-user.xml"/> + <xi:include href="xml/thunar-job-operation.xml"/> </chapter> </part> diff --git a/po/POTFILES.in b/po/POTFILES.in index d5ee7009703fe9dcd913a2951341f9abab34654f..6a7829286e33f7c95c5de213233cdc4a1723b4ca 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -38,6 +38,7 @@ thunar/thunar-io-jobs.c thunar/thunar-io-jobs-util.c thunar/thunar-io-scan-directory.c thunar/thunar-job.c +thunar/thunar-job-operation.c thunar/thunar-list-model.c thunar/thunar-location-bar.c thunar/thunar-location-button.c diff --git a/thunar/Makefile.am b/thunar/Makefile.am index ae6aee0dafa6b5e65d94749f37b2ad0ac9e98363..65572ea97c54247e2fc646de93658d9722e1e0a1 100644 --- a/thunar/Makefile.am +++ b/thunar/Makefile.am @@ -119,6 +119,8 @@ thunar_SOURCES = \ thunar-io-scan-directory.h \ thunar-job.c \ thunar-job.h \ + thunar-job-operation.c \ + thunar-job-operation.h \ thunar-list-model.c \ thunar-list-model.h \ thunar-location-bar.c \ diff --git a/thunar/thunar-action-manager.c b/thunar/thunar-action-manager.c index 2fcd5718abc27db4effda425d556e90b7c21ee2f..68cb604d61b96c91745ca88d54051c405da43199 100644 --- a/thunar/thunar-action-manager.c +++ b/thunar/thunar-action-manager.c @@ -41,6 +41,7 @@ #include <thunar/thunar-gtk-extensions.h> #include <thunar/thunar-icon-factory.h> #include <thunar/thunar-io-scan-directory.h> +#include <thunar/thunar-job-operation.h> #include <thunar/thunar-preferences.h> #include <thunar/thunar-private.h> #include <thunar/thunar-properties-dialog.h> @@ -204,6 +205,7 @@ static gboolean thunar_action_manager_action_create_document static GtkWidget *thunar_action_manager_create_document_submenu_new(ThunarActionManager *action_mgr); static void thunar_action_manager_new_files_created (ThunarActionManager *action_mgr, GList *new_thunar_files); +static gboolean thunar_action_manager_action_undo (ThunarActionManager *action_mgr); @@ -303,6 +305,7 @@ static XfceGtkActionEntry thunar_action_manager_action_entries[] = { THUNAR_ACTION_MANAGER_ACTION_MOUNT, NULL, "", XFCE_GTK_MENU_ITEM, N_ ("_Mount"), N_ ("Mount the selected device"), NULL, G_CALLBACK (thunar_action_manager_action_open), }, { THUNAR_ACTION_MANAGER_ACTION_UNMOUNT, NULL, "", XFCE_GTK_MENU_ITEM, N_ ("_Unmount"), N_ ("Unmount the selected device"), NULL, G_CALLBACK (thunar_action_manager_action_unmount), }, { THUNAR_ACTION_MANAGER_ACTION_EJECT, NULL, "", XFCE_GTK_MENU_ITEM, N_ ("_Eject"), N_ ("Eject the selected device"), NULL, G_CALLBACK (thunar_action_manager_action_eject), }, + { THUNAR_ACTION_MANAGER_ACTION_UNDO, "<Actions>/ThunarActionManager/undo", "<Primary>Z", XFCE_GTK_IMAGE_MENU_ITEM, N_ ("_Undo"), N_ ("Undo the latest operation"), "edit-undo-symbolic", G_CALLBACK (thunar_action_manager_action_undo), }, { THUNAR_ACTION_MANAGER_ACTION_EDIT_LAUNCHER, NULL, "", XFCE_GTK_IMAGE_MENU_ITEM, N_ ("_Edit Launcher"), N_ ("Edit the selected action_mgr"), "gtk-edit", G_CALLBACK (thunar_action_manager_action_edit_launcher), }, }; @@ -3366,6 +3369,18 @@ thunar_action_manager_new_files_created (ThunarActionManager *action_mgr, +static gboolean +thunar_action_manager_action_undo (ThunarActionManager *action_mgr) +{ + _thunar_return_val_if_fail (THUNAR_IS_ACTION_MANAGER (action_mgr), FALSE); + + thunar_job_operation_undo (); + + return TRUE; +} + + + XfceGtkActionEntry* thunar_action_manager_get_action_entries (void) { diff --git a/thunar/thunar-action-manager.h b/thunar/thunar-action-manager.h index 4416ee00962de9670aed1f4f49e4040fb1aa7183..e991cd83ee6eb4d7a057af1e0d184a53af8484ea 100644 --- a/thunar/thunar-action-manager.h +++ b/thunar/thunar-action-manager.h @@ -72,6 +72,7 @@ typedef enum THUNAR_ACTION_MANAGER_ACTION_MOUNT, THUNAR_ACTION_MANAGER_ACTION_UNMOUNT, THUNAR_ACTION_MANAGER_ACTION_EJECT, + THUNAR_ACTION_MANAGER_ACTION_UNDO, THUNAR_ACTION_MANAGER_ACTION_EDIT_LAUNCHER, THUNAR_ACTION_MANAGER_N_ACTIONS diff --git a/thunar/thunar-enum-types.c b/thunar/thunar-enum-types.c index 4976568d386c5675755393e29551b99ed2504ccd..7f29ee365c0106329a0b91af2e4f51393beb6bf5 100644 --- a/thunar/thunar-enum-types.c +++ b/thunar/thunar-enum-types.c @@ -640,3 +640,32 @@ thunar_status_bar_info_check_active (guint info, { return (info & mask) > 0 ? TRUE : FALSE; } + + + +GType +thunar_job_operation_kind_get_type (void) +{ + static GType type = G_TYPE_INVALID; + + if (G_UNLIKELY (type == G_TYPE_INVALID)) + { + static const GEnumValue values[] = + { + { THUNAR_JOB_OPERATION_KIND_COPY, "THUNAR_JOB_OPERATION_KIND_COPY", N_("Copy"), }, + { THUNAR_JOB_OPERATION_KIND_MOVE, "THUNAR_JOB_OPERATION_KIND_MOVE", N_("Move") }, + { THUNAR_JOB_OPERATION_KIND_RENAME, "THUNAR_JOB_OPERATION_KIND_RENAME", N_("Rename") }, + { THUNAR_JOB_OPERATION_KIND_CREATE, "THUNAR_JOB_OPERATION_KIND_CREATE", N_("Create") }, + { THUNAR_JOB_OPERATION_KIND_DELETE, "THUNAR_JOB_OPERATION_KIND_DELETE", N_("Delete (opposite of create)") }, + { THUNAR_JOB_OPERATION_KIND_TRASH, "THUNAR_JOB_OPERATION_KIND_TRASH", N_("Trash") }, + { THUNAR_JOB_OPERATION_KIND_RESTORE, "THUNAR_JOB_OPERATION_KIND_RESTORE", N_("Restore (opposite of trash)") }, + { THUNAR_JOB_OPERATION_KIND_LINK, "THUNAR_JOB_OPERATION_KIND_LINK", N_("Link") }, + { THUNAR_JOB_OPERATION_KIND_UNLINK, "THUNAR_JOB_OPERATION_KIND_UNLINK", N_("Unlink") }, + { 0, NULL, NULL } + }; + + type = g_enum_register_static ("ThunarJobOperationKind", values); + } + + return type; +} diff --git a/thunar/thunar-enum-types.h b/thunar/thunar-enum-types.h index 6b68d7b2931b53c0820dff4b3a5e9bfc026a0827..d2a2095ab7104c8ad8c174ef08ca49bb18a2ec53 100644 --- a/thunar/thunar-enum-types.h +++ b/thunar/thunar-enum-types.h @@ -425,6 +425,25 @@ guint thunar_status_bar_info_toggle_bit (guint info, gboolean thunar_status_bar_info_check_active (guint info, ThunarStatusBarInfo mask); + + +#define THUNAR_TYPE_JOB_OPERATION_KIND (thunar_job_operation_kind_get_type ()) + +typedef enum +{ + THUNAR_JOB_OPERATION_KIND_COPY, + THUNAR_JOB_OPERATION_KIND_MOVE, + THUNAR_JOB_OPERATION_KIND_RENAME, + THUNAR_JOB_OPERATION_KIND_CREATE, + THUNAR_JOB_OPERATION_KIND_DELETE, + THUNAR_JOB_OPERATION_KIND_TRASH, + THUNAR_JOB_OPERATION_KIND_RESTORE, + THUNAR_JOB_OPERATION_KIND_LINK, + THUNAR_JOB_OPERATION_KIND_UNLINK +} ThunarJobOperationKind; + +GType thunar_job_operation_kind_get_type (void) G_GNUC_CONST; + G_END_DECLS; #endif /* !__THUNAR_ENUM_TYPES_H__ */ diff --git a/thunar/thunar-job-operation.c b/thunar/thunar-job-operation.c new file mode 100644 index 0000000000000000000000000000000000000000..b7a8fbb08ff9cb72099db3b804c1c6fe1dc4798a --- /dev/null +++ b/thunar/thunar-job-operation.c @@ -0,0 +1,329 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/*- + * Copyright (c) 2022 Pratyaksh Gautam <pratyakshgautam11@gmail.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <thunar/thunar-application.h> +#include <thunar/thunar-enum-types.h> +#include <thunar/thunar-job-operation.h> +#include <thunar/thunar-private.h> + +/** + * SECTION:thunar-job-operation + * @Short_description: Manages the logging of job operations (copy, move etc.) and undoing and redoing them + * @Title: ThunarJobOperation + * + * The #ThunarJobOperation class represents a single 'job operation', a file operation like copying, moving + * etc. that can be logged centrally and undone. + * + * The @job_operation_list is a #GList of such job operations. It is not necessary that @job_operation_list + * points to the head of the list; it points to the 'marked operation', the operation that reflects + * the latest state of the operation history. + * Usually, this will be the latest performed operation, which hasn't been undone yet. + */ + +static void thunar_job_operation_dispose (GObject *object); +static void thunar_job_operation_finalize (GObject *object); +static ThunarJobOperation *thunar_job_operation_new_invert (ThunarJobOperation *job_operation); +static void thunar_job_operation_execute (ThunarJobOperation *job_operation); +static gint is_ancestor (gconstpointer descendant, + gconstpointer ancestor); + + + +struct _ThunarJobOperation +{ + GObject __parent__; + + ThunarJobOperationKind operation_kind; + GList *source_file_list; + GList *target_file_list; +}; + +G_DEFINE_TYPE (ThunarJobOperation, thunar_job_operation, G_TYPE_OBJECT) + +static GList *job_operation_list = NULL; + + + +static void +thunar_job_operation_class_init (ThunarJobOperationClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->dispose = thunar_job_operation_dispose; + gobject_class->finalize = thunar_job_operation_finalize; +} + + + +static void +thunar_job_operation_init (ThunarJobOperation *self) +{ + self->operation_kind = THUNAR_JOB_OPERATION_KIND_COPY; + self->source_file_list = NULL; + self->target_file_list = NULL; +} + + + +static void +thunar_job_operation_dispose (GObject *object) +{ + ThunarJobOperation *op; + + op = THUNAR_JOB_OPERATION (object); + + g_list_free_full (op->source_file_list, g_object_unref); + g_list_free_full (op->target_file_list, g_object_unref); + + (*G_OBJECT_CLASS (thunar_job_operation_parent_class)->dispose) (object); +} + + + +static void +thunar_job_operation_finalize (GObject *object) +{ + (*G_OBJECT_CLASS (thunar_job_operation_parent_class)->finalize) (object); +} + + + +/** + * thunar_job_operation_new: + * @kind: The kind of operation being created. + * + * Creates a new #ThunarJobOperation of the given kind. Should be unref'd by the caller after use. + * + * Return value: (transfer full): the newly created #ThunarJobOperation + **/ +ThunarJobOperation * +thunar_job_operation_new (ThunarJobOperationKind kind) +{ + ThunarJobOperation *operation; + + operation = g_object_new (THUNAR_TYPE_JOB_OPERATION, NULL); + operation->operation_kind = kind; + + return operation; +} + + + +/** + * thunar_job_operation_add: + * @job_operation: a #ThunarJobOperation + * @source_file: a #GFile representing the source file + * @target_file: a #GFile representing the target file + * + * Adds the specified @source_file-@target_file pair to the given job operation. + **/ +void +thunar_job_operation_add (ThunarJobOperation *job_operation, + GFile *source_file, + GFile *target_file) +{ + + _thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation)); + _thunar_return_if_fail (G_IS_FILE (source_file)); + _thunar_return_if_fail (G_IS_FILE (target_file)); + + /* When a directory has a file operation applied to it (for e.g. deletion), + * the operation will also automatically get applied to its descendants. + * If the descendant of a that directory is then found, it will try to apply the operation + * to it again then, meaning the operation is attempted multiple times on the same file. + * + * So to avoid such issues on executing a job operation, if the source file is + * a descendant of an existing file, do not add it to the job operation. */ + if (g_list_find_custom (job_operation->source_file_list, source_file, is_ancestor) != NULL) + return; + + job_operation->source_file_list = g_list_append (job_operation->source_file_list, g_object_ref (source_file)); + job_operation->target_file_list = g_list_append (job_operation->target_file_list, g_object_ref (target_file)); +} + + + +/** + * thunar_job_operation_commit: + * @job_operation: a #ThunarJobOperation + * + * Commits, or registers, the given thunar_job_operation, adding the job operation + * to the job operation list. + **/ +void +thunar_job_operation_commit (ThunarJobOperation *job_operation) +{ + _thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation)); + + /* do not register an 'empty' job operation */ + if (job_operation->source_file_list == NULL && job_operation->target_file_list == NULL) + return; + + /* We only keep one job operation commited in the job operation list, so we have to free the + * memory for the job operation in the list, if any, stored in before we commit the new one. */ + thunar_g_list_free_full (job_operation_list); + job_operation_list = g_list_append (NULL, g_object_ref (job_operation)); +} + + + +/** + * thunar_job_operation_undo: + * + * Undoes the job operation marked by the job operation list. First the marked job operation + * is retreived, then its inverse operation is calculated, and finally this inverse operation + * is executed. + **/ +void +thunar_job_operation_undo (void) +{ + ThunarJobOperation *operation_marker; + ThunarJobOperation *inverted_operation; + + /* do nothing in case there is no job operation to undo */ + if (job_operation_list == NULL) + return; + + /* the 'marked' operation */ + operation_marker = job_operation_list->data; + + inverted_operation = thunar_job_operation_new_invert (operation_marker); + thunar_job_operation_execute (inverted_operation); + g_object_unref (inverted_operation); + + /* Completely clear the job operation list on undo, this is because we only store the single + * most recent operation, and we do not want it to be available to undo *again* after it has + * already been undone once. */ + thunar_g_list_free_full (job_operation_list); + job_operation_list = NULL; +} + + + +/* thunar_job_operation_new_invert: + * @job_operation: a #ThunarJobOperation + * + * Creates a new job operation which is the inverse of @job_operation. + * Should be unref'd by the caller after use. + * + * Return value: (transfer full): a newly created #ThunarJobOperation which is the inverse of @job_operation + **/ +ThunarJobOperation * +thunar_job_operation_new_invert (ThunarJobOperation *job_operation) +{ + ThunarJobOperation *inverted_operation; + + _thunar_return_val_if_fail (THUNAR_IS_JOB_OPERATION (job_operation), NULL); + + switch (job_operation->operation_kind) + { + case THUNAR_JOB_OPERATION_KIND_COPY: + inverted_operation = g_object_new (THUNAR_TYPE_JOB_OPERATION, NULL); + inverted_operation->operation_kind = THUNAR_JOB_OPERATION_KIND_DELETE; + inverted_operation->source_file_list = thunar_g_list_copy_deep (job_operation->target_file_list); + break; + + default: + g_assert_not_reached (); + break; + } + + return inverted_operation; +} + + + +/* thunar_job_operation_execute: + * @job_operation: a #ThunarJobOperation + * + * Executes the given @job_operation, depending on what kind of an operation it is. + **/ +void +thunar_job_operation_execute (ThunarJobOperation *job_operation) +{ + ThunarApplication *application; + GList *thunar_file_list = NULL; + GError *error = NULL; + ThunarFile *thunar_file; + + _thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation)); + + application = thunar_application_get (); + + switch (job_operation->operation_kind) + { + case THUNAR_JOB_OPERATION_KIND_DELETE: + for (GList *lp = job_operation->source_file_list; lp != NULL; lp = lp->next) + { + if (!G_IS_FILE (lp->data)) + { + g_warning ("One of the files in the job operation list was not a valid GFile"); + continue; + } + + thunar_file = thunar_file_get (lp->data, &error); + + if (error != NULL) + { + g_warning ("Failed to convert GFile to ThunarFile: %s", error->message); + g_clear_error (&error); + } + + if (!THUNAR_IS_FILE (thunar_file)) + { + g_error ("One of the files in the job operation list did not convert to a valid ThunarFile"); + continue; + } + + thunar_file_list = g_list_append (thunar_file_list, thunar_file); + } + + thunar_application_unlink_files (application, NULL, thunar_file_list, TRUE); + + thunar_g_list_free_full (thunar_file_list); + break; + + default: + _thunar_assert_not_reached (); + break; + } + + g_object_unref (application); +} + + + +/* is_ancestor: + * @ancestor: potential ancestor of @descendant. A #GFile + * @descendant: potential descendant of @ancestor. A #GFile + * + * Helper function for #g_list_find_custom. + * + * Return value: %0 if @ancestor is actually the ancestor of @descendant, + * %1 otherwise + **/ +static gint +is_ancestor (gconstpointer ancestor, + gconstpointer descendant) +{ + if (thunar_g_file_is_descendant (G_FILE (descendant), G_FILE (ancestor))) + return 0; + else + return 1; +} diff --git a/thunar/thunar-job-operation.h b/thunar/thunar-job-operation.h new file mode 100644 index 0000000000000000000000000000000000000000..1c6b19925fd5d9976296cfff92df9ec229a0cc10 --- /dev/null +++ b/thunar/thunar-job-operation.h @@ -0,0 +1,39 @@ +/* vi:set et ai sw=2 sts=2 ts=2: */ +/*- + * Copyright (c) 2022 Pratyaksh Gautam <pratyakshgautam11@gmail.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __THUNAR_JOB_OPERATION_H__ +#define __THUNAR_JOB_OPERATION_H__ + +#include <gio/gio.h> +#include <thunar/thunar-enum-types.h> + +G_BEGIN_DECLS + +#define THUNAR_TYPE_JOB_OPERATION (thunar_job_operation_get_type ()) +G_DECLARE_FINAL_TYPE (ThunarJobOperation, thunar_job_operation, THUNAR, JOB_OPERATION, GObject) + +G_END_DECLS + +ThunarJobOperation *thunar_job_operation_new (ThunarJobOperationKind kind); +void thunar_job_operation_add (ThunarJobOperation *job_operation, + GFile *source_file, + GFile *target_file); +void thunar_job_operation_commit (ThunarJobOperation *job_operation); +void thunar_job_operation_undo (void); + +#endif /* __THUNAR_JOB_OPERATION_H__ */ diff --git a/thunar/thunar-menu.c b/thunar/thunar-menu.c index 9272b92b1bd647ad678631a9fbde7b0f51b28545..15df5b1f166811c8f31e8badd4b0344a2c14c78d 100644 --- a/thunar/thunar-menu.c +++ b/thunar/thunar-menu.c @@ -363,6 +363,14 @@ thunar_menu_add_sections (ThunarMenu *menu, } } + if (menu_sections & THUNAR_MENU_SECTION_UNDO) + { + item_added = (thunar_action_manager_append_menu_item (menu->action_mgr, GTK_MENU_SHELL (menu), THUNAR_ACTION_MANAGER_ACTION_UNDO, force) != NULL); + + if (item_added) + xfce_gtk_menu_append_separator (GTK_MENU_SHELL (menu)); + } + if (menu_sections & THUNAR_MENU_SECTION_PROPERTIES) thunar_action_manager_append_menu_item (menu->action_mgr, GTK_MENU_SHELL (menu), THUNAR_ACTION_MANAGER_ACTION_PROPERTIES, FALSE); diff --git a/thunar/thunar-menu.h b/thunar/thunar-menu.h index 08e83e07bf66e87586a9441f619f53b812cd5893..e5b3c6e709a8b75a87b6f4be0a7e07814addcd61 100644 --- a/thunar/thunar-menu.h +++ b/thunar/thunar-menu.h @@ -65,7 +65,8 @@ typedef enum THUNAR_MENU_SECTION_PROPERTIES = 1 << 13, THUNAR_MENU_SECTION_MOUNTABLE = 1 << 14, THUNAR_MENU_SECTION_REMOVE_FROM_RECENT = 1 << 15, - THUNAR_MENU_SECTION_EDIT_LAUNCHER = 1 << 16, + THUNAR_MENU_SECTION_UNDO = 1 << 16, + THUNAR_MENU_SECTION_EDIT_LAUNCHER = 1 << 17, } ThunarMenuSections; diff --git a/thunar/thunar-transfer-job.c b/thunar/thunar-transfer-job.c index 38fab1089b99e68099bc334809c4237c67d71ee8..d0987dd9be8c2dadfe0263177657962020d3209e 100644 --- a/thunar/thunar-transfer-job.c +++ b/thunar/thunar-transfer-job.c @@ -30,6 +30,7 @@ #include <thunar/thunar-io-scan-directory.h> #include <thunar/thunar-io-jobs-util.h> #include <thunar/thunar-job.h> +#include <thunar/thunar-job-operation.h> #include <thunar/thunar-preferences.h> #include <thunar/thunar-private.h> #include <thunar/thunar-thumbnail-cache.h> @@ -456,11 +457,12 @@ thunar_transfer_job_collect_node (ThunarTransferJob *job, static gboolean -ttj_copy_file (ThunarTransferJob *job, - GFile *source_file, - GFile *target_file, - GFileCopyFlags copy_flags, - GError **error) +ttj_copy_file (ThunarTransferJob *job, + ThunarJobOperation *operation, + GFile *source_file, + GFile *target_file, + GFileCopyFlags copy_flags, + GError **error) { GFileInfo *info; GFileType source_type; @@ -468,6 +470,7 @@ ttj_copy_file (ThunarTransferJob *job, gboolean target_exists; gboolean use_partial; gboolean verify_file; + gboolean add_to_operation; GError *err = NULL; _thunar_return_val_if_fail (THUNAR_IS_TRANSFER_JOB (job), FALSE); @@ -524,6 +527,12 @@ ttj_copy_file (ThunarTransferJob *job, exo_job_get_cancellable (EXO_JOB (job)), thunar_transfer_job_progress, job, &err); + /* unless the copy involved an overwrite, register the operation*/ + if (copy_flags & G_FILE_COPY_OVERWRITE) + add_to_operation = FALSE; + else + add_to_operation = TRUE; + switch (job->transfer_verify_file) { case THUNAR_VERIFY_FILE_MODE_REMOTE_ONLY: @@ -586,6 +595,10 @@ ttj_copy_file (ThunarTransferJob *job, /* we tried to overwrite a directory with a directory. this normally results * in a merge. ignore that error, since we actually *want* to merge */ g_clear_error (&err); + + /* in case of a merge, do not register the directory, its descendants + * will be registered because of the recursion either way */ + add_to_operation = FALSE; } else if (err->code == G_IO_ERROR_WOULD_RECURSE) { @@ -613,6 +626,7 @@ ttj_copy_file (ThunarTransferJob *job, if (target_exists) { /* the target still exists and thus is not a directory. try to remove it */ + add_to_operation = TRUE; g_file_delete (target_file, exo_job_get_cancellable (EXO_JOB (job)), &err); @@ -637,6 +651,8 @@ ttj_copy_file (ThunarTransferJob *job, } else { + if (add_to_operation) + thunar_job_operation_add (operation, source_file, target_file); return TRUE; } } @@ -670,12 +686,13 @@ ttj_copy_file (ThunarTransferJob *job, * on error or cancellation. **/ static GFile * -thunar_transfer_job_copy_file (ThunarTransferJob *job, - GFile *source_file, - GFile *target_file, - gboolean replace_confirmed, - gboolean rename_confirmed, - GError **error) +thunar_transfer_job_copy_file (ThunarTransferJob *job, + ThunarJobOperation *operation, + GFile *source_file, + GFile *target_file, + gboolean replace_confirmed, + gboolean rename_confirmed, + GError **error) { ThunarJobResponse response; GFile *dest_file = target_file; @@ -707,7 +724,7 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job, if (err == NULL) { /* try to copy the file from source file to the duplicate file */ - if (ttj_copy_file (job, source_file, target, copy_flags, &err)) + if (ttj_copy_file (job, operation, source_file, target, copy_flags, &err)) return target; else /* go to error case */ g_object_unref (target); @@ -734,6 +751,7 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job, /* add overwrite flag and retry if we should overwrite */ if (response == THUNAR_JOB_RESPONSE_REPLACE) { + replace_confirmed = TRUE; copy_flags |= G_FILE_COPY_OVERWRITE; continue; } @@ -773,6 +791,7 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job, static void thunar_transfer_job_copy_node (ThunarTransferJob *job, + ThunarJobOperation *operation, ThunarTransferNode *node, GFile *target_file, GFile *target_parent_file, @@ -920,7 +939,8 @@ retry_copy: thunar_transfer_job_check_pause (job); /* copy the item specified by this node (not recursively) */ - real_target_file = thunar_transfer_job_copy_file (job, node->source_file, + real_target_file = thunar_transfer_job_copy_file (job, operation, + node->source_file, target_file, node->replace_confirmed, node->rename_confirmed, @@ -939,7 +959,7 @@ retry_copy: if (node->children != NULL) { /* copy all children of this node */ - thunar_transfer_job_copy_node (job, node->children, NULL, real_target_file, NULL, &err); + thunar_transfer_job_copy_node (job, operation, node->children, NULL, real_target_file, NULL, &err); /* free resources allocted for the children */ thunar_transfer_node_free (node->children); @@ -1549,6 +1569,7 @@ thunar_transfer_job_execute (ExoJob *job, ThunarTransferNode *node; ThunarApplication *application; ThunarTransferJob *transfer_job = THUNAR_TRANSFER_JOB (job); + ThunarJobOperation *operation; GFileInfo *info; GError *err = NULL; GList *new_files_list = NULL; @@ -1642,13 +1663,14 @@ thunar_transfer_job_execute (ExoJob *job, /* transfer starts now */ transfer_job->start_time = g_get_real_time (); + operation = thunar_job_operation_new (THUNAR_JOB_OPERATION_KIND_COPY); /* perform the copy recursively for all source transfer nodes */ for (sp = transfer_job->source_node_list, tp = transfer_job->target_file_list; sp != NULL && tp != NULL && err == NULL; sp = sp->next, tp = tp->next) { - thunar_transfer_job_copy_node (transfer_job, sp->data, tp->data, NULL, + thunar_transfer_job_copy_node (transfer_job, operation, sp->data, tp->data, NULL, &new_files_list, &err); } } @@ -1663,6 +1685,10 @@ thunar_transfer_job_execute (ExoJob *job, { thunar_job_new_files (THUNAR_JOB (job), new_files_list); thunar_g_list_free_full (new_files_list); + + thunar_job_operation_commit (operation); + g_object_unref (operation); + return TRUE; } } diff --git a/thunar/thunar-window.c b/thunar/thunar-window.c index 1289b2d0fae58eb2b63690c890cda71ddb1a0479..8321c9eff04e55547477294b226e799e995568d8 100644 --- a/thunar/thunar-window.c +++ b/thunar/thunar-window.c @@ -1186,7 +1186,8 @@ thunar_window_update_edit_menu (ThunarWindow *window, thunar_gtk_menu_clean (GTK_MENU (menu)); thunar_menu_add_sections (THUNAR_MENU (menu), THUNAR_MENU_SECTION_CUT | THUNAR_MENU_SECTION_COPY_PASTE - | THUNAR_MENU_SECTION_TRASH_DELETE); + | THUNAR_MENU_SECTION_TRASH_DELETE + | THUNAR_MENU_SECTION_UNDO); if (window->view != NULL) { thunar_standard_view_append_menu_item (THUNAR_STANDARD_VIEW (window->view),