deno_cache_dir/global/
mod.rs1use std::path::PathBuf;
4use std::time::Duration;
5use std::time::SystemTime;
6use std::time::UNIX_EPOCH;
7
8use deno_maybe_sync::MaybeSend;
9use deno_maybe_sync::MaybeSync;
10use serde::Deserialize;
11use sys_traits::FsCreateDirAll;
12use sys_traits::FsMetadata;
13use sys_traits::FsMetadataValue;
14use sys_traits::FsOpen;
15use sys_traits::FsRead;
16use sys_traits::FsRemoveFile;
17use sys_traits::FsRename;
18use sys_traits::SystemRandom;
19use sys_traits::SystemTimeNow;
20use sys_traits::ThreadSleep;
21use url::Url;
22
23use super::cache::HttpCache;
24use super::cache::HttpCacheItemKey;
25use crate::cache::CacheEntry;
26use crate::cache::CacheReadFileError;
27use crate::cache::Checksum;
28use crate::cache::SerializedCachedUrlMetadata;
29use crate::cache::url_to_filename;
30use crate::common::HeadersMap;
31
32mod cache_file;
33
34#[sys_traits::auto_impl]
35pub trait GlobalHttpCacheSys:
36 FsCreateDirAll
37 + FsMetadata
38 + FsOpen
39 + FsRead
40 + FsRemoveFile
41 + FsRename
42 + ThreadSleep
43 + SystemRandom
44 + SystemTimeNow
45 + std::fmt::Debug
46 + MaybeSend
47 + MaybeSync
48 + Clone
49{
50}
51
52#[allow(clippy::disallowed_types, reason = "arc wrapper type")]
53pub type GlobalHttpCacheRc<TSys> =
54 deno_maybe_sync::MaybeArc<GlobalHttpCache<TSys>>;
55
56#[derive(Debug)]
57pub struct GlobalHttpCache<Sys: GlobalHttpCacheSys> {
58 path: PathBuf,
59 pub(crate) sys: Sys,
60}
61
62impl<Sys: GlobalHttpCacheSys> GlobalHttpCache<Sys> {
63 pub fn new(sys: Sys, path: PathBuf) -> Self {
64 #[cfg(not(target_arch = "wasm32"))]
65 assert!(path.is_absolute());
66 Self { path, sys }
67 }
68
69 pub fn dir_path(&self) -> &PathBuf {
70 &self.path
71 }
72
73 pub fn local_path_for_url(&self, url: &Url) -> std::io::Result<PathBuf> {
74 Ok(self.path.join(url_to_filename(url)?))
75 }
76
77 #[inline]
78 fn key_file_path<'a>(&self, key: &'a HttpCacheItemKey) -> &'a PathBuf {
79 key.file_path.as_ref().unwrap()
83 }
84}
85
86impl<TSys: GlobalHttpCacheSys> HttpCache for GlobalHttpCache<TSys> {
87 fn cache_item_key<'a>(
88 &self,
89 url: &'a Url,
90 ) -> std::io::Result<HttpCacheItemKey<'a>> {
91 Ok(HttpCacheItemKey {
92 #[cfg(debug_assertions)]
93 is_local_key: false,
94 url,
95 file_path: Some(self.local_path_for_url(url)?),
96 })
97 }
98
99 fn contains(&self, url: &Url) -> bool {
100 let Ok(cache_filepath) = self.local_path_for_url(url) else {
101 return false;
102 };
103 self.sys.fs_is_file(&cache_filepath).unwrap_or(false)
104 }
105
106 fn read_modified_time(
107 &self,
108 key: &HttpCacheItemKey,
109 ) -> std::io::Result<Option<SystemTime>> {
110 #[cfg(debug_assertions)]
111 debug_assert!(!key.is_local_key);
112
113 match self.sys.fs_metadata(self.key_file_path(key)) {
114 Ok(metadata) => match metadata.modified() {
115 Ok(time) => Ok(Some(time)),
116 Err(_) => Ok(Some(self.sys.sys_time_now())),
117 },
118 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
119 Err(err) => Err(err),
120 }
121 }
122
123 fn set(
124 &self,
125 url: &Url,
126 headers: HeadersMap,
127 content: &[u8],
128 ) -> std::io::Result<()> {
129 let cache_filepath = self.local_path_for_url(url)?;
130 cache_file::write(
131 &self.sys,
132 &cache_filepath,
133 content,
134 &SerializedCachedUrlMetadata {
135 time: Some(
136 self
137 .sys
138 .sys_time_now()
139 .duration_since(UNIX_EPOCH)
140 .unwrap()
141 .as_secs(),
142 ),
143 url: url.to_string(),
144 headers,
145 },
146 )?;
147
148 Ok(())
149 }
150
151 fn get(
152 &self,
153 key: &HttpCacheItemKey,
154 maybe_checksum: Option<Checksum>,
155 ) -> Result<Option<CacheEntry>, CacheReadFileError> {
156 #[cfg(debug_assertions)]
157 debug_assert!(!key.is_local_key);
158
159 let maybe_file = cache_file::read(&self.sys, self.key_file_path(key))?;
160
161 if let Some(file) = &maybe_file
162 && let Some(expected_checksum) = maybe_checksum
163 {
164 expected_checksum
165 .check(key.url, &file.content)
166 .map_err(CacheReadFileError::ChecksumIntegrity)?;
167 }
168
169 Ok(maybe_file)
170 }
171
172 fn read_headers(
173 &self,
174 key: &HttpCacheItemKey,
175 ) -> std::io::Result<Option<HeadersMap>> {
176 #[derive(Deserialize)]
178 struct SerializedHeaders {
179 pub headers: HeadersMap,
180 }
181
182 #[cfg(debug_assertions)]
183 debug_assert!(!key.is_local_key);
184
185 let maybe_metadata = cache_file::read_metadata::<SerializedHeaders>(
186 &self.sys,
187 self.key_file_path(key),
188 )?;
189 Ok(maybe_metadata.map(|m| m.headers))
190 }
191
192 fn read_download_time(
193 &self,
194 key: &HttpCacheItemKey,
195 ) -> std::io::Result<Option<SystemTime>> {
196 #[derive(Deserialize)]
198 struct SerializedTime {
199 pub time: Option<u64>,
200 }
201
202 #[cfg(debug_assertions)]
203 debug_assert!(!key.is_local_key);
204 let maybe_metadata = cache_file::read_metadata::<SerializedTime>(
205 &self.sys,
206 self.key_file_path(key),
207 )?;
208 Ok(maybe_metadata.and_then(|m| {
209 Some(SystemTime::UNIX_EPOCH + Duration::from_secs(m.time?))
210 }))
211 }
212}
213
214#[cfg(test)]
215mod test {
216 use super::*;
217
218 #[test]
219 fn test_url_to_filename() {
220 let test_cases = [
221 (
222 "https://deno.land/x/foo.ts",
223 "https/deno.land/2c0a064891b9e3fbe386f5d4a833bce5076543f5404613656042107213a7bbc8",
224 ),
225 (
226 "https://deno.land:8080/x/foo.ts",
227 "https/deno.land_PORT8080/2c0a064891b9e3fbe386f5d4a833bce5076543f5404613656042107213a7bbc8",
228 ),
229 (
230 "https://deno.land/",
231 "https/deno.land/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1",
232 ),
233 (
234 "https://deno.land/?asdf=qwer",
235 "https/deno.land/e4edd1f433165141015db6a823094e6bd8f24dd16fe33f2abd99d34a0a21a3c0",
236 ),
237 (
240 "https://deno.land/?asdf=qwer#qwer",
241 "https/deno.land/e4edd1f433165141015db6a823094e6bd8f24dd16fe33f2abd99d34a0a21a3c0",
242 ),
243 (
244 "data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=",
245 "data/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37",
246 ),
247 (
248 "data:text/plain,Hello%2C%20Deno!",
249 "data/967374e3561d6741234131e342bf5c6848b70b13758adfe23ee1a813a8131818",
250 ),
251 ];
252
253 for (url, expected) in test_cases.iter() {
254 let u = Url::parse(url).unwrap();
255 let p = url_to_filename(&u).unwrap();
256 assert_eq!(p, PathBuf::from(expected));
257 }
258 }
259}