From b3cb8fdf79d492ace55f3075608d08eed46e62a1 Mon Sep 17 00:00:00 2001
From: Matt Davis <mattdavis9@gmail.com>
Date: Sat, 14 Jul 2007 12:22:48 -0400
Subject: History with undo/redo capabilities.

Here is a working version of the history with undo/redo capabilities.
The idea here was based on a talk with Otavio Salvador who mentioned the concepts from Vanni Brutto.  The idea being to capture all changes and only apply them all at once.  This protects the user from performing an unwanted change.  What I did was capture all disk modifications that are committed to disk, and put them in a list.  The history manager allows the list to be traversed linearly, so that a change can be undone, viewed in parted (print command), or reapplied again.  Nothing actually happens to the physical disk until the 'save' command is issued.

Jim Meyering suggested that the functionality might be useful to libparted thus most of the functionality has been moved there.  The stdout displays (printf) are placed in parted.c as the library should not do any output printing on its own.

Three commands were added:
1) Undo : Undoes a disk modification
2) Redo : Redoes the most recent 'undone' modification
3) Save : Actually commits the list of non-undone modifications to disk

I feel a bit more testing needs to be done, but I am happy with the results right now.

-Matt
---
 include/parted/disk.h    |    1 +
 include/parted/history.h |   63 +++++++++++++++++++
 include/parted/parted.h  |    1 +
 libparted/Makefile.am    |    1 +
 libparted/disk.c         |   10 +++
 libparted/history.c      |  156 ++++++++++++++++++++++++++++++++++++++++++++++
 parted/command.c         |    2 +
 parted/parted.c          |   99 ++++++++++++++++++++++++-----
 8 files changed, 315 insertions(+), 18 deletions(-)
 create mode 100644 include/parted/history.h
 create mode 100644 libparted/history.c

diff --git a/include/parted/disk.h b/include/parted/disk.h
index 4dd81d9..adf16be 100644
--- a/include/parted/disk.h
+++ b/include/parted/disk.h
@@ -263,6 +263,7 @@ extern void ped_disk_destroy (PedDisk* disk);
 extern int ped_disk_commit (PedDisk* disk);
 extern int ped_disk_commit_to_dev (PedDisk* disk);
 extern int ped_disk_commit_to_os (PedDisk* disk);
+extern void ped_disk_commit_to_history (const PedDisk* disk);
 extern int ped_disk_check (const PedDisk* disk);
 extern void ped_disk_print (const PedDisk* disk);
 
diff --git a/include/parted/history.h b/include/parted/history.h
new file mode 100644
index 0000000..9dc4347
--- /dev/null
+++ b/include/parted/history.h
@@ -0,0 +1,63 @@
+#ifndef HISTORY_H_INCLUDED
+#define HISTORY_H_INCLUDED
+        
+#include "command.h"
+
+
+/*
+ * Macros
+ */
+
+#define PED_HISTORY_TAG      "[History] "
+#define PED_HISTORY_MAX_NAME 64
+        
+
+/*
+ * Structs
+ */
+ 
+typedef struct PedHistObj {
+    int                id;
+    int                ignore; /* Undo/Redo functionality */
+    const char        *name;   /* Command name */
+    PedDisk           *disk;
+    struct PedHistObj *prev;
+    struct PedHistObj *next;
+} PedHistObj;
+
+
+struct PedHistoryManager_t {
+    PedHistObj  *begin;
+    PedHistObj  *end;
+    int          n_objs;
+    int          id;
+};
+
+
+extern struct PedHistoryManager_t PedHistMgr;
+
+
+/*
+ * Funcs
+ */
+
+/* Add/Clear history */
+extern void history_add(const Command *cmd);
+extern void history_clear(void);
+
+/* Before changes are committed */
+extern void history_undo(void);
+extern void history_redo(void);
+
+/* Write changes to disk */
+extern void history_commit_to_disk(void);
+
+/* Print */
+extern void history_print(void);
+
+/* Alloc/dealloc */
+extern PedHistObj *history_alloc_obj(void);
+extern void history_dealloc_obj(PedHistObj *obj);
+
+
+#endif /* HISTORY_H_INCLUDED */
diff --git a/include/parted/parted.h b/include/parted/parted.h
index ff40ede..2f2df34 100644
--- a/include/parted/parted.h
+++ b/include/parted/parted.h
@@ -29,6 +29,7 @@ typedef struct _PedArchitecture PedArchitecture;
 #include <parted/constraint.h>
 #include <parted/device.h>
 #include <parted/disk.h>
