gravitydb_filestore/
lib.rs1use 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}