Skip to main content

kivis_fs/
repository.rs

1use kivis::Repository;
2use std::{fs, path::PathBuf};
3
4use crate::error::FileStoreError;
5
6/// A file-based storage implementation that stores each key-value pair as a separate file.
7///
8/// Uses CSV serialization with URL-encoded filenames for human-readable storage.
9/// Each record is stored in a `.dat` file within the configured directory.
10#[derive(Debug)]
11pub struct FileStore {
12    /// The directory where all data files are stored.
13    data_dir: PathBuf,
14}
15
16impl Repository for FileStore {
17    type K = String;
18    type V = String;
19    type Error = FileStoreError;
20
21    fn insert_entry(&mut self, key: &str, value: &str) -> Result<(), Self::Error> {
22        let file_path = self.key_to_filename(key);
23        fs::write(file_path, value)?;
24        Ok(())
25    }
26
27    fn get_entry(&self, key: &str) -> Result<Option<Self::V>, Self::Error> {
28        let file_path = self.key_to_filename(key);
29        match fs::read_to_string(file_path) {
30            Ok(data) => Ok(Some(data)),
31            Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
32            Err(e) => Err(e.into()),
33        }
34    }
35
36    fn remove_entry(&mut self, key: &str) -> Result<Option<Self::V>, Self::Error> {
37        let file_path = self.key_to_filename(key);
38        match fs::read_to_string(&file_path) {
39            Ok(data) => {
40                fs::remove_file(file_path)?;
41                Ok(Some(data))
42            }
43            Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
44            Err(e) => Err(e.into()),
45        }
46    }
47
48    fn scan_range(
49        &self,
50        range: std::ops::Range<Self::K>,
51    ) -> Result<impl Iterator<Item = Result<Self::K, Self::Error>>, Self::Error> {
52        let entries = fs::read_dir(&self.data_dir)?;
53
54        let mut keys: Vec<String> = Vec::new();
55        for entry in entries.flatten() {
56            if let Some(filename) = entry.file_name().to_str()
57                && let Some(key) = self.filename_to_key(filename)
58                && key >= range.start
59                && key < range.end
60            {
61                keys.push(key);
62            }
63        }
64
65        keys.sort();
66        Ok(keys.into_iter().rev().map(Ok))
67    }
68}
69
70impl FileStore {
71    /// Creates a new FileStore instance at the specified directory.
72    ///
73    /// Creates the directory if it doesn't exist.
74    ///
75    /// # Errors
76    ///
77    /// Returns an error if the directory cannot be created.
78    pub fn new(data_dir: impl Into<PathBuf>) -> std::io::Result<Self> {
79        let data_dir = data_dir.into();
80        fs::create_dir_all(&data_dir)?;
81        Ok(Self { data_dir })
82    }
83
84    /// Converts a key string to a filesystem path.
85    fn key_to_filename(&self, key: &str) -> PathBuf {
86        self.data_dir.join(format!("{key}.dat"))
87    }
88
89    /// Extracts the key from a filename by removing the `.dat` extension.
90    fn filename_to_key(&self, filename: &str) -> Option<String> {
91        filename.strip_suffix(".dat").map(String::from)
92    }
93}