create / free replicas, plus error handling
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
12
binding-tests/replica.cpp
Normal file
12
binding-tests/replica.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "doctest.h"
|
||||
#include "taskchampion.h"
|
||||
|
||||
TEST_CASE("creating a UUID") {
|
||||
StoragePtr *storage = storage_new_in_memory();
|
||||
storage_free(storage);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ name = "taskchampion"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.113"
|
||||
taskchampion = { path = "../taskchampion" }
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
@@ -1 +1 @@
|
||||
pub mod storage;
|
||||
pub mod replica;
|
||||
|
||||
92
lib/src/replica.rs
Normal file
92
lib/src/replica.rs
Normal file
@@ -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<CString>,
|
||||
}
|
||||
|
||||
/// 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<T, F>(rep: *mut Replica, f: F, err_value: T) -> T
|
||||
where
|
||||
F: FnOnce(&mut Replica) -> Result<T, &'static str>,
|
||||
{
|
||||
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) });
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
use taskchampion::{storage::Storage, StorageConfig};
|
||||
|
||||
pub struct StoragePtr(Box<dyn Storage>);
|
||||
|
||||
#[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) });
|
||||
}
|
||||
@@ -4,12 +4,31 @@
|
||||
#include <ostream>
|
||||
#include <new>
|
||||
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user