use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, RwLock};
use crate::channel::Channel;
use crate::config::{self, Config, ConfigError, LayerDef, Model, Participants};
use crate::feed::{self, Event, FeedLog, Notify};
use crate::tuplespace::Tuplespace;
pub struct LayerState {
pub def: LayerDef,
pub(crate) write: futures::lock::Mutex<()>,
pub(crate) feed: Mutex<FeedLog>,
pub(crate) model: ModelState,
}
pub enum ModelState {
Channel(RwLock<Channel>),
Tuplespace(Tuplespace),
}
impl LayerState {
pub(crate) fn channel(&self) -> &RwLock<Channel> {
match &self.model {
ModelState::Channel(channel) => channel,
_ => unreachable!("channel actions are bound only on channel layers"),
}
}
pub(crate) fn tuplespace(&self) -> &Tuplespace {
match &self.model {
ModelState::Tuplespace(space) => space,
_ => unreachable!("tuplespace actions are bound only on tuplespace layers"),
}
}
pub fn events_after(&self, after: u64) -> Vec<Event> {
self.feed.lock().expect("feed lock").after(after).to_vec()
}
pub fn last_seq(&self) -> u64 {
self.feed.lock().expect("feed lock").last()
}
}
pub struct Host {
config_dir: PathBuf,
layers: BTreeMap<String, Arc<LayerState>>,
notify: Arc<Notify>,
}
impl Host {
pub fn open(
config_dir: impl AsRef<Path>,
state_dir: impl AsRef<Path>,
) -> Result<Arc<Host>, ConfigError> {
let config = Config::load(&config_dir)?;
let state_dir = state_dir.as_ref();
let mut layers = BTreeMap::new();
for (name, def) in config.layers {
let dir = state_dir.join(&name);
let state = open_layer(def, &dir)
.map_err(|e| ConfigError(format!("layer `{name}` state: {e}")))?;
layers.insert(name, Arc::new(state));
}
Ok(Arc::new(Host {
config_dir: config.dir,
layers,
notify: Arc::new(Notify::default()),
}))
}
pub fn layers(&self) -> &BTreeMap<String, Arc<LayerState>> {
&self.layers
}
pub fn layer(&self, name: &str) -> Option<&Arc<LayerState>> {
self.layers.get(name)
}
pub fn notify(&self) -> &Notify {
&self.notify
}
pub fn participants(&self) -> Result<Participants, ConfigError> {
config::load_participants(&self.config_dir)
}
pub fn collabs(&self) -> Result<BTreeMap<String, config::Manifest>, ConfigError> {
let defs: BTreeMap<String, LayerDef> = self
.layers
.iter()
.map(|(name, state)| (name.clone(), state.def.clone()))
.collect();
config::load_collabs(&self.config_dir, &defs, &self.participants()?)
}
}
fn open_layer(def: LayerDef, dir: &Path) -> Result<LayerState, String> {
std::fs::create_dir_all(dir).map_err(|e| format!("create {}: {e}", dir.display()))?;
let feed_path = dir.join("feed.log");
feed::touch(&feed_path)?;
let mut feed = FeedLog::open(&feed_path)?;
let model = match def.model {
Model::Channel => {
let channel = Channel::open(&def, dir)?;
let logged = feed.last();
for meta in channel.entries().iter().filter(|m| m.seq > logged) {
if meta.seq != feed.last() + 1 {
return Err(format!(
"entry {} has sequence {} but the feed ends at {}: a gap no repair \
can close",
meta.iri,
meta.seq,
feed.last()
));
}
feed.append(Event {
seq: meta.seq,
time: meta.created.clone(),
kind: "say".into(),
iri: meta.iri.clone(),
by: meta.author.clone(),
})?;
}
ModelState::Channel(RwLock::new(channel))
}
Model::Tuplespace => {
let logged: Vec<String> = feed
.events()
.iter()
.filter(|e| e.kind == "out")
.map(|e| e.iri.clone())
.collect();
let (space, stored) = Tuplespace::open(&def, dir, &logged)?;
let logged_to = feed.last();
for event in stored.into_iter().filter(|e| e.seq > logged_to) {
if event.seq != feed.last() + 1 {
return Err(format!(
"{} `{}` has sequence {} but the feed ends at {}: a gap no repair \
can close",
event.kind,
event.iri,
event.seq,
feed.last()
));
}
feed.append(event)?;
}
ModelState::Tuplespace(space)
}
};
Ok(LayerState {
def,
write: futures::lock::Mutex::new(()),
feed: Mutex::new(feed),
model,
})
}