<commit_before>// Copyright:: Copyright (c) 2015-2016 The Habitat Maintainers
//
// The terms of the Evaluation Agreement (Habitat) between Chef Software Inc.
// and the party accessing this file ("Licensee") apply to Licensee's use of
// the Software until such time that the Software is made available under an
// open source license such as the Apache 2.0 License.

use std;
use std::collections::HashSet;
use std::cmp::{Ordering, PartialOrd};
use std::env;
use std::fs::{DirEntry, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use error::{Error, Result};
use fs::{self, PKG_PATH};
use package::{Identifiable, MetaFile, PackageIdent};

#[derive(Clone, Debug)]
pub struct PackageInstall {
    ident: PackageIdent,
    fs_root_path: PathBuf,
    package_root_path: PathBuf,
    installed_path: PathBuf,
}

impl PackageInstall {
    /// Verifies an installation of a package is within the package path and returns a struct
    /// representing that package installation.
    ///
    /// Only the origin and name of a package are required - the latest version/release of a
    /// package will be returned if their optional value is not specified. If only a version is
    /// specified, the latest release of that package origin, name, and version is returned.
    ///
    /// An optional `fs_root` path may be provided to search for a package that is mounted on a
    /// filesystem not currently rooted at `/`.
    pub fn load(ident: &PackageIdent, fs_root_path: Option<&Path>) -> Result<PackageInstall> {
        let fs_root_path = fs_root_path.unwrap_or(Path::new("/"));
        let package_root_path = fs_root_path.join(PKG_PATH);
        let pl = try!(Self::package_list(&package_root_path));
        if ident.fully_qualified() {
            if pl.iter().any(|ref p| p.satisfies(ident)) {
                Ok(PackageInstall {
                    ident: ident.clone(),
                    fs_root_path: PathBuf::from(fs_root_path),
                    package_root_path: package_root_path.clone(),
                    installed_path: try!(Self::calc_installed_path(ident, &package_root_path)),
                })
            } else {
                Err(Error::PackageNotFound(ident.clone()))
            }
        } else {
            let latest: Option<PackageIdent> = pl.iter()
                .filter(|&p| p.satisfies(ident))
                .fold(None, |winner, b| {
                    match winner {
                        Some(a) => {
                            match a.partial_cmp(&b) {
                                Some(Ordering::Greater) => Some(a),
                                Some(Ordering::Equal) => Some(a),
                                Some(Ordering::Less) => Some(b.clone()),
                                None => Some(a),
                            }
                        }
                        None => Some(b.clone()),
                    }
                });
            if let Some(id) = latest {
                Ok(PackageInstall {
                    ident: id.clone(),
                    fs_root_path: PathBuf::from(fs_root_path),
                    package_root_path: package_root_path.clone(),
                    installed_path: try!(Self::calc_installed_path(&id, &package_root_path)),
                })
            } else {
                Err(Error::PackageNotFound(ident.clone()))
            }
        }
    }

    pub fn new_from_parts(ident: PackageIdent,
                          fs_root_path: PathBuf,
                          package_root_path: PathBuf,
                          installed_path: PathBuf)
                          -> PackageInstall {
        PackageInstall {
            ident: ident,
            fs_root_path: fs_root_path,
            package_root_path: package_root_path,
            installed_path: installed_path,
        }
    }

    pub fn deps(&self) -> Result<Vec<PackageIdent>> {
        self.read_deps(MetaFile::Deps)
    }

    pub fn tdeps(&self) -> Result<Vec<PackageIdent>> {
        self.read_deps(MetaFile::TDeps)
    }

    /// A vector of ports we expose
    pub fn exposes(&self) -> Result<Vec<String>> {
        match self.read_metafile(MetaFile::Exposes) {
            Ok(body) => {
                let v: Vec<String> = body.split(' ')
                    .map(|x| String::from(x.trim_right_matches('\n')))
                    .collect();
                Ok(v)
            }
            Err(Error::MetaFileNotFound(MetaFile::Exposes)) => {
                let v: Vec<String> = Vec::new();
                Ok(v)
            }
            Err(e) => Err(e),
        }
    }

    pub fn ident(&self) -> &PackageIdent {
        &self.ident
    }

    /// Return the PATH string from the package metadata, if it exists
    ///
    /// # Failures
    ///
    /// * The package contains a Path metafile but it could not be read or it was malformed
    pub fn paths(&self) -> Result<Vec<PathBuf>> {
        match self.read_metafile(MetaFile::Path) {
            Ok(body) => {
                let v = env::split_paths(&body).map(|p| PathBuf::from(&p)).collect();
                Ok(v)
            }
            Err(Error::MetaFileNotFound(MetaFile::Path)) => {
                let v: Vec<PathBuf> = Vec::new();
                Ok(v)
            }
            Err(e) => Err(e),
        }
    }

    /// Returns a `String` with the full run path for this package. The `PATH` string will be
    /// constructed by add all `PATH` metadata entries from the *direct* dependencies first (in
    /// declared order) and then from any remaining transitive dependencies last (in lexically
    /// sorted order).
    pub fn runtime_path(&self) -> Result<String> {
        let mut idents = HashSet::new();
        let mut run_path = String::new();
        for path in try!(self.paths()) {
            run_path.push_str(&path.to_string_lossy());
            idents.insert(self.ident().clone());
        }
        let deps: Vec<PackageInstall> = try!(self.load_deps());
        for dep in deps.iter() {
            for path in try!(dep.paths()) {
                run_path.push(':');
                run_path.push_str(&path.to_string_lossy());
                idents.insert(dep.ident().clone());
            }
        }
        let tdeps: Vec<PackageInstall> = try!(self.load_tdeps());
        for dep in tdeps.iter() {
            if idents.contains(dep.ident()) {
                continue;
            }
            for path in try!(dep.paths()) {
                run_path.push(':');
                run_path.push_str(&path.to_string_lossy());
                idents.insert(dep.ident().clone());
            }
        }
        Ok(run_path)
    }

    pub fn installed_path(&self) -> &PathBuf {
        &self.installed_path
    }

    /// Returns the root path for service configuration, files, and data.
    pub fn svc_path(&self) -> PathBuf {
        fs::svc_path(&self.ident.name)
    }

    /// Returns the path to the service configuration.
    pub fn svc_config_path(&self) -> PathBuf {
        fs::svc_config_path(&self.ident.name)
    }

    /// Returns the path to the service data.
    pub fn svc_data_path(&self) -> PathBuf {
        fs::svc_data_path(&self.ident.name)
    }

    /// Returns the path to the service's gossiped config files.
    pub fn svc_files_path(&self) -> PathBuf {
        fs::svc_files_path(&self.ident.name)
    }

    /// Returns the path to the service hooks.
    pub fn svc_hooks_path(&self) -> PathBuf {
        fs::svc_hooks_path(&self.ident.name)
    }

    /// Returns the path to the service static content.
    pub fn svc_static_path(&self) -> PathBuf {
        fs::svc_static_path(&self.ident.name)
    }

    /// Returns the path to the service variable state.
    pub fn svc_var_path(&self) -> PathBuf {
        fs::svc_var_path(&self.ident.name)
    }

    /// Read the contents of a given metafile.
    ///
    /// # Failures
    ///
    /// * A metafile could not be found
    /// * Contents of the metafile could not be read
    /// * Contents of the metafile are unreadable or malformed
    fn read_metafile(&self, file: MetaFile) -> Result<String> {
        let filepath = self.installed_path.join(file.to_string());
        match std::fs::metadata(&filepath) {
            Ok(_) => {
                match File::open(&filepath) {
                    Ok(mut f) => {
                        let mut data = String::new();
                        if f.read_to_string(&mut data).is_err() {
                            return Err(Error::MetaFileMalformed(file));
                        }
                        Ok(data.trim().to_string())
                    }
                    Err(e) => Err(Error::MetaFileIO(e)),
                }
            }
            Err(_) => Err(Error::MetaFileNotFound(file)),
        }
    }

    /// Reads metafiles containing dependencies represented by package identifiers separated by new
    /// lines.
    ///
    /// # Failures
    ///
    /// * Metafile could not be found
    /// * Contents of the metafile could not be read
    /// * Contents of the metafile are unreadable or malformed
    fn read_deps(&self, file: MetaFile) -> Result<Vec<PackageIdent>> {
        let mut deps: Vec<PackageIdent> = vec![];
        match self.read_metafile(file) {
            Ok(body) => {
                let ids: Vec<String> = body.split("\n").map(|d| d.to_string()).collect();
                for id in &ids {
                    let package = try!(PackageIdent::from_str(id));
                    if !package.fully_qualified() {
                        return Err(Error::InvalidPackageIdent(package.to_string()));
                    }
                    deps.push(package);
                }
                Ok(deps)
            }
            Err(Error::MetaFileNotFound(_)) => Ok(deps),
            Err(e) => Err(e),
        }
    }

    /// Attempts to load the extracted package for each direct dependency and returns a
    /// `Package` struct representation of each in the returned vector.
    ///
    /// # Failures
    ///
    /// * Any direct dependency could not be located or it's contents could not be read
    ///   from disk
    fn load_deps(&self) -> Result<Vec<PackageInstall>> {
        let ddeps = try!(self.deps());
        let mut deps = Vec::with_capacity(ddeps.len());
        for dep in ddeps.iter() {
            let dep_install = try!(Self::load(dep, Some(&self.fs_root_path)));
            deps.push(dep_install);
        }
        Ok(deps)
    }

    /// Attempts to load the extracted package for each transitive dependency and returns a
    /// `Package` struct representation of each in the returned vector.
    ///
    /// # Failures
    ///
    /// * Any transitive dependency could not be located or it's contents could not be read
    ///   from disk
    fn load_tdeps(&self) -> Result<Vec<PackageInstall>> {
        let tdeps = try!(self.tdeps());
        let mut deps = Vec::with_capacity(tdeps.len());
        for dep in tdeps.iter() {
            let dep_install = try!(Self::load(dep, Some(&self.fs_root_path)));
            deps.push(dep_install);
        }
        Ok(deps)
    }

    fn calc_installed_path(ident: &PackageIdent, pkg_root: &Path) -> Result<PathBuf> {
        if ident.fully_qualified() {
            Ok(pkg_root.join(&ident.origin)
                .join(&ident.name)
                .join(ident.version.as_ref().unwrap())
                .join(ident.release.as_ref().unwrap()))
        } else {
            Err(Error::PackageNotFound(ident.clone()))
        }
    }

    /// Returns a list of package structs built from the contents of the given directory.
    fn package_list(path: &Path) -> Result<Vec<PackageIdent>> {
        let mut package_list: Vec<PackageIdent> = vec![];
        if try!(std::fs::metadata(path)).is_dir() {
            try!(Self::walk_origins(&path, &mut package_list));
        }
        Ok(package_list)
    }

    /// Helper function for package_list. Walks the given path for origin directories
    /// and builds on the given package list by recursing into name, version, and release
    /// directories.
    fn walk_origins(path: &Path, packages: &mut Vec<PackageIdent>) -> Result<()> {
        for entry in try!(std::fs::read_dir(path)) {
            let origin = try!(entry);
            if try!(std::fs::metadata(origin.path())).is_dir() {
                try!(Self::walk_names(&origin, packages));
            }
        }
        Ok(())
    }

    /// Helper function for walk_origins. Walks the given origin DirEntry for name
    /// directories and recurses into them to find version and release directories.
    fn walk_names(origin: &DirEntry, packages: &mut Vec<PackageIdent>) -> Result<()> {
        for name in try!(std::fs::read_dir(origin.path())) {
            let name = try!(name);
            let origin = origin.file_name().to_string_lossy().into_owned().to_string();
            if try!(std::fs::metadata(name.path())).is_dir() {
                try!(Self::walk_versions(&origin, &name, packages));
            }
        }
        Ok(())
    }

    /// Helper fuction for walk_names. Walks the given name DirEntry for directories and recurses
    /// into them to find release directories.
    fn walk_versions(origin: &String,
                     name: &DirEntry,
                     packages: &mut Vec<PackageIdent>)
                     -> Result<()> {
        for version in try!(std::fs::read_dir(name.path())) {
            let version = try!(version);
            let name = name.file_name().to_string_lossy().into_owned().to_string();
            if try!(std::fs::metadata(version.path())).is_dir() {
                try!(Self::walk_releases(origin, &name, &version, packages));
            }
        }
        Ok(())
    }

    /// Helper function for walk_versions. Walks the given release DirEntry for directories and recurses
    /// into them to find version directories. Finally, a Package struct is built and concatenated onto
    /// the given packages vector with the origin, name, version, and release of each.
    fn walk_releases(origin: &String,
                     name: &String,
                     version: &DirEntry,
                     packages: &mut Vec<PackageIdent>)
                     -> Result<()> {
        for release in try!(std::fs::read_dir(version.path())) {
            let release = try!(release).file_name().to_string_lossy().into_owned().to_string();
            let version = version.file_name().to_string_lossy().into_owned().to_string();
            let ident =
                PackageIdent::new(origin.clone(), name.clone(), Some(version), Some(release));
            packages.push(ident)
        }
        Ok(())
    }
}
<commit_msg>[core] Return Err in `PackageInstall::load` when pkg path not present.<commit_after>// Copyright:: Copyright (c) 2015-2016 The Habitat Maintainers
//
// The terms of the Evaluation Agreement (Habitat) between Chef Software Inc.
// and the party accessing this file ("Licensee") apply to Licensee's use of
// the Software until such time that the Software is made available under an
// open source license such as the Apache 2.0 License.

use std;
use std::collections::HashSet;
use std::cmp::{Ordering, PartialOrd};
use std::env;
use std::fs::{DirEntry, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use error::{Error, Result};
use fs::{self, PKG_PATH};
use package::{Identifiable, MetaFile, PackageIdent};

#[derive(Clone, Debug)]
pub struct PackageInstall {
    ident: PackageIdent,
    fs_root_path: PathBuf,
    package_root_path: PathBuf,
    installed_path: PathBuf,
}

impl PackageInstall {
    /// Verifies an installation of a package is within the package path and returns a struct
    /// representing that package installation.
    ///
    /// Only the origin and name of a package are required - the latest version/release of a
    /// package will be returned if their optional value is not specified. If only a version is
    /// specified, the latest release of that package origin, name, and version is returned.
    ///
    /// An optional `fs_root` path may be provided to search for a package that is mounted on a
    /// filesystem not currently rooted at `/`.
    pub fn load(ident: &PackageIdent, fs_root_path: Option<&Path>) -> Result<PackageInstall> {
        let fs_root_path = fs_root_path.unwrap_or(Path::new("/"));
        let package_root_path = fs_root_path.join(PKG_PATH);
        if !package_root_path.exists() {
            return Err(Error::PackageNotFound(ident.clone()));
        }
        let pl = try!(Self::package_list(&package_root_path));
        if ident.fully_qualified() {
            if pl.iter().any(|ref p| p.satisfies(ident)) {
                Ok(PackageInstall {
                    ident: ident.clone(),
                    fs_root_path: PathBuf::from(fs_root_path),
                    package_root_path: package_root_path.clone(),
                    installed_path: try!(Self::calc_installed_path(ident, &package_root_path)),
                })
            } else {
                Err(Error::PackageNotFound(ident.clone()))
            }
        } else {
            let latest: Option<PackageIdent> = pl.iter()
                .filter(|&p| p.satisfies(ident))
                .fold(None, |winner, b| {
                    match winner {
                        Some(a) => {
                            match a.partial_cmp(&b) {
                                Some(Ordering::Greater) => Some(a),
                                Some(Ordering::Equal) => Some(a),
                                Some(Ordering::Less) => Some(b.clone()),
                                None => Some(a),
                            }
                        }
                        None => Some(b.clone()),
                    }
                });
            if let Some(id) = latest {
                Ok(PackageInstall {
                    ident: id.clone(),
                    fs_root_path: PathBuf::from(fs_root_path),
                    package_root_path: package_root_path.clone(),
                    installed_path: try!(Self::calc_installed_path(&id, &package_root_path)),
                })
            } else {
                Err(Error::PackageNotFound(ident.clone()))
            }
        }
    }

    pub fn new_from_parts(ident: PackageIdent,
                          fs_root_path: PathBuf,
                          package_root_path: PathBuf,
                          installed_path: PathBuf)
                          -> PackageInstall {
        PackageInstall {
            ident: ident,
            fs_root_path: fs_root_path,
            package_root_path: package_root_path,
            installed_path: installed_path,
        }
    }

    pub fn deps(&self) -> Result<Vec<PackageIdent>> {
        self.read_deps(MetaFile::Deps)
    }

    pub fn tdeps(&self) -> Result<Vec<PackageIdent>> {
        self.read_deps(MetaFile::TDeps)
    }

    /// A vector of ports we expose
    pub fn exposes(&self) -> Result<Vec<String>> {
        match self.read_metafile(MetaFile::Exposes) {
            Ok(body) => {
                let v: Vec<String> = body.split(' ')
                    .map(|x| String::from(x.trim_right_matches('\n')))
                    .collect();
                Ok(v)
            }
            Err(Error::MetaFileNotFound(MetaFile::Exposes)) => {
                let v: Vec<String> = Vec::new();
                Ok(v)
            }
            Err(e) => Err(e),
        }
    }

    pub fn ident(&self) -> &PackageIdent {
        &self.ident
    }

    /// Return the PATH string from the package metadata, if it exists
    ///
    /// # Failures
    ///
    /// * The package contains a Path metafile but it could not be read or it was malformed
    pub fn paths(&self) -> Result<Vec<PathBuf>> {
        match self.read_metafile(MetaFile::Path) {
            Ok(body) => {
                let v = env::split_paths(&body).map(|p| PathBuf::from(&p)).collect();
                Ok(v)
            }
            Err(Error::MetaFileNotFound(MetaFile::Path)) => {
                let v: Vec<PathBuf> = Vec::new();
                Ok(v)
            }
            Err(e) => Err(e),
        }
    }

    /// Returns a `String` with the full run path for this package. The `PATH` string will be
    /// constructed by add all `PATH` metadata entries from the *direct* dependencies first (in
    /// declared order) and then from any remaining transitive dependencies last (in lexically
    /// sorted order).
    pub fn runtime_path(&self) -> Result<String> {
        let mut idents = HashSet::new();
        let mut run_path = String::new();
        for path in try!(self.paths()) {
            run_path.push_str(&path.to_string_lossy());
            idents.insert(self.ident().clone());
        }
        let deps: Vec<PackageInstall> = try!(self.load_deps());
        for dep in deps.iter() {
            for path in try!(dep.paths()) {
                run_path.push(':');
                run_path.push_str(&path.to_string_lossy());
                idents.insert(dep.ident().clone());
            }
        }
        let tdeps: Vec<PackageInstall> = try!(self.load_tdeps());
        for dep in tdeps.iter() {
            if idents.contains(dep.ident()) {
                continue;
            }
            for path in try!(dep.paths()) {
                run_path.push(':');
                run_path.push_str(&path.to_string_lossy());
                idents.insert(dep.ident().clone());
            }
        }
        Ok(run_path)
    }

    pub fn installed_path(&self) -> &PathBuf {
        &self.installed_path
    }

    /// Returns the root path for service configuration, files, and data.
    pub fn svc_path(&self) -> PathBuf {
        fs::svc_path(&self.ident.name)
    }

    /// Returns the path to the service configuration.
    pub fn svc_config_path(&self) -> PathBuf {
        fs::svc_config_path(&self.ident.name)
    }

    /// Returns the path to the service data.
    pub fn svc_data_path(&self) -> PathBuf {
        fs::svc_data_path(&self.ident.name)
    }

    /// Returns the path to the service's gossiped config files.
    pub fn svc_files_path(&self) -> PathBuf {
        fs::svc_files_path(&self.ident.name)
    }

    /// Returns the path to the service hooks.
    pub fn svc_hooks_path(&self) -> PathBuf {
        fs::svc_hooks_path(&self.ident.name)
    }

    /// Returns the path to the service static content.
    pub fn svc_static_path(&self) -> PathBuf {
        fs::svc_static_path(&self.ident.name)
    }

    /// Returns the path to the service variable state.
    pub fn svc_var_path(&self) -> PathBuf {
        fs::svc_var_path(&self.ident.name)
    }

    /// Read the contents of a given metafile.
    ///
    /// # Failures
    ///
    /// * A metafile could not be found
    /// * Contents of the metafile could not be read
    /// * Contents of the metafile are unreadable or malformed
    fn read_metafile(&self, file: MetaFile) -> Result<String> {
        let filepath = self.installed_path.join(file.to_string());
        match std::fs::metadata(&filepath) {
            Ok(_) => {
                match File::open(&filepath) {
                    Ok(mut f) => {
                        let mut data = String::new();
                        if f.read_to_string(&mut data).is_err() {
                            return Err(Error::MetaFileMalformed(file));
                        }
                        Ok(data.trim().to_string())
                    }
                    Err(e) => Err(Error::MetaFileIO(e)),
                }
            }
            Err(_) => Err(Error::MetaFileNotFound(file)),
        }
    }

    /// Reads metafiles containing dependencies represented by package identifiers separated by new
    /// lines.
    ///
    /// # Failures
    ///
    /// * Metafile could not be found
    /// * Contents of the metafile could not be read
    /// * Contents of the metafile are unreadable or malformed
    fn read_deps(&self, file: MetaFile) -> Result<Vec<PackageIdent>> {
        let mut deps: Vec<PackageIdent> = vec![];
        match self.read_metafile(file) {
            Ok(body) => {
                let ids: Vec<String> = body.split("\n").map(|d| d.to_string()).collect();
                for id in &ids {
                    let package = try!(PackageIdent::from_str(id));
                    if !package.fully_qualified() {
                        return Err(Error::InvalidPackageIdent(package.to_string()));
                    }
                    deps.push(package);
                }
                Ok(deps)
            }
            Err(Error::MetaFileNotFound(_)) => Ok(deps),
            Err(e) => Err(e),
        }
    }

    /// Attempts to load the extracted package for each direct dependency and returns a
    /// `Package` struct representation of each in the returned vector.
    ///
    /// # Failures
    ///
    /// * Any direct dependency could not be located or it's contents could not be read
    ///   from disk
    fn load_deps(&self) -> Result<Vec<PackageInstall>> {
        let ddeps = try!(self.deps());
        let mut deps = Vec::with_capacity(ddeps.len());
        for dep in ddeps.iter() {
            let dep_install = try!(Self::load(dep, Some(&self.fs_root_path)));
            deps.push(dep_install);
        }
        Ok(deps)
    }

    /// Attempts to load the extracted package for each transitive dependency and returns a
    /// `Package` struct representation of each in the returned vector.
    ///
    /// # Failures
    ///
    /// * Any transitive dependency could not be located or it's contents could not be read
    ///   from disk
    fn load_tdeps(&self) -> Result<Vec<PackageInstall>> {
        let tdeps = try!(self.tdeps());
        let mut deps = Vec::with_capacity(tdeps.len());
        for dep in tdeps.iter() {
            let dep_install = try!(Self::load(dep, Some(&self.fs_root_path)));
            deps.push(dep_install);
        }
        Ok(deps)
    }

    fn calc_installed_path(ident: &PackageIdent, pkg_root: &Path) -> Result<PathBuf> {
        if ident.fully_qualified() {
            Ok(pkg_root.join(&ident.origin)
                .join(&ident.name)
                .join(ident.version.as_ref().unwrap())
                .join(ident.release.as_ref().unwrap()))
        } else {
            Err(Error::PackageNotFound(ident.clone()))
        }
    }

    /// Returns a list of package structs built from the contents of the given directory.
    fn package_list(path: &Path) -> Result<Vec<PackageIdent>> {
        let mut package_list: Vec<PackageIdent> = vec![];
        if try!(std::fs::metadata(path)).is_dir() {
            try!(Self::walk_origins(&path, &mut package_list));
        }
        Ok(package_list)
    }

    /// Helper function for package_list. Walks the given path for origin directories
    /// and builds on the given package list by recursing into name, version, and release
    /// directories.
    fn walk_origins(path: &Path, packages: &mut Vec<PackageIdent>) -> Result<()> {
        for entry in try!(std::fs::read_dir(path)) {
            let origin = try!(entry);
            if try!(std::fs::metadata(origin.path())).is_dir() {
                try!(Self::walk_names(&origin, packages));
            }
        }
        Ok(())
    }

    /// Helper function for walk_origins. Walks the given origin DirEntry for name
    /// directories and recurses into them to find version and release directories.
    fn walk_names(origin: &DirEntry, packages: &mut Vec<PackageIdent>) -> Result<()> {
        for name in try!(std::fs::read_dir(origin.path())) {
            let name = try!(name);
            let origin = origin.file_name().to_string_lossy().into_owned().to_string();
            if try!(std::fs::metadata(name.path())).is_dir() {
                try!(Self::walk_versions(&origin, &name, packages));
            }
        }
        Ok(())
    }

    /// Helper fuction for walk_names. Walks the given name DirEntry for directories and recurses
    /// into them to find release directories.
    fn walk_versions(origin: &String,
                     name: &DirEntry,
                     packages: &mut Vec<PackageIdent>)
                     -> Result<()> {
        for version in try!(std::fs::read_dir(name.path())) {
            let version = try!(version);
            let name = name.file_name().to_string_lossy().into_owned().to_string();
            if try!(std::fs::metadata(version.path())).is_dir() {
                try!(Self::walk_releases(origin, &name, &version, packages));
            }
        }
        Ok(())
    }

    /// Helper function for walk_versions. Walks the given release DirEntry for directories and recurses
    /// into them to find version directories. Finally, a Package struct is built and concatenated onto
    /// the given packages vector with the origin, name, version, and release of each.
    fn walk_releases(origin: &String,
                     name: &String,
                     version: &DirEntry,
                     packages: &mut Vec<PackageIdent>)
                     -> Result<()> {
        for release in try!(std::fs::read_dir(version.path())) {
            let release = try!(release).file_name().to_string_lossy().into_owned().to_string();
            let version = version.file_name().to_string_lossy().into_owned().to_string();
            let ident =
                PackageIdent::new(origin.clone(), name.clone(), Some(version), Some(release));
            packages.push(ident)
        }
        Ok(())
    }
}
<|endoftext|>


<commit_before>use std::fs::File;
use std::io::{ BufRead, BufReader };
use std::path::Path;

fn parse_list(path: &Path) -> Result<Vec<(String, u32)>, String> {
    let list = match File::open(path) {
        Ok(x) => x,
        Err(why) => return Err(format!("{}", why))
    };
    let list = BufReader::new(list);

    let parsed_packages = Vec::new();
    for line in list.lines() {
        let split: Vec<&str> = line.unwrap_or(String::from("")).trim().split('=').collect();
        let package_name = String::from(split[0]);
        let package_version = split[1].parse::<u32>().unwrap_or(0); // For this reason, version of packages MUST NOT BE 0

        if !package_name.is_empty() || package_version != 0 {
            parsed_packages.push((package_name, package_version));
        }
    }

    if parsed_packages.len() > 0 {
        return Ok(parsed_packages)
    } else {
        return Err(String::from("No package found."))
    }
}

fn get_version(name: &str, path: &Path) -> Option<u32> {
    //TODO:
    //  Gets the version of the package

}

fn put_version() -> Result<bool, String>{
    //TODO:
    // Sets/Updates the version of the package
}<commit_msg>get_version() implementation<commit_after>use std::fs::File;
use std::io::{ BufRead, BufReader };
use std::path::Path;

fn parse_list(path: &Path) -> Result<Vec<(String, u32)>, String> {
    let list = match File::open(path) {
        Ok(x) => x,
        Err(why) => return Err(format!("{}", why))
    };
    let list = BufReader::new(list);

    let parsed_packages = Vec::new();
    for line in list.lines() {
        let split: Vec<&str> = line.unwrap_or(String::from("")).trim().split('=').collect();
        let package_name = String::from(split[0]);
        let package_version = split[1].parse::<u32>().unwrap_or(0); // For this reason, version of packages MUST NOT BE 0

        if !package_name.is_empty() || package_version != 0 {
            parsed_packages.push((package_name, package_version));
        }
    }

    if parsed_packages.len() > 0 {
        return Ok(parsed_packages)
    } else {
        return Err(String::from("No package found."))
    }
}

pub fn get_version(name: &str, path: &Path) -> Option<u32> {
    //TODO:
    //  Gets the version of the package
    let list = match parse_list(path) {
        Ok(x) => x,
        Err(why) => return None
    };

    for package in list {
        if package.0 == name {
            return Some(package.1)
        }
    }

    return None
}

pub fn update_version() -> Result<bool, String>{
    //TODO:
    // Sets/Updates the version of the package
}<|endoftext|>


<commit_before>#![feature(plugin_registrar, rustc_private)]

extern crate itertools;
extern crate rustc_plugin;
extern crate syntax;

use itertools::Itertools;
use rustc_plugin::Registry;
use syntax::ast::{self, Ident, TraitRef, Ty, TyKind};
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
use syntax::ext::quote::rt::Span;
use syntax::parse::{self, token, str_lit, PResult};
use syntax::parse::parser::{Parser, PathStyle};
use syntax::symbol::Symbol;
use syntax::ptr::P;
use syntax::tokenstream::{TokenTree, TokenStream};
use syntax::util::small_vector::SmallVector;

fn snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
    // The `expand_expr` method is called so that any macro calls in the
    // parsed expression are expanded.

    let mut item = match parser.parse_trait_item() {
        Ok(s) => s,
        Err(mut diagnostic) => {
            diagnostic.emit();
            return DummyResult::any(sp);
        }
    };

    if let Err(mut diagnostic) = parser.expect(&token::Eof) {
        diagnostic.emit();
        return DummyResult::any(sp);
    }

    let old_ident = convert(&mut item.ident);

    // As far as I know, it's not possible in macro_rules! to reference an $ident in a doc string,
    // so this is the hacky workaround.
    //
    // This code looks intimidating, but it's just iterating through the trait item's attributes
    // copying non-doc attributes, and modifying doc attributes such that replacing any {} in the
    // doc string instead holds the original, snake_case ident.
    let attrs: Vec<_> = item.attrs
        .drain(..)
        .map(|mut attr| {
            if !attr.is_sugared_doc {
                return attr;
            }

            // Getting at the underlying doc comment is surprisingly painful.
            // The call-chain goes something like:
            //
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L283
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L1067
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L1196
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/parse/mod.rs#L399
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/parse/mod.rs#L268
            //
            // Note that a docstring (i.e., something with is_sugared_doc) *always* has exactly two
            // tokens: an Eq followed by a Literal, where the Literal contains a Str_. We therefore
            // match against that, modifying the inner Str with our modified Symbol.
            let mut tokens = attr.tokens.clone().into_trees();
            if let Some(tt @ TokenTree::Token(_, token::Eq)) = tokens.next() {
                let mut docstr = tokens.next().expect("Docstrings must have literal docstring");
                if let TokenTree::Token(_, token::Literal(token::Str_(ref mut doc), _)) = docstr {
                    *doc = Symbol::intern(&str_lit(&doc.as_str()).replace("{}", &old_ident));
                } else {
                    unreachable!();
                }
                attr.tokens = TokenStream::concat(vec![tt.into(), docstr.into()]);
            } else {
                unreachable!();
            }

            attr
        })
        .collect();
    item.attrs.extend(attrs.into_iter());

    MacEager::trait_items(SmallVector::one(item))
}

fn impl_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
    // The `expand_expr` method is called so that any macro calls in the
    // parsed expression are expanded.

    let mut item = match parser.parse_impl_item() {
        Ok(s) => s,
        Err(mut diagnostic) => {
            diagnostic.emit();
            return DummyResult::any(sp);
        }
    };

    if let Err(mut diagnostic) = parser.expect(&token::Eof) {
        diagnostic.emit();
        return DummyResult::any(sp);
    }

    convert(&mut item.ident);
    MacEager::impl_items(SmallVector::one(item))
}

