<commit_before>//! This module implements config access.
use std::io;
use std::fs;
use std::env;
use std::path::PathBuf;

use dotenv;
use log;
use java_properties;
use clap::ArgMatches;
use url::Url;
use ini::Ini;

use prelude::*;
use constants::{DEFAULT_URL, VERSION, PROTOCOL_VERSION};

/// Represents the auth information
#[derive(Debug, Clone)]
pub enum Auth {
    Key(String),
    Token(String),
}

/// Represents a DSN
#[derive(Debug, Clone)]
pub struct Dsn {
    pub host: String,
    pub protocol: String,
    pub port: u16,
    pub client_id: String,
    pub secret: String,
    pub project_id: u64,
}

impl Dsn {
    /// Parses a Dsn from a given string.
    fn from_str(dsn: &str) -> Result<Dsn> {
        let url = Url::parse(dsn)?;
        let project_id = if let Some(component_iter) = url.path_segments() {
            let components: Vec<_> = component_iter.collect();
            if components.len() != 1 {
                fail!("invalid dsn: invalid project ID");
            }
            components[0].parse().or(Err("invalid dsn: invalid project id"))?
        } else {
            fail!("invalid dsn: missing project ID");
        };
        if !(url.scheme() == "http" || url.scheme() == "https") {
            fail!(format!("invalid dsn: unknown protocol '{}'", url.scheme()));
        }

        if url.username() == "" {
            fail!("invalid dsn: missing client id");
        }

        Ok(Dsn {
            protocol: url.scheme().into(),
            host: url.host_str().ok_or("invalid dsn: missing host")?.into(),
            port: url.port_or_known_default().unwrap(),
            client_id: url.username().into(),
            secret: url.password().ok_or("invalid dsn: missing secret")?.into(),
            project_id: project_id,
        })
    }

    /// Returns the URL where events should be sent.
    pub fn get_submit_url(&self) -> String {
        format!("{}://{}:{}/api/{}/store/",
                self.protocol,
                self.host,
                self.port,
                self.project_id)
    }

    /// Returns the given auth header (ts is the timestamp of the event)
    pub fn get_auth_header(&self, ts: f64) -> String {
        format!("Sentry \
            sentry_timestamp={}, \
            sentry_client=sentry-cli/{}, \
            sentry_version={}, \
            sentry_key={}, \
            sentry_secret={}",
                ts,
                VERSION,
                PROTOCOL_VERSION,
                self.client_id,
                self.secret)
    }
}

pub fn prepare_environment() {
    dotenv::dotenv().ok();
}

/// Represents the `sentry-cli` config.
#[derive(Clone)]
pub struct Config {
    pub filename: PathBuf,
    pub auth: Option<Auth>,
    pub url: String,
    pub log_level: log::LogLevelFilter,
    pub ini: Ini,
}

impl Config {
    /// Loads the CLI config from the default location and returns it.
    pub fn from_cli_config() -> Result<Config> {
        let (filename, ini) = load_cli_config()?;
        Ok(Config {
            filename: filename,
            auth: get_default_auth(&ini),
            url: get_default_url(&ini),
            log_level: get_default_log_level(&ini)?,
            ini: ini,
        })
    }

    /// Update the environment based on the config
    pub fn configure_environment(&self) {
        if !env::var("http_proxy").is_ok() {
            if let Some(proxy) = self.get_proxy_url() {
                env::set_var("http_proxy", proxy);
            }
        }
        #[cfg(not(windows))]
        {
            use openssl_probe::init_ssl_cert_env_vars;
            init_ssl_cert_env_vars();
        }
    }

    /// Returns the base url (without trailing slashes)
    pub fn get_base_url(&self) -> Result<&str> {
        let base = self.url.trim_right_matches('/');
        if !base.starts_with("http://") && !base.starts_with("https://") {
            fail!("bad sentry url: unknown scheme ({})", base);
        }
        if base.matches('/').count() != 2 {
            fail!("bad sentry url: not on URL root ({})", base);
        }
        Ok(base)
    }

    /// Returns the API URL for a path
    pub fn get_api_endpoint(&self, path: &str) -> Result<String> {
        let base = self.get_base_url()?;
        Ok(format!("{}/api/0/{}", base, path.trim_left_matches('/')))
    }

    /// Indicates whether keepalive support should be enabled.  This
    /// mostly corresponds to an ini config but also has some sensible
    /// default handling.
    pub fn allow_keepalive(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "keepalive");
        match val {
            // keepalive is broken on our dev server.  Since this makes local development
            // quite frustrating we disable keepalive (handle reuse) when we connect to
            // unprotected servers where it does not matter that much.
            None => !self.has_insecure_server(),
            Some(val) => val == "true",
        }
    }

    /// Returns the proxy URL if defined.
    fn get_proxy_url(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_url")
    }

    /// Returns the proxy username if defined.
    pub fn get_proxy_username(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_username")
    }

    /// Returns the proxy password if defined.
    pub fn get_proxy_password(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_password")
    }

    /// Indicates if SSL is enabled or disabled for the server.
    pub fn has_insecure_server(&self) -> bool {
        self.url.starts_with("http://")
    }

    /// Indicates whether SSL verification should be on or off.
    pub fn should_verify_ssl(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "verify_ssl");
        match val {
            None => true,
            Some(val) => val == "true",
        }
    }

    /// Controls the SSL revocation check on windows.  This can be used as a
    /// workaround for misconfigured local SSL proxies.
    pub fn disable_ssl_revocation_check(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "check_ssl_revoke");
        match val {
            None => true,
            Some(val) => val == "true",
        }
    }

    /// Given a match object from clap, this returns the org from it.
    pub fn get_org(&self, matches: &ArgMatches) -> Result<String> {
        Ok(matches.value_of("org")
               .map(|x| x.to_owned())
               .or_else(|| env::var("SENTRY_ORG").ok())
               .or_else(|| self.ini.get_from(Some("defaults"), "org").map(|x| x.to_owned()))
               .ok_or("An organization slug is required (provide with --org)")?)
    }

    /// Given a match object from clap, this returns a tuple in the
    /// form `(org, project)` which can either come from the match
    /// object or some defaults (envvar, ini etc.).
    pub fn get_org_and_project(&self, matches: &ArgMatches) -> Result<(String, String)> {
        let org = self.get_org(matches)?;
        let project = if let Some(project) = matches.value_of("project") {
            project.to_owned()
        } else {
            self.get_project_default()?
        };
        Ok((org, project))
    }

    /// Return the default value for a project.
    pub fn get_project_default(&self) -> Result<String> {
        Ok(env::var("SENTRY_PROJECT").ok()
            .or_else(|| self.ini.get_from(Some("defaults"), "project").map(|x| x.to_owned()))
            .ok_or("A project slug is required")?)
    }

    /// Returns the defaults for org and project.
    pub fn get_org_and_project_defaults(&self) -> (Option<String>, Option<String>) {
        (env::var("SENTRY_ORG")
             .ok()
             .or_else(|| self.ini.get_from(Some("defaults"), "org").map(|x| x.to_owned())),
         env::var("SENTRY_PROJECT")
             .ok()
             .or_else(|| self.ini.get_from(Some("defaults"), "project").map(|x| x.to_owned())))
    }

    /// Returns true if notifications should be displayed
    pub fn show_notifications(&self) -> Result<bool> {
        Ok(self.ini.get_from(Some("ui"), "show_notifications")
            .map(|x| x == "true")
            .unwrap_or(true))
    }

    /// Returns the maximum dsym upload size
    pub fn get_max_dsym_upload_size(&self) -> Result<u64> {
        Ok(self.ini.get_from(Some("dsym"), "max_upload_size")
            .and_then(|x| x.parse().ok())
            .unwrap_or(35 * 1024 * 1024))
    }

    /// Return the DSN
    pub fn get_dsn(&self) -> Result<Dsn> {
        if let Some(ref val) = env::var("SENTRY_DSN").ok() {
            Dsn::from_str(val)
        } else if let Some(val) = self.ini.get_from(Some("auth"), "dsn") {
            Dsn::from_str(val)
        } else {
            fail!("No DSN provided");
        }
    }

    /// Should we nag about updates?
    pub fn disable_update_nagger(&self) -> bool {
        if let Ok(var) = env::var("SENTRY_DISABLE_UPDATE_CHECK") {
            &var == "1" || &var == "true"
        } else {
            if let Some(val) = self.ini.get_from(Some("update"), "disable_check") {
                val == "true"
            } else {
                false
            }
        }
    }
}

fn find_project_config_file() -> Option<PathBuf> {
    env::current_dir().ok().and_then(|mut path| {
        loop {
            path.push(".sentryclirc");
            if path.exists() {
                return Some(path);
            }
            path.set_file_name("sentrycli.ini");
            if path.exists() {
                return Some(path);
            }
            path.pop();
            if !path.pop() {
                return None;
            }
        }
    })
}

fn load_cli_config() -> Result<(PathBuf, Ini)> {
    let mut home_fn = env::home_dir().ok_or("Could not find home dir")?;
    home_fn.push(".sentryclirc");

    let mut rv = match fs::File::open(&home_fn) {
        Ok(mut file) => Ini::read_from(&mut file)?,
        Err(err) => {
            if err.kind() == io::ErrorKind::NotFound {
                Ini::new()
            } else {
                fail!(err);
            }
        }
    };

    let (path, mut rv) = if let Some(project_config_path) = find_project_config_file() {
        let ini = Ini::read_from(&mut fs::File::open(&project_config_path)?)?;
        for (section, props) in ini.iter() {
            for (key, value) in props {
                rv.set_to(section.clone(), key.clone(), value.to_owned());
            }
        }
        (project_config_path, rv)
    } else {
        (home_fn, rv)
    };

    if let Ok(prop_path) = env::var("SENTRY_PROPERTIES") {
        match fs::File::open(prop_path) {
            Ok(f) => {
                let props = match java_properties::read(f) {
                    Ok(props) => props,
                    Err(err) => {
                        return Err(Error::from(format!(
                            "Could not load java style properties file: {}", err)));
                    }
                };
                for (key, value) in props {
                    let mut iter = key.rsplitn(2, '.');
                    if let Some(key) = iter.next() {
                        let section = iter.next();
                        rv.set_to(section, key.to_string(), value);
                    }
                }
            },
            Err(err) => {
                if err.kind() != io::ErrorKind::NotFound {
                    return Err(err.into());
                }
            }
        }
    }

    Ok((path, rv))
}

fn get_default_auth(ini: &Ini) -> Option<Auth> {
    if let Some(ref val) = env::var("SENTRY_AUTH_TOKEN").ok() {
        Some(Auth::Token(val.to_owned()))
    } else if let Some(ref val) = env::var("SENTRY_API_KEY").ok() {
        Some(Auth::Key(val.to_owned()))
    } else if let Some(val) = ini.get_from(Some("auth"), "token") {
        Some(Auth::Token(val.to_owned()))
    } else if let Some(val) = ini.get_from(Some("auth"), "api_key") {
        Some(Auth::Key(val.to_owned()))
    } else {
        None
    }
}

fn get_default_url(ini: &Ini) -> String {
    if let Some(ref val) = env::var("SENTRY_URL").ok() {
        val.to_owned()
    } else if let Some(val) = ini.get_from(Some("defaults"), "url") {
        val.to_owned()
    } else {
        DEFAULT_URL.to_owned()
    }
}

