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]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.97"
|
version = "0.2.113"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libgit2-sys"
|
name = "libgit2-sys"
|
||||||
@@ -3032,6 +3032,7 @@ name = "taskchampion-lib"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cbindgen",
|
"cbindgen",
|
||||||
|
"libc",
|
||||||
"taskchampion",
|
"taskchampion",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ INC=-I ../lib
|
|||||||
LIB=-L ../target/debug
|
LIB=-L ../target/debug
|
||||||
RPATH=-Wl,-rpath,../target/debug
|
RPATH=-Wl,-rpath,../target/debug
|
||||||
|
|
||||||
TESTS = uuid.cpp
|
TESTS = replica.cpp
|
||||||
|
|
||||||
.PHONY: all test
|
.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"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
libc = "0.2.113"
|
||||||
taskchampion = { path = "../taskchampion" }
|
taskchampion = { path = "../taskchampion" }
|
||||||
|
|
||||||
[build-dependencies]
|
[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 <ostream>
|
||||||
#include <new>
|
#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" {
|
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"
|
} // extern "C"
|
||||||
|
|||||||
Reference in New Issue
Block a user