duat_core/
cache.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Caching utilities for Duat
//!
//! The cache in Duat is used when restoring previous information from
//! a previous Duat instance, or when reloading Duat's configuration
//! crate.
//!
//! One example for where this is used is the [`Ui`], which has
//! information about how much a given file should be scrolled when
//! opening it in Duat. Another example is [`Cursors`], which stores a
//! list of open [`Cursor`]s, to start the file on.
//!
//! Plugins are able to cache any type that implements [`Serialize`]
//! and [`Deserialize`]. Duat provides these traits from [`serde`],
//! but in order to derive them, you will need a small addition on the
//! declaration of the type:
//!
//! ```rust
//! # use duat_core::cache::{Deserialize, Serialize};
//! #[derive(Deserialize, Serialize)]
//! #[serde(crate = "duat_core::cache::serde")]
//! enum MyCacheableEnum {
//!     // ...
//! }
//! ```
//!
//! [`Ui`]: crate::ui::Ui
//! [`Cursors`]: crate::mode::Cursors
//! [`Cursor`]: crate::mode::Cursor
//! [`Point`]: crate::text::Point
use std::{any::TypeId, io::Write, path::PathBuf};

use base64::Engine;
pub use serde::{self, Deserialize, Serialize};

use crate::{duat_name, src_crate};

/// Tries to load the cache stored by Duat for the given type
///
/// The cache must have been previously stored by [`store_cache`]. If
/// it does not exist, or the file can't be correctly interpreted,
/// returns [`None`]
pub(super) fn load_cache<C>(path: impl Into<PathBuf>) -> Option<C>
where
    C: Deserialize<'static> + 'static,
{
    let path: PathBuf = path.into();
    if TypeId::of::<C>() == TypeId::of::<()>() {
        return None;
    }

    let file_name = path.file_name()?.to_str()?;
    let mut src = dirs_next::cache_dir()?;

    let encoded: String = {
        let base64 = base64::prelude::BASE64_URL_SAFE.encode(path.to_str().unwrap());
        base64.chars().step_by(5).collect()
    };

    src.push("duat");
    src.push(format!("{encoded}:{file_name}"));
    src.push(format!("{}::{}", src_crate::<C>(), duat_name::<C>()));

    let contents = std::fs::read(src).ok()?.leak();
    bincode::deserialize(contents).ok()
}

/// Stores the cache for the given type for that file
///
/// The cache will be stored under
/// `$cache/duat/{base64_path}:{file_name}/{crate}::{type}`.
/// The cache will then later be loaded by [`load_cache`].
pub(super) fn store_cache<C>(path: impl Into<PathBuf>, cache: C)
where
    C: Serialize + 'static,
{
    let path: PathBuf = path.into();
    if TypeId::of::<C>() == TypeId::of::<()>() {
        return;
    }

    let file_name = path.file_name().unwrap().to_str().unwrap();
    let Some(mut src) = dirs_next::cache_dir() else {
        return;
    };

    let encoded: String = {
        let base64 = base64::prelude::BASE64_URL_SAFE.encode(path.to_str().unwrap());
        base64.chars().step_by(5).collect()
    };

    src.push("duat");
    src.push(format!("{encoded}:{file_name}"));

    if !src.exists() {
        std::fs::create_dir_all(src.clone()).unwrap();
    }

    src.push(format!("{}::{}", src_crate::<C>(), duat_name::<C>()));

    let mut contents = std::fs::OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open(src)
        .unwrap();

    contents
        .write_all(&bincode::serialize(&cache).unwrap())
        .unwrap();
}

/// Deletes the cache for all types for `path`
///
/// This is done if the file no longer exists, in order to prevent
/// incorrect storage.
pub(super) fn delete_cache(path: impl Into<PathBuf>) {
    let path: PathBuf = path.into();
    let file_name = path.file_name().unwrap().to_str().unwrap();
    let Some(mut src) = dirs_next::cache_dir() else {
        return;
    };

    let encoded: String = {
        let base64 = base64::prelude::BASE64_URL_SAFE.encode(path.to_str().unwrap());
        base64.chars().step_by(5).collect()
    };

    src.push("duat");
    src.push(format!("{encoded}:{file_name}"));

    if src.exists() {
        std::fs::remove_dir_all(src).unwrap();
    }
}