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