pub mod default;
mod worker;
pub use simploxide_core::SimplexVersion;
pub use simploxide_sxcrt_sys::{CallError, InitError, MigrationConfirmation};
use serde::Deserialize;
use simploxide_core::VersionInfo;
use std::{path::Path, sync::Arc, time::Duration};
pub type Command = String;
pub type Event = String;
pub type Response = String;
pub type Result<T = (), E = Arc<CallError>> = ::std::result::Result<T, E>;
type FfiResponder = tokio::sync::oneshot::Sender<Result<Response>>;
type CmdTransmitter = std::sync::mpsc::Sender<ChatCommand>;
type CmdReceiver = std::sync::mpsc::Receiver<ChatCommand>;
type EventTransmitter = tokio::sync::mpsc::UnboundedSender<Result<Event>>;
pub type EventReceiver = tokio::sync::mpsc::UnboundedReceiver<Result<Event>>;
type ShutdownEmitter = tokio::sync::watch::Sender<bool>;
type ShutdownSignal = tokio::sync::watch::Receiver<bool>;
#[derive(Default, Debug, Clone)]
pub struct WorkerConfig {
pub max_event_latency: Option<std::time::Duration>,
pub max_instances: Option<usize>,
}
impl WorkerConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_event_latency(mut self, duration: Duration) -> Self {
self.max_event_latency = Some(duration);
self
}
pub fn max_instances(mut self, max_instances: usize) -> Self {
self.max_instances = Some(max_instances);
self
}
}
pub async fn init(
default_user: DefaultUser,
db_opts: DbOpts,
) -> Result<(RawClient, RawEventQueue), InitError> {
init_with_config(default_user, db_opts, WorkerConfig::default()).await
}
pub async fn init_with_config(
default_user: DefaultUser,
db_opts: DbOpts,
config: WorkerConfig,
) -> Result<(RawClient, RawEventQueue), InitError> {
worker::init(config).spawn_chat(default_user, db_opts).await
}
#[derive(Clone)]
pub struct RawClient {
tx: CmdTransmitter,
worker: worker::Worker,
shutdown: ShutdownSignal,
}
impl RawClient {
pub async fn send(&self, command: Command) -> Result<Response> {
let (responder, response) = tokio::sync::oneshot::channel();
self.tx
.send(ChatCommand::Execute(command, responder))
.map_err(|_| CallError::Failure)?;
self.worker.wake();
response.await.map_err(|_| CallError::Failure)?
}
pub async fn version(&self) -> Result<SimplexVersion, VersionError> {
#[derive(Deserialize)]
struct VersionResult<'a> {
#[serde(borrow)]
result: VersionInfo<'a>,
}
let output = self.send("/v".to_owned()).await?;
let response = serde_json::from_str::<VersionResult>(&output)
.map_err(|e| Arc::new(CallError::InvalidJson(e)))?
.result
.version_info
.version;
let version = response
.parse()
.map_err(|_| VersionError::ParseError(response.to_owned()))?;
Ok(version)
}
pub fn disconnect(mut self) -> impl Future<Output = ()> {
let _ = self.tx.send(ChatCommand::Disconnect);
self.worker.wake();
async move {
let _ = self.shutdown.wait_for(|b| *b).await;
}
}
}
pub struct RawEventQueue {
receiver: EventReceiver,
}
impl RawEventQueue {
pub async fn next_event(&mut self) -> Option<Result<Event>> {
self.receiver.recv().await
}
pub fn into_receiver(self) -> EventReceiver {
self.receiver
}
}
#[derive(Debug, Clone)]
pub struct DefaultUser {
pub display_name: String,
pub is_bot: bool,
}
impl DefaultUser {
pub fn regular<S: Into<String>>(name: S) -> Self {
Self {
display_name: name.into(),
is_bot: false,
}
}
pub fn bot<S: Into<String>>(name: S) -> Self {
Self {
display_name: name.into(),
is_bot: true,
}
}
}
#[derive(Debug, Clone)]
pub struct DbOpts {
pub prefix: String,
pub key: Option<String>,
pub migration: MigrationConfirmation,
}
impl DbOpts {
pub fn unencrypted<P: AsRef<Path>>(db_path: P) -> Self {
Self {
prefix: db_path.as_ref().display().to_string(),
key: None,
migration: MigrationConfirmation::YesUp,
}
}
pub fn encrypted<P: AsRef<Path>, K: Into<String>>(prefix: P, key: K) -> Self {
Self {
prefix: prefix.as_ref().display().to_string(),
key: Some(key.into()),
migration: MigrationConfirmation::YesUp,
}
}
}
#[derive(Debug)]
pub enum VersionError {
Ffi(Arc<CallError>),
ParseError(String),
}
impl From<Arc<CallError>> for VersionError {
fn from(value: Arc<CallError>) -> Self {
Self::Ffi(value)
}
}
impl std::fmt::Display for VersionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ffi(e) => e.fmt(f),
Self::ParseError(s) => {
write!(
f,
"Cannot parse version, expected format: '<major>.<minor>.<patch>.<hotfix>', got {s:?}"
)
}
}
}
}
impl std::error::Error for VersionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Ffi(e) => Some(e),
Self::ParseError(_) => None,
}
}
}
enum ChatCommand {
Execute(Command, FfiResponder),
Disconnect,
}