<commit_before>use std::collections::HashSet;
use std::collections::hash_map::{HashMap, Occupied, Vacant};
use std::str;
use std::sync::Arc;

use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target};
use util::{mod, CargoResult, ChainError, internal, Config, profile};
use util::human;

use super::{Kind, KindHost, KindTarget, Compilation, BuildConfig};
use super::TargetConfig;
use super::layout::{Layout, LayoutProxy};
use super::custom_build::BuildState;

#[deriving(Show)]
pub enum PlatformRequirement {
    PlatformTarget,
    PlatformPlugin,
    PlatformPluginAndTarget,
}

pub struct Context<'a, 'b: 'a> {
    pub config: &'b Config<'b>,
    pub resolve: &'a Resolve,
    pub sources: &'a SourceMap<'b>,
    pub compilation: Compilation,
    pub build_state: Arc<BuildState>,

    env: &'a str,
    host: Layout,
    target: Option<Layout>,
    target_triple: String,
    host_dylib: Option<(String, String)>,
    package_set: &'a PackageSet,
    target_dylib: Option<(String, String)>,
    target_exe: String,
    requirements: HashMap<(&'a PackageId, &'a str), PlatformRequirement>,
    build_config: BuildConfig,
}

impl<'a, 'b: 'a> Context<'a, 'b> {
    pub fn new(env: &'a str, resolve: &'a Resolve, sources: &'a SourceMap<'b>,
               deps: &'a PackageSet, config: &'b Config<'b>,
               host: Layout, target: Option<Layout>,
               root_pkg: &Package,
               build_config: BuildConfig)
               -> CargoResult<Context<'a, 'b>> {
        let (target_dylib, target_exe) =
                try!(Context::filename_parts(config.target()));
        let host_dylib = if config.target().is_none() {
            target_dylib.clone()
        } else {
            let (dylib, _) = try!(Context::filename_parts(None));
            dylib
        };
        let target_triple = config.target().map(|s| s.to_string());
        let target_triple = target_triple.unwrap_or(config.rustc_host().to_string());
        Ok(Context {
            target_triple: target_triple,
            env: env,
            host: host,
            target: target,
            resolve: resolve,
            sources: sources,
            package_set: deps,
            config: config,
            target_dylib: target_dylib,
            target_exe: target_exe,
            host_dylib: host_dylib,
            requirements: HashMap::new(),
            compilation: Compilation::new(root_pkg),
            build_state: Arc::new(BuildState::new(build_config.clone(), deps)),
            build_config: build_config,
        })
    }

    /// Run `rustc` to discover the dylib prefix/suffix for the target
    /// specified as well as the exe suffix
    fn filename_parts(target: Option<&str>)
                      -> CargoResult<(Option<(String, String)>, String)> {
        let process = util::process("rustc")
                           .arg("-")
                           .arg("--crate-name").arg("-")
                           .arg("--crate-type").arg("dylib")
                           .arg("--crate-type").arg("bin")
                           .arg("--print-file-name");
        let process = match target {
            Some(s) => process.arg("--target").arg(s),
            None => process,
        };
        let output = try!(process.exec_with_output());

        let error = str::from_utf8(output.error.as_slice()).unwrap();
        let output = str::from_utf8(output.output.as_slice()).unwrap();
        let mut lines = output.lines();
        let dylib = if error.contains("dropping unsupported crate type `dylib`") {
            None
        } else {
            let dylib_parts: Vec<&str> = lines.next().unwrap().trim()
                                              .split('-').collect();
            assert!(dylib_parts.len() == 2,
                    "rustc --print-file-name output has changed");
            Some((dylib_parts[0].to_string(), dylib_parts[1].to_string()))
        };

        let exe_suffix = if error.contains("dropping unsupported crate type `bin`") {
            String::new()
        } else {
            lines.next().unwrap().trim()
                 .split('-').skip(1).next().unwrap().to_string()
        };
        Ok((dylib, exe_suffix.to_string()))
    }

    /// Prepare this context, ensuring that all filesystem directories are in
    /// place.
    pub fn prepare(&mut self, pkg: &'a Package) -> CargoResult<()> {
        let _p = profile::start("preparing layout");

        try!(self.host.prepare().chain_error(|| {
            internal(format!("couldn't prepare build directories for `{}`",
                             pkg.get_name()))
        }));
        match self.target {
            Some(ref mut target) => {
                try!(target.prepare().chain_error(|| {
                    internal(format!("couldn't prepare build directories \
                                      for `{}`", pkg.get_name()))
                }));
            }
            None => {}
        }

        let targets = pkg.get_targets().iter();
        for target in targets.filter(|t| t.get_profile().is_compile()) {
            self.build_requirements(pkg, target, PlatformTarget,
                                    &mut HashSet::new());
        }

        self.compilation.extra_env.insert("NUM_JOBS".to_string(),
                                          Some(self.config.jobs().to_string()));
        self.compilation.root_output =
                self.layout(pkg, KindTarget).proxy().dest().clone();
        self.compilation.deps_output =
                self.layout(pkg, KindTarget).proxy().deps().clone();

        return Ok(());
    }

    fn build_requirements(&mut self, pkg: &'a Package, target: &'a Target,
                          req: PlatformRequirement,
                          visiting: &mut HashSet<(&'a PackageId, &'a str)>) {

        let key = (pkg.get_package_id(), target.get_name());
        if !visiting.insert(key) { return }
        let req = if target.get_profile().is_for_host() {PlatformPlugin} else {req};
        match self.requirements.entry(key) {
            Occupied(mut entry) => { *entry.get_mut() = entry.get().combine(req); }
            Vacant(entry) => { entry.set(req); }
        };

        for &(pkg, dep) in self.dep_targets(pkg, target).iter() {
            self.build_requirements(pkg, dep, req, visiting);
        }

        match pkg.get_targets().iter().find(|t| t.get_profile().is_custom_build()) {
            Some(custom_build) => {
                self.build_requirements(pkg, custom_build, PlatformPlugin,
                                        visiting);
            }
            None => {}
        }

        visiting.remove(&key);
    }

    pub fn get_requirement(&self, pkg: &'a Package,
                           target: &'a Target) -> PlatformRequirement {
        let default = if target.get_profile().is_for_host() {
            PlatformPlugin
        } else {
            PlatformTarget
        };
        self.requirements.get(&(pkg.get_package_id(), target.get_name()))
            .map(|a| *a).unwrap_or(default)
    }

    /// Returns the appropriate directory layout for either a plugin or not.
    pub fn layout(&self, pkg: &Package, kind: Kind) -> LayoutProxy {
        let primary = pkg.get_package_id() == self.resolve.root();
        match kind {
            KindHost => LayoutProxy::new(&self.host, primary),
            KindTarget =>  LayoutProxy::new(self.target.as_ref()
                                                .unwrap_or(&self.host),
                                            primary),
        }
    }

    /// Return the (prefix, suffix) pair for dynamic libraries.
    ///
    /// If `plugin` is true, the pair corresponds to the host platform,
    /// otherwise it corresponds to the target platform.
    fn dylib(&self, kind: Kind) -> CargoResult<(&str, &str)> {
        let (triple, pair) = if kind == KindHost {
            (self.config.rustc_host(), &self.host_dylib)
        } else {
            (self.target_triple.as_slice(), &self.target_dylib)
        };
        match *pair {
            None => return Err(human(format!("dylib outputs are not supported \
                                              for {}", triple))),
            Some((ref s1, ref s2)) => Ok((s1.as_slice(), s2.as_slice())),
        }
    }

    /// Return the target triple which this context is targeting.
    pub fn target_triple(&self) -> &str {
        self.target_triple.as_slice()
    }

    /// Return the exact filename of the target.
    pub fn target_filenames(&self, target: &Target) -> CargoResult<Vec<String>> {
        let stem = target.file_stem();

        let mut ret = Vec::new();
        if target.is_example() || target.is_bin() ||
           target.get_profile().is_test() {
            ret.push(format!("{}{}", stem, self.target_exe));
        } else {
            if target.is_dylib() {
                let plugin = target.get_profile().is_for_host();
                let kind = if plugin {KindHost} else {KindTarget};
                let (prefix, suffix) = try!(self.dylib(kind));
                ret.push(format!("{}{}{}", prefix, stem, suffix));
            }
            if target.is_rlib() {
                ret.push(format!("lib{}.rlib", stem));
            }
            if target.is_staticlib() {
                ret.push(format!("lib{}.a", stem));
            }
        }
        assert!(ret.len() > 0);
        return Ok(ret);
    }

    /// For a package, return all targets which are registered as dependencies
    /// for that package.
    pub fn dep_targets(&self, pkg: &Package, target: &Target)
                       -> Vec<(&'a Package, &'a Target)> {
        let deps = match self.resolve.deps(pkg.get_package_id()) {
            None => return vec!(),
            Some(deps) => deps,
        };
        deps.map(|id| self.get_package(id)).filter(|dep| {
            // If this target is a build command, then we only want build
            // dependencies, otherwise we want everything *other than* build
            // dependencies.
            let pkg_dep = pkg.get_dependencies().iter().find(|d| {
                d.get_name() == dep.get_name()
            }).unwrap();
            target.get_profile().is_custom_build() == pkg_dep.is_build()
        }).filter_map(|pkg| {
            pkg.get_targets().iter().find(|&t| self.is_relevant_target(t))
               .map(|t| (pkg, t))
        }).collect()
    }

    /// Gets a package for the given package id.
    pub fn get_package(&self, id: &PackageId) -> &'a Package {
        self.package_set.iter()
            .find(|pkg| id == pkg.get_package_id())
            .expect("Should have found package")
    }

    pub fn env(&self) -> &str {
        // The "doc-all" environment just means to document everything (see
        // below), but we want to canonicalize that the the "doc" profile
        // environment, so do that here.
        if self.env == "doc-all" {"doc"} else {self.env}
    }

    pub fn is_relevant_target(&self, target: &Target) -> bool {
        target.is_lib() && match self.env {
            "doc" | "test" => target.get_profile().is_compile(),
            // doc-all == document everything, so look for doc targets and
            //            compile targets in dependencies
            "doc-all" => target.get_profile().is_compile() ||
                         (target.get_profile().get_env() == "doc" &&
                          target.get_profile().is_doc()),
            _ => target.get_profile().get_env() == self.env &&
                 !target.get_profile().is_test(),
        }
    }

    /// Get the user-specified linker for a particular host or target
    pub fn linker(&self, kind: Kind) -> Option<&str> {
        self.target_config(kind).linker.as_ref().map(|s| s.as_slice())
    }

    /// Get the user-specified `ar` program for a particular host or target
    pub fn ar(&self, kind: Kind) -> Option<&str> {
        self.target_config(kind).ar.as_ref().map(|s| s.as_slice())
    }

    /// Get the target configuration for a particular host or target
    fn target_config(&self, kind: Kind) -> &TargetConfig {
        match kind {
            KindHost => &self.build_config.host,
            KindTarget => &self.build_config.target,
        }
    }
}

