From ce56127bbfa3fe2b47942b97bd059b8deb629998 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sun, 23 Jan 2022 17:24:54 +0000 Subject: [PATCH] create / free replicas, plus error handling --- Cargo.lock | 5 ++- binding-tests/Makefile | 2 +- binding-tests/replica.cpp | 12 +++++ binding-tests/uuid.cpp | 7 --- lib/Cargo.toml | 1 + lib/src/lib.rs | 2 +- lib/src/replica.rs | 92 +++++++++++++++++++++++++++++++++++++++ lib/src/storage.rs | 16 ------- lib/taskchampion.h | 25 +++++++++-- 9 files changed, 132 insertions(+), 30 deletions(-) create mode 100644 binding-tests/replica.cpp delete mode 100644 binding-tests/uuid.cpp create mode 100644 lib/src/replica.rs delete mode 100644 lib/src/storage.rs diff --git a/Cargo.lock b/Cargo.lock index caa3684ab..91cc866d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,9 +1617,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.97" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9" [[package]] name = "libgit2-sys" @@ -3032,6 +3032,7 @@ name = "taskchampion-lib" version = "0.1.0" dependencies = [ "cbindgen", + "libc", "taskchampion", ] diff --git a/binding-tests/Makefile b/binding-tests/Makefile index e5f09d787..3599821f4 100644 --- a/binding-tests/Makefile +++ b/binding-tests/Makefile @@ -3,7 +3,7 @@ INC=-I ../lib LIB=-L ../target/debug RPATH=-Wl,-rpath,../target/debug -TESTS = uuid.cpp +TESTS = replica.cpp .PHONY: all test diff --git a/binding-tests/replica.cpp b/binding-tests/replica.cpp new file mode 100644 index 000000000..c8e11cedc --- /dev/null +++ b/binding-tests/replica.cpp @@ -0,0 +1,12 @@ +#include +#include "doctest.h" +#include "taskchampion.h" + +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); +} diff --git a/binding-tests/uuid.cpp b/binding-tests/uuid.cpp deleted file mode 100644 index 3c7979b2f..000000000 --- a/binding-tests/uuid.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "doctest.h" -#include "taskchampion.h" - -TEST_CASE("creating a UUID") { - StoragePtr *storage = storage_new_in_memory(); - storage_free(storage); -} diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 9d941a6d7..956c5cbef 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -9,6 +9,7 @@ name = "taskchampion" crate-type = ["cdylib"] [dependencies] +libc = "0.2.113" taskchampion = { path = "../taskchampion" } [build-dependencies] diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 30f61eb69..78004cf88 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1 +1 @@ -pub mod storage; +pub mod replica; diff --git a/lib/src/replica.rs b/lib/src/replica.rs new file mode 100644 index 000000000..81656519b --- /dev/null +++ b/lib/src/replica.rs @@ -0,0 +1,92 @@ +use libc::c_char; +use std::ffi::{CStr, CString, OsStr}; +use std::path::PathBuf; +use taskchampion::Replica as TCReplica; +use taskchampion::StorageConfig; + +// TODO: unix-only +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 { + inner: TCReplica, + error: Option, +} + +/// Create a new Replica. +/// +/// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the +/// on-disk storage for this replica. The path argument is no longer referenced after return. +/// +/// Returns NULL on error; see tc_replica_error. +/// +/// Replicas are not threadsafe. +#[no_mangle] +pub extern "C" fn tc_replica_new<'a>(path: *const c_char) -> *mut Replica { + let storage_res = if path.is_null() { + StorageConfig::InMemory.into_storage() + } else { + let path: &'a [u8] = unsafe { CStr::from_ptr(path) }.to_bytes(); + let path: &OsStr = OsStr::from_bytes(path); + let path: PathBuf = path.to_os_string().into(); + StorageConfig::OnDisk { taskdb_dir: path }.into_storage() + }; + + let storage = match storage_res { + Ok(storage) => storage, + // TODO: report errors somehow + Err(_) => return std::ptr::null_mut(), + }; + + Box::into_raw(Box::new(Replica { + inner: TCReplica::new(storage), + error: None, + })) +} + +/// Utility function to safely convert *mut Replica into &mut Replica +fn rep_ref(rep: *mut Replica) -> &'static mut Replica { + debug_assert!(!rep.is_null()); + unsafe { &mut *rep } +} + +fn wrap(rep: *mut Replica, f: F, err_value: T) -> T +where + F: FnOnce(&mut Replica) -> Result, +{ + debug_assert!(!rep.is_null()); + let rep = unsafe { &mut *rep }; + match f(rep) { + Ok(v) => v, + Err(e) => { + rep.error = Some(CString::new(e.as_bytes()).unwrap()); + err_value + } + } +} + +/// temporary (testing errors) +#[no_mangle] +pub extern "C" fn uhoh<'a>(rep: *mut Replica) -> u32 { + wrap(rep, |rep| Err("uhoh!"), 0) +} + +/// Get the latest error for a replica, or NULL if the last operation succeeded. +/// +/// The returned string is valid until the next replica operation. +#[no_mangle] +pub extern "C" fn tc_replica_error<'a>(rep: *mut Replica) -> *const c_char { + let rep: &'a Replica = rep_ref(rep); + if let Some(ref e) = rep.error { + e.as_ptr() + } else { + std::ptr::null() + } +} + +/// Free a Replica. +#[no_mangle] +pub extern "C" fn tc_replica_free(rep: *mut Replica) { + drop(unsafe { Box::from_raw(rep) }); +} diff --git a/lib/src/storage.rs b/lib/src/storage.rs deleted file mode 100644 index 96950dcd9..000000000 --- a/lib/src/storage.rs +++ /dev/null @@ -1,16 +0,0 @@ -use taskchampion::{storage::Storage, StorageConfig}; - -pub struct StoragePtr(Box); - -#[no_mangle] -pub extern "C" fn storage_new_in_memory() -> *mut StoragePtr { - // TODO: this is a box containing a fat pointer - Box::into_raw(Box::new(StoragePtr( - StorageConfig::InMemory.into_storage().unwrap(), - ))) -} - -#[no_mangle] -pub extern "C" fn storage_free(storage: *mut StoragePtr) { - drop(unsafe { Box::from_raw(storage) }); -} diff --git a/lib/taskchampion.h b/lib/taskchampion.h index dc8631325..ac28be560 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -4,12 +4,31 @@ #include #include -struct StoragePtr; +/// A replica represents an instance of a user's task data, providing an easy interface +/// for querying and modifying that data. +struct Replica; extern "C" { -StoragePtr *storage_new_in_memory(); +/// Create a new Replica. +/// +/// If path is NULL, then an in-memory replica is created. Otherwise, path is the path to the +/// on-disk storage for this replica. The path argument is no longer referenced after return. +/// +/// Returns NULL on error; see tc_replica_error. +/// +/// Replicas are not threadsafe. +Replica *tc_replica_new(const char *path); -void storage_free(StoragePtr *storage); +/// temporary (testing errors) +uint32_t uhoh(Replica *rep); + +/// Get the latest error for a replica, or NULL if the last operation succeeded. +/// +/// The returned string is valid until the next replica operation. +const char *tc_replica_error(Replica *rep); + +/// Free a Replica. +void tc_replica_free(Replica *rep); } // extern "C"