pkg_utils/
package.rs

1//! Provides structs with both metadata about a package
2//! and a representation of a package on the disk.
3
4use std::io::Read;
5use std::fs::File;
6use std::path::{Path, PathBuf};
7use std::result::Result as StdResult;
8
9use failure::err_msg;
10use tar::Archive;
11use version_compare::Version;
12use xz2::read::XzDecoder;
13
14use Provide;
15use error::{ParseError, Result};
16
17/// This is a handle to an actual package on the filesystem.
18// In the loosest of terms.
19#[derive(Clone, Debug, Eq, Hash, PartialEq)]
20pub struct Package {
21    pub filename: PathBuf,
22    pub meta: MetaPackage
23}
24
25impl Package {
26    /// Load package info from a `.pkg.tar.xz` on the filesystem
27    pub fn load(filename: impl AsRef<Path>) -> Result<Package> {
28        let tarball_file = File::open(&filename)?;
29        let xz_decoder = XzDecoder::new(tarball_file);
30        let mut tar_decoder = Archive::new(xz_decoder);
31        for entry in tar_decoder.entries()? {
32            let mut entry = entry?;
33            if entry.path()? == Path::new(".PKGINFO") {
34                let mut data = String::new();
35                entry.read_to_string(&mut data)?;
36                let meta_package = MetaPackage::parse_pkginfo(data)?;
37                let package = Package {
38                    filename: filename.as_ref().to_path_buf(),
39                    meta: meta_package
40                };
41                return Ok(package);
42            }
43        }
44        Err(err_msg("tarball missing a .PKGINFO entry"))
45    }
46}
47
48/// This represents metadata about a package. Construction should
49/// only be done via [`Package`](struct.Package.html) or [`Db`](struct.Db.html).
50/// For the most part, fields that were not found by my primitive
51/// parsers will just be empty. This isn't really ideal in the case of
52/// the string members.
53#[derive(Clone, Debug, Eq, Hash, PartialEq)]
54pub struct MetaPackage {
55    pub name: String,
56    version: String,
57    pub description: String,
58    //TODO: Use a crate url type
59    pub url: String,
60    // pub build_date: ?,
61    pub packager: String,
62    // Installed size
63    //pub size: ?,
64    pub architecture: String,
65    pub groups: Vec<String>,
66    pub licenses: Vec<String>,
67    pub depends: Vec<String>,
68    pub optdepends: Vec<String>,
69    pub makedepends: Vec<String>,
70    pub conflicts: Vec<String>,
71    pub replaces: Vec<String>,
72    pub provides: Vec<Provide>,
73    pub backups: Vec<String>
74}
75/* Just an idea to try and make things more efficient
76macro_rules! declare_pkg_vars {
77    () => {
78        let mut name: String;
79        let mut version: String;
80        let mut description: String;
81        let mut url: String;
82        let mut packager: String;
83        let mut architecture: String;
84        
85        let mut groups: Vec<String>;
86        let mut licenses: Vec<String>;
87        let mut depends: Vec<String>;
88        let mut optdepends: Vec<String>;
89        let mut makedepends: Vec<String>;
90        let mut conflicts: Vec<String>;
91        let mut replaces: Vec<String>;
92        let mut provides: Vec<String>;
93        let mut backups: Vec<String>;
94    }
95}
96
97macro_rules! zip_pkg {
98    () => {
99        MetaPackage {
100            name, version,
101            description, url,
102            packager, architecture,
103            groups, licenses,
104            depends, optdepends,
105            makedepends, conflicts,
106            replaces, provides,
107            backups
108        }
109    }
110}*/
111
112// There are probably more efficient ways of doing this
113macro_rules! empty_meta_pkg {
114    () => {
115        MetaPackage {
116            name: String::new(),
117            version: String::new(),
118            description: String::new(),
119            url: String::new(),
120            packager: String::new(),
121            architecture: String::new(),
122            
123            groups: Vec::new(),
124            licenses: Vec::new(),
125            depends: Vec::new(),
126            optdepends: Vec::new(),
127            makedepends: Vec::new(),
128            conflicts: Vec::new(),
129            replaces: Vec::new(),
130            provides: Vec::new(),
131            backups: Vec::new()
132        }
133    }
134}
135
136impl MetaPackage {
137    /// This function parses a `.PKGINFO` file (usually found in the root of a `.pkg.tar.xz`)
138    pub(crate) fn parse_pkginfo(pkginfo_data: String) -> StdResult<MetaPackage, ParseError> {
139        let mut pkg = empty_meta_pkg!();
140        
141        let mut line_num = 0;
142        
143        for line in pkginfo_data.lines() {
144            line_num += 1;
145            
146            let line = line.trim();
147            if line.starts_with("#") || line.len() == 0 {
148                continue
149            }
150            let mut pair = line.splitn(2, '=');
151            let key = pair.next()
152                .ok_or(ParseError::MissingKey(line_num))?
153                .trim().to_string();
154            let value = pair.next()
155                .ok_or(ParseError::MissingValue(line_num, key.to_string()))?
156                .trim().to_string();
157            
158            macro_rules! load_value {
159                ($var:expr) => (if $var.len() == 0 {
160                        $var = value;
161                    } else {
162                        return Err(ParseError::MultipleKeys(line_num, key.to_string()));
163                    })
164            }
165            
166            match key.as_str() {
167                "pkgname" => load_value!(pkg.name),
168                "pkgbase" => {}, // Unused
169                "pkgver" => load_value!(pkg.version),
170                "pkgdesc" => load_value!(pkg.description),
171                "group" => pkg.groups.push(value),
172                "url" => load_value!(pkg.url),
173                "builddate" => {}, //TODO: Parse this
174                "packager" => load_value!(pkg.packager),
175                "size" => {}, //TODO: Parse this
176                "arch" => load_value!(pkg.architecture),
177                "license" => pkg.licenses.push(value),
178                "depend" => pkg.depends.push(value),
179                "optdepend" => pkg.optdepends.push(value),
180                "makedepend" => pkg.makedepends.push(value),
181                "conflict" => pkg.conflicts.push(value),
182                "replaces" => pkg.replaces.push(value),
183                //TODO: Error instead of unwrap
184                "provides" => pkg.provides.push(Provide::parse(&value)
185                    .expect(&format!("Invalid Provide: {}", value))),
186                "backup" => pkg.backups.push(value),
187                "force" => {}, // Deprecated
188                "makepkgopt" => {}, // Unused
189                // Really finickey parser, this...
190                &_ => return Err(ParseError::UnknownKey(line_num, key.to_string()))
191            }
192        }
193        Ok(pkg)
194    }
195    
196    /// Parse a package description (`pkgspec/desc` in a db) from a database
197    //    Maybe make a distinction somehow between a sync and local db?
198    //    Their impls are a little different.
199    pub(crate) fn parse_pkgdesc(desc_data: String) -> StdResult<MetaPackage, ParseError> {
200        let mut pkg = empty_meta_pkg!();
201        // Really not sure if this is going to be accurate.
202        //   Probably doesn't matter
203        let mut line_num: u32 = 0;
204        
205        for entry in desc_data.split("\n\n") {
206            line_num += 1;
207            let mut lines = entry.lines();
208            // ignore empty entries
209            if let Some(key) = lines.next() {
210                line_num += 1;
211                
212                if key == "" {
213                    continue;
214                }
215                
216                macro_rules! load_value {
217                    ($field:ident, $required:expr) => {
218                        if let Some(val) = lines.next() {
219                            line_num += 1;
220                            pkg.$field = val.to_string();
221                        } else if !$required {
222                            line_num += 1;
223                        } else {
224                            return Err(ParseError::MissingValue(line_num, key.to_string()))
225                        }
226                    }
227                }
228                
229                macro_rules! load_value_all {
230                    ($field:ident) => {
231                        pkg.$field = lines.map(|line| line.to_string() ).collect()
232                    }
233                }
234                
235                //BUG: This might not work right if it gets two instances of a key
236                match key {
237                    "%NAME%" => load_value!(name, true),
238                    "%VERSION%" => load_value!(version, true),
239                    "%FILENAME%" => {} //not sure
240                    "%BASE%" => {}, // unused
241                    "%DESC%" => load_value!(description, false),
242                    "%GROUPS%" => load_value_all!(groups),
243                    "%URL%" => load_value!(url, false),
244                    "%LICENSE%" => load_value_all!(licenses),
245                    "%ARCH%" => load_value!(architecture, true),
246                    "%BUILDDATE%" => {}, //TODO
247                    "%INSTALLDATE%" => {}, //TODO: local only
248                    "%REASON%" => {}, //TODO: local only
249                    "%PACKAGER%" => load_value!(packager, true),
250                    "%SIZE%" => {}, //TODO
251                    "%CSIZE%" => {}, // what is this
252                    "%ISIZE%" => {}, // what is this
253                    "%MD5SUM%" => {}, //TODO
254                    "%SHA256SUM%" => {}, //TODO
255                    "%PGPSIG%" => {}, // TODO
256                    "%VALIDATION%" => {}, //TODO: local only
257                    "%REPLACES%" => load_value_all!(replaces),
258                    "%DEPENDS%" => load_value_all!(depends),
259                    "%OPTDEPENDS%" => load_value_all!(optdepends),
260                    "%MAKEDEPENDS%" => load_value_all!(makedepends),
261                    "%CHECKDEPENDS%" => {}, // TODO
262                    "%CONFLICTS%" => load_value_all!(conflicts),
263                    //TODO: Err instead of unwrap
264                    "%PROVIDES%" => pkg.provides = lines
265                        .map(|line| Provide::parse(line)
266                            .expect(&format!("Invalid Provide: {}", line)))
267                        .collect(),
268                    "%DELTAS%" => {}, //TODO
269                    "%FILES%" => {}, //TODO
270                    // Really finickey parser, this...
271                    &_ => return Err(ParseError::UnknownKey(line_num, key.to_string()))
272                }
273            }
274        }
275        //let pkg = zip_pkg!();
276        Ok(pkg)
277    }
278    
279    /// Getter over a private field
280    pub fn version(&self) -> Option<Version> {
281        Version::from(&self.version)
282    }
283    
284    /// `other.name` can either be this package name or be a
285    /// package name provided by this package. Repects versions.
286    pub fn satisfies(&self, other: &Provide) -> bool {
287        // Legit impossible for this to panic
288        let self_provide = Provide::parse(format!("{}={}", self.name, self.version))
289            .expect(&format!("{:?} does not have a name", self));
290        
291        self_provide.satisfies(other) || self.provides.iter()
292            .any(|provide| provide.satisfies(other) )
293    }
294}