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

use std::env;
use std::path::Path;

#[cfg(windows)]
use std::ptr;
#[cfg(windows)]
use user32;
#[cfg(windows)]
use widestring::WideCString;
#[cfg(windows)]
use winapi;
#[cfg(windows)]
use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS, KEY_READ};
#[cfg(windows)]
use winreg::RegKey;

use common::ui::{UIReader, UIWriter, UI};
use hcore::crypto::SigKeyPair;
use hcore::env as henv;
use hcore::package::ident;
use hcore::Error::InvalidOrigin;

use analytics;
use command;
use config;
use error::Result;
use {AUTH_TOKEN_ENVVAR, CTL_SECRET_ENVVAR, ORIGIN_ENVVAR};

pub fn start(
    ui: &mut UI,
    cache_path: &Path,
    analytics_path: &Path,
    binlink_path: &Path,
) -> Result<()> {
    let mut generated_origin = false;

    ui.br()?;
    ui.title("Habitat CLI Setup")?;
    ui.para("Welcome to hab setup. Let's get started.")?;

    ui.heading("Set up a default origin")?;
    ui.para(
        "Every package in Habitat belongs to an origin, which indicates the person or \
         organization responsible for maintaining that package. Each origin also has \
         a key used to cryptographically sign packages in that origin.",
    )?;
    ui.para(
        "Selecting a default origin tells package building operations such as 'hab \
         pkg build' what key should be used to sign the packages produced. If you \
         do not set a default origin now, you will have to tell package building \
         commands each time what origin to use.",
    )?;
    ui.para(
        "For more information on origins and how they are used in building packages, \
         please consult the docs at https://www.habitat.sh/docs/create-packages-build/",
    )?;
    if ask_default_origin(ui)? {
        ui.br()?;
        ui.para(
            "Enter the name of your origin. If you plan to publish your packages \
             publicly, we recommend that you select one that is not already in use \
             on the Habitat build service found at https://bldr.habitat.sh/.",
        )?;
        ui.para(&format!(
            "Origins must begin with a lowercase letter or number. \
             Allowed characters include lowercase letters, numbers, _, -. \
             No more than 255 characters."
        ))?;
        let mut origin = prompt_origin(ui)?;

        while !ident::is_valid_origin_name(&origin) {
            ui.br()?;
            ui.fatal(&format!("{}", InvalidOrigin(origin)))?;
            ui.br()?;

            origin = prompt_origin(ui)?;
        }
        write_cli_config_origin(&origin)?;
        ui.br()?;
        if is_origin_in_cache(&origin, cache_path) {
            ui.para(&format!(
                "You already have an origin key for {} created and \
                 installed. Great work!",
                &origin
            ))?;
        } else {
            ui.heading("Create origin key pair")?;
            ui.para(&format!(
                "It doesn't look like you have a signing key for the origin \
                 `{}'. Without it, you won't be able to build new packages \
                 successfully.",
                &origin
            ))?;
            ui.para(
                "You can either create a new signing key now, or, if you are building \
                 packages for an origin that already exists, ask the owner to give \
                 you the signing key.",
            )?;
            ui.para(
                "For more information on the use of origin keys, please consult \
                 the documentation at \
                 https://www.habitat.sh/docs/concepts-keys/#origin-keys",
            )?;
            if ask_create_origin(ui, &origin)? {
                create_origin(ui, &origin, cache_path)?;
                generated_origin = true;
            } else {
                ui.para(&format!(
                    "You might want to create an origin key later with: \
                     `hab origin key generate {}'",
                    &origin
                ))?;
            }
        }
    } else {
        ui.para("Okay, maybe another time.")?;
    }
    ui.heading("Habitat Personal Access Token")?;
    ui.para(
        "While you can perform tasks like building and running Habitat packages without needing \
         to authenticate with Builder, some operations like uploading your packages to \
         Builder, or checking status of your build jobs from the Habitat client will require \
         you to use an access token.",
    )?;
    ui.para(
        "The Habitat Personal Access Token can be generated via the Builder  \
         Profile page (https://bldr.habitat.sh/#/profile). Once you have \
         generated your token, you can enter it here.",
    )?;
    ui.para(
        "If you would like to save your token for use by the Habitat client, please enter \
         your access token. Otherwise, just enter No.",
    )?;
    ui.para(
        "For more information on using Builder, please read the \
         documentation at https://www.habitat.sh/docs/using-builder/",
    )?;
    if ask_default_auth_token(ui)? {
        ui.br()?;
        ui.para("Enter your Habitat Personal Access Token.")?;
        let auth_token = prompt_auth_token(ui)?;
        write_cli_config_auth_token(auth_token)?;
    } else {
        ui.para("Okay, maybe another time.")?;
    }
    if ask_default_ctl_secret(ui)? {
        ui.br()?;
        ui.para("Enter your Habitat Supervisor CtlGateway secret.")?;
        let ctl_secret = prompt_ctl_secret(ui)?;
        write_cli_config_ctl_secret(ctl_secret)?;
    } else {
        ui.para("Okay, maybe another time.")?;
    }
    if cfg!(windows) {
        ui.heading("Habitat Binlink Path")?;
        ui.para(
            "The `hab` command-line tool can create binlinks for package binaries \
             in the 'PATH' when using the 'pkg binlink' or 'pkg install --binlink' \
             commands. By default, Habitat will create these binlinks in the '/hab/bin' \
             directory. This directory will always be included in your 'PATH' when \
             inside a Studio environment. However, you will want this directory on your \
             machine's persistent 'PATH' in order to access binlinks outside of a Studio.",
        )?;
        if ui.prompt_yes_no("Add binlink directory to PATH?", Some(true))? {
            set_binlink_path(&binlink_path)?;
            ui.para(&format!(
                "{} has been added to your path. You will need to open a \
                 new console window for this added entry to take effect.",
                binlink_path.display()
            ))?;
        } else {
            ui.para("Okay, maybe another time.")?;
        }
    }
    ui.heading("Analytics")?;
    ui.para(
        "The `hab` command-line tool will optionally send anonymous usage data to \
         Habitat's Google Analytics account. This is a strictly opt-in activity and \
         no tracking will occur unless you respond affirmatively to the question \
         below.",
    )?;
    ui.para(
        "We collect this data to help improve Habitat's user experience. For example, we \
         would like to know the category of tasks users are performing, and which ones \
         they are having trouble with (e.g. mistyping command line arguments).",
    )?;
    ui.para(
        "To see what kinds of data are sent and how they are anonymized, please read more \
         about our analytics here: https://www.habitat.sh/docs/about-analytics/",
    )?;
    if ask_enable_analytics(ui, analytics_path)? {
        opt_in_analytics(ui, analytics_path, generated_origin)?;
    } else {
        opt_out_analytics(ui, analytics_path)?;
    }
    ui.heading("CLI Setup Complete")?;
    ui.para("That's all for now. Thanks for using Habitat!")?;
    Ok(())
}

fn ask_default_origin(ui: &mut UI) -> Result<bool> {
    Ok(ui.prompt_yes_no("Set up a default origin?", Some(true))?)
}

fn ask_create_origin(ui: &mut UI, origin: &str) -> Result<bool> {
    Ok(ui.prompt_yes_no(
        &format!("Create an origin key for `{}'?", origin),
        Some(true),
    )?)
}

fn write_cli_config_origin<T>(origin: T) -> Result<()>
where
    T: ToString,
{
    let mut config = config::load()?;
    config.origin = Some(origin.to_string());
    config::save(&config)
}

fn write_cli_config_auth_token<T>(auth_token: T) -> Result<()>
where
    T: ToString,
{
    let mut config = config::load()?;
    config.auth_token = Some(auth_token.to_string());
    config::save(&config)
}

fn write_cli_config_ctl_secret<T>(value: T) -> Result<()>
where
    T: ToString,
{
    let mut config = config::load()?;
    config.ctl_secret = Some(value.to_string());
    config::save(&config)
}

fn is_origin_in_cache(origin: &str, cache_path: &Path) -> bool {
    match SigKeyPair::get_latest_pair_for(origin, cache_path, None) {
        Ok(pair) => match pair.secret() {
            Ok(_) => true,
            _ => false,
        },
        _ => false,
    }
}

fn create_origin(ui: &mut UI, origin: &str, cache_path: &Path) -> Result<()> {
    let result = command::origin::key::generate::start(ui, &origin, cache_path);
    ui.br()?;
    result
}

fn prompt_origin(ui: &mut UI) -> Result<String> {
    let config = config::load()?;
    let default = match config.origin {
        Some(o) => {
            ui.para(&format!(
                "You already have a default origin set up as `{}', but feel \
                 free to change it if you wish.",
                &o
            ))?;
            Some(o)
        }
        None => henv::var(ORIGIN_ENVVAR).or(henv::var("USER")).ok(),
    };
    Ok(ui.prompt_ask("Default origin name", default.as_ref().map(|x| &**x))?)
}

fn ask_default_auth_token(ui: &mut UI) -> Result<bool> {
    Ok(ui.prompt_yes_no(
        "Set up a default Habitat personal access token?",
        Some(true),
    )?)
}

fn ask_default_ctl_secret(ui: &mut UI) -> Result<bool> {
    Ok(ui.prompt_yes_no(
        "Set up a default Habitat Supervisor CtlGateway secret?",
        Some(true),
    )?)
}

fn prompt_auth_token(ui: &mut UI) -> Result<String> {
    let config = config::load()?;
    let default = match config.auth_token {
        Some(o) => {
            ui.para(
                "You already have a default auth token set up, but feel free to change it \
                 if you wish.",
            )?;
            Some(o)
        }
        None => henv::var(AUTH_TOKEN_ENVVAR).ok(),
    };
    Ok(ui.prompt_ask(
        "Habitat personal access token",
        default.as_ref().map(|x| &**x),
    )?)
}

