use std::path::Path;
use bacnet_encoding::constructed::{decode_destination, encode_destination_list};
use bacnet_types::constructed::BACnetDestination;
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use bytes::BytesMut;
use super::MAX_RECIPIENT_LIST_DESTINATIONS;
use crate::durable::file::ObjectFile;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct NotificationClassSnapshot {
pub recipient_list: Option<Vec<BACnetDestination>>,
}
pub trait NotificationClassPersistence: Send + Sync {
fn load(&self, class: ObjectIdentifier) -> Result<Option<NotificationClassSnapshot>, Error>;
fn save(
&self,
class: ObjectIdentifier,
snapshot: &NotificationClassSnapshot,
) -> Result<(), Error>;
}
const MAGIC: &[u8; 8] = b"RBNNCL01";
pub(super) const MAX_FILE_BYTES: u64 = 64 * 1024;
#[derive(Clone, Debug)]
pub struct FileNotificationClassPersistence {
file: ObjectFile,
}
impl FileNotificationClassPersistence {
pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
Ok(Self {
file: ObjectFile::new(path.as_ref(), MAGIC, MAX_FILE_BYTES, "Notification Class")?,
})
}
pub fn path(&self) -> &Path {
self.file.path()
}
}
impl NotificationClassPersistence for FileNotificationClassPersistence {
fn load(&self, class: ObjectIdentifier) -> Result<Option<NotificationClassSnapshot>, Error> {
let Some(body) = self.file.load(class)? else {
return Ok(None);
};
let recipient_list = match body.split_first() {
Some((0, [])) => None,
Some((1, list)) => Some(self.file.decode_capped(
list,
MAX_RECIPIENT_LIST_DESTINATIONS,
decode_destination,
)?),
_ => return Err(self.file.corrupt("has no valid header")),
};
Ok(Some(NotificationClassSnapshot { recipient_list }))
}
fn save(
&self,
class: ObjectIdentifier,
snapshot: &NotificationClassSnapshot,
) -> Result<(), Error> {
let mut body = BytesMut::new();
body.extend_from_slice(&[u8::from(snapshot.recipient_list.is_some())]);
if let Some(list) = &snapshot.recipient_list {
encode_destination_list(&mut body, list)?;
}
self.file.save(class, &body)
}
}