+#include <parted/history.h>
 #include <parted/exception.h>
 #include <parted/filesys.h>
 #include <parted/natmath.h>
diff --git a/libparted/Makefile.am b/libparted/Makefile.am
index 0b013cc..83f4a3e 100644
--- a/libparted/Makefile.am
+++ b/libparted/Makefile.am
@@ -24,6 +24,7 @@ libparted_la_SOURCES  = debug.c			\
 			timer.c			\
 			unit.c			\
 			disk.c			\
+			history.c		\
 			cs/geom.c		\
 			cs/constraint.c		\
 			cs/natmath.c		\
diff --git a/libparted/disk.c b/libparted/disk.c
index 547d037..bc868a3 100644
--- a/libparted/disk.c
+++ b/libparted/disk.c
@@ -509,6 +509,16 @@ ped_disk_commit (PedDisk* disk)
 	return ped_disk_commit_to_os (disk);
 }
 
+/** 
+ * This adds the disk changes to history.  
+ * These changes can further be undone or redone
+ * until the choses to save them to the partition table.
+ */
+void
+ped_disk_commit_to_history (const PedDisk *disk)
+{
+}
+
 /**
  * \addtogroup PedPartition
  *
diff --git a/libparted/history.c b/libparted/history.c
new file mode 100644
index 0000000..ab4d392
--- /dev/null
+++ b/libparted/history.c
@@ -0,0 +1,156 @@
+#include <stdio.h>
+#include "history.h"
+#include "ui.h"
+
+
+struct PedHistoryManager_t PedHistMgr = {0};
+
+
+void 
+history_add(const char *name)
+{
+    PedHistObj *addme;
+
+    /* Deep copy and add to end of list */
+    addme = history_alloc_obj();
+    addme->name = strndup(name, PED_HISTORY_MAX_NAME);
+    addme->id = PedHistMgr.id++;
+            
+    /* This becomes the new end, so remember who is before us */
+    addme->prev = PedHistMgr.end;
+
+    /* Add this to the end of the list */
+    if (PedHistMgr.end)
+      PedHistMgr.end->next = addme;
+    
+    PedHistMgr.end = addme;
+    
+    if (!PedHistMgr.begin)
+      PedHistMgr.begin = addme;
+    
+    PedHistMgr.n_objs++;
+}
+
+
+void 
+history_clear(void)
+{
+    PedHistObj *ii, *freeme;
+ 
+    ii = PedHistMgr.begin;
+    while (ii)
+    {
+        freeme = ii;
+        ii = ii->next;
+        history_dealloc_obj(freeme);
+    }
+    memset(&PedHistMgr, 0, sizeof(PedHistMgr));
+}
+
+
+void 
+history_undo(void)
+{
+    PedHistObj *ii;
+
+    /* Mark the most recent change as ignored  and that is an
+     * actual 'disk' modification
+     * Start with the last command issued before this 'undo' call
+     */
+     for (ii=PedHistMgr.end->prev; ii; ii=ii->prev)
+       if (!ii->ignore && ii->disk)
+         break;
+ 
+     if (!ii)
+     {
+         printf(PED_HISTORY_TAG "Cannot undo\n");
+         return;
+     }
+
+     ii->ignore = 1;
+     printf(PED_HISTORY_TAG "Undo: %s\n", ii->name);
+}
+
+    
+void 
+history_redo(void)
+{
+    PedHistObj *ii;
+
+    /* Find the most recent undone entry that is a 'disk' modification  */
+    for (ii=PedHistMgr.begin; ii; ii=ii->next)
+      if (ii->ignore && ii->disk)
+        break;
+
+    if (!ii)
+    {
+        printf(PED_HISTORY_TAG "Cannot redo\n");
+        return;
+    }
+
+    ii->ignore = 0;
+    printf(PED_HISTORY_TAG "Redo: %s\n", ii->name);
+}
+
+
+PedHistObj *
+history_alloc_obj(void)
+{
+    PedHistObj *obj = (PedHistObj *)ped_malloc(sizeof(PedHistObj));
+    memset(obj, 0, sizeof(PedHistObj));
+    return obj;
+}
+
+
+void 
+history_dealloc_obj(PedHistObj *obj)
+{
+    if (!obj)
+      return;
+
+    if (obj->disk)
+      ped_disk_destroy(obj->disk);
+  
+    free(obj->name);
+    ped_free(obj);
+}
+
+
+void 
+history_commit_to_disk(void)
+{
+    PedHistObj *ii;
+
+    for (ii=PedHistMgr.begin; ii; ii=ii->next)
+      if (ii->disk && !ii->ignore)
+      {
+          printf(PED_HISTORY_TAG "Apply %s", ii->name);
+          if (ped_disk_commit(ii->disk))
+            printf(" (Success)\n");
+          else
+            printf(" (Failure)\n");
+      }
+   
+    /* Start fresh */
+    history_clear();
+} 
+
+
+/* Print all history objects */
+void 
+history_print(void)
+{
+    PedHistObj *ii;
+    
+    for (ii=PedHistMgr.begin; ii; ii=ii->next)
+    {
+        /* Only print disk changes */
+        if (!obj->disk)
+          return;
+            
+        printf("[History %d]\t%s", obj->id, obj->name);
+    
+        if (obj->ignore)
+          printf(" (UNDONE)");
+    }
+}
diff --git a/parted/command.c b/parted/command.c
index a5c4998..a233afc 100644
--- a/parted/command.c
+++ b/parted/command.c
@@ -22,6 +22,7 @@
 #include "ui.h"
 
 #include <parted/debug.h>
