eidetica/backend/database/in_memory/
persistence.rs1use std::{collections::HashMap, path::Path, sync::RwLock};
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use super::{InMemory, InMemoryInner, TreeTipsCache};
11use crate::{
12 Error, Result,
13 backend::{InstanceMetadata, InstanceSecrets, VerificationStatus, errors::BackendError},
14 entry::{Entry, ID},
15};
16
17const PERSISTENCE_VERSION: u8 = 0;
20
21fn is_v0(v: &u8) -> bool {
23 *v == 0
24}
25
26fn validate_persistence_version<'de, D>(deserializer: D) -> std::result::Result<u8, D::Error>
28where
29 D: Deserializer<'de>,
30{
31 use serde::Deserialize;
32 let version = u8::deserialize(deserializer)?;
33 if version != PERSISTENCE_VERSION {
34 return Err(serde::de::Error::custom(format!(
35 "unsupported persistence version {version}; only version {PERSISTENCE_VERSION} is supported"
36 )));
37 }
38 Ok(version)
39}
40
41#[derive(Serialize, Deserialize)]
43struct SerializableDatabase {
44 #[serde(
46 rename = "_v",
47 default,
48 skip_serializing_if = "is_v0",
49 deserialize_with = "validate_persistence_version"
50 )]
51 version: u8,
52 entries: HashMap<ID, Entry>,
53 #[serde(default)]
54 verification_status: HashMap<ID, VerificationStatus>,
55 #[serde(default)]
57 instance_metadata: Option<InstanceMetadata>,
58 #[serde(default)]
60 instance_secrets: Option<InstanceSecrets>,
61 #[serde(default, skip_serializing)]
68 #[allow(dead_code)]
69 cache: Option<serde_json::Value>,
70 #[serde(default)]
72 tips: HashMap<ID, TreeTipsCache>,
73}
74
75impl Serialize for InMemory {
76 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
77 where
78 S: Serializer,
79 {
80 let serializable = {
84 let inner = self.inner.read().unwrap();
85 SerializableDatabase {
86 version: PERSISTENCE_VERSION,
87 entries: inner.entries.clone(),
88 verification_status: inner.verification_status.clone(),
89 instance_metadata: inner.instance_metadata.clone(),
90 instance_secrets: inner.instance_secrets.clone(),
91 cache: None,
92 tips: inner.tips.clone(),
93 }
94 };
95
96 serializable.serialize(serializer)
97 }
98}
99
100impl<'de> Deserialize<'de> for InMemory {
101 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
102 where
103 D: Deserializer<'de>,
104 {
105 let serializable = SerializableDatabase::deserialize(deserializer)?;
107
108 Ok(InMemory {
109 inner: RwLock::new(InMemoryInner {
110 entries: serializable.entries,
111 store_state_namespaces: HashMap::new(),
113 verification_status: serializable.verification_status,
114 instance_metadata: serializable.instance_metadata,
115 instance_secrets: serializable.instance_secrets,
116 tips: serializable.tips,
117 }),
118 store_state_point_reads: std::sync::atomic::AtomicUsize::new(0),
119 store_state_scan_reads: std::sync::atomic::AtomicUsize::new(0),
120 #[cfg(feature = "testing")]
121 store_history_reads: std::sync::atomic::AtomicUsize::new(0),
122 })
123 }
124}
125
126pub(crate) fn save_to_file<P: AsRef<Path>>(backend: &InMemory, path: P) -> Result<()> {
142 let serializable = {
145 let inner = backend.inner.read().unwrap();
146 SerializableDatabase {
147 version: PERSISTENCE_VERSION,
148 entries: inner.entries.clone(),
149 verification_status: inner.verification_status.clone(),
150 instance_metadata: inner.instance_metadata.clone(),
151 instance_secrets: inner.instance_secrets.clone(),
152 cache: None,
153 tips: inner.tips.clone(),
154 }
155 };
156
157 let json = serde_json::to_string_pretty(&serializable)
158 .map_err(|e| -> Error { BackendError::SerializationFailed { source: e }.into() })?;
159
160 let path = path.as_ref();
164 let mut tmp = path.as_os_str().to_owned();
165 tmp.push(".tmp");
166 let tmp_path = std::path::PathBuf::from(tmp);
167
168 std::fs::write(&tmp_path, json.as_bytes())
169 .map_err(|e| -> Error { BackendError::FileIo { source: e }.into() })?;
170 std::fs::rename(&tmp_path, path).map_err(|e| -> Error {
171 let _ = std::fs::remove_file(&tmp_path);
175 BackendError::FileIo { source: e }.into()
176 })
177}
178
179pub(crate) fn try_load_from_file<P: AsRef<Path>>(path: P) -> Result<Option<InMemory>> {
190 match std::fs::read_to_string(path) {
191 Ok(json) => {
192 let database: InMemory = serde_json::from_str(&json).map_err(|e| -> Error {
193 BackendError::DeserializationFailed { source: e }.into()
194 })?;
195 Ok(Some(database))
196 }
197 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
198 Err(e) => Err(BackendError::FileIo { source: e }.into()),
199 }
200}
201
202pub(crate) fn load_from_file<P: AsRef<Path>>(path: P) -> Result<InMemory> {
214 Ok(try_load_from_file(path)?.unwrap_or_else(InMemory::new))
215}