rusty_store/
storage.rs

1use ron::ser::PrettyConfig;
2use serde::{Deserialize, Serialize};
3use std::fmt::Debug;
4use std::fs::{self, File, OpenOptions};
5use std::io::{Read, Write};
6use std::path::PathBuf;
7use thiserror::Error;
8
9use log::debug;
10use log::info;
11use log::log;
12use log::warn;
13
14use crate::manager::StoreManager;
15
16#[derive(Error, Debug)]
17pub enum StoreError {
18    #[error("RON parsing error: {0}")]
19    RonParse(#[source] ron::error::SpannedError),
20
21    #[error("RON error: {0}")]
22    Ron(#[source] ron::error::Error),
23
24    #[error("Failed to open file: {0}")]
25    FileOpen(#[source] std::io::Error),
26
27    #[error("Failed to read from file: {0}")]
28    Read(#[source] std::io::Error),
29
30    #[error("Failed to create directory: {0}")]
31    CreateDir(#[source] std::io::Error),
32
33    #[error("Failed to write to file: {0}")]
34    Write(#[source] std::io::Error),
35}
36
37#[derive(Debug, Default)]
38pub enum StoringType {
39    Cache,
40    #[default]
41    Data,
42    Config,
43}
44
45pub trait Storing: Serialize + for<'de> Deserialize<'de> + Default {
46    fn store_type() -> StoringType {
47        StoringType::default()
48    }
49}
50
51/// `StoreHandle` acts as a container that holds store data in memory and provides methods to access
52/// and modify this data. The `StoreHandle` does not manage storage directly but facilitates the read and
53/// write operations by holding the data and its identifier.
54#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
55pub struct StoreHandle<T> {
56    store_id: String,
57    store: T,
58}
59
60impl<T: Storing> StoreHandle<T> {
61    pub fn new(store_id: &str) -> Self {
62        Self {
63            store: T::default(),
64            store_id: store_id.to_owned(),
65        }
66    }
67
68    fn set_store(&mut self, store: T) {
69        debug!("Setting store with id: {}", self.store_id);
70        self.store = store;
71    }
72
73    /// Returns a mutable reference to the stored data.
74    pub fn get_store_mut(&mut self) -> &mut T {
75        &mut self.store
76    }
77
78    /// Returns a reference to the stored data.
79    pub fn get_store(&self) -> &T {
80        &self.store
81    }
82
83    pub fn store_id(&self) -> &str {
84        &self.store_id
85    }
86}
87
88/// Handles file system paths for reading from and writing to data storage.
89///
90/// The `Storage` struct provides a way to manage file paths used for storing data in different locations.
91/// It simplifies the process of accessing and modifying data by providing methods for these operations.
92///
93/// # Example
94///
95/// ```
96/// use rusty_store::{Storage, StoreHandle, Storing};
97/// use serde::{Deserialize, Serialize};
98///
99/// #[derive(Serialize, Deserialize, Default, Storing)]
100/// pub struct MyStore {
101///     pub count: u32,
102/// }
103///
104/// impl MyStore {
105///     fn increment_count(&mut self) {
106///         self.count += 1;
107///     }
108/// }
109///
110///
111/// // Initialize the Storage with the defaults
112/// let storage = Storage::new("APP_ID");
113///
114/// // Create a handle for managing the store data.
115/// let mut handle = StoreHandle::<MyStore>::new("my_store_id");
116///
117/// // Read existing store from storage
118/// storage
119///     .read(&mut handle)
120///     .expect("Failed to read from storage");
121///
122/// // Modify the store data
123/// let counter = handle.get_store_mut();
124///
125/// counter.increment_count();
126/// counter.increment_count();
127/// counter.increment_count();
128///
129/// // Write changes to disk
130/// storage
131///     .write(&mut handle)
132///     .expect("Failed to write to storage");
133///
134/// let counter = handle.get_store();
135///
136/// println!("Count: {}", counter.count);
137
138/// ```
139#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
140pub struct Storage {
141    cache_dir: PathBuf,
142    data_dir: PathBuf,
143    config_dir: PathBuf,
144}
145
146impl Storage {
147    /// Creates a new `Storage` instance by obtaining the paths for cache, data, and configuration directories.
148    ///
149    /// # Panics
150    ///
151    /// - Panics if the cache directory, data directory, or configuration directory path cannot be determined.
152    pub fn new(app_id: &str) -> Self {
153        Self {
154            data_dir: dirs::data_dir()
155                .expect("Failed to determine cache directory path")
156                .join(app_id),
157            config_dir: dirs::config_dir()
158                .expect("Failed to determine data directory path")
159                .join(app_id),
160            cache_dir: dirs::cache_dir()
161                .expect("Failed to determine configuration directory path")
162                .join(app_id),
163        }
164    }
165
166    /// Creates a new `Storage` instance with specific cache, data and config paths
167    pub fn from_dirs(cache_dir: PathBuf, data_dir: PathBuf, config_dir: PathBuf) -> Self {
168        Self {
169            cache_dir,
170            data_dir,
171            config_dir,
172        }
173    }
174
175    /// Returns a new StoreManager of type `T` with the given `store_id`
176    pub fn new_manager<T: Storing>(&self, store_id: &str) -> Result<StoreManager<T>, StoreError> {
177        StoreManager::<T>::new(self, store_id)
178    }
179
180    /// Returns a new Handle of type `T` with the given `store_id`
181    pub fn new_handle<T: Storing>(&self, store_id: &str) -> StoreHandle<T> {
182        StoreHandle::<T>::new(store_id)
183    }
184
185    /// Reads the store from a file and updates the provided `StoreHandle`.
186    /// If the file does not exist, it creates a default store if a default is available.
187    ///
188    /// # Example
189    ///
190    /// ```
191    /// let storage = Storage::new(app);
192    /// let handle: StoreHandle<MyStore> = StoreHandle::new("my_store_id");
193    ///
194    /// storage.read(&mut handle).expect("Failed to read store");
195    ///
196    /// ```
197    pub fn read<T: Storing>(&self, handle: &mut StoreHandle<T>) -> Result<(), StoreError> {
198        debug!("Reading store with id: {}", handle.store_id());
199        self.open_file::<T, _>(
200            |file, handle| {
201                let store = Self::read_string(file).map_err(StoreError::Read)?;
202                let store_data: T = ron::from_str(&store).map_err(StoreError::RonParse)?;
203
204                handle.set_store(store_data);
205
206                info!("Successfully read store with id: {}", handle.store_id());
207                Ok(())
208            },
209            handle,
210        )
211    }
212
213    /// Writes the current store `T` from the provided `StoreHandle` to a file.
214    /// If the file does not exist, it creates a default store if a default is available.
215    ///
216    /// # Example
217    ///
218    /// ```
219    /// let storage = Storage::new(app);
220    /// let handle: StoreHandle<MyStore> = StoreHandle::new("my_store_id");
221    ///
222    /// storage.write(&mut handle).expect("Failed to read store");
223    ///
224    /// ```
225    pub fn write<T: Storing>(&self, handle: &mut StoreHandle<T>) -> Result<(), StoreError> {
226        debug!("Writing store with id: {}", handle.store_id());
227        self.open_file::<T, _>(
228            |file: &mut File, handle| {
229                let store = handle.get_store_mut();
230
231                let str =
232                    ron::ser::to_string_pretty(&store, PrettyConfig::new().compact_arrays(true))
233                        .map_err(StoreError::Ron)?;
234
235                file.set_len(0).map_err(StoreError::Write)?;
236                file.write_all(str.as_bytes()).map_err(StoreError::Write)?;
237                file.flush().map_err(StoreError::Write)?;
238
239                info!("Successfully wrote store with id: {}", handle.store_id());
240                Ok(())
241            },
242            handle,
243        )
244    }
245
246    /// Opens the file for reading or writing. If the file does not exist, it attempts
247    /// to create a default store if a default is provided.
248    ///
249    /// # Example
250    ///
251    /// ```rust
252    /// storage.open_file::<MyStore, _>(|file, handle| {
253    ///     // Perform file operations
254    ///     Ok(())
255    /// }, &mut handle);
256    /// ```
257    fn open_file<T, F>(
258        &self,
259        mut operation: F,
260        handle: &mut StoreHandle<T>,
261    ) -> Result<(), StoreError>
262    where
263        T: Storing,
264        F: FnMut(&mut File, &mut StoreHandle<T>) -> Result<(), StoreError>,
265    {
266        let mut dir_path = self.dir_path::<T>();
267        dir_path.push(handle.store_id()); // i don't like this
268
269        debug!("Opening file at path: {:?}", dir_path);
270
271        match OpenOptions::new()
272            .read(true)
273            .write(true)
274            .create(false)
275            .truncate(false)
276            .open(&dir_path)
277        {
278            Ok(mut config) => {
279                debug!("File opened successfully at path: {:?}", dir_path);
280                operation(&mut config, handle)
281            }
282            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
283                warn!(
284                    "File not found at path: {:?}, creating default store",
285                    dir_path
286                );
287                self.store_default::<T>(dir_path)?;
288                self.open_file(operation, handle)
289            }
290            Err(err) => {
291                warn!(
292                    "Failed to open file at path: {:?}, error: {:?}",
293                    dir_path, err
294                );
295                Err(StoreError::FileOpen(err))
296            }
297        }
298    }
299
300    fn store_default<T: Storing>(&self, path: PathBuf) -> Result<(), StoreError> {
301        debug!("Storing default configuration at path: {:?}", path);
302        if let Some(parent) = path.parent() {
303            fs::create_dir_all(parent).map_err(StoreError::CreateDir)?;
304            info!("Created directory for path: {:?}", parent);
305        }
306
307        let default_store = T::default();
308        let str = ron::ser::to_string_pretty(&default_store, PrettyConfig::new())
309            .map_err(StoreError::Ron)?;
310        fs::write(&path, str).map_err(StoreError::Write)?;
311        info!("Default store written at path: {:?}", &path);
312
313        Ok(())
314    }
315
316    fn dir_path<T: Storing>(&self) -> PathBuf {
317        let path = match T::store_type() {
318            StoringType::Cache => self.cache_dir.clone(),
319            StoringType::Data => self.data_dir.clone(),
320            StoringType::Config => self.config_dir.clone(),
321        };
322        debug!(
323            "Resolved directory path for store type: {:?} to path: {:?}",
324            T::store_type(),
325            path
326        );
327        path
328    }
329
330    fn read_string(mut file: &File) -> Result<String, std::io::Error> {
331        let mut buf = String::new();
332        file.read_to_string(&mut buf)?;
333        debug!("Read string from file, length: {}", buf.len());
334        Ok(buf)
335    }
336}