fn prompt_ctl_secret(ui: &mut UI) -> Result<String> {
    let config = config::load()?;
    let default = match config.ctl_secret {
        Some(o) => {
            ui.para(
                "You already have a default CtlGateway secret set up, but feel free to change it
                if you wish.",
            )?;
            Some(o)
        }
        None => henv::var(CTL_SECRET_ENVVAR).ok(),
    };
    Ok(ui.prompt_ask(
        "Habitat Supervisor CtlGateway secret",
        default.as_ref().map(|x| &**x),
    )?)
}

fn ask_enable_analytics(ui: &mut UI, analytics_path: &Path) -> Result<bool> {
    let default = match analytics::is_opted_in(analytics_path) {
        Some(val) => Some(val),
        None => Some(true),
    };
    Ok(ui.prompt_yes_no("Enable analytics?", default)?)
}

fn opt_in_analytics(ui: &mut UI, analytics_path: &Path, generated_origin: bool) -> Result<()> {
    let result = analytics::opt_in(ui, analytics_path, generated_origin);
    ui.br()?;
    result
}

fn opt_out_analytics(ui: &mut UI, analytics_path: &Path) -> Result<()> {
    let result = analytics::opt_out(ui, analytics_path);
    ui.br()?;
    result
}

#[cfg(windows)]
fn binlink_is_on_path(binlink_path: &Path) -> bool {
    match RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(
        r"System\CurrentControlSet\Control\Session Manager\Environment",
        KEY_READ,
    ) {
        Ok(env) => {
            let path: String = env
                .get_value("path")
                .expect("could not find a machine PATH");
            env::split_paths(&path).any(|p| p == binlink_path)
        }
        _ => false,
    }
}

/// this sets the permanent machine PATH and not
/// the path of this process. These are maintained
/// in the Windows registry
#[cfg(windows)]
fn set_binlink_path(binlink_path: &Path) -> Result<()> {
    if !binlink_is_on_path(binlink_path) {
        let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
        let env = hklm.open_subkey_with_flags(
            r"System\CurrentControlSet\Control\Session Manager\Environment",
            KEY_ALL_ACCESS,
        )?;
        let path: String = env.get_value("path")?;
        let new_paths = [binlink_path, Path::new(&path)];
        env.set_value(
            "path",
            &env::join_paths(new_paths.iter())?.to_str().unwrap(),
        )?;

        // After altering the machine environment, we must broadcast
        // a WM_SETTINGCHANGE message to all windows so the user
        // will not need to sign out/in for the new path to take effect
        unsafe {
            user32::SendMessageTimeoutW(
                winapi::HWND_BROADCAST,
                winapi::WM_SETTINGCHANGE,
                0,
                WideCString::from_str("Environment").unwrap().as_ptr() as winapi::LPARAM,
                winapi::SMTO_ABORTIFHUNG,
                5000,
                ptr::null_mut(),
            );
        }
    }
    Ok(())
}

#[cfg(not(target_os = "windows"))]
fn set_binlink_path(binlink_path: &Path) -> Result<()> {
    unreachable!()
}
<commit_msg>fix splitting and joining of path directories in setup<commit_after>// Copyright (c) 2016 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::env;
use std::path::Path;

#[cfg(windows)]
use std::ptr;
#[cfg(windows)]
use user32;
#[cfg(windows)]
use widestring::WideCString;
#[cfg(windows)]
use winapi;
#[cfg(windows)]
use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS, KEY_READ};
#[cfg(windows)]
use winreg::RegKey;

use common::ui::{UIReader, UIWriter, UI};
use hcore::crypto::SigKeyPair;
use hcore::env as henv;
use hcore::package::ident;
use hcore::Error::InvalidOrigin;

use analytics;
use command;
use config;
use error::Result;
use {AUTH_TOKEN_ENVVAR, CTL_SECRET_ENVVAR, ORIGIN_ENVVAR};

pub fn start(
    ui: &mut UI,
    cache_path: &Path,
    analytics_path: &Path,
    binlink_path: &Path,
) -> Result<()> {
    let mut generated_origin = false;

    ui.br()?;
    ui.title("Habitat CLI Setup")?;
    ui.para("Welcome to hab setup. Let's get started.")?;

    ui.heading("Set up a default origin")?;
    ui.para(
        "Every package in Habitat belongs to an origin, which indicates the person or \
         organization responsible for maintaining that package. Each origin also has \
         a key used to cryptographically sign packages in that origin.",
    )?;
    ui.para(
        "Selecting a default origin tells package building operations such as 'hab \
         pkg build' what key should be used to sign the packages produced. If you \
         do not set a default origin now, you will have to tell package building \
         commands each time what origin to use.",
    )?;
    ui.para(
        "For more information on origins and how they are used in building packages, \
         please consult the docs at https://www.habitat.sh/docs/create-packages-build/",
    )?;
    if ask_default_origin(ui)? {
        ui.br()?;
        ui.para(
            "Enter the name of your origin. If you plan to publish your packages \
             publicly, we recommend that you select one that is not already in use \
             on the Habitat build service found at https://bldr.habitat.sh/.",
        )?;
        ui.para(&format!(
            "Origins must begin with a lowercase letter or number. \
             Allowed characters include lowercase letters, numbers, _, -. \
             No more than 255 characters."
        ))?;
        let mut origin = prompt_origin(ui)?;

        while !ident::is_valid_origin_name(&origin) {
            ui.br()?;
            ui.fatal(&format!("{}", InvalidOrigin(origin)))?;
            ui.br()?;

            origin = prompt_origin(ui)?;
        }
        write_cli_config_origin(&origin)?;
        ui.br()?;
        if is_origin_in_cache(&origin, cache_path) {
            ui.para(&format!(
                "You already have an origin key for {} created and \
                 installed. Great work!",
                &origin
            ))?;
        } else {
            ui.heading("Create origin key pair")?;
            ui.para(&format!(
                "It doesn't look like you have a signing key for the origin \
                 `{}'. Without it, you won't be able to build new packages \
                 successfully.",
                &origin
            ))?;
            ui.para(
                "You can either create a new signing key now, or, if you are building \
                 packages for an origin that already exists, ask the owner to give \
                 you the signing key.",
            )?;
            ui.para(
                "For more information on the use of origin keys, please consult \
                 the documentation at \
                 https://www.habitat.sh/docs/concepts-keys/#origin-keys",
            )?;
            if ask_create_origin(ui, &origin)? {
                create_origin(ui, &origin, cache_path)?;
                generated_origin = true;
            } else {
                ui.para(&format!(
                    "You might want to create an origin key later with: \
                     `hab origin key generate {}'",
                    &origin
                ))?;
            }
        }
    } else {
        ui.para("Okay, maybe another time.")?;
    }
    ui.heading("Habitat Personal Access Token")?;
    ui.para(
        "While you can perform tasks like building and running Habitat packages without needing \
         to authenticate with Builder, some operations like uploading your packages to \
         Builder, or checking status of your build jobs from the Habitat client will require \
         you to use an access token.",
    )?;
    ui.para(
        "The Habitat Personal Access Token can be generated via the Builder  \
         Profile page (https://bldr.habitat.sh/#/profile). Once you have \
         generated your token, you can enter it here.",
    )?;
    ui.para(
        "If you would like to save your token for use by the Habitat client, please enter \
         your access token. Otherwise, just enter No.",
    )?;
    ui.para(
        "For more information on using Builder, please read the \
         documentation at https://www.habitat.sh/docs/using-builder/",
    )?;
    if ask_default_auth_token(ui)? {
        ui.br()?;
        ui.para("Enter your Habitat Personal Access Token.")?;
        let auth_token = prompt_auth_token(ui)?;
        write_cli_config_auth_token(auth_token)?;
    } else {
        ui.para("Okay, maybe another time.")?;
    }
    if ask_default_ctl_secret(ui)? {
        ui.br()?;
        ui.para("Enter your Habitat Supervisor CtlGateway secret.")?;
        let ctl_secret = prompt_ctl_secret(ui)?;
        write_cli_config_ctl_secret(ctl_secret)?;
    } else {
        ui.para("Okay, maybe another time.")?;
    }
    if cfg!(windows) {
        ui.heading("Habitat Binlink Path")?;
        ui.para(
            "The `hab` command-line tool can create binlinks for package binaries \
             in the 'PATH' when using the 'pkg binlink' or 'pkg install --binlink' \
             commands. By default, Habitat will create these binlinks in the '/hab/bin' \
             directory. This directory will always be included in your 'PATH' when \
             inside a Studio environment. However, you will want this directory on your \
             machine's persistent 'PATH' in order to access binlinks outside of a Studio.",
        )?;
        if ui.prompt_yes_no("Add binlink directory to PATH?", Some(true))? {
            set_binlink_path(&binlink_path)?;
            ui.para(&format!(
                "{} has been added to your path. You will need to open a \
                 new console window for this added entry to take effect.",
                binlink_path.display()
            ))?;
        } else {
            ui.para("Okay, maybe another time.")?;
        }
    }
    ui.heading("Analytics")?;
    ui.para(
        "The `hab` command-line tool will optionally send anonymous usage data to \
         Habitat's Google Analytics account. This is a strictly opt-in activity and \
         no tracking will occur unless you respond affirmatively to the question \
         below.",
    )?;
    ui.para(
        "We collect this data to help improve Habitat's user experience. For example, we \
         would like to know the category of tasks users are performing, and which ones \
         they are having trouble with (e.g. mistyping command line arguments).",
    )?;
    ui.para(
        "To see what kinds of data are sent and how they are anonymized, please read more \
         about our analytics here: https://www.habitat.sh/docs/about-analytics/",
    )?;
    if ask_enable_analytics(ui, analytics_path)? {
        opt_in_analytics(ui, analytics_path, generated_origin)?;
    } else {
        opt_out_analytics(ui, analytics_path)?;
    }
    ui.heading("CLI Setup Complete")?;
    ui.para("That's all for now. Thanks for using Habitat!")?;
    Ok(())
}

