Skip to main content

bacnet_objects/trend/
durable.rs

1//! A Trend Log kept in storage (#1541); see [`crate::log_persistence`].
2
3use std::sync::Arc;
4
5use bacnet_types::constructed::BACnetLogRecord;
6use bacnet_types::error::Error;
7use bacnet_types::primitives::{Date, Time};
8
9use super::TrendLogObject;
10use crate::log_buffer::LogRecordBuffer;
11use crate::log_persistence::store::{
12    fit_to_buffer_size, impl_durable_log, DurableLog, LogState, LogStore,
13};
14use crate::log_persistence::{LogCommitCadence, LogPersistence};
15
16impl TrendLogObject {
17    /// Open a Trend Log kept in `persistence`, or start one there when it
18    /// holds none yet.
19    ///
20    /// A log storage holds comes back as it was saved: Enable,
21    /// Stop_When_Full, its records and Total_Record_Count, its BUFFER_READY
22    /// counting and Notification_Threshold, and the settings a peer can write
23    /// (the window, Logging_Type, Log_Interval, Align_Intervals,
24    /// Interval_Offset and Log_DeviceObjectProperty). `buffer_size` wins
25    /// over the saved Buffer_Size; a smaller one keeps the newest records.
26    /// It then owes a LOG_INTERRUPTED record, appended once it can read a
27    /// valid clock, unless it reopens full and stopped. See
28    /// [`crate::log_persistence`] for when it commits.
29    ///
30    /// Fails with [`Error::OutOfRange`] for a `buffer_size` a slot can't
31    /// hold, with storage's error when the snapshot can't be loaded, and
32    /// when it doesn't hold together as a Trend Log's: another object's
33    /// identity, a zero generation, more records than Buffer_Size, records
34    /// with a Total_Record_Count of zero, a full buffer while Stop_When_Full
35    /// and Enable are both TRUE, a window end that isn't an actual moment, a
36    /// Logging_Type a Trend Log can't take, or more than one reference.
37    pub fn with_persistence(
38        instance: u32,
39        name: impl Into<String>,
40        buffer_size: u32,
41        persistence: Arc<dyn LogPersistence<BACnetLogRecord>>,
42    ) -> Result<Self, Error> {
43        let mut log = Self::new(instance, name, buffer_size)?;
44        let (mut store, loaded) = LogStore::open(log.oid, buffer_size, persistence)?;
45        if let Some(mut snapshot) = loaded {
46            let resized = fit_to_buffer_size(&mut snapshot, buffer_size);
47            log.lifecycle().reopen(&mut snapshot)?;
48            if let Some(acquisition) = snapshot.acquisition {
49                log.acquisition.restore(acquisition);
50            }
51            log.log_device_object_property = snapshot.log_device_object_property.pop();
52            store.reopened(log.log_state(), resized);
53        }
54        log.store = Some(store);
55        Ok(log)
56    }
57
58    /// Whether the log came back from a snapshot its storage held, rather
59    /// than starting empty. Always false without storage.
60    pub fn reopened(&self) -> bool {
61        self.store.as_ref().is_some_and(LogStore::is_reopened)
62    }
63
64    /// When the log commits to its storage; `None` without storage.
65    pub fn commit_cadence(&self) -> Option<LogCommitCadence> {
66        self.store.as_ref().map(LogStore::cadence)
67    }
68
69    /// Set when the log commits to its storage. Does nothing without
70    /// storage.
71    pub fn set_commit_cadence(&mut self, cadence: LogCommitCadence) {
72        if let Some(store) = &mut self.store {
73            store.set_cadence(cadence);
74        }
75    }
76
77    /// The generation of the last snapshot storage took: the loaded one's
78    /// until a commit lands, zero for storage that held none, and `None`
79    /// without storage.
80    pub fn storage_generation(&self) -> Option<u64> {
81        self.store.as_ref().map(LogStore::generation)
82    }
83}
84
85impl DurableLog for TrendLogObject {
86    type Record = BACnetLogRecord;
87
88    fn log_state(&self) -> LogState {
89        LogState::of(
90            &self.log_buffer,
91            self.log_enable,
92            self.stop_when_full,
93            &self.window,
94            &self.reporting,
95        )
96        .trend(
97            self.acquisition.settings(),
98            self.log_device_object_property.iter().cloned().collect(),
99        )
100    }
101
102    fn durable_parts(
103        &mut self,
104    ) -> (
105        Option<&mut LogStore<BACnetLogRecord>>,
106        &LogRecordBuffer<BACnetLogRecord>,
107    ) {
108        (self.store.as_mut(), &self.log_buffer)
109    }
110
111    fn record_owed_interruption(&mut self, fallback: Option<(Date, Time)>) -> bool {
112        self.lifecycle().record_interruption_now(fallback)
113    }
114}
115
116impl_durable_log!(TrendLogObject);