1use crate::runtime::{Mode, Snapshot};
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8use std::fs;
9use std::path::PathBuf;
10
11#[derive(Debug, Clone, Serialize, Deserialize, Default)]
12pub struct StoreData {
13 #[serde(default)]
14 pub defs: Vec<String>,
15 #[serde(default)]
16 pub instances: Vec<Snapshot>,
17 #[serde(default)]
18 pub clock: u64,
19 #[serde(default = "default_mode")]
20 pub mode: String,
21}
22
23fn default_mode() -> String {
24 "auto".to_string()
25}
26
27impl StoreData {
28 pub fn mode(&self) -> Mode {
29 if self.mode == "manual" {
30 Mode::Manual
31 } else {
32 Mode::Auto
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
39pub enum StoreSpec {
40 File(PathBuf),
41 Mem,
42 Sqlite(PathBuf),
43}
44
45impl StoreSpec {
46 pub fn parse(spec: &str) -> StoreSpec {
47 if let Some(rest) = spec.strip_prefix("file:") {
48 StoreSpec::File(PathBuf::from(rest))
49 } else if spec == "mem:" || spec == "mem" {
50 StoreSpec::Mem
51 } else if let Some(rest) = spec.strip_prefix("sqlite:") {
52 StoreSpec::Sqlite(PathBuf::from(rest))
53 } else {
54 StoreSpec::File(PathBuf::from(spec))
55 }
56 }
57}
58
59pub trait Store {
60 fn load(&self) -> StoreData;
61 fn save(&self, data: &StoreData);
62}
63
64pub struct FileStore {
67 dir: PathBuf,
68}
69
70impl FileStore {
71 pub fn new(dir: PathBuf) -> Self {
72 FileStore { dir }
73 }
74 fn path(&self) -> PathBuf {
75 self.dir.join("store.json")
76 }
77}
78
79impl Store for FileStore {
80 fn load(&self) -> StoreData {
81 match fs::read(self.path()) {
82 Ok(bytes) => serde_json::from_slice(&bytes).unwrap_or_default(),
83 Err(_) => StoreData::default(),
84 }
85 }
86 fn save(&self, data: &StoreData) {
87 let _ = fs::create_dir_all(&self.dir);
88 let bytes = serde_json::to_vec_pretty(data).expect("serialize store");
89 let _ = fs::write(self.path(), bytes);
90 }
91}
92
93use std::cell::RefCell;
96use std::rc::Rc;
97
98pub struct MemStore {
99 inner: Rc<RefCell<StoreData>>,
100}
101
102impl MemStore {
103 pub fn new() -> Self {
104 MemStore {
105 inner: Rc::new(RefCell::new(StoreData::default())),
106 }
107 }
108 pub fn handle(&self) -> Rc<RefCell<StoreData>> {
109 self.inner.clone()
110 }
111}
112
113impl Default for MemStore {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl Store for MemStore {
120 fn load(&self) -> StoreData {
121 self.inner.borrow().clone()
122 }
123 fn save(&self, data: &StoreData) {
124 *self.inner.borrow_mut() = data.clone();
125 }
126}
127
128pub struct SqliteStore {
131 path: PathBuf,
132}
133
134impl SqliteStore {
135 pub fn new(path: PathBuf) -> Self {
136 SqliteStore { path }
137 }
138 fn conn(&self) -> rusqlite::Result<rusqlite::Connection> {
139 if let Some(parent) = self.path.parent() {
140 let _ = std::fs::create_dir_all(parent);
141 }
142 let conn = rusqlite::Connection::open(&self.path)?;
143 conn.execute_batch(
144 "CREATE TABLE IF NOT EXISTS determa_store (id INTEGER PRIMARY KEY, data TEXT NOT NULL);",
145 )?;
146 Ok(conn)
147 }
148}
149
150impl Store for SqliteStore {
151 fn load(&self) -> StoreData {
152 let conn = match self.conn() {
153 Ok(c) => c,
154 Err(_) => return StoreData::default(),
155 };
156 let row: Option<String> = conn
157 .query_row(
158 "SELECT data FROM determa_store WHERE id = 0",
159 [],
160 |r| r.get(0),
161 )
162 .ok();
163 match row {
164 Some(s) => serde_json::from_str(&s).unwrap_or_default(),
165 None => StoreData::default(),
166 }
167 }
168 fn save(&self, data: &StoreData) {
169 if let Ok(conn) = self.conn() {
170 let s = serde_json::to_string(data).unwrap_or_default();
171 let _ = conn.execute(
172 "INSERT INTO determa_store (id, data) VALUES (0, ?1) \
173 ON CONFLICT(id) DO UPDATE SET data = excluded.data",
174 rusqlite::params![s],
175 );
176 }
177 }
178}
179
180pub fn open(spec: &StoreSpec) -> Box<dyn Store> {
182 match spec {
183 StoreSpec::File(dir) => Box::new(FileStore::new(dir.clone())),
184 StoreSpec::Mem => Box::new(MemStore::new()),
185 StoreSpec::Sqlite(path) => Box::new(SqliteStore::new(path.clone())),
186 }
187}
188
189pub fn _inflate(_m: BTreeMap<String, crate::Value>) {}