impl PlatformRequirement {
    fn combine(self, other: PlatformRequirement) -> PlatformRequirement {
        match (self, other) {
            (PlatformTarget, PlatformTarget) => PlatformTarget,
            (PlatformPlugin, PlatformPlugin) => PlatformPlugin,
            _ => PlatformPluginAndTarget,
        }
    }
}
<commit_msg>auto merge of #823 : DiamondLovesYou/cargo/master, r=alexcrichton<commit_after>use std::collections::HashSet;
use std::collections::hash_map::{HashMap, Occupied, Vacant};
use std::str;
use std::sync::Arc;

use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target};
use util::{mod, CargoResult, ChainError, internal, Config, profile};
use util::human;

use super::{Kind, KindHost, KindTarget, Compilation, BuildConfig};
use super::TargetConfig;
use super::layout::{Layout, LayoutProxy};
use super::custom_build::BuildState;

#[deriving(Show)]
pub enum PlatformRequirement {
    PlatformTarget,
    PlatformPlugin,
    PlatformPluginAndTarget,
}

pub struct Context<'a, 'b: 'a> {
    pub config: &'b Config<'b>,
    pub resolve: &'a Resolve,
    pub sources: &'a SourceMap<'b>,
    pub compilation: Compilation,
    pub build_state: Arc<BuildState>,

    env: &'a str,
    host: Layout,
    target: Option<Layout>,
    target_triple: String,
    host_dylib: Option<(String, String)>,
    host_exe: String,
    package_set: &'a PackageSet,
    target_dylib: Option<(String, String)>,
    target_exe: String,
    requirements: HashMap<(&'a PackageId, &'a str), PlatformRequirement>,
    build_config: BuildConfig,
}

impl<'a, 'b: 'a> Context<'a, 'b> {
    pub fn new(env: &'a str, resolve: &'a Resolve, sources: &'a SourceMap<'b>,
               deps: &'a PackageSet, config: &'b Config<'b>,
               host: Layout, target: Option<Layout>,
               root_pkg: &Package,
               build_config: BuildConfig)
               -> CargoResult<Context<'a, 'b>> {
        let (target_dylib, target_exe) =
                try!(Context::filename_parts(config.target()));
        let (host_dylib, host_exe) = if config.target().is_none() {
            (target_dylib.clone(),
             target_exe.clone())
        } else {
            try!(Context::filename_parts(None))
        };
        let target_triple = config.target().map(|s| s.to_string());
        let target_triple = target_triple.unwrap_or(config.rustc_host().to_string());
        Ok(Context {
            target_triple: target_triple,
            env: env,
            host: host,
            target: target,
            resolve: resolve,
            sources: sources,
            package_set: deps,
            config: config,
            target_dylib: target_dylib,
            target_exe: target_exe,
            host_dylib: host_dylib,
            host_exe: host_exe,
            requirements: HashMap::new(),
            compilation: Compilation::new(root_pkg),
            build_state: Arc::new(BuildState::new(build_config.clone(), deps)),
            build_config: build_config,
        })
    }

    /// Run `rustc` to discover the dylib prefix/suffix for the target
    /// specified as well as the exe suffix
    fn filename_parts(target: Option<&str>)
                      -> CargoResult<(Option<(String, String)>, String)> {
        let process = util::process("rustc")
                           .arg("-")
                           .arg("--crate-name").arg("-")
                           .arg("--crate-type").arg("dylib")
                           .arg("--crate-type").arg("bin")
                           .arg("--print-file-name");
        let process = match target {
            Some(s) => process.arg("--target").arg(s),
            None => process,
        };
        let output = try!(process.exec_with_output());

        let error = str::from_utf8(output.error.as_slice()).unwrap();
        let output = str::from_utf8(output.output.as_slice()).unwrap();
        let mut lines = output.lines();
        let dylib = if error.contains("dropping unsupported crate type `dylib`") {
            None
        } else {
            let dylib_parts: Vec<&str> = lines.next().unwrap().trim()
                                              .split('-').collect();
            assert!(dylib_parts.len() == 2,
                    "rustc --print-file-name output has changed");
            Some((dylib_parts[0].to_string(), dylib_parts[1].to_string()))
        };

        let exe_suffix = if error.contains("dropping unsupported crate type `bin`") {
            String::new()
        } else {
            lines.next().unwrap().trim()
                 .split('-').skip(1).next().unwrap().to_string()
        };
        Ok((dylib, exe_suffix.to_string()))
    }

    /// Prepare this context, ensuring that all filesystem directories are in
    /// place.
    pub fn prepare(&mut self, pkg: &'a Package) -> CargoResult<()> {
        let _p = profile::start("preparing layout");

        try!(self.host.prepare().chain_error(|| {
            internal(format!("couldn't prepare build directories for `{}`",
                             pkg.get_name()))
        }));
        match self.target {
            Some(ref mut target) => {
                try!(target.prepare().chain_error(|| {
                    internal(format!("couldn't prepare build directories \
                                      for `{}`", pkg.get_name()))
                }));
            }
            None => {}
        }

        let targets = pkg.get_targets().iter();
        for target in targets.filter(|t| t.get_profile().is_compile()) {
            self.build_requirements(pkg, target, PlatformTarget,
                                    &mut HashSet::new());
        }

        self.compilation.extra_env.insert("NUM_JOBS".to_string(),
                                          Some(self.config.jobs().to_string()));
        self.compilation.root_output =
                self.layout(pkg, KindTarget).proxy().dest().clone();
        self.compilation.deps_output =
                self.layout(pkg, KindTarget).proxy().deps().clone();

        return Ok(());
    }

    fn build_requirements(&mut self, pkg: &'a Package, target: &'a Target,
                          req: PlatformRequirement,
                          visiting: &mut HashSet<(&'a PackageId, &'a str)>) {

        let key = (pkg.get_package_id(), target.get_name());
        if !visiting.insert(key) { return }
        let req = if target.get_profile().is_for_host() {PlatformPlugin} else {req};
        match self.requirements.entry(key) {
            Occupied(mut entry) => { *entry.get_mut() = entry.get().combine(req); }
            Vacant(entry) => { entry.set(req); }
        };

        for &(pkg, dep) in self.dep_targets(pkg, target).iter() {
            self.build_requirements(pkg, dep, req, visiting);
        }

        match pkg.get_targets().iter().find(|t| t.get_profile().is_custom_build()) {
            Some(custom_build) => {
                self.build_requirements(pkg, custom_build, PlatformPlugin,
                                        visiting);
            }
            None => {}
        }

        visiting.remove(&key);
    }

    pub fn get_requirement(&self, pkg: &'a Package,
                           target: &'a Target) -> PlatformRequirement {
        let default = if target.get_profile().is_for_host() {
            PlatformPlugin
        } else {
            PlatformTarget
        };
        self.requirements.get(&(pkg.get_package_id(), target.get_name()))
            .map(|a| *a).unwrap_or(default)
    }

    /// Returns the appropriate directory layout for either a plugin or not.
    pub fn layout(&self, pkg: &Package, kind: Kind) -> LayoutProxy {
        let primary = pkg.get_package_id() == self.resolve.root();
        match kind {
            KindHost => LayoutProxy::new(&self.host, primary),
            KindTarget =>  LayoutProxy::new(self.target.as_ref()
                                                .unwrap_or(&self.host),
                                            primary),
        }
    }

    /// Return the (prefix, suffix) pair for dynamic libraries.
    ///
    /// If `plugin` is true, the pair corresponds to the host platform,
    /// otherwise it corresponds to the target platform.
    fn dylib(&self, kind: Kind) -> CargoResult<(&str, &str)> {
        let (triple, pair) = if kind == KindHost {
            (self.config.rustc_host(), &self.host_dylib)
        } else {
            (self.target_triple.as_slice(), &self.target_dylib)
        };
        match *pair {
            None => return Err(human(format!("dylib outputs are not supported \
                                              for {}", triple))),
            Some((ref s1, ref s2)) => Ok((s1.as_slice(), s2.as_slice())),
        }
    }

    /// Return the target triple which this context is targeting.
    pub fn target_triple(&self) -> &str {
        self.target_triple.as_slice()
    }

    /// Return the exact filename of the target.
    pub fn target_filenames(&self, target: &Target) -> CargoResult<Vec<String>> {
        let stem = target.file_stem();

        let mut ret = Vec::new();
        if target.is_example() || target.is_bin() ||
           target.get_profile().is_test() {
            ret.push(format!("{}{}", stem,
                             if target.get_profile().is_for_host() {
                                 self.host_exe.as_slice()
                             } else {
                                 self.target_exe.as_slice()
                             }));
        } else {
            if target.is_dylib() {
                let plugin = target.get_profile().is_for_host();
                let kind = if plugin {KindHost} else {KindTarget};
                let (prefix, suffix) = try!(self.dylib(kind));
                ret.push(format!("{}{}{}", prefix, stem, suffix));
            }
            if target.is_rlib() {
                ret.push(format!("lib{}.rlib", stem));
            }
            if target.is_staticlib() {
                ret.push(format!("lib{}.a", stem));
            }
        }
        assert!(ret.len() > 0);
        return Ok(ret);
    }

    /// For a package, return all targets which are registered as dependencies
    /// for that package.
    pub fn dep_targets(&self, pkg: &Package, target: &Target)
                       -> Vec<(&'a Package, &'a Target)> {
        let deps = match self.resolve.deps(pkg.get_package_id()) {
            None => return vec!(),
            Some(deps) => deps,
        };
        deps.map(|id| self.get_package(id)).filter(|dep| {
            // If this target is a build command, then we only want build
            // dependencies, otherwise we want everything *other than* build
            // dependencies.
            let pkg_dep = pkg.get_dependencies().iter().find(|d| {
                d.get_name() == dep.get_name()
            }).unwrap();
            target.get_profile().is_custom_build() == pkg_dep.is_build()
        }).filter_map(|pkg| {
            pkg.get_targets().iter().find(|&t| self.is_relevant_target(t))
               .map(|t| (pkg, t))
        }).collect()
    }

    /// Gets a package for the given package id.
    pub fn get_package(&self, id: &PackageId) -> &'a Package {
        self.package_set.iter()
            .find(|pkg| id == pkg.get_package_id())
            .expect("Should have found package")
    }

    pub fn env(&self) -> &str {
        // The "doc-all" environment just means to document everything (see
        // below), but we want to canonicalize that the the "doc" profile
        // environment, so do that here.
        if self.env == "doc-all" {"doc"} else {self.env}
    }

    pub fn is_relevant_target(&self, target: &Target) -> bool {
        target.is_lib() && match self.env {
            "doc" | "test" => target.get_profile().is_compile(),
            // doc-all == document everything, so look for doc targets and
            //            compile targets in dependencies
            "doc-all" => target.get_profile().is_compile() ||
                         (target.get_profile().get_env() == "doc" &&
                          target.get_profile().is_doc()),
            _ => target.get_profile().get_env() == self.env &&
                 !target.get_profile().is_test(),
        }
    }

    /// Get the user-specified linker for a particular host or target
    pub fn linker(&self, kind: Kind) -> Option<&str> {
        self.target_config(kind).linker.as_ref().map(|s| s.as_slice())
    }

    /// Get the user-specified `ar` program for a particular host or target
    pub fn ar(&self, kind: Kind) -> Option<&str> {
        self.target_config(kind).ar.as_ref().map(|s| s.as_slice())
    }

    /// Get the target configuration for a particular host or target
    fn target_config(&self, kind: Kind) -> &TargetConfig {
        match kind {
            KindHost => &self.build_config.host,
            KindTarget => &self.build_config.target,
        }
    }
}

