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 crate::raw::oio;
30use crate::raw::oio::HierarchyLister;
31use crate::raw::*;
32use crate::services::MiniMokaConfig;
33use crate::*;
34
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    const SCHEME: Scheme = Scheme::MiniMoka;
107    type Config = MiniMokaConfig;
108
109    fn build(self) -> Result<impl Access> {
110        debug!("backend build started: {:?}", &self);
111
112        let mut builder: mini_moka::sync::CacheBuilder<String, MiniMokaValue, _> =
113            mini_moka::sync::Cache::builder();
114
115        // Use entries' bytes as capacity weigher.
116        builder = builder.weigher(|k, v| (k.len() + v.content.len()) as u32);
117
118        if let Some(v) = self.config.max_capacity {
119            builder = builder.max_capacity(v);
120        }
121        if let Some(v) = self.config.time_to_live {
122            builder = builder.time_to_live(v);
123        }
124        if let Some(v) = self.config.time_to_idle {
125            builder = builder.time_to_idle(v);
126        }
127
128        let cache = builder.build();
129
130        let root = normalize_root(self.config.root.as_deref().unwrap_or("/"));
131
132        let core = Arc::new(MiniMokaCore { cache });
133
134        debug!("backend build finished: {root}");
135        Ok(MiniMokaBackend::new(core, root))
136    }
137}
138
139#[derive(Debug)]
140struct MiniMokaBackend {
141    core: Arc<MiniMokaCore>,
142    root: String,
143}
144
145impl MiniMokaBackend {
146    fn new(core: Arc<MiniMokaCore>, root: String) -> Self {
147        Self { core, root }
148    }
149}
150
151impl Access for MiniMokaBackend {
152    type Reader = Buffer;
153    type Writer = MiniMokaWriter;
154    type Lister = HierarchyLister<MiniMokaLister>;
155    type Deleter = oio::OneShotDeleter<MiniMokaDeleter>;
156
157    fn info(&self) -> Arc<AccessorInfo> {
158        let info = AccessorInfo::default();
159        info.set_scheme(Scheme::MiniMoka)
160            .set_root(&self.root)
161            .set_native_capability(Capability {
162                stat: true,
163                read: true,
164                write: true,
165                write_can_empty: true,
166                delete: true,
167                list: true,
168
169                ..Default::default()
170            });
171
172        Arc::new(info)
173    }
174
175    async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
176        let p = build_abs_path(&self.root, path);
177
178        // Check if path exists directly in cache
179        match self.core.get(&p) {
180            Some(value) => {
181                let mut metadata = value.metadata.clone();
182                if p.ends_with('/') {
183                    metadata.set_mode(EntryMode::DIR);
184                } else {
185                    metadata.set_mode(EntryMode::FILE);
186                }
187                Ok(RpStat::new(metadata))
188            }
189            None => {
190                if p.ends_with('/') {
191                    let is_prefix = self
192                        .core
193                        .cache
194                        .iter()
195                        .any(|entry| entry.key().starts_with(&p) && entry.key() != &p);
196
197                    if is_prefix {
198                        let mut metadata = Metadata::default();
199                        metadata.set_mode(EntryMode::DIR);
200                        return Ok(RpStat::new(metadata));
201                    }
202                }
203
204                Err(Error::new(ErrorKind::NotFound, "path not found"))
205            }
206        }
207    }
208
209    async fn read(&self, path: &str, op: OpRead) -> Result<(RpRead, Self::Reader)> {
210        let p = build_abs_path(&self.root, path);
211
212        match self.core.get(&p) {
213            Some(value) => {
214                let range = op.range();
215
216                // If range is full, return the content buffer directly
217                if range.is_full() {
218                    return Ok((RpRead::new(), value.content));
219                }
220
221                let offset = range.offset() as usize;
222                if offset >= value.content.len() {
223                    return Err(Error::new(
224                        ErrorKind::RangeNotSatisfied,
225                        "range start offset exceeds content length",
226                    ));
227                }
228
229                let size = range.size().map(|s| s as usize);
230                let end = size.map_or(value.content.len(), |s| {
231                    (offset + s).min(value.content.len())
232                });
233                let sliced_content = value.content.slice(offset..end);
234
235                Ok((RpRead::new(), sliced_content))
236            }
237            None => Err(Error::new(ErrorKind::NotFound, "path not found")),
238        }
239    }
240
241    async fn write(&self, path: &str, op: OpWrite) -> Result<(RpWrite, Self::Writer)> {
242        let p = build_abs_path(&self.root, path);
243        let writer = MiniMokaWriter::new(self.core.clone(), p, op);
244        Ok((RpWrite::new(), writer))
245    }
246
247    async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
248        let deleter =
249            oio::OneShotDeleter::new(MiniMokaDeleter::new(self.core.clone(), self.root.clone()));
250        Ok((RpDelete::default(), deleter))
251    }
252
253    async fn list(&self, path: &str, op: OpList) -> Result<(RpList, Self::Lister)> {
254        let p = build_abs_path(&self.root, path);
255
256        let mini_moka_lister = MiniMokaLister::new(self.core.clone(), self.root.clone(), p);
257        let lister = HierarchyLister::new(mini_moka_lister, path, op.recursive());
258
259        Ok((RpList::default(), lister))
260    }
261}