fn ask_default_origin(ui: &mut UI) -> Result<bool> {
    Ok(ui.prompt_yes_no("Set up a default origin?", Some(true))?)
}

fn ask_create_origin(ui: &mut UI, origin: &str) -> Result<bool> {
    Ok(ui.prompt_yes_no(
        &format!("Create an origin key for `{}'?", origin),
        Some(true),
    )?)
}

fn write_cli_config_origin<T>(origin: T) -> Result<()>
where
    T: ToString,
{
    let mut config = config::load()?;
    config.origin = Some(origin.to_string());
    config::save(&config)
}

fn write_cli_config_auth_token<T>(auth_token: T) -> Result<()>
where
    T: ToString,
{
    let mut config = config::load()?;
    config.auth_token = Some(auth_token.to_string());
    config::save(&config)
}

fn write_cli_config_ctl_secret<T>(value: T) -> Result<()>
where
    T: ToString,
{
    let mut config = config::load()?;
    config.ctl_secret = Some(value.to_string());
    config::save(&config)
}

fn is_origin_in_cache(origin: &str, cache_path: &Path) -> bool {
    match SigKeyPair::get_latest_pair_for(origin, cache_path, None) {
        Ok(pair) => match pair.secret() {
            Ok(_) => true,
            _ => false,
        },
        _ => false,
    }
}

fn create_origin(ui: &mut UI, origin: &str, cache_path: &Path) -> Result<()> {
    let result = command::origin::key::generate::start(ui, &origin, cache_path);
    ui.br()?;
    result
}

fn prompt_origin(ui: &mut UI) -> Result<String> {
    let config = config::load()?;
    let default = match config.origin {
        Some(o) => {
            ui.para(&format!(
                "You already have a default origin set up as `{}', but feel \
                 free to change it if you wish.",
                &o
            ))?;
            Some(o)
        }
        None => henv::var(ORIGIN_ENVVAR).or(henv::var("USER")).ok(),
    };
    Ok(ui.prompt_ask("Default origin name", default.as_ref().map(|x| &**x))?)
}

fn ask_default_auth_token(ui: &mut UI) -> Result<bool> {
    Ok(ui.prompt_yes_no(
        "Set up a default Habitat personal access token?",
        Some(true),
    )?)
}

fn ask_default_ctl_secret(ui: &mut UI) -> Result<bool> {
    Ok(ui.prompt_yes_no(
        "Set up a default Habitat Supervisor CtlGateway secret?",
        Some(true),
    )?)
}

fn prompt_auth_token(ui: &mut UI) -> Result<String> {
    let config = config::load()?;
    let default = match config.auth_token {
        Some(o) => {
            ui.para(
                "You already have a default auth token set up, but feel free to change it \
                 if you wish.",
            )?;
            Some(o)
        }
        None => henv::var(AUTH_TOKEN_ENVVAR).ok(),
    };
    Ok(ui.prompt_ask(
        "Habitat personal access token",
        default.as_ref().map(|x| &**x),
    )?)
}

fn prompt_ctl_secret(ui: &mut UI) -> Result<String> {
    let config = config::load()?;
    let default = match config.ctl_secret {
        Some(o) => {
            ui.para(
                "You already have a default CtlGateway secret set up, but feel free to change it
                if you wish.",
            )?;
            Some(o)
        }
        None => henv::var(CTL_SECRET_ENVVAR).ok(),
    };
    Ok(ui.prompt_ask(
        "Habitat Supervisor CtlGateway secret",
        default.as_ref().map(|x| &**x),
    )?)
}

fn ask_enable_analytics(ui: &mut UI, analytics_path: &Path) -> Result<bool> {
    let default = match analytics::is_opted_in(analytics_path) {
        Some(val) => Some(val),
        None => Some(true),
    };
    Ok(ui.prompt_yes_no("Enable analytics?", default)?)
}

fn opt_in_analytics(ui: &mut UI, analytics_path: &Path, generated_origin: bool) -> Result<()> {
    let result = analytics::opt_in(ui, analytics_path, generated_origin);
    ui.br()?;
    result
}

fn opt_out_analytics(ui: &mut UI, analytics_path: &Path) -> Result<()> {
    let result = analytics::opt_out(ui, analytics_path);
    ui.br()?;
    result
}

#[cfg(windows)]
fn binlink_is_on_path(binlink_path: &Path) -> bool {
    match RegKey::predef(HKEY_LOCAL_MACHINE).open_subkey_with_flags(
        r"System\CurrentControlSet\Control\Session Manager\Environment",
        KEY_READ,
    ) {
        Ok(env) => {
            let path: String = env
                .get_value("path")
                .expect("could not find a machine PATH");
            env::split_paths(&path).any(|p| p == binlink_path)
        }
        _ => false,
    }
}

/// this sets the permanent machine PATH and not
/// the path of this process. These are maintained
/// in the Windows registry
#[cfg(windows)]
fn set_binlink_path(binlink_path: &Path) -> Result<()> {
    if !binlink_is_on_path(binlink_path) {
        let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
        let env = hklm.open_subkey_with_flags(
            r"System\CurrentControlSet\Control\Session Manager\Environment",
            KEY_ALL_ACCESS,
        )?;
        let mut paths = vec![binlink_path.to_path_buf()];
        let path: String = env.get_value("path")?;
        paths.append(&mut env::split_paths(&path).collect());
        env.set_value("path", &env::join_paths(paths)?.to_str().unwrap())?;

        // After altering the machine environment, we must broadcast
        // a WM_SETTINGCHANGE message to all windows so the user
        // will not need to sign out/in for the new path to take effect
        unsafe {
            user32::SendMessageTimeoutW(
                winapi::HWND_BROADCAST,
                winapi::WM_SETTINGCHANGE,
                0,
                WideCString::from_str("Environment").unwrap().as_ptr() as winapi::LPARAM,
                winapi::SMTO_ABORTIFHUNG,
                5000,
                ptr::null_mut(),
            );
        }
    }
    Ok(())
}

#[cfg(not(target_os = "windows"))]
fn set_binlink_path(binlink_path: &Path) -> Result<()> {
    unreachable!()
}
<|endoftext|>


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

use std::env;
use std::fs::create_dir_all;
use std::fs::{File, canonicalize, OpenOptions};
use std::io::{Write, BufRead, BufReader};
use std::path::Path;
use std::collections::HashMap;

use handlebars::Handlebars;
use hcore::package::PackageIdent;

use common::ui::{UI, Status};
use error::Result;

const DEFAULT_PLAN_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/default_template_plan.sh"
));
const FULL_PLAN_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/full_template_plan.sh"
));
const DEFAULT_TOML_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/template_default.toml"
));
const GITIGNORE_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/template_gitignore"
));
const README_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/template_README.md"
));

const DEFAULT_PKG_VERSION: &'static str = "0.1.0";

pub fn start(
    ui: &mut UI,
    origin: String,
    with_docs: bool,
    with_callbacks: bool,
    with_all: bool,
    scaffolding_ident: Option<PackageIdent>,
    maybe_name: Option<String>,
) -> Result<()> {
    ui.begin("Constructing a cozy habitat for your app...")?;
    ui.br()?;

    let (root, name) = match maybe_name {
        Some(name) => (name.clone(), name.clone()),
        // The name of the current working directory.
        None => {
            (
                "habitat".into(),
                canonicalize(".")
                    .ok()
                    .and_then(|path| {
                        path.components().last().and_then(|val| {
                            // Type gymnastics!
                            val.as_os_str().to_os_string().into_string().ok()
                        })
                    })
                    .unwrap_or("unnamed".into()),
            )
        }
    };

    // Build out the variables passed.
    let handlebars = Handlebars::new();
    let mut data = HashMap::new();
    data.insert("pkg_name".to_string(), name);
    data.insert("pkg_origin".to_string(), origin);
    data.insert("pkg_version".to_string(), DEFAULT_PKG_VERSION.to_string());

    let scaffold = match scaffolding_ident {
        Some(ident) => Some(data.insert(
            "scaffolding_ident".to_string(),
            ident.to_string(),
        )),
        None => None,
    };

    if with_callbacks {
        data.insert("with_callbacks".to_string(), "true".to_string());
    }
    if with_docs {
        data.insert("with_docs".to_string(), "true".to_string());
    }
    // Add all environment variables that start with "pkg_" as variables in
    // the template.
    for (key, value) in env::vars() {
        if key.starts_with("pkg_") {
            data.insert(key, value);
        }
    }

    // We want to render the configured variables.
    if with_all || scaffold.is_none() {
        let rendered_plan = handlebars.template_render(FULL_PLAN_TEMPLATE, &data)?;
        create_with_template(ui, &format!("{}/plan.sh", root), &rendered_plan)?;
    } else {
        let rendered_plan = handlebars.template_render(DEFAULT_PLAN_TEMPLATE, &data)?;
        create_with_template(ui, &format!("{}/plan.sh", root), &rendered_plan)?;
    }
    ui.para(
        "`plan.sh` is the foundation of your new habitat. It contains \
        metadata, dependencies, and tasks.",
    )?;
    let rendered_default_toml = handlebars.template_render(DEFAULT_TOML_TEMPLATE, &data)?;
    create_with_template(
        ui,
        &format!("{}/default.toml", root),
        &rendered_default_toml,
    )?;
    ui.para(
        "`default.toml` contains default values for `cfg` prefixed variables.",
    )?;

    let rendered_readme_md = handlebars.template_render(README_TEMPLATE, &data)?;
    create_with_template(
        ui,
        &format!("{}/README.md", root),
        &rendered_readme_md,
    )?;
    ui.para(
        "`README.md` contains a basic README document which you should update.",
    )?;

    let config_path = format!("{}/config/", root);
    match Path::new(&config_path).exists() {
        true => {
            ui.status(
                Status::Using,
                format!("existing directory: {}", config_path),
            )?
        }
        false => {
            ui.status(
                Status::Creating,
                format!("directory: {}", config_path),
            )?;
            create_dir_all(&config_path)?;
        }
    };
    ui.para(
        "`/config/` contains configuration files for your app.",
    )?;

    let hooks_path = format!("{}/hooks/", root);
    match Path::new(&hooks_path).exists() {
        true => {
            ui.status(
                Status::Using,
                format!("existing directory: {}", hooks_path),
            )?
        }
        false => {
            ui.status(
                Status::Creating,
                format!("directory: {}", hooks_path),
            )?;
            create_dir_all(&hooks_path)?;
        }
    };
    ui.para(
        "`/hooks/` contains automation hooks into your habitat.",
    )?;

    ui.para(
        "For more information on any of the files: \
        https://www.habitat.sh/docs/reference/plan-syntax/",
    )?;

    render_ignorefile(ui, &root)?;

    ui.end("An abode for your code is initialized!")?;
    Ok(())
}