impl PlatformRequirement {
    fn combine(self, other: PlatformRequirement) -> PlatformRequirement {
        match (self, other) {
            (PlatformTarget, PlatformTarget) => PlatformTarget,
            (PlatformPlugin, PlatformPlugin) => PlatformPlugin,
            _ => PlatformPluginAndTarget,
        }
    }
}
<|endoftext|>


<commit_before>use std::io::SeekFrom;
use std::io::prelude::*;
use std::path::Path;

use curl::easy::Easy;
use git2;
use rustc_serialize::json;
use rustc_serialize::hex::ToHex;

use core::{PackageId, SourceId};
use ops;
use sources::git;
use sources::registry::{RegistryData, RegistryConfig, INDEX_LOCK};
use util::network;
use util::paths;
use util::{FileLock, Filesystem};
use util::{Config, CargoResult, ChainError, human, Sha256, ToUrl};

pub struct RemoteRegistry<'cfg> {
    index_path: Filesystem,
    cache_path: Filesystem,
    source_id: SourceId,
    config: &'cfg Config,
    handle: Option<Easy>,
}

impl<'cfg> RemoteRegistry<'cfg> {
    pub fn new(source_id: &SourceId, config: &'cfg Config, name: &str)
               -> RemoteRegistry<'cfg> {
        RemoteRegistry {
            index_path: config.registry_index_path().join(name),
            cache_path: config.registry_cache_path().join(name),
            source_id: source_id.clone(),
            config: config,
            handle: None,
        }
    }
}

impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
    fn index_path(&self) -> &Filesystem {
        &self.index_path
    }

    fn config(&self) -> CargoResult<Option<RegistryConfig>> {
        let lock = try!(self.index_path.open_ro(Path::new(INDEX_LOCK),
                                                self.config,
                                                "the registry index"));
        let path = lock.path().parent().unwrap();
        let contents = try!(paths::read(&path.join("config.json")));
        let config = try!(json::decode(&contents));
        Ok(Some(config))
    }

    fn update_index(&mut self) -> CargoResult<()> {
        // Ensure that we'll actually be able to acquire an HTTP handle later on
        // once we start trying to download crates. This will weed out any
        // problems with `.cargo/config` configuration related to HTTP.
        //
        // This way if there's a problem the error gets printed before we even
        // hit the index, which may not actually read this configuration.
        try!(ops::http_handle(self.config));

        // Then we actually update the index
        try!(self.index_path.create_dir());
        let lock = try!(self.index_path.open_rw(Path::new(INDEX_LOCK),
                                                self.config,
                                                "the registry index"));
        let path = lock.path().parent().unwrap();

        try!(self.config.shell().status("Updating",
             format!("registry `{}`", self.source_id.url())));
        let repo = try!(git2::Repository::open(path).or_else(|_| {
            let _ = lock.remove_siblings();
            git2::Repository::init(path)
        }));

        // git fetch origin
        let url = self.source_id.url().to_string();
        let refspec = "refs/heads/*:refs/remotes/origin/*";

        try!(git::fetch(&repo, &url, refspec, &self.config).chain_error(|| {
            human(format!("failed to fetch `{}`", url))
        }));

        // git reset --hard origin/master
        let reference = "refs/remotes/origin/master";
        let oid = try!(repo.refname_to_id(reference));
        trace!("[{}] updating to rev {}", self.source_id, oid);
        let object = try!(repo.find_object(oid, None));
        try!(repo.reset(&object, git2::ResetType::Hard, None));
        Ok(())
    }

    fn download(&mut self, pkg: &PackageId, checksum: &str)
                -> CargoResult<FileLock> {
        let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
        let path = Path::new(&filename);
        let mut dst = try!(self.cache_path.open_rw(path, self.config, &filename));
        let meta = try!(dst.file().metadata());
        if meta.len() > 0 {
            return Ok(dst)
        }
        try!(self.config.shell().status("Downloading", pkg));

        let config = try!(self.config()).unwrap();
        let mut url = try!(config.dl.to_url());
        url.path_segments_mut().unwrap()
            .push(pkg.name())
            .push(&pkg.version().to_string())
            .push("download");

        let handle = match self.handle {
            Some(ref mut handle) => handle,
            None => {
                self.handle = Some(try!(ops::http_handle(self.config)));
                self.handle.as_mut().unwrap()
            }
        };
        // TODO: don't download into memory, but ensure that if we ctrl-c a
        //       download we should resume either from the start or the middle
        //       on the next time
        try!(handle.get(true));
        try!(handle.url(&url.to_string()));
        try!(handle.follow_location(true));
        let mut state = Sha256::new();
        let mut body = Vec::new();
        {
            let mut handle = handle.transfer();
            try!(handle.write_function(|buf| {
                state.update(buf);
                body.extend_from_slice(buf);
                Ok(buf.len())
            }));
            try!(network::with_retry(self.config, || {
                handle.perform()
            }))
        }
        let code = try!(handle.response_code());
        if code != 200 && code != 0 {
            bail!("failed to get 200 response from `{}`, got {}", url, code)
        }

        // Verify what we just downloaded
        if state.finish().to_hex() != checksum {
            bail!("failed to verify the checksum of `{}`", pkg)
        }

        try!(dst.write_all(&body));
        try!(dst.seek(SeekFrom::Start(0)));
        Ok(dst)
    }
}
<commit_msg>Speed up noop registry updates with GitHub<commit_after>use std::io::SeekFrom;
use std::io::prelude::*;
use std::path::Path;

use curl::easy::{Easy, List};
use git2;
use rustc_serialize::hex::ToHex;
use rustc_serialize::json;
use url::Url;

use core::{PackageId, SourceId};
use ops;
use sources::git;
use sources::registry::{RegistryData, RegistryConfig, INDEX_LOCK};
use util::network;
use util::paths;
use util::{FileLock, Filesystem};
use util::{Config, CargoResult, ChainError, human, Sha256, ToUrl};

pub struct RemoteRegistry<'cfg> {
    index_path: Filesystem,
    cache_path: Filesystem,
    source_id: SourceId,
    config: &'cfg Config,
    handle: Option<Easy>,
}

impl<'cfg> RemoteRegistry<'cfg> {
    pub fn new(source_id: &SourceId, config: &'cfg Config, name: &str)
               -> RemoteRegistry<'cfg> {
        RemoteRegistry {
            index_path: config.registry_index_path().join(name),
            cache_path: config.registry_cache_path().join(name),
            source_id: source_id.clone(),
            config: config,
            handle: None,
        }
    }
}

impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
    fn index_path(&self) -> &Filesystem {
        &self.index_path
    }

    fn config(&self) -> CargoResult<Option<RegistryConfig>> {
        let lock = try!(self.index_path.open_ro(Path::new(INDEX_LOCK),
                                                self.config,
                                                "the registry index"));
        let path = lock.path().parent().unwrap();
        let contents = try!(paths::read(&path.join("config.json")));
        let config = try!(json::decode(&contents));
        Ok(Some(config))
    }

    fn update_index(&mut self) -> CargoResult<()> {
        // Ensure that we'll actually be able to acquire an HTTP handle later on
        // once we start trying to download crates. This will weed out any
        // problems with `.cargo/config` configuration related to HTTP.
        //
        // This way if there's a problem the error gets printed before we even
        // hit the index, which may not actually read this configuration.
        try!(ops::http_handle(self.config));

        // Then we actually update the index
        try!(self.index_path.create_dir());
        let lock = try!(self.index_path.open_rw(Path::new(INDEX_LOCK),
                                                self.config,
                                                "the registry index"));
        let path = lock.path().parent().unwrap();

        try!(self.config.shell().status("Updating",
             format!("registry `{}`", self.source_id.url())));

        let repo = try!(git2::Repository::open(path).or_else(|_| {
            let _ = lock.remove_siblings();
            git2::Repository::init(path)
        }));

        if self.source_id.url().host_str() == Some("github.com") {
            if let Ok(oid) = repo.refname_to_id("refs/heads/master") {
                let handle = match self.handle {
                    Some(ref mut handle) => handle,
                    None => {
                        self.handle = Some(try!(ops::http_handle(self.config)));
                        self.handle.as_mut().unwrap()
                    }
                };
                debug!("attempting github fast path for {}",
                       self.source_id.url());
                if github_up_to_date(handle, &self.source_id.url(), &oid) {
                    return Ok(())
                }
                debug!("fast path failed, falling back to a git fetch");
            }
        }

        // git fetch origin
        let url = self.source_id.url().to_string();
        let refspec = "refs/heads/*:refs/remotes/origin/*";

        try!(git::fetch(&repo, &url, refspec, &self.config).chain_error(|| {
            human(format!("failed to fetch `{}`", url))
        }));

        // git reset --hard origin/master
        let reference = "refs/remotes/origin/master";
        let oid = try!(repo.refname_to_id(reference));
        trace!("[{}] updating to rev {}", self.source_id, oid);
        let object = try!(repo.find_object(oid, None));
        try!(repo.reset(&object, git2::ResetType::Hard, None));
        Ok(())
    }

    fn download(&mut self, pkg: &PackageId, checksum: &str)
                -> CargoResult<FileLock> {
        let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
        let path = Path::new(&filename);
        let mut dst = try!(self.cache_path.open_rw(path, self.config, &filename));
        let meta = try!(dst.file().metadata());
        if meta.len() > 0 {
            return Ok(dst)
        }
        try!(self.config.shell().status("Downloading", pkg));

        let config = try!(self.config()).unwrap();
        let mut url = try!(config.dl.to_url());
        url.path_segments_mut().unwrap()
            .push(pkg.name())
            .push(&pkg.version().to_string())
            .push("download");

        let handle = match self.handle {
            Some(ref mut handle) => handle,
            None => {
                self.handle = Some(try!(ops::http_handle(self.config)));
                self.handle.as_mut().unwrap()
            }
        };
        // TODO: don't download into memory, but ensure that if we ctrl-c a
        //       download we should resume either from the start or the middle
        //       on the next time
        try!(handle.get(true));
        try!(handle.url(&url.to_string()));
        try!(handle.follow_location(true));
        let mut state = Sha256::new();
        let mut body = Vec::new();
        {
            let mut handle = handle.transfer();
            try!(handle.write_function(|buf| {
                state.update(buf);
                body.extend_from_slice(buf);
                Ok(buf.len())
            }));
            try!(network::with_retry(self.config, || {
                handle.perform()
            }))
        }
        let code = try!(handle.response_code());
        if code != 200 && code != 0 {
            bail!("failed to get 200 response from `{}`, got {}", url, code)
        }

        // Verify what we just downloaded
        if state.finish().to_hex() != checksum {
            bail!("failed to verify the checksum of `{}`", pkg)
        }

        try!(dst.write_all(&body));
        try!(dst.seek(SeekFrom::Start(0)));
        Ok(dst)
    }
}

/// Updating the index is done pretty regularly so we want it to be as fast as
/// possible. For registries hosted on github (like the crates.io index) there's
/// a fast path available to use [1] to tell us that there's no updates to be
/// made.
///
/// This function will attempt to hit that fast path and verify that the `oid`
/// is actually the current `master` branch of the repository. If `true` is
/// returned then no update needs to be performed, but if `false` is returned
/// then the standard update logic still needs to happen.
///
/// [1]: https://developer.github.com/v3/repos/commits/#get-the-sha-1-of-a-commit-reference
///
/// Note that this function should never cause an actual failure because it's
/// just a fast path. As a result all errors are ignored in this function and we
/// just return a `bool`. Any real errors will be reported through the normal
/// update path above.
fn github_up_to_date(handle: &mut Easy, url: &Url, oid: &git2::Oid) -> bool {
    macro_rules! try {
        ($e:expr) => (match $e {
            Some(e) => e,
            None => return false,
        })
    }

    // This expects github urls in the form `github.com/user/repo` and nothing
    // else
    let mut pieces = try!(url.path_segments());
    let username = try!(pieces.next());
    let repo = try!(pieces.next());
    if pieces.next().is_some() {
        return false
    }

    let url = format!("https://api.github.com/repos/{}/{}/commits/master",
                      username, repo);
    try!(handle.get(true).ok());
    try!(handle.url(&url).ok());
    try!(handle.useragent("cargo").ok());
    let mut headers = List::new();
    try!(headers.append("Accept: application/vnd.github.3.sha").ok());
    try!(headers.append(&format!("If-None-Match: \"{}\"", oid)).ok());
    try!(handle.http_headers(headers).ok());
    try!(handle.perform().ok());

    try!(handle.response_code().ok()) == 304
}
<|endoftext|>


<commit_before>use std::cell::{RefCell, Ref, Cell};
use std::io::SeekFrom;
use std::io::prelude::*;
use std::mem;
use std::path::Path;

use curl::easy::{Easy, List};
use git2;
use rustc_serialize::hex::ToHex;
use serde_json;
use url::Url;

use core::{PackageId, SourceId};
use ops;
use sources::git;
use sources::registry::{RegistryData, RegistryConfig, INDEX_LOCK};
use util::network;
use util::{FileLock, Filesystem, LazyCell};
use util::{Config, CargoResult, ChainError, human, Sha256, ToUrl};
use util::errors::HttpError;

pub struct RemoteRegistry<'cfg> {
    index_path: Filesystem,
    cache_path: Filesystem,
    source_id: SourceId,
    config: &'cfg Config,
    handle: LazyCell<RefCell<Easy>>,
    tree: RefCell<Option<git2::Tree<'static>>>,
    repo: LazyCell<git2::Repository>,
    head: Cell<Option<git2::Oid>>,
}

impl<'cfg> RemoteRegistry<'cfg> {
    pub fn new(source_id: &SourceId, config: &'cfg Config, name: &str)
               -> RemoteRegistry<'cfg> {
        RemoteRegistry {
            index_path: config.registry_index_path().join(name),
            cache_path: config.registry_cache_path().join(name),
            source_id: source_id.clone(),
            config: config,
            tree: RefCell::new(None),
            handle: LazyCell::new(),
            repo: LazyCell::new(),
            head: Cell::new(None),
        }
    }

    fn easy(&self) -> CargoResult<&RefCell<Easy>> {
        self.handle.get_or_try_init(|| {
            ops::http_handle(self.config).map(RefCell::new)
        })
    }

    fn repo(&self) -> CargoResult<&git2::Repository> {
        self.repo.get_or_try_init(|| {
            let path = self.index_path.clone().into_path_unlocked();

            // Fast path without a lock
            if let Ok(repo) = git2::Repository::open(&path) {
                return Ok(repo)
            }

            // Ok, now we need to lock and try the whole thing over again.
            let lock = self.index_path.open_rw(Path::new(INDEX_LOCK),
                                               self.config,
                                               "the registry index")?;
            match git2::Repository::open(&path) {
                Ok(repo) => Ok(repo),
                Err(_) => {
                    let _ = lock.remove_siblings();
                    Ok(git2::Repository::init_bare(&path)?)
                }
            }
        })
    }

    fn head(&self) -> CargoResult<git2::Oid> {
        if self.head.get().is_none() {
            let oid = self.repo()?.refname_to_id("refs/remotes/origin/master")?;
            self.head.set(Some(oid));
        }
        Ok(self.head.get().unwrap())
    }

    fn tree(&self) -> CargoResult<Ref<git2::Tree>> {
        {
            let tree = self.tree.borrow();
            if tree.is_some() {
                return Ok(Ref::map(tree, |s| s.as_ref().unwrap()))
            }
        }
        let repo = self.repo()?;
        let commit = repo.find_commit(self.head()?)?;
        let tree = commit.tree()?;

        // Unfortunately in libgit2 the tree objects look like they've got a
        // reference to the repository object which means that a tree cannot
        // outlive the repository that it came from. Here we want to cache this
        // tree, though, so to accomplish this we transmute it to a static
        // lifetime.
        //
        // Note that we don't actually hand out the static lifetime, instead we
        // only return a scoped one from this function. Additionally the repo
        // we loaded from (above) lives as long as this object
        // (`RemoteRegistry`) so we then just need to ensure that the tree is
        // destroyed first in the destructor, hence the destructor on
        // `RemoteRegistry` below.
        let tree = unsafe {
            mem::transmute::<git2::Tree, git2::Tree<'static>>(tree)
        };
        *self.tree.borrow_mut() = Some(tree);
        Ok(Ref::map(self.tree.borrow(), |s| s.as_ref().unwrap()))
    }
}

impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
    fn index_path(&self) -> &Filesystem {
        &self.index_path
    }

    fn load(&self, _root: &Path, path: &Path) -> CargoResult<Vec<u8>> {
        // Note that the index calls this method and the filesystem is locked
        // in the index, so we don't need to worry about an `update_index`
        // happening in a different process.
        let repo = self.repo()?;
        let tree = self.tree()?;
        let entry = tree.get_path(path)?;
        let object = entry.to_object(&repo)?;
        let blob = match object.as_blob() {
            Some(blob) => blob,
            None => bail!("path `{}` is not a blob in the git repo", path.display()),
        };
        Ok(blob.content().to_vec())
    }

    fn config(&mut self) -> CargoResult<Option<RegistryConfig>> {
        self.repo()?; // create intermediate dirs and initialize the repo
        let _lock = self.index_path.open_ro(Path::new(INDEX_LOCK),
                                            self.config,
                                            "the registry index")?;
        let json = self.load(Path::new(""), Path::new("config.json"))?;
        let config = serde_json::from_slice(&json)?;
        Ok(Some(config))
    }

    fn update_index(&mut self) -> CargoResult<()> {
        // Ensure that we'll actually be able to acquire an HTTP handle later on
        // once we start trying to download crates. This will weed out any
        // problems with `.cargo/config` configuration related to HTTP.
        //
        // This way if there's a problem the error gets printed before we even
        // hit the index, which may not actually read this configuration.
        ops::http_handle(self.config)?;

        let repo = self.repo()?;
        let _lock = self.index_path.open_rw(Path::new(INDEX_LOCK),
                                            self.config,
                                            "the registry index")?;
        self.config.shell().status("Updating",
             format!("registry `{}`", self.source_id.url()))?;
        let mut needs_fetch = true;

        if self.source_id.url().host_str() == Some("github.com") {
            if let Ok(oid) = self.head() {
                let mut handle = self.easy()?.borrow_mut();
                debug!("attempting github fast path for {}",
                       self.source_id.url());
                if github_up_to_date(&mut handle, self.source_id.url(), &oid) {
                    needs_fetch = false;
                } else {
                    debug!("fast path failed, falling back to a git fetch");
                }
            }
        }

        if needs_fetch {
            // git fetch origin master
            let url = self.source_id.url().to_string();
            let refspec = "refs/heads/master:refs/remotes/origin/master";
            git::fetch(&repo, &url, refspec, self.config).chain_error(|| {
                human(format!("failed to fetch `{}`", url))
            })?;
        }
        self.head.set(None);
        *self.tree.borrow_mut() = None;
        Ok(())
    }

    fn download(&mut self, pkg: &PackageId, checksum: &str)
                -> CargoResult<FileLock> {
        let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
        let path = Path::new(&filename);

        // Attempt to open an read-only copy first to avoid an exclusive write
        // lock and also work with read-only filesystems. Note that we check the
        // length of the file like below to handle interrupted downloads.
        //
        // If this fails then we fall through to the exclusive path where we may
        // have to redownload the file.
        if let Ok(dst) = self.cache_path.open_ro(path, self.config, &filename) {
            let meta = dst.file().metadata()?;
            if meta.len() > 0 {
                return Ok(dst)
            }
        }
        let mut dst = self.cache_path.open_rw(path, self.config, &filename)?;
        let meta = dst.file().metadata()?;
        if meta.len() > 0 {
            return Ok(dst)
        }
        self.config.shell().status("Downloading", pkg)?;

        let config = self.config()?.unwrap();
        let mut url = config.dl.to_url()?;
        url.path_segments_mut().unwrap()
            .push(pkg.name())
            .push(&pkg.version().to_string())
            .push("download");

        // TODO: don't download into memory, but ensure that if we ctrl-c a
        //       download we should resume either from the start or the middle
        //       on the next time
        let url = url.to_string();
        let mut handle = self.easy()?.borrow_mut();
        handle.get(true)?;
        handle.url(&url)?;
        handle.follow_location(true)?;
        let mut state = Sha256::new();
        let mut body = Vec::new();
        network::with_retry(self.config, || {
            state = Sha256::new();
            body = Vec::new();
            {
                let mut handle = handle.transfer();
                handle.write_function(|buf| {
                    state.update(buf);
                    body.extend_from_slice(buf);
                    Ok(buf.len())
                })?;
                handle.perform()?;
            }
            let code = handle.response_code()?;
            if code != 200 && code != 0 {
                let url = handle.effective_url()?.unwrap_or(&url);
                Err(HttpError::Not200(code, url.to_string()))
            } else {
                Ok(())
            }
        })?;

        // Verify what we just downloaded
        if state.finish().to_hex() != checksum {
            bail!("failed to verify the checksum of `{}`", pkg)
        }

        dst.write_all(&body)?;
        dst.seek(SeekFrom::Start(0))?;
        Ok(dst)
    }
}

impl<'cfg> Drop for RemoteRegistry<'cfg> {
    fn drop(&mut self) {
        // Just be sure to drop this before our other fields
        self.tree.borrow_mut().take();
    }
}

/// Updating the index is done pretty regularly so we want it to be as fast as
/// possible. For registries hosted on github (like the crates.io index) there's
/// a fast path available to use [1] to tell us that there's no updates to be
/// made.
///
/// This function will attempt to hit that fast path and verify that the `oid`
/// is actually the current `master` branch of the repository. If `true` is
/// returned then no update needs to be performed, but if `false` is returned
/// then the standard update logic still needs to happen.
///
/// [1]: https://developer.github.com/v3/repos/commits/#get-the-sha-1-of-a-commit-reference
///
/// Note that this function should never cause an actual failure because it's
/// just a fast path. As a result all errors are ignored in this function and we
/// just return a `bool`. Any real errors will be reported through the normal
/// update path above.
fn github_up_to_date(handle: &mut Easy, url: &Url, oid: &git2::Oid) -> bool {
    macro_rules! try {
        ($e:expr) => (match $e {
            Some(e) => e,
            None => return false,
        })
    }

    // This expects github urls in the form `github.com/user/repo` and nothing
    // else
    let mut pieces = try!(url.path_segments());
    let username = try!(pieces.next());
    let repo = try!(pieces.next());
    if pieces.next().is_some() {
        return false
    }

    let url = format!("https://api.github.com/repos/{}/{}/commits/master",
                      username, repo);
    try!(handle.get(true).ok());
    try!(handle.url(&url).ok());
    try!(handle.useragent("cargo").ok());
    let mut headers = List::new();
    try!(headers.append("Accept: application/vnd.github.3.sha").ok());
    try!(headers.append(&format!("If-None-Match: \"{}\"", oid)).ok());
    try!(handle.http_headers(headers).ok());
    try!(handle.perform().ok());

    try!(handle.response_code().ok()) == 304
}
<commit_msg>Auto merge of #4060 - alexcrichton:nobare, r=matklad<commit_after>use std::cell::{RefCell, Ref, Cell};
use std::io::SeekFrom;
use std::io::prelude::*;
use std::mem;
use std::path::Path;

use curl::easy::{Easy, List};
use git2;
use rustc_serialize::hex::ToHex;
use serde_json;
use url::Url;

use core::{PackageId, SourceId};
use ops;
use sources::git;
use sources::registry::{RegistryData, RegistryConfig, INDEX_LOCK};
use util::network;
use util::{FileLock, Filesystem, LazyCell};
use util::{Config, CargoResult, ChainError, human, Sha256, ToUrl};
use util::errors::HttpError;

pub struct RemoteRegistry<'cfg> {
    index_path: Filesystem,
    cache_path: Filesystem,
    source_id: SourceId,
    config: &'cfg Config,
    handle: LazyCell<RefCell<Easy>>,
    tree: RefCell<Option<git2::Tree<'static>>>,
    repo: LazyCell<git2::Repository>,
    head: Cell<Option<git2::Oid>>,
}

impl<'cfg> RemoteRegistry<'cfg> {
    pub fn new(source_id: &SourceId, config: &'cfg Config, name: &str)
               -> RemoteRegistry<'cfg> {
        RemoteRegistry {
            index_path: config.registry_index_path().join(name),
            cache_path: config.registry_cache_path().join(name),
            source_id: source_id.clone(),
            config: config,
            tree: RefCell::new(None),
            handle: LazyCell::new(),
            repo: LazyCell::new(),
            head: Cell::new(None),
        }
    }

    fn easy(&self) -> CargoResult<&RefCell<Easy>> {
        self.handle.get_or_try_init(|| {
            ops::http_handle(self.config).map(RefCell::new)
        })
    }

    fn repo(&self) -> CargoResult<&git2::Repository> {
        self.repo.get_or_try_init(|| {
            let path = self.index_path.clone().into_path_unlocked();

            // Fast path without a lock
            if let Ok(repo) = git2::Repository::open(&path) {
                return Ok(repo)
            }

            // Ok, now we need to lock and try the whole thing over again.
            let lock = self.index_path.open_rw(Path::new(INDEX_LOCK),
                                               self.config,
                                               "the registry index")?;
            match git2::Repository::open(&path) {
                Ok(repo) => Ok(repo),
                Err(_) => {
                    let _ = lock.remove_siblings();

                    // Note that we'd actually prefer to use a bare repository
                    // here as we're not actually going to check anything out.
                    // All versions of Cargo, though, share the same CARGO_HOME,
                    // so for compatibility with older Cargo which *does* do
                    // checkouts we make sure to initialize a new full
                    // repository (not a bare one).
                    //
                    // We should change this to `init_bare` whenever we feel
                    // like enough time has passed or if we change the directory
                    // that the folder is located in, such as by changing the
                    // hash at the end of the directory.
                    Ok(git2::Repository::init(&path)?)
                }
            }
        })
    }

    fn head(&self) -> CargoResult<git2::Oid> {
        if self.head.get().is_none() {
            let oid = self.repo()?.refname_to_id("refs/remotes/origin/master")?;
            self.head.set(Some(oid));
        }
        Ok(self.head.get().unwrap())
    }

    fn tree(&self) -> CargoResult<Ref<git2::Tree>> {
        {
            let tree = self.tree.borrow();
            if tree.is_some() {
                return Ok(Ref::map(tree, |s| s.as_ref().unwrap()))
            }
        }
        let repo = self.repo()?;
        let commit = repo.find_commit(self.head()?)?;
        let tree = commit.tree()?;

        // Unfortunately in libgit2 the tree objects look like they've got a
        // reference to the repository object which means that a tree cannot
        // outlive the repository that it came from. Here we want to cache this
        // tree, though, so to accomplish this we transmute it to a static
        // lifetime.
        //
        // Note that we don't actually hand out the static lifetime, instead we
        // only return a scoped one from this function. Additionally the repo
        // we loaded from (above) lives as long as this object
        // (`RemoteRegistry`) so we then just need to ensure that the tree is
        // destroyed first in the destructor, hence the destructor on
        // `RemoteRegistry` below.
        let tree = unsafe {
            mem::transmute::<git2::Tree, git2::Tree<'static>>(tree)
        };
        *self.tree.borrow_mut() = Some(tree);
        Ok(Ref::map(self.tree.borrow(), |s| s.as_ref().unwrap()))
    }
}

impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
    fn index_path(&self) -> &Filesystem {
        &self.index_path
    }

    fn load(&self, _root: &Path, path: &Path) -> CargoResult<Vec<u8>> {
        // Note that the index calls this method and the filesystem is locked
        // in the index, so we don't need to worry about an `update_index`
        // happening in a different process.
        let repo = self.repo()?;
        let tree = self.tree()?;
        let entry = tree.get_path(path)?;
        let object = entry.to_object(&repo)?;
        let blob = match object.as_blob() {
            Some(blob) => blob,
            None => bail!("path `{}` is not a blob in the git repo", path.display()),
        };
        Ok(blob.content().to_vec())
    }

    fn config(&mut self) -> CargoResult<Option<RegistryConfig>> {
        self.repo()?; // create intermediate dirs and initialize the repo
        let _lock = self.index_path.open_ro(Path::new(INDEX_LOCK),
                                            self.config,
                                            "the registry index")?;
        let json = self.load(Path::new(""), Path::new("config.json"))?;
        let config = serde_json::from_slice(&json)?;
        Ok(Some(config))
    }

    fn update_index(&mut self) -> CargoResult<()> {
        // Ensure that we'll actually be able to acquire an HTTP handle later on
        // once we start trying to download crates. This will weed out any
        // problems with `.cargo/config` configuration related to HTTP.
        //
        // This way if there's a problem the error gets printed before we even
        // hit the index, which may not actually read this configuration.
        ops::http_handle(self.config)?;

        let repo = self.repo()?;
        let _lock = self.index_path.open_rw(Path::new(INDEX_LOCK),
                                            self.config,
                                            "the registry index")?;
        self.config.shell().status("Updating",
             format!("registry `{}`", self.source_id.url()))?;
        let mut needs_fetch = true;

        if self.source_id.url().host_str() == Some("github.com") {
            if let Ok(oid) = self.head() {
                let mut handle = self.easy()?.borrow_mut();
                debug!("attempting github fast path for {}",
                       self.source_id.url());
                if github_up_to_date(&mut handle, self.source_id.url(), &oid) {
                    needs_fetch = false;
                } else {
                    debug!("fast path failed, falling back to a git fetch");
                }
            }
        }

        if needs_fetch {
            // git fetch origin master
            let url = self.source_id.url().to_string();
            let refspec = "refs/heads/master:refs/remotes/origin/master";
            git::fetch(&repo, &url, refspec, self.config).chain_error(|| {
                human(format!("failed to fetch `{}`", url))
            })?;
        }
        self.head.set(None);
        *self.tree.borrow_mut() = None;
        Ok(())
    }

    fn download(&mut self, pkg: &PackageId, checksum: &str)
                -> CargoResult<FileLock> {
        let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
        let path = Path::new(&filename);

        // Attempt to open an read-only copy first to avoid an exclusive write
        // lock and also work with read-only filesystems. Note that we check the
        // length of the file like below to handle interrupted downloads.
        //
        // If this fails then we fall through to the exclusive path where we may
        // have to redownload the file.
        if let Ok(dst) = self.cache_path.open_ro(path, self.config, &filename) {
            let meta = dst.file().metadata()?;
            if meta.len() > 0 {
                return Ok(dst)
            }
        }
        let mut dst = self.cache_path.open_rw(path, self.config, &filename)?;
        let meta = dst.file().metadata()?;
        if meta.len() > 0 {
            return Ok(dst)
        }
        self.config.shell().status("Downloading", pkg)?;

        let config = self.config()?.unwrap();
        let mut url = config.dl.to_url()?;
        url.path_segments_mut().unwrap()
            .push(pkg.name())
            .push(&pkg.version().to_string())
            .push("download");

        // TODO: don't download into memory, but ensure that if we ctrl-c a
        //       download we should resume either from the start or the middle
        //       on the next time
        let url = url.to_string();
        let mut handle = self.easy()?.borrow_mut();
        handle.get(true)?;
        handle.url(&url)?;
        handle.follow_location(true)?;
        let mut state = Sha256::new();
        let mut body = Vec::new();
        network::with_retry(self.config, || {
            state = Sha256::new();
            body = Vec::new();
            {
                let mut handle = handle.transfer();
                handle.write_function(|buf| {
                    state.update(buf);
                    body.extend_from_slice(buf);
                    Ok(buf.len())
                })?;
                handle.perform()?;
            }
            let code = handle.response_code()?;
            if code != 200 && code != 0 {
                let url = handle.effective_url()?.unwrap_or(&url);
                Err(HttpError::Not200(code, url.to_string()))
            } else {
                Ok(())
            }
        })?;

        // Verify what we just downloaded
        if state.finish().to_hex() != checksum {
            bail!("failed to verify the checksum of `{}`", pkg)
        }

        dst.write_all(&body)?;
        dst.seek(SeekFrom::Start(0))?;
        Ok(dst)
    }
}

impl<'cfg> Drop for RemoteRegistry<'cfg> {
    fn drop(&mut self) {
        // Just be sure to drop this before our other fields
        self.tree.borrow_mut().take();
    }
}

/// Updating the index is done pretty regularly so we want it to be as fast as
/// possible. For registries hosted on github (like the crates.io index) there's
/// a fast path available to use [1] to tell us that there's no updates to be
/// made.
///
/// This function will attempt to hit that fast path and verify that the `oid`
/// is actually the current `master` branch of the repository. If `true` is
/// returned then no update needs to be performed, but if `false` is returned
/// then the standard update logic still needs to happen.
///
/// [1]: https://developer.github.com/v3/repos/commits/#get-the-sha-1-of-a-commit-reference
///
/// Note that this function should never cause an actual failure because it's
/// just a fast path. As a result all errors are ignored in this function and we
/// just return a `bool`. Any real errors will be reported through the normal
/// update path above.
fn github_up_to_date(handle: &mut Easy, url: &Url, oid: &git2::Oid) -> bool {
    macro_rules! try {
        ($e:expr) => (match $e {
            Some(e) => e,
            None => return false,
        })
    }

    // This expects github urls in the form `github.com/user/repo` and nothing
    // else
    let mut pieces = try!(url.path_segments());
    let username = try!(pieces.next());
    let repo = try!(pieces.next());
    if pieces.next().is_some() {
        return false
    }

    let url = format!("https://api.github.com/repos/{}/{}/commits/master",
                      username, repo);
    try!(handle.get(true).ok());
    try!(handle.url(&url).ok());
    try!(handle.useragent("cargo").ok());
    let mut headers = List::new();
    try!(headers.append("Accept: application/vnd.github.3.sha").ok());
    try!(headers.append(&format!("If-None-Match: \"{}\"", oid)).ok());
    try!(handle.http_headers(headers).ok());
    try!(handle.perform().ok());

    try!(handle.response_code().ok()) == 304
}
<|endoftext|>


<commit_before>// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use libc;

use ffi::CStr;
use io;
use mem;
use ptr;
use sys::backtrace::BacktraceContext;
use sys_common::backtrace::Frame;

pub fn foreach_symbol_fileline<F>(frame: Frame,
                                  mut f: F,
                                  _: &BacktraceContext) -> io::Result<bool>
where F: FnMut(&[u8], libc::c_int) -> io::Result<()>
{
    // pcinfo may return an arbitrary number of file:line pairs,
    // in the order of stack trace (i.e. inlined calls first).
    // in order to avoid allocation, we stack-allocate a fixed size of entries.
    const FILELINE_SIZE: usize = 32;
    let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE];
    let ret;
    let fileline_count = {
        let state = unsafe { init_state() };
        let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
        let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
        ret = unsafe {
            backtrace_pcinfo(state,
                             frame.exact_position as libc::uintptr_t,
                             pcinfo_cb,
                             error_cb,
                             fileline_addr as *mut libc::c_void)
        };
        FILELINE_SIZE - fileline_win.len()
    };
    if ret == 0 {
        for &(file, line) in &fileline_buf[..fileline_count] {
            if file.is_null() { continue; } // just to be sure
            let file = unsafe { CStr::from_ptr(file).to_bytes() };
            f(file, line)?;
        }
        Ok(fileline_count == FILELINE_SIZE)
    } else {
        Ok(false)
    }
}

/// Converts a pointer to symbol to its string value.
pub fn resolve_symname<F>(frame: Frame,
                          callback: F,
                          _: &BacktraceContext) -> io::Result<()>
    where F: FnOnce(Option<&str>) -> io::Result<()>
{
    let symname = {
        let state = unsafe { init_state() };
        if state.is_null() {
            None
        } else {
            let mut data = ptr::null();
            let data_addr = &mut data as *mut *const libc::c_char;
            let ret = unsafe {
                backtrace_syminfo(state,
                                  frame.symbol_addr as libc::uintptr_t,
                                  syminfo_cb,
                                  error_cb,
                                  data_addr as *mut libc::c_void)
            };
            if ret == 0 || data.is_null() {
                None
            } else {
                unsafe {
                    CStr::from_ptr(data).to_str().ok()
                }
            }
        }
    };
    callback(symname)
}

////////////////////////////////////////////////////////////////////////
// libbacktrace.h API
////////////////////////////////////////////////////////////////////////
type backtrace_syminfo_callback =
extern "C" fn(data: *mut libc::c_void,
              pc: libc::uintptr_t,
              symname: *const libc::c_char,
              symval: libc::uintptr_t,
              symsize: libc::uintptr_t);
type backtrace_full_callback =
extern "C" fn(data: *mut libc::c_void,
              pc: libc::uintptr_t,
              filename: *const libc::c_char,
              lineno: libc::c_int,
              function: *const libc::c_char) -> libc::c_int;
type backtrace_error_callback =
extern "C" fn(data: *mut libc::c_void,
              msg: *const libc::c_char,
              errnum: libc::c_int);
enum backtrace_state {}

extern {
    fn backtrace_create_state(filename: *const libc::c_char,
                              threaded: libc::c_int,
                              error: backtrace_error_callback,
                              data: *mut libc::c_void)
        -> *mut backtrace_state;
    fn backtrace_syminfo(state: *mut backtrace_state,
                         addr: libc::uintptr_t,
                         cb: backtrace_syminfo_callback,
                         error: backtrace_error_callback,
                         data: *mut libc::c_void) -> libc::c_int;
    fn backtrace_pcinfo(state: *mut backtrace_state,
                        addr: libc::uintptr_t,
                        cb: backtrace_full_callback,
                        error: backtrace_error_callback,
                        data: *mut libc::c_void) -> libc::c_int;
}

////////////////////////////////////////////////////////////////////////
// helper callbacks
////////////////////////////////////////////////////////////////////////

type FileLine = (*const libc::c_char, libc::c_int);

extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char,
                   _errnum: libc::c_int) {
    // do nothing for now
}
extern fn syminfo_cb(data: *mut libc::c_void,
                     _pc: libc::uintptr_t,
                     symname: *const libc::c_char,
                     _symval: libc::uintptr_t,
                     _symsize: libc::uintptr_t) {
    let slot = data as *mut *const libc::c_char;
    unsafe { *slot = symname; }
}
extern fn pcinfo_cb(data: *mut libc::c_void,
                    _pc: libc::uintptr_t,
                    filename: *const libc::c_char,
                    lineno: libc::c_int,
                    _function: *const libc::c_char) -> libc::c_int {
    if !filename.is_null() {
        let slot = data as *mut &mut [FileLine];
        let buffer = unsafe {ptr::read(slot)};

        // if the buffer is not full, add file:line to the buffer
        // and adjust the buffer for next possible calls to pcinfo_cb.
        if !buffer.is_empty() {
            buffer[0] = (filename, lineno);
            unsafe { ptr::write(slot, &mut buffer[1..]); }
        }
    }

    0
}

// The libbacktrace API supports creating a state, but it does not
// support destroying a state. I personally take this to mean that a
// state is meant to be created and then live forever.
//
// I would love to register an at_exit() handler which cleans up this
// state, but libbacktrace provides no way to do so.
//
// With these constraints, this function has a statically cached state
// that is calculated the first time this is requested. Remember that
// backtracing all happens serially (one global lock).
//
// Things don't work so well on not-Linux since libbacktrace can't track
// down that executable this is. We at one point used env::current_exe but
// it turns out that there are some serious security issues with that
// approach.
//
// Specifically, on certain platforms like BSDs, a malicious actor can cause
// an arbitrary file to be placed at the path returned by current_exe.
// libbacktrace does not behave defensively in the presence of ill-formed
// DWARF information, and has been demonstrated to segfault in at least one
// case. There is no evidence at the moment to suggest that a more carefully
// constructed file can't cause arbitrary code execution. As a result of all
// of this, we don't hint libbacktrace with the path to the current process.
unsafe fn init_state() -> *mut backtrace_state {
    static mut STATE: *mut backtrace_state = ptr::null_mut();
    if !STATE.is_null() { return STATE  }

    let filename = match ::sys::backtrace::gnu::get_executable_filename() {
        Ok((filename, file)) => {
            // filename is purposely leaked here since libbacktrace requires
            // it to stay allocated permanently, file is also leaked so that
            // the file stays locked
            let filename_ptr = filename.as_ptr();
            mem::forget(filename);
            mem::forget(file);
            filename_ptr
        },
        Err(_) => ptr::null(),
    };

    STATE = backtrace_create_state(filename, 0, error_cb,
                                   ptr::null_mut());
    STATE
}
<commit_msg>Correctly bubble up errors from libbacktrace<commit_after>// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use libc;

use ffi::CStr;
use io;
use mem;
use ptr;
use sys::backtrace::BacktraceContext;
use sys_common::backtrace::Frame;

pub fn foreach_symbol_fileline<F>(frame: Frame,
                                  mut f: F,
                                  _: &BacktraceContext) -> io::Result<bool>
where F: FnMut(&[u8], libc::c_int) -> io::Result<()>
{
    // pcinfo may return an arbitrary number of file:line pairs,
    // in the order of stack trace (i.e. inlined calls first).
    // in order to avoid allocation, we stack-allocate a fixed size of entries.
    const FILELINE_SIZE: usize = 32;
    let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE];
    let ret;
    let fileline_count = {
        let state = unsafe { init_state() };
        if state.is_null() {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                "failed to allocate libbacktrace state")
            )
        }
        let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
        let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
        ret = unsafe {
            backtrace_pcinfo(state,
                             frame.exact_position as libc::uintptr_t,
                             pcinfo_cb,
                             error_cb,
                             fileline_addr as *mut libc::c_void)
        };
        FILELINE_SIZE - fileline_win.len()
    };
    if ret == 0 {
        for &(file, line) in &fileline_buf[..fileline_count] {
            if file.is_null() { continue; } // just to be sure
            let file = unsafe { CStr::from_ptr(file).to_bytes() };
            f(file, line)?;
        }
        Ok(fileline_count == FILELINE_SIZE)
    } else {
        Ok(false)
    }
}

/// Converts a pointer to symbol to its string value.
pub fn resolve_symname<F>(frame: Frame,
                          callback: F,
                          _: &BacktraceContext) -> io::Result<()>
    where F: FnOnce(Option<&str>) -> io::Result<()>
{
    let symname = {
        let state = unsafe { init_state() };
        if state.is_null() {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                "failed to allocate libbacktrace state")
            )
        }
        let mut data = ptr::null();
        let data_addr = &mut data as *mut *const libc::c_char;
        let ret = unsafe {
            backtrace_syminfo(state,
                              frame.symbol_addr as libc::uintptr_t,
                              syminfo_cb,
                              error_cb,
                              data_addr as *mut libc::c_void)
        };
        if ret == 0 || data.is_null() {
            None
        } else {
            unsafe {
                CStr::from_ptr(data).to_str().ok()
            }
        }
    };
    callback(symname)
}

////////////////////////////////////////////////////////////////////////
// libbacktrace.h API
////////////////////////////////////////////////////////////////////////
type backtrace_syminfo_callback =
extern "C" fn(data: *mut libc::c_void,
              pc: libc::uintptr_t,
              symname: *const libc::c_char,
              symval: libc::uintptr_t,
              symsize: libc::uintptr_t);
type backtrace_full_callback =
extern "C" fn(data: *mut libc::c_void,
              pc: libc::uintptr_t,
              filename: *const libc::c_char,
              lineno: libc::c_int,
              function: *const libc::c_char) -> libc::c_int;
type backtrace_error_callback =
extern "C" fn(data: *mut libc::c_void,
              msg: *const libc::c_char,
              errnum: libc::c_int);
enum backtrace_state {}

extern {
    fn backtrace_create_state(filename: *const libc::c_char,
                              threaded: libc::c_int,
                              error: backtrace_error_callback,
                              data: *mut libc::c_void)
        -> *mut backtrace_state;
    fn backtrace_syminfo(state: *mut backtrace_state,
                         addr: libc::uintptr_t,
                         cb: backtrace_syminfo_callback,
                         error: backtrace_error_callback,
                         data: *mut libc::c_void) -> libc::c_int;
    fn backtrace_pcinfo(state: *mut backtrace_state,
                        addr: libc::uintptr_t,
                        cb: backtrace_full_callback,
                        error: backtrace_error_callback,
                        data: *mut libc::c_void) -> libc::c_int;
}

////////////////////////////////////////////////////////////////////////
// helper callbacks
////////////////////////////////////////////////////////////////////////

type FileLine = (*const libc::c_char, libc::c_int);

extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char,
                   _errnum: libc::c_int) {
    // do nothing for now
}
extern fn syminfo_cb(data: *mut libc::c_void,
                     _pc: libc::uintptr_t,
                     symname: *const libc::c_char,
                     _symval: libc::uintptr_t,
                     _symsize: libc::uintptr_t) {
    let slot = data as *mut *const libc::c_char;
    unsafe { *slot = symname; }
}
extern fn pcinfo_cb(data: *mut libc::c_void,
                    _pc: libc::uintptr_t,
                    filename: *const libc::c_char,
                    lineno: libc::c_int,
                    _function: *const libc::c_char) -> libc::c_int {
    if !filename.is_null() {
        let slot = data as *mut &mut [FileLine];
        let buffer = unsafe {ptr::read(slot)};

        // if the buffer is not full, add file:line to the buffer
        // and adjust the buffer for next possible calls to pcinfo_cb.
        if !buffer.is_empty() {
            buffer[0] = (filename, lineno);
            unsafe { ptr::write(slot, &mut buffer[1..]); }
        }
    }

    0
}

// The libbacktrace API supports creating a state, but it does not
// support destroying a state. I personally take this to mean that a
// state is meant to be created and then live forever.
//
// I would love to register an at_exit() handler which cleans up this
// state, but libbacktrace provides no way to do so.
//
// With these constraints, this function has a statically cached state
// that is calculated the first time this is requested. Remember that
// backtracing all happens serially (one global lock).
//
// Things don't work so well on not-Linux since libbacktrace can't track
// down that executable this is. We at one point used env::current_exe but
// it turns out that there are some serious security issues with that
// approach.
//
// Specifically, on certain platforms like BSDs, a malicious actor can cause
// an arbitrary file to be placed at the path returned by current_exe.
// libbacktrace does not behave defensively in the presence of ill-formed
// DWARF information, and has been demonstrated to segfault in at least one
// case. There is no evidence at the moment to suggest that a more carefully
// constructed file can't cause arbitrary code execution. As a result of all
// of this, we don't hint libbacktrace with the path to the current process.
unsafe fn init_state() -> *mut backtrace_state {
    static mut STATE: *mut backtrace_state = ptr::null_mut();
    if !STATE.is_null() { return STATE  }

    let filename = match ::sys::backtrace::gnu::get_executable_filename() {
        Ok((filename, file)) => {
            // filename is purposely leaked here since libbacktrace requires
            // it to stay allocated permanently, file is also leaked so that
            // the file stays locked
            let filename_ptr = filename.as_ptr();
            mem::forget(filename);
            mem::forget(file);
            filename_ptr
        },
        Err(_) => ptr::null(),
    };

    STATE = backtrace_create_state(filename, 0, error_cb,
                                   ptr::null_mut());
    STATE
}
<|endoftext|>


<commit_before>extern crate requests;
extern crate getopts;
extern crate chrono;

use chrono::Duration;
use std::fmt;

fn main() {
    use std::env;
    use std::fs::File;
    use std::io::Write;

    let args: Vec<String> = env::args().collect();
    let mut opts = getopts::Options::new();

    opts.optopt("o", "", "set output file name", "NAME");
    let matches = opts.parse(&args[1..]).unwrap();
    let output = matches.opt_str("o");

    let pr_list = pr_list();

    match output {
        None => println!("{:?}", pr_list),
        Some(output) => {
            let mut file = File::create(output).expect("error while opening/creating output file");
            file.write_all(pr_list.as_bytes()).expect("error while writing to output file");
        }
    }
}