fn get_default_log_level(ini: &Ini) -> Result<log::LogLevelFilter> {
    if let Ok(level_str) = env::var("SENTRY_LOG_LEVEL") {
        if let Ok(level) = level_str.parse() {
            return Ok(level);
        }
    }

    if let Some(level_str) = ini.get_from(Some("log"), "level") {
        if let Ok(level) = level_str.parse() {
            return Ok(level);
        }
    }

    Ok(log::LogLevelFilter::Warn)
}
<commit_msg>feat: Add better error reporting for config related io errors (#145)<commit_after>//! This module implements config access.
use std::io;
use std::fs;
use std::env;
use std::path::PathBuf;

use dotenv;
use log;
use java_properties;
use clap::ArgMatches;
use url::Url;
use ini::Ini;

use prelude::*;
use constants::{DEFAULT_URL, VERSION, PROTOCOL_VERSION};

/// Represents the auth information
#[derive(Debug, Clone)]
pub enum Auth {
    Key(String),
    Token(String),
}

/// Represents a DSN
#[derive(Debug, Clone)]
pub struct Dsn {
    pub host: String,
    pub protocol: String,
    pub port: u16,
    pub client_id: String,
    pub secret: String,
    pub project_id: u64,
}

impl Dsn {
    /// Parses a Dsn from a given string.
    fn from_str(dsn: &str) -> Result<Dsn> {
        let url = Url::parse(dsn)?;
        let project_id = if let Some(component_iter) = url.path_segments() {
            let components: Vec<_> = component_iter.collect();
            if components.len() != 1 {
                fail!("invalid dsn: invalid project ID");
            }
            components[0].parse().or(Err("invalid dsn: invalid project id"))?
        } else {
            fail!("invalid dsn: missing project ID");
        };
        if !(url.scheme() == "http" || url.scheme() == "https") {
            fail!(format!("invalid dsn: unknown protocol '{}'", url.scheme()));
        }

        if url.username() == "" {
            fail!("invalid dsn: missing client id");
        }

        Ok(Dsn {
            protocol: url.scheme().into(),
            host: url.host_str().ok_or("invalid dsn: missing host")?.into(),
            port: url.port_or_known_default().unwrap(),
            client_id: url.username().into(),
            secret: url.password().ok_or("invalid dsn: missing secret")?.into(),
            project_id: project_id,
        })
    }

    /// Returns the URL where events should be sent.
    pub fn get_submit_url(&self) -> String {
        format!("{}://{}:{}/api/{}/store/",
                self.protocol,
                self.host,
                self.port,
                self.project_id)
    }

    /// Returns the given auth header (ts is the timestamp of the event)
    pub fn get_auth_header(&self, ts: f64) -> String {
        format!("Sentry \
            sentry_timestamp={}, \
            sentry_client=sentry-cli/{}, \
            sentry_version={}, \
            sentry_key={}, \
            sentry_secret={}",
                ts,
                VERSION,
                PROTOCOL_VERSION,
                self.client_id,
                self.secret)
    }
}

pub fn prepare_environment() {
    dotenv::dotenv().ok();
}

/// Represents the `sentry-cli` config.
#[derive(Clone)]
pub struct Config {
    pub filename: PathBuf,
    pub auth: Option<Auth>,
    pub url: String,
    pub log_level: log::LogLevelFilter,
    pub ini: Ini,
}

impl Config {
    /// Loads the CLI config from the default location and returns it.
    pub fn from_cli_config() -> Result<Config> {
        let (filename, ini) = load_cli_config()?;
        Ok(Config {
            filename: filename,
            auth: get_default_auth(&ini),
            url: get_default_url(&ini),
            log_level: get_default_log_level(&ini)?,
            ini: ini,
        })
    }

    /// Update the environment based on the config
    pub fn configure_environment(&self) {
        if !env::var("http_proxy").is_ok() {
            if let Some(proxy) = self.get_proxy_url() {
                env::set_var("http_proxy", proxy);
            }
        }
        #[cfg(not(windows))]
        {
            use openssl_probe::init_ssl_cert_env_vars;
            init_ssl_cert_env_vars();
        }
    }

    /// Returns the base url (without trailing slashes)
    pub fn get_base_url(&self) -> Result<&str> {
        let base = self.url.trim_right_matches('/');
        if !base.starts_with("http://") && !base.starts_with("https://") {
            fail!("bad sentry url: unknown scheme ({})", base);
        }
        if base.matches('/').count() != 2 {
            fail!("bad sentry url: not on URL root ({})", base);
        }
        Ok(base)
    }

    /// Returns the API URL for a path
    pub fn get_api_endpoint(&self, path: &str) -> Result<String> {
        let base = self.get_base_url()?;
        Ok(format!("{}/api/0/{}", base, path.trim_left_matches('/')))
    }

    /// Indicates whether keepalive support should be enabled.  This
    /// mostly corresponds to an ini config but also has some sensible
    /// default handling.
    pub fn allow_keepalive(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "keepalive");
        match val {
            // keepalive is broken on our dev server.  Since this makes local development
            // quite frustrating we disable keepalive (handle reuse) when we connect to
            // unprotected servers where it does not matter that much.
            None => !self.has_insecure_server(),
            Some(val) => val == "true",
        }
    }

    /// Returns the proxy URL if defined.
    fn get_proxy_url(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_url")
    }

    /// Returns the proxy username if defined.
    pub fn get_proxy_username(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_username")
    }

    /// Returns the proxy password if defined.
    pub fn get_proxy_password(&self) -> Option<&str> {
        self.ini.get_from(Some("http"), "proxy_password")
    }

    /// Indicates if SSL is enabled or disabled for the server.
    pub fn has_insecure_server(&self) -> bool {
        self.url.starts_with("http://")
    }

    /// Indicates whether SSL verification should be on or off.
    pub fn should_verify_ssl(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "verify_ssl");
        match val {
            None => true,
            Some(val) => val == "true",
        }
    }

    /// Controls the SSL revocation check on windows.  This can be used as a
    /// workaround for misconfigured local SSL proxies.
    pub fn disable_ssl_revocation_check(&self) -> bool {
        let val = self.ini.get_from(Some("http"), "check_ssl_revoke");
        match val {
            None => true,
            Some(val) => val == "true",
        }
    }

    /// Given a match object from clap, this returns the org from it.
    pub fn get_org(&self, matches: &ArgMatches) -> Result<String> {
        Ok(matches.value_of("org")
               .map(|x| x.to_owned())
               .or_else(|| env::var("SENTRY_ORG").ok())
               .or_else(|| self.ini.get_from(Some("defaults"), "org").map(|x| x.to_owned()))
               .ok_or("An organization slug is required (provide with --org)")?)
    }

    /// Given a match object from clap, this returns a tuple in the
    /// form `(org, project)` which can either come from the match
    /// object or some defaults (envvar, ini etc.).
    pub fn get_org_and_project(&self, matches: &ArgMatches) -> Result<(String, String)> {
        let org = self.get_org(matches)?;
        let project = if let Some(project) = matches.value_of("project") {
            project.to_owned()
        } else {
            self.get_project_default()?
        };
        Ok((org, project))
    }

    /// Return the default value for a project.
    pub fn get_project_default(&self) -> Result<String> {
        Ok(env::var("SENTRY_PROJECT").ok()
            .or_else(|| self.ini.get_from(Some("defaults"), "project").map(|x| x.to_owned()))
            .ok_or("A project slug is required")?)
    }

    /// Returns the defaults for org and project.
    pub fn get_org_and_project_defaults(&self) -> (Option<String>, Option<String>) {
        (env::var("SENTRY_ORG")
             .ok()
             .or_else(|| self.ini.get_from(Some("defaults"), "org").map(|x| x.to_owned())),
         env::var("SENTRY_PROJECT")
             .ok()
             .or_else(|| self.ini.get_from(Some("defaults"), "project").map(|x| x.to_owned())))
    }

    /// Returns true if notifications should be displayed
    pub fn show_notifications(&self) -> Result<bool> {
        Ok(self.ini.get_from(Some("ui"), "show_notifications")
            .map(|x| x == "true")
            .unwrap_or(true))
    }

    /// Returns the maximum dsym upload size
    pub fn get_max_dsym_upload_size(&self) -> Result<u64> {
        Ok(self.ini.get_from(Some("dsym"), "max_upload_size")
            .and_then(|x| x.parse().ok())
            .unwrap_or(35 * 1024 * 1024))
    }

    /// Return the DSN
    pub fn get_dsn(&self) -> Result<Dsn> {
        if let Some(ref val) = env::var("SENTRY_DSN").ok() {
            Dsn::from_str(val)
        } else if let Some(val) = self.ini.get_from(Some("auth"), "dsn") {
            Dsn::from_str(val)
        } else {
            fail!("No DSN provided");
        }
    }

    /// Should we nag about updates?
    pub fn disable_update_nagger(&self) -> bool {
        if let Ok(var) = env::var("SENTRY_DISABLE_UPDATE_CHECK") {
            &var == "1" || &var == "true"
        } else {
            if let Some(val) = self.ini.get_from(Some("update"), "disable_check") {
                val == "true"
            } else {
                false
            }
        }
    }
}

fn find_project_config_file() -> Option<PathBuf> {
    env::current_dir().ok().and_then(|mut path| {
        loop {
            path.push(".sentryclirc");
            if path.exists() {
                return Some(path);
            }
            path.set_file_name("sentrycli.ini");
            if path.exists() {
                return Some(path);
            }
            path.pop();
            if !path.pop() {
                return None;
            }
        }
    })
}

fn load_cli_config() -> Result<(PathBuf, Ini)> {
    let mut home_fn = env::home_dir().ok_or("Could not find home dir")?;
    home_fn.push(".sentryclirc");

    let mut rv = match fs::File::open(&home_fn) {
        Ok(mut file) => Ini::read_from(&mut file)?,
        Err(err) => {
            if err.kind() == io::ErrorKind::NotFound {
                Ini::new()
            } else {
                return Err(err).chain_err(
                    || "Failed to load .sentryclirc file from the home folder.");
            }
        }
    };

    let (path, mut rv) = if let Some(project_config_path) = find_project_config_file() {
        let mut f = fs::File::open(&project_config_path)
            .chain_err(|| format!("Failed to load .sentryclirc file from project path ({})",
                project_config_path.display()))?;
        let ini = Ini::read_from(&mut f)?;
        for (section, props) in ini.iter() {
            for (key, value) in props {
                rv.set_to(section.clone(), key.clone(), value.to_owned());
            }
        }
        (project_config_path, rv)
    } else {
        (home_fn, rv)
    };

    if let Ok(prop_path) = env::var("SENTRY_PROPERTIES") {
        match fs::File::open(&prop_path) {
            Ok(f) => {
                let props = match java_properties::read(f) {
                    Ok(props) => props,
                    Err(err) => {
                        return Err(Error::from(format!(
                            "Could not load java style properties file: {}", err)));
                    }
                };
                for (key, value) in props {
                    let mut iter = key.rsplitn(2, '.');
                    if let Some(key) = iter.next() {
                        let section = iter.next();
                        rv.set_to(section, key.to_string(), value);
                    }
                }
            },
            Err(err) => {
                if err.kind() != io::ErrorKind::NotFound {
                    return Err(Error::from(err)).chain_err(
                        || format!("Failed to load file referenced by SENTRY_PROPERTIES ({})",
                                   &prop_path));
                }
            }
        }
    }

    Ok((path, rv))
}

