opendal/services/mini_moka/
backend.rs1use std::sync::Arc;
19use std::time::Duration;
20
21use log::debug;
22
23use super::MINI_MOKA_SCHEME;
24use super::config::MiniMokaConfig;
25use super::core::*;
26use super::delete::MiniMokaDeleter;
27use super::lister::MiniMokaLister;
28use super::writer::MiniMokaWriter;
29use crate::raw::*;
30use crate::*;
31
32#[doc = include_str!("docs.md")]
34#[derive(Debug, Default)]
35pub struct MiniMokaBuilder {
36 pub(super) config: MiniMokaConfig,
37}
38
39impl MiniMokaBuilder {
40 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub fn max_capacity(mut self, v: u64) -> Self {
49 if v != 0 {
50 self.config.max_capacity = Some(v);
51 }
52 self
53 }
54
55 pub fn time_to_live(mut self, v: Duration) -> Self {
59 if !v.is_zero() {
60 self.config.time_to_live = Some(format!("{}s", v.as_secs()));
61 }
62 self
63 }
64
65 pub fn time_to_idle(mut self, v: Duration) -> Self {
69 if !v.is_zero() {
70 self.config.time_to_idle = Some(format!("{}s", v.as_secs()));
71 }
72 self
73 }
74
75 pub fn root(mut self, path: &str) -> Self {
77 self.config.root = if path.is_empty() {
78 None
79 } else {
80 Some(path.to_string())
81 };
82
83 self
84 }
85}
86
87impl Builder for MiniMokaBuilder {
88 type Config = MiniMokaConfig;
89
90 fn build(self) -> Result<impl Access> {
91 debug!("backend build started: {:?}", &self);
92
93 let mut builder: mini_moka::sync::CacheBuilder<String, MiniMokaValue, _> =
94 mini_moka::sync::Cache::builder();
95
96 builder = builder.weigher(|k, v| (k.len() + v.content.len()) as u32);
98
99 if let Some(v) = self.config.max_capacity {
100 builder = builder.max_capacity(v);
101 }
102 if let Some(value) = self.config.time_to_live.as_deref() {
103 let duration = signed_to_duration(value)?;
104 builder = builder.time_to_live(duration);
105 }
106 if let Some(value) = self.config.time_to_idle.as_deref() {
107 let duration = signed_to_duration(value)?;
108 builder = builder.time_to_idle(duration);
109 }
110
111 let cache = builder.build();
112
113 let root = normalize_root(self.config.root.as_deref().unwrap_or("/"));
114
115 let core = Arc::new(MiniMokaCore { cache });
116
117 debug!("backend build finished: {root}");
118 Ok(MiniMokaBackend::new(core, root))
119 }
120}
121
122#[derive(Debug)]
123struct MiniMokaBackend {
124 core: Arc<MiniMokaCore>,
125 root: String,
126}
127
128impl MiniMokaBackend {
129 fn new(core: Arc<MiniMokaCore>, root: String) -> Self {
130 Self { core, root }
131 }
132}
133
134impl Access for MiniMokaBackend {
135 type Reader = Buffer;
136 type Writer = MiniMokaWriter;
137 type Lister = oio::HierarchyLister<MiniMokaLister>;
138 type Deleter = oio::OneShotDeleter<MiniMokaDeleter>;
139
140 fn info(&self) -> Arc<AccessorInfo> {
141 let info = AccessorInfo::default();
142 info.set_scheme(MINI_MOKA_SCHEME)
143 .set_root(&self.root)
144 .set_native_capability(Capability {
145 stat: true,
146 read: true,
147 write: true,
148 write_can_empty: true,
149 delete: true,
150 list: true,
151
152 ..Default::default()
153 });
154
155 Arc::new(info)
156 }
157
158 async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
159 let p = build_abs_path(&self.root, path);
160
161 match self.core.get(&p) {
163 Some(value) => {
164 let mut metadata = value.metadata.clone();
165 if p.ends_with('/') {
166 metadata.set_mode(EntryMode::DIR);
167 } else {
168 metadata.set_mode(EntryMode::FILE);
169 }
170 Ok(RpStat::new(metadata))
171 }
172 None => {
173 if p.ends_with('/') {
174 let is_prefix = self
175 .core
176 .cache
177 .iter()
178 .any(|entry| entry.key().starts_with(&p) && entry.key() != &p);
179
180 if is_prefix {
181 let mut metadata = Metadata::default();
182 metadata.set_mode(EntryMode::DIR);
183 return Ok(RpStat::new(metadata));
184 }
185 }
186
187 Err(Error::new(ErrorKind::NotFound, "path not found"))
188 }
189 }
190 }
191
192 async fn read(&self, path: &str, op: OpRead) -> Result<(RpRead, Self::Reader)> {
193 let p = build_abs_path(&self.root, path);
194
195 match self.core.get(&p) {
196 Some(value) => {
197 let range = op.range();
198
199 if range.is_full() {
201 return Ok((RpRead::new(), value.content));
202 }
203
204 let offset = range.offset() as usize;
205 if offset >= value.content.len() {
206 return Err(Error::new(
207 ErrorKind::RangeNotSatisfied,
208 "range start offset exceeds content length",
209 ));
210 }
211
212 let size = range.size().map(|s| s as usize);
213 let end = size.map_or(value.content.len(), |s| {
214 (offset + s).min(value.content.len())
215 });
216 let sliced_content = value.content.slice(offset..end);
217
218 Ok((RpRead::new(), sliced_content))
219 }
220 None => Err(Error::new(ErrorKind::NotFound, "path not found")),
221 }
222 }
223
224 async fn write(&self, path: &str, op: OpWrite) -> Result<(RpWrite, Self::Writer)> {
225 let p = build_abs_path(&self.root, path);
226 let writer = MiniMokaWriter::new(self.core.clone(), p, op);
227 Ok((RpWrite::new(), writer))
228 }
229
230 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
231 let deleter =
232 oio::OneShotDeleter::new(MiniMokaDeleter::new(self.core.clone(), self.root.clone()));
233 Ok((RpDelete::default(), deleter))
234 }
235
236 async fn list(&self, path: &str, op: OpList) -> Result<(RpList, Self::Lister)> {
237 let p = build_abs_path(&self.root, path);
238
239 let mini_moka_lister = MiniMokaLister::new(self.core.clone(), self.root.clone(), p);
240 let lister = oio::HierarchyLister::new(mini_moka_lister, path, op.recursive());
241
242 Ok((RpList::default(), lister))
243 }
244}