fn pr_list() -> String {
    use chrono::{UTC, DateTime};

    const URL: &'static str = "https://api.github.com/search/issues?q=repo:phil-opp/blog_os+type:\
                               pr+is:merged+label:relnotes";

    let mut ret = String::from("
");

    let res = requests::get(URL).expect("Error while querying GitHub API");
    let data = res.json().expect("Error parsing JSON");

    for pr in data["items"].members().take(5) {
        let now = UTC::now();
        let merged_at = pr["closed_at"].as_str().unwrap().parse::<DateTime<UTC>>().unwrap();
        let ago = now - merged_at;

        let item = format!(r#"
{} {}"#,
                           pr["html_url"],
                           pr["title"],
                           DateFmt(ago));
        ret.push_str(&item);
    }

    ret.push_str("
");
    ret
}

struct DateFmt(Duration);

impl fmt::Display for DateFmt {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        try!(write!(f, r#"<time datetime="{}">"#, self.0));

        try!(if self.0.num_minutes() == 1 {
            write!(f, "1 minute ago")
        } else if self.0.num_minutes() < 60 {
            write!(f, "{} minutes ago", self.0.num_minutes())
        } else if self.0.num_hours() == 1 {
            write!(f, "1 hour ago")
        } else if self.0.num_hours() < 24 {
            write!(f, "{} hours ago", self.0.num_hours())
        } else if self.0.num_days() == 1 {
            write!(f, "1 day ago")
        } else if self.0.num_days() < 7 {
            write!(f, "{} days ago", self.0.num_days())
        } else if self.0.num_weeks() == 1 {
            write!(f, "1 week ago")
        } else if self.0.num_weeks() < 4 {
            write!(f, "{} weeks ago", self.0.num_weeks())
        } else if self.0.num_weeks() == 4 {
            write!(f, "1 month ago")
        } else if self.0.num_days() < 365 {
            write!(f, "{} months ago", self.0.num_days() / 30)
        } else if self.0.num_days() < 365 * 2 {
            write!(f, "1 year ago")
        } else {
            write!(f, "{} years ago", self.0.num_days() / 365)
        });

        write!(f, "</datetime>")
    }
}
<commit_msg>Use non-breaking spaces when formatting durations<commit_after>extern crate requests;
extern crate getopts;
extern crate chrono;

use chrono::Duration;
use std::fmt;

fn main() {
    use std::env;
    use std::fs::File;
    use std::io::Write;

    let args: Vec<String> = env::args().collect();
    let mut opts = getopts::Options::new();

    opts.optopt("o", "", "set output file name", "NAME");
    let matches = opts.parse(&args[1..]).unwrap();
    let output = matches.opt_str("o");

    let pr_list = pr_list();

    match output {
        None => println!("{:?}", pr_list),
        Some(output) => {
            let mut file = File::create(output).expect("error while opening/creating output file");
            file.write_all(pr_list.as_bytes()).expect("error while writing to output file");
        }
    }
}

fn pr_list() -> String {
    use chrono::{UTC, DateTime};

    const URL: &'static str = "https://api.github.com/search/issues?q=repo:phil-opp/blog_os+type:\
                               pr+is:merged+label:relnotes";

    let mut ret = String::from("
");

    let res = requests::get(URL).expect("Error while querying GitHub API");
    let data = res.json().expect("Error parsing JSON");

    for pr in data["items"].members().take(5) {
        let now = UTC::now();
        let merged_at = pr["closed_at"].as_str().unwrap().parse::<DateTime<UTC>>().unwrap();
        let ago = now - merged_at;

        let item = format!(r#"
{} {}"#,
                           pr["html_url"],
                           pr["title"],
                           DateFmt(ago));
        ret.push_str(&item);
    }

    ret.push_str("
");
    ret
}

struct DateFmt(Duration);

impl fmt::Display for DateFmt {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        try!(write!(f, r#"<time datetime="{}">"#, self.0));

        try!(if self.0.num_minutes() == 1 {
            write!(f, "1\u{a0}minute\u{a0}ago")
        } else if self.0.num_minutes() < 60 {
            write!(f, "{}\u{a0}minutes\u{a0}ago", self.0.num_minutes())
        } else if self.0.num_hours() == 1 {
            write!(f, "1\u{a0}hour\u{a0}ago")
        } else if self.0.num_hours() < 24 {
            write!(f, "{}\u{a0}hours\u{a0}ago", self.0.num_hours())
        } else if self.0.num_days() == 1 {
            write!(f, "1\u{a0}day\u{a0}ago")
        } else if self.0.num_days() < 7 {
            write!(f, "{}\u{a0}days\u{a0}ago", self.0.num_days())
        } else if self.0.num_weeks() == 1 {
            write!(f, "1\u{a0}week\u{a0}ago")
        } else if self.0.num_weeks() < 4 {
            write!(f, "{}\u{a0}weeks\u{a0}ago", self.0.num_weeks())
        } else if self.0.num_weeks() == 4 {
            write!(f, "1\u{a0}month\u{a0}ago")
        } else if self.0.num_days() < 365 {
            write!(f, "{}\u{a0}months\u{a0}ago", self.0.num_days() / 30)
        } else if self.0.num_days() < 365 * 2 {
            write!(f, "1\u{a0}year\u{a0}ago")
        } else {
            write!(f, "{} years ago", self.0.num_days() / 365)
        });

        write!(f, "</datetime>")
    }
}
<|endoftext|>


<commit_before>extern crate requests;
extern crate getopts;
extern crate chrono;

use chrono::Duration;
use std::fmt;

fn main() {
    use std::env;
    use std::fs::File;
    use std::io::Write;

    let args: Vec<String> = env::args().collect();
    let mut opts = getopts::Options::new();

    opts.optopt("o", "", "set output file name", "NAME");
    let matches = opts.parse(&args[1..]).unwrap();
    let output = matches.opt_str("o");

    let pr_list = pr_list();

    match output {
        None => println!("{:?}", pr_list),
        Some(output) => {
            let mut file = File::create(output).expect("error while opening/creating output file");
            file.write_all(pr_list.as_bytes()).expect("error while writing to output file");
        }
    }
}

fn pr_list() -> String {
    use chrono::{UTC, DateTime};

    const URL: &'static str = "https://api.github.com/search/issues?q=repo:phil-opp/blog_os+type:\
                               pr+is:merged+label:relnotes";

    let mut ret = String::from("
");

    let res = requests::get(URL).expect("Error while querying GitHub API");
    let data = res.json().expect("Error parsing JSON");

    for pr in data["items"].members().take(5) {
        let now = UTC::now();
        let merged_at = pr["closed_at"].as_str().unwrap().parse::<DateTime<UTC>>().unwrap();
        let ago = now - merged_at;

        let item = format!(r#"
{} {}"#,
                           pr["html_url"],
                           pr["title"],
                           DateFmt(ago));
        ret.push_str(&item);
    }

    ret.push_str("
");
    ret
}

struct DateFmt(Duration);

impl fmt::Display for DateFmt {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        try!(write!(f, r#"<time datetime="{}">"#, self.0));

        try!(if self.0.num_minutes() == 1 {
            write!(f, "1\u{a0}minute\u{a0}ago")
        } else if self.0.num_minutes() < 60 {
            write!(f, "{}\u{a0}minutes\u{a0}ago", self.0.num_minutes())
        } else if self.0.num_hours() == 1 {
            write!(f, "1\u{a0}hour\u{a0}ago")
        } else if self.0.num_hours() < 24 {
            write!(f, "{}\u{a0}hours\u{a0}ago", self.0.num_hours())
        } else if self.0.num_days() == 1 {
            write!(f, "1\u{a0}day\u{a0}ago")
        } else if self.0.num_days() < 7 {
            write!(f, "{}\u{a0}days\u{a0}ago", self.0.num_days())
        } else if self.0.num_weeks() == 1 {
            write!(f, "1\u{a0}week\u{a0}ago")
        } else if self.0.num_weeks() < 4 {
            write!(f, "{}\u{a0}weeks\u{a0}ago", self.0.num_weeks())
        } else if self.0.num_weeks() == 4 {
            write!(f, "1\u{a0}month\u{a0}ago")
        } else if self.0.num_days() < 365 {
            write!(f, "{}\u{a0}months\u{a0}ago", self.0.num_days() / 30)
        } else if self.0.num_days() < 365 * 2 {
            write!(f, "1\u{a0}year\u{a0}ago")
        } else {
            write!(f, "{} years ago", self.0.num_days() / 365)
        });

        write!(f, "</datetime>")
    }
}
<commit_msg>Use absolute times instead of durations (#236)<commit_after>extern crate requests;
extern crate getopts;
extern crate chrono;

use chrono::{DateTime, UTC};
use std::fmt;

fn main() {
    use std::env;
    use std::fs::File;
    use std::io::Write;

    let args: Vec<String> = env::args().collect();
    let mut opts = getopts::Options::new();

    opts.optopt("o", "", "set output file name", "NAME");
    let matches = opts.parse(&args[1..]).unwrap();
    let output = matches.opt_str("o");

    let pr_list = pr_list();

    match output {
        None => println!("{:?}", pr_list),
        Some(output) => {
            let mut file = File::create(output).expect("error while opening/creating output file");
            file.write_all(pr_list.as_bytes()).expect("error while writing to output file");
        }
    }
}

fn pr_list() -> String {
    const URL: &'static str = "https://api.github.com/search/issues?q=repo:phil-opp/blog_os+type:\
                               pr+is:merged+label:relnotes";

    let mut ret = String::from("
");

    let res = requests::get(URL).expect("Error while querying GitHub API");
    let data = res.json().expect("Error parsing JSON");

    for pr in data["items"].members().take(5) {
        let merged_at = pr["closed_at"].as_str().unwrap().parse::<DateTime<UTC>>().unwrap();
        let item = format!("
{} {}",
                           pr["html_url"],
                           pr["title"],
                           DateFmt(merged_at));
        ret.push_str(&item);
    }

    ret.push_str("
");
    ret
}

struct DateFmt(DateTime<UTC>);

impl fmt::Display for DateFmt {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               r#"<time datetime="{}">{}</datetime>"#,
               self.0,
               self.0.format("%b\u{a0}%d"))
    }
}
<|endoftext|>