use std::sync::Arc;
use std::time::Duration;
use bacnet_types::constructed::BACnetDestination;
use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use super::persistence::{NotificationClassPersistence, NotificationClassSnapshot};
use super::{recipient_list, NotificationClass, MAX_RECIPIENT_LIST_DESTINATIONS};
use crate::common;
use crate::durable::staged::{self, StagedSaves};
use crate::durable::{DurableWrites, PendingWrite, SaveWait, SaveWriter, StageStep};
pub(super) type Storage = StagedSaves<NotificationClassSnapshot, Vec<BACnetDestination>>;
impl NotificationClass {
pub fn with_persistence(
instance: u32,
name: impl Into<String>,
persistence: Arc<dyn NotificationClassPersistence>,
) -> Result<Self, Error> {
let mut class = Self::new(instance, name)?;
let oid = class.oid;
if let Some(list) = persistence
.load(oid)?
.and_then(|saved| saved.recipient_list)
{
if list.len() > MAX_RECIPIENT_LIST_DESTINATIONS {
return Err(recipient_list::no_space_error());
}
for destination in &list {
recipient_list::check_added(destination)?;
}
class.recipient_list = list;
class.recipient_list_written = true;
}
let writer = SaveWriter::new(
format!("bacnet-nc-{instance}-save"),
move |snapshot: &NotificationClassSnapshot| persistence.save(oid, snapshot),
move |_, result| {
if let Err(error) = result {
tracing::warn!(
class = %oid,
%error,
"Failed to save a Notification Class Recipient_List"
);
}
},
);
class.storage = Some(StagedSaves::new(writer));
Ok(class)
}
pub fn recipient_list_saved(&self) -> bool {
self.storage.is_some() && self.recipient_list_written
}
pub fn wait_for_saves(&self) {
if let Some(storage) = &self.storage {
storage.wait_idle();
}
}
fn snapshot(&self) -> NotificationClassSnapshot {
NotificationClassSnapshot {
recipient_list: self
.recipient_list_written
.then(|| self.recipient_list.clone()),
}
}
fn install(&mut self, list: Vec<BACnetDestination>) {
self.recipient_list = list;
self.recipient_list_written = true;
self.list_writes = self.list_writes.wrapping_add(1);
}
pub(super) fn write_recipient_list(&mut self, value: PropertyValue) -> Result<(), Error> {
let refused =
|_| common::protocol_error(ErrorClass::DEVICE, ErrorCode::OPERATIONAL_PROBLEM);
if let Some(taken) = self.take_staged(&value) {
return taken.map_err(refused);
}
let next = recipient_list::decode_write(value)?;
if self.storage.is_some() {
let mut snapshot = self.snapshot();
apply(&mut snapshot, &next);
self.with_storage(Storage::drop_staged);
let storage = self.storage.as_mut().expect("checked above");
storage.save_now(snapshot).map_err(refused)?;
}
self.install(next);
Ok(())
}
fn take_staged(&mut self, value: &PropertyValue) -> Option<Result<(), Error>> {
let mut storage = self.storage.take()?;
let base = self.list_writes;
let taken = storage
.claim(PropertyIdentifier::RECIPIENT_LIST, None, value, base)
.map(|claimed| claimed.map(|list| self.install(list)));
storage.correct(|| self.snapshot());
self.storage = Some(storage);
taken
}
pub(super) fn expire_staged_write(&mut self, now: Duration) {
self.with_storage(|storage| storage.expire(now));
}
fn with_storage<R>(&mut self, f: impl FnOnce(&mut Storage) -> R) -> Option<R> {
let mut storage = self.storage.take()?;
let result = f(&mut storage);
storage.correct(|| self.snapshot());
self.storage = Some(storage);
Some(result)
}
}
fn apply(snapshot: &mut NotificationClassSnapshot, list: &[BACnetDestination]) {
snapshot.recipient_list = Some(list.to_vec());
}
impl DurableWrites for NotificationClass {
fn stage_write(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: &PropertyValue,
) -> StageStep {
self.stage_writes(&[PendingWrite {
property,
array_index,
value: value.clone(),
}])
}
fn stage_writes(&mut self, writes: &[PendingWrite]) -> StageStep {
let listed = |write: &PendingWrite| write.property == PropertyIdentifier::RECIPIENT_LIST;
if self.storage.is_none() || !writes.iter().any(listed) {
return StageStep::Skip;
}
if let Some(wait) = self.with_storage(Storage::busy).flatten() {
return StageStep::Busy(wait);
}
let steps = staged::steps(writes, |_, write| {
if !listed(write) {
return Ok(None);
}
if write.array_index.is_some() {
return Err(common::property_is_not_an_array_error());
}
recipient_list::decode_write(write.value.clone()).map(Some)
});
let served = self.snapshot();
let base = self.list_writes;
let storage = self.storage.as_mut().expect("checked above");
storage.stage(steps, base, served, |snapshot, list| apply(snapshot, list))
}
fn release_staged_write(&mut self, staged: &SaveWait) {
self.with_storage(|storage| storage.release(staged));
}
fn has_staged_write(&self) -> bool {
self.storage.as_ref().is_some_and(Storage::is_staged)
}
fn settle_forgotten_writes(&mut self) -> Option<SaveWait> {
let mut storage = self.storage.take()?;
let wait = storage.drop_forgotten(|| self.snapshot());
self.storage = Some(storage);
Some(wait)
}
}