fn ty_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
    // The `expand_expr` method is called so that any macro calls in the
    // parsed expression are expanded.

    let mut ty = match parser.parse_ty_path() {
        Ok(s) => s,
        Err(mut diagnostic) => {
            diagnostic.emit();
            return DummyResult::any(sp);
        }
    };

    if let Err(mut diagnostic) = parser.expect(&token::Eof) {
        diagnostic.emit();
        return DummyResult::any(sp);
    }

    // Only capitalize the final segment
    if let TyKind::Path(_, ref mut path) = ty {
        convert(&mut path.segments.last_mut().unwrap().identifier);
    } else {
        unreachable!()
    }
    MacEager::ty(P(Ty {
        id: ast::DUMMY_NODE_ID,
        node: ty,
        span: sp,
    }))
}

/// Converts an ident in-place to CamelCase and returns the previous ident.
fn convert(ident: &mut Ident) -> String {
    let ident_str = ident.to_string();
    let mut camel_ty = String::new();

    {
        // Find the first non-underscore and add it capitalized.
        let mut chars = ident_str.chars();

        // Find the first non-underscore char, uppercase it, and append it.
        // Guaranteed to succeed because all idents must have at least one non-underscore char.
        camel_ty.extend(chars.find(|&c| c != '_').unwrap().to_uppercase());

        // When we find an underscore, we remove it and capitalize the next char. To do this,
        // we need to ensure the next char is not another underscore.
        let mut chars = chars.coalesce(|c1, c2| {
            if c1 == '_' && c2 == '_' {
                Ok(c1)
            } else {
                Err((c1, c2))
            }
        });

        while let Some(c) = chars.next() {
            if c != '_' {
                camel_ty.push(c);
            } else {
                if let Some(c) = chars.next() {
                    camel_ty.extend(c.to_uppercase());
                }
            }
        }
    }

    // The Fut suffix is hardcoded right now; this macro isn't really meant to be general-purpose.
    camel_ty.push_str("Fut");

    *ident = Ident::with_empty_ctxt(Symbol::intern(&camel_ty));
    ident_str
}

