awpack 0.1.0

A package manager for the Awesome tiling window manager
use Error;
use package::{Package, PackagePath, PackageError};

use std::collections::HashMap;
use std::path::PathBuf;
use std::fs;

use walkdir::WalkDir;
use git2;

pub struct PackageManager
{
    pub directory: PathBuf,
    pub packages: HashMap<PackagePath, Package>,
}

impl PackageManager
{
    pub fn new(directory: PathBuf) -> Result<Self, Error> {
        let mut pm = PackageManager {
            directory: directory,
            packages: HashMap::new(),
        };

        pm.update_package_cache()?;

        Ok(pm)
    }

    pub fn install(&mut self, path: &PackagePath) -> Result<(), Error> {
        let url = path.url();

        let destination = self.package_directory(path);

        if destination.exists() {
            return Err(Error::Package(PackageError::AlreadyExists));
        }

        // Clone the repo to a temporary folder.
        git2::Repository::clone(&url, &destination).expect("error cloning");

        let package = Package::open(path.clone(), &destination)?;

        self.packages.insert(package.remote_path.clone(), package);

        Ok(())
    }

    pub fn uninstall(&mut self, path: &PackagePath) -> Result<(), Error> {
        match self.packages.remove(path) {
            Some(package) => {
                package.destroy()?;
                Ok(())
            },
            None => Err(Error::Package(PackageError::NotInstalled)),
        }
    }

    pub fn lookup(&self, path: &PackagePath) -> Option<&Package> {
        self.packages.get(path)
    }

    pub fn lookup_mut(&mut self, path: &PackagePath) -> Option<&mut Package> {
        self.packages.get_mut(path)
    }

    fn ensure_directory_exist(&mut self) -> Result<(), Error> {
        if !self.directory.exists() {
            fs::create_dir_all(&self.directory)?
        }

        Ok(())
    }

    fn update_package_cache(&mut self) -> Result<(), Error> {
        self.ensure_directory_exist()?;

        self.packages = self.current_packages(&self.directory)?;
        Ok(())
    }

    fn package_directory(&self, path: &PackagePath) -> PathBuf {
        self.directory.join(path.directory())
    }

    fn current_packages(&self, directory: &PathBuf) -> Result<HashMap<PackagePath, Package>, Error> {
        let manifest_paths: Vec<_> = WalkDir::new(directory).min_depth(1).into_iter().filter_map(|entry| {
            let entry = entry.unwrap();
            let path = entry.path().to_owned();

            if let Some(name) = path.file_name() {
                if name == "package.toml" { Some(path.clone()) } else { None }
            } else {
                None
            }
        }).collect();

        let package_paths: Vec<_> = manifest_paths.into_iter().map(|path| path.parent().unwrap().to_owned()).collect();

        let packages: Result<Vec<_>, _> = package_paths.into_iter().map(|package_path| {
            let relative_path = package_path.strip_prefix(&self.directory).unwrap();
            let components: Vec<_> = relative_path.components().collect();

            assert_eq!(components.len(), 2);

            let remote_path = PackagePath::GitHub {
                organisation: components[0].as_os_str().to_str().unwrap().to_owned(),
                repository: components[1].as_os_str().to_str().unwrap().to_owned(),
            };

            Package::open(remote_path, &package_path)
        }).collect();

        let packages = packages?;

        Ok(packages.into_iter().map(|package| (package.remote_path.clone(), package)).collect())
    }

}