use crate::hyperspace::executor::cli::os::CliOsExecutor;
use crate::hyperspace::executor::cli::HostEnv;
use crate::hyperspace::executor::dialect::filestore::{FileStore, FileStoreErr, FILE_STORE_ROOT};
use crate::hyperspace::executor::{ExeConf, Executor};
use crate::hyperspace::host::err::HostErr;
use crate::hyperspace::host::{ExeStub, Host, HostCli};
use crate::hyperspace::machine::MachineErr;
use itertools::Itertools;
use nom::AsBytes;
use crate::space::err::SpaceErr;
use crate::space::kind::Kind;
use crate::space::loc::ToBaseKind;
use crate::space::particle::Status;
use crate::space::point::Point;
use crate::space::selector::KindSelector;
use crate::space::util::{IdSelector, MatchSelector, OptSelector, RegexMatcher, ValueMatcher};
use crate::space::wave::exchange::asynch::{DirectedHandler, Router};
use std::env;
use std::fmt::{Display, Formatter};
use std::future::Future;
use std::hash::Hash;
use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::path::{absolute, PathBuf};
use std::str::FromStr;
use strum_macros::EnumString;
use thiserror::Error;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
pub type FileStoreService = Service<FileStore>;
impl FileStoreService {
pub async fn sub_root(&self, sub_root: PathBuf) -> Result<FileStoreService, ServiceErr> {
let runner = self.runner.sub_root(sub_root).await?;
Ok(FileStoreService {
template: self.template.clone(),
runner,
})
}
}
pub struct ServiceCall<I, O> {
pub input: I,
pub output: oneshot::Sender<Result<O, ServiceErr>>,
}
#[derive(Clone)]
pub struct ServiceStub<I, O> {
tx: tokio::sync::mpsc::Sender<ServiceCall<I, O>>,
status: tokio::sync::watch::Receiver<Status>,
}
pub struct Service<R> {
pub template: ServiceTemplate,
runner: R,
}
impl Service<ServiceRunner> {
pub fn new(template: ServiceTemplate) -> Service<ServiceRunner> {
let runner = template.config.clone();
Self { template, runner }
}
pub fn filestore(self) -> Result<FileStoreService, ServiceErr> {
Ok(FileStoreService {
template: self.template,
runner: self.runner.filestore()?,
})
}
}
impl<R> Deref for Service<R> {
type Target = R;
fn deref(&self) -> &Self::Target {
&self.runner
}
}
#[derive(Clone)]
pub enum ServiceRunner {
Exe(ExeConf),
}
impl ServiceRunner {
pub fn filestore(&self) -> Result<FileStore, ServiceErr> {
match self {
ServiceRunner::Exe(exe) => Ok(exe.create()?),
}
}
}
#[derive(Hash, Clone, Eq, PartialEq, Debug, EnumString, strum_macros::Display)]
pub enum ServiceKind {
FileStore,
}
impl Into<Service<ServiceRunner>> for ServiceTemplate {
fn into(self) -> Service<ServiceRunner> {
let runner = self.config.clone();
Service {
template: self,
runner,
}
}
}
impl TryInto<Service<FileStore>> for Service<ServiceRunner> {
type Error = ServiceErr;
fn try_into(self) -> Result<Service<FileStore>, Self::Error> {
let filestore = self.runner.filestore()?;
Ok(Service {
template: self.template,
runner: filestore,
})
}
}
#[derive(Clone, Debug)]
pub struct ServiceSelector {
pub name: IdSelector<String>,
pub kind: ServiceKind,
pub driver: Option<Kind>,
}
impl Display for ServiceSelector {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.driver {
None => {
write!(f, "{}<*:{}>", self.name.to_string(), self.kind.to_string())
}
Some(kind) => {
write!(
f,
"{}<{}:{}>",
self.name.to_string(),
kind.to_string(),
self.kind.to_string()
)
}
}
}
}
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum ServiceScopeKind {
Global,
Point,
}
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum ServiceScope {
Global,
Point(Point),
}
#[derive(Clone)]
pub struct ServiceTemplate {
pub name: String,
pub kind: ServiceKind,
pub driver: OptSelector<KindSelector>,
pub config: ServiceConf,
}
impl PartialEq<ServiceTemplate> for ServiceSelector {
fn eq(&self, other: &ServiceTemplate) -> bool {
self.name == other.name && self.kind == other.kind && other.driver == self.driver
}
}
pub type ServiceConf = ServiceRunner;
pub fn service_conf() -> ServiceConf {
todo!("service_config() needs a minor refactor before it can be used again")
}
#[cfg(test)]
pub mod tests {
use crate::hyperspace::host::{ExeStub, Host};
use crate::hyperspace::executor::cli::HostEnv;
use crate::hyperspace::executor::dialect::filestore::{FileStore, FileStoreIn, FileStoreOut};
use crate::hyperspace::executor::{ExeConf, Executor};
use crate::hyperspace::host::HostCli;
use crate::hyperspace::service::{
service_conf, Service, ServiceConf, ServiceErr, ServiceKind, ServiceTemplate,
};
use crate::space::kind::{BaseKind, Kind};
use crate::space::selector::KindSelector;
use crate::space::util::OptSelector;
use std::path::{absolute, PathBuf};
use std::{env, io};
use tokio::fs;
fn filestore() -> FileStore {
if std::fs::exists("./tmp").unwrap() {
std::fs::remove_dir_all("./tmp").unwrap();
}
let mut builder = HostEnv::builder();
builder.pwd(
absolute(env::current_dir().unwrap())
.unwrap()
.to_str()
.unwrap()
.to_string(),
);
println!("{}", env::current_dir().unwrap().to_str().unwrap());
builder.env(
"FILE_STORE_ROOT",
format!("{}/tmp", env::current_dir().unwrap().to_str().unwrap()),
);
let env = builder.build();
let path = "../target/debug/starlane-cli-filestore-service".to_string();
let args: Option<Vec<String>> = Option::None;
let stub = ExeStub::new(path.into(), env);
let info = ExeConf::Host(Host::Cli(HostCli::Os(stub.clone())));
info.create().unwrap()
}
pub async fn filestore_from_service() -> Result<Service<FileStore>, ServiceErr> {
let config = service_conf();
let template = ServiceTemplate {
name: "some-filestore".to_string(),
kind: ServiceKind::FileStore,
driver: OptSelector::Selector(KindSelector::from_base(BaseKind::Repo)),
config,
};
let service = Service::new(template);
Ok(service.try_into()?)
}
#[tokio::test]
pub async fn test_filestore() {
let executor = filestore_from_service().await.unwrap();
if let io::Result::Ok(true) = fs::try_exists("./tmp").await {
fs::remove_dir_all("./tmp").await.unwrap();
}
{
let init = FileStoreIn::Init;
executor.execute(init).await.unwrap();
}
let path = PathBuf::from("tmp");
assert!(path.exists());
assert!(path.is_dir());
{
let args = FileStoreIn::Mkdir {
path: "blah".into(),
};
let mut child = executor.execute(args).await.unwrap();
}
let path = PathBuf::from("tmp/blah");
assert!(path.exists());
assert!(path.is_dir());
let content = "HEllo from me";
{
let args = FileStoreIn::Write {
path: "blah/somefile.txt".into(),
state: content.clone().into(),
};
let mut child = executor.execute(args).await.unwrap();
}
let path = PathBuf::from("tmp/blah/somefile.txt");
assert!(path.exists());
assert!(path.is_file());
{
let args = FileStoreIn::Read {
path: "blah/somefile.txt".into(),
};
let mut child = executor.execute(args).await.unwrap();
if let FileStoreOut::Read(bin) = child {
let read = String::from_utf8(bin).unwrap();
println!("content: {}", read);
assert_eq!(content, read);
} else {
assert!(false);
}
}
}
}
#[derive(Debug, Error, Clone)]
pub enum ServiceErr {
#[error(transparent)]
MachineErr(#[from] MachineErr),
#[error(transparent)]
FileStoreErr(#[from] FileStoreErr),
#[error(transparent)]
SpaceErr(#[from] SpaceErr),
#[error(transparent)]
HostErr(#[from] HostErr),
#[error(
"no template available that matches ServiceSelector: '{0}' (name<DriverKind:ServiceKind>)"
)]
NoTemplate(ServiceSelector),
#[error("call not processed")]
CallRecvErr(#[from] tokio::sync::oneshot::error::RecvError),
}