fn get_default_auth(ini: &Ini) -> Option<Auth> {
    if let Some(ref val) = env::var("SENTRY_AUTH_TOKEN").ok() {
        Some(Auth::Token(val.to_owned()))
    } else if let Some(ref val) = env::var("SENTRY_API_KEY").ok() {
        Some(Auth::Key(val.to_owned()))
    } else if let Some(val) = ini.get_from(Some("auth"), "token") {
        Some(Auth::Token(val.to_owned()))
    } else if let Some(val) = ini.get_from(Some("auth"), "api_key") {
        Some(Auth::Key(val.to_owned()))
    } else {
        None
    }
}

fn get_default_url(ini: &Ini) -> String {
    if let Some(ref val) = env::var("SENTRY_URL").ok() {
        val.to_owned()
    } else if let Some(val) = ini.get_from(Some("defaults"), "url") {
        val.to_owned()
    } else {
        DEFAULT_URL.to_owned()
    }
}

fn get_default_log_level(ini: &Ini) -> Result<log::LogLevelFilter> {
    if let Ok(level_str) = env::var("SENTRY_LOG_LEVEL") {
        if let Ok(level) = level_str.parse() {
            return Ok(level);
        }
    }

    if let Some(level_str) = ini.get_from(Some("log"), "level") {
        if let Ok(level) = level_str.parse() {
            return Ok(level);
        }
    }

    Ok(log::LogLevelFilter::Warn)
}
<|endoftext|>


<commit_before>use etcd;
use std::thread;
use std::sync::atomic::{Ordering, AtomicBool};
use std::fmt;
use std::sync::Arc;
use std::collections::BTreeMap;
use time::{SteadyTime, Duration};
use serde_json as json;
use serde::ser::Serialize;
use serde::de::Deserialize;

include!(concat!(env!("OUT_DIR"), "/config_data.rs"));

pub struct ConfigClient<T> {
    client: Arc<InnerClient<T>>,
    lease_mgr: thread::JoinHandle<()>,
    watcher: thread::JoinHandle<()>,
}

#[derive(Clone,Debug, Default)]
pub struct ConfigurationView<T> {
    pub epoch: Epoch,
    ord: usize,
    next: Option<T>,
}

struct InnerClient<T> {
    etcd: etcd::Client,
    data: T,
    lease_time: Duration,
    callback: Box<Fn(ConfigurationView<T>) + Send + Sync + 'static>,
    on_exit: Box<Fn() + Send + Sync + 'static>,
    lease_key: Option<String>,
    lease_alive: AtomicBool,
    watcher_alive: AtomicBool,
}

impl Epoch {
    fn succ(&mut self) {
        self.0 += 1
    }
}

struct DeathWatch<'a, F>(&'a AtomicBool, F) where F: Fn();

impl<'a, F> DeathWatch<'a, F>
    where F: Fn()
{
    fn new(ctr: &'a AtomicBool, f: F) -> DeathWatch<'a, F> {
        ctr.store(true, Ordering::SeqCst);
        DeathWatch(ctr, f)
    }
    fn is_alive(&self) -> bool {
        self.0.load(Ordering::Relaxed)
    }
}
impl<'a, F> Drop for DeathWatch<'a, F>
    where F: Fn()
{
    fn drop(&mut self) {
        warn!("Exiting: {:?}", self);
        let &mut DeathWatch(ref alivep, ref mut cb) = self;
        alivep.store(false, Ordering::Relaxed);
        (cb)();
    }
}

impl<'a, F> fmt::Debug for DeathWatch<'a, F>
    where F: Fn()
{
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("DeathWatch").field("alive", &self.is_alive()).finish()
    }
}

impl<T: 'static + Deserialize + Serialize + fmt::Debug + Eq + Clone + Send + Sync> ConfigClient<T> {
    pub fn new<F: Fn(ConfigurationView<T>) + Send + Sync + 'static, S: Fn() + Send + Sync + 'static>
        (addr: &str,
         data: T,
         lease_time: Duration,
         callback: F,
         on_exit: S)
         -> Result<ConfigClient<T>, ()> {
        let etcd = etcd::Client::new(addr).expect("etcd client");
        let mut client = InnerClient {
            etcd: etcd,
            data: data,
            lease_time: lease_time,
            callback: Box::new(callback),
            on_exit: Box::new(on_exit),
            lease_key: None,
            lease_alive: AtomicBool::new(true),
            watcher_alive: AtomicBool::new(true),
        };
        let lease = client.setup_lease();

        let client = Arc::new(client);
        let lease_mgr = {
            let client = client.clone();
            thread::Builder::new()
                .name("etcd config".to_string())
                .spawn(move || client.run_lease(lease))
                .expect("etcd thread")
        };
        let watcher = {
            let client = client.clone();
            thread::Builder::new()
                .name("etcd watcher".to_string())
                .spawn(move || client.run_watch())
                .expect("etcd watcher")
        };
        Ok(ConfigClient {
            client: client,
            lease_mgr: lease_mgr,
            watcher: watcher,
        })
    }

    pub fn is_running(&self) -> bool {
        self.client.is_running()
    }
}

const MEMBERS: &'static str = "/chain/members";
const SEQUENCER: &'static str = "/chain/config_seq";
const KEY_EXISTS: u64 = 105;
const KEY_NOT_EXISTS: u64 = 100;
const COMPARE_FAILED: u64 = 101;

impl<T: Deserialize + Serialize + fmt::Debug + Eq + Clone> InnerClient<T> {
    fn setup_lease(&mut self) -> u64 {
        match self.etcd.create_dir(MEMBERS, None) {
            Ok(res) => info!("Created dir: {}: {:?}: ", MEMBERS, res),
            Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_EXISTS => {
                info!("Dir exists: {:?}: ", MEMBERS)
            }
            Err(e) => panic!("Unexpected error creating {}: {:?}", MEMBERS, e),
        }

        let seq: ConfigSequencer = Default::default();
        match self.etcd.create(SEQUENCER,
                               &json::to_string(&seq).expect("encode epoch"),
                               None) {
            Ok(res) => info!("Created seq: {}: {:?}: ", SEQUENCER, res),
            Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_EXISTS => {
                info!("Sequencer exists: {:?}: ", SEQUENCER)
            }
            Err(e) => panic!("Unexpected error creating {}: {:?}", SEQUENCER, e),
        }

        let me = self.etcd
                     .create_in_order(MEMBERS,
                                      &self.data_json(),
                                      Some(self.lease_time.num_seconds() as u64))
                     .expect("Create unique node");
        info!("My node! {:?}", me);
        self.lease_key = Some(me.node.key.expect("Key name"));
        me.node.modified_index.expect("Lease node version")
    }

    fn data_json(&self) -> String {
        json::to_string(&self.data).expect("json encode")
    }

    fn run_lease(&self, mut lease_index: u64) {
        let watch = DeathWatch::new(&self.lease_alive, || (*self.on_exit)());
        let update_interval = self.lease_time / 2;
        let lease_key = self.lease_key.as_ref().expect("Should have created lease node");

        loop {
            trace!("touch node: {:?}={:?}", lease_key, self.data);
            let start_time = SteadyTime::now();
            let next_wakeup = start_time + update_interval;
            let res = match self.etcd
                                .compare_and_swap(&lease_key,
                                                  &self.data_json(),
                                                  Some(self.lease_time.num_seconds() as u64),
                                                  None,
                                                  Some(lease_index)) {
                Ok(res) => {
                    trace!("refreshed lease:: {}: {:?}: ",
                           lease_key,
                           res.node.modified_index);
                    res
                }
                Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_NOT_EXISTS => {
                    panic!("Could not update lease: expired");
                }
                Err(e) => panic!("Unexpected error updating lease {}: {:?}", lease_key, e),
            };
            lease_index = res.node.modified_index.expect("lease version");
            let end_time = SteadyTime::now();
            trace!("Updated in {}: {:?}", end_time - start_time, res);
            let pausetime = next_wakeup - end_time;
            trace!("Pause for {}", pausetime);
            if pausetime > Duration::zero() {
                thread::sleep_ms(pausetime.num_milliseconds() as u32);
            }
        }
    }

    fn list_members(&self) -> BTreeMap<String, T> {
        let current_listing = self.etcd.get(MEMBERS, true, true, true).expect("List members");
        trace!("Listing: {:?}", current_listing);
        current_listing.node
                       .nodes
                       .unwrap_or_else(|| Vec::new())
                       .into_iter()
                       .filter_map(|n| {
                           if let (Some(k), Some(v)) = (n.key, n.value) {
                               Some((k, json::from_str(&v).expect("decode json")))
                           } else {
                               None
                           }
                       })
                       .collect::<BTreeMap<String, T>>()
    }

    fn run_watch(&self) {
        let watch = DeathWatch::new(&self.watcher_alive, || (*self.on_exit)());
        info!("Starting etcd watcher");
        let lease_key = self.lease_key.as_ref().expect("Should have created lease node");

        let current_listing = self.etcd.get(MEMBERS, true, true, true).expect("List members");
        debug!("Listing: {:?}", current_listing);
        let mut last_observed_index = current_listing.node
                                                     .nodes
                                                     .unwrap_or_else(|| Vec::new())
                                                     .into_iter()
                                                     .filter_map(|x| x.modified_index)
                                                     .max();

        let mut curr_members = BTreeMap::new();
        loop {
            trace!("Awaiting for {} from {:?}", MEMBERS, last_observed_index);
            let res = self.etcd.watch(MEMBERS, last_observed_index, true).expect("watch");
            trace!("Watch: {:?}", res);
            last_observed_index = res.node.modified_index.map(|x| x + 1);

            let members = self.list_members();
            let seq = self.verify_epoch(&members);

            trace!("Members: {:?}; seq: {:?}", members, seq);
            if curr_members != members {
                curr_members = members;
                info!("Membership change! {:?}", curr_members);
                if let Some(view) = ConfigurationView::of_membership(seq.epoch,
                                                                     lease_key,
                                                                     curr_members.clone()) {
                    (self.callback)(view)
                } else {
                    warn!("I seem to have died: {:?}", lease_key);
                    (self.on_exit)();
                    break;
                }
            }
        }

    }

    fn verify_epoch(&self, members: &BTreeMap<String, T>) -> ConfigSequencer {
        loop {
            let (seq_index, mut seq): (u64, ConfigSequencer) = match self.etcd.get(SEQUENCER,
                                                                                false,
                                                                                false,
                                                                                false) {
                Ok(etcd::KeySpaceInfo {
                    node: etcd::keys::Node {
                        modified_index: Some(modified_index),
                        value: Some(value),
                        ..
                    } ,
                    ..
                }) => {
                    (modified_index,
                     json::from_str(&value).expect("decode epoch"))
                }
                Ok(e) => panic!("Unexpected response reading {}: {:?}", SEQUENCER, e),
                Err(e) => panic!("Unexpected error reading {}: {:?}", SEQUENCER, e),
            };
            trace!("Sequencer: {}/{:?}", seq_index, seq);
            let current_keys = members.keys().cloned().collect::<Vec<_>>();
            if current_keys != seq.keys {
                debug!("Stale! {:?}", seq);

                seq.keys = current_keys;
                seq.epoch.succ();

                match self.etcd.compare_and_swap(SEQUENCER,
                                                 &json::to_string(&seq).expect("encode epoch"),
                                                 None,
                                                 None,
                                                 Some(seq_index)) {
                    Ok(res) => {
                        debug!("verify_epoch: Updated seq: {}: {:?}: ", SEQUENCER, seq);
                        return seq;
                    }
                    Err(etcd::Error::Etcd(ref e)) if e.error_code == COMPARE_FAILED => {
                        debug!("verify_epoch: Raced out; retry")
                    }
                    Err(e) => panic!("Unexpected error creating {}: {:?}", SEQUENCER, e),
                }
            } else {
                debug!("verify_epoch: Fresh! {:?}", seq);
                return seq;
            }
        }
    }

