debian_packaging/repository/
contents.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5/*! `Contents` index file handling. */
6
7use {
8    crate::error::Result,
9    futures::{AsyncBufRead, AsyncBufReadExt},
10    pin_project::pin_project,
11    std::{
12        collections::{BTreeMap, BTreeSet},
13        io::{BufRead, Write},
14    },
15};
16
17/// Represents a `Contents` file.
18///
19/// A `Contents` file maps paths to packages. It facilitates lookups of which paths
20/// are in which packages.
21///
22/// Internally, paths are stored as [String] because bulk operations against paths
23/// can be expensive due to more expensive comparison/equality checks.
24#[derive(Clone, Debug, Default)]
25pub struct ContentsFile {
26    /// Mapping of paths to packages they occur in.
27    paths: BTreeMap<String, BTreeSet<String>>,
28    /// Mapping of package names to paths they contain.
29    packages: BTreeMap<String, BTreeSet<String>>,
30}
31
32impl ContentsFile {
33    fn parse_and_add_line(&mut self, line: &str) -> Result<()> {
34        // According to https://wiki.debian.org/DebianRepository/Format#A.22Contents.22_indices
35        // `Contents` files begin with freeform text then have a table of path to package list.
36        // Invalid lines are ignored.
37
38        let words = line.split_ascii_whitespace().collect::<Vec<_>>();
39
40        if words.len() != 2 {
41            return Ok(());
42        }
43
44        let path = words[0];
45        let packages = words[1];
46
47        for package in packages.split(',') {
48            self.paths
49                .entry(path.to_string())
50                .or_default()
51                .insert(package.to_string());
52            self.packages
53                .entry(package.to_string())
54                .or_default()
55                .insert(path.to_string());
56        }
57
58        Ok(())
59    }
60
61    /// Register a path as belonging to a package.
62    pub fn add_package_path(&mut self, path: String, package: String) {
63        self.paths
64            .entry(path.clone())
65            .or_default()
66            .insert(package.clone());
67        self.packages.entry(package).or_default().insert(path);
68    }
69
70    /// Obtain an iterator of packages having the specified path.
71    pub fn packages_with_path(&self, path: &str) -> Box<dyn Iterator<Item = &str> + '_> {
72        if let Some(packages) = self.paths.get(path) {
73            Box::new(packages.iter().map(|x| x.as_str()))
74        } else {
75            Box::new(std::iter::empty())
76        }
77    }
78
79    /// Obtain an iterator of paths in a given package.
80    pub fn package_paths(&self, package: &str) -> Box<dyn Iterator<Item = &str> + '_> {
81        if let Some(paths) = self.packages.get(package) {
82            Box::new(paths.iter().map(|x| x.as_str()))
83        } else {
84            Box::new(std::iter::empty())
85        }
86    }
87
88    /// Emit lines constituting this file.
89    pub fn as_lines(&self) -> impl Iterator<Item = String> + '_ {
90        self.paths.iter().map(|(path, packages)| {
91            // BTreeSet doesn't have a .join(). So we need to build a collection that does.
92            let packages = packages.iter().map(|s| s.as_str()).collect::<Vec<_>>();
93
94            format!("{}    {}\n", path, packages.join(",'"))
95        })
96    }
97
98    /// Write the content of this file to a writer.
99    ///
100    /// Returns the total number of bytes written.
101    pub fn write_to(&self, writer: &mut impl Write) -> Result<usize> {
102        let mut bytes_count = 0;
103
104        for line in self.as_lines() {
105            writer.write_all(line.as_bytes())?;
106            bytes_count += line.as_bytes().len();
107        }
108
109        Ok(bytes_count)
110    }
111}
112
113#[derive(Clone, Debug)]
114pub struct ContentsFileReader<R> {
115    reader: R,
116    contents: ContentsFile,
117}
118
119impl<R: BufRead> ContentsFileReader<R> {
120    /// Create a new instance bound to a reader.
121    pub fn new(reader: R) -> Self {
122        Self {
123            reader,
124            contents: ContentsFile::default(),
125        }
126    }
127
128    /// Consumes the instance, returning the original reader.
129    pub fn into_inner(self) -> R {
130        self.reader
131    }
132
133    /// Parse the entirety of the source reader.
134    pub fn read_all(&mut self) -> Result<usize> {
135        let mut bytes_read = 0;
136
137        while let Ok(read_size) = self.read_line() {
138            if read_size == 0 {
139                break;
140            }
141
142            bytes_read += read_size;
143        }
144
145        Ok(bytes_read)
146    }
147
148    /// Read and parse a single line from the reader.
149    pub fn read_line(&mut self) -> Result<usize> {
150        let mut line = String::new();
151        let read_size = self.reader.read_line(&mut line)?;
152
153        if read_size != 0 {
154            self.contents.parse_and_add_line(&line)?;
155        }
156
157        Ok(read_size)
158    }
159
160    /// Consume the instance and return the inner [ContentsFile] and the reader.
161    pub fn consume(self) -> (ContentsFile, R) {
162        (self.contents, self.reader)
163    }
164}
165
166#[pin_project]
167pub struct ContentsFileAsyncReader<R> {
168    #[pin]
169    reader: R,
170    contents: ContentsFile,
171}
172
173impl<R> ContentsFileAsyncReader<R>
174where
175    R: AsyncBufRead + Unpin,
176{
177    /// Create a new instance bound to a reader.
178    pub fn new(reader: R) -> Self {
179        Self {
180            reader,
181            contents: ContentsFile::default(),
182        }
183    }
184
185    /// Consumes self, returning the inner reader.
186    pub fn into_inner(self) -> R {
187        self.reader
188    }
189
190    /// Parse the entirety of the source reader.
191    pub async fn read_all(&mut self) -> Result<usize> {
192        let mut bytes_read = 0;
193
194        while let Ok(read_size) = self.read_line().await {
195            if read_size == 0 {
196                break;
197            }
198
199            bytes_read += read_size;
200        }
201
202        Ok(bytes_read)
203    }
204
205    /// Read and parse a single line from the reader.
206    pub async fn read_line(&mut self) -> Result<usize> {
207        let mut line = String::new();
208        let read_size = self.reader.read_line(&mut line).await?;
209
210        if read_size != 0 {
211            self.contents.parse_and_add_line(&line)?;
212        }
213
214        Ok(read_size)
215    }
216
217    /// Consume the instance and return the inner [ContentsFile] and source reader.
218    pub fn consume(self) -> (ContentsFile, R) {
219        (self.contents, self.reader)
220    }
221}