1use std::collections::HashMap;
5use std::fs::{create_dir_all, File, OpenOptions};
6use std::io::{Read, Seek, Write};
7use std::path::{Path, PathBuf};
8
9use anyhow::{bail, Result};
10use serde::{Deserialize, Serialize};
11
12#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
13pub struct Index {
14 pub offset: u64,
15 pub length: u64,
16}
17
18pub struct Store {
19 pub indexes: HashMap<String, Index>,
20 pub empties: Vec<Index>,
21 pub blob_file: File,
22 pub empty_file: File,
23 pub db_file: File,
24}
25fn open_file<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
27 OpenOptions::new()
28 .read(true)
29 .write(true)
30 .open(path.as_ref())
31}
32
33fn create_file<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
35 OpenOptions::new()
36 .read(true)
37 .write(true)
38 .create(true)
39 .truncate(true)
40 .open(path.as_ref())
41}
42
43impl Store {
44 pub fn new(directory: PathBuf) -> Result<Self> {
46 if !directory.exists() {
47 create_dir_all(&directory)?;
48 } else if !directory.is_dir() {
49 bail!("{:?} is not a directory", directory);
50 }
51
52 let blob_file = create_file(directory.join("mammon_blobs.bin"))?;
53 let empty_file = create_file(directory.join("mammon_empties.cbor"))?;
54 let db_file = create_file(directory.join("mammon.cbor"))?;
55
56 Ok(Self {
57 indexes: HashMap::new(),
58 empties: vec![],
59 blob_file,
60 empty_file,
61 db_file,
62 })
63 }
64 pub fn open(directory: PathBuf) -> Result<Self> {
66 if !directory.exists() {
67 bail!("{:?} does not exist", directory);
68 }
69
70 let blob_file = open_file(directory.join("mammon_blobs.bin"))?;
71 let empty_file = open_file(directory.join("mammon_empties.cbor"))?;
72 let db_file = open_file(directory.join("mammon.cbor"))?;
73
74 let indexes: HashMap<String, Index> = ciborium::from_reader(&db_file)?;
75 let empties: Vec<Index> = ciborium::from_reader(&empty_file)?;
76
77 Ok(Self {
78 indexes,
79 empties,
80 blob_file,
81 empty_file,
82 db_file,
83 })
84 }
85
86 pub fn store(&mut self, key: impl ToString, val: Vec<u8>) -> Result<()> {
88 let offset = self.blob_file.seek(std::io::SeekFrom::End(0))?;
89 let length = val.len() as u64;
90
91 self.blob_file.write_all(val.as_slice())?;
92
93 self.indexes
94 .insert(key.to_string(), Index { offset, length });
95
96 ciborium::into_writer(&self.indexes, &self.db_file)?;
97
98 Ok(())
99 }
100
101 pub fn retrieve(&mut self, key: impl ToString) -> Result<Vec<u8>> {
103 let index = self
104 .indexes
105 .get(&key.to_string())
106 .ok_or_else(|| anyhow::anyhow!("key not found"))?;
107
108 self.blob_file
109 .seek(std::io::SeekFrom::Start(index.offset))?;
110 let mut buf = vec![0; index.length as usize];
111 self.blob_file.read_exact(&mut buf)?;
112
113 return Ok(buf.clone());
114 }
115
116 pub fn delete(&mut self, key: impl ToString) -> Result<()> {
118 let index = self
119 .indexes
120 .get(&key.to_string())
121 .ok_or_else(|| anyhow::anyhow!("key not found"))?;
122
123 self.empties.push(Index {
124 offset: index.offset,
125 length: index.length,
126 }); self.indexes.remove(&key.to_string());
129
130 ciborium::into_writer(&self.indexes, &self.db_file)?;
131 ciborium::into_writer(&self.empties, &self.empty_file)?;
132
133 Ok(())
134 }
135
136 }