use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReceiptState {
Queued,
Injected,
}
impl ReceiptState {
pub(crate) fn as_str(self) -> &'static str {
match self {
ReceiptState::Queued => "queued",
ReceiptState::Injected => "injected",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct NotifyReceipt {
pub target: Uuid,
pub state: ReceiptState,
pub queued_at: chrono::DateTime<chrono::Utc>,
pub injected_at: Option<chrono::DateTime<chrono::Utc>>,
}
fn receipts() -> &'static Mutex<HashMap<Uuid, NotifyReceipt>> {
static RECEIPTS: OnceLock<Mutex<HashMap<Uuid, NotifyReceipt>>> = OnceLock::new();
RECEIPTS.get_or_init(|| Mutex::new(HashMap::new()))
}
pub(crate) fn record_queued(id: Uuid, target: Uuid) {
let receipt = NotifyReceipt {
target,
state: ReceiptState::Queued,
queued_at: chrono::Utc::now(),
injected_at: None,
};
match receipts().lock() {
Ok(mut map) => {
map.insert(id, receipt);
}
Err(e) => {
tracing::error!(
target: "quiet_delivery",
"notify receipts poisoned, receipt {id} not recorded: {e}"
);
}
}
}
pub(crate) fn reserve(id: Uuid, target: Uuid) -> bool {
let receipt = NotifyReceipt {
target,
state: ReceiptState::Queued,
queued_at: chrono::Utc::now(),
injected_at: None,
};
match receipts().lock() {
Ok(mut map) => match map.entry(id) {
std::collections::hash_map::Entry::Vacant(slot) => {
slot.insert(receipt);
true
}
std::collections::hash_map::Entry::Occupied(_) => false,
},
Err(e) => {
tracing::error!(
target: "quiet_delivery",
"notify receipts poisoned, receipt {id} not reserved: {e}"
);
true
}
}
}
pub(crate) fn forget(id: Uuid) {
if let Ok(mut map) = receipts().lock() {
map.remove(&id);
}
}
pub(crate) fn mark_injected_for_target(target: Uuid) -> usize {
let now = chrono::Utc::now();
match receipts().lock() {
Ok(mut map) => {
let mut stamped = 0;
for receipt in map.values_mut() {
if receipt.target == target && receipt.state == ReceiptState::Queued {
receipt.state = ReceiptState::Injected;
receipt.injected_at = Some(now);
stamped += 1;
}
}
stamped
}
Err(e) => {
tracing::error!(
target: "quiet_delivery",
"notify receipts poisoned, injection stamp lost for {target}: {e}"
);
0
}
}
}
pub(crate) fn status(id: Uuid) -> Option<NotifyReceipt> {
receipts().lock().ok()?.get(&id).cloned()
}