rust_releases_io/client/
cached_client.rs1use crate::client::errors::{HttpError, IoError};
2use crate::client::remote_client::HttpClient;
3use crate::{
4 is_stale, ClientError, Document, IsStaleError, ResourceFile, RetrievalLocation,
5 RetrievedDocument, RustReleasesClient,
6};
7use std::fs;
8use std::io::{self, BufReader, BufWriter, Read, Write};
9use std::path::{Path, PathBuf};
10use std::time::Duration;
11
12const DEFAULT_MEMORY_SIZE: usize = 4096;
13
14const DEFAULT_TIMEOUT: Duration = Duration::from_secs(150);
15
16#[derive(Debug)]
23pub struct HttpCachedClient {
24 cache_folder: PathBuf,
25 cache_timeout: Duration,
26}
27
28impl HttpCachedClient {
29 pub fn new(cache_folder: PathBuf, cache_timeout: Duration) -> Self {
40 Self {
41 cache_folder,
42 cache_timeout,
43 }
44 }
45}
46
47impl RustReleasesClient for HttpCachedClient {
48 type Error = HttpCachedClientError;
49
50 fn fetch(&self, resource: ResourceFile) -> Result<RetrievedDocument, Self::Error> {
51 let path = self.cache_folder.join(resource.name());
52 let exists = path.exists();
53
54 if exists && !is_stale(&path, self.cache_timeout)? {
56 let buffer = read_from_path(&path)?;
57 let document = Document::new(buffer);
58
59 return Ok(RetrievedDocument::new(
60 document,
61 RetrievalLocation::Path(path),
62 ));
63 }
64
65 if !exists {
67 setup_cache_folder(&path)?;
68 }
69
70 let client = HttpClient::new(DEFAULT_TIMEOUT);
71 let mut retrieved = client
72 .fetch(resource)
73 .map_err(HttpCachedClientError::from)?;
74
75 let document = retrieved.mut_document();
76
77 write_document_and_cache(document, &path)?;
79
80 Ok(retrieved)
81 }
82}
83
84fn read_from_path(path: &Path) -> Result<Vec<u8>, HttpCachedClientError> {
85 let mut reader = BufReader::new(
86 fs::File::open(path).map_err(|err| IoError::inaccessible(err, path.to_path_buf()))?,
87 );
88
89 let mut memory = Vec::with_capacity(DEFAULT_MEMORY_SIZE);
90 reader
91 .read_to_end(&mut memory)
92 .map_err(IoError::auxiliary)?;
93
94 Ok(memory)
95}
96
97fn setup_cache_folder(manifest_path: &Path) -> Result<(), HttpCachedClientError> {
99 fn create_dir_all(path: &Path) -> Result<(), IoError> {
100 fs::create_dir_all(path).map_err(|err| IoError::inaccessible(err, path.to_path_buf()))
101 }
102
103 if let Some(cache_folder) = manifest_path.parent() {
105 match fs::metadata(cache_folder) {
107 Ok(m) if m.is_dir() => Ok(()),
109 Ok(_) => Err(IoError::is_file(cache_folder.to_path_buf())),
113 Err(err) if err.kind() == io::ErrorKind::NotFound => create_dir_all(cache_folder),
115 Err(err) => Err(IoError::inaccessible(err, cache_folder.to_path_buf())),
117 }?;
118 }
119
120 Ok(())
121}
122
123fn write_document_and_cache(
124 document: &mut Document,
125 file_path: &Path,
126) -> Result<(), HttpCachedClientError> {
127 let mut file = fs::File::create(file_path)
128 .map_err(|err| IoError::inaccessible(err, file_path.to_path_buf()))?;
129
130 let mut writer = BufWriter::new(&mut file);
131 writer
132 .write_all(document.buffer())
133 .map_err(|err| IoError::inaccessible(err, file_path.to_path_buf()))?;
134
135 Ok(())
136}
137
138#[derive(Debug, thiserror::Error)]
140#[non_exhaustive]
141pub enum HttpCachedClientError {
142 #[error("Received empty file")]
144 EmptyFile,
145
146 #[error(transparent)]
148 Http(#[from] HttpError),
149
150 #[error(transparent)]
152 Io(#[from] IoError),
153
154 #[error(transparent)]
157 IsStale(#[from] IsStaleError),
158}
159
160impl From<ClientError> for HttpCachedClientError {
161 fn from(err: ClientError) -> Self {
162 match err {
163 ClientError::Empty => HttpCachedClientError::EmptyFile,
164 ClientError::Http(err) => HttpCachedClientError::Http(err),
165 ClientError::Io(err) => HttpCachedClientError::Io(err),
166 }
167 }
168}