From 928e59c4868623f49cc08e192cdaa8ab691231c9 Mon Sep 17 00:00:00 2001
From: Alexander Schwinn <alexxcons@xfce.org>
Date: Sat, 1 Oct 2022 22:36:17 +0200
Subject: [PATCH] Add 'undo' for the latest file-create operation (Issue 819)

---
 thunar/thunar-action-manager.c |  4 +-
 thunar/thunar-application.c    | 56 +++++++++++---------
 thunar/thunar-application.h    | 65 ++++++++++++-----------
 thunar/thunar-dbus-service.c   |  4 +-
 thunar/thunar-io-jobs.c        | 94 +++++++++++++++++++++++++---------
 thunar/thunar-job-operation.c  |  8 ++-
 6 files changed, 150 insertions(+), 81 deletions(-)

diff --git a/thunar/thunar-action-manager.c b/thunar/thunar-action-manager.c
index d59881f69..a873da5b3 100644
--- a/thunar/thunar-action-manager.c
+++ b/thunar/thunar-action-manager.c
@@ -2643,7 +2643,7 @@ thunar_action_manager_action_create_folder (ThunarActionManager *action_mgr)
 
       /* launch the operation */
       application = thunar_application_get ();
-      thunar_application_mkdir (application, action_mgr->widget, &path_list, action_mgr->new_files_created_closure);
+      thunar_application_mkdir (application, action_mgr->widget, &path_list, action_mgr->new_files_created_closure, THUNAR_OPERATION_LOG_OPERATIONS);
       g_object_unref (G_OBJECT (application));
 
       /* release the path */
@@ -2721,7 +2721,7 @@ thunar_action_manager_action_create_document (ThunarActionManager *action_mgr,
           application = thunar_application_get ();
           thunar_application_creat (application, action_mgr->widget, &target_path_list,
                                     template_file != NULL ? thunar_file_get_file (template_file) : NULL,
-                                    action_mgr->new_files_created_closure);
+                                    action_mgr->new_files_created_closure, THUNAR_OPERATION_LOG_OPERATIONS);
           g_object_unref (G_OBJECT (application));
 
           /* release the target path */
