dir_cache/
opts.rs

1use crate::disk::{ensure_dir, exists, FileObjectExists};
2use crate::error::{Error, Result};
3use crate::{DirCache, DirCacheInner};
4use std::fmt::Display;
5use std::num::NonZeroUsize;
6use std::path::Path;
7use std::time::Duration;
8
9/// Options for controlling the behavior of operations on a [`DirCache`].
10/// See the specific options for more details
11#[derive(Debug, Copy, Clone, Default)]
12pub struct DirCacheOpts {
13    pub mem_pull_opt: MemPullOpt,
14    pub mem_push_opt: MemPushOpt,
15    pub generation_opt: GenerationOpt,
16    pub sync_opt: SyncOpt,
17}
18
19impl DirCacheOpts {
20    #[must_use]
21    pub const fn new(
22        mem_pull_opt: MemPullOpt,
23        mem_push_opt: MemPushOpt,
24        generation_opt: GenerationOpt,
25        sync_opt: SyncOpt,
26    ) -> Self {
27        Self {
28            mem_pull_opt,
29            mem_push_opt,
30            generation_opt,
31            sync_opt,
32        }
33    }
34
35    #[must_use]
36    pub const fn with_mem_pull_opt(mut self, mem_pull_opt: MemPullOpt) -> Self {
37        self.mem_pull_opt = mem_pull_opt;
38        self
39    }
40
41    #[must_use]
42    pub const fn with_mem_push_opt(mut self, mem_push_opt: MemPushOpt) -> Self {
43        self.mem_push_opt = mem_push_opt;
44        self
45    }
46
47    #[must_use]
48    pub const fn with_generation_opt(mut self, generation_opt: GenerationOpt) -> Self {
49        self.generation_opt = generation_opt;
50        self
51    }
52
53    #[must_use]
54    pub const fn with_sync_opt(mut self, sync_opt: SyncOpt) -> Self {
55        self.sync_opt = sync_opt;
56        self
57    }
58
59    /// Use these [`DirCacheOpts`] to open a [`DirCache`].
60    /// # Errors
61    /// Depending on the open options a directory already being present or not may cause failure.
62    /// Various io-errors, from creating the [`DirCache`].
63    pub fn open(self, path: &Path, cache_open_options: CacheOpenOptions) -> Result<DirCache> {
64        match cache_open_options.dir_open {
65            DirOpenOpt::OnlyIfExists => {
66                match exists(path)? {
67                    FileObjectExists::AsDir => {}
68                    FileObjectExists::No => {
69                        return Err(Error::Open(format!(
70                            "Opened with OnlyIfExists but path {path:?} does not exist"
71                        )));
72                    }
73                    FileObjectExists::AsFile => {
74                        return Err(Error::Open(format!(
75                            "Wanted to open at {path:?}, but path is a file"
76                        )));
77                    }
78                };
79            }
80            DirOpenOpt::CreateIfMissing => {
81                ensure_dir(path)?;
82            }
83        }
84        let inner = DirCacheInner::read_from_disk(
85            path.to_path_buf(),
86            cache_open_options.eager_load_to_ram,
87            self.generation_opt,
88        )?;
89        Ok(DirCache { inner, opts: self })
90    }
91}
92
93#[derive(Debug, Copy, Clone, Default)]
94pub struct CacheOpenOptions {
95    pub(crate) dir_open: DirOpenOpt,
96    pub(crate) eager_load_to_ram: bool,
97}
98
99impl CacheOpenOptions {
100    #[must_use]
101    pub fn new(dir_open: DirOpenOpt, eager_load_to_ram: bool) -> Self {
102        Self {
103            dir_open,
104            eager_load_to_ram,
105        }
106    }
107}
108
109/// Options for when a [`DirCache`] is opened
110#[derive(Debug, Copy, Clone, Default)]
111pub enum DirOpenOpt {
112    /// Only open if a `dir-cache` directory already exists at the path, otherwise fail
113    OnlyIfExists,
114    /// Create a `dir-cache` directory if none exists at the path
115    #[default]
116    CreateIfMissing,
117}
118
119/// Memory push option, determines whether the data should be retained in memory when written to disk
120#[derive(Debug, Copy, Clone, Default)]
121pub enum MemPushOpt {
122    /// Keep the data in memory after writing
123    RetainAndWrite,
124    /// Write the data into memory, don't automatically sync to disk
125    MemoryOnly,
126    /// Remove the data from memory after writing
127    #[default]
128    PassthroughWrite,
129}
130
131/// Memory pull options, determines whether data should be cached in memory when pulled from disk,
132/// such as during a `get` operation.
133#[derive(Debug, Copy, Clone, Default)]
134pub enum MemPullOpt {
135    /// Reads the value from disk, then retains it in memory
136    #[default]
137    KeepInMemoryOnRead,
138    /// Reads the value from disk, but does not keep it stored in memory
139    DontKeepInMemoryOnRead,
140}
141
142/// Expiration options, how to determine if an entry has expired
143#[derive(Debug, Copy, Clone, Default)]
144pub enum ExpirationOpt {
145    /// Entries never expire
146    #[default]
147    NoExpiry,
148    /// Entries expire after
149    ExpiresAfter(Duration),
150}
151
152impl ExpirationOpt {
153    #[inline]
154    pub(crate) fn as_dur(self) -> Duration {
155        match self {
156            // End of all times
157            ExpirationOpt::NoExpiry => Duration::MAX,
158            ExpirationOpt::ExpiresAfter(dur) => dur,
159        }
160    }
161}
162
163/// Data can be saved as generations (keeping older values of keys),
164/// these options determine how those generations are managed
165#[derive(Debug, Copy, Clone)]
166pub struct GenerationOpt {
167    /// How many old copies to keep, 1 effectively means no generations, just one value.
168    pub max_generations: NonZeroUsize,
169    /// How to encode older generations
170    pub(crate) old_gen_encoding: Encoding,
171    /// How to determine when a value of any generation has expired
172    pub(crate) expiration: ExpirationOpt,
173}
174
175impl Default for GenerationOpt {
176    #[inline]
177    fn default() -> Self {
178        Self::new(NonZeroUsize::MIN, Encoding::Plain, ExpirationOpt::NoExpiry)
179    }
180}
181
182impl GenerationOpt {
183    #[must_use]
184    pub const fn new(
185        max_generations: NonZeroUsize,
186        old_gen_encoding: Encoding,
187        expiration: ExpirationOpt,
188    ) -> Self {
189        Self {
190            max_generations,
191            old_gen_encoding,
192            expiration,
193        }
194    }
195}
196
197/// Different encoding options
198#[derive(Copy, Clone, Debug)]
199pub enum Encoding {
200    /// No encoding
201    Plain,
202    /// Compress using lz4
203    #[cfg(feature = "lz4")]
204    Lz4,
205}
206
207impl Encoding {
208    pub(crate) fn serialize(self) -> impl Display {
209        match self {
210            Encoding::Plain => 0u8,
211            #[cfg(feature = "lz4")]
212            Encoding::Lz4 => 1u8,
213        }
214    }
215
216    pub(crate) fn deserialize(s: &str) -> Result<Self> {
217        match s {
218            "0" => Ok(Self::Plain),
219            #[cfg(feature = "lz4")]
220            "1" => Ok(Self::Lz4),
221            v => Err(Error::ParseMetadata(format!(
222                "Failed to parse encoding from {v}"
223            ))),
224        }
225    }
226
227    #[inline]
228    #[allow(clippy::unnecessary_wraps)]
229    pub(crate) fn encode(self, content: Vec<u8>) -> Result<Vec<u8>> {
230        match self {
231            Encoding::Plain => Ok(content),
232            #[cfg(feature = "lz4")]
233            Encoding::Lz4 => {
234                let mut buf = Vec::new();
235                let mut encoder = lz4::EncoderBuilder::new().build(&mut buf).map_err(|e| {
236                    Error::EncodingError(format!("Failed to create lz4 encoder builder: {e}"))
237                })?;
238                std::io::Write::write(&mut encoder, &content).map_err(|e| {
239                    Error::EncodingError(format!("Failed to lz4 encode content: {e}"))
240                })?;
241                Ok(buf)
242            }
243        }
244    }
245}
246
247/// Options controlling syncing, ensuring that the [`DirCache`]'s state kept in memory is committed to disk.
248/// Unnecessary if all keys are not written with [`MemPushOpt::MemoryOnly`]
249#[derive(Debug, Copy, Clone, Default)]
250pub enum SyncOpt {
251    /// Sync when dropped (syncing can still be done manually)
252    SyncOnDrop,
253    /// Only sync manually
254    #[default]
255    ManualSync,
256}