trait ParseTraitRef {
    fn parse_trait_ref(&mut self) -> PResult<TraitRef>;
}

impl<'a> ParseTraitRef for Parser<'a> {
    /// Parse a::B<String,i32>
    fn parse_trait_ref(&mut self) -> PResult<TraitRef> {
        Ok(TraitRef {
            path: self.parse_path(PathStyle::Type)?,
            ref_id: ast::DUMMY_NODE_ID,
        })
    }
}

#[plugin_registrar]
#[doc(hidden)]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("snake_to_camel", snake_to_camel);
    reg.register_macro("impl_snake_to_camel", impl_snake_to_camel);
    reg.register_macro("ty_snake_to_camel", ty_snake_to_camel);
}
<commit_msg>Fix following removal of parse_ty_path (#138)<commit_after>#![feature(plugin_registrar, rustc_private)]

extern crate itertools;
extern crate rustc_plugin;
extern crate syntax;

use itertools::Itertools;
use rustc_plugin::Registry;
use syntax::ast::{self, Ident, TraitRef, Ty, TyKind};
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
use syntax::ext::quote::rt::Span;
use syntax::parse::{self, token, str_lit, PResult};
use syntax::parse::parser::{Parser, PathStyle};
use syntax::symbol::Symbol;
use syntax::ptr::P;
use syntax::tokenstream::{TokenTree, TokenStream};
use syntax::util::small_vector::SmallVector;

fn snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
    // The `expand_expr` method is called so that any macro calls in the
    // parsed expression are expanded.

    let mut item = match parser.parse_trait_item() {
        Ok(s) => s,
        Err(mut diagnostic) => {
            diagnostic.emit();
            return DummyResult::any(sp);
        }
    };

    if let Err(mut diagnostic) = parser.expect(&token::Eof) {
        diagnostic.emit();
        return DummyResult::any(sp);
    }

    let old_ident = convert(&mut item.ident);

    // As far as I know, it's not possible in macro_rules! to reference an $ident in a doc string,
    // so this is the hacky workaround.
    //
    // This code looks intimidating, but it's just iterating through the trait item's attributes
    // copying non-doc attributes, and modifying doc attributes such that replacing any {} in the
    // doc string instead holds the original, snake_case ident.
    let attrs: Vec<_> = item.attrs
        .drain(..)
        .map(|mut attr| {
            if !attr.is_sugared_doc {
                return attr;
            }

            // Getting at the underlying doc comment is surprisingly painful.
            // The call-chain goes something like:
            //
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L283
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L1067
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L1196
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/parse/mod.rs#L399
            //  - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/parse/mod.rs#L268
            //
            // Note that a docstring (i.e., something with is_sugared_doc) *always* has exactly two
            // tokens: an Eq followed by a Literal, where the Literal contains a Str_. We therefore
            // match against that, modifying the inner Str with our modified Symbol.
            let mut tokens = attr.tokens.clone().into_trees();
            if let Some(tt @ TokenTree::Token(_, token::Eq)) = tokens.next() {
                let mut docstr = tokens.next().expect("Docstrings must have literal docstring");
                if let TokenTree::Token(_, token::Literal(token::Str_(ref mut doc), _)) = docstr {
                    *doc = Symbol::intern(&str_lit(&doc.as_str()).replace("{}", &old_ident));
                } else {
                    unreachable!();
                }
                attr.tokens = TokenStream::concat(vec![tt.into(), docstr.into()]);
            } else {
                unreachable!();
            }

            attr
        })
        .collect();
    item.attrs.extend(attrs.into_iter());

    MacEager::trait_items(SmallVector::one(item))
}

