1#[cfg(feature = "cli")]
6#[doc(hidden)]
7pub mod cli;
8
9mod error;
10mod schema;
11pub mod v1;
12
13use std::{
14 fmt::Display,
15 fs::File,
16 io::Read,
17 path::{Path, PathBuf},
18 str::FromStr,
19};
20
21use alpm_common::{FileFormatSchema, MetadataFile};
22pub use error::Error;
23use fluent_i18n::t;
24pub use schema::DbFilesSchema;
25pub use v1::{BackupEntry, DbFilesV1};
26
27#[derive(Clone, Debug, serde::Serialize)]
33#[serde(untagged)]
34pub enum DbFiles {
35 V1(DbFilesV1),
39}
40
41impl Display for DbFiles {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 DbFiles::V1(files) => files.fmt(f),
46 }
47 }
48}
49
50impl AsRef<[PathBuf]> for DbFiles {
51 fn as_ref(&self) -> &[PathBuf] {
53 match self {
54 DbFiles::V1(files) => files.as_ref(),
55 }
56 }
57}
58
59impl DbFiles {
60 pub fn backups(&self) -> &[BackupEntry] {
62 match self {
63 DbFiles::V1(files) => files.backups(),
64 }
65 }
66}
67
68impl MetadataFile<DbFilesSchema> for DbFiles {
69 type Err = Error;
70
71 fn from_file_with_schema(
112 file: impl AsRef<Path>,
113 schema: Option<DbFilesSchema>,
114 ) -> Result<Self, Self::Err>
115 where
116 Self: Sized,
117 {
118 let path = file.as_ref();
119 Self::from_reader_with_schema(
120 File::open(path).map_err(|source| Error::IoPath {
121 path: path.to_path_buf(),
122 context: t!("error-io-path-context-opening-the-file-for-reading"),
123 source,
124 })?,
125 schema,
126 )
127 }
128
129 fn from_reader_with_schema(
171 mut reader: impl Read,
172 schema: Option<DbFilesSchema>,
173 ) -> Result<Self, Self::Err>
174 where
175 Self: Sized,
176 {
177 let mut buf = String::new();
178 reader
179 .read_to_string(&mut buf)
180 .map_err(|source| Error::Io {
181 context: t!("error-io-context-reading-alpm-db-files-data"),
182 source,
183 })?;
184 Self::from_str_with_schema(&buf, schema)
185 }
186
187 fn from_str_with_schema(s: &str, schema: Option<DbFilesSchema>) -> Result<Self, Self::Err>
219 where
220 Self: Sized,
221 {
222 let schema = match schema {
223 Some(schema) => schema,
224 None => DbFilesSchema::derive_from_str(s)?,
225 };
226
227 match schema {
228 DbFilesSchema::V1(_) => Ok(DbFiles::V1(DbFilesV1::from_str(s)?)),
229 }
230 }
231}
232
233impl FromStr for DbFiles {
234 type Err = Error;
235
236 fn from_str(s: &str) -> Result<Self, Self::Err> {
246 Self::from_str_with_schema(s, None)
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use std::{io::Write, str::FromStr};
253
254 use alpm_types::{Md5Checksum, RelativeFilePath, SchemaVersion, semver_version::Version};
255 use rstest::rstest;
256 use tempfile::NamedTempFile;
257 use testresult::TestResult;
258
259 use super::*;
260
261 #[rstest]
263 #[case(
264 vec![
265 PathBuf::from("usr/"),
266 PathBuf::from("usr/bin/"),
267 PathBuf::from("usr/bin/foo"),
268 ],
269 r#"%FILES%
270usr/
271usr/bin/
272usr/bin/foo
273
274"#
275 )]
276 #[case(Vec::new(), "")]
277 fn files_to_string(#[case] input: Vec<PathBuf>, #[case] expected_output: &str) -> TestResult {
278 let files = DbFiles::V1(DbFilesV1::try_from(input)?);
279
280 assert_eq!(files.to_string(), expected_output);
281
282 Ok(())
283 }
284
285 #[test]
286 fn files_to_string_with_backup() -> TestResult {
287 let files = DbFiles::V1(DbFilesV1::try_from((
288 vec![
289 PathBuf::from("usr/"),
290 PathBuf::from("usr/bin/"),
291 PathBuf::from("usr/bin/foo"),
292 ],
293 vec![BackupEntry {
294 path: RelativeFilePath::from_str("usr/bin/foo")?,
295 md5: Md5Checksum::from_str("d41d8cd98f00b204e9800998ecf8427e")?,
296 }],
297 ))?);
298
299 let expected_output = r#"%FILES%
300usr/
301usr/bin/
302usr/bin/foo
303
304%BACKUP%
305usr/bin/foo d41d8cd98f00b204e9800998ecf8427e
306"#;
307
308 assert_eq!(files.to_string(), expected_output);
309
310 Ok(())
311 }
312
313 #[test]
314 fn files_from_str() -> TestResult {
315 let input = r#"%FILES%
316usr/
317usr/bin/
318usr/bin/foo
319
320"#;
321 let expected_paths = vec![
322 PathBuf::from("usr/"),
323 PathBuf::from("usr/bin/"),
324 PathBuf::from("usr/bin/foo"),
325 ];
326 let files = DbFiles::from_str(input)?;
327
328 assert_eq!(files.as_ref(), expected_paths);
329
330 Ok(())
331 }
332
333 #[test]
334 fn files_from_str_with_backup() -> TestResult {
335 let input = r#"%FILES%
336usr/
337usr/bin/
338usr/bin/foo
339
340%BACKUP%
341usr/bin/foo d41d8cd98f00b204e9800998ecf8427e
342"#;
343 let files = DbFiles::from_str(input)?;
344
345 let expected_backup = BackupEntry {
346 path: RelativeFilePath::from_str("usr/bin/foo")?,
347 md5: Md5Checksum::from_str("d41d8cd98f00b204e9800998ecf8427e")?,
348 };
349
350 assert_eq!(
351 files.as_ref(),
352 &[
353 PathBuf::from("usr/"),
354 PathBuf::from("usr/bin/"),
355 PathBuf::from("usr/bin/foo")
356 ]
357 );
358
359 assert_eq!(files.backups(), &[expected_backup]);
360
361 Ok(())
362 }
363
364 const ALPM_DB_FILES_FULL: &str = r#"%FILES%
365usr/
366usr/bin/
367usr/bin/foo
368
369"#;
370 const ALPM_DB_FILES_EMPTY: &str = "";
371 const ALPM_REPO_FILES_FULL: &str = r#"%FILES%
372usr/
373usr/bin/
374usr/bin/foo
375"#;
376 const ALPM_REPO_FILES_EMPTY: &str = "%FILES%";
377
378 #[rstest]
380 #[case::alpm_db_files_full(ALPM_DB_FILES_FULL, 3)]
381 #[case::alpm_db_files_empty(ALPM_DB_FILES_EMPTY, 0)]
382 #[case::alpm_repo_files_full(ALPM_REPO_FILES_FULL, 3)]
383 #[case::alpm_repo_files_full(ALPM_REPO_FILES_EMPTY, 0)]
384 fn files_from_file_with_schema_succeeds(#[case] data: &str, #[case] len: usize) -> TestResult {
385 let mut temp_file = NamedTempFile::new()?;
386 write!(temp_file, "{data}")?;
387
388 let files = DbFiles::from_file_with_schema(
389 temp_file.path(),
390 Some(DbFilesSchema::V1(SchemaVersion::new(Version::new(1, 0, 0)))),
391 )?;
392
393 assert!(matches!(files, DbFiles::V1(_)));
394 assert_eq!(files.as_ref().len(), len);
395
396 Ok(())
397 }
398
399 #[test]
400 fn files_from_file_with_backup_section() -> TestResult {
401 let data = r#"%FILES%
402usr/
403usr/bin/
404usr/bin/foo
405
406%BACKUP%
407usr/bin/foo d41d8cd98f00b204e9800998ecf8427e
408"#;
409 let mut temp_file = NamedTempFile::new()?;
410 write!(temp_file, "{data}")?;
411
412 let files = DbFiles::from_file_with_schema(
413 temp_file.path(),
414 Some(DbFilesSchema::V1(SchemaVersion::new(Version::new(1, 0, 0)))),
415 )?;
416
417 assert!(matches!(files, DbFiles::V1(_)));
418 assert_eq!(files.backups().len(), 1);
419
420 Ok(())
421 }
422}