← Back to team overview

libbls-dev-list team mailing list archive

[Merge] lp:~libbls/libbls/undo-redo into lp:libbls

 

Alexandros Frantzis has proposed merging lp:~libbls/libbls/undo-redo into lp:libbls.

Requested reviews:
    libbls developers (libbls)

Undo/redo support for buffers.
-- 
https://code.launchpad.net/~libbls/libbls/undo-redo/+merge/5760
Your team Libbls development mailing list is subscribed to branch lp:libbls.
=== modified file 'bindings/libbls.i'
--- bindings/libbls.i	2009-04-01 18:55:46 +0000
+++ bindings/libbls.i	2009-04-04 08:50:29 +0000
@@ -19,6 +19,8 @@
 #include "buffer_util.h"
 #include "list.h"
 #include "util.h"
+#include "buffer_action.h"
+#include "buffer_action_edit.h"
 %}
 
 %pointer_class (size_t, size_tp)
@@ -68,7 +70,7 @@
 %apply segment_t ** { segcol_t ** , segcol_iter_t **, data_object_t **, void **}
 %apply segment_t ** { bless_buffer_t **, bless_buffer_source_t ** }
 %apply segment_t ** { priority_queue_t **, overlap_graph_t **, disjoint_set_t ** }
-%apply segment_t ** { struct list **, char **}
+%apply segment_t ** { struct list **, char **, buffer_action_t **}
 
 
 /* Exception for void **: Append void * to return list without conversion */
@@ -375,4 +377,6 @@
 %include "../src/list.h"
 %include "../src/buffer_options.h"
 %include "../src/util.h"
+%include "../src/buffer_action.h"
+%include "../src/buffer_action_edit.h"
 

=== modified file 'doc/user/user_guide.txt'
--- doc/user/user_guide.txt	2009-03-14 19:37:15 +0000
+++ doc/user/user_guide.txt	2009-04-21 19:59:28 +0000
@@ -1,5 +1,5 @@
 =======================
-Libbls 0.1.1 user guide
+Libbls 0.2 user guide
 =======================
 
 :Authors: Alexandros Frantzis, Michael Iatrou
@@ -385,6 +385,39 @@
     if (err)
         ...
 
+Undoing and redoing operations
+------------------------------
+
+Libbls buffers have built-in undo-redo capabilities. The functions used to
+perform undo and redo are unsuprisingly named ``bless_buffer_undo`` and 
+``bless_buffer_redo``::
+
+    int bless_buffer_undo(bless_buffer_t *buf);
+
+    int bless_buffer_redo(bless_buffer_t *buf);
+
+There are two related functions that query if there are any actions to 
+undo or redo::
+
+    int bless_buffer_can_undo(bless_buffer_t *buf, int *can_undo);
+
+    int bless_buffer_can_redo(bless_buffer_t *buf, int *can_redo);
+
+As usual for a linear undo-redo scheme, when you edit the buffer the 
+list of actions that can be redone is cleared.
+
+The maximum number of actions that can be undone/redone is controlled
+by the ``BLESS_BUF_UNDO_LIMIT`` buffer option (see `Setting buffer options`_).
+The default value for this options is ``"infinite"`` which means that there
+is no undo limiting.
+
+Note that undo/redo capabilities incur a performance and memory overhead so if
+you don't need them it is recommended that you turn them off (set
+``BLESS_BUF_UNDO_LIMIT`` to ``"0"``).
+
+By default, libbls tries to retain as much as possible of undo/redo history
+after a save. This is controlled by the ``BLESS_BUF_UNDO_AFTER_SAVE`` buffer
+option (see `Setting buffer options`_).
 
 Reading data from the buffer
 ============================
@@ -434,6 +467,10 @@
 sufficient. The directory used to store temporary files can be set using the
 ``BLESS_BUF_TMP_DIR`` option (see `Setting buffer options`_).
 
+By default, libbls tries to retain as much as possible of undo/redo history
+after a save. This is controlled by the ``BLESS_BUF_UNDO_AFTER_SAVE`` buffer
+option (see `Setting buffer options`_).
+
 An example of how to save a buffer::
 
     /* Assume "buf" is initialized and contains some data */
