use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use zcash_primitives::transaction::TxId;
use zcash_protocol::consensus::BlockHeight;
use crate::error::SqliteClientError;
#[derive(Clone, Default)]
pub struct TransparentPirSession(Arc<Mutex<State>>);
#[derive(Clone, Default)]
struct State {
queued_at: BTreeMap<TxId, BlockHeight>,
}
#[derive(Clone)]
enum Change {
Queued(BlockHeight, Vec<TxId>),
}
impl Change {
fn apply(&self, state: &mut State) {
match self {
Self::Queued(tip, txids) => {
state.queued_at.retain(|_, at| at == tip);
state
.queued_at
.extend(txids.iter().map(|txid| (*txid, *tip)));
}
}
}
}
#[derive(Clone, Default)]
pub(crate) struct SessionState {
pub(crate) session: TransparentPirSession,
journals: Vec<Arc<Mutex<Vec<Change>>>>,
}
impl SessionState {
pub(crate) fn check_transaction(
&self,
conn: &rusqlite::Connection,
) -> Result<(), SqliteClientError> {
if !conn.is_autocommit() && self.journals.is_empty() {
Err(SqliteClientError::TransparentSessionTransactionRequired)
} else {
Ok(())
}
}
pub(crate) fn new(session: TransparentPirSession) -> Self {
Self {
session,
journals: vec![],
}
}
pub(crate) fn transaction(&self) -> Self {
let mut journals = self.journals.clone();
journals.push(Arc::new(Mutex::new(Vec::new())));
Self {
session: self.session.clone(),
journals,
}
}
pub(crate) fn commit(&self) {
if let Some((own, outer)) = self.journals.split_last() {
let changes = std::mem::take(&mut *own.lock().unwrap_or_else(|e| e.into_inner()));
match outer.last() {
Some(enclosing) => enclosing
.lock()
.unwrap_or_else(|e| e.into_inner())
.extend(changes),
None => {
let mut state = self.session.0.lock().unwrap_or_else(|e| e.into_inner());
for change in changes {
change.apply(&mut state);
}
}
}
}
}
fn change(&self, change: Change) {
if let Some(own) = self.journals.last() {
own.lock().unwrap_or_else(|e| e.into_inner()).push(change);
} else {
change.apply(&mut self.session.0.lock().unwrap_or_else(|e| e.into_inner()));
}
}
fn state(&self) -> State {
let mut state = self
.session
.0
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone();
for journal in &self.journals {
for change in journal.lock().unwrap_or_else(|e| e.into_inner()).iter() {
change.apply(&mut state);
}
}
state
}
pub(crate) fn queued(&self, tip: BlockHeight, txids: Vec<TxId>) {
if !txids.is_empty() {
self.change(Change::Queued(tip, txids));
}
}
pub(crate) fn held(&self, tip: BlockHeight) -> Vec<TxId> {
self.state()
.queued_at
.into_iter()
.filter(|(_, at)| *at == tip)
.map(|(txid, _)| txid)
.collect()
}
}