debian_packaging/repository/
filesystem.rs1use {
8 crate::{
9 error::{DebianError, Result},
10 io::{Compression, ContentDigest, DataResolver, DigestingReader},
11 repository::{
12 release::ReleaseFile, ReleaseReader, RepositoryPathVerification,
13 RepositoryPathVerificationState, RepositoryRootReader, RepositoryWrite,
14 RepositoryWriter,
15 },
16 },
17 async_trait::async_trait,
18 futures::{io::BufReader, AsyncRead, AsyncReadExt},
19 std::{
20 borrow::Cow,
21 path::{Path, PathBuf},
22 pin::Pin,
23 },
24 url::Url,
25};
26
27#[derive(Clone, Debug)]
29pub struct FilesystemRepositoryReader {
30 root_dir: PathBuf,
31}
32
33impl FilesystemRepositoryReader {
34 pub fn new(path: impl AsRef<Path>) -> Self {
38 Self {
39 root_dir: path.as_ref().to_path_buf(),
40 }
41 }
42}
43
44#[async_trait]
45impl DataResolver for FilesystemRepositoryReader {
46 async fn get_path(&self, path: &str) -> Result<Pin<Box<dyn AsyncRead + Send>>> {
47 let path = self.root_dir.join(path);
48
49 let f = std::fs::File::open(&path)
50 .map_err(|e| DebianError::RepositoryIoPath(format!("{}", path.display()), e))?;
51
52 Ok(Box::pin(futures::io::AllowStdIo::new(f)))
53 }
54}
55
56#[async_trait]
57impl RepositoryRootReader for FilesystemRepositoryReader {
58 fn url(&self) -> Result<Url> {
59 Url::from_file_path(&self.root_dir)
60 .map_err(|_| DebianError::Other("error converting filesystem path to URL".to_string()))
61 }
62
63 async fn release_reader_with_distribution_path(
64 &self,
65 path: &str,
66 ) -> Result<Box<dyn ReleaseReader>> {
67 let distribution_path = path.trim_matches('/').to_string();
68 let inrelease_path = format!("{}/InRelease", distribution_path);
69 let release_path = format!("{}/Release", distribution_path);
70 let distribution_dir = self.root_dir.join(&distribution_path);
71
72 let release = self
73 .fetch_inrelease_or_release(&inrelease_path, &release_path)
74 .await?;
75
76 let fetch_compression = Compression::default_preferred_order()
77 .next()
78 .expect("iterator should not be empty");
79
80 Ok(Box::new(FilesystemReleaseClient {
81 distribution_dir,
82 relative_path: distribution_path,
83 release,
84 fetch_compression,
85 }))
86 }
87}
88
89pub struct FilesystemReleaseClient {
90 distribution_dir: PathBuf,
91 relative_path: String,
92 release: ReleaseFile<'static>,
93 fetch_compression: Compression,
94}
95
96#[async_trait]
97impl DataResolver for FilesystemReleaseClient {
98 async fn get_path(&self, path: &str) -> Result<Pin<Box<dyn AsyncRead + Send>>> {
99 let path = self.distribution_dir.join(path);
100
101 let f = std::fs::File::open(&path)
102 .map_err(|e| DebianError::RepositoryIoPath(format!("{}", path.display()), e))?;
103
104 Ok(Box::pin(BufReader::new(futures::io::AllowStdIo::new(f))))
105 }
106}
107
108#[async_trait]
109impl ReleaseReader for FilesystemReleaseClient {
110 fn url(&self) -> Result<Url> {
111 Url::from_file_path(&self.distribution_dir)
112 .map_err(|_| DebianError::Other("error converting filesystem path to URL".to_string()))
113 }
114
115 fn root_relative_path(&self) -> &str {
116 &self.relative_path
117 }
118
119 fn release_file(&self) -> &ReleaseFile<'static> {
120 &self.release
121 }
122
123 fn preferred_compression(&self) -> Compression {
124 self.fetch_compression
125 }
126
127 fn set_preferred_compression(&mut self, compression: Compression) {
128 self.fetch_compression = compression;
129 }
130}
131
132pub struct FilesystemRepositoryWriter {
134 root_dir: PathBuf,
135}
136
137impl FilesystemRepositoryWriter {
138 pub fn new(path: impl AsRef<Path>) -> Self {
142 Self {
143 root_dir: path.as_ref().to_path_buf(),
144 }
145 }
146}
147
148#[async_trait]
149impl RepositoryWriter for FilesystemRepositoryWriter {
150 async fn verify_path<'path>(
151 &self,
152 path: &'path str,
153 expected_content: Option<(u64, ContentDigest)>,
154 ) -> Result<RepositoryPathVerification<'path>> {
155 let dest_path = self.root_dir.join(path);
156
157 let metadata = match async_std::fs::metadata(&dest_path).await {
158 Ok(res) => res,
159 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
160 return Ok(RepositoryPathVerification {
161 path,
162 state: RepositoryPathVerificationState::Missing,
163 });
164 }
165 Err(e) => return Err(DebianError::RepositoryIoPath(path.to_string(), e)),
166 };
167
168 if metadata.is_file() {
169 if let Some((expected_size, expected_digest)) = expected_content {
170 if metadata.len() != expected_size {
171 Ok(RepositoryPathVerification {
172 path,
173 state: RepositoryPathVerificationState::ExistsIntegrityMismatch,
174 })
175 } else {
176 let f = async_std::fs::File::open(&dest_path)
177 .await
178 .map_err(|e| DebianError::RepositoryIoPath(path.to_string(), e))?;
179
180 let mut remaining = expected_size;
181 let mut reader = DigestingReader::new(f);
182 let mut buf = [0u8; 16384];
183
184 loop {
185 let size = reader
186 .read(&mut buf[..])
187 .await
188 .map_err(|e| DebianError::RepositoryIoPath(path.to_string(), e))?
189 as u64;
190
191 if size >= remaining || size == 0 {
192 break;
193 }
194
195 remaining -= size;
196 }
197
198 let digest = reader.finish().1;
199
200 Ok(RepositoryPathVerification {
201 path,
202 state: if digest.matches_digest(&expected_digest) {
203 RepositoryPathVerificationState::ExistsIntegrityVerified
204 } else {
205 RepositoryPathVerificationState::ExistsIntegrityMismatch
206 },
207 })
208 }
209 } else {
210 Ok(RepositoryPathVerification {
211 path,
212 state: RepositoryPathVerificationState::ExistsNoIntegrityCheck,
213 })
214 }
215 } else {
216 Ok(RepositoryPathVerification {
217 path,
218 state: RepositoryPathVerificationState::Missing,
219 })
220 }
221 }
222
223 async fn write_path<'path, 'reader>(
224 &self,
225 path: Cow<'path, str>,
226 reader: Pin<Box<dyn AsyncRead + Send + 'reader>>,
227 ) -> Result<RepositoryWrite<'path>> {
228 let dest_path = self.root_dir.join(path.as_ref());
229
230 if let Some(parent) = dest_path.parent() {
231 std::fs::create_dir_all(parent)
232 .map_err(|e| DebianError::RepositoryIoPath(format!("{}", parent.display()), e))?;
233 }
234
235 let fh = std::fs::File::create(&dest_path)
236 .map_err(|e| DebianError::RepositoryIoPath(format!("{}", dest_path.display()), e))?;
237
238 let mut writer = futures::io::AllowStdIo::new(fh);
239
240 let bytes_written = futures::io::copy(reader, &mut writer)
241 .await
242 .map_err(|e| DebianError::RepositoryIoPath(format!("{}", dest_path.display()), e))?;
243
244 Ok(RepositoryWrite {
245 path,
246 bytes_written,
247 })
248 }
249}