arch_pkg_db/text/
local.rs

1use super::{Text, TextCollection};
2use core::error::Error;
3use derive_more::{Display, Error};
4use pipe_trait::Pipe;
5use rayon::prelude::*;
6use std::{
7    fs::{read_dir, read_to_string},
8    io::{self, ErrorKind},
9    path::{Path, PathBuf},
10};
11
12/// Error when trying to read the directory of the local pacman database.
13#[derive(Debug, Display, Error)]
14#[display("Failed to read {path:?} as a directory: {error}")]
15pub struct ReadLocalDbDirError<'a> {
16    #[error(source)]
17    error: io::Error,
18    path: &'a Path,
19}
20
21impl<'a> ReadLocalDbDirError<'a> {
22    /// Create an error.
23    fn new(error: io::Error, path: &'a Path) -> Self {
24        Self { error, path }
25    }
26
27    /// The source of this error.
28    pub fn source(&self) -> &io::Error {
29        &self.error
30    }
31
32    /// Path to the directory of the local pacman database.
33    pub fn path(&self) -> &Path {
34        self.path
35    }
36}
37
38/// Error when trying to read a file in the local pacman database.
39#[derive(Debug, Display, Error)]
40#[display("Failed to read {path:?} as a text file: {error}")]
41pub struct ReadLocalDbFileError {
42    #[error(source)]
43    error: io::Error,
44    path: PathBuf,
45}
46
47impl ReadLocalDbFileError {
48    /// Create an error.
49    fn new(error: io::Error, path: PathBuf) -> Self {
50        Self { error, path }
51    }
52
53    /// The source of this error.
54    pub fn source(&self) -> &io::Error {
55        &self.error
56    }
57
58    /// Path to the directory of the local pacman database.
59    pub fn path(&self) -> &Path {
60        &self.path
61    }
62}
63
64/// Error when trying to load data from a local pacman database.
65#[derive(Debug, Display)]
66pub enum LoadLocalDbError<'a> {
67    ReadDir(ReadLocalDbDirError<'a>),
68    ReadFile(ReadLocalDbFileError),
69}
70
71// We implement Error manually because derive_more::Error was unable to handle it.
72// Issue: <https://github.com/JelteF/derive_more/issues/511>
73impl<'a> Error for LoadLocalDbError<'a> {
74    fn source(&self) -> Option<&(dyn Error + 'static)> {
75        match self {
76            LoadLocalDbError::ReadDir(error) => Error::source(error),
77            LoadLocalDbError::ReadFile(error) => Error::source(error),
78        }
79    }
80}
81
82impl TextCollection {
83    /// Load data from a local pacman database.
84    ///
85    /// A local pacman database is a directory usually located at `$ARCH_ROOT/var/lib/pacman/local/`.
86    pub fn extend_from_local_db<'path>(
87        &mut self,
88        local_db_path: &'path Path,
89    ) -> Result<(), LoadLocalDbError<'path>> {
90        let entries = local_db_path
91            .pipe(read_dir)
92            .map_err(|error| ReadLocalDbDirError::new(error, local_db_path))
93            .map_err(LoadLocalDbError::ReadDir)?;
94
95        for entry in entries {
96            let Ok(entry) = entry else { continue };
97            let Ok(file_type) = entry.file_type() else {
98                continue;
99            };
100            if !file_type.is_dir() {
101                continue;
102            }
103            let file_path = entry.path().join("desc");
104            match read_to_string(&file_path) {
105                Ok(text) => self.insert(text.into()),
106                Err(error) if error.kind() == ErrorKind::NotFound => continue,
107                Err(error) => {
108                    return ReadLocalDbFileError::new(error, file_path)
109                        .pipe(LoadLocalDbError::ReadFile)
110                        .pipe(Err);
111                }
112            };
113        }
114
115        Ok(())
116    }
117
118    /// Load data from a local pacman database.
119    ///
120    /// A local pacman database is a directory usually located at `$ARCH_ROOT/var/lib/pacman/local/`.
121    pub fn add_local_db(mut self, local_db_path: &'_ Path) -> Result<Self, LoadLocalDbError<'_>> {
122        self.extend_from_local_db(local_db_path)?;
123        Ok(self)
124    }
125
126    /// Load data from a local pacman database.
127    ///
128    /// A local pacman database is a directory usually located at `$ARCH_ROOT/var/lib/pacman/local/`.
129    pub fn from_local_db(local_db_path: &'_ Path) -> Result<Self, LoadLocalDbError<'_>> {
130        TextCollection::new().add_local_db(local_db_path)
131    }
132
133    /// Load data from a local pacman database in parallel.
134    ///
135    /// A local pacman database is a directory usually located at `$ARCH_ROOT/var/lib/pacman/local/`.
136    pub fn par_extend_from_local_db<'path>(
137        &mut self,
138        local_db_path: &'path Path,
139    ) -> Result<(), LoadLocalDbError<'path>> {
140        let texts = local_db_path
141            .pipe(read_dir)
142            .map_err(|error| ReadLocalDbDirError::new(error, local_db_path))
143            .map_err(LoadLocalDbError::ReadDir)?
144            .par_bridge()
145            .flatten()
146            .filter(|entry| {
147                entry
148                    .file_type()
149                    .ok()
150                    .map(|file_type| file_type.is_dir())
151                    .unwrap_or(false)
152            })
153            .map(|entry| -> Result<Option<String>, LoadLocalDbError> {
154                let file_path = entry.path().join("desc");
155                match read_to_string(&file_path) {
156                    Ok(text) => Ok(Some(text)),
157                    Err(error) if error.kind() == ErrorKind::NotFound => Ok(None),
158                    Err(error) => ReadLocalDbFileError::new(error, file_path)
159                        .pipe(LoadLocalDbError::ReadFile)
160                        .pipe(Err),
161                }
162            })
163            .collect::<Result<Vec<Option<String>>, LoadLocalDbError>>()?
164            .into_iter()
165            .flatten()
166            .map(Text::from);
167        self.extend(texts);
168        Ok(())
169    }
170
171    /// Load data from a local pacman database in parallel.
172    ///
173    /// A local pacman database is a directory usually located at `$ARCH_ROOT/var/lib/pacman/local/`.
174    pub fn par_add_local_db(
175        mut self,
176        local_db_path: &'_ Path,
177    ) -> Result<Self, LoadLocalDbError<'_>> {
178        self.par_extend_from_local_db(local_db_path)?;
179        Ok(self)
180    }
181
182    /// Load data from a local pacman database in parallel.
183    ///
184    /// A local pacman database is a directory usually located at `$ARCH_ROOT/var/lib/pacman/local/`.
185    pub fn par_from_local_db(local_db_path: &Path) -> Result<Self, LoadLocalDbError<'_>> {
186        TextCollection::new().par_add_local_db(local_db_path)
187    }
188}