Skip to main content

opendal_service_mini_moka/
backend.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::sync::Arc;
19
20use log::debug;
21use opendal_core::raw::*;
22use opendal_core::*;
23
24use super::MINI_MOKA_SCHEME;
25use super::config::MiniMokaConfig;
26use super::core::*;
27use super::deleter::MiniMokaDeleter;
28use super::lister::MiniMokaLister;
29use super::reader::*;
30use super::writer::MiniMokaWriter;
31
32/// [mini-moka](https://github.com/moka-rs/mini-moka) backend support.
33#[doc = include_str!("docs.md")]
34#[derive(Debug, Default)]
35pub struct MiniMokaBuilder {
36    pub(super) config: MiniMokaConfig,
37}
38
39impl MiniMokaBuilder {
40    /// Create a [`MiniMokaBuilder`] with default configuration.
41    pub fn new() -> Self {
42        Self::default()
43    }
44
45    /// Sets the max capacity of the cache.
46    ///
47    /// Refer to [`mini-moka::sync::CacheBuilder::max_capacity`](https://docs.rs/mini-moka/latest/mini_moka/sync/struct.CacheBuilder.html#method.max_capacity)
48    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    /// Sets the time to live of the cache.
56    ///
57    /// Refer to [`mini-moka::sync::CacheBuilder::time_to_live`](https://docs.rs/mini-moka/latest/mini_moka/sync/struct.CacheBuilder.html#method.time_to_live)
58    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    /// Sets the time to idle of the cache.
66    ///
67    /// Refer to [`mini-moka::sync::CacheBuilder::time_to_idle`](https://docs.rs/mini-moka/latest/mini_moka/sync/struct.CacheBuilder.html#method.time_to_idle)
68    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    /// Set root path of this backend
76    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 Service> {
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        // Use entries' bytes as capacity weigher.
97        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, Clone)]
123pub(crate) struct MiniMokaBackend {
124    pub(crate) core: Arc<MiniMokaCore>,
125    pub(crate) root: String,
126    capability: Capability,
127}
128
129impl MiniMokaBackend {
130    fn new(core: Arc<MiniMokaCore>, root: String) -> Self {
131        let capability = Capability {
132            stat: true,
133            read: true,
134            write: true,
135            write_can_empty: true,
136            delete: true,
137            list: true,
138
139            ..Default::default()
140        };
141
142        Self {
143            core,
144            root,
145            capability,
146        }
147    }
148}
149
150impl Service for MiniMokaBackend {
151    type Reader = oio::StreamReader<MiniMokaReader>;
152    type Writer = MiniMokaWriter;
153    type Lister = oio::HierarchyLister<MiniMokaLister>;
154    type Deleter = oio::OneShotDeleter<MiniMokaDeleter>;
155    type Copier = ();
156    type Composer = ();
157
158    fn info(&self) -> ServiceInfo {
159        ServiceInfo::new(MINI_MOKA_SCHEME, &self.root, "")
160    }
161
162    fn capability(&self) -> Capability {
163        self.capability
164    }
165
166    async fn create_dir(
167        &self,
168        _ctx: &OperationContext,
169        _path: &str,
170        _args: OpCreateDir,
171    ) -> Result<RpCreateDir> {
172        Err(Error::new(
173            ErrorKind::Unsupported,
174            "operation is not supported",
175        ))
176    }
177
178    async fn stat(&self, _ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
179        let p = build_abs_path(&self.root, path);
180
181        // Check if path exists directly in cache
182        match self.core.get(&p) {
183            Some(value) => {
184                let mut metadata = value.metadata.clone().into_builder();
185                if p.ends_with('/') {
186                    metadata.set_dir();
187                } else {
188                    metadata.set_file(value.metadata.content_length());
189                }
190                Ok(RpStat::new(metadata.build()))
191            }
192            None => {
193                if p.ends_with('/') {
194                    let is_prefix = self
195                        .core
196                        .cache
197                        .iter()
198                        .any(|entry| entry.key().starts_with(&p) && entry.key() != &p);
199
200                    if is_prefix {
201                        let mut metadata = MetadataBuilder::unknown();
202                        metadata.set_dir();
203                        return Ok(RpStat::new(metadata.build()));
204                    }
205                }
206
207                Err(Error::new(ErrorKind::NotFound, "path not found"))
208            }
209        }
210    }
211    fn read(&self, _ctx: &OperationContext, path: &str, op: OpRead) -> Result<Self::Reader> {
212        let output: oio::StreamReader<MiniMokaReader> = {
213            Ok(oio::StreamReader::new(MiniMokaReader::new(
214                self.clone(),
215                path,
216                op,
217            )))
218        }?;
219
220        Ok(output)
221    }
222
223    fn write(&self, _ctx: &OperationContext, path: &str, op: OpWrite) -> Result<Self::Writer> {
224        let output: MiniMokaWriter = {
225            let p = build_abs_path(&self.root, path);
226            let writer = MiniMokaWriter::new(self.core.clone(), p, op);
227            Ok(writer)
228        }?;
229
230        Ok(output)
231    }
232
233    fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
234        let output: oio::OneShotDeleter<MiniMokaDeleter> = {
235            let deleter = oio::OneShotDeleter::new(MiniMokaDeleter::new(
236                self.core.clone(),
237                self.root.clone(),
238            ));
239            Ok(deleter)
240        }?;
241
242        Ok(output)
243    }
244
245    fn list(&self, _ctx: &OperationContext, path: &str, op: OpList) -> Result<Self::Lister> {
246        let output: oio::HierarchyLister<MiniMokaLister> = {
247            let p = build_abs_path(&self.root, path);
248
249            let mini_moka_lister = MiniMokaLister::new(self.core.clone(), self.root.clone(), p);
250            let lister = oio::HierarchyLister::new(mini_moka_lister, path, op.recursive());
251
252            Ok(lister)
253        }?;
254
255        Ok(output)
256    }
257
258    fn copy(
259        &self,
260        _ctx: &OperationContext,
261        _from: &str,
262        _to: &str,
263        _args: OpCopy,
264    ) -> Result<Self::Copier> {
265        Err(Error::new(
266            ErrorKind::Unsupported,
267            "operation is not supported",
268        ))
269    }
270
271    async fn rename(
272        &self,
273        _ctx: &OperationContext,
274        _from: &str,
275        _to: &str,
276        _args: OpRename,
277    ) -> Result<RpRename> {
278        Err(Error::new(
279            ErrorKind::Unsupported,
280            "operation is not supported",
281        ))
282    }
283
284    async fn presign(
285        &self,
286        _ctx: &OperationContext,
287        _path: &str,
288        _args: OpPresign,
289    ) -> Result<RpPresign> {
290        Err(Error::new(
291            ErrorKind::Unsupported,
292            "operation is not supported",
293        ))
294    }
295}