fn impl_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
    // The `expand_expr` method is called so that any macro calls in the
    // parsed expression are expanded.

    let mut item = match parser.parse_impl_item() {
        Ok(s) => s,
        Err(mut diagnostic) => {
            diagnostic.emit();
            return DummyResult::any(sp);
        }
    };

    if let Err(mut diagnostic) = parser.expect(&token::Eof) {
        diagnostic.emit();
        return DummyResult::any(sp);
    }

    convert(&mut item.ident);
    MacEager::impl_items(SmallVector::one(item))
}

fn ty_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
    // The `expand_expr` method is called so that any macro calls in the
    // parsed expression are expanded.

    let mut path = match parser.parse_path(PathStyle::Type) {
        Ok(s) => s,
        Err(mut diagnostic) => {
            diagnostic.emit();
            return DummyResult::any(sp);
        }
    };

    if let Err(mut diagnostic) = parser.expect(&token::Eof) {
        diagnostic.emit();
        return DummyResult::any(sp);
    }

    // Only capitalize the final segment
    convert(&mut path.segments
                     .last_mut()
                     .unwrap()
                     .identifier);
    MacEager::ty(P(Ty {
        id: ast::DUMMY_NODE_ID,
        node: TyKind::Path(None, path),
        span: sp,
    }))
}