    fn is_running(&self) -> bool {
        self.lease_alive.load(Ordering::Relaxed) && self.watcher_alive.load(Ordering::Relaxed)
    }
}

impl<T: Clone> ConfigurationView<T> {
    fn of_membership(epoch: Epoch,
                     this_node: &str,
                     members: BTreeMap<String, T>)
                     -> Option<ConfigurationView<T>> {
        let ordp = members.keys().position(|x| *x == this_node);
        ordp.map(|ord| {
            let next = members.values().nth(ord + 1).map(|v| v.clone());
            ConfigurationView {
                epoch: epoch,
                ord: ord,
                next: next,
            }
        })
    }

    pub fn is_head(&self) -> bool {
        self.ord == 0
    }

    pub fn should_listen_for_clients(&self) -> bool {
        self.is_head()
    }
    pub fn should_listen_for_upstream(&self) -> bool {
        !self.is_head()
    }

    pub fn should_connect_downstream<'a>(&'a self) -> Option<&'a T> {
        self.next.as_ref()
    }
}
<commit_msg>Make the behavior of the epoch/config sequencer slightly more explicit.<commit_after>use etcd;
use std::thread;
use std::sync::atomic::{Ordering, AtomicBool};
use std::fmt;
use std::sync::Arc;
use std::collections::BTreeMap;
use time::{SteadyTime, Duration};
use serde_json as json;
use serde::ser::Serialize;
use serde::de::Deserialize;

include!(concat!(env!("OUT_DIR"), "/config_data.rs"));

impl ConfigSequencer {
    fn update_from_keys(&mut self, current_keys: Vec<String>) {
        self.keys = current_keys;
        self.epoch = self.epoch.succ();
    }
}

pub struct ConfigClient<T> {
    client: Arc<InnerClient<T>>,
    lease_mgr: thread::JoinHandle<()>,
    watcher: thread::JoinHandle<()>,
}

#[derive(Clone,Debug, Default)]
pub struct ConfigurationView<T> {
    pub epoch: Epoch,
    ord: usize,
    next: Option<T>,
}

struct InnerClient<T> {
    etcd: etcd::Client,
    data: T,
    lease_time: Duration,
    callback: Box<Fn(ConfigurationView<T>) + Send + Sync + 'static>,
    on_exit: Box<Fn() + Send + Sync + 'static>,
    lease_key: Option<String>,
    lease_alive: AtomicBool,
    watcher_alive: AtomicBool,
}

impl Epoch {
    fn succ(&self) -> Epoch {
        Epoch(self.0 + 1)
    }
}

struct DeathWatch<'a, F>(&'a AtomicBool, F) where F: Fn();

impl<'a, F> DeathWatch<'a, F>
    where F: Fn()
{
    fn new(ctr: &'a AtomicBool, f: F) -> DeathWatch<'a, F> {
        ctr.store(true, Ordering::SeqCst);
        DeathWatch(ctr, f)
    }
    fn is_alive(&self) -> bool {
        self.0.load(Ordering::Relaxed)
    }
}
impl<'a, F> Drop for DeathWatch<'a, F>
    where F: Fn()
{
    fn drop(&mut self) {
        warn!("Exiting: {:?}", self);
        let &mut DeathWatch(ref alivep, ref mut cb) = self;
        alivep.store(false, Ordering::Relaxed);
        (cb)();
    }
}

impl<'a, F> fmt::Debug for DeathWatch<'a, F>
    where F: Fn()
{
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("DeathWatch").field("alive", &self.is_alive()).finish()
    }
}

impl<T: 'static + Deserialize + Serialize + fmt::Debug + Eq + Clone + Send + Sync> ConfigClient<T> {
    pub fn new<F: Fn(ConfigurationView<T>) + Send + Sync + 'static, S: Fn() + Send + Sync + 'static>
        (addr: &str,
         data: T,
         lease_time: Duration,
         callback: F,
         on_exit: S)
         -> Result<ConfigClient<T>, ()> {
        let etcd = etcd::Client::new(addr).expect("etcd client");
        let mut client = InnerClient {
            etcd: etcd,
            data: data,
            lease_time: lease_time,
            callback: Box::new(callback),
            on_exit: Box::new(on_exit),
            lease_key: None,
            lease_alive: AtomicBool::new(true),
            watcher_alive: AtomicBool::new(true),
        };
        let lease = client.setup_lease();

        let client = Arc::new(client);
        let lease_mgr = {
            let client = client.clone();
            thread::Builder::new()
                .name("etcd config".to_string())
                .spawn(move || client.run_lease(lease))
                .expect("etcd thread")
        };
        let watcher = {
            let client = client.clone();
            thread::Builder::new()
                .name("etcd watcher".to_string())
                .spawn(move || client.run_watch())
                .expect("etcd watcher")
        };
        Ok(ConfigClient {
            client: client,
            lease_mgr: lease_mgr,
            watcher: watcher,
        })
    }

    pub fn is_running(&self) -> bool {
        self.client.is_running()
    }
}

const MEMBERS: &'static str = "/chain/members";
const SEQUENCER: &'static str = "/chain/config_seq";
const KEY_EXISTS: u64 = 105;
const KEY_NOT_EXISTS: u64 = 100;
const COMPARE_FAILED: u64 = 101;

impl<T: Deserialize + Serialize + fmt::Debug + Eq + Clone> InnerClient<T> {
    fn setup_lease(&mut self) -> u64 {
        match self.etcd.create_dir(MEMBERS, None) {
            Ok(res) => info!("Created dir: {}: {:?}: ", MEMBERS, res),
            Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_EXISTS => {
                info!("Dir exists: {:?}: ", MEMBERS)
            }
            Err(e) => panic!("Unexpected error creating {}: {:?}", MEMBERS, e),
        }

        let seq: ConfigSequencer = Default::default();
        match self.etcd.create(SEQUENCER,
                               &json::to_string(&seq).expect("encode epoch"),
                               None) {
            Ok(res) => info!("Created seq: {}: {:?}: ", SEQUENCER, res),
            Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_EXISTS => {
                info!("Sequencer exists: {:?}: ", SEQUENCER)
            }
            Err(e) => panic!("Unexpected error creating {}: {:?}", SEQUENCER, e),
        }

        let me = self.etcd
                     .create_in_order(MEMBERS,
                                      &self.data_json(),
                                      Some(self.lease_time.num_seconds() as u64))
                     .expect("Create unique node");
        info!("My node! {:?}", me);
        self.lease_key = Some(me.node.key.expect("Key name"));
        me.node.modified_index.expect("Lease node version")
    }

    fn data_json(&self) -> String {
        json::to_string(&self.data).expect("json encode")
    }

    fn run_lease(&self, mut lease_index: u64) {
        let watch = DeathWatch::new(&self.lease_alive, || (*self.on_exit)());
        let update_interval = self.lease_time / 2;
        let lease_key = self.lease_key.as_ref().expect("Should have created lease node");

        loop {
            trace!("touch node: {:?}={:?}", lease_key, self.data);
            let start_time = SteadyTime::now();
            let next_wakeup = start_time + update_interval;
            let res = match self.etcd
                                .compare_and_swap(&lease_key,
                                                  &self.data_json(),
                                                  Some(self.lease_time.num_seconds() as u64),
                                                  None,
                                                  Some(lease_index)) {
                Ok(res) => {
                    trace!("refreshed lease:: {}: {:?}: ",
                           lease_key,
                           res.node.modified_index);
                    res
                }
                Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_NOT_EXISTS => {
                    panic!("Could not update lease: expired");
                }
                Err(e) => panic!("Unexpected error updating lease {}: {:?}", lease_key, e),
            };
            lease_index = res.node.modified_index.expect("lease version");
            let end_time = SteadyTime::now();
            trace!("Updated in {}: {:?}", end_time - start_time, res);
            let pausetime = next_wakeup - end_time;
            trace!("Pause for {}", pausetime);
            if pausetime > Duration::zero() {
                thread::sleep_ms(pausetime.num_milliseconds() as u32);
            }
        }
    }

    fn list_members(&self) -> BTreeMap<String, T> {
        let current_listing = self.etcd.get(MEMBERS, true, true, true).expect("List members");
        trace!("Listing: {:?}", current_listing);
        current_listing.node
                       .nodes
                       .unwrap_or_else(|| Vec::new())
                       .into_iter()
                       .filter_map(|n| {
                           if let (Some(k), Some(v)) = (n.key, n.value) {
                               Some((k, json::from_str(&v).expect("decode json")))
                           } else {
                               None
                           }
                       })
                       .collect::<BTreeMap<String, T>>()
    }

    fn run_watch(&self) {
        let watch = DeathWatch::new(&self.watcher_alive, || (*self.on_exit)());
        info!("Starting etcd watcher");
        let lease_key = self.lease_key.as_ref().expect("Should have created lease node");

        let current_listing = self.etcd.get(MEMBERS, true, true, true).expect("List members");
        debug!("Listing: {:?}", current_listing);
        let mut last_observed_index = current_listing.node
                                                     .nodes
                                                     .unwrap_or_else(|| Vec::new())
                                                     .into_iter()
                                                     .filter_map(|x| x.modified_index)
                                                     .max();

        let mut curr_members = BTreeMap::new();
        loop {
            trace!("Awaiting for {} from etcd index {:?}", MEMBERS, last_observed_index);
            let res = self.etcd.watch(MEMBERS, last_observed_index, true).expect("watch");
            trace!("Watch: {:?}", res);
            last_observed_index = res.node.modified_index.map(|x| x + 1);

            let members = self.list_members();
            let seq = self.verify_epoch(&members);

            trace!("Members: {:?}; seq: {:?}", members, seq);
            if curr_members != members {
                curr_members = members;
                info!("Membership change! {:?}", curr_members);
                if let Some(view) = ConfigurationView::of_membership(seq.epoch,
                                                                     lease_key,
                                                                     curr_members.clone()) {
                    (self.callback)(view)
                } else {
                    warn!("I seem to have died: {:?}", lease_key);
                    (self.on_exit)();
                    break;
                }
            }
        }

    }