fn render_ignorefile(ui: &mut UI, root: &str) -> Result<()> {
    let parent = format!("{}/..", root);
    let expanded = canonicalize(&parent)?;
    let current_path = Path::new(&expanded);

    if is_git_managed(&current_path) {
        let target = format!("{}/.gitignore", parent);
        let target_path = Path::new(&target);

        if !target_path.exists() {
            create_with_template(ui, &target, GITIGNORE_TEMPLATE)?
        } else {
            let file = OpenOptions::new().read(true).append(true).open(target_path)?;

            let entries: Vec<String> = BufReader::new(&file)
                .lines()
                .map(|l| l.expect("Failed to parse line"))
                .collect();

            let mut appended = 0;

            for line in GITIGNORE_TEMPLATE.lines() {
                let s = line.to_string();

                if !entries.contains(&s) {
                    writeln!(&file, "{}", s)?;
                    appended += 1;
                }
            }

            ui.status(
                Status::Using,
                format!(
                    "existing file: {} ({} lines appended)",
                    &target,
                    appended
                ),
            )?;
        }
    }
    Ok(())
}

fn is_git_managed(path: &Path) -> bool {

    if path.join(".git").is_dir() {
        return true;
    }

    if let Some(parent) = path.parent() {
        return is_git_managed(&parent);
    }

    return false;
}

fn create_with_template(ui: &mut UI, location: &str, template: &str) -> Result<()> {
    let path = Path::new(&location);
    match path.exists() {
        false => {
            ui.status(Status::Creating, format!("file: {}", location))?;
            // If the directory doesn't exist we need to make it.
            if let Some(directory) = path.parent() {
                create_dir_all(directory)?;
            }
            // Create and then render the template with Handlebars
            File::create(path).and_then(
                |mut file| file.write(template.as_bytes()),
            )?;
        }
        true => {
            // If the user has already configured a file overwriting would be impolite.
            ui.status(
                Status::Using,
                format!("existing file: {}", location),
            )?;
        }
    };
    Ok(())
}
<commit_msg>Lint fixes for readme generation<commit_after>// Copyright (c) 2016 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::env;
use std::fs::create_dir_all;
use std::fs::{File, canonicalize, OpenOptions};
use std::io::{Write, BufRead, BufReader};
use std::path::Path;
use std::collections::HashMap;

use handlebars::Handlebars;
use hcore::package::PackageIdent;

use common::ui::{UI, Status};
use error::Result;

const DEFAULT_PLAN_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/default_template_plan.sh"
));
const FULL_PLAN_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/full_template_plan.sh"
));
const DEFAULT_TOML_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/template_default.toml"
));
const GITIGNORE_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/template_gitignore"
));
const README_TEMPLATE: &'static str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/static/template_README.md"
));

const DEFAULT_PKG_VERSION: &'static str = "0.1.0";

pub fn start(
    ui: &mut UI,
    origin: String,
    with_docs: bool,
    with_callbacks: bool,
    with_all: bool,
    scaffolding_ident: Option<PackageIdent>,
    maybe_name: Option<String>,
) -> Result<()> {
    ui.begin("Constructing a cozy habitat for your app...")?;
    ui.br()?;

    let (root, name) = match maybe_name {
        Some(name) => (name.clone(), name.clone()),
        // The name of the current working directory.
        None => {
            (
                "habitat".into(),
                canonicalize(".")
                    .ok()
                    .and_then(|path| {
                        path.components().last().and_then(|val| {
                            // Type gymnastics!
                            val.as_os_str().to_os_string().into_string().ok()
                        })
                    })
                    .unwrap_or("unnamed".into()),
            )
        }
    };

    // Build out the variables passed.
    let handlebars = Handlebars::new();
    let mut data = HashMap::new();
    data.insert("pkg_name".to_string(), name);
    data.insert("pkg_origin".to_string(), origin);
    data.insert("pkg_version".to_string(), DEFAULT_PKG_VERSION.to_string());

    let scaffold = match scaffolding_ident {
        Some(ident) => Some(data.insert(
            "scaffolding_ident".to_string(),
            ident.to_string(),
        )),
        None => None,
    };

    if with_callbacks {
        data.insert("with_callbacks".to_string(), "true".to_string());
    }
    if with_docs {
        data.insert("with_docs".to_string(), "true".to_string());
    }
    // Add all environment variables that start with "pkg_" as variables in
    // the template.
    for (key, value) in env::vars() {
        if key.starts_with("pkg_") {
            data.insert(key, value);
        }
    }

    // We want to render the configured variables.
    if with_all || scaffold.is_none() {
        let rendered_plan = handlebars.template_render(FULL_PLAN_TEMPLATE, &data)?;
        create_with_template(ui, &format!("{}/plan.sh", root), &rendered_plan)?;
    } else {
        let rendered_plan = handlebars.template_render(DEFAULT_PLAN_TEMPLATE, &data)?;
        create_with_template(ui, &format!("{}/plan.sh", root), &rendered_plan)?;
    }
    ui.para(
        "`plan.sh` is the foundation of your new habitat. It contains \
        metadata, dependencies, and tasks.",
    )?;
    let rendered_default_toml = handlebars.template_render(DEFAULT_TOML_TEMPLATE, &data)?;
    create_with_template(
        ui,
        &format!("{}/default.toml", root),
        &rendered_default_toml,
    )?;
    ui.para(
        "`default.toml` contains default values for `cfg` prefixed variables.",
    )?;

    let rendered_readme_md = handlebars.template_render(README_TEMPLATE, &data)?;
    create_with_template(ui, &format!("{}/README.md", root), &rendered_readme_md)?;
    ui.para(
        "`README.md` contains a basic README document which you should update.",
    )?;

    let config_path = format!("{}/config/", root);
    match Path::new(&config_path).exists() {
        true => {
            ui.status(
                Status::Using,
                format!("existing directory: {}", config_path),
            )?
        }
        false => {
            ui.status(
                Status::Creating,
                format!("directory: {}", config_path),
            )?;
            create_dir_all(&config_path)?;
        }
    };
    ui.para(
        "`/config/` contains configuration files for your app.",
    )?;

    let hooks_path = format!("{}/hooks/", root);
    match Path::new(&hooks_path).exists() {
        true => {
            ui.status(
                Status::Using,
                format!("existing directory: {}", hooks_path),
            )?
        }
        false => {
            ui.status(
                Status::Creating,
                format!("directory: {}", hooks_path),
            )?;
            create_dir_all(&hooks_path)?;
        }
    };
    ui.para(
        "`/hooks/` contains automation hooks into your habitat.",
    )?;

    ui.para(
        "For more information on any of the files: \
        https://www.habitat.sh/docs/reference/plan-syntax/",
    )?;

    render_ignorefile(ui, &root)?;

    ui.end("An abode for your code is initialized!")?;
    Ok(())
}

fn render_ignorefile(ui: &mut UI, root: &str) -> Result<()> {
    let parent = format!("{}/..", root);
    let expanded = canonicalize(&parent)?;
    let current_path = Path::new(&expanded);

    if is_git_managed(&current_path) {
        let target = format!("{}/.gitignore", parent);
        let target_path = Path::new(&target);

        if !target_path.exists() {
            create_with_template(ui, &target, GITIGNORE_TEMPLATE)?
        } else {
            let file = OpenOptions::new().read(true).append(true).open(target_path)?;

            let entries: Vec<String> = BufReader::new(&file)
                .lines()
                .map(|l| l.expect("Failed to parse line"))
                .collect();

            let mut appended = 0;

            for line in GITIGNORE_TEMPLATE.lines() {
                let s = line.to_string();

                if !entries.contains(&s) {
                    writeln!(&file, "{}", s)?;
                    appended += 1;
                }
            }

            ui.status(
                Status::Using,
                format!(
                    "existing file: {} ({} lines appended)",
                    &target,
                    appended
                ),
            )?;
        }
    }
    Ok(())
}

