automerge_persistent_localstorage/
lib.rs

1#![warn(missing_docs)]
2#![warn(missing_crate_level_docs)]
3#![warn(missing_doc_code_examples)]
4#![warn(clippy::pedantic)]
5#![warn(clippy::nursery)]
6
7//! A persister targetting `LocalStorage` in the browser.
8//!
9//! ```rust,no_run
10//! # use automerge_persistent_localstorage::{LocalStoragePersister, LocalStoragePersisterError};
11//! # use automerge_persistent::PersistentAutomerge;
12//! # fn main() -> Result<(), LocalStoragePersisterError> {
13//! let storage = web_sys::window()
14//!     .unwrap()
15//!     .local_storage()
16//!     .map_err(LocalStoragePersisterError::StorageError)?
17//!     .unwrap();
18//!
19//! let persister = LocalStoragePersister::new(storage, "document".to_owned(), "changes".to_owned(), "sync-states".to_owned())?;
20//! let doc = PersistentAutomerge::load(persister).unwrap();
21//! # Ok(())
22//! # }
23//! ```
24
25use std::collections::HashMap;
26
27use automerge::ActorId;
28use automerge_persistent::{Persister, StoredSizes};
29use base64::Engine;
30
31/// Persist changes and documents in to `LocalStorage`.
32///
33/// While aimed at `LocalStorage`, it accepts any storage that  conforms to the [`web_sys::Storage`]
34/// API.
35///
36/// Since `LocalStorage` is limited we store changes in a JSON map in one key.
37#[derive(Debug)]
38pub struct LocalStoragePersister {
39    storage: web_sys::Storage,
40    changes: HashMap<String, Vec<u8>>,
41    /// Base64 encoded peer_ids are used for the keys so they can be serialized to json.
42    sync_states: HashMap<String, Vec<u8>>,
43    document_key: String,
44    changes_key: String,
45    sync_states_key: String,
46    sizes: StoredSizes,
47}
48
49/// Possible errors from persisting.
50#[derive(Debug, thiserror::Error)]
51pub enum LocalStoragePersisterError {
52    /// Serde failure, converting the change/document into JSON.
53    #[error(transparent)]
54    SerdeError(#[from] serde_json::Error),
55    /// An underlying storage error.
56    #[error("storage error {0:?}")]
57    StorageError(wasm_bindgen::JsValue),
58}
59
60impl LocalStoragePersister {
61    /// Construct a new `LocalStoragePersister`.
62    pub fn new(
63        storage: web_sys::Storage,
64        document_key: String,
65        changes_key: String,
66        sync_states_key: String,
67    ) -> Result<Self, LocalStoragePersisterError> {
68        let changes = if let Some(stored) = storage
69            .get_item(&changes_key)
70            .map_err(LocalStoragePersisterError::StorageError)?
71        {
72            serde_json::from_str(&stored)?
73        } else {
74            HashMap::new()
75        };
76        let sync_states = if let Some(stored) = storage
77            .get_item(&sync_states_key)
78            .map_err(LocalStoragePersisterError::StorageError)?
79        {
80            serde_json::from_str(&stored)?
81        } else {
82            HashMap::new()
83        };
84        let document = if let Some(doc_string) = storage
85            .get_item(&document_key)
86            .map_err(LocalStoragePersisterError::StorageError)?
87        {
88            let doc = serde_json::from_str::<Vec<u8>>(&doc_string)?;
89            Some(doc)
90        } else {
91            None
92        };
93        let sizes = StoredSizes {
94            changes: changes.values().map(Vec::len).sum::<usize>() as u64,
95            document: document.unwrap_or_default().len() as u64,
96            sync_states: sync_states.values().map(Vec::len).sum::<usize>() as u64,
97        };
98        Ok(Self {
99            storage,
100            changes,
101            sync_states,
102            document_key,
103            changes_key,
104            sync_states_key,
105            sizes,
106        })
107    }
108}
109
110impl Persister for LocalStoragePersister {
111    type Error = LocalStoragePersisterError;
112
113    fn get_changes(&self) -> Result<Vec<Vec<u8>>, Self::Error> {
114        Ok(self.changes.values().cloned().collect())
115    }
116
117    fn insert_changes(&mut self, changes: Vec<(ActorId, u64, Vec<u8>)>) -> Result<(), Self::Error> {
118        for (a, s, c) in changes {
119            let key = make_key(&a, s);
120
121            self.sizes.changes += c.len() as u64;
122            if let Some(old) = self.changes.insert(key, c) {
123                self.sizes.changes -= old.len() as u64;
124            }
125        }
126        self.storage
127            .set_item(&self.changes_key, &serde_json::to_string(&self.changes)?)
128            .map_err(LocalStoragePersisterError::StorageError)?;
129        Ok(())
130    }
131
132    fn remove_changes(&mut self, changes: Vec<(&ActorId, u64)>) -> Result<(), Self::Error> {
133        let mut some_removal = false;
134        for (a, s) in changes {
135            let key = make_key(a, s);
136            if let Some(old) = self.changes.remove(&key) {
137                self.sizes.changes -= old.len() as u64;
138                some_removal = true;
139            }
140        }
141
142        if some_removal {
143            let s = serde_json::to_string(&self.changes)?;
144            self.storage
145                .set_item(&self.changes_key, &s)
146                .map_err(LocalStoragePersisterError::StorageError)?;
147        }
148        Ok(())
149    }
150
151    fn get_document(&self) -> Result<Option<Vec<u8>>, Self::Error> {
152        if let Some(doc_string) = self
153            .storage
154            .get_item(&self.document_key)
155            .map_err(LocalStoragePersisterError::StorageError)?
156        {
157            let doc = serde_json::from_str(&doc_string)?;
158            Ok(Some(doc))
159        } else {
160            Ok(None)
161        }
162    }
163
164    fn set_document(&mut self, data: Vec<u8>) -> Result<(), Self::Error> {
165        self.sizes.document = data.len() as u64;
166        let data = serde_json::to_string(&data)?;
167        self.storage
168            .set_item(&self.document_key, &data)
169            .map_err(LocalStoragePersisterError::StorageError)?;
170        Ok(())
171    }
172
173    fn get_sync_state(&self, peer_id: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
174        let peer_id = base64::engine::general_purpose::STANDARD.encode(peer_id);
175        Ok(self.sync_states.get(&peer_id).cloned())
176    }
177
178    fn set_sync_state(&mut self, peer_id: Vec<u8>, sync_state: Vec<u8>) -> Result<(), Self::Error> {
179        self.sizes.sync_states += sync_state.len() as u64;
180        let peer_id = base64::engine::general_purpose::STANDARD.encode(peer_id);
181        if let Some(old) = self.sync_states.insert(peer_id, sync_state) {
182            self.sizes.sync_states -= old.len() as u64;
183        }
184        self.storage
185            .set_item(
186                &self.sync_states_key,
187                &serde_json::to_string(&self.sync_states)?,
188            )
189            .map_err(LocalStoragePersisterError::StorageError)?;
190        Ok(())
191    }
192
193    fn remove_sync_states(&mut self, peer_ids: &[&[u8]]) -> Result<(), Self::Error> {
194        for peer_id in peer_ids {
195            let peer_id = base64::engine::general_purpose::STANDARD.encode(peer_id);
196            if let Some(old) = self.sync_states.remove(&peer_id) {
197                self.sizes.sync_states -= old.len() as u64;
198            }
199        }
200        self.storage
201            .set_item(
202                &self.sync_states_key,
203                &serde_json::to_string(&self.sync_states)?,
204            )
205            .map_err(LocalStoragePersisterError::StorageError)?;
206        Ok(())
207    }
208
209    fn get_peer_ids(&self) -> Result<Vec<Vec<u8>>, Self::Error> {
210        Ok(self
211            .sync_states
212            .keys()
213            .map(|key| {
214                base64::engine::general_purpose::STANDARD
215                    .decode(key)
216                    .expect("Failed to base64 decode they peer_id")
217            })
218            .collect())
219    }
220
221    fn sizes(&self) -> StoredSizes {
222        self.sizes.clone()
223    }
224
225    fn flush(&mut self) -> Result<usize, Self::Error> {
226        Ok(0)
227    }
228}
229
230/// Make a key from the `actor_id` and `sequence_number`.
231///
232/// Converts the `actor_id` to a string and appends the `sequence_number`.
233fn make_key(actor_id: &ActorId, seq: u64) -> String {
234    let mut key = actor_id.to_hex_string();
235    key.push_str(&seq.to_string());
236    key
237}