    fn verify_epoch(&self, members: &BTreeMap<String, T>) -> ConfigSequencer {
        loop {
            let (seq_index, mut seq): (u64, ConfigSequencer) = match self.etcd.get(SEQUENCER,
                                                                                false,
                                                                                false,
                                                                                false) {
                Ok(etcd::KeySpaceInfo {
                    node: etcd::keys::Node {
                        modified_index: Some(modified_index),
                        value: Some(value),
                        ..
                    } ,
                    ..
                }) => {
                    (modified_index,
                     json::from_str(&value).expect("decode epoch"))
                }
                Ok(e) => panic!("Unexpected response reading {}: {:?}", SEQUENCER, e),
                Err(e) => panic!("Unexpected error reading {}: {:?}", SEQUENCER, e),
            };
            trace!("Sequencer: {}/{:?}", seq_index, seq);
            let current_keys = members.keys().cloned().collect::<Vec<_>>();
            if current_keys != seq.keys {
                debug!("verify_epoch: Stale! {:?}", seq);

                seq.update_from_keys(current_keys);

                match self.etcd.compare_and_swap(SEQUENCER,
                                                 &json::to_string(&seq).expect("encode epoch"),
                                                 None,
                                                 None,
                                                 Some(seq_index)) {
                    Ok(res) => {
                        debug!("verify_epoch: Updated seq: {}: {:?}: ", SEQUENCER, seq);
                        return seq;
                    }
                    Err(etcd::Error::Etcd(ref e)) if e.error_code == COMPARE_FAILED => {
                        debug!("verify_epoch: Raced out; retry")
                    }
                    Err(e) => panic!("Unexpected error creating {}: {:?}", SEQUENCER, e),
                }
            } else {
                debug!("verify_epoch: Fresh! {:?}", seq);
                return seq;
            }
        }
    }

    fn is_running(&self) -> bool {
        self.lease_alive.load(Ordering::Relaxed) && self.watcher_alive.load(Ordering::Relaxed)
    }
}

impl<T: Clone> ConfigurationView<T> {
    fn of_membership(epoch: Epoch,
                     this_node: &str,
                     members: BTreeMap<String, T>)
                     -> Option<ConfigurationView<T>> {
        let ordp = members.keys().position(|x| *x == this_node);
        ordp.map(|ord| {
            let next = members.values().nth(ord + 1).map(|v| v.clone());
            ConfigurationView {
                epoch: epoch,
                ord: ord,
                next: next,
            }
        })
    }

    pub fn is_head(&self) -> bool {
        self.ord == 0
    }

    pub fn should_listen_for_clients(&self) -> bool {
        self.is_head()
    }
    pub fn should_listen_for_upstream(&self) -> bool {
        !self.is_head()
    }

    pub fn should_connect_downstream<'a>(&'a self) -> Option<&'a T> {
        self.next.as_ref()
    }
}
<|endoftext|>


<commit_before>use std::fmt::Debug;

use crate::builder::{ConfigBuilder, DefaultState};
use serde::de::Deserialize;
use serde::ser::Serialize;

use crate::error::{ConfigError, Result};
use crate::map::Map;
use crate::path;
use crate::ser::ConfigSerializer;
use crate::source::Source;
use crate::value::{Table, Value};

/// A prioritized configuration repository. It maintains a set of
/// configuration sources, fetches values to populate those, and provides
/// them according to the source's priority.
#[derive(Clone, Debug)]
pub struct Config {
    defaults: Map<path::Expression, Value>,
    overrides: Map<path::Expression, Value>,
    sources: Vec<Box<dyn Source + Send + Sync>>,

    /// Root of the cached configuration.
    pub cache: Value,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            defaults: Default::default(),
            overrides: Default::default(),
            sources: Default::default(),
            cache: Value::new(None, Table::new()),
        }
    }
}

impl Config {
    pub(crate) fn new(value: Value) -> Self {
        Self {
            cache: value,
            ..Self::default()
        }
    }

    /// Creates new [`ConfigBuilder`] instance
    pub fn builder() -> ConfigBuilder<DefaultState> {
        ConfigBuilder::<DefaultState>::default()
    }

    /// Merge in a configuration property source.
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn merge<T>(&mut self, source: T) -> Result<&mut Self>
    where
        T: 'static,
        T: Source + Send + Sync,
    {
        self.sources.push(Box::new(source));

        #[allow(deprecated)]
        self.refresh()
    }

    /// Merge in a configuration property source.
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn with_merged<T>(mut self, source: T) -> Result<Self>
    where
        T: 'static,
        T: Source + Send + Sync,
    {
        self.sources.push(Box::new(source));

        #[allow(deprecated)]
        self.refresh()?;
        Ok(self)
    }

    /// Refresh the configuration cache with fresh
    /// data from added sources.
    ///
    /// Configuration is automatically refreshed after a mutation
    /// operation (`set`, `merge`, `set_default`, etc.).
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn refresh(&mut self) -> Result<&mut Self> {
        self.cache = {
            let mut cache: Value = Map::<String, Value>::new().into();

            // Add defaults
            for (key, val) in &self.defaults {
                key.set(&mut cache, val.clone());
            }

            // Add sources
            self.sources.collect_to(&mut cache)?;

            // Add overrides
            for (key, val) in &self.overrides {
                key.set(&mut cache, val.clone());
            }

            cache
        };

        Ok(self)
    }

    /// Set a default `value` at `key`
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<&mut Self>
    where
        T: Into<Value>,
    {
        self.defaults.insert(key.parse()?, value.into());

        #[allow(deprecated)]
        self.refresh()
    }

    /// Set an overwrite
    ///
    /// This function sets an overwrite value.
    /// The overwrite `value` is written to the `key` location on every `refresh()`
    ///
    /// # Warning
    ///
    /// Errors if config is frozen
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn set<T>(&mut self, key: &str, value: T) -> Result<&mut Self>
    where
        T: Into<Value>,
    {
        self.overrides.insert(key.parse()?, value.into());

        #[allow(deprecated)]
        self.refresh()
    }

    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn set_once(&mut self, key: &str, value: Value) -> Result<()> {
        let expr: path::Expression = key.parse()?;

        // Traverse the cache using the path to (possibly) retrieve a value
        if let Some(ref mut val) = expr.get_mut(&mut self.cache) {
            **val = value;
        } else {
            expr.set(&mut self.cache, value);
        }
        Ok(())
    }

    pub fn get<'de, T: Deserialize<'de>>(&self, key: &str) -> Result<T> {
        // Parse the key into a path expression
        let expr: path::Expression = key.parse()?;

        // Traverse the cache using the path to (possibly) retrieve a value
        let value = expr.get(&self.cache).cloned();

        match value {
            Some(value) => {
                // Deserialize the received value into the requested type
                T::deserialize(value).map_err(|e| e.extend_with_key(key))
            }

            None => Err(ConfigError::NotFound(key.into())),
        }
    }

    pub fn get_string(&self, key: &str) -> Result<String> {
        self.get(key).and_then(Value::into_string)
    }

    pub fn get_int(&self, key: &str) -> Result<i64> {
        self.get(key).and_then(Value::into_int)
    }

    pub fn get_float(&self, key: &str) -> Result<f64> {
        self.get(key).and_then(Value::into_float)
    }

    pub fn get_bool(&self, key: &str) -> Result<bool> {
        self.get(key).and_then(Value::into_bool)
    }

    pub fn get_table(&self, key: &str) -> Result<Map<String, Value>> {
        self.get(key).and_then(Value::into_table)
    }

    pub fn get_array(&self, key: &str) -> Result<Vec<Value>> {
        self.get(key).and_then(Value::into_array)
    }

    /// Attempt to deserialize the entire configuration into the requested type.
    pub fn try_deserialize<'de, T: Deserialize<'de>>(self) -> Result<T> {
        T::deserialize(self)
    }

    /// Attempt to serialize the entire configuration from the given type.
    pub fn try_from<T: Serialize>(from: &T) -> Result<Self> {
        let mut serializer = ConfigSerializer::default();
        from.serialize(&mut serializer)?;
        Ok(serializer.output)
    }

    #[deprecated(since = "0.7.0", note = "please use 'try_deserialize' instead")]
    pub fn deserialize<'de, T: Deserialize<'de>>(self) -> Result<T> {
        self.try_deserialize()
    }
}

impl Source for Config {
    fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
        Box::new((*self).clone())
    }

    fn collect(&self) -> Result<Map<String, Value>> {
        self.cache.clone().into_table()
    }
}
<commit_msg>change config methods to use lowercase keys<commit_after>use std::fmt::Debug;

use crate::builder::{ConfigBuilder, DefaultState};
use serde::de::Deserialize;
use serde::ser::Serialize;

use crate::error::{ConfigError, Result};
use crate::map::Map;
use crate::path;
use crate::ser::ConfigSerializer;
use crate::source::Source;
use crate::value::{Table, Value};

/// A prioritized configuration repository. It maintains a set of
/// configuration sources, fetches values to populate those, and provides
/// them according to the source's priority.
#[derive(Clone, Debug)]
pub struct Config {
    defaults: Map<path::Expression, Value>,
    overrides: Map<path::Expression, Value>,
    sources: Vec<Box<dyn Source + Send + Sync>>,

    /// Root of the cached configuration.
    pub cache: Value,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            defaults: Default::default(),
            overrides: Default::default(),
            sources: Default::default(),
            cache: Value::new(None, Table::new()),
        }
    }
}

impl Config {
    pub(crate) fn new(value: Value) -> Self {
        Self {
            cache: value,
            ..Self::default()
        }
    }

    /// Creates new [`ConfigBuilder`] instance
    pub fn builder() -> ConfigBuilder<DefaultState> {
        ConfigBuilder::<DefaultState>::default()
    }

    /// Merge in a configuration property source.
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn merge<T>(&mut self, source: T) -> Result<&mut Self>
    where
        T: 'static,
        T: Source + Send + Sync,
    {
        self.sources.push(Box::new(source));

        #[allow(deprecated)]
        self.refresh()
    }

    /// Merge in a configuration property source.
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn with_merged<T>(mut self, source: T) -> Result<Self>
    where
        T: 'static,
        T: Source + Send + Sync,
    {
        self.sources.push(Box::new(source));

        #[allow(deprecated)]
        self.refresh()?;
        Ok(self)
    }

    /// Refresh the configuration cache with fresh
    /// data from added sources.
    ///
    /// Configuration is automatically refreshed after a mutation
    /// operation (`set`, `merge`, `set_default`, etc.).
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn refresh(&mut self) -> Result<&mut Self> {
        self.cache = {
            let mut cache: Value = Map::<String, Value>::new().into();

            // Add defaults
            for (key, val) in &self.defaults {
                key.set(&mut cache, val.clone());
            }

            // Add sources
            self.sources.collect_to(&mut cache)?;

            // Add overrides
            for (key, val) in &self.overrides {
                key.set(&mut cache, val.clone());
            }

            cache
        };

        Ok(self)
    }

    /// Set a default `value` at `key`
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<&mut Self>
    where
        T: Into<Value>,
    {
        self.defaults
            .insert(key.to_lowercase().as_str().parse()?, value.into());

        #[allow(deprecated)]
        self.refresh()
    }

    /// Set an overwrite
    ///
    /// This function sets an overwrite value.
    /// The overwrite `value` is written to the `key` location on every `refresh()`
    ///
    /// # Warning
    ///
    /// Errors if config is frozen
    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn set<T>(&mut self, key: &str, value: T) -> Result<&mut Self>
    where
        T: Into<Value>,
    {
        self.overrides
            .insert(key.to_lowercase().as_str().parse()?, value.into());

        #[allow(deprecated)]
        self.refresh()
    }

    #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
    pub fn set_once(&mut self, key: &str, value: Value) -> Result<()> {
        let expr: path::Expression = key.to_lowercase().as_str().parse()?;

        // Traverse the cache using the path to (possibly) retrieve a value
        if let Some(ref mut val) = expr.get_mut(&mut self.cache) {
            **val = value;
        } else {
            expr.set(&mut self.cache, value);
        }
        Ok(())
    }