fn is_git_managed(path: &Path) -> bool {

    if path.join(".git").is_dir() {
        return true;
    }

    if let Some(parent) = path.parent() {
        return is_git_managed(&parent);
    }

    return false;
}

fn create_with_template(ui: &mut UI, location: &str, template: &str) -> Result<()> {
    let path = Path::new(&location);
    match path.exists() {
        false => {
            ui.status(Status::Creating, format!("file: {}", location))?;
            // If the directory doesn't exist we need to make it.
            if let Some(directory) = path.parent() {
                create_dir_all(directory)?;
            }
            // Create and then render the template with Handlebars
            File::create(path).and_then(
                |mut file| file.write(template.as_bytes()),
            )?;
        }
        true => {
            // If the user has already configured a file overwriting would be impolite.
            ui.status(
                Status::Using,
                format!("existing file: {}", location),
            )?;
        }
    };
    Ok(())
}
<|endoftext|>


<commit_before>extern crate openzwave_stateful as openzwave;
extern crate foxbox_taxonomy as taxonomy;
extern crate transformable_channels;
#[macro_use]
extern crate log;

use taxonomy::util::Id as TaxId;
use taxonomy::services::{ Setter, Getter, AdapterId, ServiceId, Service, Channel, ChannelKind };
use taxonomy::values::*;
use taxonomy::api::{ ResultMap, Error as TaxError, InternalError, User };
use taxonomy::adapter::{ AdapterManagerHandle, AdapterWatchGuard, WatchEvent };
use transformable_channels::mpsc::ExtSender;

use openzwave::{ InitOptions, ZWaveManager, ZWaveNotification };
use openzwave::{ CommandClass, ValueGenre, ValueType, ValueID };
use openzwave::{ Controller };

use std::error;
use std::fmt;
use std::thread;
use std::sync::mpsc;
use std::sync::{ Arc, Mutex, RwLock, Weak };
use std::collections::{ HashMap, HashSet };

pub use self::OpenzwaveAdapter as Adapter;

#[derive(Debug)]
pub enum Error {
    TaxonomyError(TaxError),
    OpenzwaveError(openzwave::Error),
    UnknownError
}

impl From<TaxError> for Error {
    fn from(err: TaxError) -> Self {
        Error::TaxonomyError(err)
    }
}

impl From<()> for Error {
    fn from(_: ()) -> Self {
        Error::UnknownError
    }
}

impl From<openzwave::Error> for Error {
    fn from(error: openzwave::Error) -> Self {
        Error::OpenzwaveError(error)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::TaxonomyError(ref err)  => write!(f, "{}: {}", error::Error::description(self), err),
            Error::OpenzwaveError(ref err) => write!(f, "{}: {}", error::Error::description(self), err),
            Error::UnknownError => write!(f, "{}", error::Error::description(self)),
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::TaxonomyError(_) => "Taxonomy Error",
            Error::OpenzwaveError(_) => "Openzwave Error",
            Error::UnknownError => "Unknown error",
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::TaxonomyError(ref err) => Some(err),
            Error::OpenzwaveError(ref err) => Some(err),
            Error::UnknownError => None,
        }
    }
}

#[derive(Debug, Clone)]
struct IdMap<Kind, Type> {
    map: Arc<RwLock<Vec<(TaxId<Kind>, Type)>>>
}

impl<Kind, Type> IdMap<Kind, Type> where Type: Eq + Clone, Kind: Clone {
    fn new() -> Self {
        IdMap {
            map: Arc::new(RwLock::new(Vec::new()))
        }
    }

    fn push(&mut self, id: TaxId<Kind>, ozw_object: Type) -> Result<(), ()> {
        let mut guard = try!(self.map.write().or(Err(())));
        guard.push((id, ozw_object));
        Ok(())
    }

    fn find_tax_id_from_ozw(&self, needle: &Type) -> Result<Option<TaxId<Kind>>, ()> {
        let guard = try!(self.map.read().or(Err(())));
        let find_result = guard.iter().find(|&&(_, ref controller)| controller == needle);
        Ok(find_result.map(|&(ref id, _)| id.clone()))
    }

    fn find_ozw_from_tax_id(&self, needle: &TaxId<Kind>) -> Result<Option<Type>, ()> {
        let guard = try!(self.map.read().or(Err(())));
        let find_result = guard.iter().find(|&&(ref id, _)| id == needle);
        Ok(find_result.map(|&(_, ref ozw_object)| ozw_object.clone()))
    }
}

type SyncExtSender = Mutex<Box<ExtSender<WatchEvent>>>;
type WatchersMap = HashMap<usize, Arc<SyncExtSender>>;
struct Watchers {
    current_index: usize,
    map: Arc<Mutex<WatchersMap>>,
    getter_map: HashMap<TaxId<Getter>, Vec<Weak<SyncExtSender>>>,
}

impl Watchers {
    fn new() -> Self {
        Watchers {
            current_index: 0,
            map: Arc::new(Mutex::new(HashMap::new())),
            getter_map: HashMap::new(),
        }
    }

    fn push(&mut self, tax_id: TaxId<Getter>, watcher: Arc<SyncExtSender>) -> WatcherGuard {
        let index = self.current_index;
        self.current_index += 1;
        {
            let mut map = self.map.lock().unwrap();
            map.insert(index, watcher.clone());
        }

        let entry = self.getter_map.entry(tax_id).or_insert(Vec::new());
        entry.push(Arc::downgrade(&watcher));

        WatcherGuard {
            key: index,
            map: self.map.clone()
        }
    }

    fn get(&self, index: usize) -> Option<Arc<SyncExtSender>> {
        let map = self.map.lock().unwrap();
        map.get(&index).cloned()
    }

    fn get_from_tax_id(&self, tax_id: &TaxId<Getter>) -> Option<Vec<Arc<SyncExtSender>>> {
        self.getter_map.get(tax_id).and_then(|vec| {
            let vec: Vec<_> = vec.iter().filter_map(|weak_sender| weak_sender.upgrade()).collect();
            if vec.len() == 0 { None } else { Some(vec) }
        })
    }
}

fn kind_from_value(value: ValueID) -> Option<ChannelKind> {
    value.get_command_class().map(|cc| match cc {
        CommandClass::SensorBinary => ChannelKind::OpenClosed,
        _ => ChannelKind::Ready // TODO
    })
}

fn to_open_closed(value: &ValueID) -> Option<Value> {
    debug_assert_eq!(value.get_type(), ValueType::ValueType_Bool);

    value.as_bool().ok().map(|val| {
        Value::OpenClosed(
            if val { OpenClosed::Open } else { OpenClosed::Closed }
        )
    })
}

struct WatcherGuard {
    key: usize,
    map: Arc<Mutex<WatchersMap>>,
}

impl Drop for WatcherGuard {
    fn drop(&mut self) {
        let mut map = self.map.lock().unwrap();
        map.remove(&self.key);
    }
}

impl AdapterWatchGuard for WatcherGuard {}

pub struct OpenzwaveAdapter {
    id: TaxId<AdapterId>,
    name: String,
    vendor: String,
    version: [u32; 4],
    ozw: ZWaveManager,
    controller_map: IdMap<ServiceId, Controller>,
    getter_map: IdMap<Getter, ValueID>,
    setter_map: IdMap<Setter, ValueID>,
    watchers: Arc<Mutex<Watchers>>,
}

impl OpenzwaveAdapter {
    pub fn init<T: AdapterManagerHandle + Send + Sync + 'static> (box_manager: &Arc<T>) -> Result<(), Error> {
        let options = InitOptions {
            device: None, // TODO we should expose this as a Value
            config_path: "./config/openzwave/".to_string(),
            user_path: "./config/openzwave/".to_string(),
        };

        let (ozw, rx) = try!(match openzwave::init(&options) {
            Err(openzwave::Error::NoDeviceFound) => {
                // early return: we should not impair foxbox startup for this error.
                // TODO concept of FatalError vs IgnoreableError
                error!("No ZWave device has been found.");
                return Ok(());
            }
            result => result
        });

        let name = String::from("OpenZwave Adapter");
        let adapter = Arc::new(OpenzwaveAdapter {
            id: TaxId::new(&name),
            name: name,
            vendor: String::from("Mozilla"),
            version: [1, 0, 0, 0],
            ozw: ozw,
            controller_map: IdMap::new(),
            getter_map: IdMap::new(),
            setter_map: IdMap::new(),
            watchers: Arc::new(Mutex::new(Watchers::new())),
        });

        adapter.spawn_notification_thread(rx, box_manager);
        try!(box_manager.add_adapter(adapter));

        info!("Started Openzwave adapter.");

