Skip to main content

gravitydb_filestore/
lib.rs

1use vfs::{MemoryFS, PhysicalFS, VfsError, VfsPath, VfsResult};
2use gravitydb::KVStore;
3use std::path::Path;
4use thiserror::Error;
5pub mod cli_helpers;
6
7pub struct FsKvStore {
8  base_path: VfsPath,
9}
10
11impl KVStore<FileStoreError> for FsKvStore
12{
13  fn create_bucket(&mut self, key: &[u8]) -> Result<(), FileStoreError> {
14    Ok(self.key_to_path(key)?.create_dir_all()?)
15  }
16
17  fn delete_record(&mut self, key: &[u8]) -> Result<(), FileStoreError> {
18    Ok(self.key_to_path(key)?.remove_file()?)
19  }
20
21  fn store_record(&mut self, key: &[u8], value: &[u8]) -> Result<(), FileStoreError> {
22    Ok(self.key_to_path(key)?.create_file()?.write_all(value)?)
23  }
24
25  fn fetch_record(&self, key: &[u8]) -> Result<Vec<u8>, FileStoreError> {
26    let mut content = vec![];
27    self.key_to_path(key)?.open_file()?.read_to_end(&mut content)?;
28    Ok(content)
29  }
30
31  fn list_records(&self, from: &[u8], to: &[u8]) -> Result<Vec<Vec<u8>>, FileStoreError> {
32    let to_path = if to.len() != 0 {
33      self.key_to_path(to)?
34    } else {
35      let mut to: Vec<u8> = from.to_vec();
36      *to.last_mut().unwrap() += 1;
37      self.key_to_path(&to)?
38    };
39    let from_path = self.key_to_path(from)?;
40    let base = match longest_shared_path(&from_path, &to_path) {
41      Some(base) => base,
42      None => return Err(FileStoreError::InvalidParameters),
43    };
44    Ok(list_files(&base)?
45      .into_iter()
46      .filter(|key| **key < *from || **key > *to)
47      .collect())
48  }
49
50  fn exists(&self, key: &[u8]) -> Result<bool, FileStoreError> {
51    Ok(self.key_to_path(key)?.exists()?)
52  }
53}
54
55impl FsKvStore {
56  fn key_to_path(&self, key: &[u8]) -> Result<VfsPath, FileStoreError> {
57    let mut path = self.base_path.clone();
58    for component in String::from_utf8_lossy(key).split("/") {
59      path = path.join(component)?;
60    }
61    Ok(path)
62  }
63
64  pub fn open(path: &Path) -> Result<Self, FileStoreError> {
65    let root = VfsPath::new(PhysicalFS::new(path.to_path_buf()));
66
67    if !root.is_dir()? {
68      return Err(FileStoreError::MalformedDB);
69    }
70
71    let check_dirs = ["nodes", "edges", "props", "indexes"];
72    for dir in &check_dirs {
73      if !root.join(dir)?.is_dir()? {
74        return Err(FileStoreError::MalformedDB);
75      }
76    }
77
78    Ok(FsKvStore {
79      base_path: root,
80    })
81  }
82
83  pub fn init(path: &Path) -> Result<Self, FileStoreError> {
84    let root = VfsPath::new(PhysicalFS::new(path.to_path_buf()));
85    if !root.is_dir()? {
86      if root.exists()? {
87        return Err(FileStoreError::MalformedDB);
88      } else {
89        root.create_dir_all()?;
90      }
91    }
92
93    let check_dirs = ["nodes", "edges", "props", "indexes"];
94    for dir in &check_dirs {
95      root.join(dir)?.create_dir_all()?;
96    }
97
98    Ok(FsKvStore {
99      base_path: root,
100    })
101  }
102
103  pub fn from_memory() -> Result<Self, FileStoreError> {
104    let root = VfsPath::new(MemoryFS::new());
105
106    let check_dirs = ["nodes", "edges", "props", "indexes"];
107    for dir in &check_dirs {
108      root.join(dir)?.create_dir_all()?;
109    }
110
111    Ok(FsKvStore { base_path: root })
112  }
113
114  pub fn get_root(self) -> VfsPath {
115    self.base_path
116  }
117}
118
119#[derive(Error, Debug)]
120pub enum FileStoreError {
121  #[error("wrongly formatted database at path TODO")]
122  MalformedDB,
123  #[error("io error")]
124  Io { #[from] source: std::io::Error },
125  #[error("vfs error")]
126  Vfs { #[from] source: VfsError },
127  #[error("invalid input parameters")]
128  InvalidParameters,
129}
130
131fn list_files(dir: &VfsPath) -> VfsResult<Vec<Vec<u8>>> {
132  let mut result = vec![];
133
134  if dir.is_dir()? {
135    for path in dir.read_dir()? {
136      if path.is_dir()? {
137        result.append(&mut list_files(&path)?);
138      } else {
139        let path = path.as_str();
140        let path = match path.strip_prefix("/") {
141          Some(path) => path,
142          None => path,
143        };
144        result.push(path.as_bytes().to_vec());
145      }
146    }
147  }
148  Ok(result)
149}
150
151fn longest_shared_path(path1: &VfsPath, path2: &VfsPath) -> Option<VfsPath> {
152  let s1 = path1.as_str();
153  let s2 = path2.as_str();
154
155  let mut shared = String::new();
156
157  for (c1, c2) in s1.chars().zip(s2.chars()) {
158    if c1 == c2 {
159      shared.push(c1);
160    } else {
161      break;
162    }
163  }
164
165  if !shared.is_empty() {
166    let shared = path1.root().join(shared).ok()?;
167    if shared.is_dir().ok()? {
168      Some(shared)
169    } else {
170      Some(shared.parent())
171    }
172  } else {
173    None
174  }
175}