    pub fn get<'de, T: Deserialize<'de>>(&self, key: &str) -> Result<T> {
        let k = key.to_lowercase();
        let key = k.as_str();
        // Parse the key into a path expression
        let expr: path::Expression = key.parse()?;

        // Traverse the cache using the path to (possibly) retrieve a value
        let value = expr.get(&self.cache).cloned();

        match value {
            Some(value) => {
                // Deserialize the received value into the requested type
                T::deserialize(value).map_err(|e| e.extend_with_key(key))
            }

            None => Err(ConfigError::NotFound(key.into())),
        }
    }

    pub fn get_string(&self, key: &str) -> Result<String> {
        self.get(key).and_then(Value::into_string)
    }

    pub fn get_int(&self, key: &str) -> Result<i64> {
        self.get(key).and_then(Value::into_int)
    }

    pub fn get_float(&self, key: &str) -> Result<f64> {
        self.get(key).and_then(Value::into_float)
    }

    pub fn get_bool(&self, key: &str) -> Result<bool> {
        self.get(key).and_then(Value::into_bool)
    }

    pub fn get_table(&self, key: &str) -> Result<Map<String, Value>> {
        self.get(key).and_then(Value::into_table)
    }

    pub fn get_array(&self, key: &str) -> Result<Vec<Value>> {
        self.get(key).and_then(Value::into_array)
    }

    /// Attempt to deserialize the entire configuration into the requested type.
    pub fn try_deserialize<'de, T: Deserialize<'de>>(self) -> Result<T> {
        T::deserialize(self)
    }

    /// Attempt to serialize the entire configuration from the given type.
    pub fn try_from<T: Serialize>(from: &T) -> Result<Self> {
        let mut serializer = ConfigSerializer::default();
        from.serialize(&mut serializer)?;
        Ok(serializer.output)
    }

    #[deprecated(since = "0.7.0", note = "please use 'try_deserialize' instead")]
    pub fn deserialize<'de, T: Deserialize<'de>>(self) -> Result<T> {
        self.try_deserialize()
    }
}

impl Source for Config {
    fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
        Box::new((*self).clone())
    }

    fn collect(&self) -> Result<Map<String, Value>> {
        self.cache.clone().into_table()
    }
}
<|endoftext|>


<commit_before>// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::result;

use kvm;
use sys_util;

const MAX_KVM_CPUID_ENTRIES: usize = 256;
const VENDOR_EBX_VAL: u32 = 0x534f5243;
const VENDOR_ECX_VAL: u32 = 0x4d56534f;
const VENDOR_EDX_VAL: u32 = 0x52434d56;

#[derive(Debug, PartialEq)]
pub enum Error {
    GetSupportedCpusFailed(sys_util::Error),
    SetSupportedCpusFailed(sys_util::Error),
}
pub type Result<T> = result::Result<T, Error>;

// CPUID bits in ebx, ecx, and edx.
const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size.
const EBX_CLFLUSH_SIZE_SHIFT: u32 = 8; // Bytes flushed when executing CLFLUSH.
const EBX_CPU_COUNT_SHIFT: u32 = 16; // Index of this CPU.
const EBX_CPUID_SHIFT: u32 = 24; // Index of this CPU.
const ECX_EPB_SHIFT: u32 = 3; // "Energy Performance Bias" bit.
const ECX_HYPERVISOR_SHIFT: u32 = 31; // Flag to be set when the cpu is running on a hypervisor.
const EDX_HTT_SHIFT: u32 = 28; // Hyper Threading Enabled.

fn filter_cpuid(cpu_id: u64, cpu_count: u64, kvm_cpuid: &mut kvm::CpuId) -> Result<()> {
    let entries = kvm_cpuid.mut_entries_slice();

    for entry in entries.iter_mut() {
        match entry.function {
            0 => {
                // Vendor name "CROSVMCROSVM" in little endian.
                entry.ebx = VENDOR_EBX_VAL;
                entry.ecx = VENDOR_ECX_VAL;
                entry.edx = VENDOR_EDX_VAL;
            }
            1 => {
                // X86 hypervisor feature
                if entry.index == 0 {
                    entry.ecx |= 1 << ECX_HYPERVISOR_SHIFT;
                }
                entry.ebx = (cpu_id << EBX_CPUID_SHIFT) as u32 |
                    (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT);
                if cpu_count > 1 {
                    entry.ebx |= (cpu_count as u32) << EBX_CPU_COUNT_SHIFT;
                    entry.edx |= 1 << EDX_HTT_SHIFT;
                }
            }
            6 => {
                // Clear X86 EPB feature.  No frequency selection in the hypervisor.
                entry.ecx &= !(1 << ECX_EPB_SHIFT);
            }
            _ => (),
        }
    }

    Ok(())
}

