opendal/services/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;
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/// [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 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        // 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)]
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        // Check if path exists directly in cache
162        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, return the content buffer directly
200                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}