        Ok(())
    }

    fn spawn_notification_thread<T: AdapterManagerHandle + Send + Sync + 'static>(&self, rx: mpsc::Receiver<ZWaveNotification>, box_manager: &Arc<T>) {
        let adapter_id = self.id.clone();
        let box_manager = box_manager.clone();
        let mut controller_map = self.controller_map.clone();
        let mut getter_map = self.getter_map.clone();
        let mut setter_map = self.setter_map.clone();
        let watchers = self.watchers.clone();

        thread::spawn(move || {
            for notification in rx {
                match notification {
                    ZWaveNotification::ControllerReady(controller) => {
                        let service = format!("OpenZWave/{}", controller.get_home_id());
                        let service_id = TaxId::new(&service);
                        controller_map.push(service_id.clone(), controller);

                        box_manager.add_service(Service::empty(service_id.clone(), adapter_id.clone()));
                    }
                    ZWaveNotification::NodeNew(node)               => {}
                    ZWaveNotification::NodeAdded(node)             => {}
                    ZWaveNotification::NodeRemoved(node)           => {}
                    ZWaveNotification::ValueAdded(value)           => {
                        if value.get_genre() != ValueGenre::ValueGenre_User { continue }

                        let value_id = format!("OpenZWave/{} ({})", value.get_id(), value.get_label());

                        let controller_id = controller_map.find_tax_id_from_ozw(&value.get_controller()).unwrap();
                        if controller_id.is_none() { continue }
                        let controller_id = controller_id.unwrap();

                        let has_getter = !value.is_write_only();
                        let has_setter = !value.is_read_only();

                        let kind = kind_from_value(value);
                        if kind.is_none() { continue }
                        let kind = kind.unwrap();

                        if has_getter {
                            let getter_id = TaxId::new(&value_id);
                            getter_map.push(getter_id.clone(), value);
                            box_manager.add_getter(Channel {
                                id: getter_id.clone(),
                                service: controller_id.clone(),
                                adapter: adapter_id.clone(),
                                last_seen: None,
                                tags: HashSet::new(),
                                mechanism: Getter {
                                    kind: kind.clone(),
                                    updated: None
                                }
                            });
                        }

                        if has_setter {
                            let setter_id = TaxId::new(&value_id);
                            setter_map.push(setter_id.clone(), value);
                            box_manager.add_setter(Channel {
                                id: setter_id.clone(),
                                service: controller_id.clone(),
                                adapter: adapter_id.clone(),
                                last_seen: None,
                                tags: HashSet::new(),
                                mechanism: Setter {
                                    kind: kind,
                                    updated: None
                                }
                            });
                        }
                    }
                    ZWaveNotification::ValueChanged(value)         => {
                        match value.get_type() {
                            ValueType::ValueType_Bool => {},
                            _ => continue // ignore non-bool vals for now
                        };

                        let tax_id = match getter_map.find_tax_id_from_ozw(&value) {
                            Ok(Some(tax_id)) => tax_id,
                            _ => continue
                        };

                        let watchers = watchers.lock().unwrap();

                        let watchers = match watchers.get_from_tax_id(&tax_id) {
                            Some(watchers) => watchers,
                            _ => continue
                        };

                        for sender in &watchers {
                            let sender = sender.lock().unwrap();
                            if let Some(value) = to_open_closed(&value) {
                                sender.send(
                                    WatchEvent::Enter { id: tax_id.clone(), value: value }
                                );
                            }
                        }
                    }
                    ZWaveNotification::ValueRemoved(value)         => {}
                    ZWaveNotification::Generic(string)             => {}
                    other => { warn!("Notification not handled {:?}", other)}
                }
            }
        });
    }
}

impl taxonomy::adapter::Adapter for OpenzwaveAdapter {
    fn id(&self) -> TaxId<AdapterId> {
        self.id.clone()
    }

    fn name(&self) -> &str {
        &self.name
    }

    fn vendor(&self) -> &str {
        &self.vendor
    }

    fn version(&self) -> &[u32; 4] {
        &self.version
    }

    fn fetch_values(&self, mut set: Vec<TaxId<Getter>>, _: User) -> ResultMap<TaxId<Getter>, Option<Value>, TaxError> {
        set.drain(..).map(|id| {
            let ozw_value: Option<ValueID> = self.getter_map.find_ozw_from_tax_id(&id).unwrap(); //FIXME no unwrap

            let ozw_value: Option<Option<Value>> = ozw_value.map(|ozw_value: ValueID| {
                if !ozw_value.is_set() { return None }

                let result: Option<Value> = match ozw_value.get_type() {
                    ValueType::ValueType_Bool => to_open_closed(&ozw_value),
                    _ => Some(Value::Unit)
                };
                result
            });
            let value_result: Result<Option<Value>, TaxError> = ozw_value.ok_or(TaxError::InternalError(InternalError::NoSuchGetter(id.clone())));
            (id, value_result)
        }).collect()
    }

    fn send_values(&self, values: HashMap<TaxId<Setter>, Value>, _: User) -> ResultMap<TaxId<Setter>, (), TaxError> {
        unimplemented!()
    }

    fn register_watch(&self, mut values: Vec<(TaxId<Getter>, Option<Range>)>, sender: Box<ExtSender<WatchEvent>>) -> ResultMap<TaxId<Getter>, Box<AdapterWatchGuard>, TaxError> {
        let sender = Arc::new(Mutex::new(sender)); // Mutex is necessary because cb is not Sync.
        values.drain(..).map(|(id, _)| {
            let watch_guard = {
                let mut watchers = self.watchers.lock().unwrap();
                watchers.push(id.clone(), sender.clone())
            };
            let value_result: Result<Box<AdapterWatchGuard>, TaxError> = Ok(Box::new(watch_guard));

            // if there is a set value already, let's send it.
            let ozw_value: Option<ValueID> = self.getter_map.find_ozw_from_tax_id(&id).unwrap(); // FIXME no unwrap
            if let Some(value) = ozw_value {
                if value.is_set() && value.get_type() == ValueType::ValueType_Bool {
                    if let Some(value) = to_open_closed(&value) {
                        let sender = sender.lock().unwrap();
                        sender.send(
                            WatchEvent::Enter { id: id.clone(), value: value }
                        );
                    }
                }
            }

            (id, value_result)
        }).collect()
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
    }
}

<commit_msg>[fxbox/openzwave-adapter] use references instead of owned objects<commit_after>extern crate openzwave_stateful as openzwave;
extern crate foxbox_taxonomy as taxonomy;
extern crate transformable_channels;
#[macro_use]
extern crate log;

use taxonomy::util::Id as TaxId;
use taxonomy::services::{ Setter, Getter, AdapterId, ServiceId, Service, Channel, ChannelKind };
use taxonomy::values::*;
use taxonomy::api::{ ResultMap, Error as TaxError, InternalError, User };
use taxonomy::adapter::{ AdapterManagerHandle, AdapterWatchGuard, WatchEvent };
use transformable_channels::mpsc::ExtSender;

use openzwave::{ InitOptions, ZWaveManager, ZWaveNotification };
use openzwave::{ CommandClass, ValueGenre, ValueType, ValueID };
use openzwave::{ Controller };

use std::error;
use std::fmt;
use std::thread;
use std::sync::mpsc;
use std::sync::{ Arc, Mutex, RwLock, Weak };
use std::collections::{ HashMap, HashSet };

pub use self::OpenzwaveAdapter as Adapter;

#[derive(Debug)]
pub enum Error {
    TaxonomyError(TaxError),
    OpenzwaveError(openzwave::Error),
    UnknownError
}

impl From<TaxError> for Error {
    fn from(err: TaxError) -> Self {
        Error::TaxonomyError(err)
    }
}

impl From<()> for Error {
    fn from(_: ()) -> Self {
        Error::UnknownError
    }
}

impl From<openzwave::Error> for Error {
    fn from(error: openzwave::Error) -> Self {
        Error::OpenzwaveError(error)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::TaxonomyError(ref err)  => write!(f, "{}: {}", error::Error::description(self), err),
            Error::OpenzwaveError(ref err) => write!(f, "{}: {}", error::Error::description(self), err),
            Error::UnknownError => write!(f, "{}", error::Error::description(self)),
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::TaxonomyError(_) => "Taxonomy Error",
            Error::OpenzwaveError(_) => "Openzwave Error",
            Error::UnknownError => "Unknown error",
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::TaxonomyError(ref err) => Some(err),
            Error::OpenzwaveError(ref err) => Some(err),
            Error::UnknownError => None,
        }
    }
}

#[derive(Debug, Clone)]
struct IdMap<Kind, Type> {
    map: Arc<RwLock<Vec<(TaxId<Kind>, Type)>>>
}

impl<Kind, Type> IdMap<Kind, Type> where Type: Eq + Clone, Kind: Clone {
    fn new() -> Self {
        IdMap {
            map: Arc::new(RwLock::new(Vec::new()))
        }
    }

    fn push(&mut self, id: TaxId<Kind>, ozw_object: Type) -> Result<(), ()> {
        let mut guard = try!(self.map.write().or(Err(())));
        guard.push((id, ozw_object));
        Ok(())
    }

    fn find_tax_id_from_ozw(&self, needle: &Type) -> Result<Option<TaxId<Kind>>, ()> {
        let guard = try!(self.map.read().or(Err(())));
        let find_result = guard.iter().find(|&&(_, ref controller)| controller == needle);
        Ok(find_result.map(|&(ref id, _)| id.clone()))
    }

    fn find_ozw_from_tax_id(&self, needle: &TaxId<Kind>) -> Result<Option<Type>, ()> {
        let guard = try!(self.map.read().or(Err(())));
        let find_result = guard.iter().find(|&&(ref id, _)| id == needle);
        Ok(find_result.map(|&(_, ref ozw_object)| ozw_object.clone()))
    }
}

type SyncExtSender = Mutex<Box<ExtSender<WatchEvent>>>;
type WatchersMap = HashMap<usize, Arc<SyncExtSender>>;
struct Watchers {
    current_index: usize,
    map: Arc<Mutex<WatchersMap>>,
    getter_map: HashMap<TaxId<Getter>, Vec<Weak<SyncExtSender>>>,
}

impl Watchers {
    fn new() -> Self {
        Watchers {
            current_index: 0,
            map: Arc::new(Mutex::new(HashMap::new())),
            getter_map: HashMap::new(),
        }
    }

