Skip to main content

alpm_db/files/
mod.rs

1//! The representation of [alpm-db-files] files.
2//!
3//! [alpm-db-files]: https://alpm.archlinux.page/specifications/alpm-db-files.5.html
4
5#[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/// The representation of [alpm-db-files] data.
28///
29/// Tracks all known versions of the specification.
30///
31/// [alpm-db-files]: https://alpm.archlinux.page/specifications/alpm-db-files.5.html
32#[derive(Clone, Debug, serde::Serialize)]
33#[serde(untagged)]
34pub enum DbFiles {
35    /// Version 1 of the [alpm-db-files] specification.
36    ///
37    /// [alpm-db-files]: https://alpm.archlinux.page/specifications/alpm-db-files.5.html
38    V1(DbFilesV1),
39}
40
41impl Display for DbFiles {
42    /// Formats the [`DbFiles`] as a string according to its style.
43    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    /// Returns a reference to the inner [`Vec`] of [`PathBuf`]s.
52    fn as_ref(&self) -> &[PathBuf] {
53        match self {
54            DbFiles::V1(files) => files.as_ref(),
55        }
56    }
57}
58
59impl DbFiles {
60    /// Returns the backup entries associated with this [`DbFiles`].
61    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    /// Creates a new [`DbFiles`] from a file [`Path`] and an optional [`DbFilesSchema`].
72    ///
73    /// # Note
74    ///
75    /// Delegates to [`Self::from_reader_with_schema`] after opening `file` for reading.
76    ///
77    /// # Errors
78    ///
79    /// Returns an error if
80    ///
81    /// - the `file` cannot be opened for reading,
82    /// - or [`Self::from_reader_with_schema`] fails.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use std::io::Write;
88    ///
89    /// use alpm_common::MetadataFile;
90    /// use alpm_db::files::{DbFiles, DbFilesSchema};
91    /// use alpm_types::{SchemaVersion, semver_version::Version};
92    /// use tempfile::NamedTempFile;
93    ///
94    /// # fn main() -> testresult::TestResult {
95    /// let data = r#"%FILES%
96    /// usr/
97    /// usr/bin/
98    /// usr/bin/foo
99    /// "#;
100    /// let mut temp_file = NamedTempFile::new()?;
101    /// write!(temp_file, "{data}")?;
102    /// let files = DbFiles::from_file_with_schema(
103    ///     temp_file.path(),
104    ///     Some(DbFilesSchema::V1(SchemaVersion::new(Version::new(1, 0, 0)))),
105    /// )?;
106    /// matches!(files, DbFiles::V1(_));
107    /// assert_eq!(files.as_ref().len(), 3);
108    /// # Ok(())
109    /// # }
110    /// ```
111    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    /// Creates a new [`DbFiles`] from a [`Read`] implementation and an optional [`DbFilesSchema`].
130    ///
131    /// # Note
132    ///
133    /// Delegates to [`Self::from_str_with_schema`] after reading `reader` to string.
134    ///
135    /// # Errors
136    ///
137    /// Returns an error if
138    ///
139    /// - the `reader` cannot be read to string,
140    /// - or [`Self::from_str_with_schema`] fails.
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// use std::io::{Seek, SeekFrom, Write};
146    ///
147    /// use alpm_common::MetadataFile;
148    /// use alpm_db::files::{DbFiles, DbFilesSchema};
149    /// use alpm_types::{SchemaVersion, semver_version::Version};
150    /// use tempfile::tempfile;
151    ///
152    /// # fn main() -> testresult::TestResult {
153    /// let data = r#"%FILES%
154    /// usr/
155    /// usr/bin/
156    /// usr/bin/foo
157    /// "#;
158    /// let mut temp_file = tempfile()?;
159    /// write!(temp_file, "{data}")?;
160    /// temp_file.seek(SeekFrom::Start(0))?;
161    /// let files = DbFiles::from_reader_with_schema(
162    ///     temp_file,
163    ///     Some(DbFilesSchema::V1(SchemaVersion::new(Version::new(1, 0, 0)))),
164    /// )?;
165    /// matches!(files, DbFiles::V1(_));
166    /// assert_eq!(files.as_ref().len(), 3);
167    /// # Ok(())
168    /// # }
169    /// ```
170    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    /// Creates a new [`DbFiles`] from a string slice and an optional [`DbFilesSchema`].
188    ///
189    /// # Errors
190    ///
191    /// Returns an error if
192    ///
193    /// - `schema` is [`None`] and a [`DbFilesSchema`] cannot be derived from `s`,
194    /// - or a [`DbFilesV1`] cannot be created from `s`.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use alpm_common::MetadataFile;
200    /// use alpm_db::files::{DbFiles, DbFilesSchema};
201    /// use alpm_types::{SchemaVersion, semver_version::Version};
202    ///
203    /// # fn main() -> Result<(), alpm_db::files::Error> {
204    /// let data = r#"%FILES%
205    /// usr/
206    /// usr/bin/
207    /// usr/bin/foo
208    /// "#;
209    /// let files = DbFiles::from_str_with_schema(
210    ///     data,
211    ///     Some(DbFilesSchema::V1(SchemaVersion::new(Version::new(1, 0, 0)))),
212    /// )?;
213    /// matches!(files, DbFiles::V1(_));
214    /// assert_eq!(files.as_ref().len(), 3);
215    /// # Ok(())
216    /// # }
217    /// ```
218    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    /// Creates a new [`DbFiles`] from string slice.
237    ///
238    /// # Note
239    ///
240    /// Delegates to [`Self::from_str_with_schema`] while not providing a [`DbFilesSchema`].
241    ///
242    /// # Errors
243    ///
244    /// Returns an error if [`Self::from_str_with_schema`] fails.
245    fn from_str(s: &str) -> Result<Self, Self::Err> {
246        Self::from_str_with_schema(s, None)
247    }
248}