From e590dc7c9894f0fe642d1f4bd67f63b6c6137028 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 17:34:59 +0000 Subject: [PATCH] add tc_replica_undo --- Cargo.lock | 1 + binding-tests/replica.cpp | 12 +++++++++--- lib/Cargo.toml | 1 + lib/src/replica.rs | 26 +++++++++++++++++--------- lib/taskchampion.h | 7 +++++-- 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91cc866d9..b2f438354 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3031,6 +3031,7 @@ dependencies = [ name = "taskchampion-lib" version = "0.1.0" dependencies = [ + "anyhow", "cbindgen", "libc", "taskchampion", diff --git a/binding-tests/replica.cpp b/binding-tests/replica.cpp index c8e11cedc..6400b8002 100644 --- a/binding-tests/replica.cpp +++ b/binding-tests/replica.cpp @@ -5,8 +5,14 @@ TEST_CASE("creating an in-memory Replica does not crash") { Replica *rep = tc_replica_new(NULL); CHECK(tc_replica_error(rep) == NULL); - uhoh(rep); - REQUIRE(tc_replica_error(rep) != NULL); - CHECK(strcmp(tc_replica_error(rep), "uhoh!") == 0); + tc_replica_free(rep); +} + +TEST_CASE("undo on an empty in-memory Replica does nothing") { + Replica *rep = tc_replica_new(NULL); + CHECK(tc_replica_error(rep) == NULL); + int rv = tc_replica_undo(rep); + CHECK(rv == 0); + CHECK(tc_replica_error(rep) == NULL); tc_replica_free(rep); } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 956c5cbef..5d9b44fc6 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["cdylib"] [dependencies] libc = "0.2.113" taskchampion = { path = "../taskchampion" } +anyhow = "1.0" [build-dependencies] cbindgen = "0.20.0" diff --git a/lib/src/replica.rs b/lib/src/replica.rs index 81656519b..98b38e389 100644 --- a/lib/src/replica.rs +++ b/lib/src/replica.rs @@ -10,6 +10,7 @@ use std::os::unix::ffi::OsStrExt; /// A replica represents an instance of a user's task data, providing an easy interface /// for querying and modifying that data. pub struct Replica { + // TODO: make this an option so that it can be take()n when holding a mut ref inner: TCReplica, error: Option, } @@ -51,25 +52,32 @@ fn rep_ref(rep: *mut Replica) -> &'static mut Replica { unsafe { &mut *rep } } -fn wrap(rep: *mut Replica, f: F, err_value: T) -> T +fn wrap<'a, T, F>(rep: *mut Replica, f: F, err_value: T) -> T where - F: FnOnce(&mut Replica) -> Result, + F: FnOnce(&mut TCReplica) -> anyhow::Result, { - debug_assert!(!rep.is_null()); - let rep = unsafe { &mut *rep }; - match f(rep) { + let rep: &'a mut Replica = rep_ref(rep); + match f(&mut rep.inner) { Ok(v) => v, Err(e) => { - rep.error = Some(CString::new(e.as_bytes()).unwrap()); + let error = e.to_string(); + let error = match CString::new(error.as_bytes()) { + Ok(e) => e, + Err(_) => CString::new("(invalid error message)".as_bytes()).unwrap(), + }; + rep.error = Some(error); err_value } } } -/// temporary (testing errors) +/// Undo local operations until the most recent UndoPoint. +/// +/// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were +/// undone. #[no_mangle] -pub extern "C" fn uhoh<'a>(rep: *mut Replica) -> u32 { - wrap(rep, |rep| Err("uhoh!"), 0) +pub extern "C" fn tc_replica_undo<'a>(rep: *mut Replica) -> i32 { + wrap(rep, |rep| Ok(if rep.undo()? { 1 } else { 0 }), -1) } /// Get the latest error for a replica, or NULL if the last operation succeeded. diff --git a/lib/taskchampion.h b/lib/taskchampion.h index ac28be560..081dd09cc 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -20,8 +20,11 @@ extern "C" { /// Replicas are not threadsafe. Replica *tc_replica_new(const char *path); -/// temporary (testing errors) -uint32_t uhoh(Replica *rep); +/// Undo local operations until the most recent UndoPoint. +/// +/// Returns -1 on error, 0 if there are no local operations to undo, and 1 if operations were +/// undone. +int32_t tc_replica_undo(Replica *rep); /// Get the latest error for a replica, or NULL if the last operation succeeded. ///