use std::collections::{BTreeMap, BTreeSet};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use std::time::Duration;
use super::doubt::{redelivered, unknown};
use super::{Call, Capture, Invocation};
pub(crate) mod read;
use read::{HOLD_TICK, HOLD_WAITS};
const TTL_SECONDS: i64 = 3600;
#[derive(Debug, Clone, PartialEq)]
struct Slot {
client: String,
by: String,
invocation: Invocation,
handed: u32,
capture: Option<Capture>,
at: i64,
}
#[derive(Default)]
struct Slots {
live: BTreeMap<String, Slot>,
seq: u64,
reading: BTreeSet<String>,
}
type MailCell = Arc<Mutex<Slots>>;
fn lock_mail(cell: &MailCell) -> MutexGuard<'_, Slots> {
cell.lock().unwrap_or_else(PoisonError::into_inner)
}
#[derive(Clone)]
pub struct Mailbox {
cell: MailCell,
waits: u32,
tick: Duration,
}
impl Default for Mailbox {
fn default() -> Self {
Self {
cell: MailCell::default(),
waits: HOLD_WAITS,
tick: HOLD_TICK,
}
}
}
impl Mailbox {
pub fn holding(waits: u32, tick: Duration) -> Self {
Self {
cell: MailCell::default(),
waits,
tick,
}
}
pub fn post(&self, now: i64, by: &str, call: &Call) -> String {
let mut slots = lock_mail(&self.cell);
slots.live.retain(|_, slot| now - slot.at <= TTL_SECONDS);
slots.seq += 1;
let id = format!("inv-{}", slots.seq);
slots.live.insert(
id.clone(),
Slot {
client: call.client.clone(),
by: by.to_owned(),
invocation: Invocation {
id: id.clone(),
tool: call.tool.clone(),
input: call.input.clone(),
cwd: call.cwd.clone(),
},
handed: 0,
capture: None,
at: now,
},
);
id
}
pub fn complete(
&self,
client: &str,
invocation: &str,
capture: &Capture,
) -> Result<Capture, String> {
let mut slots = lock_mail(&self.cell);
let slot = slots
.live
.get_mut(invocation)
.filter(|slot| slot.client == client)
.ok_or_else(|| unknown(invocation))?;
let stored = redelivered(client, slot.handed, capture);
slot.capture = Some(stored.clone());
Ok(stored)
}
pub fn collect(&self, by: &str, invocation: &str) -> Result<Option<Capture>, String> {
let mut slots = lock_mail(&self.cell);
let slot = slots
.live
.get(invocation)
.filter(|slot| slot.by == by)
.ok_or_else(|| unknown(invocation))?;
let Some(capture) = slot.capture.clone() else {
return Ok(None);
};
slots.live.remove(invocation);
Ok(Some(capture))
}
}
#[cfg(test)]
mod tests;