/// Sets up the cpuid entries for the given vcpu.  Can fail if there are too many CPUs specified or
/// if an ioctl returns an error.
///
/// # Arguments
///
/// * `kvm` - `Kvm` structure created with KVM_CREATE_VM ioctl.
/// * `vcpu` - `Vcpu` for setting CPU ID.
/// * `cpu_id` - The index of the CPU `vcpu` is for.
/// * `nrcpus` - The number of vcpus being used by this VM.
pub fn setup_cpuid(kvm: &kvm::Kvm, vcpu: &kvm::Vcpu, cpu_id: u64, nrcpus: u64) -> Result<()> {
    let mut kvm_cpuid = kvm.get_supported_cpuid(MAX_KVM_CPUID_ENTRIES).map_err(
        Error::GetSupportedCpusFailed,
    )?;

    filter_cpuid(cpu_id, nrcpus, &mut kvm_cpuid)?;

    vcpu.set_cpuid2(&kvm_cpuid).map_err(
        Error::SetSupportedCpusFailed,
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn feature_and_vendor_name() {
        let mut cpuid = kvm::CpuId::new(2);

        {
            let entries = cpuid.mut_entries_slice();
            entries[0].function = 0;
            entries[1].function = 1;
            entries[1].ecx = 0x10;
            entries[1].edx = 0;
        }
        assert_eq!(Ok(()), filter_cpuid(1, 2, &mut cpuid));
        {
            let entries = cpuid.mut_entries_slice();
            assert_eq!(entries[0].function, 0);
            assert_eq!(entries[0].ebx, VENDOR_EBX_VAL);
            assert_eq!(entries[0].ecx, VENDOR_ECX_VAL);
            assert_eq!(entries[0].edx, VENDOR_EDX_VAL);
            assert_eq!(1, (entries[1].ebx >> EBX_CPUID_SHIFT) & 0x000000ff);
            assert_eq!(2, (entries[1].ebx >> EBX_CPU_COUNT_SHIFT) & 0x000000ff);
            assert_eq!(
                EBX_CLFLUSH_CACHELINE,
                (entries[1].ebx >> EBX_CLFLUSH_SIZE_SHIFT) & 0x000000ff
            );
            assert_ne!(0, entries[1].ecx & (1 << ECX_HYPERVISOR_SHIFT));
            assert_ne!(0, entries[1].edx & (1 << EDX_HTT_SHIFT));
        }
    }
}
<commit_msg>change the vcpu vendor name<commit_after>// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::result;

use kvm;
use sys_util;

const MAX_KVM_CPUID_ENTRIES: usize = 256;
const VENDOR_EBX_VAL: u32 = 0x45524946;
const VENDOR_ECX_VAL: u32 = 0x0052454B;
const VENDOR_EDX_VAL: u32 = 0x43415243;

#[derive(Debug, PartialEq)]
pub enum Error {
    GetSupportedCpusFailed(sys_util::Error),
    SetSupportedCpusFailed(sys_util::Error),
}
pub type Result<T> = result::Result<T, Error>;

// CPUID bits in ebx, ecx, and edx.
const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size.
const EBX_CLFLUSH_SIZE_SHIFT: u32 = 8; // Bytes flushed when executing CLFLUSH.
const EBX_CPU_COUNT_SHIFT: u32 = 16; // Index of this CPU.
const EBX_CPUID_SHIFT: u32 = 24; // Index of this CPU.
const ECX_EPB_SHIFT: u32 = 3; // "Energy Performance Bias" bit.
const ECX_HYPERVISOR_SHIFT: u32 = 31; // Flag to be set when the cpu is running on a hypervisor.
const EDX_HTT_SHIFT: u32 = 28; // Hyper Threading Enabled.

fn filter_cpuid(cpu_id: u64, cpu_count: u64, kvm_cpuid: &mut kvm::CpuId) -> Result<()> {
    let entries = kvm_cpuid.mut_entries_slice();

    for entry in entries.iter_mut() {
        match entry.function {
            0 => {
                // Vendor name "FIRECRACKER" in little endian.
                entry.ebx = VENDOR_EBX_VAL;
                entry.ecx = VENDOR_ECX_VAL;
                entry.edx = VENDOR_EDX_VAL;
            }
            1 => {
                // X86 hypervisor feature
                if entry.index == 0 {
                    entry.ecx |= 1 << ECX_HYPERVISOR_SHIFT;
                }
                entry.ebx = (cpu_id << EBX_CPUID_SHIFT) as u32 |
                    (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT);
                if cpu_count > 1 {
                    entry.ebx |= (cpu_count as u32) << EBX_CPU_COUNT_SHIFT;
                    entry.edx |= 1 << EDX_HTT_SHIFT;
                }
            }
            6 => {
                // Clear X86 EPB feature.  No frequency selection in the hypervisor.
                entry.ecx &= !(1 << ECX_EPB_SHIFT);
            }
            _ => (),
        }
    }

    Ok(())
}

/// Sets up the cpuid entries for the given vcpu.  Can fail if there are too many CPUs specified or
/// if an ioctl returns an error.
///
/// # Arguments
///
/// * `kvm` - `Kvm` structure created with KVM_CREATE_VM ioctl.
/// * `vcpu` - `Vcpu` for setting CPU ID.
/// * `cpu_id` - The index of the CPU `vcpu` is for.
/// * `nrcpus` - The number of vcpus being used by this VM.
pub fn setup_cpuid(kvm: &kvm::Kvm, vcpu: &kvm::Vcpu, cpu_id: u64, nrcpus: u64) -> Result<()> {
    let mut kvm_cpuid = kvm.get_supported_cpuid(MAX_KVM_CPUID_ENTRIES).map_err(
        Error::GetSupportedCpusFailed,
    )?;

    filter_cpuid(cpu_id, nrcpus, &mut kvm_cpuid)?;

    vcpu.set_cpuid2(&kvm_cpuid).map_err(
        Error::SetSupportedCpusFailed,
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn feature_and_vendor_name() {
        let mut cpuid = kvm::CpuId::new(2);

        {
            let entries = cpuid.mut_entries_slice();
            entries[0].function = 0;
            entries[1].function = 1;
            entries[1].ecx = 0x10;
            entries[1].edx = 0;
        }
        assert_eq!(Ok(()), filter_cpuid(1, 2, &mut cpuid));
        {
            let entries = cpuid.mut_entries_slice();
            assert_eq!(entries[0].function, 0);
            assert_eq!(entries[0].ebx, VENDOR_EBX_VAL);
            assert_eq!(entries[0].ecx, VENDOR_ECX_VAL);
            assert_eq!(entries[0].edx, VENDOR_EDX_VAL);
            assert_eq!(1, (entries[1].ebx >> EBX_CPUID_SHIFT) & 0x000000ff);
            assert_eq!(2, (entries[1].ebx >> EBX_CPU_COUNT_SHIFT) & 0x000000ff);
            assert_eq!(
                EBX_CLFLUSH_CACHELINE,
                (entries[1].ebx >> EBX_CLFLUSH_SIZE_SHIFT) & 0x000000ff
            );
            assert_ne!(0, entries[1].ecx & (1 << ECX_HYPERVISOR_SHIFT));
            assert_ne!(0, entries[1].edx & (1 << EDX_HTT_SHIFT));
        }
    }
}
<|endoftext|>


<commit_before>//! Defines a simple eye light integrator used for debugging

use scene::Scene;
use linalg::Ray;
use geometry::Intersection;
use film::Colorf;
use integrator::Integrator;
use bxdf::BxDFType;

/// The EyeLight integrator illuminates all objects in the scene from the camera
/// and only accounts for direct lighting. Rays from the camera to objects bring
/// light back from the objects along the same direction towards the camera
#[derive(Copy)]
pub struct EyeLight {
    /// Strength of the eye light
    intensity: Colorf,
}

impl EyeLight {
    /// Create a new eye light integrator and set the intensity of the light at the
    /// camera
    pub fn new(intensity: &Colorf) -> EyeLight { EyeLight { intensity: *intensity } }
}

impl Integrator for EyeLight {
    fn illumination(&self, _: &Scene, ray: &Ray, hit: &Intersection) -> Colorf {
        let bsdf = hit.instance.material.bsdf(hit);
        let w_o = -ray.d;
        bsdf.eval(&w_o, &ray.d, BxDFType::all())
    }
}

<commit_msg>Fix 'light' incident direction for the eyelight renderer<commit_after>//! Defines a simple eye light integrator used for debugging

use std::num::Float;

use scene::Scene;
use linalg;
use linalg::Ray;
use geometry::Intersection;
use film::Colorf;
use integrator::Integrator;
use bxdf::BxDFType;

/// The EyeLight integrator illuminates all objects in the scene from the camera
/// and only accounts for direct lighting. Rays from the camera to objects bring
/// light back from the objects along the same direction towards the camera
#[derive(Copy)]
pub struct EyeLight {
    /// Strength of the eye light
    intensity: Colorf,
}

impl EyeLight {
    /// Create a new eye light integrator and set the intensity of the light at the
    /// camera
    pub fn new(intensity: &Colorf) -> EyeLight { EyeLight { intensity: *intensity } }
}

impl Integrator for EyeLight {
    fn illumination(&self, _: &Scene, ray: &Ray, hit: &Intersection) -> Colorf {
        let bsdf = hit.instance.material.bsdf(hit);
        let w_o = -ray.d;
        self.intensity * bsdf.eval(&w_o, &w_o, BxDFType::all())
            * Float::abs(linalg::dot(&ray.d, &bsdf.n))
    }
}

<|endoftext|>


<commit_before>use block::ConcreteBlock;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSString, NSUInteger};
use error::NSError;
use objc::runtime::YES;
use objc_bringup::NSArray;
use sys::{MTLCopyAllDevices, MTLCreateSystemDefaultDevice, MTLDevice};
use std::borrow::Cow;
use std::convert::From;
use std::error::Error;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::marker::PhantomData;
use std::ops::Deref;
use std::path::Path;
use std::sync::mpsc;
use sys::MTLFeatureSet;
use {AsRaw, Buffer, CommandQueue, CommandQueueError, CompileOptions, DepthStencilState,
     DepthStencilStateDescriptor, FromRaw, FromRawError, Library, LibraryError, ResourceOptions,
     Size, Texture, TextureDescriptor};

pub struct Device(id);

impl Device {
    pub fn system_default_device() -> Result<Self, DeviceError> {
        let device = unsafe { MTLCreateSystemDefaultDevice() };
        if device != nil { Ok(Device(device)) } else { Err(DeviceError::ConstructionFailed) }
    }

    pub fn enumerate_all_system_devices() -> Vec<Result<Self, DeviceError>> {
        let all_devices = unsafe { MTLCopyAllDevices() };
        let mut devices_vec = vec![];

        unsafe {
            for i in 0..all_devices.count() {
                let device = all_devices.objectAtIndex(i);
                if device != nil {
                    devices_vec.push(Ok(Device(device)));
                } else {
                    devices_vec.push(Err(DeviceError::ConstructionFailed));
                }
            }
        }

        devices_vec
    }

    pub fn is_depth24_stencil8_pixel_format_supported(&self) -> bool {
        unsafe { self.0.depth24Stencil8PixelFormatSupported() == YES }
    }

    pub fn is_headless(&self) -> bool {
        unsafe { self.0.headless() == YES }
    }

    pub fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() == YES }
    }

    pub fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    pub fn name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    pub fn supports_feature_set(&self, feature_set: FeatureSet) -> bool {
        unsafe { self.0.supportsFeatureSet(feature_set.into()) == YES }
    }

    pub fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) == YES }
    }

    pub fn new_command_queue(&mut self) -> Result<CommandQueue, CommandQueueError> {
        let command_queue = unsafe { self.0.newCommandQueue() };
        Ok(try!(FromRaw::from_raw(command_queue)))
    }

    pub fn new_command_queue_with_max_buffer_count(&mut self, max_command_buffer_count: usize)
                                                   -> Result<CommandQueue, CommandQueueError> {
        let command_queue = unsafe {
            self.0.newCommandQueueWithMaxCommandBufferCount(max_command_buffer_count as NSUInteger)
        };
        Ok(try!(FromRaw::from_raw(command_queue)))
    }

    // TODO(George): This will panic in the metal lib if not called from a binary in a bundle. Need
    // to add proper error checking
    pub fn new_default_library(&mut self) -> Result<Library, LibraryError> {
        unsafe {
            let library = self.0.newDefaultLibrary();
            Ok(try!(FromRaw::from_raw(library)))
        }
    }

    #[cfg_attr(rustfmt, rustfmt_skip)]
    pub fn new_library_with_file(&mut self, file_path: &Path) -> Result<Library, LibraryError> {
        unsafe {
            let path_string = match file_path.as_os_str().to_str() {
                None => unimplemented!(),
                Some(s) => s
            };
            let path = NSString::alloc(nil).init_str(path_string);
            let mut error = nil;
            let library = self.0.newLibraryWithFile_error(path, &mut error);
            if library == nil {
                Err(LibraryError::from(NSError::new(error)))
            } else {
                Ok(try!(FromRaw::from_raw(library)))
            }
        }
    }

    pub fn new_library_with_source(&mut self, source: &str, compile_options: &CompileOptions)
                                   -> Result<Library, LibraryError> {
        unsafe {
            let source = NSString::alloc(nil).init_str(source);
            let options = compile_options.mtl_compile_options();
            let mut error = nil;
            let library = self.0.newLibraryWithSource_options_error(source, options, &mut error);
            if library == nil {
                Err(LibraryError::from(NSError::new(error)))
            } else {
                Ok(try!(FromRaw::from_raw(library)))
            }
        }
    }

    pub fn new_library_with_source_async(&mut self, source: &str, compile_options: &CompileOptions)
                                         -> mpsc::Receiver<Result<Library, LibraryError>> {
        unsafe {
            let source = NSString::alloc(nil).init_str(source);
            let options = compile_options.mtl_compile_options();
            let (sender, receiver) = mpsc::channel();
            let block = ConcreteBlock::new(move |lib, err| {
                if lib != nil {
                    match FromRaw::from_raw(lib) {
                        Ok(library) => sender.send(Ok(library)).unwrap(),
                        Err(e) => sender.send(Err(LibraryError::from(e))).unwrap(),
                    }
                } else {
                    sender.send(Err(LibraryError::from(NSError::new(err)))).unwrap();
                }
            });
            self.0.newLibraryWithSource_options_completionHandler(source, options, &block.copy());
            receiver
        }
    }

    #[allow(unused_variables)]
    // TODO(burtonageo): `data` parameter should be of the correct type
    pub fn new_library_with_data(&mut self, data: ()) -> Result<Library, LibraryError> {
        unimplemented!();
    }

    pub fn new_buffer_with_length(&mut self, length: usize, options: ResourceOptions) -> Buffer {
        unsafe {
            FromRaw::from_raw(self.0.newBufferWithLength_options(length as NSUInteger,
                                                                 options.into()))
                .unwrap()
        }
    }

    #[cfg_attr(rustfmt, rustfmt_skip)]
    pub fn new_buffer_with_bytes(&mut self, bytes: &[u8], options: ResourceOptions)
                                 -> Buffer {
        let len = bytes.len() as NSUInteger;
        let bytes = bytes.as_ptr() as *const _;
        unsafe {
            FromRaw::from_raw(self.0.newBufferWithBytes_length_options(
                bytes, len,options.into())).unwrap()
        }
    }

    pub fn new_buffer_with_bytes_no_copy<'a>(&mut self, bytes: &'a mut [u8], options: ResourceOptions)
                                             -> BufferRef<'a> {
        let len = bytes.len() as NSUInteger;
        let bytes = bytes.as_ptr() as *mut _;
        let dealloc = ConcreteBlock::new(|ptr, len| {
            use std::slice;
            unsafe {
                drop(slice::from_raw_parts(ptr as *const u8, len as usize))
            }
        });
        let buffer = unsafe {
            FromRaw::from_raw(self.0.newBufferWithBytesNoCopy_length_options_deallocator(bytes, len,
                                                                                options.into(),
                                                                                &dealloc)).unwrap()
        };
        BufferRef {
            buffer: buffer,
            marker: PhantomData
        }
    }

    pub fn new_texture(&mut self, descriptor: &TextureDescriptor) -> Result<Texture, FromRawError> {
        unsafe { FromRaw::from_raw(self.0.newTextureWithDescriptor(*descriptor.as_raw())) }
    }

    #[allow(unused_variables)]
    // TODO(burtonageo): Model types correctly
    pub fn new_sampler_state(&mut self, descriptor: ()) -> ! {
        unimplemented!();
    }

    pub fn new_depth_stencil_state(&mut self, descriptor: &DepthStencilStateDescriptor)
                                   -> Result<DepthStencilState, FromRawError> {
        unsafe {
            FromRaw::from_raw(self.0.newDepthStencilStateWithDescriptor(*descriptor.as_raw()))
        }
    }
}

pub struct BufferRef<'a> {
    buffer: Buffer,
    marker: PhantomData<&'a ()>
}

impl<'a> Deref for BufferRef<'a> {
    type Target = Buffer;
    fn deref(&self) -> &Self::Target {
        &self.buffer
    }
}

impl_from_into_raw!(Device, of protocol "MTLDevice");

#[cfg(target_os = "ios")]
convertible_enum! {
    #[allow(non_camel_case_types)]
    #[derive(Clone, Copy, Eq, Hash, PartialEq)]
    pub enum FeatureSet: MTLFeatureSet {
        iOSGpuFamily1_v1 => MTLFeatureSet_iOS_GPUFamily1_v1,
        iOSGpuFamily2_v1 => MTLFeatureSet_iOS_GPUFamily2_v1,
        iOSGpuFamily1_v2 => MTLFeatureSet_iOS_GPUFamily1_v2,
        iOSGpuFamily2_v2 => MTLFeatureSet_iOS_GPUFamily2_v2,
        iOSGpuFamily3_v1 => MTLFeatureSet_iOS_GPUFamily3_v1
    }
}

