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();
}
}