pkg-utils 0.1.0

A reimplementation of some pacman functionality in pure rust. WIP
Documentation
//! Db abstraction, providing access and search. Currently
//! mostly unoptimized, contribs here would be appreciated

use std::io::Read;
use std::fs::{File, read_dir};
use std::path::{Path, PathBuf};

use flate2::read::GzDecoder;
use itertools::process_results;
use rayon::prelude::*;
use tar::Archive;

use Provide;
use error::Result;
use package::MetaPackage;

#[derive(Debug)]
pub struct Db {
    path: PathBuf,
    pub packages: Vec<MetaPackage>
}

impl Db {
    /// Pass this `"/var/lib/pacman"`. WIP: This is multithreaded, but
    /// threads are hard and this is probably naive.
    pub fn sync_dbs(location: impl AsRef<Path>) -> Result<Vec<Db>> {
        let mut sync_dbs_path = PathBuf::from(location.as_ref());
        sync_dbs_path.push("sync");
        
        let mut dbs = Vec::new();
        for db_path in read_dir(&sync_dbs_path)? {
            let db_path = db_path?.path();
            if let Some(extension) = db_path.extension() {
                if extension == "db" {
                    dbs.push(db_path.to_path_buf());
                }
            }
        }
        
        let dbs: Vec<_> = dbs.par_iter()
            .map(|db_path| -> Result<Db> {
                let packages = Db::sync_db_pkgs(&db_path)?;
                Ok(Db {
                    path: db_path.to_path_buf(),
                    packages: packages
                })
            })
            .collect();
        
        process_results(dbs, |iter| iter.collect() )
    }
    
    /// Pass this `"/var/lib/pacman"`.
    pub fn local_db(location: impl AsRef<Path>) -> Result<Db> {
        let mut local_db_path = PathBuf::from(location.as_ref());
        local_db_path.push("local");
        let packages = Db::local_db_pkgs(&local_db_path)?;
        Ok(Db {
            path: local_db_path,
            packages: packages
        })
    }
    
    fn local_db_pkgs(path: impl AsRef<Path>) -> Result<Vec<MetaPackage>> {
        let mut pkgs = Vec::with_capacity(200);
        let mut iter_count = 0;
        
        for spec_dir in read_dir(&path)? {
            let spec_dir = spec_dir?;
            let mut pkg_path = spec_dir.path();
            
            if pkg_path != Path::join(path.as_ref(), "ALPM_DB_VERSION") {
                iter_count += 1;
                if iter_count >= 200 {
                    iter_count = 0;
                    pkgs.reserve(200);
                }
                pkg_path.push("desc");
                let mut file = File::open(pkg_path)?;
                
                let mut data = String::new();
                file.read_to_string(&mut data)?;
                pkgs.push(MetaPackage::parse_pkgdesc(data)?);
            }
        }
        Ok(pkgs)
    }
    
    fn sync_db_pkgs(path: impl AsRef<Path>) -> Result<Vec<MetaPackage>> {
        let db = File::open(&path)?;
        let db = GzDecoder::new(db);
        let mut db = Archive::new(db);
        
        let mut pkgs = Vec::with_capacity(200);
        let mut iter_count = 0;
        
        for desc in db.entries()? {
            let mut desc = desc?;
            let path = desc.path()?.to_path_buf();
            
            if path.to_string_lossy() != "ALPM_DB_VERSION" {
                iter_count += 1;
                if iter_count >= 200 {
                    iter_count = 0;
                    pkgs.reserve(200);
                }
                let mut data = String::new();
                desc.read_to_string(&mut data)?;
                
                pkgs.push(MetaPackage::parse_pkgdesc(data)?);
            }
        }
        Ok(pkgs)
    }
    
    /// Get some package metadata from this Db. Helper function over this
    /// function's source
    pub fn pkg(&self, pkgname: impl AsRef<str>) -> Option<&MetaPackage> {
        self.packages.iter()
            .find(|pkg| pkg.name == pkgname.as_ref() )
    }
    
    /// Determines if a `Provide` is provided by this db.
    /// ```rust
    /// # use pkg_utils::{db::Db, Provide};
    /// let db = Db::local_db("/var/lib/pacman").unwrap();
    /// // This indicates any version
    /// db.provides(&Provide::parse("java-environment").unwrap());
    /// // Version 8 only
    /// db.provides(&Provide::parse("java-environment=8").unwrap());
    /// ```
    pub fn provides(&self, provide: &Provide) -> bool {
        self.packages.iter()
            .any(|pkg| pkg.satisfies(provide) )
    }
}