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}