Skip to main content

bacnet_objects/trend/
durable.rs

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