add server support

This commit is contained in:
Dustin J. Mitchell
2022-02-13 20:18:07 +00:00
parent c0403f3f38
commit 41a578ab2b
7 changed files with 267 additions and 6 deletions

139
lib/src/server.rs Normal file
View File

@@ -0,0 +1,139 @@
use crate::traits::*;
use crate::types::*;
use crate::util::err_to_tcstring;
use taskchampion::{Server, ServerConfig};
/// TCServer represents an interface to a sync server. Aside from new and free, a server
/// has no C-accessible API, but is designed to be passed to `tc_replica_sync`.
///
/// ## Safety
///
/// TCServer are not threadsafe, and must not be used with multiple replicas simultaneously.
pub struct TCServer(Box<dyn Server>);
impl PassByPointer for TCServer {}
impl From<Box<dyn Server>> for TCServer {
fn from(server: Box<dyn Server>) -> TCServer {
TCServer(server)
}
}
impl AsMut<Box<dyn Server>> for TCServer {
fn as_mut(&mut self) -> &mut Box<dyn Server> {
&mut self.0
}
}
/// Utility function to allow using `?` notation to return an error value.
fn wrap<T, F>(f: F, error_out: *mut *mut TCString, err_value: T) -> T
where
F: FnOnce() -> anyhow::Result<T>,
{
match f() {
Ok(v) => v,
Err(e) => {
if !error_out.is_null() {
// SAFETY:
// - error_out is not NULL (checked)
// - ..and points to a valid pointer (promised by caller)
// - caller will free this string (promised by caller)
unsafe {
*error_out = err_to_tcstring(e).return_ptr();
}
}
err_value
}
}
}
/// Create a new TCServer that operates locally (on-disk). See the TaskChampion docs for the
/// description of the arguments.
///
/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is
/// returned. The caller must free this string.
///
/// The server must be freed after it is used - tc_replica_sync does not automatically free it.
#[no_mangle]
pub unsafe extern "C" fn tc_server_new_local(
server_dir: *mut TCString,
error_out: *mut *mut TCString,
) -> *mut TCServer {
wrap(
|| {
// SAFETY: see TCString docstring
let server_dir = unsafe { TCString::take_from_ptr_arg(server_dir) };
let server_config = ServerConfig::Local {
server_dir: server_dir.to_path_buf(),
};
let server = server_config.into_server()?;
// SAFETY: caller promises to free this server.
Ok(unsafe { TCServer::return_ptr(server.into()) })
},
error_out,
std::ptr::null_mut(),
)
}
/// Create a new TCServer that connects to a remote server. See the TaskChampion docs for the
/// description of the arguments.
///
/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is
/// returned. The caller must free this string.
///
/// The server must be freed after it is used - tc_replica_sync does not automatically free it.
#[no_mangle]
pub unsafe extern "C" fn tc_server_new_remote(
origin: *mut TCString,
client_key: TCUuid,
encryption_secret: *mut TCString,
error_out: *mut *mut TCString,
) -> *mut TCServer {
wrap(
|| {
debug_assert!(!origin.is_null());
debug_assert!(!encryption_secret.is_null());
// SAFETY:
// - origin is not NULL
// - origin is valid (promised by caller)
// - origin ownership is transferred to this function
let origin = unsafe { TCString::take_from_ptr_arg(origin) }.into_string()?;
// SAFETY:
// - client_key is a valid Uuid (any 8-byte sequence counts)
let client_key = unsafe { TCUuid::val_from_arg(client_key) };
// SAFETY:
// - encryption_secret is not NULL
// - encryption_secret is valid (promised by caller)
// - encryption_secret ownership is transferred to this function
let encryption_secret = unsafe { TCString::take_from_ptr_arg(encryption_secret) }
.as_bytes()
.to_vec();
let server_config = ServerConfig::Remote {
origin,
client_key,
encryption_secret,
};
let server = server_config.into_server()?;
// SAFETY: caller promises to free this server.
Ok(unsafe { TCServer::return_ptr(server.into()) })
},
error_out,
std::ptr::null_mut(),
)
}
/// Free a server. The server may not be used after this function returns and must not be freed
/// more than once.
#[no_mangle]
pub unsafe extern "C" fn tc_server_free(server: *mut TCServer) {
debug_assert!(!server.is_null());
// SAFETY:
// - server is not NULL
// - server came from tc_server_new_.., which used return_ptr
// - server will not be used after (promised by caller)
let server = unsafe { TCServer::take_from_ptr_arg(server) };
drop(server);
}