    fn push(&mut self, tax_id: TaxId<Getter>, watcher: Arc<SyncExtSender>) -> WatcherGuard {
        let index = self.current_index;
        self.current_index += 1;
        {
            let mut map = self.map.lock().unwrap();
            map.insert(index, watcher.clone());
        }

        let entry = self.getter_map.entry(tax_id).or_insert(Vec::new());
        entry.push(Arc::downgrade(&watcher));

        WatcherGuard {
            key: index,
            map: self.map.clone()
        }
    }

    fn get(&self, index: usize) -> Option<Arc<SyncExtSender>> {
        let map = self.map.lock().unwrap();
        map.get(&index).cloned()
    }

    fn get_from_tax_id(&self, tax_id: &TaxId<Getter>) -> Option<Vec<Arc<SyncExtSender>>> {
        self.getter_map.get(tax_id).and_then(|vec| {
            let vec: Vec<_> = vec.iter().filter_map(|weak_sender| weak_sender.upgrade()).collect();
            if vec.len() == 0 { None } else { Some(vec) }
        })
    }
}

fn kind_from_value(value: ValueID) -> Option<ChannelKind> {
    value.get_command_class().map(|cc| match cc {
        CommandClass::SensorBinary => ChannelKind::OpenClosed,
        _ => ChannelKind::Ready // TODO
    })
}

fn to_open_closed(value: &ValueID) -> Option<Value> {
    debug_assert_eq!(value.get_type(), ValueType::ValueType_Bool);

    value.as_bool().ok().map(|val| {
        Value::OpenClosed(
            if val { OpenClosed::Open } else { OpenClosed::Closed }
        )
    })
}

struct WatcherGuard {
    key: usize,
    map: Arc<Mutex<WatchersMap>>,
}

impl Drop for WatcherGuard {
    fn drop(&mut self) {
        let mut map = self.map.lock().unwrap();
        map.remove(&self.key);
    }
}

impl AdapterWatchGuard for WatcherGuard {}

pub struct OpenzwaveAdapter {
    id: TaxId<AdapterId>,
    name: String,
    vendor: String,
    version: [u32; 4],
    ozw: ZWaveManager,
    controller_map: IdMap<ServiceId, Controller>,
    getter_map: IdMap<Getter, ValueID>,
    setter_map: IdMap<Setter, ValueID>,
    watchers: Arc<Mutex<Watchers>>,
}

impl OpenzwaveAdapter {
    pub fn init<T: AdapterManagerHandle + Send + Sync + 'static> (box_manager: &Arc<T>) -> Result<(), Error> {
        let options = InitOptions {
            device: None, // TODO we should expose this as a Value
            config_path: "./config/openzwave/",
            user_path: "./config/openzwave/",
        };

        let (ozw, rx) = try!(match openzwave::init(&options) {
            Err(openzwave::Error::NoDeviceFound) => {
                // early return: we should not impair foxbox startup for this error.
                // TODO concept of FatalError vs IgnoreableError
                error!("No ZWave device has been found.");
                return Ok(());
            }
            result => result
        });

        let name = String::from("OpenZwave Adapter");
        let adapter = Arc::new(OpenzwaveAdapter {
            id: TaxId::new(&name),
            name: name,
            vendor: String::from("Mozilla"),
            version: [1, 0, 0, 0],
            ozw: ozw,
            controller_map: IdMap::new(),
            getter_map: IdMap::new(),
            setter_map: IdMap::new(),
            watchers: Arc::new(Mutex::new(Watchers::new())),
        });

        adapter.spawn_notification_thread(rx, box_manager);
        try!(box_manager.add_adapter(adapter));

        info!("Started Openzwave adapter.");

        Ok(())
    }

    fn spawn_notification_thread<T: AdapterManagerHandle + Send + Sync + 'static>(&self, rx: mpsc::Receiver<ZWaveNotification>, box_manager: &Arc<T>) {
        let adapter_id = self.id.clone();
        let box_manager = box_manager.clone();
        let mut controller_map = self.controller_map.clone();
        let mut getter_map = self.getter_map.clone();
        let mut setter_map = self.setter_map.clone();
        let watchers = self.watchers.clone();

        thread::spawn(move || {
            for notification in rx {
                match notification {
                    ZWaveNotification::ControllerReady(controller) => {
                        let service = format!("OpenZWave/{}", controller.get_home_id());
                        let service_id = TaxId::new(&service);
                        controller_map.push(service_id.clone(), controller);

                        box_manager.add_service(Service::empty(service_id.clone(), adapter_id.clone()));
                    }
                    ZWaveNotification::NodeNew(node)               => {}
                    ZWaveNotification::NodeAdded(node)             => {}
                    ZWaveNotification::NodeRemoved(node)           => {}
                    ZWaveNotification::ValueAdded(value)           => {
                        if value.get_genre() != ValueGenre::ValueGenre_User { continue }

                        let value_id = format!("OpenZWave/{} ({})", value.get_id(), value.get_label());

                        let controller_id = controller_map.find_tax_id_from_ozw(&value.get_controller()).unwrap();
                        if controller_id.is_none() { continue }
                        let controller_id = controller_id.unwrap();

                        let has_getter = !value.is_write_only();
                        let has_setter = !value.is_read_only();

                        let kind = kind_from_value(value);
                        if kind.is_none() { continue }
                        let kind = kind.unwrap();

                        if has_getter {
                            let getter_id = TaxId::new(&value_id);
                            getter_map.push(getter_id.clone(), value);
                            box_manager.add_getter(Channel {
                                id: getter_id.clone(),
                                service: controller_id.clone(),
                                adapter: adapter_id.clone(),
                                last_seen: None,
                                tags: HashSet::new(),
                                mechanism: Getter {
                                    kind: kind.clone(),
                                    updated: None
                                }
                            });
                        }

                        if has_setter {
                            let setter_id = TaxId::new(&value_id);
                            setter_map.push(setter_id.clone(), value);
                            box_manager.add_setter(Channel {
                                id: setter_id.clone(),
                                service: controller_id.clone(),
                                adapter: adapter_id.clone(),
                                last_seen: None,
                                tags: HashSet::new(),
                                mechanism: Setter {
                                    kind: kind,
                                    updated: None
                                }
                            });
                        }
                    }
                    ZWaveNotification::ValueChanged(value)         => {
                        match value.get_type() {
                            ValueType::ValueType_Bool => {},
                            _ => continue // ignore non-bool vals for now
                        };

                        let tax_id = match getter_map.find_tax_id_from_ozw(&value) {
                            Ok(Some(tax_id)) => tax_id,
                            _ => continue
                        };

                        let watchers = watchers.lock().unwrap();

                        let watchers = match watchers.get_from_tax_id(&tax_id) {
                            Some(watchers) => watchers,
                            _ => continue
                        };

                        for sender in &watchers {
                            let sender = sender.lock().unwrap();
                            if let Some(value) = to_open_closed(&value) {
                                sender.send(
                                    WatchEvent::Enter { id: tax_id.clone(), value: value }
                                );
                            }
                        }
                    }
                    ZWaveNotification::ValueRemoved(value)         => {}
                    ZWaveNotification::Generic(string)             => {}
                    other => { warn!("Notification not handled {:?}", other)}
                }
            }
        });
    }
}

impl taxonomy::adapter::Adapter for OpenzwaveAdapter {
    fn id(&self) -> TaxId<AdapterId> {
        self.id.clone()
    }

    fn name(&self) -> &str {
        &self.name
    }

    fn vendor(&self) -> &str {
        &self.vendor
    }

    fn version(&self) -> &[u32; 4] {
        &self.version
    }

    fn fetch_values(&self, mut set: Vec<TaxId<Getter>>, _: User) -> ResultMap<TaxId<Getter>, Option<Value>, TaxError> {
        set.drain(..).map(|id| {
            let ozw_value: Option<ValueID> = self.getter_map.find_ozw_from_tax_id(&id).unwrap(); //FIXME no unwrap

            let ozw_value: Option<Option<Value>> = ozw_value.map(|ozw_value: ValueID| {
                if !ozw_value.is_set() { return None }

                let result: Option<Value> = match ozw_value.get_type() {
                    ValueType::ValueType_Bool => to_open_closed(&ozw_value),
                    _ => Some(Value::Unit)
                };
                result
            });
            let value_result: Result<Option<Value>, TaxError> = ozw_value.ok_or(TaxError::InternalError(InternalError::NoSuchGetter(id.clone())));
            (id, value_result)
        }).collect()
    }

    fn send_values(&self, values: HashMap<TaxId<Setter>, Value>, _: User) -> ResultMap<TaxId<Setter>, (), TaxError> {
        unimplemented!()
    }

    fn register_watch(&self, mut values: Vec<(TaxId<Getter>, Option<Range>)>, sender: Box<ExtSender<WatchEvent>>) -> ResultMap<TaxId<Getter>, Box<AdapterWatchGuard>, TaxError> {
        let sender = Arc::new(Mutex::new(sender)); // Mutex is necessary because cb is not Sync.
        values.drain(..).map(|(id, _)| {
            let watch_guard = {
                let mut watchers = self.watchers.lock().unwrap();
                watchers.push(id.clone(), sender.clone())
            };
            let value_result: Result<Box<AdapterWatchGuard>, TaxError> = Ok(Box::new(watch_guard));

            // if there is a set value already, let's send it.
            let ozw_value: Option<ValueID> = self.getter_map.find_ozw_from_tax_id(&id).unwrap(); // FIXME no unwrap
            if let Some(value) = ozw_value {
                if value.is_set() && value.get_type() == ValueType::ValueType_Bool {
                    if let Some(value) = to_open_closed(&value) {
                        let sender = sender.lock().unwrap();
                        sender.send(
                            WatchEvent::Enter { id: id.clone(), value: value }
                        );
                    }
                }
            }

            (id, value_result)
        }).collect()
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
    }
}

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use cssparser::ToCss;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener;
use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods;
use dom::bindings::codegen::Bindings::MediaQueryListBinding::{self, MediaQueryListMethods};
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::reflect_dom_object;
use dom::bindings::str::DOMString;
use dom::bindings::trace::JSTraceable;
use dom::bindings::weakref::{WeakRef, WeakRefVec};
use dom::document::Document;
use dom::eventtarget::EventTarget;
use euclid::scale_factor::ScaleFactor;
use js::jsapi::JSTracer;
use std::cell::Cell;
use std::rc::Rc;
use style;
use style::media_queries::{Device, MediaType};
use style_traits::{PagePx, ViewportPx};

