1use super::super::errors::*;
2
3use {
4 kutil::std::error::*,
5 rand::{distr::*, *},
6 std::{collections::*, env::*, fs::*, path::*, sync::*},
7 tracing::info,
8};
9
10const RANDOM_NAME_LENGTH: usize = 32;
11
12pub type PathBufRef = Arc<Mutex<PathBuf>>;
14
15type PathBufRefMap = LazyLock<Mutex<HashMap<String, PathBufRef>>>;
16
17#[derive(Debug)]
23pub struct UrlCache {
24 pub base_directory: PathBuf,
26
27 pub files: PathBufRefMap,
29
30 pub directories: PathBufRefMap,
32}
33
34impl UrlCache {
37 pub fn new(base_directory: Option<PathBuf>) -> Self {
39 let base_directory = base_directory.unwrap_or_else(|| Self::default_base_directory());
40
41 Self {
42 base_directory,
43 files: LazyLock::new(|| HashMap::default().into()),
44 directories: LazyLock::new(|| HashMap::default().into()),
45 }
46 }
47
48 pub fn default_base_directory() -> PathBuf {
50 temp_dir().join("read-url")
51 }
52
53 pub fn reset(&self) -> Result<(), UrlError> {
55 let mut errors = Vec::default();
56
57 let mut files = self.files.lock()?;
58 for path in files.values() {
59 let path = path.lock()?;
60 info!("deleting file: {}", path.display());
61 let path = path.as_path();
62 if let Err(error) = remove_file(path) {
63 errors.push(error.with_path(path));
64 }
65 }
66 files.clear();
67
68 let mut directories = self.directories.lock()?;
69 for path in directories.values() {
70 let path = path.lock()?;
71 info!("deleting directory: {}", path.display());
72 let path = path.as_path();
73 if let Err(error) = remove_dir_all(path) {
74 errors.push(error.with_path(path));
75 }
76 }
77 directories.clear();
78
79 if errors.is_empty() { Ok(()) } else { Err(UrlError::IoMany(errors)) }
80 }
81
82 pub fn file(&self, key: &str, prefix: &str) -> Result<(PathBufRef, bool), UrlError> {
86 let key = key.to_string();
87
88 let mut files = self.files.lock()?;
89 match files.get(&key) {
90 Some(path) => {
91 info!("existing file: {}", path.clone().lock()?.display());
92 Ok((path.clone(), true))
93 }
94
95 None => {
96 let path = self.new_path(prefix)?;
97 info!("new file: {}", path.display());
98 let path = Arc::new(Mutex::new(path));
99 files.insert(key, path.clone());
100 Ok((path, false))
101 }
102 }
103 }
104
105 pub fn directory(&self, key: &str, prefix: &str) -> Result<(PathBufRef, bool), UrlError> {
110 let key = key.to_string();
111
112 let mut directories = self.directories.lock()?;
113 match directories.get(&key) {
114 Some(path) => {
115 info!("existing directory: {}", path.clone().lock()?.display());
116 Ok((path.clone(), true))
117 }
118
119 None => {
120 let path = self.new_path(prefix)?;
121 info!("new directory: {}", path.display());
122 let path = Arc::new(Mutex::new(path));
123 directories.insert(key, path.clone());
124 Ok((path, false))
125 }
126 }
127 }
128
129 pub(crate) fn new_path(&self, prefix: &str) -> Result<PathBuf, UrlError> {
130 create_dir_all(&self.base_directory).with_path(&self.base_directory)?;
131
132 let distribution = Uniform::new_inclusive('a', 'z').expect("Uniform::new_inclusive");
134 let path: String = rng().sample_iter(distribution).take(RANDOM_NAME_LENGTH).collect();
135 let path = prefix.to_string() + &path;
136 Ok(self.base_directory.join(path))
137 }
138}
139
140impl Drop for UrlCache {
141 fn drop(&mut self) {
142 _ = self.reset();
143 }
144}