1use kvdb::{DBKeyValue, DBOp, DBTransaction, DBValue, KeyValueDB};
2use kvdb_memorydb::InMemory;
3use std::fs;
4use std::io;
5use std::path::{Path, PathBuf};
6
7#[derive(Default)]
10pub struct InFile {
11 path: String,
12 in_memory: InMemory,
13}
14impl InFile {
15 fn col_path(&self, col: u32) -> PathBuf {
16 let mut path = PathBuf::from(&self.path);
17 path.push(col.to_string());
18 path
19 }
20 fn key2file(&self, col: u32, key: &[u8]) -> PathBuf {
21 let mut path = PathBuf::from(&self.path);
22 path.push(col.to_string());
23 path.push(format!("0x{}", hex::encode(key)));
24 path
25 }
26 fn file2key(path: &Path) -> Option<Vec<u8>> {
27 if let Some(name) = path.file_name() {
28 let name = name.to_string_lossy();
29 if let Ok(key) = hex::decode(&name[2..]) {
30 return Some(key);
31 }
32 }
33 None
34 }
35 pub fn open<P: AsRef<Path>>(path: P, num_cols: u32) -> Result<InFile, io::Error> {
36 let in_memory = kvdb_memorydb::create(num_cols);
37 let mut txn = DBTransaction::new();
38 for col in 0..num_cols {
39 let col_dir = path.as_ref().join(col.to_string());
40 fs::create_dir_all(&col_dir)?;
41 for entry in fs::read_dir(col_dir)? {
42 let file = entry?.path();
43 if file.is_file() {
44 if let Some(key) = Self::file2key(&file) {
45 let value = fs::read(file)?;
46 txn.put_vec(col, &key, value);
47 }
48 }
49 }
50 }
51
52 in_memory.write(txn)?;
53 Ok(InFile {
54 path: path.as_ref().to_string_lossy().into_owned(),
55 in_memory,
56 })
57 }
58}
59
60impl KeyValueDB for InFile {
61 fn get(&self, col: u32, key: &[u8]) -> io::Result<Option<DBValue>> {
62 self.in_memory.get(col, key)
63 }
64
65 fn get_by_prefix(&self, col: u32, prefix: &[u8]) -> io::Result<Option<Vec<u8>>> {
66 self.in_memory.get_by_prefix(col, prefix)
67 }
68
69 fn write(&self, transaction: DBTransaction) -> io::Result<()> {
70 for op in &transaction.ops {
71 match op {
72 DBOp::Insert { col, key, value } => {
73 let file = self.key2file(*col, key);
74 fs::write(file, value)?;
75 }
76 DBOp::Delete { col, key } => {
77 let file = self.key2file(*col, key);
78 if file.is_file() {
79 fs::remove_file(file)?;
80 }
81 }
82 DBOp::DeletePrefix { col, prefix } => {
83 let col_dir = self.col_path(*col);
84 if prefix.is_empty() {
85 for entry in fs::read_dir(col_dir)? {
86 let file = entry?.path();
87 if file.is_file() {
88 fs::remove_file(file)?;
89 }
90 }
91 } else {
92 for entry in fs::read_dir(col_dir)? {
93 let file = entry?.path();
94 if file.is_file() {
95 if let Some(key) = Self::file2key(&file) {
96 if key.starts_with(&prefix) {
97 fs::remove_file(file)?;
98 }
99 }
100 }
101 }
102 }
103 }
104 }
105 }
106 self.in_memory.write(transaction)
107 }
108
109 fn iter<'a>(&'a self, col: u32) -> Box<dyn Iterator<Item = io::Result<DBKeyValue>> + 'a> {
111 self.in_memory.iter(col)
112 }
113
114 fn iter_with_prefix<'a>(
116 &'a self,
117 col: u32,
118 prefix: &'a [u8],
119 ) -> Box<dyn Iterator<Item = io::Result<DBKeyValue>> + 'a> {
120 self.in_memory.iter_with_prefix(col, prefix)
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::InFile;
127 use kvdb_shared_tests as st;
128 use std::time::SystemTime;
129 use std::{fs, io};
130
131 fn timestramp() -> u64 {
132 let dur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH);
133 dur.unwrap().as_nanos() as u64
134 }
135 #[test]
136 fn get_fails_with_non_existing_column() -> io::Result<()> {
137 let db = InFile::open(format!("{:?}", timestramp()), 1)?;
138 st::test_get_fails_with_non_existing_column(&db)?;
139 fs::remove_dir_all(&db.path)
140 }
141
142 #[test]
143 fn put_and_get() -> io::Result<()> {
144 let db = InFile::open(format!("{:?}", timestramp()), 1)?;
145 st::test_put_and_get(&db)?;
146 fs::remove_dir_all(&db.path)
147 }
148
149 #[test]
150 fn delete_and_get() -> io::Result<()> {
151 let db = InFile::open(format!("{:?}", timestramp()), 1)?;
152 st::test_delete_and_get(&db)?;
153 fs::remove_dir_all(&db.path)
154 }
155
156 #[test]
157 fn delete_prefix() -> io::Result<()> {
158 let db = InFile::open(format!("{:?}", timestramp()), st::DELETE_PREFIX_NUM_COLUMNS)?;
159 st::test_delete_prefix(&db)?;
160 fs::remove_dir_all(&db.path)
161 }
162
163 #[test]
164 fn iter() -> io::Result<()> {
165 let db = InFile::open(format!("{:?}", timestramp()), 1)?;
166 st::test_iter(&db)?;
167 fs::remove_dir_all(&db.path)
168 }
169
170 #[test]
171 fn iter_with_prefix() -> io::Result<()> {
172 let db = InFile::open(format!("{:?}", timestramp()), 1)?;
173 st::test_iter_with_prefix(&db)?;
174 fs::remove_dir_all(&db.path)
175 }
176
177 #[test]
178 fn complex() -> io::Result<()> {
179 let db = InFile::open(format!("{:?}", timestramp()), 1)?;
180 st::test_complex(&db)?;
181 fs::remove_dir_all(&db.path)
182 }
183}