diff --git a/thunar/thunar-application.c b/thunar/thunar-application.c
index a78c6a364..bd4046a71 100644
--- a/thunar/thunar-application.c
+++ b/thunar/thunar-application.c
@@ -1928,17 +1928,19 @@ thunar_application_rename_file (ThunarApplication      *application,
  *                     to open on the default screen.
  * @startup_id       : startup id from startup notification passed along
  *                     with dbus to make focus stealing work properly.
+ * @log_mode          : a #ThunarOperationLogMode to control logging
  *
  * Prompts the user to create a new file or directory in @parent_directory.
  * The @content_type defines the icon and other elements in the filename
  * prompt dialog.
  **/
 void
-thunar_application_create_file (ThunarApplication *application,
-                                ThunarFile        *parent_directory,
-                                const gchar       *content_type,
-                                GdkScreen         *screen,
-                                const gchar       *startup_id)
+thunar_application_create_file (ThunarApplication      *application,
+                                ThunarFile             *parent_directory,
+                                const gchar            *content_type,
+                                GdkScreen              *screen,
+                                const gchar            *startup_id,
+                                ThunarOperationLogMode  log_mode)
 {
   const gchar *dialog_title;
   const gchar *title;
@@ -1976,9 +1978,9 @@ thunar_application_create_file (ThunarApplication *application,
 
       /* launch the operation */
       if (is_directory)
-        thunar_application_mkdir (application, screen, &path_list, NULL);
+        thunar_application_mkdir (application, screen, &path_list, NULL, log_mode);
       else
-        thunar_application_creat (application, screen, &path_list, NULL, NULL);
+        thunar_application_creat (application, screen, &path_list, NULL, NULL, log_mode);
 
       g_object_unref (path_list.data);
       g_free (name);
@@ -1996,17 +1998,19 @@ thunar_application_create_file (ThunarApplication *application,
  *                     to open on the default screen.
  * @startup_id       : startup id from startup notification passed along
  *                     with dbus to make focus stealing work properly.
+ * @log_mode          : a #ThunarOperationLogMode to control logging
  *
  * Prompts the user to create a new file or directory in @parent_directory
  * from an existing @template_file which predefines the name and extension
  * in the create dialog.
  **/
 void
-thunar_application_create_file_from_template (ThunarApplication *application,
-                                              ThunarFile        *parent_directory,
-                                              ThunarFile        *template_file,
-                                              GdkScreen         *screen,
-                                              const gchar       *startup_id)
+thunar_application_create_file_from_template (ThunarApplication      *application,
+                                              ThunarFile             *parent_directory,
+                                              ThunarFile             *template_file,
+                                              GdkScreen              *screen,
+                                              const gchar            *startup_id,
+                                              ThunarOperationLogMode log_mode)
 {
   GList  target_path_list;
   gchar *name;
@@ -2039,7 +2043,7 @@ thunar_application_create_file_from_template (ThunarApplication *application,
       thunar_application_creat (application, screen,
                                 &target_path_list,
                                 thunar_file_get_file (template_file),
-                                NULL);
+                                NULL, log_mode);
 
       /* release the target path */
       g_object_unref (target_path_list.data);
@@ -2489,16 +2493,18 @@ creat_stub (GList *template_file,
  *                      which will be emitted when the job finishes with the
  *                      list of #GFile<!---->s created by the job, or
  *                      %NULL if you're not interested in the signal.
+ * @log_mode          : a #ThunarOperationLogMode to control logging
  *
  * Creates empty files for all #GFile<!---->s listed in @file_list. This
  * method takes care of all user interaction.
  **/
 void
-thunar_application_creat (ThunarApplication *application,
-                          gpointer           parent,
-                          GList             *file_list,
-                          GFile             *template_file,
-                          GClosure          *new_files_closure)
+thunar_application_creat (ThunarApplication      *application,
+                          gpointer                parent,
+                          GList                  *file_list,
+                          GFile                  *template_file,
+                          GClosure               *new_files_closure,
+                          ThunarOperationLogMode  log_mode)
 {
   GList template_list;
 
@@ -2511,7 +2517,7 @@ thunar_application_creat (ThunarApplication *application,
   /* launch the operation */
   thunar_application_launch (application, parent, "document-new",
                              _("Creating files..."), creat_stub,
-                             &template_list, file_list, FALSE, TRUE, THUNAR_OPERATION_LOG_OPERATIONS, new_files_closure);
+                             &template_list, file_list, FALSE, TRUE, log_mode, new_files_closure);
 }
 
 
@@ -2534,15 +2540,17 @@ mkdir_stub (GList *source_path_list,
  *                      which will be emitted when the job finishes with the
  *                      list of #GFile<!---->s created by the job, or
  *                      %NULL if you're not interested in the signal.
+ * @log_mode          : a #ThunarOperationLogMode to control logging
  *
  * Creates all directories referenced by the @file_list. This method takes care of all user
  * interaction.
  **/
 void
-thunar_application_mkdir (ThunarApplication *application,
-                          gpointer           parent,
-                          GList             *file_list,
-                          GClosure          *new_files_closure)
+thunar_application_mkdir (ThunarApplication      *application,
+                          gpointer                parent,
+                          GList                  *file_list,
+                          GClosure               *new_files_closure,
+                          ThunarOperationLogMode  log_mode)
 {
   _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent));
   _thunar_return_if_fail (THUNAR_IS_APPLICATION (application));
@@ -2550,7 +2558,7 @@ thunar_application_mkdir (ThunarApplication *application,
   /* launch the operation */
   thunar_application_launch (application, parent, "folder-new",
                              _("Creating directories..."), mkdir_stub,
-                             file_list, file_list, TRUE, FALSE, THUNAR_OPERATION_LOG_OPERATIONS, new_files_closure);
+                             file_list, file_list, TRUE, FALSE, log_mode, new_files_closure);
 }
 
 
diff --git a/thunar/thunar-application.h b/thunar/thunar-application.h
index c89ae5c79..0e7ccbc07 100644
--- a/thunar/thunar-application.h
+++ b/thunar/thunar-application.h
@@ -89,16 +89,21 @@ void                  thunar_application_rename_file                (ThunarAppli
                                                                      GdkScreen              *screen,
                                                                      const gchar            *startup_id,
                                                                      ThunarOperationLogMode  log_mode);
-void                  thunar_application_create_file                (ThunarApplication *application,
-                                                                     ThunarFile        *parent_directory,
-                                                                     const gchar       *content_type,
-                                                                     GdkScreen         *screen,
-                                                                     const gchar       *startup_id);
-void                  thunar_application_create_file_from_template (ThunarApplication *application,
-                                                                    ThunarFile        *parent_directory,
-                                                                    ThunarFile        *template_file,
-                                                                    GdkScreen         *screen,
-                                                                    const gchar       *startup_id);
+
+void                  thunar_application_create_file                (ThunarApplication      *application,
+                                                                     ThunarFile             *parent_directory,
+                                                                     const gchar            *content_type,
+                                                                     GdkScreen              *screen,
+                                                                     const gchar            *startup_id,
+                                                                     ThunarOperationLogMode  log_mode);
+
+void                  thunar_application_create_file_from_template (ThunarApplication       *application,
+                                                                    ThunarFile              *parent_directory,
+                                                                    ThunarFile              *template_file,
+                                                                    GdkScreen               *screen,
+                                                                    const gchar             *startup_id,
+                                                                    ThunarOperationLogMode   log_mode);
+
 void                  thunar_application_copy_to                   (ThunarApplication *application,
                                                                     gpointer           parent,
                                                                     GList             *source_file_list,
@@ -131,26 +136,28 @@ void                  thunar_application_move_files                (ThunarApplic
                                                                     ThunarOperationLogMode   log_mode,
                                                                     GClosure                *new_files_closure);
 
-void                  thunar_application_unlink_files              (ThunarApplication *application,
-                                                                    gpointer           parent,
-                                                                    GList             *file_list,
-                                                                    gboolean           permanently);
-
-void                  thunar_application_trash                     (ThunarApplication     *application,
-                                                                    gpointer               parent,
-                                                                    GList                 *file_list,
-                                                                    ThunarOperationLogMode log_mode);
-
-void                  thunar_application_creat                     (ThunarApplication *application,
-                                                                    gpointer           parent,
-                                                                    GList             *file_list,
-                                                                    GFile             *template_file,
-                                                                    GClosure          *new_files_closure);
+void                  thunar_application_unlink_files              (ThunarApplication       *application,
+                                                                    gpointer                 parent,
+                                                                    GList                   *file_list,
+                                                                    gboolean                 permanently);
 
-void                  thunar_application_mkdir                     (ThunarApplication *application,
-                                                                    gpointer           parent,
-                                                                    GList             *file_list,
-                                                                    GClosure          *new_files_closure);
+void                  thunar_application_trash                     (ThunarApplication       *application,
+                                                                    gpointer                 parent,
+                                                                    GList                   *file_list,
+                                                                    ThunarOperationLogMode   log_mode);
+
+void                  thunar_application_creat                     (ThunarApplication        *application,
+                                                                    gpointer                  parent,
+                                                                    GList                    *file_list,
+                                                                    GFile                    *template_file,
+                                                                    GClosure                 *new_files_closure,
+                                                                    ThunarOperationLogMode    log_mode);
+
+void                  thunar_application_mkdir                     (ThunarApplication        *application,
+                                                                    gpointer                  parent,
+                                                                    GList                    *file_list,
+                                                                    GClosure                 *new_files_closure,
+                                                                    ThunarOperationLogMode    log_mode);
 
 void                  thunar_application_empty_trash               (ThunarApplication *application,
                                                                     gpointer           parent,
diff --git a/thunar/thunar-dbus-service.c b/thunar/thunar-dbus-service.c
index 530cf3834..b7cd2eb7d 100644
--- a/thunar/thunar-dbus-service.c
+++ b/thunar/thunar-dbus-service.c
@@ -1119,7 +1119,7 @@ thunar_dbus_service_create_file (ThunarDBusFileManager  *object,
 
   /* popup a new window for the folder */
   application = thunar_application_get ();
-  thunar_application_create_file (application, file, content_type, screen, startup_id);
+  thunar_application_create_file (application, file, content_type, screen, startup_id, THUNAR_OPERATION_LOG_NO_OPERATIONS);
   g_object_unref (G_OBJECT (application));
 
   /* cleanup */
@@ -1163,7 +1163,7 @@ thunar_dbus_service_create_file_from_template (ThunarDBusFileManager  *object,
 
   /* popup a new window for the folder */
   application = thunar_application_get ();
-  thunar_application_create_file_from_template (application, file, template_file, screen, startup_id);
+  thunar_application_create_file_from_template (application, file, template_file, screen, startup_id, THUNAR_OPERATION_LOG_NO_OPERATIONS);
   g_object_unref (G_OBJECT (application));
 
   /* cleanup */
diff --git a/thunar/thunar-io-jobs.c b/thunar/thunar-io-jobs.c
index b66658cf4..e4d1bb7ec 100644
--- a/thunar/thunar-io-jobs.c
+++ b/thunar/thunar-io-jobs.c
@@ -121,17 +121,19 @@ _thunar_io_jobs_create (ThunarJob  *job,
                         GArray     *param_values,
                         GError    **error)
 {
-  GFileOutputStream *stream;
-  ThunarJobResponse  response = THUNAR_JOB_RESPONSE_CANCEL;
-  GFileInfo         *info;
-  GError            *err = NULL;
-  GList             *file_list;
-  GList             *lp;
-  gchar             *base_name;
-  gchar             *display_name;
-  guint              n_processed = 0;
-  GFile             *template_file;
-  GFileInputStream  *template_stream = NULL;
+  GFileOutputStream      *stream;
+  ThunarJobResponse       response = THUNAR_JOB_RESPONSE_CANCEL;
+  GFileInfo              *info;
+  GError                 *err = NULL;
+  GList                  *file_list;
+  GList                  *lp;
+  gchar                  *base_name;
+  gchar                  *display_name;
+  guint                   n_processed = 0;
+  GFile                  *template_file;
+  GFileInputStream       *template_stream = NULL;
+  ThunarJobOperation     *operation = NULL;
+  ThunarOperationLogMode  log_mode;
 
   _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
   _thunar_return_val_if_fail (param_values != NULL, FALSE);
@@ -157,6 +159,11 @@ _thunar_io_jobs_create (ThunarJob  *job,
         }
     }
 
+  log_mode = thunar_job_get_log_mode (job);
+
+  if (log_mode == THUNAR_OPERATION_LOG_OPERATIONS)
+    operation = thunar_job_operation_new (THUNAR_JOB_OPERATION_KIND_CREATE);
+
   /* iterate over all files in the list */
   for (lp = file_list;
        err == NULL && lp != NULL && !exo_job_is_cancelled (EXO_JOB (job));
@@ -247,6 +254,11 @@ _thunar_io_jobs_create (ThunarJob  *job,
         }
       else
         {
+
+          /* remember the file for possible undo */
+          if (log_mode == THUNAR_OPERATION_LOG_OPERATIONS)
+               thunar_job_operation_add (operation, NULL, lp->data);
+
           if (template_stream != NULL)
             {
               /* write the template into the new file */
@@ -268,12 +280,24 @@ _thunar_io_jobs_create (ThunarJob  *job,
   if (err != NULL)
     {
       g_propagate_error (error, err);
+      if (operation != NULL)
+        g_object_unref (operation);
       return FALSE;
     }
 
   /* check if the job was cancelled */
   if (exo_job_is_cancelled (EXO_JOB (job)))
-    return FALSE;
+    {
+      if (operation != NULL)
+        g_object_unref (operation);
+      return FALSE;
+    }
+
+  if (log_mode == THUNAR_OPERATION_LOG_OPERATIONS)
+    {
+      thunar_job_operation_commit (operation);
+      g_object_unref (operation);
+    }
 
   /* emit the "new-files" signal with the given file list */
   thunar_job_new_files (THUNAR_JOB (job), file_list);
@@ -299,14 +323,16 @@ _thunar_io_jobs_mkdir (ThunarJob  *job,
                        GArray     *param_values,
                        GError    **error)
 {
-  ThunarJobResponse response;
-  GFileInfo        *info;
-  GError           *err = NULL;
-  GList            *file_list;
-  GList            *lp;
-  gchar            *base_name;
-  gchar            *display_name;
-  guint             n_processed = 0;
+  ThunarJobResponse       response;
+  GFileInfo              *info;
+  GError                 *err = NULL;
+  GList                  *file_list;
+  GList                  *lp;
+  gchar                  *base_name;
+  gchar                  *display_name;
+  guint                  n_processed = 0;
+  ThunarJobOperation     *operation = NULL;
+  ThunarOperationLogMode  log_mode;
 
   _thunar_return_val_if_fail (THUNAR_IS_JOB (job), FALSE);
   _thunar_return_val_if_fail (param_values != NULL, FALSE);
@@ -318,6 +344,11 @@ _thunar_io_jobs_mkdir (ThunarJob  *job,
   /* we know the total list of files to process */
   thunar_job_set_total_files (THUNAR_JOB (job), file_list);
 
+  log_mode = thunar_job_get_log_mode (job);
+
+  if (log_mode == THUNAR_OPERATION_LOG_OPERATIONS)
+    operation = thunar_job_operation_new (THUNAR_JOB_OPERATION_KIND_CREATE);
+
   for (lp = file_list;
        err == NULL && lp != NULL && !exo_job_is_cancelled (EXO_JOB (job));
        lp = lp->next,  n_processed++)
@@ -400,19 +431,36 @@ _thunar_io_jobs_mkdir (ThunarJob  *job,
               if (response == THUNAR_JOB_RESPONSE_RETRY)
                 goto again;
             }
-        }
-    }
+        } /* end try creation */
+
+      /* remember the file for possible undo */
+      if (log_mode == THUNAR_OPERATION_LOG_OPERATIONS)
+          thunar_job_operation_add (operation, NULL, lp->data);
+
+    } /* end for all files */
 
   /* check if we have failed */
   if (err != NULL)
     {
       g_propagate_error (error, err);
+      if (operation != NULL)
+        g_object_unref (operation);
       return FALSE;
     }
 
   /* check if the job was cancelled */
   if (exo_job_is_cancelled (EXO_JOB (job)))
-    return FALSE;
+    {
+      if (operation != NULL)
+        g_object_unref (operation);
+      return FALSE;
+    }
+
+  if (log_mode == THUNAR_OPERATION_LOG_OPERATIONS)
+    {
+      thunar_job_operation_commit (operation);
+      g_object_unref (operation);
+    }
 
   /* emit the "new-files" signal with the given file list */
   thunar_job_new_files (THUNAR_JOB (job), file_list);
diff --git a/thunar/thunar-job-operation.c b/thunar/thunar-job-operation.c
index fcfbe0593..8197506cc 100644
--- a/thunar/thunar-job-operation.c
+++ b/thunar/thunar-job-operation.c
@@ -157,7 +157,7 @@ thunar_job_operation_add (ThunarJobOperation *job_operation,
 {
 
   _thunar_return_if_fail (THUNAR_IS_JOB_OPERATION (job_operation));
-  _thunar_return_if_fail (G_IS_FILE (source_file));
+  _thunar_return_if_fail (source_file == NULL || G_IS_FILE (source_file));
   _thunar_return_if_fail (target_file == NULL || G_IS_FILE (target_file));
 
   /* When a directory has a file operation applied to it (for e.g. deletion),
@@ -372,6 +372,12 @@ thunar_job_operation_new_invert (ThunarJobOperation *job_operation)
         inverted_operation->end_timestamp = job_operation->end_timestamp;
         break;
 
+      case THUNAR_JOB_OPERATION_KIND_CREATE:
+        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;
-- 
GitLab