#[cfg(target_os = "macos")]
convertible_enum! {
    #[allow(non_camel_case_types)]
    #[derive(Clone, Copy, Eq, Hash, PartialEq)]
    pub enum FeatureSet: MTLFeatureSet {
        OsxGpuFamily1_v1 => MTLFeatureSet_OSX_GPUFamily1_v1,
        _non_unary_compile_dummy => _non_unary_compile_dummy
    }
}

#[derive(Clone, Debug)]
pub enum DeviceError {
    ConstructionFailed,
    RawError(FromRawError)
}

impl From<FromRawError> for DeviceError {
    fn from(error: FromRawError) -> Self {
        DeviceError::RawError(error)
    }
}

impl Display for DeviceError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let descr = match *self {
            DeviceError::ConstructionFailed => "DeviceError::ConstructionFailed",
            DeviceError::RawError(_) => "DeviceError::ConstructionFailed",
        };
        write!(f, "{}", descr)
    }
}

impl Error for DeviceError {
    fn description(&self) -> &str {
        match *self {
            DeviceError::ConstructionFailed => {
                "Could not create a default device. Please ensure that you are using at least OSX \
                 10.11 or iOS 8.0"
            }
            DeviceError::RawError(_) => "There was an error with the device pointer",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match self {
            &DeviceError::ConstructionFailed => None,
            &DeviceError::RawError(ref raw_err) => Some(raw_err),
        }
    }
}
<commit_msg>Rustfmt<commit_after>use block::ConcreteBlock;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSString, NSUInteger};
use error::NSError;
use objc::runtime::YES;
use objc_bringup::NSArray;
use sys::{MTLCopyAllDevices, MTLCreateSystemDefaultDevice, MTLDevice};
use std::borrow::Cow;
use std::convert::From;
use std::error::Error;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::marker::PhantomData;
use std::ops::Deref;
use std::path::Path;
use std::sync::mpsc;
use sys::MTLFeatureSet;
use {AsRaw, Buffer, CommandQueue, CommandQueueError, CompileOptions, DepthStencilState,
     DepthStencilStateDescriptor, FromRaw, FromRawError, Library, LibraryError, ResourceOptions,
     Size, Texture, TextureDescriptor};

pub struct Device(id);

impl Device {
    pub fn system_default_device() -> Result<Self, DeviceError> {
        let device = unsafe { MTLCreateSystemDefaultDevice() };
        if device != nil { Ok(Device(device)) } else { Err(DeviceError::ConstructionFailed) }
    }

    pub fn enumerate_all_system_devices() -> Vec<Result<Self, DeviceError>> {
        let all_devices = unsafe { MTLCopyAllDevices() };
        let mut devices_vec = vec![];

        unsafe {
            for i in 0..all_devices.count() {
                let device = all_devices.objectAtIndex(i);
                if device != nil {
                    devices_vec.push(Ok(Device(device)));
                } else {
                    devices_vec.push(Err(DeviceError::ConstructionFailed));
                }
            }
        }

        devices_vec
    }

    pub fn is_depth24_stencil8_pixel_format_supported(&self) -> bool {
        unsafe { self.0.depth24Stencil8PixelFormatSupported() == YES }
    }

    pub fn is_headless(&self) -> bool {
        unsafe { self.0.headless() == YES }
    }

    pub fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() == YES }
    }

    pub fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    pub fn name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    pub fn supports_feature_set(&self, feature_set: FeatureSet) -> bool {
        unsafe { self.0.supportsFeatureSet(feature_set.into()) == YES }
    }

    pub fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) == YES }
    }

    pub fn new_command_queue(&mut self) -> Result<CommandQueue, CommandQueueError> {
        let command_queue = unsafe { self.0.newCommandQueue() };
        Ok(try!(FromRaw::from_raw(command_queue)))
    }

    pub fn new_command_queue_with_max_buffer_count(&mut self, max_command_buffer_count: usize)
                                                   -> Result<CommandQueue, CommandQueueError> {
        let command_queue = unsafe {
            self.0.newCommandQueueWithMaxCommandBufferCount(max_command_buffer_count as NSUInteger)
        };
        Ok(try!(FromRaw::from_raw(command_queue)))
    }

    // TODO(George): This will panic in the metal lib if not called from a binary in a bundle. Need
    // to add proper error checking
    pub fn new_default_library(&mut self) -> Result<Library, LibraryError> {
        unsafe {
            let library = self.0.newDefaultLibrary();
            Ok(try!(FromRaw::from_raw(library)))
        }
    }

    #[cfg_attr(rustfmt, rustfmt_skip)]
    pub fn new_library_with_file(&mut self, file_path: &Path) -> Result<Library, LibraryError> {
        unsafe {
            let path_string = match file_path.as_os_str().to_str() {
                None => unimplemented!(),
                Some(s) => s
            };
            let path = NSString::alloc(nil).init_str(path_string);
            let mut error = nil;
            let library = self.0.newLibraryWithFile_error(path, &mut error);
            if library == nil {
                Err(LibraryError::from(NSError::new(error)))
            } else {
                Ok(try!(FromRaw::from_raw(library)))
            }
        }
    }

    pub fn new_library_with_source(&mut self, source: &str, compile_options: &CompileOptions)
                                   -> Result<Library, LibraryError> {
        unsafe {
            let source = NSString::alloc(nil).init_str(source);
            let options = compile_options.mtl_compile_options();
            let mut error = nil;
            let library = self.0.newLibraryWithSource_options_error(source, options, &mut error);
            if library == nil {
                Err(LibraryError::from(NSError::new(error)))
            } else {
                Ok(try!(FromRaw::from_raw(library)))
            }
        }
    }

    pub fn new_library_with_source_async(&mut self, source: &str, compile_options: &CompileOptions)
                                         -> mpsc::Receiver<Result<Library, LibraryError>> {
        unsafe {
            let source = NSString::alloc(nil).init_str(source);
            let options = compile_options.mtl_compile_options();
            let (sender, receiver) = mpsc::channel();
            let block = ConcreteBlock::new(move |lib, err| {
                if lib != nil {
                    match FromRaw::from_raw(lib) {
                        Ok(library) => sender.send(Ok(library)).unwrap(),
                        Err(e) => sender.send(Err(LibraryError::from(e))).unwrap(),
                    }
                } else {
                    sender.send(Err(LibraryError::from(NSError::new(err)))).unwrap();
                }
            });
            self.0.newLibraryWithSource_options_completionHandler(source, options, &block.copy());
            receiver
        }
    }

    #[allow(unused_variables)]
    // TODO(burtonageo): `data` parameter should be of the correct type
    pub fn new_library_with_data(&mut self, data: ()) -> Result<Library, LibraryError> {
        unimplemented!();
    }

    pub fn new_buffer_with_length(&mut self, length: usize, options: ResourceOptions) -> Buffer {
        unsafe {
            FromRaw::from_raw(self.0.newBufferWithLength_options(length as NSUInteger,
                                                                 options.into()))
                .unwrap()
        }
    }

    #[cfg_attr(rustfmt, rustfmt_skip)]
    pub fn new_buffer_with_bytes(&mut self, bytes: &[u8], options: ResourceOptions)
                                 -> Buffer {
        let len = bytes.len() as NSUInteger;
        let bytes = bytes.as_ptr() as *const _;
        unsafe {
            FromRaw::from_raw(self.0.newBufferWithBytes_length_options(
                bytes, len,options.into())).unwrap()
        }
    }

    pub fn new_buffer_with_bytes_no_copy<'a>(&mut self, bytes: &'a mut [u8],
                                             options: ResourceOptions)
                                             -> BufferRef<'a> {
        let len = bytes.len() as NSUInteger;
        let bytes = bytes.as_ptr() as *mut _;
        let dealloc = ConcreteBlock::new(|ptr, len| {
            use std::slice;
            unsafe { drop(slice::from_raw_parts(ptr as *const u8, len as usize)) }
        });
        let buffer = unsafe {
            FromRaw::from_raw(self.0.newBufferWithBytesNoCopy_length_options_deallocator(bytes, len,
                                                                                options.into(),
                                                                                &dealloc)).unwrap()
        };
        BufferRef { buffer: buffer, marker: PhantomData }
    }

    pub fn new_texture(&mut self, descriptor: &TextureDescriptor) -> Result<Texture, FromRawError> {
        unsafe { FromRaw::from_raw(self.0.newTextureWithDescriptor(*descriptor.as_raw())) }
    }

    #[allow(unused_variables)]
    // TODO(burtonageo): Model types correctly
    pub fn new_sampler_state(&mut self, descriptor: ()) -> ! {
        unimplemented!();
    }

    pub fn new_depth_stencil_state(&mut self, descriptor: &DepthStencilStateDescriptor)
                                   -> Result<DepthStencilState, FromRawError> {
        unsafe {
            FromRaw::from_raw(self.0.newDepthStencilStateWithDescriptor(*descriptor.as_raw()))
        }
    }
}

pub struct BufferRef<'a> {
    buffer: Buffer,
    marker: PhantomData<&'a ()>
}

impl<'a> Deref for BufferRef<'a> {
    type Target = Buffer;
    fn deref(&self) -> &Self::Target {
        &self.buffer
    }
}

impl_from_into_raw!(Device, of protocol "MTLDevice");

#[cfg(target_os = "ios")]
convertible_enum! {
    #[allow(non_camel_case_types)]
    #[derive(Clone, Copy, Eq, Hash, PartialEq)]
    pub enum FeatureSet: MTLFeatureSet {
        iOSGpuFamily1_v1 => MTLFeatureSet_iOS_GPUFamily1_v1,
        iOSGpuFamily2_v1 => MTLFeatureSet_iOS_GPUFamily2_v1,
        iOSGpuFamily1_v2 => MTLFeatureSet_iOS_GPUFamily1_v2,
        iOSGpuFamily2_v2 => MTLFeatureSet_iOS_GPUFamily2_v2,
        iOSGpuFamily3_v1 => MTLFeatureSet_iOS_GPUFamily3_v1
    }
}

#[cfg(target_os = "macos")]
convertible_enum! {
    #[allow(non_camel_case_types)]
    #[derive(Clone, Copy, Eq, Hash, PartialEq)]
    pub enum FeatureSet: MTLFeatureSet {
        OsxGpuFamily1_v1 => MTLFeatureSet_OSX_GPUFamily1_v1,
        _non_unary_compile_dummy => _non_unary_compile_dummy
    }
}

#[derive(Clone, Debug)]
pub enum DeviceError {
    ConstructionFailed,
    RawError(FromRawError)
}

impl From<FromRawError> for DeviceError {
    fn from(error: FromRawError) -> Self {
        DeviceError::RawError(error)
    }
}

impl Display for DeviceError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let descr = match *self {
            DeviceError::ConstructionFailed => "DeviceError::ConstructionFailed",
            DeviceError::RawError(_) => "DeviceError::ConstructionFailed",
        };
        write!(f, "{}", descr)
    }
}

impl Error for DeviceError {
    fn description(&self) -> &str {
        match *self {
            DeviceError::ConstructionFailed => {
                "Could not create a default device. Please ensure that you are using at least OSX \
                 10.11 or iOS 8.0"
            }
            DeviceError::RawError(_) => "There was an error with the device pointer",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match self {
            &DeviceError::ConstructionFailed => None,
            &DeviceError::RawError(ref raw_err) => Some(raw_err),
        }
    }
}
<|endoftext|>