use crate::error::{Error, Result};
use crate::op;
use crate::session::{ReadOnly, ReadWrite, Session};
use crate::transport::Transport;
use crate::wire::{AllocationUnit, Bank, Location, ObjectClass, Partition};
pub struct Geometry {
entries: Vec<Entry>,
}
struct Entry {
partition: Partition,
banks: Vec<Bank>,
}
impl Geometry {
pub async fn read<T: Transport, C>(session: &mut Session<'_, T, C>) -> Result<Self> {
let mut entries = Vec::new();
for partition in op::partitions(session).await? {
let banks = op::banks(session, partition.index).await?;
entries.push(Entry { partition, banks });
}
Ok(Self { entries })
}
pub fn entries(&self) -> impl Iterator<Item = (&Partition, &[Bank])> {
self.entries
.iter()
.map(|entry| (&entry.partition, entry.banks.as_slice()))
}
pub fn partition(&self, class: ObjectClass) -> Result<&Partition> {
Ok(&self.entry(class)?.partition)
}
pub fn banks(&self, class: ObjectClass) -> Result<&[Bank]> {
Ok(&self.entry(class)?.banks)
}
pub fn allocation_unit(&self, class: ObjectClass) -> Result<AllocationUnit> {
self.partition(class)?.allocation_unit()
}
pub fn check_address(&self, class: ObjectClass, at: Location) -> Result<Option<String>> {
Ok(op::address_refusal(self.banks(class)?, at))
}
fn entry(&self, class: ObjectClass) -> Result<&Entry> {
self.entries
.iter()
.find(|entry| entry.partition.index == class.to_raw())
.ok_or_else(|| {
Error::InvalidArgument(format!("the instrument has no {} partition", class.label()))
})
}
}
pub struct Device<T: Transport> {
transport: T,
geometry: Option<Geometry>,
changed: bool,
}
impl<T: Transport> Device<T> {
pub fn new(transport: T) -> Self {
Self {
transport,
geometry: None,
changed: false,
}
}
pub fn transport(&mut self) -> &mut T {
&mut self.transport
}
pub fn into_transport(self) -> T {
self.transport
}
pub async fn read<R>(
&mut self,
class: ObjectClass,
f: impl AsyncFnOnce(&mut Session<'_, T, ReadOnly>) -> Result<R>,
) -> Result<R> {
let session = Session::open(&mut self.transport, class).await?;
bracket(&mut self.changed, session, f).await
}
pub async fn destructive<R>(
&mut self,
class: ObjectClass,
f: impl AsyncFnOnce(&mut Session<'_, T, ReadWrite>) -> Result<R>,
) -> Result<R> {
let session = Session::open(&mut self.transport, class)
.await?
.allow_destructive_writes();
bracket(&mut self.changed, session, f).await
}
pub fn take_changed(&mut self) -> bool {
std::mem::take(&mut self.changed)
}
pub async fn geometry(&mut self) -> Result<&Geometry> {
if self.geometry.is_none() {
let read = self
.read(ObjectClass::Program, async |s| Geometry::read(s).await)
.await?;
self.geometry = Some(read);
}
Ok(self.geometry.as_ref().expect("just read"))
}
pub async fn write(
&mut self,
class: ObjectClass,
at: Location,
file: &[u8],
name: &str,
timestamp: u32,
) -> Result<()> {
let unit = self.geometry().await?.allocation_unit(class)?;
self.destructive(class, async |s| {
op::write(s, unit, at, file, name, timestamp).await
})
.await
}
}
async fn bracket<T: Transport, C, R>(
changed: &mut bool,
mut session: Session<'_, T, C>,
f: impl AsyncFnOnce(&mut Session<'_, T, C>) -> Result<R>,
) -> Result<R> {
let result = f(&mut session).await;
let (closed, session_changed) = session.commit_observing_changed().await;
*changed |= session_changed;
match (result, closed) {
(Ok(value), Ok(())) => Ok(value),
(Ok(_), Err(close)) => Err(close),
(Err(Error::DeviceStatus(_)), Err(close @ Error::Transport(_))) => Err(close),
(Err(chain), _) => Err(chain),
}
}