use std::path::Path;
use bacnet_encoding::constructed::{decode_name_value, encode_name_value};
use bacnet_types::constructed::BACnetNameValue;
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use bytes::BytesMut;
use super::{check_tags, no_space_error, MAX_TAGS};
use crate::durable::file::ObjectFile;
pub const MAX_TAGS_SNAPSHOT_BYTES: usize = 1024 * 1024;
const HEADER_BYTES: usize = 12;
const MAGIC: &[u8; 8] = b"RBNTAG01";
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TagsSnapshot {
pub tags: Option<Vec<BACnetNameValue>>,
}
pub trait TagsPersistence: Send + Sync {
fn load(&self, object: ObjectIdentifier) -> Result<Option<TagsSnapshot>, Error>;
fn save(&self, object: ObjectIdentifier, snapshot: &TagsSnapshot) -> Result<(), Error>;
}
#[derive(Clone, Debug)]
pub struct FileTagsPersistence {
file: ObjectFile,
}
impl FileTagsPersistence {
pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
Ok(Self {
file: ObjectFile::new(path.as_ref(), MAGIC, MAX_TAGS_SNAPSHOT_BYTES as u64, "Tags")?,
})
}
pub fn path(&self) -> &Path {
self.file.path()
}
}
pub(super) fn encode_snapshot(snapshot: &TagsSnapshot) -> Result<BytesMut, Error> {
let mut body = BytesMut::new();
body.extend_from_slice(&[u8::from(snapshot.tags.is_some())]);
if let Some(tags) = &snapshot.tags {
check_tags(tags)?;
for tag in tags {
encode_name_value(&mut body, tag)?;
if body.len() > MAX_TAGS_SNAPSHOT_BYTES - HEADER_BYTES {
return Err(no_space_error());
}
}
}
Ok(body)
}
impl TagsPersistence for FileTagsPersistence {
fn load(&self, object: ObjectIdentifier) -> Result<Option<TagsSnapshot>, Error> {
let Some(body) = self.file.load(object)? else {
return Ok(None);
};
let tags = match body.split_first() {
Some((0, [])) => None,
Some((1, bytes)) => Some(self.file.decode_capped(
bytes,
MAX_TAGS,
decode_name_value,
)?),
_ => return Err(self.file.corrupt("has no valid presence marker")),
};
let snapshot = TagsSnapshot { tags };
encode_snapshot(&snapshot)?;
Ok(Some(snapshot))
}
fn save(&self, object: ObjectIdentifier, snapshot: &TagsSnapshot) -> Result<(), Error> {
let body = encode_snapshot(snapshot)?;
self.file.save(object, &body)
}
}