/// Converts an ident in-place to CamelCase and returns the previous ident.
fn convert(ident: &mut Ident) -> String {
    let ident_str = ident.to_string();
    let mut camel_ty = String::new();

    {
        // Find the first non-underscore and add it capitalized.
        let mut chars = ident_str.chars();

        // Find the first non-underscore char, uppercase it, and append it.
        // Guaranteed to succeed because all idents must have at least one non-underscore char.
        camel_ty.extend(chars.find(|&c| c != '_').unwrap().to_uppercase());

        // When we find an underscore, we remove it and capitalize the next char. To do this,
        // we need to ensure the next char is not another underscore.
        let mut chars = chars.coalesce(|c1, c2| {
            if c1 == '_' && c2 == '_' {
                Ok(c1)
            } else {
                Err((c1, c2))
            }
        });

        while let Some(c) = chars.next() {
            if c != '_' {
                camel_ty.push(c);
            } else {
                if let Some(c) = chars.next() {
                    camel_ty.extend(c.to_uppercase());
                }
            }
        }
    }

    // The Fut suffix is hardcoded right now; this macro isn't really meant to be general-purpose.
    camel_ty.push_str("Fut");

    *ident = Ident::with_empty_ctxt(Symbol::intern(&camel_ty));
    ident_str
}

trait ParseTraitRef {
    fn parse_trait_ref(&mut self) -> PResult<TraitRef>;
}

impl<'a> ParseTraitRef for Parser<'a> {
    /// Parse a::B<String,i32>
    fn parse_trait_ref(&mut self) -> PResult<TraitRef> {
        Ok(TraitRef {
            path: self.parse_path(PathStyle::Type)?,
            ref_id: ast::DUMMY_NODE_ID,
        })
    }
}

#[plugin_registrar]
#[doc(hidden)]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("snake_to_camel", snake_to_camel);
    reg.register_macro("impl_snake_to_camel", impl_snake_to_camel);
    reg.register_macro("ty_snake_to_camel", ty_snake_to_camel);
}
<|endoftext|>


<commit_before>use std::convert::From;
use std::io::{Read, Write};
use std::sync::mpsc::{Sender, channel};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::error::Error;

use VERSION;

use bincode::serde::{DeserializeError, deserialize, serialize};
use bincode::SizeLimit;
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
use mio::{EventLoop as MioEventLoop, EventSet, Handler as MioHandler, PollOpt, Token};
use mio::tcp::{Shutdown, TcpListener, TcpStream};
use mio::util::Slab;
use slab::Index;

pub mod client;
#[cfg(Test)]
pub mod test;

/// The default port for a server to listen on.
pub const STANDARD_PORT: u16 = 25566;
/// Standard number to ensure network connections are syncronized and the same protocol is being used.
/// Reexported incase it is of use for something not-networking.
pub const NET_MAGIC_NUMBER: u32 = 0xCB011043; //0xcafebade + 0x25565, because programming references.
const MAX_CONNECTIONS: usize = 1024;

pub enum HandlerMessage {
    Send(NetworkPacket, Token),
    AddStream(TcpStream, Sender<Token>),
    Kill(Token),
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum NetworkPacket {
    /// Sent on connection to verify everything is in sync.
    Init {
        version: String,
        should_crash: bool,
    },
    /// An error that should crash the game and show an error to the user, but only on a client.
    Error(NetworkError),
    /// Used to unit test networking. When recived, increments a value. TODO: Locate that value.
    #[cfg(test)]
    Test,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum NetworkError {
    VersionMismatch(String, String),
    /// If both peers have should_crash == false, then this error should be sent.
    ///
    /// Do note that this error should not be rewrapped into a reerror, since it would cause a loop.
    /// Instead, it should be logged and ignored.
    ShouldCrashBothTrue,
}

impl Display for NetworkError {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
        match *self {
            NetworkError::VersionMismatch(ref ver1, ref ver2) => {
                write!(fmt,
                       "VersionMismatch: The versions of the client and server attempting to connect mismatch. ver1: {}, ver2: {}",
                       ver1,
                       ver2)
            }
            NetworkError::ShouldCrashBothTrue => write!(fmt, "ShouldCrashBothTrue: Both peers have should_crash == false."),
        }
    }
}

impl Error for NetworkError {
    fn description(&self) -> &str {
        match *self {
            NetworkError::VersionMismatch(_, _) => "VersionMismatch: The versions of the client and server attempting to connect mismatch.",
            NetworkError::ShouldCrashBothTrue => "ShouldCrashBothTrue: Both peers have should_crash == false.",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            NetworkError::VersionMismatch(_, _) => None,
            NetworkError::ShouldCrashBothTrue => None,
        }
    }
}

pub type EventLoop = MioEventLoop<Handler>;
pub struct Handler {
    connections: Slab<Connection>,
    listener: Option<TcpListener>,
}

impl Handler {
    /// Creates a new instance of a Handler. Does not listen for connections.
    pub fn new() -> Self {
        Handler {
            connections: Slab::new_starting_at(Token::from_usize(1), MAX_CONNECTIONS),
            listener: None,
        }
    }

    /// Creates a new instance of a Handler. Does listen for connections.
    pub fn new_listener(listener: TcpListener) -> Self {
        Handler {
            connections: Slab::new_starting_at(Token::from_usize(1), MAX_CONNECTIONS),
            listener: Some(listener),
        }
    }
}

#[derive(Debug)]
struct Connection {
    message_queue: Vec<NetworkPacket>,
    stream: TcpStream,
}

impl Connection {
    fn new(stream: TcpStream) -> Connection {
        Connection {
            stream: stream,
            message_queue: Vec::new(),
        }
    }
}

impl MioHandler for Handler {
    type Timeout = ();
    type Message = HandlerMessage;
    fn ready(&mut self, event_loop: &mut MioEventLoop<Handler>, token: Token, events: EventSet) {
        if events.is_readable() {
            // Read the 6 byte header of each packet, throw it into get_packet_length, then read that number of bytes.
            // Then throw those bytes into deserialize_packet. Afterwards, throw it to handle_packet.

            let mut header = [0; 6];
            self.connections[token]
                .stream
                .read(&mut header)
                .expect(&format!("An error occured reading from socket {:?}", token));    // TODO: Figure out possible errors and take care of them.
            let length = get_packet_length(header).unwrap_or(0);
            if length == 0 {
                kill(event_loop, token);
                // I directly kill the connection, becasue if the magic number doesn't match,
                // the peer probably doesn't share the same protocol. It wouldn't understand a normal error packet.
                return;
            }
            let mut packet = Vec::new();
            (&mut self.connections[token].stream).take(length as u64).read_to_end(&mut packet).unwrap();
            // I do this because Read.take takes a self, instead of a reasonable alternitive.
            // However &mut Read is it's self a Reader. So I use that instead.
            let dese = deserialize_packet(&packet).unwrap();
            handle_packet(dese, token, event_loop);
        }

        if events.is_writable() {
            // Get all the messages that should be send to that token, seralize all of them, then send all of them.
            // Afterwards, flush the buffer.
            let to_send = self.connections[token].message_queue.clone();
            self.connections[token].message_queue = Vec::new();
            if !to_send.is_empty() {
                for send in to_send {
                    let bytes = seralize_packet(&send);
                    self.connections[token]
                        .stream
                        .write(&bytes)
                        .expect(&format!("Error writing {:?} to connection {:?}/{:?}",
                                         send,
                                         token,
                                         self.connections[token]));
                }
                self.connections[token]
                    .stream
                    .flush()
                    .expect(&format!("Error flushing connection {:?}/{:?}",
                                     token,
                                     self.connections[token]));;
            }
        }

        if self.listener.is_some() {
            match self.listener.as_ref().unwrap().accept() {
                Ok(Some((socket, address))) => {
                    let token = self.connections.insert(Connection::new(socket)).unwrap();
                    send(event_loop,
                         NetworkPacket::Init { version: VERSION.to_owned(), should_crash: ::check_should_crash() },
                         token);
                    info!("Accepted connection {}.", address);
                }
                Ok(None) => debug!("Recived Ok(None) in listener.accept()."),
                Err(error) => panic!("listener.accept() errored with {:?}!", error),
                // TODO: See if any of these errors can be handled better.
            }
        }
    }

