use std::collections::BTreeMap;
use std::future::{Future, ready};
use std::io;
use std::pin::pin;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use std::task::{Context, Poll, Waker};
use rig_core::wasm_compat::WasmCompatSend;
use crate::SessionError;
#[derive(Debug, thiserror::Error)]
pub enum LogWriteError {
#[error("the log changed since it was read")]
Changed,
#[error(transparent)]
Io(#[from] io::Error),
}
impl From<LogWriteError> for io::Error {
fn from(error: LogWriteError) -> Self {
match error {
LogWriteError::Changed => io::Error::other(LogWriteError::Changed.to_string()),
LogWriteError::Io(error) => error,
}
}
}
pub trait Files: Send + Sync + 'static {
fn write_file(&self, name: &str, bytes: &[u8]) -> io::Result<()>;
fn append_file(&self, name: &str, bytes: &[u8]) -> io::Result<()>;
fn read_file(&self, name: &str) -> io::Result<Vec<u8>>;
fn remove_file(&self, name: &str) -> io::Result<()>;
fn files(&self) -> io::Result<Vec<String>>;
}
pub trait Store: Files {
fn logs(&self) -> io::Result<Vec<String>>;
fn read_log(&self, name: &str) -> io::Result<Vec<u8>>;
fn append_log(&self, name: &str, len: u64, bytes: &[u8]) -> Result<(), LogWriteError>;
fn replace_log(&self, name: &str, len: u64, bytes: &[u8]) -> Result<(), LogWriteError>;
fn destroy(&self) -> io::Result<()>;
}
pub trait AsyncStore {
fn read_log_async(
&self,
name: &str,
) -> impl Future<Output = io::Result<Vec<u8>>> + WasmCompatSend;
fn append_log_async(
&self,
name: &str,
len: u64,
bytes: &[u8],
) -> impl Future<Output = Result<(), LogWriteError>> + WasmCompatSend;
fn replace_log_async(
&self,
name: &str,
len: u64,
bytes: &[u8],
) -> impl Future<Output = Result<(), LogWriteError>> + WasmCompatSend;
fn read_file_async(
&self,
name: &str,
) -> impl Future<Output = io::Result<Vec<u8>>> + WasmCompatSend;
fn write_file_async(
&self,
name: &str,
bytes: &[u8],
) -> impl Future<Output = io::Result<()>> + WasmCompatSend;
}
impl<S: Store + ?Sized> AsyncStore for S {
fn read_log_async(
&self,
name: &str,
) -> impl Future<Output = io::Result<Vec<u8>>> + WasmCompatSend {
ready(self.read_log(name))
}
fn append_log_async(
&self,
name: &str,
len: u64,
bytes: &[u8],
) -> impl Future<Output = Result<(), LogWriteError>> + WasmCompatSend {
ready(self.append_log(name, len, bytes))
}
fn replace_log_async(
&self,
name: &str,
len: u64,
bytes: &[u8],
) -> impl Future<Output = Result<(), LogWriteError>> + WasmCompatSend {
ready(self.replace_log(name, len, bytes))
}
fn read_file_async(
&self,
name: &str,
) -> impl Future<Output = io::Result<Vec<u8>>> + WasmCompatSend {
ready(self.read_file(name))
}
fn write_file_async(
&self,
name: &str,
bytes: &[u8],
) -> impl Future<Output = io::Result<()>> + WasmCompatSend {
ready(self.write_file(name, bytes))
}
}
macro_rules! forward {
($($pointer:ident),*) => {$(
impl<S: Files + ?Sized> Files for $pointer<S> {
fn write_file(&self, name: &str, bytes: &[u8]) -> io::Result<()> {
(**self).write_file(name, bytes)
}
fn append_file(&self, name: &str, bytes: &[u8]) -> io::Result<()> {
(**self).append_file(name, bytes)
}
fn read_file(&self, name: &str) -> io::Result<Vec<u8>> {
(**self).read_file(name)
}
fn remove_file(&self, name: &str) -> io::Result<()> {
(**self).remove_file(name)
}
fn files(&self) -> io::Result<Vec<String>> {
(**self).files()
}
}
impl<S: Store + ?Sized> Store for $pointer<S> {
fn logs(&self) -> io::Result<Vec<String>> {
(**self).logs()
}
fn read_log(&self, name: &str) -> io::Result<Vec<u8>> {
(**self).read_log(name)
}
fn append_log(&self, name: &str, len: u64, bytes: &[u8]) -> Result<(), LogWriteError> {
(**self).append_log(name, len, bytes)
}
fn replace_log(&self, name: &str, len: u64, bytes: &[u8]) -> Result<(), LogWriteError> {
(**self).replace_log(name, len, bytes)
}
fn destroy(&self) -> io::Result<()> {
(**self).destroy()
}
}
)*};
}
forward!(Arc, Box);
pub(crate) fn now<T>(
work: impl Future<Output = Result<T, SessionError>>,
) -> Result<T, SessionError> {
match pin!(work).poll(&mut Context::from_waker(Waker::noop())) {
Poll::Ready(output) => output,
Poll::Pending => Err(io::Error::other("a local store's call did not finish").into()),
}
}
pub fn check_name(name: &str) -> io::Result<()> {
let bad = |part: &str| matches!(part, "" | "." | "..") || part.contains('\\');
if name.split('/').any(bad) {
let why = format!("{name:?} is no name in a session");
return Err(io::Error::new(io::ErrorKind::InvalidInput, why));
}
Ok(())
}
pub(crate) fn check_leaf(name: &str) -> io::Result<()> {
check_name(name)?;
if name.contains('/') {
let why = format!("{name:?} is no log name");
return Err(io::Error::new(io::ErrorKind::InvalidInput, why));
}
Ok(())
}
#[derive(Clone, Default)]
pub struct MemoryStore(Arc<Mutex<Memory>>);
#[derive(Default)]
struct Memory {
logs: BTreeMap<String, Vec<u8>>,
files: BTreeMap<String, Vec<u8>>,
}
impl MemoryStore {
fn memory(&self) -> MutexGuard<'_, Memory> {
self.0.lock().unwrap_or_else(PoisonError::into_inner)
}
}
fn not_found() -> io::Error {
io::ErrorKind::NotFound.into()
}
impl Files for MemoryStore {
fn write_file(&self, name: &str, bytes: &[u8]) -> io::Result<()> {
check_name(name)?;
self.memory().files.insert(name.to_owned(), bytes.to_vec());
Ok(())
}
fn append_file(&self, name: &str, bytes: &[u8]) -> io::Result<()> {
check_name(name)?;
let mut memory = self.memory();
let file = memory.files.entry(name.to_owned()).or_default();
file.extend_from_slice(bytes);
Ok(())
}
fn read_file(&self, name: &str) -> io::Result<Vec<u8>> {
check_name(name)?;
self.memory().files.get(name).cloned().ok_or_else(not_found)
}
fn remove_file(&self, name: &str) -> io::Result<()> {
check_name(name)?;
self.memory().files.remove(name);
Ok(())
}
fn files(&self) -> io::Result<Vec<String>> {
Ok(self.memory().files.keys().cloned().collect())
}
}
impl Store for MemoryStore {
fn logs(&self) -> io::Result<Vec<String>> {
Ok(self.memory().logs.keys().cloned().collect())
}
fn read_log(&self, name: &str) -> io::Result<Vec<u8>> {
self.memory().logs.get(name).cloned().ok_or_else(not_found)
}
fn append_log(&self, name: &str, len: u64, bytes: &[u8]) -> Result<(), LogWriteError> {
check_leaf(name)?;
let mut memory = self.memory();
if memory.logs.get(name).map_or(0, Vec::len) as u64 != len {
return Err(LogWriteError::Changed);
}
let log = memory.logs.entry(name.to_owned()).or_default();
log.extend_from_slice(bytes);
Ok(())
}
fn replace_log(&self, name: &str, len: u64, bytes: &[u8]) -> Result<(), LogWriteError> {
check_leaf(name)?;
let mut memory = self.memory();
if memory.logs.get(name).map_or(0, Vec::len) as u64 != len {
return Err(LogWriteError::Changed);
}
memory.logs.insert(name.to_owned(), bytes.to_vec());
Ok(())
}
fn destroy(&self) -> io::Result<()> {
*self.memory() = Memory::default();
Ok(())
}
}
pub const PLUGINS: &str = "plugins/";
pub struct PluginFiles(pub Arc<dyn Files>);
impl Files for PluginFiles {
fn write_file(&self, name: &str, bytes: &[u8]) -> io::Result<()> {
self.0.write_file(&format!("{PLUGINS}{name}"), bytes)
}
fn append_file(&self, name: &str, bytes: &[u8]) -> io::Result<()> {
self.0.append_file(&format!("{PLUGINS}{name}"), bytes)
}
fn read_file(&self, name: &str) -> io::Result<Vec<u8>> {
self.0.read_file(&format!("{PLUGINS}{name}"))
}
fn remove_file(&self, name: &str) -> io::Result<()> {
self.0.remove_file(&format!("{PLUGINS}{name}"))
}
fn files(&self) -> io::Result<Vec<String>> {
let files = self.0.files()?.into_iter();
let within = files.filter_map(|name| Some(name.strip_prefix(PLUGINS)?.to_owned()));
Ok(within.collect())
}
}