+#include <parted/history.h>
 
 #include <stdlib.h>
 #include <string.h>
@@ -137,6 +138,7 @@ command_print_help (Command* cmd)
 int
 command_run (Command* cmd, PedDevice** dev)
 {
+	history_add(str_list_convert(cmd->names));
 	return cmd->method (dev);
 }
 
diff --git a/parted/parted.c b/parted/parted.c
index 73be7a4..f65405a 100644
--- a/parted/parted.c
+++ b/parted/parted.c
@@ -44,6 +44,7 @@
 
 #include <parted/parted.h>
 #include <parted/debug.h>
+#include <parted/history.h>
 
 #include <ctype.h>
 #include <stdarg.h>
@@ -535,8 +536,7 @@ do_cp (PedDevice** dev)
 /* update the partition table, close disks */
         if (!ped_partition_set_system (dst, dst_fs_type))
                 goto error_destroy_disk;
-        if (!ped_disk_commit (dst_disk))
-                goto error_destroy_disk;
+        ped_disk_commit_to_history (dst_disk);
         if (src_disk != dst_disk)
                 ped_disk_destroy (src_disk);
         ped_disk_destroy (dst_disk);
@@ -620,8 +620,7 @@ do_mklabel (PedDevice** dev)
         if (!disk)
                 goto error;
 
-        if (!ped_disk_commit (disk))
-                goto error_destroy_disk;
+        ped_disk_commit_to_history (disk);
         ped_disk_destroy (disk);
 
         if ((*dev)->type != PED_DEVICE_FILE)
@@ -666,8 +665,7 @@ do_mkfs (PedDevice** dev)
                 goto error_destroy_disk;
         if (ped_partition_is_flag_available (part, PED_PARTITION_LBA))
                 ped_partition_set_flag (part, PED_PARTITION_LBA, 1);
-        if (!ped_disk_commit (disk))
-                goto error_destroy_disk;
+        ped_disk_commit_to_history (disk);
         ped_disk_destroy (disk);
 
         if ((*dev)->type != PED_DEVICE_FILE)
@@ -798,8 +796,7 @@ do_mkpart (PedDevice** dev)
         if (ped_partition_is_flag_available (part, PED_PARTITION_LBA))
                 ped_partition_set_flag (part, PED_PARTITION_LBA, 1);
         
-        if (!ped_disk_commit (disk))
-                goto error_destroy_disk;
+        ped_disk_commit_to_history (disk);
         
         /* clean up */
         ped_constraint_destroy (final_constraint);
@@ -973,8 +970,7 @@ do_mkpartfs (PedDevice** dev)
         if (!ped_partition_set_system (part, fs_type))
                 goto error_destroy_disk;
 