    fn notify(&mut self, event_loop: &mut MioEventLoop<Handler>, msg: HandlerMessage) {
        match msg {
            HandlerMessage::AddStream(stream, tx) => {
                let token = self.connections.insert(Connection::new(stream)).unwrap();
                event_loop.register(&self.connections[token].stream,
                                    token,
                                    EventSet::all(),
                                    PollOpt::level())
                          .unwrap();
                tx.send(token).unwrap();
            }
            HandlerMessage::Send(packet, token) => {
                self.connections[token].message_queue.push(packet);
            }
            HandlerMessage::Kill(token) => {
                event_loop.deregister(&self.connections[token].stream).expect("io::Error while deregistering socket.");
                self.connections[token].stream.shutdown(Shutdown::Both).expect("io::Error while shutting down socket.");
                self.connections.remove(token);
            }
        }
    }
}

fn add_socket(event_loop: &EventLoop, socket: TcpStream) -> Token {
    let (tx, rx) = channel();   // How expensive is this? Should I be creating a whole new channel for just 1 message?
    event_loop.channel().send(HandlerMessage::AddStream(socket, tx)).unwrap();
    rx.recv().unwrap() // I should feel bad.
}

/// Send a NetworkPacket over the network.
///
/// This is a static function instead of an impl function because you can't impl on external structs. In this case, mio::EventLoop.
pub fn send(event_loop: &EventLoop, to_send: NetworkPacket, token: Token) {
    event_loop.channel().send(HandlerMessage::Send(to_send, token)).unwrap();
}

pub fn kill(event_loop: &EventLoop, token: Token) {
    event_loop.channel().send(HandlerMessage::Kill(token)).unwrap();
}

fn seralize_packet(to_ser: &NetworkPacket) -> Vec<u8> {
    let mut result: Vec<u8> = Vec::new();
    result.write_u32::<LittleEndian>(NET_MAGIC_NUMBER).unwrap();   // No possible errors here.
    // The NET_MAGIC_NUMBER is used before every packet, so incase the stream is desynced for whatever reason, the game doesn't just read arbratrary data and crash badly.
    // Instead, it can either recover somehow, by disconnecting and reconnecting, or just erroring gracefully.
    let mut encoded = serialize(to_ser, SizeLimit::Infinite).unwrap();
    // Since the size limit is infinite and i'm not encoding to a stream, there is no error and I can safely unwrap();
    result.write_u16::<LittleEndian>(encoded.len() as u16).unwrap();
    result.append(&mut encoded);
    result
}

#[derive(Debug)]
enum PacketDeseError {
    InvalidEncoding(DeserializeError),
}

impl From<DeserializeError> for PacketDeseError {
    fn from(err: DeserializeError) -> PacketDeseError {
        PacketDeseError::InvalidEncoding(err)
    }
}

/// Returns the length of a given packet, or a None if the first four bytes do not match NET_MAGIC_NUMBER.
fn get_packet_length(to_ln: [u8; 6]) -> Option<u16> {
    let (first_four, next_two) = to_ln.split_at(4);
    let should_be_magic_num = LittleEndian::read_u32(&first_four);
    if should_be_magic_num != NET_MAGIC_NUMBER {
        return None;
    }
    let length = LittleEndian::read_u16(&next_two);
    Some(length)
}

fn deserialize_packet(to_de: &[u8]) -> Result<NetworkPacket, PacketDeseError> {
    Ok(try!(deserialize::<NetworkPacket>(to_de)))
}

fn handle_packet(to_handle: NetworkPacket, sender: Token, event_loop: &EventLoop) {
    match to_handle {
        NetworkPacket::Init{version, should_crash} => {
            if !should_crash && !::check_should_crash() {
                send(event_loop,
                    NetworkPacket::Error(NetworkError::ShouldCrashBothTrue), sender);
                kill(event_loop, sender)
            }
            if version != VERSION {
                send(event_loop,
                     NetworkPacket::Error(NetworkError::VersionMismatch(version.to_owned(), VERSION.to_owned())),
                     sender)
            }
        }
        #[cfg(Test)]
        NetworkPacket::Test => test::TEST_INT.fetch_add(1, Ordering::Relaxed),
        NetworkPacket::Error(error) => if ::check_should_crash() { panic!(error) } else { unimplemented!() },
    }
}
<commit_msg>Documents vairous things<commit_after>use std::convert::From;
use std::io::{Read, Write};
use std::sync::mpsc::{Sender, channel};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::error::Error;

use VERSION;

use bincode::serde::{DeserializeError, deserialize, serialize};
use bincode::SizeLimit;
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
use mio::{EventLoop as MioEventLoop, EventSet, Handler as MioHandler, PollOpt, Token};
use mio::tcp::{Shutdown, TcpListener, TcpStream};
use mio::util::Slab;
use slab::Index;

pub mod client;
#[cfg(Test)]
pub mod test;

/// The default port for a server to listen on.
pub const STANDARD_PORT: u16 = 25566;
/// Standard number to ensure network connections are syncronized and the same protocol is being used.
/// Reexported incase it is of use for something not-networking.
pub const NET_MAGIC_NUMBER: u32 = 0xCB011043; //0xcafebade + 0x25565, because programming references.
/// The maximum nunber of connections that can be had.
///
/// If the number of connections exceeds this number, new connections should be denied.
const MAX_CONNECTIONS: usize = 1024;

/// Sent to the handler to facilitate certan actions that require access of the data accocated with the handler.
enum HandlerMessage {
    Send(NetworkPacket, Token),
    AddStream(TcpStream, Sender<Token>),
    Kill(Token),
}

/// Messages that can be sent between peers to facilitate vairous actions.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum NetworkPacket {
    /// Sent on connection to verify everything is in sync.
    Init {
        version: String,
        should_crash: bool,
    },
    /// An error that should crash the game and show an error to the user, but only on a client.
    Error(NetworkError),
    /// Used to unit test networking. When recived, increments a value. TODO: Locate that value.
    #[cfg(test)]
    Test,
}

/// Sent in the case of an error that should be sent to the peer.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum NetworkError {
    VersionMismatch(String, String),
    /// If both peers have should_crash == false, then this error should be sent.
    ///
    /// Do note that this error should not be rewrapped into a reerror, since it would cause a loop.
    /// Instead, it should be logged and ignored.
    ShouldCrashBothTrue,
}

impl Display for NetworkError {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
        match *self {
            NetworkError::VersionMismatch(ref ver1, ref ver2) => {
                write!(fmt,
                       "VersionMismatch: The versions of the client and server attempting to connect mismatch. ver1: {}, ver2: {}",
                       ver1,
                       ver2)
            }
            NetworkError::ShouldCrashBothTrue => write!(fmt, "ShouldCrashBothTrue: Both peers have should_crash == false."),
        }
    }
}

impl Error for NetworkError {
    fn description(&self) -> &str {
        match *self {
            NetworkError::VersionMismatch(_, _) => "VersionMismatch: The versions of the client and server attempting to connect mismatch.",
            NetworkError::ShouldCrashBothTrue => "ShouldCrashBothTrue: Both peers have should_crash == false.",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            NetworkError::VersionMismatch(_, _) => None,
            NetworkError::ShouldCrashBothTrue => None,
        }
    }
}

pub type EventLoop = MioEventLoop<Handler>;

/// Keeps the data that is nescary during packet handling.
pub struct Handler {
    connections: Slab<Connection>,
    listener: Option<TcpListener>,
}

impl Handler {
    /// Creates a new instance of a Handler. Does not listen for connections.
    pub fn new() -> Self {
        Handler {
            connections: Slab::new_starting_at(Token::from_usize(1), MAX_CONNECTIONS),
            listener: None,
        }
    }

    /// Creates a new instance of a Handler. Does listen for connections.
    pub fn new_listener(listener: TcpListener) -> Self {
        Handler {
            connections: Slab::new_starting_at(Token::from_usize(1), MAX_CONNECTIONS),
            listener: Some(listener),
        }
    }
}

#[derive(Debug)]
struct Connection {
    message_queue: Vec<NetworkPacket>,
    stream: TcpStream,
}

impl Connection {
    fn new(stream: TcpStream) -> Connection {
        Connection {
            stream: stream,
            message_queue: Vec::new(),
        }
    }
}

impl MioHandler for Handler {
    type Timeout = ();
    type Message = HandlerMessage;
    fn ready(&mut self, event_loop: &mut MioEventLoop<Handler>, token: Token, events: EventSet) {
        if events.is_readable() {
            // Read the 6 byte header of each packet, throw it into get_packet_length, then read that number of bytes.
            // Then throw those bytes into deserialize_packet. Afterwards, throw it to handle_packet.

            let mut header = [0; 6];
            self.connections[token]
                .stream
                .read(&mut header)
                .expect(&format!("An error occured reading from socket {:?}", token));    // TODO: Figure out possible errors and take care of them.
            let length = get_packet_length(header).unwrap_or(0);
            if length == 0 {
                kill(event_loop, token);
                // I directly kill the connection, becasue if the magic number doesn't match,
                // the peer probably doesn't share the same protocol. It wouldn't understand a normal error packet.
                return;
                // Returning pervents other actions from hapening as well.
                // After all, the token is now invalid and will panic or something if left around.
            }
            let mut packet = Vec::new();
            (&mut self.connections[token].stream).take(length as u64).read_to_end(&mut packet).unwrap();
            // I do this because Read.take takes a self, instead of a reasonable alternitive.
            // However &mut Read is it's self a Reader. So I use that instead.
            let dese = deserialize_packet(&packet).unwrap();
            handle_packet(dese, token, event_loop);
        }

        if events.is_writable() {
            // Get all the messages that should be send to that token, seralize all of them, then send all of them.
            // Afterwards, flush the buffer.
            let to_send = self.connections[token].message_queue.clone();
            self.connections[token].message_queue = Vec::new();
            if !to_send.is_empty() {
                for send in to_send {
                    let bytes = seralize_packet(&send);
                    self.connections[token]
                        .stream
                        .write(&bytes)
                        .expect(&format!("Error writing {:?} to connection {:?}/{:?}",
                                         send,
                                         token,
                                         self.connections[token]));
                }
                self.connections[token]
                    .stream
                    .flush()
                    .expect(&format!("Error flushing connection {:?}/{:?}",
                                     token,
                                     self.connections[token]));;
            }
        }

        if self.listener.is_some() {
            match self.listener.as_ref().unwrap().accept() {
                Ok(Some((socket, address))) => {
                    let token = self.connections.insert(Connection::new(socket)).unwrap();
                    send(event_loop,
                         NetworkPacket::Init { version: VERSION.to_owned(), should_crash: ::check_should_crash() },
                         token);
                    info!("Accepted connection {}.", address);
                }
                Ok(None) => debug!("Recived Ok(None) in listener.accept()."),
                Err(error) => panic!("listener.accept() errored with {:?}!", error),
                // TODO: See if any of these errors can be handled better.
            }
        }
    }

