Skip to main content

duat_core/context/
cache.rs

1//! Caching utilities for Duat
2//!
3//! The cache in Duat is used when restoring previous information from
4//! a previous Duat instance, or when reloading Duat's configuration
5//! crate.
6//!
7//! One example for where this is used is the [Ui], which has
8//! information about how much a given buffer should be scrolled when
9//! opening it in Duat. Another example is [`Selections`], which
10//! stores a list of open [`Cursor`]s, to start the buffer on.
11//!
12//! Plugins are able to cache any type that implements [`Encode`]
13//! and [`Decode`]. Duat provides these traits from the [`bincode`]
14//! crate, but in order to derive them, you will need a small addition
15//! on the declaration of the type:
16//!
17//! ```rust
18//! use duat_core::context::cache::{Decode, Encode};
19//! #[derive(Encode, Decode)]
20//! #[bincode(crate = "duat_core::context::cache::bincode")]
21//! enum MyCacheableEnum {
22//!     // ...
23//! }
24//! ```
25//!
26//! [Ui]: crate::ui::traits::RawUi
27//! [`Selections`]: crate::mode::Selections
28//! [`Cursor`]: crate::mode::Cursor
29//! [`Point`]: crate::text::Point
30use std::{
31    ffi::OsString,
32    fs::File,
33    hash::{DefaultHasher, Hash, Hasher},
34    path::{Path, PathBuf},
35};
36
37use bincode::config;
38pub use bincode::{self, Decode, Encode};
39
40use self::bincode::encode_into_std_write;
41use crate::{
42    text::{Text, txt},
43    utils::{duat_name, src_crate},
44};
45
46/// Tries to load the cache stored by Duat for the given type
47///
48/// The cache must have been previously stored by
49/// [`cache::store`]. If it does not exist, or the buffer can't
50/// be correctly interpreted, returns [`None`]
51///
52/// [`cache::store`]: store
53pub fn load<C: Decode<()> + Default + 'static>(path: impl AsRef<Path>) -> Result<C, Text> {
54    let mut cache_file = cache_file::<C>(path.as_ref(), false)?;
55
56    if cache_file.metadata()?.len() == 0 {
57        return Ok(C::default());
58    }
59
60    let config = config::standard();
61    bincode::decode_from_std_read(&mut cache_file, config).map_err(|err| txt!("{err}"))
62}
63
64/// Stores the cache for the given type for that buffer
65///
66/// The cache will be stored under
67/// `$cache/duat/{base64_path}:{file_name}/{crate}::{type}`.
68/// The cache can then later be loaded by [`cache::load`].
69///
70/// [`cache::load`]: load
71pub fn store<C: Encode + 'static>(path: impl AsRef<Path>, cache: C) -> Result<usize, Text> {
72    let mut cache_file = cache_file::<C>(path.as_ref(), true)?;
73
74    let config = config::standard();
75    encode_into_std_write(cache, &mut cache_file, config).map_err(|err| txt!("{err}"))
76}
77
78/// Deletes the cache for all types for `path`
79///
80/// This is done if the buffer no longer exists, in order to
81/// prevent incorrect storage.
82pub fn delete(path: impl Into<PathBuf>) {
83    fn delete_cache_inner(path: PathBuf) {
84        let (Some(cache_dir), Some(file_name)) = (dirs_next::cache_dir(), path.file_name()) else {
85            return;
86        };
87
88        let mut hasher = DefaultHasher::new();
89        path.hash(&mut hasher);
90        let hash_value = hasher.finish();
91
92        let cached_file_name = {
93            let mut name = OsString::from(format!("{hash_value}-"));
94            name.push(file_name);
95            name
96        };
97
98        let src = cache_dir
99            .join("duat")
100            .join("structs")
101            .join(cached_file_name);
102        // It could fail if the directory doesn't exist, but we don't really
103        // care.
104        let _ = std::fs::remove_dir_all(src);
105    }
106
107    delete_cache_inner(path.into());
108}
109
110/// Deletes the cache for everything related to the given `path`
111pub fn delete_for<C: 'static>(path: impl AsRef<Path>) {
112    let path = path.as_ref();
113    let (Some(cache_dir), Some(file_name)) = (dirs_next::cache_dir(), path.file_name()) else {
114        return;
115    };
116
117    let mut hasher = DefaultHasher::new();
118    path.hash(&mut hasher);
119    let hash_value = hasher.finish();
120
121    let cached_file_name = {
122        let mut name = OsString::from(format!("{hash_value}-"));
123        name.push(file_name);
124        name
125    };
126
127    let src = cache_dir
128        .join("duat")
129        .join("structs")
130        .join(cached_file_name)
131        .join(format!("{}-{}", src_crate::<C>(), duat_name::<C>()));
132
133    if let Ok(true) = src.try_exists() {
134        std::fs::remove_file(src).unwrap();
135    }
136}
137
138fn cache_file<C: 'static>(path: &Path, truncate: bool) -> std::io::Result<File> {
139    let mut hasher = DefaultHasher::new();
140    path.hash(&mut hasher);
141    let hash_value = hasher.finish();
142
143    let cached_file_name = {
144        let mut name = OsString::from(format!("{hash_value}-"));
145        name.push(path.file_name().ok_or_else(|| {
146            std::io::Error::new(
147                std::io::ErrorKind::IsADirectory,
148                format!("{path:?} is a directory, not a buffer for caching"),
149            )
150        })?);
151        name
152    };
153
154    let src_dir = dirs_next::cache_dir()
155        .ok_or_else(|| {
156            std::io::Error::new(std::io::ErrorKind::NotFound, "cache directory isn't set")
157        })?
158        .join("duat")
159        .join("structs")
160        .join(cached_file_name.clone());
161
162    if !src_dir.exists() {
163        std::fs::create_dir_all(src_dir.clone())?;
164    }
165
166    let src = src_dir.join(format!("{}-{}", src_crate::<C>(), duat_name::<C>()));
167
168    std::fs::OpenOptions::new()
169        .create(true)
170        .read(true)
171        .write(true)
172        .truncate(truncate)
173        .open(src)
174}