-        if (!ped_disk_commit (disk))
-                goto error_destroy_disk;
+        ped_disk_commit_to_history (disk);
 
         /* clean up */
         ped_constraint_destroy (final_constraint);
@@ -1091,8 +1087,7 @@ do_move (PedDevice** dev)
                 goto error_close_fs;
         ped_file_system_close (fs_copy);
         ped_file_system_close (fs);
-        if (!ped_disk_commit (disk))
-                goto error_destroy_disk;
+        ped_disk_commit_to_history (disk);
         ped_disk_destroy (disk);
         if (range_start != NULL)
                 ped_geometry_destroy (range_start);
@@ -1140,7 +1135,7 @@ do_name (PedDevice** dev)
                 goto error_free_name;
         free (name);
 
-        if (!ped_disk_commit (disk))
+        ped_disk_commit_to_history (disk);
                 goto error_destroy_disk;
         ped_disk_destroy (disk);
         return 1;
@@ -1641,7 +1636,7 @@ _rescue_add_partition (PedPartition* part)
         }
 
         ped_partition_set_system (part, fs_type);
-        ped_disk_commit (part->disk);
+        ped_disk_commit_to_history (part->disk);
         return 1;
 }
 
@@ -1817,7 +1812,7 @@ do_resize (PedDevice** dev)
                 ped_file_system_close (fs);
         }
 
-        ped_disk_commit (disk);
+        ped_disk_commit_to_history (disk);
         ped_constraint_destroy (constraint);
         ped_disk_destroy (disk);
         if (range_start != NULL)
@@ -1860,7 +1855,7 @@ do_rm (PedDevice** dev)
                 goto error_destroy_disk;
 
         ped_disk_delete_partition (disk, part);
-        ped_disk_commit (disk);
+        ped_disk_commit_to_history (disk);
         ped_disk_destroy (disk);
 
         if ((*dev)->type != PED_DEVICE_FILE)
@@ -1915,8 +1910,7 @@ do_set (PedDevice** dev)
     
         if (!ped_partition_set_flag (part, flag, state))
 	        	goto error_destroy_disk;
-    	if (!ped_disk_commit (disk))
-	        	goto error_destroy_disk;
+    	ped_disk_commit_to_history (disk);
     	ped_disk_destroy (disk);
 
         if ((*dev)->type != PED_DEVICE_FILE)
@@ -1961,6 +1955,34 @@ do_version ()
     return 1;
 }
 
+static int
+do_undo ()
+{
+    history_undo();
+    return 1;
+}
+    
+static int
+do_redo ()
+{
+    history_redo();
+    return 1;
+}
+    
+static int
+do_history ()
+{
+    history_print();
+    return 1;
+}
+    
+static int
+do_save ()
+{
+    history_commit_to_disk();
+    return 1;
+}
+ 
 static void
 _init_messages ()
 {
@@ -2279,6 +2301,47 @@ _("'version' displays copyright and version information corresponding to this "
 "copy of GNU Parted\n"),
 NULL), 1));
 
+command_register (commands, command_create (
+        str_list_create_unique ("undo", _("undo"), NULL),
+        do_undo,
+        str_list_create (
+_("undo                                     Undo change to partition table"), 
+  NULL),
+        str_list_create (
+_("'undo' undoes the previous partition table modifcation "), NULL), 1));
+
+command_register (commands, command_create (
+        str_list_create_unique ("redo", _("redo"), NULL),
+        do_redo,
+        str_list_create (
+_("redo                                     Redo change to partition table"), 
+  NULL),
+        str_list_create (
+_("'redo' redoes the previous change to the parititon table"),
+NULL), 1));
+
+
+
+command_register (commands, command_create (
+        str_list_create_unique ("history", _("history"), NULL),
+        do_history,
+        str_list_create (
+_("history                                  display command history"), NULL),
+        str_list_create (
+_("'history' displays list of commands that have are to be executed\n"),
+NULL), 1));
+
+command_register (commands, command_create (
+        str_list_create_unique ("save", _("save"), NULL),
+        do_save,
+        str_list_create (
+_("save                                     Write changes to partition table"), 
+  NULL),
+        str_list_create (
+_("'save' commits the changes that were created during the current parted "
+   "session.  This writes the changes to disk."),
+NULL), 1));
+
 }
 
 static void
-- 
1.5.1.5