    fn notify(&mut self, event_loop: &mut MioEventLoop<Handler>, msg: HandlerMessage) {
        match msg {
            HandlerMessage::AddStream(stream, tx) => {
                let token = self.connections.insert(Connection::new(stream)).unwrap();
                event_loop.register(&self.connections[token].stream,
                                    token,
                                    EventSet::all(),
                                    PollOpt::level())
                          .unwrap();
                tx.send(token).unwrap();
            }
            HandlerMessage::Send(packet, token) => {
                self.connections[token].message_queue.push(packet);
            }
            HandlerMessage::Kill(token) => {
                event_loop.deregister(&self.connections[token].stream).expect("io::Error while deregistering socket.");
                self.connections[token].stream.shutdown(Shutdown::Both).expect("io::Error while shutting down socket.");
                self.connections.remove(token);
            }
        }
    }
}

fn add_socket(event_loop: &EventLoop, socket: TcpStream) -> Token {
    let (tx, rx) = channel();   // How expensive is this? Should I be creating a whole new channel for just 1 message?
    event_loop.channel().send(HandlerMessage::AddStream(socket, tx)).unwrap();
    rx.recv().unwrap() // I should feel bad.
}

/// Send a NetworkPacket over the network.
///
/// This is a static function instead of an impl function because you can't impl on external structs. In this case, mio::EventLoop.
pub fn send(event_loop: &EventLoop, to_send: NetworkPacket, token: Token) {
    event_loop.channel().send(HandlerMessage::Send(to_send, token)).unwrap();
}

pub fn kill(event_loop: &EventLoop, token: Token) {
    event_loop.channel().send(HandlerMessage::Kill(token)).unwrap();
}

fn seralize_packet(to_ser: &NetworkPacket) -> Vec<u8> {
    let mut result: Vec<u8> = Vec::new();
    result.write_u32::<LittleEndian>(NET_MAGIC_NUMBER).unwrap();   // No possible errors here.
    // The NET_MAGIC_NUMBER is used before every packet, so incase the stream is desynced for whatever reason, the game doesn't just read arbratrary data and crash badly.
    // Instead, it can either recover somehow, by disconnecting and reconnecting, or just erroring gracefully.
    let mut encoded = serialize(to_ser, SizeLimit::Infinite).unwrap();
    // Since the size limit is infinite and i'm not encoding to a stream, there is no error and I can safely unwrap();
    result.write_u16::<LittleEndian>(encoded.len() as u16).unwrap();
    result.append(&mut encoded);
    result
}

#[derive(Debug)]
enum PacketDeseError {
    InvalidEncoding(DeserializeError),
}

impl From<DeserializeError> for PacketDeseError {
    fn from(err: DeserializeError) -> PacketDeseError {
        PacketDeseError::InvalidEncoding(err)
    }
}

/// Returns the length of a given packet, or a None if the first four bytes do not match NET_MAGIC_NUMBER.
fn get_packet_length(to_ln: [u8; 6]) -> Option<u16> {
    let (first_four, next_two) = to_ln.split_at(4);
    let should_be_magic_num = LittleEndian::read_u32(&first_four);
    if should_be_magic_num != NET_MAGIC_NUMBER {
        return None;
    }
    let length = LittleEndian::read_u16(&next_two);
    Some(length)
}

fn deserialize_packet(to_de: &[u8]) -> Result<NetworkPacket, PacketDeseError> {
    Ok(try!(deserialize::<NetworkPacket>(to_de)))
}

fn handle_packet(to_handle: NetworkPacket, sender: Token, event_loop: &EventLoop) {
    match to_handle {
        NetworkPacket::Init{version, should_crash} => {
            if !should_crash && !::check_should_crash() {
                send(event_loop,
                    NetworkPacket::Error(NetworkError::ShouldCrashBothTrue), sender);
                kill(event_loop, sender)
            }
            if version != VERSION {
                send(event_loop,
                     NetworkPacket::Error(NetworkError::VersionMismatch(version.to_owned(), VERSION.to_owned())),
                     sender)
            }
        }
        #[cfg(Test)]
        NetworkPacket::Test => test::TEST_INT.fetch_add(1, Ordering::Relaxed),
        NetworkPacket::Error(error) => if ::check_should_crash() { panic!(error) } else { unimplemented!() },
    }
}
<|endoftext|>


<commit_before>//
// Copyright 2020 The Project Oak Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use crate::io::{ReceiverExt, SenderExt};
use log::{info, warn};
use oak_abi::{label::Label, proto::oak::application::NodeConfiguration};

/// Client for a gRPC service in another Node.
pub struct Client {
    pub invocation_sender: crate::io::Sender<crate::grpc::Invocation>,
}

impl Client {
    /// Similar to [`Client::new_with_label`] but with a fixed label corresponding to "public
    /// untrusted".
    pub fn new(config: &NodeConfiguration) -> Option<Client> {
        Client::new_with_label(config, &Label::public_untrusted())
    }

    /// Creates a new Node that implements a gRPC service, and if successful return a Client.
    ///
    /// The config name specifies the Node configuration that provides the service; the
    /// entrypoint name is required if this specifies another WebAssembly Oak Node, but is
    /// ignored if the Node configuration is for a gRPC client pseudo-Node (which acts as a
    /// proxy for a remote non-Oak gRPC service).
    ///
    /// The provided [`Label`] specifies the label for the newly created Node and Channel.
    pub fn new_with_label(config: &NodeConfiguration, label: &Label) -> Option<Client> {
        let (invocation_sender, invocation_receiver) =
            crate::io::channel_create().expect("failed to create channel");
        let status = crate::node_create_with_label(config, label, invocation_receiver.handle);
        invocation_receiver
            .close()
            .expect("failed to close channel");
        match status {
            Ok(_) => {
                info!(
                    "Client created for '{:?}', accessible via channel {:?}",
                    config, invocation_sender.handle
                );
                Some(Client { invocation_sender })
            }
            Err(status) => {
                warn!("failed to create Client for '{:?}': {:?}", config, status);
                None
            }
        }
    }
}

