use super::inbox_dir;
use crate::prompt::Clock;
use crate::template::GitRunner;
use std::io;
use std::path::{Path, PathBuf};
pub(crate) mod result;
pub use result::{Epitaph, deposit_result, returned_ref};
const MESSAGE_EXT: &str = "md";
const SEQ_WIDTH: usize = 3;
const FIRST_SEQ: u32 = 1;
#[derive(Debug, thiserror::Error)]
pub enum DepositError {
#[error("inbox i/o at {path}: {source}")]
Io {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("mark refs/litany/returned/{child}: {source}")]
Mark {
child: String,
#[source]
source: io::Error,
},
}
fn io_err(path: &Path, source: io::Error) -> DepositError {
DepositError::Io {
path: path.to_path_buf(),
source,
}
}
pub fn deposit(
workspace: &Path,
agent_id: &str,
sender: &str,
content: &str,
clock: &dyn Clock,
git: &dyn GitRunner,
) -> Result<PathBuf, DepositError> {
let dir = inbox_dir(workspace, agent_id);
std::fs::create_dir_all(&dir).map_err(|e| io_err(&dir, e))?;
let seq = next_sequence(&dir, sender).map_err(|e| io_err(&dir, e))?;
let filename = message_filename(sender, seq);
let fields = fields(workspace, sender, &clock.now_iso8601(), git);
let body = format!("---\n{fields}---\n{content}");
atomic_create(&dir, &filename, body.as_bytes())?;
Ok(dir.join(filename))
}
fn message_filename(sender: &str, seq: u32) -> String {
format!("{sender}-{seq:0width$}.{MESSAGE_EXT}", width = SEQ_WIDTH)
}
pub(super) fn next_sequence(dir: &Path, sender: &str) -> io::Result<u32> {
let prefix = format!("{sender}-");
let suffix = format!(".{MESSAGE_EXT}");
let mut max: Option<u32> = None;
for entry in std::fs::read_dir(dir)?.flatten() {
let name = entry.file_name();
let Some(name) = name.to_str() else { continue };
if let Some(seq) = parse_seq(name, &prefix, &suffix) {
max = Some(max.map_or(seq, |m| m.max(seq)));
}
}
Ok(max.map_or(FIRST_SEQ, |m| m + 1))
}
fn parse_seq(name: &str, prefix: &str, suffix: &str) -> Option<u32> {
let mid = name.strip_prefix(prefix)?.strip_suffix(suffix)?;
mid.parse::<u32>().ok()
}
fn fields(workspace: &Path, sender: &str, deposited_at: &str, git: &dyn GitRunner) -> String {
let name = (sender != super::USER_SENDER)
.then(|| crate::workspace::agent_name::read(workspace, sender, git))
.flatten();
let named = match name {
Some(name) => format!("from_name: {name}\n"),
None => String::new(),
};
format!("from: {sender}\n{named}deposited_at: {deposited_at}\n")
}
pub(super) fn atomic_create(dir: &Path, filename: &str, bytes: &[u8]) -> Result<(), DepositError> {
let final_path = dir.join(filename);
let tmp_path = dir.join(format!(".{filename}.tmp"));
std::fs::write(&tmp_path, bytes).map_err(|e| io_err(&tmp_path, e))?;
std::fs::rename(&tmp_path, &final_path).map_err(|e| io_err(&final_path, e))?;
Ok(())
}