@@ -470,7 +507,22 @@
 
 BLESS_BUF_TMP_DIR
     The directory to store temporary files in (see `Saving the buffer contents
-    to a file`_). The default value is `/tmp`.
+    to a file`_). The default value is ``"/tmp"``.
+
+BLESS_BUF_UNDO_LIMIT
+    The maximum number of actions that can be undone or redone. Acceptable
+    values are strings representing natural numbers or ``"infinite"`` to
+    turn off undo limiting. A value of ``"0"`` turns off undo/redo
+    capabilities. The default value is ``"infinite"`` (no undo limiting).
+
+BLESS_BUF_UNDO_AFTER_SAVE
+    Whether to be able to undo/redo after saving a buffer. The acceptable
+    values are ``"always"``, ``"never"`` and ``"best_effort"``. If the value is
+    ``"always"`` a save won't be performed unless it is guaranteed that all
+    undo/redo actions can be safely performed after the save. A value of
+    ``"never"`` clears all undo redo actions after a successful save. If the
+    value is ``"best_effort"`` libbls does its best to keep as much history as
+    it can (eg what fits in memory). The default value is ``"best_effort"``.
 
 An example of setting a buffer option::
 

=== modified file 'src/buffer.h'
--- src/buffer.h	2009-02-22 13:48:53 +0000
+++ src/buffer.h	2009-03-29 14:41:18 +0000
@@ -106,11 +106,11 @@
  * @{
  */
 
-/* Not yet implemented
 int bless_buffer_undo(bless_buffer_t *buf);
 
 int bless_buffer_redo(bless_buffer_t *buf);
 
+/* Not yet implemented
 int bless_buffer_begin_multi_op(bless_buffer_t *buf);
 
 int bless_buffer_end_multi_op(bless_buffer_t *buf);
@@ -123,11 +123,9 @@
  * @{
  */
 
-/* Not yet implemented
 int bless_buffer_can_undo(bless_buffer_t *buf, int *can_undo);
 
 int bless_buffer_can_redo(bless_buffer_t *buf, int *can_redo);
-*/
 
 int bless_buffer_get_size(bless_buffer_t *buf, off_t *size);
 

=== added file 'src/buffer_action.c'
--- src/buffer_action.c	1970-01-01 00:00:00 +0000
+++ src/buffer_action.c	2009-04-06 18:23:30 +0000
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2009 Alexandros Frantzis, Michael Iatrou
+ *
+ * This file is part of libbls.
+ *
+ * libbls is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * libbls 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
+ * Foobar.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file buffer_action.c
+ *
+ * Buffer actions implementation
+ */
+#include <stdlib.h>
+#include <errno.h>
+
+#include "data_object.h"
+#include "buffer_action.h"
+#include "buffer_action_internal.h"
+#include "util.h"
+
+struct buffer_action {
+	void *impl;
+	struct buffer_action_funcs *funcs;
+};
+
+/**********************
+ * Internal functions *
+ **********************/
+
+/**
+ * Creates a buffer_action_t using a specific implementation.
+ *
+ * @param[out] action the created buffer_action_t
+ * @param impl the implementation private data
+ * @param funcs function pointers to the implementations' functions
+ *
+ * @return the operation status code
+ */
+int buffer_action_create_impl(buffer_action_t **action, void *impl,
+		struct buffer_action_funcs *funcs)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	*action = malloc(sizeof(buffer_action_t));
+
+	if (*action == NULL)
+		return_error(ENOMEM);
+
+	(*action)->impl = impl;
+	(*action)->funcs = funcs;
+
+	return 0;
+}
+
+/**
+ * Gets the implementation of a buffer_action_t
+ *
+ * @param action the buffer_action_t to get the implementation of
+ *
+ * @return the implementation
+ */
+void *buffer_action_get_impl(buffer_action_t *action)
+{
+	return action->impl;
+}
+
+/*****************
+ * API functions *
+ *****************/
+
+
+/** 
+ * Performs a buffer_action_t.
+ * 
+ * @param action the action to perform
+ * 
+ * @return the operation error code
+ */
+int buffer_action_do(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	return (*action->funcs->do_func)(action);
+}
+
+/** 
+ * Reverts a buffer_action_t.
+ * 
+ * @param action the action to perform
+ * 
+ * @return the operation error code
+ */
+int buffer_action_undo(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	return (*action->funcs->undo_func)(action);
+}
+
+/** 
+ * Makes a private copy of all the data held by this action
+ * that belong to a data object.
+ *
+ * The data object is represented by a supplied
+ * data_object_t although the original data may have been
+ * accessed using another data_object_t (which of course
+ * refers to the same data object as the supplied).
+ * 
+ * @param action the action to perform
+ * @param dobj the data_object_t the data must belong to
+ * 
+ * @return the operation error code
+ */
+int buffer_action_private_copy(buffer_action_t *action, data_object_t *dobj)
+{
+	if (action == NULL || dobj == NULL)
+		return_error(EINVAL);
+
+	return (*action->funcs->private_copy_func)(action, dobj);
+}
+
+/** 
+ * Frees a buffer_action_t.
+ * 
+ * @param action the action to perform
+ * 
+ * @return the operation error code
+ */
+int buffer_action_free(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	(*action->funcs->free_func)(action);
+	free(action);
+	return 0;
+}

=== added file 'src/buffer_action.h'
--- src/buffer_action.h	1970-01-01 00:00:00 +0000
+++ src/buffer_action.h	2009-04-06 18:23:30 +0000
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2009 Alexandros Frantzis, Michael Iatrou
+ *
+ * This file is part of libbls.
+ *
+ * libbls is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * libbls 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
+ * Foobar.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file buffer_action.h
+ *
+ * Buffer actions API
+ */
+#ifndef _BUFFER_ACTION_H
+#define _BUFFER_ACTION_H
+
+#include "data_object.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup buffer_action Buffer Action
+ *
+ * A buffer action abstracts the notion of a doable and undoable actions
+ * on the buffer.
+ *
+ * There are many kinds of buffer actions (eg append, insert). To create
+ * a buffer action one must use the specific constructor function.
+ *
+ * @{
+ */
+
+/**
+ * Opaque type for buffer action ADT.
+ */
+typedef struct buffer_action buffer_action_t;
+
+int buffer_action_do(buffer_action_t *action);
+
+int buffer_action_undo(buffer_action_t *action);
+
+int buffer_action_private_copy(buffer_action_t *action, data_object_t *dobj);
+
+int buffer_action_free(buffer_action_t *action);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BUFFER_ACTION_H */
+

=== added file 'src/buffer_action_edit.c'
--- src/buffer_action_edit.c	1970-01-01 00:00:00 +0000
+++ src/buffer_action_edit.c	2009-04-21 19:37:06 +0000
@@ -0,0 +1,717 @@
+/*
+ * Copyright 2009 Alexandros Frantzis, Michael Iatrou
+ *
+ * This file is part of libbls.
+ *
+ * libbls is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * libbls 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
+ * Foobar.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file buffer_action_edit.h
+ *
+ * Buffer edit actions implementation
+ */
+#include <stdlib.h>
+#include <errno.h>
+
+#include "buffer.h"
+#include "buffer_internal.h"
+#include "buffer_source.h"
+#include "buffer_action.h"
+#include "buffer_action_internal.h"
+#include "buffer_action_edit.h"
+#include "buffer_util.h"
+#include "segcol.h"
+#include "data_object.h"
+#include "data_object_memory.h"
+#include "util.h"
+
+
+/* Forward declarations */
+
+/* Helper functions */
+static int create_segment_from_source(segment_t **seg, 
+		bless_buffer_source_t *src, off_t src_offset, off_t length);
+static int segment_inplace_private_copy(segment_t *seg, data_object_t *cmp_dobj);
+static int segcol_inplace_private_copy(segcol_t *segcol, data_object_t *cmp_dobj);
+
+/* API functions */
+static int buffer_action_append_do(buffer_action_t *action);
+static int buffer_action_append_undo(buffer_action_t *action);
+static int buffer_action_append_private_copy(buffer_action_t *action,
+		data_object_t *dobj);
+static int buffer_action_append_free(buffer_action_t *action);
+
+static int buffer_action_insert_do(buffer_action_t *action);
+static int buffer_action_insert_undo(buffer_action_t *action);
+static int buffer_action_insert_private_copy(buffer_action_t *action,
+		data_object_t *dobj);
+static int buffer_action_insert_free(buffer_action_t *action);
+
+static int buffer_action_delete_do(buffer_action_t *action);
+static int buffer_action_delete_undo(buffer_action_t *action);
+static int buffer_action_delete_private_copy(buffer_action_t *action,
+		data_object_t *dobj);
+static int buffer_action_delete_free(buffer_action_t *action);
+
+/* Action functions */
+static struct buffer_action_funcs buffer_action_append_funcs = {
+	.do_func = buffer_action_append_do,
+	.undo_func = buffer_action_append_undo,
+	.private_copy_func = buffer_action_append_private_copy,
+	.free_func = buffer_action_append_free
+};
+
+static struct buffer_action_funcs buffer_action_insert_funcs = {
+	.do_func = buffer_action_insert_do,
+	.undo_func = buffer_action_insert_undo,
+	.private_copy_func = buffer_action_insert_private_copy,
+	.free_func = buffer_action_insert_free
+};
+
+static struct buffer_action_funcs buffer_action_delete_funcs = {
+	.do_func = buffer_action_delete_do,
+	.undo_func = buffer_action_delete_undo,
+	.private_copy_func = buffer_action_delete_private_copy,
+	.free_func = buffer_action_delete_free
+};
+
+/* Action implementations */
+struct buffer_action_append_impl {
+	bless_buffer_t *buf;
+	segment_t *seg;
+};
+
+struct buffer_action_insert_impl {
+	bless_buffer_t *buf;
+	off_t offset;
+	segment_t *seg;
+};
+
+struct buffer_action_delete_impl {
+	bless_buffer_t *buf;
+	off_t offset;
+	off_t length;
+	segcol_t *deleted;
+};
+
+/********************
+ * Helper functions *
+ ********************/
+
+/**
+ * Create a segment from a data_object_t.
+ *
+ * @param[out] seg the created segment
+ * @param src_dobj the data_object_t
+ * @param src_offset the start of the segment range in src
+ * @param length the length of the segment range
+ *
+ * @return the operation error code
+ */
+static int create_segment_from_source(segment_t **seg, 
+		bless_buffer_source_t *src, off_t src_offset, off_t length)
+{
+	data_object_t *dobj = (data_object_t *) src;
+	/* Create a segment pointing to the data object */
+	int err = segment_new(seg, dobj, src_offset, length,
+			data_object_update_usage);
+	if (err)
+		return_error(err);
+
+	/* 
+	 * Check if the specified file range is valid. This is done 
+	 * here so that overflow has already been checked by segment_new().
+	 */
+	off_t dobj_size;
+	err = data_object_get_size(dobj, &dobj_size);
+	if (err)
+		goto fail;
+
+	if (src_offset + length - 1 * (length != 0) >= dobj_size) {
+		err = EINVAL;
+		goto fail;
+	}
+
+	return 0;
+
+fail:
+	/* No need to free obj, this is handled by segment_free */
+	segment_free(*seg);
+	return_error(err);
+}
+
+/** 
+ * Makes a private copy of the data held by a segment if
+ * that data belongs to a specific data_object_t.
+ *
+ * The operation is performed in-place: upon completion
+ * the segment will point to the private copy of the data.
+ * 
+ * @param seg the segment_t
+ * @param cmp_dobj the data_object_t the data must belong to
+ * 
+ * @return the operation error code
+ */
+static int segment_inplace_private_copy(segment_t *seg, data_object_t *cmp_dobj)
+{
+	data_object_t *seg_dobj;
+	off_t seg_start;
+	off_t seg_size;
+
+	/* Get segment information */
+	int err = segment_get_data(seg, (void **)&seg_dobj);
+	if (err)
+		return_error(err);
+
+	/* Continue this operation only if the data comes from the cmp_dobj */
+	int result;
+	err = data_object_compare(&result, seg_dobj, cmp_dobj);
+	if (err)
+		return_error(err);
+	if (result != 0)
+		return 0;
+
+	err = segment_get_start(seg, &seg_start);
+	if (err)
+		return_error(err);
+	err = segment_get_size(seg, &seg_size);
+	if (err)
+		return_error(err);
+
+	/* Create new data object to hold data */
+	void *new_data = malloc(seg_size);
+	if (new_data == NULL)
+		return_error(ENOMEM);
+
+	data_object_t *new_dobj;
+	err = data_object_memory_new(&new_dobj, new_data, seg_size);
+	if (err) {
+		free(new_data);
+		return_error(err);
+	}
+
+	/* Set the data object's free function */
+	err = data_object_memory_set_free_func(new_dobj, free);
+	if (err) {
+		free(new_data);
+		data_object_free(new_dobj);
+		return_error(err);
+	}
+
+	/* Copy the data to the new object and setup the segment */
+	err = read_data_object(seg_dobj, seg_start, new_data, seg_size);
+	if (err) {
+		data_object_free(new_dobj);
+		return_error(err);
+	}
+
+	err = segment_set_range(seg, 0, seg_size);
+	if (err) {
+		data_object_free(new_dobj);
+		return_error(err);
+	}
+
+	err = segment_set_data(seg, new_dobj, data_object_update_usage);
+	if (err) {
+		segment_set_range(seg, seg_start, seg_size);
+		data_object_free(new_dobj);
+		return_error(err);
+	}
+
+
+	return 0;
+}
+
+/** 
+ * Makes a private copy of the data held by a segcol if
+ * that data belongs to a specific data_object_t.
+ *
+ * The operation is performed in-place: upon completion
+ * the segments in the segment collection will point to
+ * the private copy of the data.
+ * 
+ * @param seg the segcol_t
+ * @param cmp_dobj the data_object_t the data must belong to
+ * 
+ * @return the operation error code
+ */
+static int segcol_inplace_private_copy(segcol_t *segcol, data_object_t *cmp_dobj)
+{
+	segcol_iter_t *iter;
+	int err = segcol_iter_new(segcol, &iter);
+	if (err)
+		return_error(err);
+
+	int iter_valid;
+
+	/* 
+	 * Iterate over the whole segcol
+	 */
+	while (!(err = segcol_iter_is_valid(iter, &iter_valid)) && iter_valid) {
+		segment_t *seg;
+
+		err = segcol_iter_get_segment(iter, &seg);
+		if (err)
+			goto fail;
+
+		/* Make private copy of segment data (if they belong to cmp_dobj) */
+		err = segment_inplace_private_copy(seg, cmp_dobj);
+		if (err)
+			goto fail;
+
+		err = segcol_iter_next(iter);
+		if (err)
+			goto fail;
+
+	}
+
+	segcol_iter_free(iter);
+
+	return 0;
+
+fail:
+	segcol_iter_free(iter);
+	return_error(err);
+}
+
+/****************
+ * Constructors *
+ ****************/
+
+/** 
+ * Creates a new append buffer_action_t.
+ * 
+ * @param [out] action the created buffer_action_t
+ * @param buf the buffer_t to append data to
+ * @param src the source to append data from
+ * @param src_offset the offset of the source to get data from
+ * @param length the length in bytes of the data to append
+ * 
+ * @return the operation error code
+ */
+int buffer_action_append_new(buffer_action_t **action, bless_buffer_t *buf,
+		bless_buffer_source_t *src, off_t src_offset, off_t length)
+{
+	if (action == NULL || buf == NULL || src == NULL)
+		return_error(EINVAL);
+
+	/* Allocate memory for implementation */
+	struct buffer_action_append_impl *impl =
+		malloc(sizeof(struct buffer_action_append_impl));
+	
+	if (impl == NULL)
+		return_error(EINVAL);
+
+	/* Create buffer_action_t */
+	int err = buffer_action_create_impl(action, impl,
+			&buffer_action_append_funcs);
+
+	if (err)
+		goto fail;
+
+	/* Initialize implementation */
+	err = create_segment_from_source(&impl->seg, src, src_offset, length);
+	if (err) 
+		goto fail_segment;
+
+	impl->buf = buf;
+
+	return 0;
+
+fail_segment:
+	free(*action);
+fail:
+	free(impl);
+	return_error(err);
+}
+
+/** 
+ * Creates a new insert buffer_action_t.
+ * 
+ * @param [out] action the created buffer_action_t
+ * @param buf the buffer_t to insert data to
+ * @param offset the offset in the buffer_t to insert to
+ * @param src the source to append data from
+ * @param src_offset the offset of the source to get data from
+ * @param length the length in bytes of the data to append
+ * 
+ * @return the operation error code
+ */
+int buffer_action_insert_new(buffer_action_t **action, bless_buffer_t *buf,
+		off_t offset, bless_buffer_source_t *src, off_t src_offset, off_t length)
+{
+	if (action == NULL || buf == NULL || src == NULL)
+		return_error(EINVAL);
+
+	/* Allocate memory for implementation */
+	struct buffer_action_insert_impl *impl =
+		malloc(sizeof(struct buffer_action_insert_impl));
+	
+	if (impl == NULL)
+		return_error(EINVAL);
+
+	/* Create buffer_action_t */
+	int err = buffer_action_create_impl(action, impl,
+			&buffer_action_insert_funcs);
+
+	if (err)
+		goto fail;
+
+	/* Initialize implementation */
+	err = create_segment_from_source(&impl->seg, src, src_offset, length);
+	if (err)
+		goto fail_segment;
+
+	impl->buf = buf;
+	impl->offset = offset;
+
+	return 0;
+
+fail_segment:
+	free(*action);
+fail:
+	free(impl);
+	return_error(err);
+}
+
+/** 
+ * Creates a new delete buffer_action_t.
+ * 
+ * @param [out] action the created buffer_action_t
+ * @param buf the buffer_t to delete data from
+ * @param offset the offset in the buffer_t to delete from
+ * @param length the length in bytes of the data to delete
+ * 
+ * @return the operation error code
+ */
+int buffer_action_delete_new(buffer_action_t **action, bless_buffer_t *buf,
+		off_t offset, off_t length)
+{
+	if (action == NULL || buf == NULL)
+		return_error(EINVAL);
+
+	/* Allocate memory for implementation */
+	struct buffer_action_delete_impl *impl =
+		malloc(sizeof(struct buffer_action_delete_impl));
+	
+	if (impl == NULL)
+		return_error(EINVAL);
+
+	/* Create buffer_action_t */
+	int err = buffer_action_create_impl(action, impl,
+			&buffer_action_delete_funcs);
+
+	if (err)
+		goto fail;
+
+	/* Initialize implementation */
+	impl->buf = buf;
+	impl->offset = offset;
+	impl->length = length;
+	impl->deleted = NULL;
+
+	return 0;
+
+fail:
+	free(impl);
+	return_error(err);
+}
+
+
+/********************
+ * Append Functions *
+ ********************/
+
+static int buffer_action_append_do(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_append_impl *impl =
+		(struct buffer_action_append_impl *) buffer_action_get_impl(action);
+
+	/* 
+	 * No need to check for overflow, because it is detected by the
+	 * functions that follow.
+	 */
+	segment_t *seg;
+
+	int err = segment_copy(impl->seg, &seg);
+	if (err)
+		return_error(err);
+	
+	segcol_t *sc = impl->buf->segcol;
+
+	/* Append segment to the segcol */
+	err = segcol_append(sc, seg);
+	if (err) 
+		goto fail;
+
+	return 0;
+
+fail:
+	segment_free(seg);
+	return_error(err);
+}
+
+static int buffer_action_append_undo(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_append_impl *impl =
+		(struct buffer_action_append_impl *) buffer_action_get_impl(action);
+
+	/* 
+	 * No need to check for overflow, because it is detected by the
+	 * functions that follow.
+	 */
+	segcol_t *sc = impl->buf->segcol;
+	off_t segcol_size;
+	int err = segcol_get_size(sc, &segcol_size);
+	if (err)
+		return_error(err);
+
+	off_t seg_size;
+	err = segment_get_size(impl->seg, &seg_size);
+	if (err)
+		return_error(err);
+
+	/* Delete range from the segcol */
+	err = segcol_delete(sc, NULL, segcol_size - seg_size, seg_size);
+	if (err) 
+		return_error(err);
+
+	return 0;
+}
+
+static int buffer_action_append_private_copy(buffer_action_t *action,
+		data_object_t *cmp_dobj)
+{
+	if (action == NULL || cmp_dobj == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_append_impl *impl =
+		(struct buffer_action_append_impl *) buffer_action_get_impl(action);
+
+	int err = segment_inplace_private_copy(impl->seg, cmp_dobj);
+	if (err)
+		return_error(err);
+
+	return 0;
+}
+
+static int buffer_action_append_free(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_append_impl *impl =
+		(struct buffer_action_append_impl *) buffer_action_get_impl(action);
+
+	int err = segment_free(impl->seg);
+	if (err)
+		return_error(err);
+
+	free(impl);
+
+	return 0;
+}
+
+/********************
+ * Insert Functions *
+ ********************/
+
+static int buffer_action_insert_do(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_insert_impl *impl =
+		(struct buffer_action_insert_impl *) buffer_action_get_impl(action);
+
+	/* 
+	 * No need to check for overflow, because it is detected by the
+	 * functions that follow.
+	 */
+	segment_t *seg;
+
+	int err = segment_copy(impl->seg, &seg);
+	if (err)
+		return_error(err);
+	
+	segcol_t *sc = impl->buf->segcol;
+
+	/* Append segment to the segcol */
+	err = segcol_insert(sc, impl->offset, seg);
+	if (err) 
+		goto fail;
+
+	return 0;
+fail:
+	segment_free(seg);
+	return_error(err);
+}
+
+static int buffer_action_insert_undo(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_insert_impl *impl =
+		(struct buffer_action_insert_impl *) buffer_action_get_impl(action);
+
+	/* 
+	 * No need to check for overflow, because it is detected by the
+	 * functions that follow.
+	 */
+	segcol_t *sc = impl->buf->segcol;
+
+	off_t seg_size;
+	int err = segment_get_size(impl->seg, &seg_size);
+	if (err)
+		return_error(err);
+
+	/* Delete range from the segcol */
+	err = segcol_delete(sc, NULL, impl->offset, seg_size);
+	if (err) 
+		return_error(err);
+
+	return 0;
+}
+
+static int buffer_action_insert_private_copy(buffer_action_t *action,
+		data_object_t *cmp_dobj)
+{
+	if (action == NULL || cmp_dobj == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_insert_impl *impl =
+		(struct buffer_action_insert_impl *) buffer_action_get_impl(action);
+
+	int err = segment_inplace_private_copy(impl->seg, cmp_dobj);
+	if (err)
+		return_error(err);
+
+	return 0;
+}
+
+static int buffer_action_insert_free(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_insert_impl *impl =
+		(struct buffer_action_insert_impl *) buffer_action_get_impl(action);
+
+	int err = segment_free(impl->seg);
+	if (err)
+		return_error(err);
+
+	free(impl);
+
+	return 0;
+}
+
+/********************
+ * Delete Functions *
+ ********************/
+
+static int buffer_action_delete_do(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_delete_impl *impl =
+		(struct buffer_action_delete_impl *) buffer_action_get_impl(action);
+
+	/* 
+	 * No need to check for overflow, valid ranges etc.
+	 * They are all checked in segcol_delete().
+	 */
+	segcol_t *deleted;
+	int err = segcol_delete(impl->buf->segcol, &deleted, impl->offset,
+			impl->length);
+
+	if (err)
+		return_error(err);
+
+	/* Free previous deleted data if any */
+	if (impl->deleted != NULL) {
+		err = segcol_free(impl->deleted);
+		if (err) {
+			segcol_add_copy(impl->buf->segcol, impl->offset, deleted);
+			segcol_free(deleted);
+			return_error(err);
+		}
+	}
+
+	/* Store new deleted data */
+	impl->deleted = deleted;
+
+	return 0;
+}
+
+static int buffer_action_delete_undo(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_delete_impl *impl =
+		(struct buffer_action_delete_impl *) buffer_action_get_impl(action);
+
+	/* Add the deleted data back to the segcol */
+	int err = segcol_add_copy(impl->buf->segcol, impl->offset, impl->deleted);
+	if (err)
+		return_error(err);
+		
+	return 0;
+}
+
+static int buffer_action_delete_private_copy(buffer_action_t *action,
+		data_object_t *cmp_dobj)
+{
+	if (action == NULL || cmp_dobj == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_delete_impl *impl =
+		(struct buffer_action_delete_impl *) buffer_action_get_impl(action);
+
+	int err = segcol_inplace_private_copy(impl->deleted, cmp_dobj);
+	if (err)
+		return_error(err);
+
+	return 0;
+}
+
+static int buffer_action_delete_free(buffer_action_t *action)
+{
+	if (action == NULL)
+		return_error(EINVAL);
+
+	struct buffer_action_delete_impl *impl =
+		(struct buffer_action_delete_impl *) buffer_action_get_impl(action);
+
+	if (impl->deleted != NULL) {
+		int err = segcol_free(impl->deleted);
+		if (err)
+			return err;
+	}
+
+	free(impl);
+
+	return 0;
+}
+

=== added file 'src/buffer_action_edit.h'
--- src/buffer_action_edit.h	1970-01-01 00:00:00 +0000
+++ src/buffer_action_edit.h	2009-03-28 10:08:29 +0000
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2009 Alexandros Frantzis, Michael Iatrou
+ *
+ * This file is part of libbls.
+ *
+ * libbls is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * libbls 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
+ * Foobar.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file buffer_action_edit.h
+ *
+ * Buffer edit actions API
+ */
+#ifndef _BUFFER_ACTION_EDIT_H
+#define _BUFFER_ACTION_EDIT_H
+
+#include "buffer.h"
+#include "buffer_source.h"
+#include "buffer_action.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup buffer_action
+ *
+ * @{
+ */
+
+/**
+ * @name Constructor functions
+ * 
+ * @{
+ */
+int buffer_action_append_new(buffer_action_t **action, bless_buffer_t *buf,
+		bless_buffer_source_t *src, off_t src_offset, off_t length);
+
+int buffer_action_insert_new(buffer_action_t **action, bless_buffer_t *buf,
+		off_t offset, bless_buffer_source_t *src, off_t src_offset, off_t length);
+
+int buffer_action_delete_new(buffer_action_t **action, bless_buffer_t *buf,
+		off_t offset, off_t length);
+
+/** @} */
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BUFFER_ACTION_EDIT_H */

=== added file 'src/buffer_action_internal.h'
--- src/buffer_action_internal.h	1970-01-01 00:00:00 +0000
+++ src/buffer_action_internal.h	2009-04-06 18:23:30 +0000
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2009 Alexandros Frantzis, Michael Iatrou
+ *
+ * This file is part of libbls.
+ *
+ * libbls is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * libbls 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
+ * Foobar.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file buffer_action_internal.h
+ *
+ * Buffer actions internal API
+ */
+#ifndef _BUFFER_ACTION_INTERNAL_H
+#define _BUFFER_ACTION_INTERNAL_H
+
+#include "data_object.h"
+#include "buffer_action.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup buffer_action
+ *
+ * @{
+ */
+
+/** 
+ * Struct that holds the function pointers for an implementation of a
+ * buffer_action_t.
+ */
+struct buffer_action_funcs {
+	int (*do_func)(buffer_action_t *action);
+	int (*undo_func)(buffer_action_t *action);
+	int (*private_copy_func)(buffer_action_t *action, data_object_t *obj);
+	int (*free_func)(buffer_action_t *action);
+};
+
+/**
+ * @name Internal functions
+ * 
+ * @{
+ */
+int buffer_action_create_impl(buffer_action_t **action, void *impl,
+		struct buffer_action_funcs *funcs);
+
+void *buffer_action_get_impl(buffer_action_t *action);
+
+/** @} */
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BUFFER_ACTION_INTERNAL_H */

=== modified file 'src/buffer_edit.c'
--- src/buffer_edit.c	2009-01-28 10:36:54 +0000
+++ src/buffer_edit.c	2009-04-17 17:03:58 +0000
@@ -32,56 +32,16 @@
 #include "data_object.h"
 #include "data_object_memory.h"
 #include "type_limits.h"
+#include "buffer_action.h"
+#include "buffer_action_edit.h"
 
 #include "util.h"
 
 #pragma GCC visibility push(default)
 
-/********************/
-/* Helper functions */
-/********************/
-
-/**
- * Create a segment from a bless_buffer_source_t.
- *
- * @param[out] seg the created segment
- * @param src the source
- * @param src_offset the start of the segment range in src
- * @param length the length of the segment range
- *
- * @return the operation error code
- */
-static int create_segment_from_source(segment_t **seg, bless_buffer_source_t *src,
-		off_t src_offset, off_t length)
-{
-	data_object_t *obj = (data_object_t *) src;
-
-	/* Create a segment pointing to the data object */
-	int err = segment_new(seg, obj, src_offset, length, data_object_update_usage);
-	if (err)
-		return_error(err);
-
-	/* 
-	 * Check if the specified file range is valid. This is done 
-	 * here so that overflow has already been checked by segment_new().
-	 */
-	off_t obj_size;
-	err = data_object_get_size(obj, &obj_size);
-	if (err)
-		goto fail;
-
-	if (src_offset + length - 1 * (length != 0) >= obj_size) {
-		err = EINVAL;
-		goto fail;
-	}
-
-	return 0;
-
-fail:
-	/* No need to free obj, this is handled by segment_free */
-	segment_free(*seg);
-	return_error(err);
-}
+/********************
+ * Helper functions *
+ ********************/
 
 /**
  * A segcol_foreach_func that reads data from a segment_t into memory.
@@ -114,9 +74,44 @@
 	return 0;
 }
 
-/*****************/
-/* API Functions */
-/*****************/
+/** 
+ * Appends a buffer_action_t to an action list.
+ * 
+ * @param list the action list to append to
+ * @param action the action to append
+ * 
+ * @return the operation error code
+ */
+static int undo_list_append(bless_buffer_t *buf, buffer_action_t *action)
+{
+	if (buf == NULL || action == NULL)
+		return_error(EINVAL);
+
+	/* Create a new buffer_action_entry */
+	struct buffer_action_entry *entry;
+
+	int err = list_new_entry(&entry, struct buffer_action_entry, ln);
+	if (err)
+		return_error(err);
+
+	entry->action = action;
+
+	/* Append it to the list */
+	err = list_insert_before(action_list_tail(buf->undo_list), &entry->ln);
+	if (err) {
+		free(entry);
+		return_error(err);
+	}
+
+	++buf->undo_list_size;
+
+	return 0;
+}
+
+
+/*****************
+ * API Functions *
+ *****************/
 
 /**
  * Appends data to a bless_buffer_t.
@@ -134,28 +129,44 @@
 	if (buf == NULL || src == NULL) 
 		return_error(EINVAL);
 
-	/* 
-	 * No need to check for overflow, because it is detected by the
-	 * functions that follow.
-	 */
-
-	segment_t *seg;
-
-	int err = create_segment_from_source(&seg, src, src_offset, length);
+	buffer_action_t *action;
+
+	/* Create an append action */
+	int err = buffer_action_append_new(&action, buf, src, src_offset, length);
+
 	if (err)
 		return_error(err);
-	
-	segcol_t *sc = buf->segcol;
-
-	/* Append segment to the segcol */
-	err = segcol_append(sc, seg);
-	if (err) 
-		goto fail;
+
+	/* Perform action */
+	err = buffer_action_do(action);
+	if (err)
+		goto fail;
+		
+	/* 
+	 * Make sure that the undo list has space for one action (provided the
+	 * undo limit is > 0).
+	 */
+	err = undo_list_enforce_limit(buf, 1);
+	if (err)
+		goto fail;
+
+	/* 
+	 * If we have space in the undo list to append the action.
+	 * Then only case we won't have space is when the undo limit is 0.
+	 */
+	if (buf->undo_list_size < buf->options->undo_limit) {
+		err = undo_list_append(buf, action);
+		if (err)
+			goto fail;
+	}
+
+	action_list_clear(buf->redo_list);
+	buf->redo_list_size = 0;
 
 	return 0;
+
 fail:
-	/* No need to free obj, this is handled by segment_free */
-	segment_free(seg);
+	buffer_action_free(action);
 	return_error(err);
 }
 
@@ -176,28 +187,47 @@
 	if (buf == NULL || src == NULL) 
 		return_error(EINVAL);
 
+	/* Create an insert action */
+	buffer_action_t *action;
+
+	int err = buffer_action_insert_new(&action, buf, offset, src, src_offset,
+			length);
+
+	if (err)
+		return_error(err);
+
+	/* Perform action */
+	err = buffer_action_do(action);
+	if (err) {
+		buffer_action_free(action);
+		return_error(err);
+	}
+		
 	/* 
-	 * No need to check for overflow, because it is detected by the
-	 * functions that follow.
+	 * Make sure that the undo list has space for one action (provided the
+	 * undo limit is > 0).
 	 */
-
-	segment_t *seg;
-
-	int err = create_segment_from_source(&seg, src, src_offset, length);
+	err = undo_list_enforce_limit(buf, 1);
 	if (err)
-		return_error(err);
-
-	segcol_t *sc = buf->segcol;
-
-	/* Insert segment to the segcol */
-	err = segcol_insert(sc, offset, seg);
-	if (err) 
 		goto fail;
 
+	/* 
+	 * If we have space in the undo list to append the action.
+	 * Then only case we won't have space is when the undo limit is 0.
+	 */
+	if (buf->undo_list_size < buf->options->undo_limit) {
+		err = undo_list_append(buf, action);
+		if (err)
+			goto fail;
+	}
+
+	action_list_clear(buf->redo_list);
+	buf->redo_list_size = 0;
+
 	return 0;
+
 fail:
-	/* No need to free obj, this is handled by segment_free */
-	segment_free(seg);
+	buffer_action_free(action);
 	return_error(err);
 }
 
@@ -212,20 +242,50 @@
  */
 int bless_buffer_delete(bless_buffer_t *buf, off_t offset, off_t length)
 {
-	if (buf == NULL) 
+	if (buf == NULL)
 		return_error(EINVAL);
 
-	/* 
-	 * No need to check for overflow, valid ranges etc.
-	 * They are all checked in segcol_delete().
-	 */
-
-	int err = segcol_delete(buf->segcol, NULL, offset, length);
-
-	if (err)
-		return_error(err);
+	/* Create a delete action */
+	buffer_action_t *action;
+
+	int err = buffer_action_delete_new(&action, buf, offset, length);
+
+	if (err)
+		return_error(err);
+
+	/* Perform action */
+	err = buffer_action_do(action);
+	if (err) {
+		buffer_action_free(action);
+		return_error(err);
+	}
+		
+	/* 
+	 * Make sure that the undo list has space for one action (provided the
+	 * undo limit is > 0).
+	 */
+	err = undo_list_enforce_limit(buf, 1);
+	if (err)
+		goto fail;
+
+	/* 
+	 * If we have space in the undo list to append the action.
+	 * Then only case we won't have space is when the undo limit is 0.
+	 */
+	if (buf->undo_list_size < buf->options->undo_limit) {
+		err = undo_list_append(buf, action);
+		if (err)
+			goto fail;
+	}
+
+	action_list_clear(buf->redo_list);
+	buf->redo_list_size = 0;
 
 	return 0;
+
+fail:
+	buffer_action_free(action);
+	return_error(err);
 }
 
 /**

=== modified file 'src/buffer_file.c'
--- src/buffer_file.c	2009-04-04 08:36:34 +0000
+++ src/buffer_file.c	2009-04-21 19:37:06 +0000
@@ -39,6 +39,7 @@
 #include "list.h"
 #include "buffer_util.h"
 #include "util.h"
+#include "type_limits.h"
 
 
 #pragma GCC visibility push(default)
@@ -361,6 +362,109 @@
 	return 0;
 }
 
+
+/** 
+ * Makes private copies of buffer (undo/redo) action data that belong to
+ * a specific data object.
+ *
+ * This is done to ensure the integrity of the data in case the specified
+ * data object changes (eg a file during save).
+ *
+ * If 'del' is non-zero and a private copy of an action can not be made, that and
+ * older actions are removed from the undo/redo list. Otherwise in case a private
+ * copy fails an error is immediately returned.
+ *  
+ * @param buf the bless_buffer_t 
+ * @param obj the data_object_t the data must belong to
+ * @param del whether to delete any actions (and actions older than them) that
+ *            we cannot make private copies of.
+ * 
+ * @return the operation error code
+ */
+static int actions_make_private_copy(bless_buffer_t *buf, data_object_t *obj,
+		int del)
+{
+	int err;
+	struct list_node *node;
+	struct list_node *tmp;
+	int undo_err = 0;
+	int redo_err = 0;
+
+	/* 
+	 * Try to make private copies of undo actions starting from the newest one.
+	 * If a private copy of an action fails, remove that and all older actions
+	 * from the undo list.
+	 */
+	list_for_each_reverse_safe(action_list_tail(buf->undo_list)->prev, node, tmp) {
+		struct buffer_action_entry *entry =
+			list_entry(node, struct buffer_action_entry , ln);
+
+		/* If we have previously encountered an error, remove the action */
+		if (undo_err) {
+			list_delete_chain(node, node);
+			buffer_action_free(entry->action);
+			free(entry);
+		} 
+		else
+			err = buffer_action_private_copy(entry->action, obj);
+
+		/* 
+		 * If the private copy failed remove the action and mark the undo_err.
+		 * However, if the caller doesn't want us to delete anything just return
+		 * an error.
+		 */
+		if (!undo_err && err) {
+			if (!del)
+				return_error(err);
+			undo_err = err;
+			list_delete_chain(node, node);
+			buffer_action_free(entry->action);
+			free(entry);
+		}
+	}
+
+	/* 
+	 * Try to make private copies of redo actions starting from the newest one.
+	 * If a private copy of an action fails, remove that and all older actions
+	 * from the redo list.
+	 */
+	list_for_each_safe(action_list_head(buf->redo_list)->next, node, tmp) {
+		struct buffer_action_entry *entry =
+			list_entry(node, struct buffer_action_entry , ln);
+
+		/* If we have previously encountered an error, remove the action */
+		if (redo_err) {
+			list_delete_chain(node, node);
+			buffer_action_free(entry->action);
+			free(entry);
+		} 
+		else
+			err = buffer_action_private_copy(entry->action, obj);
+
+		/* 
+		 * If the private copy failed remove the action and mark the undo_err.
+		 * However, if the caller doesn't want us to delete anything just return
+		 * an error.
+		 */
+		if (!redo_err && err) {
+			if (!del)
+				return_error(err);
+			redo_err = err;
+			list_delete_chain(node, node);
+			buffer_action_free(entry->action);
+			free(entry);
+		}
+	}
+
+	if (undo_err)
+		return_error(undo_err);
+
+	if (redo_err)
+		return_error(redo_err);
+
+	return 0;
+}
+
 /** 
  * Creates a new buffer_options struct.
  * 
@@ -374,10 +478,25 @@
 	if (*opts == NULL)
 		return_error(ENOMEM);
 
+	/* Set default values for options */
 	(*opts)->tmp_dir = strdup("/tmp");
 	if ((*opts)->tmp_dir == NULL)
 		return_error(ENOMEM);
 
+	(*opts)->undo_limit = __MAX(size_t);
+
+	(*opts)->undo_limit_str = strdup("infinite");
+	if ((*opts)->undo_limit_str == NULL) {
+		free((*opts)->tmp_dir);
+		return_error(ENOMEM);
+	}
+
+	(*opts)->undo_after_save = strdup("best_effort");
+	if ((*opts)->undo_after_save == NULL) {
+		free((*opts)->undo_limit_str);
+		free((*opts)->tmp_dir);
+		return_error(ENOMEM);
+	}
 
 	return 0;
 }
@@ -392,6 +511,8 @@
 static int buffer_options_free(struct buffer_options *opts)
 {
 	free(opts->tmp_dir);
+	free(opts->undo_limit_str);
+	free(opts->undo_after_save);
 	free(opts);
 
 	return 0;
@@ -419,19 +540,38 @@
 		return_error(ENOMEM);
 	
 	int err = segcol_list_new(&(*buf)->segcol);
-	if (err) {
-		free(buf);
-		return_error(err);
-	}
+	if (err)
+		goto fail_segcol;
 
 	err = buffer_options_new(&(*buf)->options);
-	if (err) {
-		segcol_free((*buf)->segcol);
-		free(buf);
-		return_error(err);
-	}
+	if (err)
+		goto fail_options;
+		
+	err = list_new(&(*buf)->undo_list, struct buffer_action_entry, ln);
+	if (err)
+		goto fail_undo;
+
+	(*buf)->undo_list_size = 0;
+
+	err = list_new(&(*buf)->redo_list, struct buffer_action_entry, ln);
+	if (err)
+		goto fail_redo;
+
+	(*buf)->redo_list_size = 0;
 
 	return 0;
+
+	/* Handle failures */
+fail_redo:
+	list_free((*buf)->undo_list, struct buffer_action_entry, ln);
+fail_undo:
+	buffer_options_free((*buf)->options);
+fail_options:
+	segcol_free((*buf)->segcol);
+fail_segcol:
+	free(buf);
+
+	return_error(err);
 }
 
 /**
@@ -486,6 +626,30 @@
 	if (err)
 		return_error(err);
 
+	/* Make private copies of data in undo/redo actions. */
+	if (!strcmp(buf->options->undo_after_save, "always")) {
+		/* 
+		 * If the policy is "always" and we cannot safely keep the whole
+		 * action history, don't carry on with the save.
+		 */
+		err = actions_make_private_copy(buf, fd_obj, 0);
+		if (err)
+			goto fail1;
+	}
+	else if (!strcmp(buf->options->undo_after_save, "best_effort")) {
+		/* 
+		 * If the policy is "best_effort" try our best to make private copies,
+		 * but if we fail just carry on with the part of the action history
+		 * that we can safely use (if any).
+		 */
+		actions_make_private_copy(buf, fd_obj, 1);
+	}
+	else if (strcmp(buf->options->undo_after_save, "never")) {
+		/* Invalid option value. We shouldn't get here, but just in case... */
+		err = EINVAL;
+		goto fail1;
+	}
+
 	/* 
 	 * Create the overlap graph and remove any cycles
 	 */
@@ -535,15 +699,13 @@
 
 	segment_t *fd_seg;
 	err = segment_new(&fd_seg, fd_obj, 0, segcol_size, data_object_update_usage);
-	if (err) {
-		segcol_free(segcol_tmp);
-		goto fail1;
-	}
+	if (err)
+		goto fail4;
 
 	err = segcol_append(segcol_tmp, fd_seg);
 	if (err) {
-		segcol_free(segcol_tmp);
-		goto fail1;
+		segment_free(fd_seg);
+		goto fail4;
 	}
 
 	/* 
@@ -552,13 +714,13 @@
 	 */
 	err = create_overlap_graph(&g, buf->segcol, fd_obj);
 	if (err)
-		goto fail1;
+		goto fail4;
 
 	/* Write the file segments to file in topological order */
 	struct list *vertices;
 	err = overlap_graph_get_vertices_topo(g, &vertices);
 	if (err)
-		goto fail2;
+		goto fail5;
 
 	first_node =
 		list_head(vertices, struct vertex_entry, ln)->next;
@@ -567,7 +729,7 @@
 		struct vertex_entry *v = list_entry(node, struct vertex_entry, ln);
 		err = write_segment(fd, v->segment, v->mapping, v->self_loop_weight);
 		if (err)
-			goto fail4;
+			goto fail6;
 	}
 	
 	list_free(vertices, struct vertex_entry, ln);
@@ -576,14 +738,14 @@
 	/* Write the rest of the segments */
 	err = write_segcol_rest(fd, buf->segcol, fd_obj);
 	if (err)
-		goto fail1;
+		goto fail4;
 
 	/* Truncate file to final size (only if it is a resizable file) */
 	if (fd_resizable == 1) {
 		err = ftruncate(fd, segcol_size);
 		if (err == -1) {
 			err = errno;
-			goto fail1;
+			goto fail4;
 		}
 	}
 
@@ -591,6 +753,15 @@
 	segcol_free(buf->segcol);
 	buf->segcol = segcol_tmp;
 
+	if (!strcmp(buf->options->undo_after_save, "never")) {
+		/* If the policy is "never" clear the undo/redo lists */
+		action_list_clear(buf->undo_list);
+		buf->undo_list_size = 0;
+
+		action_list_clear(buf->redo_list);
+		buf->redo_list_size = 0;
+	}
+
 	return 0;
 
 /* Prevent memory leaks on failure */
@@ -603,9 +774,13 @@
 
 	return_error(err);
 
+fail6:
+	list_free(vertices, struct vertex_entry, ln);
+fail5:
+	overlap_graph_free(g);
 fail4:
-	list_free(vertices, struct vertex_entry, ln);
-	goto fail2;
+	segcol_free(segcol_tmp);
+	goto fail1;
 
 }
 
@@ -630,6 +805,28 @@
 	if (err)
 		return_error(err);
 
+	/* Free the stored undo actions */
+	struct list_node *node;
+
+	list_for_each(action_list_head(buf->undo_list)->next, node) {
+		struct buffer_action_entry *entry =
+			list_entry(node, struct buffer_action_entry , ln);
+
+		buffer_action_free(entry->action);
+	}
+
+	list_free(buf->undo_list, struct buffer_action_entry, ln);
+
+	/* Free the stored redo actions */
+	list_for_each(action_list_head(buf->redo_list)->next, node) {
+		struct buffer_action_entry *entry =
+			list_entry(node, struct buffer_action_entry , ln);
+
+		buffer_action_free(entry->action);
+	}
+
+	list_free(buf->redo_list, struct buffer_action_entry, ln);
+
 	free(buf);
 
 	return 0;

=== modified file 'src/buffer_info.c'
--- src/buffer_info.c	2009-04-04 08:36:34 +0000
+++ src/buffer_info.c	2009-04-17 18:27:50 +0000
@@ -28,14 +28,14 @@
 #include <string.h>
 #include "buffer.h"
 #include "buffer_internal.h"
+#include "buffer_util.h"
+#include "type_limits.h"
 
 #include "util.h"
 
 
 #pragma GCC visibility push(default)
 
-/* bless_buffer_can_{undo,redo} are not implemented yet */
-#pragma GCC visibility push(hidden)
 
 /**
  * Checks whether the last operation in a bless_buffer_t can be undone.
@@ -47,7 +47,14 @@
  */
 int bless_buffer_can_undo(bless_buffer_t *buf, int *can_undo)
 {
-	return_error(ENOSYS);
+	if (buf == NULL || can_undo == NULL)
+		return_error(EINVAL);
+
+	struct list_node *first = action_list_head(buf->undo_list)->next;
+
+	*can_undo = !(first->next == first);
+
+	return 0;
 }
 
 /**
@@ -60,11 +67,16 @@
  */
 int bless_buffer_can_redo(bless_buffer_t *buf, int *can_redo)
 {
-	return_error(ENOSYS);
+	if (buf == NULL || can_redo == NULL)
+		return_error(EINVAL);
+
+	struct list_node *first = action_list_head(buf->redo_list)->next;
+
+	*can_redo = !(first->next == first);
+
+	return 0;
 }
 
-#pragma GCC visibility pop
-
 /**
  * Gets the size of a bless_buffer_t.
  *
@@ -112,6 +124,64 @@
 			}
 			break;
 
+		case BLESS_BUF_UNDO_LIMIT:
+			if (val == NULL)
+				return_error(EINVAL);
+			else if (!strcmp(val, "infinite")) {
+				char *dup = strdup(val);
+				if (dup == NULL)
+					return_error(ENOMEM);
+
+				/* Free old value and set new one */
+				if (buf->options->undo_limit_str != NULL)
+					free(buf->options->undo_limit_str);
+
+				buf->options->undo_limit_str = dup;
+				buf->options->undo_limit = __MAX(size_t);
+			}
+			else {
+				char *endptr;
+				size_t limit = strtoul(val, &endptr, 10);
+				if (*val == '\0' || *endptr != '\0')
+					return_error(EINVAL);
+
+				char *dup = strdup(val);
+				if (dup == NULL)
+					return_error(ENOMEM);
+
+				/* Free old value and set new one */
+				if (buf->options->undo_limit_str != NULL)
+					free(buf->options->undo_limit_str);
+
+				buf->options->undo_limit_str = dup;
+				buf->options->undo_limit = limit;
+			}
+
+			/* 
+			 * Make sure that the undo list size adheres to the new limit and
+			 * clear the redo list.
+			 */
+			undo_list_enforce_limit(buf, 0);
+			action_list_clear(buf->redo_list);
+			buf->redo_list_size = 0;
+
+			break;
+
+		case BLESS_BUF_UNDO_AFTER_SAVE:
+			if (val == NULL || (strcmp(val, "always") && strcmp(val, "never")
+					&& strcmp(val, "best_effort")))
+				return_error(EINVAL);
+			else {
+				char *dup = strdup(val);
+				if (dup == NULL)
+					return_error(ENOMEM);
+
+				/* Free old value and set new one */
+				if (buf->options->undo_after_save != NULL)
+					free(buf->options->undo_after_save);
+				buf->options->undo_after_save = dup;
+			}
+			break;
 		default:
 			break;
 	}
@@ -142,6 +212,14 @@
 			*val = buf->options->tmp_dir;
 			break;
 
+		case BLESS_BUF_UNDO_LIMIT:
+			*val = buf->options->undo_limit_str;
+			break;
+
+		case BLESS_BUF_UNDO_AFTER_SAVE:
+			*val = buf->options->undo_after_save;
+			break;
+
 		default:
 			*val = NULL;
 			break;

=== modified file 'src/buffer_internal.h'
--- src/buffer_internal.h	2009-04-01 18:55:46 +0000
+++ src/buffer_internal.h	2009-04-17 18:27:50 +0000
@@ -29,12 +29,31 @@
 #endif
 
 #include "segcol.h"
+#include "list.h"
+#include "buffer_action.h"
+
+/* Helper macros for action list */
+#define action_list_head(ptr) list_head((ptr), struct buffer_action_entry, ln)
+#define action_list_tail(ptr) list_tail((ptr), struct buffer_action_entry, ln)
+
+/** 
+ * Buffer action list entry.
+ */
+struct buffer_action_entry {
+	struct list_node ln;
+	buffer_action_t *action;
+};
 
 /** 
  * Buffer options struct
  */
 struct buffer_options {
 	char *tmp_dir;
+
+	size_t undo_limit;
+	char *undo_limit_str;
+
+	char *undo_after_save;
 };
 
 /**
@@ -43,6 +62,10 @@
 struct bless_buffer {
 	segcol_t *segcol;
 	struct buffer_options *options;
+	struct list *undo_list;
+	struct list *redo_list;
+	size_t undo_list_size;
+	size_t redo_list_size;
 };
 
 #ifdef __cplusplus

=== modified file 'src/buffer_options.h'
--- src/buffer_options.h	2009-02-22 13:48:53 +0000
+++ src/buffer_options.h	2009-04-17 18:27:50 +0000
@@ -31,7 +31,9 @@
 
 /** Buffer options */
 typedef enum { 
-	BLESS_BUF_TMP_DIR, /**< The directory to use for saving temporary files */
+	BLESS_BUF_TMP_DIR,    /**< The directory to use for saving temporary files */
+	BLESS_BUF_UNDO_LIMIT, /**< The maximum number of actions that can be undone */
+	BLESS_BUF_UNDO_AFTER_SAVE, /**< Whether to support undo after having saved */
 	BLESS_BUF_SENTINEL
 } bless_buffer_option_t;
 

=== modified file 'src/buffer_undo.c'
--- src/buffer_undo.c	2009-01-28 10:36:54 +0000
+++ src/buffer_undo.c	2009-04-04 14:17:34 +0000
@@ -24,13 +24,13 @@
  */
 
 #include <errno.h>
+#include <stdlib.h>
 #include "buffer.h"
 #include "buffer_internal.h"
+#include "buffer_action.h"
 #include "util.h"
 
 
-#pragma GCC visibility push(hidden)
-
 /**
  * Undoes the last operation in a bless_buffer_t.
  *
@@ -40,9 +40,54 @@
  */
 int bless_buffer_undo(bless_buffer_t *buf)
 {
-	return_error(ENOSYS);
+	if (buf == NULL)
+		return_error(EINVAL);
+
+	/* Make sure we can undo */
+	int can_undo;
+	int err = bless_buffer_can_undo(buf, &can_undo);
+	if (err)
+		return_error(err);
+
+	if (!can_undo)
+		return_error(EINVAL);
+
+	/* Get the last action from the undo list and undo it */
+	struct list_node *last = action_list_tail(buf->undo_list)->prev;
+
+	struct buffer_action_entry *entry = 
+		list_entry(last, struct buffer_action_entry, ln);
+
+	err = buffer_action_undo(entry->action);
+	if (err)
+		return_error(err);
+	
+	/* Remove the action from the undo list */
+	err = list_delete_chain(last, last);
+	if (err) {
+		/* If we can't remove the action, redo it and report error */
+		buffer_action_do(entry->action);
+		return_error(err);
+	}
+
+	--buf->undo_list_size;
+
+	/* Add the entry to the redo list */
+	err = list_insert_before(action_list_tail(buf->redo_list), &entry->ln);
+	if (err) {
+		/* Add it back to the undo list and redo the action */
+		list_insert_before(action_list_tail(buf->undo_list), &entry->ln);
+		++buf->undo_list_size;
+		buffer_action_do(entry->action);
+		return_error(err);
+	}
+
+	++buf->redo_list_size;
+
+	return 0;
 }
 
+
 /**
  * Redoes the last undone operation in a bless_buffer_t.
  *
@@ -52,9 +97,60 @@
  */
 int bless_buffer_redo(bless_buffer_t *buf)
 {
-	return_error(ENOSYS);
+	if (buf == NULL)
+		return_error(EINVAL);
+
+	/* Make sure we can redo */
+	int can_redo;
+	int err = bless_buffer_can_redo(buf, &can_redo);
+	if (err)
+		return_error(err);
+
+	if (!can_redo)
+		return_error(EINVAL);
+
+	/* Get the last action from the redo list and do it */
+	struct list_node *last = action_list_tail(buf->redo_list)->prev;
+
+	struct buffer_action_entry *entry = 
+		list_entry(last, struct buffer_action_entry, ln);
+
+	err = buffer_action_do(entry->action);
+	if (err)
+		return_error(err);
+	
+	/* Remove the action from the redo list */
+	err = list_delete_chain(last, last);
+	if (err) {
+		/* If we can't remove the action, undo it and report error */
+		buffer_action_undo(entry->action);
+		return_error(err);
+	}
+
+	--buf->redo_list_size;
+
+	/* 
+	 * Add the entry to the undo list.
+	 * We don't need to check if we can indeed move the entry to the undo list
+	 * without surpassing the undo limit, because throughout the program we
+	 * maintain the undo-redo invariant (undo+redo actions <= undo_limit).
+	 */
+	err = list_insert_before(action_list_tail(buf->undo_list), &entry->ln);
+	if (err) {
+		/* Add it back to the redo list and undo the action */
+		list_insert_before(action_list_tail(buf->redo_list), &entry->ln);
+		++buf->redo_list_size;
+		buffer_action_undo(entry->action);
+		return_error(err);
+	}
+
+	++buf->undo_list_size;
+
+	return 0;
 }
 
+#pragma GCC visibility push(hidden)
+
 /**
  * Marks the beginning of a multi-op.
  *

=== modified file 'src/buffer_util.c'
--- src/buffer_util.c	2009-03-14 19:52:05 +0000
+++ src/buffer_util.c	2009-04-17 17:03:58 +0000
@@ -29,7 +29,9 @@
 #include <stdlib.h>
 #include <unistd.h>
 
+#include "buffer.h"
 #include "buffer_util.h"
+#include "buffer_internal.h"
 #include "segcol.h"
 #include "segment.h"
 #include "data_object.h"
@@ -530,3 +532,169 @@
 	return 0;
 }
 
+/** 
+ * Copies data from a segcol into another.
+ * 
+ * The dst and src segcol must not be the same.
+ *
+ * @param dst the segcol to copy data into
+ * @param offset the offset in the segcol to copy data into
+ * @param src the segcol to copy data from
+ * 
+ * @return the operation error code
+ */
+int segcol_add_copy(segcol_t *dst, off_t offset, segcol_t *src)
+{
+	if (dst == NULL || src == NULL || offset < 0 || dst == src)
+		return_error(EINVAL);
+
+	off_t dst_size;
+	int err = segcol_get_size(dst, &dst_size);
+	if (err)
+		return_error(err);
+
+	segcol_iter_t *iter;
+	err = segcol_iter_new(src, &iter);
+	if (err)
+		return_error(err);
+
+	/* If the deleted data was beyond the end of file we must append it */
+	int use_append = (offset >= dst_size);
+
+	/* The offset of the last byte we re-added to the segcol */
+	off_t offset_reached = offset - 1;
+
+	int valid;
+
+	/* Re-add a copy of every segment to the segcol at its original position */
+	while (!segcol_iter_is_valid(iter, &valid) && valid) {
+		segment_t *seg;
+		off_t mapping;
+		segcol_iter_get_segment(iter, &seg);
+		segcol_iter_get_mapping(iter, &mapping);
+
+		segment_t *seg_copy;
+		segment_copy(seg, &seg_copy);
+
+		if (use_append)
+			err = segcol_append(dst, seg_copy);
+		else
+			err = segcol_insert(dst, offset + mapping, seg_copy);
+
+		if (err) {
+			segment_free(seg_copy);
+			goto fail;
+		}
+
+		offset_reached = offset + mapping - 1;
+
+		err = segcol_iter_next(iter);
+		if (err)
+			goto fail;
+	}
+
+	err = segcol_iter_free(iter);
+	if (err)
+		goto fail_iter_free;
+
+	return 0;
+
+fail:
+	segcol_iter_free(iter);
+fail_iter_free:
+	/* 
+	 * If we fail try to restore the previous state of the buffer by
+	 * deleting any segments we re-added.
+	 */
+	if (offset_reached >= offset)
+		segcol_delete(dst, NULL, offset, offset_reached - offset + 1);
+
+	return_error(err);
+}
+
+/**
+ * Enforces the undo limit on the undo list.
+ *
+ * After the operation the undo list contains at most the most recent
+ * buf->options->undo_limit actions. Additionally if ensure_vacancy == 1 the
+ * undo list contains space for at least one action (unless the undo limit is
+ * 0).
+ * 
+ * @param buf the bless_buffer_t
+ * @param ensure_vacancy whether to make sure that there is space for one
+ *                       additional action
+ * 
+ * @return the operation error code
+ */
+int undo_list_enforce_limit(bless_buffer_t *buf, int ensure_vacancy)
+{
+	if (buf == NULL)
+		return_error(EINVAL);
+
+	size_t limit = buf->options->undo_limit;
+	/* 
+	 * if we want to ensure vacancy of one action we must delete one more
+	 * existing action than we would normally do.
+	 */
+	if (limit != 0 && ensure_vacancy)
+		limit--;
+
+	/* 
+	 * Remove actions from the start of the undo list (older ones) until
+	 * we reach the limit.
+	 */
+	struct list_node *node;
+	struct list_node *tmp;
+
+  list_for_each_safe(action_list_head(buf->undo_list)->next, node, tmp) {
+    if (buf->undo_list_size <= limit)
+      break;
+
+		int err = list_delete_chain(node, node);
+		if (err)
+			return_error(err);
+
+    --buf->undo_list_size;
+
+		struct buffer_action_entry *del_entry = 
+			list_entry(node, struct buffer_action_entry, ln);
+
+		buffer_action_free(del_entry->action);
+		free(del_entry);
+  }
+
+
+	return 0;
+}
+
+/** 
+ * Clears an action list's contents without freeing the list itself.
+ * 
+ * @param action_list the action_list
+ * 
+ * @return the operation error code
+ */
+int action_list_clear(struct list *action_list)
+{
+	if (action_list == NULL)
+		return_error(EINVAL);
+
+	struct list_node *node;
+	struct list_node *tmp;
+
+	/* 
+	 * Use the safe iterator so that we can delete the current 
+	 * node from the list as we traverse it.
+	 */
+	list_for_each_safe(action_list_head(action_list)->next, node, tmp) {
+		struct buffer_action_entry *entry =
+			list_entry(node, struct buffer_action_entry , ln);
+
+		list_delete_chain(node, node);
+		buffer_action_free(entry->action);
+		free(entry);
+	}
+
+	return 0;
+}
+

=== modified file 'src/buffer_util.h'
--- src/buffer_util.h	2009-03-14 18:35:47 +0000
+++ src/buffer_util.h	2009-04-17 17:03:58 +0000
@@ -30,9 +30,11 @@
 #endif
 
 #include <sys/types.h>
+#include "buffer.h"
 #include "segcol.h"
 #include "segment.h"
 #include "data_object.h"
+#include "list.h"
 
 typedef int (segcol_foreach_func)(segcol_t *segcol, segment_t *seg,
 		off_t mapping, off_t read_start, off_t read_length, void *user_data);
@@ -50,6 +52,12 @@
 int segcol_store_in_file(segcol_t *segcol, off_t offset, off_t length,
 		char *tmpdir);
 
+int segcol_add_copy(segcol_t *dst, off_t offset, segcol_t *src);
+
+int undo_list_enforce_limit(bless_buffer_t *buf, int ensure_vacancy);
+
+int action_list_clear(struct list *action_list);
+
 #ifdef __cplusplus
 }
 #endif

=== modified file 'src/list.h'
--- src/list.h	2009-02-04 14:59:49 +0000
+++ src/list.h	2009-04-16 10:23:58 +0000
@@ -48,6 +48,22 @@
 			(node) = (tmp), (tmp) = (tmp)->next)
 
 /** 
+ * Reverse iterate safely through the nodes in a list.
+ *
+ * This macro should be used when nodes are going to be altered or
+ * deleted during the iteration.
+ * 
+ * @param last the list node to start from
+ * @param node a struct list_node pointer that will hold the current node in
+ *             each iteration
+ * @param tmp a struct list_node pointer that will be used internally for safe
+ *            iteration
+ */
+#define list_for_each_reverse_safe(last, node, tmp) \
+	for ((node) = (last), (tmp) = (node)->prev; (node) != (node)->prev; \
+			(node) = (tmp), (tmp) = (tmp)->prev)
+
+/** 
  * Iterate through the nodes in a list.
  *
  * If nodes are going to be altered or deleted during the iteration use
@@ -60,6 +76,19 @@
 #define list_for_each(first, node) \
 	for ((node) = (first); (node) != (node)->next; (node) = (node)->next)
 
+/** 
+ * Reverse iterate through the nodes in a list.
+ *
+ * If nodes are going to be altered or deleted during the iteration use
+ * list_for_each_reverse_safe().
+ * 
+ * @param last the list node to start from
+ * @param node a struct list_node pointer that will hold the current node in
+ *             each iteration
+ */
+#define list_for_each_reverse(last, node) \
+	for ((node) = (last); (node) != (node)->prev; (node) = (node)->prev)
+
 /**
  * Gets the entry containing a list node.
  *

=== modified file 'src/segcol_list.c'
--- src/segcol_list.c	2009-02-04 14:59:49 +0000
+++ src/segcol_list.c	2009-03-29 08:30:43 +0000
@@ -465,8 +465,15 @@
 	 * This check is placed after the first find_seg_entry() so that the
 	 * validity of offset (if it is in range) is checked first.
 	 */
-	if (length == 0)
+	if (length == 0) {
+		/* Return an empty deleted segcol if the caller wants one */
+		if (deleted != NULL) {
+			err = segcol_list_new(deleted);
+			if (err)
+				return_error(err);
+		}
 		return 0;
+	}
 
 	err = find_seg_entry(segcol, &last_entry, &last_mapping,
 			offset + length - 1 * (length != 0));

=== added file 'test/buffer_action_tests.py'
--- test/buffer_action_tests.py	1970-01-01 00:00:00 +0000
+++ test/buffer_action_tests.py	2009-03-28 17:38:10 +0000
@@ -0,0 +1,207 @@
+import unittest
+import errno
+from ctypes import create_string_buffer
+from libbls import *
+
+# This test suite contains only basic tests.
+# The buffer_action_t ADT is more thoroughly checked indirectly
+# in the buffer_tests.py suite.
+
+class BufferActionTests(unittest.TestCase):
+
+	def setUp(self):
+		(err, self.buf) = bless_buffer_new();
+		self.actions = []
+		self.assertEqual(err, 0)
+
+	def tearDown(self):
+		for action in self.actions:
+			err = buffer_action_free(action)
+			self.assertEqual(err, 0)
+
+		err = bless_buffer_free(self.buf)
+		self.assertEqual(err, 0)
+	
+	def check_buffer(self, expected_data):
+		expected_length = len(expected_data)
+
+		(err, size) = bless_buffer_get_size(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(size, expected_length)
+
+		read_data = create_string_buffer(expected_length)
+		err = bless_buffer_read(self.buf, 0, read_data, 0, expected_length)
+		self.assertEqual(err, 0)
+
+		for i in range(len(read_data)):
+			self.assertEqual(read_data[i], expected_data[i])
+
+	def testDoAppendAction(self):
+		"Do Append data"
+
+		data = "0123456789" 
+		(err, data_src) = bless_buffer_source_memory(data, 10, None)
+		self.assertEqual(err, 0)
+
+        # Create and perform the action
+		(err, action) = buffer_action_append_new(self.buf, data_src, 0, 10);
+		self.assertEqual(err, 0)
+
+		err = buffer_action_do(action)
+		self.assertEqual(err, 0)
+
+		self.actions.append(action)
+
+		err = bless_buffer_source_unref(data_src)
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		self.check_buffer("0123456789")
+
+	def testDoInsertAction(self):
+		"Insert data"
+
+		self.testDoAppendAction();
+
+		data = "abc" 
+		(err, data_src) = bless_buffer_source_memory(data, 3, None)
+		self.assertEqual(err, 0)
+
+        # Create and perform the action
+		(err, action) = buffer_action_insert_new(self.buf, 4, data_src, 0, 3);
+		self.assertEqual(err, 0)
+
+		err = buffer_action_do(action)
+		self.assertEqual(err, 0)
+
+		self.actions.append(action)
+
+		err = bless_buffer_source_unref(data_src)
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		self.check_buffer("0123abc456789")
+
+	def testDoDeleteAction(self):
+		"Delete data"
+
+		self.testDoInsertAction()
+
+        # Create and perform the action
+		(err, action) = buffer_action_delete_new(self.buf, 5, 4)
+		self.assertEqual(err, 0)
+
+		err = buffer_action_do(action)
+		self.assertEqual(err, 0)
+
+		self.actions.append(action)
+
+		# Check buffer
+		self.check_buffer("0123a6789")
+
+	def testDoDeleteEndAction(self):
+		"Delete data at the end of the buffer"
+
+		self.testDoDeleteAction()
+
+        # Create and perform the action
+		(err, action) = buffer_action_delete_new(self.buf, 4, 5)
+		self.assertEqual(err, 0)
+
+		err = buffer_action_do(action)
+		self.assertEqual(err, 0)
+
+		self.actions.append(action)
+
+		# Check buffer
+		self.check_buffer("0123")
+
+	def testUndoAppendAction(self):
+		"Undo Append data"
+
+		self.testDoAppendAction();
+
+		err = buffer_action_undo(self.actions[0])
+		self.assertEqual(err, 0)
+
+		(err, size) = bless_buffer_get_size(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(size, 0)
+
+	def testUndoInsertAction(self):
+		"Undo Append data"
+
+		self.testDoInsertAction();
+
+		err = buffer_action_undo(self.actions[1])
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		self.check_buffer("0123456789")
+
+		err = buffer_action_undo(self.actions[0])
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		(err, size) = bless_buffer_get_size(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(size, 0)
+
+	def testUndoDeleteAction(self):
+		"Undo delete data"
+
+		self.testDoDeleteAction();
+
+		err = buffer_action_undo(self.actions[2])
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		self.check_buffer("0123abc456789")
+
+		err = buffer_action_undo(self.actions[1])
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		self.check_buffer("0123456789")
+
+		err = buffer_action_undo(self.actions[0])
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		(err, size) = bless_buffer_get_size(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(size, 0)
+
+	def testUndoDeleteEndAction(self):
+		"Undo delete end data"
+
+		self.testDoDeleteEndAction()
+
+		err = buffer_action_undo(self.actions[3])
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		self.check_buffer("0123a6789")
+
+		err = buffer_action_undo(self.actions[2])
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		self.check_buffer("0123abc456789")
+
+		err = buffer_action_undo(self.actions[1])
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		self.check_buffer("0123456789")
+
+		err = buffer_action_undo(self.actions[0])
+		self.assertEqual(err, 0)
+
+		# Check buffer
+		(err, size) = bless_buffer_get_size(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(size, 0)
+
+if __name__ == '__main__':
+	unittest.main()

=== modified file 'test/buffer_tests.py'
--- test/buffer_tests.py	2009-04-04 08:36:34 +0000
+++ test/buffer_tests.py	2009-04-17 18:55:07 +0000
@@ -48,13 +48,40 @@
 		self.assertNotEqual(self.buf, None)
 
 	def tearDown(self):
-		bless_buffer_free(self.buf)
+		err = bless_buffer_free(self.buf)
+		self.assertEqual(err, 0)
 
 	def testNew(self):
 		(err, size) = bless_buffer_get_size(self.buf)
 		self.assertEqual(err, 0)
 		self.assertEqual(size, 0)
 
+	def check_buffer(self, buf, expected_data):
+		"Check if the buffer contains the expected_data"
+
+		# Read data from buffer and compare it to expected data
+		err, buf_size = bless_buffer_get_size(buf)
+		read_data = create_string_buffer(buf_size)
+		err = bless_buffer_read(buf, 0, read_data, 0, buf_size)
+		self.assertEqual(err, 0)
+
+		self.assertEqual(expected_data, read_data.value)
+
+	def undo_redo_mix(self, expected_after_redo, expected_after_undo):
+		"Redo, then undo, then redo again"
+
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, expected_after_redo)
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, expected_after_undo)
+
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, expected_after_redo)
+
 	def testAppend(self):
 		"Append data to the buffer"
 
@@ -836,6 +863,7 @@
 		(err, val) = bless_buffer_get_option(self.buf, BLESS_BUF_SENTINEL)
 		self.assertEqual(err, errno.EINVAL)
 
+		# BLESS_BUF_TMP_DIR
 		(err, val) = bless_buffer_get_option(self.buf, BLESS_BUF_TMP_DIR)
 		self.assertEqual(err, 0)
 		self.assertEqual(val, '/tmp')
@@ -847,6 +875,596 @@
 		self.assertEqual(err, 0)
 		self.assertEqual(val, '/mydir/tmp')
 
+		# BLESS_BUF_UNDO_LIMIT
+		(err, val) = bless_buffer_get_option(self.buf, BLESS_BUF_UNDO_LIMIT)
+		self.assertEqual(err, 0)
+		self.assertEqual(val, 'infinite')
+
+		err = bless_buffer_set_option(self.buf, BLESS_BUF_UNDO_LIMIT, '2rst')
+		self.assertEqual(err, errno.EINVAL)
+
+		(err, val) = bless_buffer_get_option(self.buf, BLESS_BUF_UNDO_LIMIT)
+		self.assertEqual(err, 0)
+		self.assertEqual(val, 'infinite')
+
+		err = bless_buffer_set_option(self.buf, BLESS_BUF_UNDO_LIMIT, '1024')
+		self.assertEqual(err, 0)
+
+		(err, val) = bless_buffer_get_option(self.buf, BLESS_BUF_UNDO_LIMIT) 
+		self.assertEqual(err, 0)
+		self.assertEqual(val, '1024')
+
+	def fill_buffer_for_undo(self):
+		data = "0123456789abcdefghij" 
+		(err, src) = bless_buffer_source_memory(data, 20, None)
+		self.assertEqual(err, 0)
+
+		# Add data
+		err = bless_buffer_append(self.buf, src, 0, 10)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "0123456789")
+
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 1)
+
+		err = bless_buffer_insert(self.buf, 5, src, 10, 3);
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "01234abc56789")
+
+		err = bless_buffer_delete(self.buf, 0, 2);
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789")
+
+		err = bless_buffer_insert(self.buf, 0, src, 13, 4);
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg234abc56789")
+
+		err = bless_buffer_delete(self.buf, 2, 13);
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "de")
+
+		err = bless_buffer_append(self.buf, src, 17, 3);
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "dehij")
+
+		err = bless_buffer_source_unref(src)
+		self.assertEqual(err, 0)
+		
+	def testBufferUndo(self):
+		"Undo buffer actions"
+
+		# No actions to undo, these should fail
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+		
+		err = bless_buffer_undo(self.buf)
+		self.assertNotEqual(err, 0)
+
+		self.fill_buffer_for_undo()
+
+		# Start undoing
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "de")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg234abc56789")
+		
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "01234abc56789")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "0123456789")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+
+		(err, bufsize) = bless_buffer_get_size(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(bufsize, 0)
+
+		# No more actions to undo, these should fail
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+
+		err = bless_buffer_undo(self.buf)
+		self.assertNotEqual(err, 0)
+
+	def testBufferRedo(self):
+		"Redo buffer actions"
+
+		self.testBufferUndo()
+
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+
+		# Start undo-redo mixes (redo, then undo, then redo again)
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "0123456789")
+
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 1)
+
+		self.undo_redo_mix("01234abc56789", "0123456789")
+
+		self.undo_redo_mix("234abc56789", "01234abc56789")
+
+		self.undo_redo_mix("defg234abc56789", "234abc56789")
+
+		self.undo_redo_mix("de", "defg234abc56789")
+
+		self.undo_redo_mix("dehij", "de")
+
+		(err, can_redo) = bless_buffer_can_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_redo, 0)
+
+		err = bless_buffer_redo(self.buf)
+		self.assertNotEqual(err, 0)
+
+	def testBufferUndoMixedEdits(self):
+		"Undo buffer actions and perform edit between undos"
+
+		data = "!@#$%^&*()" 
+		(err, src) = bless_buffer_source_memory(data, 10, None)
+		self.assertEqual(err, 0)
+
+		self.fill_buffer_for_undo()
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "de")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg234abc56789")
+
+		(err, can_redo) = bless_buffer_can_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_redo, 1)
+
+		err = bless_buffer_insert(self.buf, 5, src, 0, 2)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg2!@34abc56789")
+
+		# We must not be able to redo after performing an action
+		(err, can_redo) = bless_buffer_can_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_redo, 0)
+		
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg234abc56789")
+
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg2!@34abc56789")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg234abc56789")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789")
+
+		err = bless_buffer_delete(self.buf, 0, 6)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "56789")
+
+		# We must not be able to redo after performing an action
+		(err, can_redo) = bless_buffer_can_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_redo, 0)
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789")
+
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "56789")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789")
+
+		err = bless_buffer_append(self.buf, src, 3, 6)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789$%^&*(")
+
+		# We must not be able to redo after performing an action
+		(err, can_redo) = bless_buffer_can_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_redo, 0)
+
+		err = bless_buffer_source_unref(src)
+		self.assertEqual(err, 0)
+
+	def testBufferUndoLimitZero(self):
+		"Try to undo actions when the limit is zero"
+
+		# No actions to undo, these should fail
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+		
+		err = bless_buffer_undo(self.buf)
+		self.assertNotEqual(err, 0)
+
+		# Add some data
+		data = "0123456789abcdefghij" 
+		(err, src) = bless_buffer_source_memory(data, 20, None)
+		self.assertEqual(err, 0)
+
+		err = bless_buffer_append(self.buf, src, 0, 10)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "0123456789")
+
+		err = bless_buffer_source_unref(src)
+		self.assertEqual(err, 0)
+
+		# Set undo limit
+		err = bless_buffer_set_option(self.buf, BLESS_BUF_UNDO_LIMIT, '0')
+		self.assertEqual(err, 0)
+
+		# The undo limit is 0, we shouldn't be able to undo
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+
+	def testBufferUndoLimit(self):
+		"Enforce an undo limit"
+
+		# Set undo limit
+		err = bless_buffer_set_option(self.buf, BLESS_BUF_UNDO_LIMIT, '2')
+		self.assertEqual(err, 0)
+
+		# No actions to undo, these should fail
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+		
+		err = bless_buffer_undo(self.buf)
+		self.assertNotEqual(err, 0)
+
+		# Fill the buffer
+		self.fill_buffer_for_undo()
+
+		# Undo some actions
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "de")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg234abc56789")
+		
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+
+
+	def testBufferUndoLimitAfter(self):
+		"Enforce an undo limit after having performed actions"
+
+		# No actions to undo, these should fail
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+		
+		err = bless_buffer_undo(self.buf)
+		self.assertNotEqual(err, 0)
+
+		# Fill the buffer
+		self.fill_buffer_for_undo()
+
+		# Undo some actions
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "de")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg234abc56789")
+		
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789")
+
+		# Set undo limit
+		err = bless_buffer_set_option(self.buf, BLESS_BUF_UNDO_LIMIT, '1')
+		self.assertEqual(err, 0)
+
+		(err, can_redo) = bless_buffer_can_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_redo, 0)
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "01234abc56789")
+
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+
+	def testBufferUndoLimitIncrease(self):
+		"Increase the undo limit"
+
+		# No actions to undo, these should fail
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+		
+		err = bless_buffer_undo(self.buf)
+		self.assertNotEqual(err, 0)
+
+		# Fill the buffer
+		self.fill_buffer_for_undo()
+
+		# Undo some actions
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "de")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "defg234abc56789")
+		
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789")
+
+		# Set undo limit
+		err = bless_buffer_set_option(self.buf, BLESS_BUF_UNDO_LIMIT, '2')
+		self.assertEqual(err, 0)
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "01234abc56789")
+
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789")
+
+		# Increase undo limit
+		err = bless_buffer_set_option(self.buf, BLESS_BUF_UNDO_LIMIT, '4')
+		self.assertEqual(err, 0)
+
+		# Some more actions
+		data = "!@#$%^&*()" 
+		(err, src) = bless_buffer_source_memory(data, 10, None)
+		self.assertEqual(err, 0)
+
+		err = bless_buffer_append(self.buf, src, 0, 2)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789!@")
+
+		err = bless_buffer_insert(self.buf, 5, src, 2, 2)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234ab#$c56789!@")
+
+		err = bless_buffer_delete(self.buf, 8, 3)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234ab#$c89!@")
+
+		err = bless_buffer_append(self.buf, src, 8, 2)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234ab#$c89!@()")
+
+		err = bless_buffer_source_unref(src)
+		self.assertEqual(err, 0)
+
+		# Only the last 4 actions should be available
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234ab#$c89!@")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234ab#$c56789!@")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789!@")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "234abc56789")
+
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+
+	def testUndoAfterSave(self):
+		"Undo actions after having saved a file"
+
+		(fd1, fd1_path) = get_tmp_copy_file_fd("buffer_test_file1.bin",
+				os.O_RDWR)
+		
+		(err, src) = bless_buffer_source_file(fd1, None)
+		self.assertEqual(err, 0)
+
+		# Perform actions
+		err = bless_buffer_append(self.buf, src, 5, 5)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "67890")
+
+		err = bless_buffer_append(self.buf, src, 0, 5)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "6789012345")
+
+		err = bless_buffer_delete(self.buf, 3, 4)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "678345")
+
+		err = bless_buffer_source_unref(src)
+		self.assertEqual(err, 0)
+
+		# Save
+		err = bless_buffer_save(self.buf, fd1, None)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "678345")
+
+		# Undo 
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "6789012345")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "67890")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		(err, size) = bless_buffer_get_size(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(size, 0)
+
+		# Redo
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "67890")
+
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "6789012345")
+
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "678345")
+
+		# Remove temporary file
+		os.remove(fd1_path)
+
+	def testUndoAfterSaveNeverOption(self):
+		"""Try to undo actions after having saved a buffer that has
+		the BLESS_BUF_UNDO_AFTER_SAVE option set to 'never'"""
+
+		(fd1, fd1_path) = get_tmp_copy_file_fd("buffer_test_file1.bin",
+				os.O_RDWR)
+		
+		(err, src) = bless_buffer_source_file(fd1, None)
+		self.assertEqual(err, 0)
+
+		# Perform actions
+		err = bless_buffer_append(self.buf, src, 5, 5)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "67890")
+
+		err = bless_buffer_append(self.buf, src, 0, 5)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "6789012345")
+
+		err = bless_buffer_delete(self.buf, 3, 4)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "678345")
+
+		err = bless_buffer_source_unref(src)
+		self.assertEqual(err, 0)
+
+		# Save
+		err = bless_buffer_set_option(self.buf,
+				BLESS_BUF_UNDO_AFTER_SAVE, "never");
+		self.assertEqual(err, 0)
+
+		# Save
+		err = bless_buffer_save(self.buf, fd1, None)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "678345")
+
+		(err, can_undo) = bless_buffer_can_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_undo, 0)
+
+		(err, can_redo) = bless_buffer_can_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(can_redo, 0)
+
+		# Remove temporary file
+		os.remove(fd1_path)
+
+	def testUndoAfterSaveNeverOptionFail(self):
+		"""Undo actions after having saved a buffer that has
+		the BLESS_BUF_UNDO_AFTER_SAVE option set to 'never' but the
+		save failed"""
+
+		(fd1, fd1_path) = get_tmp_copy_file_fd("buffer_test_file1.bin",
+				os.O_RDONLY)
+		
+		(err, src) = bless_buffer_source_file(fd1, None)
+		self.assertEqual(err, 0)
+
+		# Perform actions
+		err = bless_buffer_append(self.buf, src, 5, 5)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "67890")
+
+		err = bless_buffer_append(self.buf, src, 0, 5)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "6789012345")
+
+		err = bless_buffer_delete(self.buf, 3, 4)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "678345")
+
+		err = bless_buffer_source_unref(src)
+		self.assertEqual(err, 0)
+
+		# Save
+		err = bless_buffer_set_option(self.buf,
+				BLESS_BUF_UNDO_AFTER_SAVE, "never");
+		self.assertEqual(err, 0)
+
+		# Save (should fail because fd1 is read-only)
+		err = bless_buffer_save(self.buf, fd1, None)
+		self.assertNotEqual(err, 0)
+		self.check_buffer(self.buf, "678345")
+
+		# We should be able to Undo/Redo normally regardless of the
+		# BLESS_BUF_UNDO_AFTER_SAVE = "never" option because the save failed
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "6789012345")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "67890")
+
+		err = bless_buffer_undo(self.buf)
+		self.assertEqual(err, 0)
+		(err, size) = bless_buffer_get_size(self.buf)
+		self.assertEqual(err, 0)
+		self.assertEqual(size, 0)
+
+		# Redo
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "67890")
+
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "6789012345")
+
+		err = bless_buffer_redo(self.buf)
+		self.assertEqual(err, 0)
+		self.check_buffer(self.buf, "678345")
+
+		# Remove temporary file
+		os.remove(fd1_path)
 
 if __name__ == '__main__':
 	unittest.main()

=== modified file 'test/buffer_util_tests.py'
--- test/buffer_util_tests.py	2009-03-14 18:32:17 +0000
+++ test/buffer_util_tests.py	2009-03-28 17:36:04 +0000
@@ -156,7 +156,70 @@
 		self.check_buffer(buf, data)
 
 		bless_buffer_free(buf)	
-		
+	
+	def testSegcolAddCopy(self):
+		"Copy data from a segcol into another"
+
+		# Create and fill memory data object
+		(err, data_obj) = data_object_memory_new_ptr(bless_malloc(10), 10)
+		self.assertEqual(err, 0)
+
+		data = "0123456789"
+		(err, buf) = data_object_get_data(data_obj, 0, 10, DATA_OBJECT_WRITE)
+		self.assertEqual(err, 0)
+		buf[:] = data
+
+		# Create segments
+		(err, seg1) = segment_new_ptr(int(data_obj), 0, 4, None)
+		self.assertEqual(err, 0)
+
+		(err, seg2) = segment_new_ptr(int(data_obj), 4, 3, None)
+		self.assertEqual(err, 0)
+
+		(err, seg3) = segment_new_ptr(int(data_obj), 7, 3, None)
+		self.assertEqual(err, 0)
+
+		# Append the segments to the list
+		(err, segcol) = segcol_list_new()
+		self.assertEqual(err, 0)
+
+		segcol_append(segcol, seg2)
+		segcol_append(segcol, seg3)
+		segcol_append(segcol, seg1)
+
+		(err, segcol1) = segcol_list_new()
+		self.assertEqual(err, 0)
+
+		segcol_append(segcol1, segment_copy(seg3)[1])
+		segcol_append(segcol1, segment_copy(seg1)[1])
+		segcol_append(segcol1, segment_copy(seg2)[1])
+
+		# Create a bless buffer and set the segcol
+		(err, buf) = bless_buffer_new()
+		self.assertEqual(err, 0)
+		set_buffer_segcol(buf, segcol)
+
+		# Read data from buffer and compare it to data read from python
+		read_data = create_string_buffer(10)
+		err = bless_buffer_read(buf, 0, read_data, 0, 10)
+		self.assertEqual(err, 0)
+		self.assertEqual("4567890123", read_data.value)
+
+		err = segcol_add_copy(segcol, 5, segcol1)
+		self.assertEqual(err, 0)
+
+		err, segcol_size = segcol_get_size(segcol)
+		self.assertEqual(err, 0)
+		self.assertEqual(segcol_size, 20)
+
+		read_data = create_string_buffer(20)
+		err = bless_buffer_read(buf, 0, read_data, 0, 20)
+		self.assertEqual(err, 0)
+		self.assertEqual("45678789012345690123", read_data.value)
+
+		segcol_free(segcol1)
+		bless_buffer_free(buf)
+
 if __name__ == '__main__':
 	unittest.main()