impl Drop for Client {
    fn drop(&mut self) {
        info!("Closing Client channel {:?}", self.invocation_sender.handle);
        if let Err(status) = self.invocation_sender.close() {
            warn!(
                "Failed to close Client channel {:?}: {:?}",
                self.invocation_sender.handle, status
            );
        }
    }
}
<commit_msg>Create labeled channel in gRPC client (#1613)<commit_after>//
// Copyright 2020 The Project Oak Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use crate::io::{ReceiverExt, SenderExt};
use log::{info, warn};
use oak_abi::{label::Label, proto::oak::application::NodeConfiguration};

/// Client for a gRPC service in another Node.
pub struct Client {
    pub invocation_sender: crate::io::Sender<crate::grpc::Invocation>,
}

impl Client {
    /// Similar to [`Client::new_with_label`] but with a fixed label corresponding to "public
    /// untrusted".
    pub fn new(config: &NodeConfiguration) -> Option<Client> {
        Client::new_with_label(config, &Label::public_untrusted())
    }

    /// Creates a new Node that implements a gRPC service, and if successful return a Client.
    ///
    /// The config name specifies the Node configuration that provides the service; the
    /// entrypoint name is required if this specifies another WebAssembly Oak Node, but is
    /// ignored if the Node configuration is for a gRPC client pseudo-Node (which acts as a
    /// proxy for a remote non-Oak gRPC service).
    ///
    /// The provided [`Label`] specifies the label for the newly created Node and Channel.
    pub fn new_with_label(config: &NodeConfiguration, label: &Label) -> Option<Client> {
        let (invocation_sender, invocation_receiver) =
            crate::io::channel_create_with_label(label).expect("failed to create channel");
        let status = crate::node_create_with_label(config, label, invocation_receiver.handle);
        invocation_receiver
            .close()
            .expect("failed to close channel");
        match status {
            Ok(_) => {
                info!(
                    "Client created for '{:?}', accessible via channel {:?}",
                    config, invocation_sender.handle
                );
                Some(Client { invocation_sender })
            }
            Err(status) => {
                warn!("failed to create Client for '{:?}': {:?}", config, status);
                None
            }
        }
    }
}

impl Drop for Client {
    fn drop(&mut self) {
        info!("Closing Client channel {:?}", self.invocation_sender.handle);
        if let Err(status) = self.invocation_sender.close() {
            warn!(
                "Failed to close Client channel {:?}: {:?}",
                self.invocation_sender.handle, status
            );
        }
    }
}
<|endoftext|>


<commit_before>use std::from_str::FromStr;
use std::fmt::{Formatter, FormatError, Show};
use std::option::Option;
use std::cmp::Eq;

#[deriving(Clone, PartialEq)]
pub enum Arch {
    i686,
    x86_64,
    any
}

impl Show for Arch {
    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> {
        match self {
            &i686   => write!(f, "i686"),
            &x86_64 => write!(f, "x86_64"),
            &any    => write!(f, "any")
        }
    }
}

#[deriving(Clone, PartialEq)]
pub struct Package {
    pkgname: String,
    pkgver: String,
    pkgrel: uint,
    epoch: uint,
    arch: Arch
}

impl Package {
    pub fn new(pkgname: &str, pkgver: &str, pkgrel: uint, epoch: uint,
               arch: Arch) -> Package {
        Package {
            pkgname: String::from_str(pkgname),
            pkgver: String::from_str(pkgver),
            pkgrel: pkgrel,
            epoch: epoch,
            arch: arch,
        }
    }
}

impl FromStr for Package {
    fn from_str(s: &str) -> Option<Package> {
        for i in s.graphemes(true).rev() {
            // if
        }
    }
}

impl Show for Package {
    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> {
        if self.epoch == 0 {
            write!(f, "{}-{}-{}-{}.pkg.tar.xz", self.pkgname, self.pkgver,
                   self.pkgrel, self.arch)
        } else {
            write!(f, "{}-{}:{}-{}-{}.pkg.tar.xz", self.pkgname, self.epoch,
                   self.pkgver, self.pkgrel, self.arch)
        }
    }
}

#[test]
fn package_from_string_parsing() {
    let package1: Package = from_str("lnav-0.5.1-1-x86_64.pkg.tar.xz");
    let package2 = Package::new("lnav", "0.5.1", 1, 0, x86_64);
    assert!(package1, package2);
}

#[test]
fn package_to_string() {
    let filename = "lnav-0.5.1-1-x86_64.pkg.tar.xz";
    let package = Package::new("lnav", "0.5.1", 1, 0, x86_64);
    assert!(filename, format!("{}", package));
}

#[test]
fn package_from_and_to_string() {
    let filename = "lnav-0.5.1-1-x86_64.pkg.tar.xz";
    let package: Package = from_str("lnav", "0.5.1", 1, 0, x86_64);
    assert!(filename, format!("{}", package));
}

#[test]
fn package_with_epoch_from_string_parsing() {
    let package1: Package = from_str("docker-1:1.3.1-1-x86_64.pkg.tar.xz");
    let package2 = Package::new("docker", "1.3.1", 1, 1, x86_64);
    assert!(package1, package2);
}

#[test]
fn package_with_epoch_to_string() {
    let filename = "docker-1:1.3.1-1-x86_64.pkg.tar.xz";
    let package = Package::new("docker", "1.3.1", 1, 1, x86_64);
    assert!(filename, format!("{}", package));
}

#[test]
fn package_with_epoch_from_and_to_string() {
    let filename = "docker-1:1.3.1-1-x86_64.pkg.tar.xz";
    let package: Package = from_str("docker", "1.3.1", 1, 1, x86_64);
    assert!(filename, format!("{}", package));
}
<commit_msg>WIP: package, naïve version<commit_after>use std::from_str::FromStr;
use std::fmt::{Formatter, FormatError, Show};
use std::option::Option;

#[deriving(Clone, PartialEq)]
pub enum Arch {
    i686,
    x86_64,
    any
}

impl Show for Arch {
    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> {
        match self {
            &i686   => write!(f, "i686"),
            &x86_64 => write!(f, "x86_64"),
            &any    => write!(f, "any")
        }
    }
}

#[deriving(Clone, PartialEq)]
pub struct Package {
    pkgname:   String,
    pkgver:    String,
    pkgrel:    uint,
    epoch:     uint,
    arch:      Arch,
    signature: bool
}

impl Package {
    pub fn new(pkgname: &str, pkgver: &str, pkgrel: uint, epoch: uint,
               arch: Arch, signature: bool) -> Package {
        Package {
            pkgname:   String::from_str(pkgname),
            pkgver:    String::from_str(pkgver),
            pkgrel:    pkgrel,
            epoch:     epoch,
            arch:      arch,
            signature: signature
        }
    }
}

impl FromStr for Package {
    fn from_str(s: &str) -> Option<Package> {
        let mut package = Package::new("", "", 1, 0, x86_64, false);
        let mut tmp = "";

        println!("Looking at {}", s);
        // Looking for signature suffix
        if s.ends_with(".sig") {
            package.signature = true;
            tmp = s.slice_to(s.len() - ".sig".len());
            println!("Found signature suffix");
        } else {
            println!("No signature suffix found");
        }

        println!("Looking at {}", if tmp == "" { s } else { tmp });
        // Looking for package prefix
        if tmp.ends_with(".pkg.tar.xz") {
            tmp = tmp.slice_to(tmp.len() - ".pkg.tar.xz".len());
            println!("Found package suffix");
        } else {
            println!("No package prefix found!");
            return None
        }

        println!("Looking at {}", tmp);
        // Looking for architecture
        let mut pos = match tmp.rfind('-') {
            None    => return None,
            Some(x) => x
        };
        println!("Found '-' at pos {} in {}", pos, tmp);
        package.arch = match tmp.slice_from(pos + 1) {
            "i686"   => i686,
            "x86_64" => x86_64,
            "any"    => any,
            _        => return None
        };
        tmp = tmp.slice_to(pos);
        println!("Package arch is {}", package.arch);

        println!("Looking at {}", tmp);
        // Looking for pkgrel
        pos = match tmp.rfind('-') {
            None    => return None,
            Some(x) => x
        };
        println!("Found '-' at pos {} in {}", pos, tmp);
        let rel: Option<uint> = from_str(tmp.slice_from(pos + 1));
        match rel {
            None    => return None,
            Some(x) => {
                package.pkgrel = x;
                tmp = tmp.slice_to(pos);
            }
        }
        println!("Package pkgrel is {}", package.pkgrel);

        println!("Looking at {}", tmp);
        // Looking for epoch+pkgver
        pos = match tmp.rfind('-') {
            None    => return None,
            Some(x) => x
        };
        println!("Found '-' at pos {} in {}", pos, tmp);
        pos = match tmp.slice_from(pos + 1).find(':') {
            None    => pos,
            Some(x) => {
                println!("Found ':' at pos {} in {}", pos + 1 + x, tmp);
                let epoch: Option<uint> = from_str(tmp.slice_chars(pos + 1, pos + 1 + x));
                match epoch {
                    None    => return None,
                    Some(z) => {
                        package.epoch = z;
                        pos + 1 + x
                    }
                }
            }
        };
        println!("Package epoch is {}", package.epoch);
        println!("Looking at {}", tmp);
        package.pkgver = match from_str(tmp.slice_from(pos + 1)) {
            None    => return None,
            Some(s) => s
        };
        tmp = tmp.slice_to(pos);
        println!("Package pkgver is {}", package.pkgver);

        println!("Looking at {}", tmp);
        // Everything else is the pkgname
        package.pkgname = match from_str(tmp) {
            None    => return None,
            Some(s) => s
        };
        println!("Package name is {}", package.pkgname);

        Some(package)
    }
}

impl Show for Package {
    fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> {
        let version = if self.epoch == 0 {
            format!("{}", self.pkgver)
        } else {
            format!("{}:{}", self.epoch, self.pkgver)
        };

        let suffix = if self.signature {
            ".sig"
        } else {
            ""
        };

        write!(f, "{}-{}-{}-{}.pkg.tar.xz{}", self.pkgname, version,
               self.pkgrel, self.arch, suffix)
    }
}

// Testing without epoch and signature

#[test]
fn package_from_string_parsing() {
    let package1: Package = from_str("lnav-0.5.1-1-x86_64.pkg.tar.xz").unwrap();
    let package2 = Package::new("lnav", "0.5.1", 1, 0, x86_64, false);
    assert_eq!(package1, package2);
}

#[test]
fn package_to_string() {
    let filename = "lnav-0.5.1-1-x86_64.pkg.tar.xz";
    let package = Package::new("lnav", "0.5.1", 1, 0, x86_64, false);
    assert_eq!(filename, format!("{}", package).as_slice());
}

#[test]
fn package_from_and_to_string() {
    let filename = "lnav-0.5.1-1-x86_64.pkg.tar.xz";
    let package: Package = from_str("lnav-0.5.1-1-x86_64.pkg.tar.xz").unwrap();
    assert_eq!(filename, format!("{}", package).as_slice());
}

// Testing with epoch and no signature

#[test]
fn package_with_epoch_from_string_parsing() {
    let package1: Package = from_str("docker-1:1.3.1-1-x86_64.pkg.tar.xz").unwrap();
    let package2 = Package::new("docker", "1.3.1", 1, 1, x86_64, false);
    assert_eq!(package1, package2);
}

#[test]
fn package_with_epoch_to_string() {
    let filename = "docker-1:1.3.1-1-x86_64.pkg.tar.xz";
    let package = Package::new("docker", "1.3.1", 1, 1, x86_64, false);
    assert_eq!(filename, format!("{}", package).as_slice());
}

#[test]
fn package_with_epoch_from_and_to_string() {
    let filename = "docker-1:1.3.1-1-x86_64.pkg.tar.xz";
    let package: Package = from_str("docker-1:1.3.1-1-x86_64.pkg.tar.xz").unwrap();
    assert_eq!(filename, format!("{}", package).as_slice());
}

// Testing with epoch and signature

#[test]
fn package_with_epoch_and_sign_from_string_parsing() {
    let package1: Package = from_str("docker-1:1.3.1-1-x86_64.pkg.tar.xz.sig").unwrap();
    let package2 = Package::new("docker", "1.3.1", 1, 1, x86_64, true);
    assert_eq!(package1, package2);
}

#[test]
fn package_with_epoch_and_sign_to_string() {
    let filename = "docker-1:1.3.1-1-x86_64.pkg.tar.xz.sig";
    let package = Package::new("docker", "1.3.1", 1, 1, x86_64, true);
    assert_eq!(filename, format!("{}", package).as_slice());
}

#[test]
fn package_with_epoch_and_sign_from_and_to_string() {
    let filename = "docker-1:1.3.1-1-x86_64.pkg.tar.xz.sig";
    let package: Package = from_str("docker-1:1.3.1-1-x86_64.pkg.tar.xz.sig").unwrap();
    assert_eq!(filename, format!("{}", package).as_slice());
}
<|endoftext|>