libbls-dev-list team mailing list archive
-
libbls-dev-list team
-
Mailing list archive
-
Message #00006
[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()