pub enum MediaQueryListMatchState {
    Same(bool),
    Changed(bool),
}

#[dom_struct]
pub struct MediaQueryList {
    eventtarget: EventTarget,
    document: JS<Document>,
    media_query_list: style::media_queries::MediaQueryList,
    last_match_state: Cell<Option<bool>>
}

impl MediaQueryList {
    fn new_inherited(document: &Document,
                     media_query_list: style::media_queries::MediaQueryList) -> MediaQueryList {
        MediaQueryList {
            eventtarget: EventTarget::new_inherited(),
            document: JS::from_ref(document),
            media_query_list: media_query_list,
            last_match_state: Cell::new(None),
        }
    }

    pub fn new(document: &Document,
               media_query_list: style::media_queries::MediaQueryList) -> Root<MediaQueryList> {
        reflect_dom_object(box MediaQueryList::new_inherited(document, media_query_list),
                           document.window(),
                           MediaQueryListBinding::Wrap)
    }
}

impl MediaQueryList {
    fn evaluate_changes(&self) -> MediaQueryListMatchState {
        let matches = self.evaluate();

        let result = if let Some(old_matches) = self.last_match_state.get() {
            if old_matches == matches {
                MediaQueryListMatchState::Same(matches)
            } else {
                MediaQueryListMatchState::Changed(matches)
            }
        } else {
            MediaQueryListMatchState::Changed(matches)
        };

        self.last_match_state.set(Some(matches));
        result
    }

    pub fn evaluate(&self) -> bool {
        if let Some(window_size) = self.document.window().window_size() {
            let viewport_size = window_size.visible_viewport;
            // TODO: support real ViewportPx, including zoom level
            // This information seems not to be tracked currently, so we assume
            // ViewportPx == PagePx
            let page_to_viewport: ScaleFactor<f32, PagePx, ViewportPx> = ScaleFactor::new(1.0);
            let device = Device::new(MediaType::Screen, viewport_size * page_to_viewport);
            self.media_query_list.evaluate(&device)
        } else {
            false
        }
    }
}

impl MediaQueryListMethods for MediaQueryList {
    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-media
    fn Media(&self) -> DOMString {
        let mut s = String::new();
        self.media_query_list.to_css(&mut s).unwrap();
        DOMString::from_string(s)
    }

    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-matches
    fn Matches(&self) -> bool {
        match self.last_match_state.get() {
            None => self.evaluate(),
            Some(state) => state,
        }
    }

    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-addlistener
    fn AddListener(&self, listener: Option<Rc<EventListener>>) {
        self.upcast::<EventTarget>().AddEventListener(DOMString::from_string("change".to_owned()),
                                                      listener, false);
    }

    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-removelistener
    fn RemoveListener(&self, listener: Option<Rc<EventListener>>) {
        self.upcast::<EventTarget>().RemoveEventListener(DOMString::from_string("change".to_owned()),
                                                         listener, false);
    }

    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-onchange
    event_handler!(change, GetOnchange, SetOnchange);
}

#[derive(HeapSizeOf)]
pub struct WeakMediaQueryListVec {
    cell: DOMRefCell<WeakRefVec<MediaQueryList>>,
}

#[allow(unsafe_code)]
impl WeakMediaQueryListVec {
    /// Create a new vector of weak references to MediaQueryList
    pub fn new() -> Self {
        WeakMediaQueryListVec { cell: DOMRefCell::new(WeakRefVec::new()) }
    }

    pub fn push(&self, mql: &MediaQueryList) {
        self.cell.borrow_mut().push(WeakRef::new(mql));
    }

    /// Evaluate media query lists and report changes
    /// https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes
    pub fn evaluate_and_report_changes(&self) {
        for mql in self.cell.borrow().iter() {
            if let MediaQueryListMatchState::Changed(_) = mql.root().unwrap().evaluate_changes() {
                mql.root().unwrap().upcast::<EventTarget>().fire_event(atom!("change"));
            }
        }
    }
}

#[allow(unsafe_code)]
impl JSTraceable for WeakMediaQueryListVec {
    fn trace(&self, _: *mut JSTracer) {
        self.cell.borrow_mut().retain_alive()
    }
}
<commit_msg>Remove unneeded #allow(unsafe_code)<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use cssparser::ToCss;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener;
use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods;
use dom::bindings::codegen::Bindings::MediaQueryListBinding::{self, MediaQueryListMethods};
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::reflect_dom_object;
use dom::bindings::str::DOMString;
use dom::bindings::trace::JSTraceable;
use dom::bindings::weakref::{WeakRef, WeakRefVec};
use dom::document::Document;
use dom::eventtarget::EventTarget;
use euclid::scale_factor::ScaleFactor;
use js::jsapi::JSTracer;
use std::cell::Cell;
use std::rc::Rc;
use style;
use style::media_queries::{Device, MediaType};
use style_traits::{PagePx, ViewportPx};

pub enum MediaQueryListMatchState {
    Same(bool),
    Changed(bool),
}

#[dom_struct]
pub struct MediaQueryList {
    eventtarget: EventTarget,
    document: JS<Document>,
    media_query_list: style::media_queries::MediaQueryList,
    last_match_state: Cell<Option<bool>>
}

impl MediaQueryList {
    fn new_inherited(document: &Document,
                     media_query_list: style::media_queries::MediaQueryList) -> MediaQueryList {
        MediaQueryList {
            eventtarget: EventTarget::new_inherited(),
            document: JS::from_ref(document),
            media_query_list: media_query_list,
            last_match_state: Cell::new(None),
        }
    }

    pub fn new(document: &Document,
               media_query_list: style::media_queries::MediaQueryList) -> Root<MediaQueryList> {
        reflect_dom_object(box MediaQueryList::new_inherited(document, media_query_list),
                           document.window(),
                           MediaQueryListBinding::Wrap)
    }
}

impl MediaQueryList {
    fn evaluate_changes(&self) -> MediaQueryListMatchState {
        let matches = self.evaluate();

        let result = if let Some(old_matches) = self.last_match_state.get() {
            if old_matches == matches {
                MediaQueryListMatchState::Same(matches)
            } else {
                MediaQueryListMatchState::Changed(matches)
            }
        } else {
            MediaQueryListMatchState::Changed(matches)
        };

        self.last_match_state.set(Some(matches));
        result
    }

    pub fn evaluate(&self) -> bool {
        if let Some(window_size) = self.document.window().window_size() {
            let viewport_size = window_size.visible_viewport;
            // TODO: support real ViewportPx, including zoom level
            // This information seems not to be tracked currently, so we assume
            // ViewportPx == PagePx
            let page_to_viewport: ScaleFactor<f32, PagePx, ViewportPx> = ScaleFactor::new(1.0);
            let device = Device::new(MediaType::Screen, viewport_size * page_to_viewport);
            self.media_query_list.evaluate(&device)
        } else {
            false
        }
    }
}

impl MediaQueryListMethods for MediaQueryList {
    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-media
    fn Media(&self) -> DOMString {
        let mut s = String::new();
        self.media_query_list.to_css(&mut s).unwrap();
        DOMString::from_string(s)
    }

    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-matches
    fn Matches(&self) -> bool {
        match self.last_match_state.get() {
            None => self.evaluate(),
            Some(state) => state,
        }
    }

    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-addlistener
    fn AddListener(&self, listener: Option<Rc<EventListener>>) {
        self.upcast::<EventTarget>().AddEventListener(DOMString::from_string("change".to_owned()),
                                                      listener, false);
    }

    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-removelistener
    fn RemoveListener(&self, listener: Option<Rc<EventListener>>) {
        self.upcast::<EventTarget>().RemoveEventListener(DOMString::from_string("change".to_owned()),
                                                         listener, false);
    }

    // https://drafts.csswg.org/cssom-view/#dom-mediaquerylist-onchange
    event_handler!(change, GetOnchange, SetOnchange);
}

#[derive(HeapSizeOf)]
pub struct WeakMediaQueryListVec {
    cell: DOMRefCell<WeakRefVec<MediaQueryList>>,
}

impl WeakMediaQueryListVec {
    /// Create a new vector of weak references to MediaQueryList
    pub fn new() -> Self {
        WeakMediaQueryListVec { cell: DOMRefCell::new(WeakRefVec::new()) }
    }

    pub fn push(&self, mql: &MediaQueryList) {
        self.cell.borrow_mut().push(WeakRef::new(mql));
    }

    /// Evaluate media query lists and report changes
    /// https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes
    pub fn evaluate_and_report_changes(&self) {
        for mql in self.cell.borrow().iter() {
            if let MediaQueryListMatchState::Changed(_) = mql.root().unwrap().evaluate_changes() {
                mql.root().unwrap().upcast::<EventTarget>().fire_event(atom!("change"));
            }
        }
    }
}

impl JSTraceable for WeakMediaQueryListVec {
    fn trace(&self, _: *mut JSTracer) {
        self.cell.borrow_mut().retain_alive()
    }
}
<|endoftext|>