<commit_before>// Copyright (c) 2017 Jason White
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

use std::io;
use std::io::Write as IoWrite;
use std::fmt;
use std::fmt::Write as FmtWrite;
use std::time::Duration;
use std::path::PathBuf;
use std::process;
use std::collections::BTreeMap;
use std::ffi::OsString;

use tempfile::{NamedTempFile, TempPath};

use node::{Error, Task};
use util::{NeverAlwaysAuto, Counter};
use retry;

/// A task that executes a single command. A command is simply a process to be
/// spawned.
#[derive(Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Hash, Clone)]
pub struct Command {
    /// Process and arguments to spawn.
    args: Vec<String>,

    /// Optional working directory to spawn the process in. If `None`, uses the
    /// working directory of the parent process (i.e., the build process).
    cwd: Option<PathBuf>,

    /// Optional environment variables.
    env: Option<BTreeMap<String, String>>,

    /// Response file creation.
    ///
    /// If `Always`, creates a temporary response file with all the command line
    /// arguments and passes that as the first command line argument instead.
    /// This is useful for very long command lines that exceed operating system
    /// limits.
    #[serde(default)]
    response_file: NeverAlwaysAuto,

    /// Redirect standard output to a file instead.
    stdout: Option<PathBuf>,

    /// String to display when executing the task. If `None`, the command
    /// arguments are displayed in full instead.
    display: Option<String>,

    /// How much time to give the command to execute. If `None`, there is no
    /// time limit.
    timeout: Option<Duration>,

    /// Retry settings.
    #[serde(default)]
    retry: retry::Retry,
}

impl Command {
    #[cfg(test)]
    pub fn new(args: Vec<String>) -> Command {
        Command {
            args: args,
            cwd: None,
            env: None,
            stdout: None,
            display: None,
            timeout: None,
            retry: retry::Retry::new(),
        }
    }
}

impl Command {
    // Sets the working directory for the command.
    #[allow(dead_code)]
    pub fn cwd(mut self, path: PathBuf) -> Command {
        self.cwd = Some(path);
        self
    }

    // Sets the stdout file for the command.
    #[allow(dead_code)]
    pub fn stdout(mut self, path: PathBuf) -> Command {
        self.stdout = Some(path);
        self
    }

    // Sets the display string for the command.
    #[allow(dead_code)]
    pub fn display(mut self, display: String) -> Command {
        self.display = Some(display);
        self
    }

    // Sets the timeout for the command.
    #[allow(dead_code)]
    pub fn timeout(mut self, timeout: Duration) -> Command {
        self.timeout = Some(timeout);
        self
    }

    // Sets the retry configuration.
    #[allow(dead_code)]
    pub fn retry(mut self, retry: retry::Retry) -> Command {
        self.retry = retry;
        self
    }

    fn execute_impl(&self, log: &mut io::Write) -> Result<(), Error> {
        //writeln!(log, "Executing `{}`", self)?;

        assert!(self.args.len() > 0);

        // TODO:
        //  1. Spawn the process
        //  2. Capture stdout/stderr appropriately.
        //  4. Add implicit dependency detection framework and refactor all of
        //     the above to make it work.
        //  5. Implement timeouts.

        let mut cmd = process::Command::new(&self.args[0]);
        cmd.stdin(process::Stdio::null());

        // Generate a response file if necessary.
        let generate_response_file = match self.response_file {
            NeverAlwaysAuto::Never => false,
            NeverAlwaysAuto::Always => true,
            NeverAlwaysAuto::Auto => args_too_large(&self.args),
        };

        // The temporary response file needs to outlive the spawned process, so
        // it needs to be bound to a variable even if it is never used.
        let _rsp = if generate_response_file {
            let temp = response_file(&self.args[1..])?;

            let mut arg = OsString::new();
            arg.push("@");
            arg.push(&temp);
            cmd.arg(&arg);

            Some(temp)
        } else {
            cmd.args(&self.args[1..]);
            None
        };

        if let Some(ref cwd) = self.cwd {
            cmd.current_dir(cwd);
        }

        if let Some(ref env) = self.env {
            cmd.envs(env);
        }

        let output = cmd.output()?;

        // TODO: Interleave stdout and stderr.
        log.write(&output.stdout)?;
        log.write(&output.stderr)?;

        if output.status.success() {
            Ok(())
        } else {
            match output.status.code() {
                Some(code) => {
                    Err(io::Error::new(io::ErrorKind::Other,
                                       format!("Process exited with error code {}",
                                               code)))
                }
                None => {
                    Err(io::Error::new(
                            io::ErrorKind::Other, "Process terminated by signal"))
                }
            }
        }
    }
}

impl fmt::Display for Command {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(ref display) = self.display {
            write!(f, "{}", display)
        } else {
            let mut args = self.args.iter();

            if let Some(arg) = args.next() {
                write!(f, "{}", Arg::new(arg))?;
            }

            for arg in args {
                write!(f, " {}", Arg::new(arg))?;
            }

            Ok(())
        }
    }
}

impl fmt::Debug for Command {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

        write!(f, "\"")?;

        let mut args = self.args.iter();

        if let Some(arg) = args.next() {
            write!(f, "{}", Arg::new(arg))?;
        }

        for arg in args {
            write!(f, " {}", Arg::new(arg))?;
        }

        write!(f, "\"")?;

        Ok(())
    }
}

impl Task for Command {
    fn execute(&self, log: &mut io::Write) -> Result<(), Error> {
        self.retry
            .call(|| self.execute_impl(log), retry::progress_dummy)
    }
}

/// Helper type for formatting command line arguments.
struct Arg<'a> {
    arg: &'a str,
}

impl<'a> Arg<'a> {
    pub fn new(arg: &'a str) -> Arg<'a> {
        Arg { arg: arg }
    }

    /// Quotes the argument such that it is safe to pass to the shell.
    #[cfg(windows)]
    pub fn quote(&self, writer: &mut fmt::Write) -> fmt::Result {
        let quote = self.arg.chars().any(|c| c == ' ' || c == '\t') ||
                    self.arg.is_empty();

        if quote {
            writer.write_char('"')?;
        }

        let mut backslashes: usize = 0;

        for x in self.arg.chars() {
            if x == '\\' {
                backslashes += 1;
            } else {
                // Dump backslashes if we hit a quotation mark.
                if x == '"' {
                    // We need 2n+1 backslashes to escape a quote.
                    for _ in 0..(backslashes+1) {
                        writer.write_char('\\')?;
                    }
                }

                backslashes = 0;
            }

            writer.write_char(x)?;
        }

        if quote {
            // Escape any trailing backslashes.
            for _ in 0..backslashes {
                writer.write_char('\\')?;
            }

            writer.write_char('"')?;
        }

        Ok(())
    }
}

impl<'a> fmt::Display for Arg<'a> {
    /// Converts an argument such that it is safe to append to a command line
    /// string.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.quote(f)
    }
}

/// Writes the response file to a stream.
fn write_response_file<S, I>(args: I, writer: &mut io::Write) -> io::Result<()>
    where I: IntoIterator<Item = S>,
          S: AsRef<str>
{
    let mut iter = args.into_iter();

    // Write UTF-8 BOM. Some tools require this to properly decode it as UTF-8
    // instead of ASCII.
    writer.write(b"\xEF\xBB\xBF")?;

    if let Some(arg) = iter.next() {
        write!(writer, "{}", Arg::new(arg.as_ref())).unwrap();
    }

    for arg in iter {
        write!(writer, " {}", Arg::new(arg.as_ref())).unwrap();
    }

    // Some programs require a trailing new line (notably LIB.exe and LINK.exe).
    writer.write(b"\n")?;

    Ok(())
}

/// Generates a temporary response file for the given command line arguments.
fn response_file<S, I>(args: I) -> io::Result<TempPath>
    where I: IntoIterator<Item = S>,
          S: AsRef<str>
{
    let tempfile = NamedTempFile::new()?;

    {
        let mut writer = io::BufWriter::new(&tempfile);
        write_response_file(args, &mut writer)?;

        // Explicitly flush to catch any errors.
        writer.flush()?;
    }

    Ok(tempfile.into_temp_path())
}

/// Checks if the given command line arguments are too large and should instead
/// go into a response file. The entire list of arguments, including the program
/// name should be passed to this function.
#[cfg(windows)]
fn args_too_large<S, I>(args: I) -> bool
    where I: IntoIterator<Item = S>,
          S: AsRef<str>
{
    // The maximum length is 32768 characters, including the NULL terminator.
    let mut counter = Counter::new();

    let mut iter = args.into_iter();

    if let Some(arg) = iter.next() {
        write!(counter, "{}", Arg::new(arg.as_ref())).unwrap();
    }

    for arg in iter {
        write!(counter, " {}", Arg::new(arg.as_ref())).unwrap();
    }

    // Final NULL terminator.
    counter += 1;

    counter.count() > 32768
}

#[cfg(unix)]
fn args_too_large<S, I>(args: I) -> bool
    where I: IntoIterator<Item = S>,
          S: AsRef<str>
{
    let mut size: usize = 0;

    for arg in args.into_iter() {
        // +1 for the NULL terminator.
        size += arg.len() + 1;
    }

    // +1 for the final NULL terminator.
    size += 1;

    // Can't be larger than 128 kB.
    size > 0x20000
}

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

    #[test]
    #[cfg(windows)]
    fn test_arg_display() {
        assert_eq!(format!("{}", Arg::new("foo bar")), "\"foo bar\"");
        assert_eq!(format!("{}", Arg::new("foo\tbar")), "\"foo\tbar\"");
        assert_eq!(format!("{}", Arg::new("foobar")), "foobar");
        assert_eq!(format!("{}", Arg::new("\"foo bar\"")), "\"\\\"foo bar\\\"\"");
        assert_eq!(format!("{}", Arg::new(r"C:\foo\bar")), r"C:\foo\bar");
        assert_eq!(format!("{}", Arg::new(r"\\foo\bar")), r"\\foo\bar");
    }
}
<commit_msg>Add more docs on Command::response_file<commit_after>// Copyright (c) 2017 Jason White
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

use std::io;
use std::io::Write as IoWrite;
use std::fmt;
use std::fmt::Write as FmtWrite;
use std::time::Duration;
use std::path::PathBuf;
use std::process;
use std::collections::BTreeMap;
use std::ffi::OsString;

use tempfile::{NamedTempFile, TempPath};

use node::{Error, Task};
use util::{NeverAlwaysAuto, Counter};
use retry;

/// A task that executes a single command. A command is simply a process to be
/// spawned.
#[derive(Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Hash, Clone)]
pub struct Command {
    /// Process and arguments to spawn.
    args: Vec<String>,

    /// Optional working directory to spawn the process in. If `None`, uses the
    /// working directory of the parent process (i.e., the build process).
    cwd: Option<PathBuf>,

    /// Optional environment variables.
    env: Option<BTreeMap<String, String>>,

    /// Response file creation.
    ///
    /// If `Never`, never creates a response file. If the command line length
    /// exceeds the operating system limits, the command will fail.
    ///
    /// If `Always`, creates a temporary response file with all the command line
    /// arguments and passes that as the first command line argument instead.
    /// This is useful for very long command lines that exceed operating system
    /// limits.
    ///
    /// If `Auto`, creates a temporary response file only if the size of the
    /// arguments exceeds the operating system limits.
    #[serde(default)]
    response_file: NeverAlwaysAuto,

    /// Redirect standard output to a file instead.
    stdout: Option<PathBuf>,

    /// String to display when executing the task. If `None`, the command
    /// arguments are displayed in full instead.
    display: Option<String>,

    /// How much time to give the command to execute. If `None`, there is no
    /// time limit.
    timeout: Option<Duration>,

    /// Retry settings.
    #[serde(default)]
    retry: retry::Retry,
}

impl Command {
    #[cfg(test)]
    pub fn new(args: Vec<String>) -> Command {
        Command {
            args: args,
            cwd: None,
            env: None,
            response_file: NeverAlwaysAuto::default(),
            stdout: None,
            display: None,
            timeout: None,
            retry: retry::Retry::new(),
        }
    }
}

impl Command {
    // Sets the working directory for the command.
    #[allow(dead_code)]
    pub fn cwd(mut self, path: PathBuf) -> Command {
        self.cwd = Some(path);
        self
    }

    // Sets the stdout file for the command.
    #[allow(dead_code)]
    pub fn stdout(mut self, path: PathBuf) -> Command {
        self.stdout = Some(path);
        self
    }

    // Sets the display string for the command.
    #[allow(dead_code)]
    pub fn display(mut self, display: String) -> Command {
        self.display = Some(display);
        self
    }

    // Sets the timeout for the command.
    #[allow(dead_code)]
    pub fn timeout(mut self, timeout: Duration) -> Command {
        self.timeout = Some(timeout);
        self
    }

    // Sets the retry configuration.
    #[allow(dead_code)]
    pub fn retry(mut self, retry: retry::Retry) -> Command {
        self.retry = retry;
        self
    }

    fn execute_impl(&self, log: &mut io::Write) -> Result<(), Error> {
        //writeln!(log, "Executing `{}`", self)?;

        assert!(self.args.len() > 0);

        // TODO:
        //  1. Spawn the process
        //  2. Capture stdout/stderr appropriately.
        //  4. Add implicit dependency detection framework and refactor all of
        //     the above to make it work.
        //  5. Implement timeouts.

        let mut cmd = process::Command::new(&self.args[0]);
        cmd.stdin(process::Stdio::null());

        // Generate a response file if necessary.
        let generate_response_file = match self.response_file {
            NeverAlwaysAuto::Never => false,
            NeverAlwaysAuto::Always => true,
            NeverAlwaysAuto::Auto => args_too_large(&self.args),
        };

        // The temporary response file needs to outlive the spawned process, so
        // it needs to be bound to a variable even if it is never used.
        let _rsp = if generate_response_file {
            let temp = response_file(&self.args[1..])?;

            let mut arg = OsString::new();
            arg.push("@");
            arg.push(&temp);
            cmd.arg(&arg);

            Some(temp)
        } else {
            cmd.args(&self.args[1..]);
            None
        };

        if let Some(ref cwd) = self.cwd {
            cmd.current_dir(cwd);
        }

        if let Some(ref env) = self.env {
            cmd.envs(env);
        }

        let output = cmd.output()?;

        // TODO: Interleave stdout and stderr.
        log.write(&output.stdout)?;
        log.write(&output.stderr)?;

        if output.status.success() {
            Ok(())
        } else {
            match output.status.code() {
                Some(code) => {
                    Err(io::Error::new(io::ErrorKind::Other,
                                       format!("Process exited with error code {}",
                                               code)))
                }
                None => {
                    Err(io::Error::new(
                            io::ErrorKind::Other, "Process terminated by signal"))
                }
            }
        }
    }
}

impl fmt::Display for Command {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(ref display) = self.display {
            write!(f, "{}", display)
        } else {
            let mut args = self.args.iter();

            if let Some(arg) = args.next() {
                write!(f, "{}", Arg::new(arg))?;
            }

            for arg in args {
                write!(f, " {}", Arg::new(arg))?;
            }

            Ok(())
        }
    }
}

impl fmt::Debug for Command {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

        write!(f, "\"")?;

        let mut args = self.args.iter();

        if let Some(arg) = args.next() {
            write!(f, "{}", Arg::new(arg))?;
        }

        for arg in args {
            write!(f, " {}", Arg::new(arg))?;
        }

        write!(f, "\"")?;

        Ok(())
    }
}

impl Task for Command {
    fn execute(&self, log: &mut io::Write) -> Result<(), Error> {
        self.retry
            .call(|| self.execute_impl(log), retry::progress_dummy)
    }
}

/// Helper type for formatting command line arguments.
struct Arg<'a> {
    arg: &'a str,
}

impl<'a> Arg<'a> {
    pub fn new(arg: &'a str) -> Arg<'a> {
        Arg { arg: arg }
    }

    /// Quotes the argument such that it is safe to pass to the shell.
    #[cfg(windows)]
    pub fn quote(&self, writer: &mut fmt::Write) -> fmt::Result {
        let quote = self.arg.chars().any(|c| c == ' ' || c == '\t') ||
                    self.arg.is_empty();

        if quote {
            writer.write_char('"')?;
        }

        let mut backslashes: usize = 0;

        for x in self.arg.chars() {
            if x == '\\' {
                backslashes += 1;
            } else {
                // Dump backslashes if we hit a quotation mark.
                if x == '"' {
                    // We need 2n+1 backslashes to escape a quote.
                    for _ in 0..(backslashes+1) {
                        writer.write_char('\\')?;
                    }
                }

                backslashes = 0;
            }

            writer.write_char(x)?;
        }

        if quote {
            // Escape any trailing backslashes.
            for _ in 0..backslashes {
                writer.write_char('\\')?;
            }

            writer.write_char('"')?;
        }

        Ok(())
    }
}

impl<'a> fmt::Display for Arg<'a> {
    /// Converts an argument such that it is safe to append to a command line
    /// string.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.quote(f)
    }
}

/// Writes the response file to a stream.
fn write_response_file<S, I>(args: I, writer: &mut io::Write) -> io::Result<()>
    where I: IntoIterator<Item = S>,
          S: AsRef<str>
{
    let mut iter = args.into_iter();

    // Write UTF-8 BOM. Some tools require this to properly decode it as UTF-8
    // instead of ASCII.
    writer.write(b"\xEF\xBB\xBF")?;

    if let Some(arg) = iter.next() {
        write!(writer, "{}", Arg::new(arg.as_ref())).unwrap();
    }

    for arg in iter {
        write!(writer, " {}", Arg::new(arg.as_ref())).unwrap();
    }

    // Some programs require a trailing new line (notably LIB.exe and LINK.exe).
    writer.write(b"\n")?;

    Ok(())
}

/// Generates a temporary response file for the given command line arguments.
fn response_file<S, I>(args: I) -> io::Result<TempPath>
    where I: IntoIterator<Item = S>,
          S: AsRef<str>
{
    let tempfile = NamedTempFile::new()?;

    {
        let mut writer = io::BufWriter::new(&tempfile);
        write_response_file(args, &mut writer)?;

        // Explicitly flush to catch any errors.
        writer.flush()?;
    }

    Ok(tempfile.into_temp_path())
}

/// Checks if the given command line arguments are too large and should instead
/// go into a response file. The entire list of arguments, including the program
/// name should be passed to this function.
#[cfg(windows)]
fn args_too_large<S, I>(args: I) -> bool
    where I: IntoIterator<Item = S>,
          S: AsRef<str>
{
    // The maximum length is 32768 characters, including the NULL terminator.
    let mut counter = Counter::new();

    let mut iter = args.into_iter();

    if let Some(arg) = iter.next() {
        write!(counter, "{}", Arg::new(arg.as_ref())).unwrap();
    }

    for arg in iter {
        write!(counter, " {}", Arg::new(arg.as_ref())).unwrap();
    }

    // Final NULL terminator.
    counter += 1;

    counter.count() > 32768
}

#[cfg(unix)]
fn args_too_large<S, I>(args: I) -> bool
    where I: IntoIterator<Item = S>,
          S: AsRef<str>
{
    let mut size: usize = 0;

    for arg in args.into_iter() {
        // +1 for the NULL terminator.
        size += arg.len() + 1;
    }

    // +1 for the final NULL terminator.
    size += 1;

    // Can't be larger than 128 kB.
    size > 0x20000
}

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

    #[test]
    #[cfg(windows)]
    fn test_arg_display() {
        assert_eq!(format!("{}", Arg::new("foo bar")), "\"foo bar\"");
        assert_eq!(format!("{}", Arg::new("foo\tbar")), "\"foo\tbar\"");
        assert_eq!(format!("{}", Arg::new("foobar")), "foobar");
        assert_eq!(format!("{}", Arg::new("\"foo bar\"")), "\"\\\"foo bar\\\"\"");
        assert_eq!(format!("{}", Arg::new(r"C:\foo\bar")), r"C:\foo\bar");
        assert_eq!(format!("{}", Arg::new(r"\\foo\bar")), r"\\foo\bar");
    }
}
<|endoftext|>


<commit_before>use std::collections::HashSet;
use std::fs;
use std::path::Path;
use std::process::Command;

use core::{Package, PackageIdSpec};
use ops;
use util::CargoResult;

pub struct DocOptions<'a> {
    pub open_result: bool,
    pub compile_opts: ops::CompileOptions<'a>,
}

pub fn doc(manifest_path: &Path,
           options: &DocOptions) -> CargoResult<()> {
    let package = try!(Package::for_path(manifest_path, options.compile_opts.config));

    let mut lib_names = HashSet::new();
    let mut bin_names = HashSet::new();
    if options.compile_opts.spec.is_empty() {
        for target in package.targets().iter().filter(|t| t.documented()) {
            if target.is_lib() {
                assert!(lib_names.insert(target.crate_name()));
            } else {
                assert!(bin_names.insert(target.crate_name()));
            }
        }
        for bin in bin_names.iter() {
            if lib_names.contains(bin) {
                bail!("cannot document a package where a library and a binary \
                       have the same name. Consider renaming one or marking \
                       the target as `doc = false`")
            }
        }
    }

    try!(ops::compile(manifest_path, &options.compile_opts));

    if options.open_result {
        let name = if options.compile_opts.spec.len() > 1 {
            bail!("Passing multiple packages and `open` is not supported")
        } else if options.compile_opts.spec.len() == 1 {
            try!(PackageIdSpec::parse(&options.compile_opts.spec[0]))
                                             .name().replace("-", "_")
        } else {
            match lib_names.iter().chain(bin_names.iter()).nth(0) {
                Some(s) => s.to_string(),
                None => return Ok(())
            }
        };

        let target_dir = options.compile_opts.config.target_dir(&package);
        let path = target_dir.join("doc").join(&name).join("index.html");
        if fs::metadata(&path).is_ok() {
            open_docs(&path);
        }
    }

    Ok(())
}

#[cfg(not(any(target_os = "windows", target_os = "macos")))]
fn open_docs(path: &Path) {
    // trying xdg-open
    match Command::new("xdg-open").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };

    // trying gnome-open
    match Command::new("gnome-open").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };

    // trying kde-open
    match Command::new("kde-open").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };
}

#[cfg(target_os = "windows")]
fn open_docs(path: &Path) {
    match Command::new("cmd").arg("/C").arg("start").arg("").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };
}

#[cfg(target_os = "macos")]
fn open_docs(path: &Path) {
    match Command::new("open").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };
}
<commit_msg>Add $BROWSER as possible client<commit_after>use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;

use core::{Package, PackageIdSpec};
use ops;
use util::CargoResult;

pub struct DocOptions<'a> {
    pub open_result: bool,
    pub compile_opts: ops::CompileOptions<'a>,
}

pub fn doc(manifest_path: &Path,
           options: &DocOptions) -> CargoResult<()> {
    let package = try!(Package::for_path(manifest_path, options.compile_opts.config));

    let mut lib_names = HashSet::new();
    let mut bin_names = HashSet::new();
    if options.compile_opts.spec.is_empty() {
        for target in package.targets().iter().filter(|t| t.documented()) {
            if target.is_lib() {
                assert!(lib_names.insert(target.crate_name()));
            } else {
                assert!(bin_names.insert(target.crate_name()));
            }
        }
        for bin in bin_names.iter() {
            if lib_names.contains(bin) {
                bail!("cannot document a package where a library and a binary \
                       have the same name. Consider renaming one or marking \
                       the target as `doc = false`")
            }
        }
    }

    try!(ops::compile(manifest_path, &options.compile_opts));

    if options.open_result {
        let name = if options.compile_opts.spec.len() > 1 {
            bail!("Passing multiple packages and `open` is not supported")
        } else if options.compile_opts.spec.len() == 1 {
            try!(PackageIdSpec::parse(&options.compile_opts.spec[0]))
                                             .name().replace("-", "_")
        } else {
            match lib_names.iter().chain(bin_names.iter()).nth(0) {
                Some(s) => s.to_string(),
                None => return Ok(())
            }
        };

        let target_dir = options.compile_opts.config.target_dir(&package);
        let path = target_dir.join("doc").join(&name).join("index.html");
        if fs::metadata(&path).is_ok() {
            open_docs(&path);
        }
    }

    Ok(())
}

#[cfg(not(any(target_os = "windows", target_os = "macos")))]
fn open_docs(path: &Path) {
    // trying $BROWSER
    match env::var("BROWSER").map(|name| Command::new(name).arg(path).status()) {
        Ok(_) => return,
        Err(_) => ()
    }

    // trying xdg-open
    match Command::new("xdg-open").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };

    // trying gnome-open
    match Command::new("gnome-open").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };

    // trying kde-open
    match Command::new("kde-open").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };
}

#[cfg(target_os = "windows")]
fn open_docs(path: &Path) {
    match Command::new("cmd").arg("/C").arg("start").arg("").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };
}

#[cfg(target_os = "macos")]
fn open_docs(path: &Path) {
    match Command::new("open").arg(path).status() {
        Ok(_) => return,
        Err(_) => ()
    };
}
<|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/. */

//! Configuration options for a single run of the servo application. Created
//! from command line arguments.

use azure::azure_hl::{BackendType, CairoBackend, CoreGraphicsBackend};
use azure::azure_hl::{CoreGraphicsAcceleratedBackend, Direct2DBackend, SkiaBackend};

use std::float;
use std::result;
use std::uint;

pub struct Opts {
    urls: ~[~str],
    render_backend: BackendType,
    n_render_threads: uint,
    tile_size: uint,
    profiler_period: Option<float>,

    /// A scale factor to apply to tiles, to allow rendering tiles at higher resolutions for
    /// testing pan and zoom code.
    zoom: uint,
}

#[allow(non_implicitly_copyable_typarams)]
pub fn from_cmdline_args(args: &[~str]) -> Opts {
    use extra::getopts;

    let args = args.tail();

    let opts = ~[
        getopts::optopt("o"),  // output file
        getopts::optopt("r"),  // rendering backend
        getopts::optopt("s"),  // size of tiles
        getopts::optopt("t"),  // threads to render with
        getopts::optflagopt("p"),  // profiler flag and output interval
        getopts::optopt("z"),  // zoom level
    ];

    let opt_match = match getopts::getopts(args, opts) {
      result::Ok(m) => m,
      result::Err(f) => fail!(getopts::fail_str(copy f)),
    };

    let urls = if opt_match.free.is_empty() {
        fail!(~"servo asks that you provide 1 or more URLs")
    } else {
        copy opt_match.free
    };

    if getopts::opt_present(&opt_match, "o") {
        fail!(~"servo cannot treat 'o' option now.")
    }

    let render_backend = match getopts::opt_maybe_str(&opt_match, "r") {
        Some(backend_str) => {
            if backend_str == ~"direct2d" {
                Direct2DBackend
            } else if backend_str == ~"core-graphics" {
                CoreGraphicsBackend
            } else if backend_str == ~"core-graphics-accelerated" {
                CoreGraphicsAcceleratedBackend
            } else if backend_str == ~"cairo" {
                CairoBackend
            } else if backend_str == ~"skia" {
                SkiaBackend
            } else {
                fail!(~"unknown backend type")
            }
        }
        None => SkiaBackend
    };

    let tile_size: uint = match getopts::opt_maybe_str(&opt_match, "s") {
        Some(tile_size_str) => uint::from_str(tile_size_str).get(),
        None => 512,
    };

    let n_render_threads: uint = match getopts::opt_maybe_str(&opt_match, "t") {
        Some(n_render_threads_str) => uint::from_str(n_render_threads_str).get(),
        None => 1,      // FIXME: Number of cores.
    };

    let profiler_period: Option<float> =
        // if only flag is present, default to 5 second period
        match getopts::opt_default(&opt_match, "p", "5") {
        Some(period) => Some(float::from_str(period).get()),
        None => None,
    };

    let zoom: uint = match getopts::opt_maybe_str(&opt_match, "z") {
        Some(zoom_str) => uint::from_str(zoom_str).get(),
        None => 1,
    };

    Opts {
        urls: urls,
        render_backend: render_backend,
        n_render_threads: n_render_threads,
        tile_size: tile_size,
        profiler_period: profiler_period,
        zoom: zoom,
    }
}
<commit_msg>Remove unused -z opt<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/. */

//! Configuration options for a single run of the servo application. Created
//! from command line arguments.

use azure::azure_hl::{BackendType, CairoBackend, CoreGraphicsBackend};
use azure::azure_hl::{CoreGraphicsAcceleratedBackend, Direct2DBackend, SkiaBackend};

use std::float;
use std::result;
use std::uint;

pub struct Opts {
    urls: ~[~str],
    render_backend: BackendType,
    n_render_threads: uint,
    tile_size: uint,
    profiler_period: Option<float>,
}

#[allow(non_implicitly_copyable_typarams)]
pub fn from_cmdline_args(args: &[~str]) -> Opts {
    use extra::getopts;

    let args = args.tail();

    let opts = ~[
        getopts::optopt("o"),  // output file
        getopts::optopt("r"),  // rendering backend
        getopts::optopt("s"),  // size of tiles
        getopts::optopt("t"),  // threads to render with
        getopts::optflagopt("p"),  // profiler flag and output interval
    ];

    let opt_match = match getopts::getopts(args, opts) {
      result::Ok(m) => m,
      result::Err(f) => fail!(getopts::fail_str(copy f)),
    };

    let urls = if opt_match.free.is_empty() {
        fail!(~"servo asks that you provide 1 or more URLs")
    } else {
        copy opt_match.free
    };

    if getopts::opt_present(&opt_match, "o") {
        fail!(~"servo cannot treat 'o' option now.")
    }

    let render_backend = match getopts::opt_maybe_str(&opt_match, "r") {
        Some(backend_str) => {
            if backend_str == ~"direct2d" {
                Direct2DBackend
            } else if backend_str == ~"core-graphics" {
                CoreGraphicsBackend
            } else if backend_str == ~"core-graphics-accelerated" {
                CoreGraphicsAcceleratedBackend
            } else if backend_str == ~"cairo" {
                CairoBackend
            } else if backend_str == ~"skia" {
                SkiaBackend
            } else {
                fail!(~"unknown backend type")
            }
        }
        None => SkiaBackend
    };

    let tile_size: uint = match getopts::opt_maybe_str(&opt_match, "s") {
        Some(tile_size_str) => uint::from_str(tile_size_str).get(),
        None => 512,
    };

    let n_render_threads: uint = match getopts::opt_maybe_str(&opt_match, "t") {
        Some(n_render_threads_str) => uint::from_str(n_render_threads_str).get(),
        None => 1,      // FIXME: Number of cores.
    };

    let profiler_period: Option<float> =
        // if only flag is present, default to 5 second period
        match getopts::opt_default(&opt_match, "p", "5") {
        Some(period) => Some(float::from_str(period).get()),
        None => None,
    };

    Opts {
        urls: urls,
        render_backend: render_backend,
        n_render_threads: n_render_threads,
        tile_size: tile_size,
        profiler_period: profiler_period,
    }
}
<|endoftext|>


<commit_before>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

//! The Ref object is a helper over the link functionality, so one is able to create references to
//! files outside of the imag store.

use std::path::PathBuf;
use std::fs::File;
use std::fs::Permissions;

use libimagstore::store::Entry;
use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use libimagstore::store::Store;
use libimagerror::into::IntoError;

use toml::Value;
use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt;
use toml_query::insert::TomlValueInsertExt;

use error::RefErrorKind as REK;
use error::MapErrInto;
use result::Result;
use hasher::*;

pub trait Ref {

    /// Get the hash from the path of the ref
    fn get_path_hash(&self) -> Result<String>;

    /// Get the hash of the link target which is stored in the ref object
    fn get_stored_hash(&self) -> Result<String>;

    /// Get the hahs of the link target which is stored in the ref object, which is hashed with a
    /// custom Hasher instance.
    fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String>;

    /// Get the hash of the link target by reading the link target and hashing the contents
    fn get_current_hash(&self) -> Result<String>;

    /// Get the hash of the link target by reading the link target and hashing the contents with the
    /// custom hasher
    fn get_current_hash_with_hasher<H: Hasher>(&self, h: H) -> Result<String>;

    /// check whether the pointer the Ref represents still points to a file which exists
    fn fs_link_exists(&self) -> Result<bool>;

    /// Alias for `r.fs_link_exists() && r.deref().is_file()`
    fn is_ref_to_file(&self) -> Result<bool>;

    /// Alias for `r.fs_link_exists() && r.deref().is_dir()`
    fn is_ref_to_dir(&self) -> Result<bool>;

    /// Alias for `!Ref::fs_link_exists()`
    fn is_dangling(&self) -> Result<bool>;

    /// check whether the pointer the Ref represents is valid
    /// This includes:
    ///     - Hashsum of the file is still the same as stored in the Ref
    ///     - file permissions are still valid
    fn fs_link_valid(&self) -> Result<bool>;

    /// Check whether the file permissions of the referenced file are equal to the stored
    /// permissions
    fn fs_link_valid_permissions(&self) -> Result<bool>;

    /// Check whether the Hashsum of the referenced file is equal to the stored hashsum
    fn fs_link_valid_hash(&self) -> Result<bool>;

    /// Update the Ref by re-checking the file from FS
    /// This errors if the file is not present or cannot be read()
    fn update_ref(&mut self) -> Result<()>;

    /// Update the Ref by re-checking the file from FS using the passed Hasher instance
    /// This errors if the file is not present or cannot be read()
    fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()>;

    /// Get the path of the file which is reffered to by this Ref
    fn fs_file(&self) -> Result<PathBuf>;

    /// Re-find a referenced file
    ///
    /// This function tries to re-find a ref by searching all directories in `search_roots` recursively
    /// for a file which matches the hash of the Ref.
    ///
    /// If `search_roots` is `None`, it starts at the filesystem root `/`.
    ///
    /// If the target cannot be found, this yields a RefTargetDoesNotExist error kind.
    ///
    /// # Warning
    ///
    /// This option causes heavy I/O as it recursively searches the Filesystem.
    fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf>;

    /// See documentation of `Ref::refind()`
    fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, h: H)
        -> Result<PathBuf>;

    /// Get the permissions of the file which are present
    fn get_current_permissions(&self) -> Result<Permissions>;
}

impl Ref for Entry {

    /// Get the hash from the path of the ref
    fn get_path_hash(&self) -> Result<String> {
        self.get_location()
            .clone()
            .into_pathbuf()
            .map_err_into(REK::StoreIdError)
            .and_then(|pb| {
                pb.file_name()
                    .and_then(|osstr| osstr.to_str())
                    .and_then(|s| s.split("~").next())
                    .map(String::from)
                    .ok_or(REK::StoreIdError.into_error())
            })
    }

    /// Get the hash of the link target which is stored in the ref object
    fn get_stored_hash(&self) -> Result<String> {
        self.get_stored_hash_with_hasher(&DefaultHasher::new())
    }

    /// Get the hahs of the link target which is stored in the ref object, which is hashed with a
    /// custom Hasher instance.
    fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String> {
        match self.get_header().read(&format!("ref.content_hash.{}", h.hash_name())[..]) {
            // content hash stored...
            Ok(Some(&Value::String(ref s))) => Ok(s.clone()),

            // content hash header field has wrong type
            Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),

            // content hash not stored
            Ok(None) => Err(REK::HeaderFieldMissingError.into_error()),

            // Error
            Err(e) => Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
        }
    }

    /// Get the hash of the link target by reading the link target and hashing the contents
    fn get_current_hash(&self) -> Result<String> {
        self.get_current_hash_with_hasher(DefaultHasher::new())
    }

    /// Get the hash of the link target by reading the link target and hashing the contents with the
    /// custom hasher
    fn get_current_hash_with_hasher<H: Hasher>(&self, mut h: H) -> Result<String> {
        self.fs_file()
            .and_then(|pb| File::open(pb.clone()).map(|f| (pb, f)).map_err_into(REK::IOError))
            .and_then(|(path, mut file)| h.create_hash(&path, &mut file))
    }

    /// check whether the pointer the Ref represents still points to a file which exists
    fn fs_link_exists(&self) -> Result<bool> {
        self.fs_file().map(|pathbuf| pathbuf.exists())
    }

    /// Alias for `r.fs_link_exists() && r.deref().is_file()`
    fn is_ref_to_file(&self) -> Result<bool> {
        self.fs_file().map(|pathbuf| pathbuf.is_file())
    }

    /// Alias for `r.fs_link_exists() && r.deref().is_dir()`
    fn is_ref_to_dir(&self) -> Result<bool> {
        self.fs_file().map(|pathbuf| pathbuf.is_dir())
    }

    /// Alias for `!Ref::fs_link_exists()`
    fn is_dangling(&self) -> Result<bool> {
        self.fs_link_exists().map(|b| !b)
    }

    /// check whether the pointer the Ref represents is valid
    /// This includes:
    ///     - Hashsum of the file is still the same as stored in the Ref
    ///     - file permissions are still valid
    fn fs_link_valid(&self) -> Result<bool> {
        match (self.fs_link_valid_permissions(), self.fs_link_valid_hash()) {
            (Ok(true) , Ok(true)) => Ok(true),
            (Ok(_)    , Ok(_))    => Ok(false),
            (Err(e)   , _)        => Err(e),
            (_        , Err(e))   => Err(e),
        }
    }

    /// Check whether the file permissions of the referenced file are equal to the stored
    /// permissions
    fn fs_link_valid_permissions(&self) -> Result<bool> {
        self
            .get_header()
            .read("ref.permissions.ro")
            .map_err_into(REK::HeaderFieldReadError)
            .and_then(|ro| {
                match ro {
                    Some(&Value::Boolean(b)) => Ok(b),
                    Some(_)                 => Err(REK::HeaderTypeError.into_error()),
                    None                    => Err(REK::HeaderFieldMissingError.into_error()),
                }
            })
            .and_then(|ro| self.get_current_permissions().map(|perm| ro == perm.readonly()))
            .map_err_into(REK::RefTargetCannotReadPermissions)
    }

    /// Check whether the Hashsum of the referenced file is equal to the stored hashsum
    fn fs_link_valid_hash(&self) -> Result<bool> {
        let stored_hash  = try!(self.get_stored_hash());
        let current_hash = try!(self.get_current_hash());
        Ok(stored_hash == current_hash)
    }

    /// Update the Ref by re-checking the file from FS
    /// This errors if the file is not present or cannot be read()
    fn update_ref(&mut self) -> Result<()> {
        self.update_ref_with_hasher(&DefaultHasher::new())
    }

    /// Update the Ref by re-checking the file from FS using the passed Hasher instance
    /// This errors if the file is not present or cannot be read()
    fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()> {
        let current_hash = try!(self.get_current_hash()); // uses the default hasher
        let current_perm = try!(self.get_current_permissions());

        try!(self
            .get_header_mut()
            .set("ref.permissions.ro", Value::Boolean(current_perm.readonly()))
            .map_err_into(REK::StoreWriteError)
        );

        try!(self
            .get_header_mut()
            .set(&format!("ref.content_hash.{}", h.hash_name())[..], Value::String(current_hash))
            .map_err_into(REK::StoreWriteError)
        );

        Ok(())
    }

    /// Get the path of the file which is reffered to by this Ref
    fn fs_file(&self) -> Result<PathBuf> {
        match self.get_header().read("ref.path") {
            Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
            Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
            Ok(None)    => Err(REK::HeaderFieldMissingError.into_error()),
            Err(e)      => Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
        }
    }

    /// Re-find a referenced file
    ///
    /// This function tries to re-find a ref by searching all directories in `search_roots` recursively
    /// for a file which matches the hash of the Ref.
    ///
    /// If `search_roots` is `None`, it starts at the filesystem root `/`.
    ///
    /// If the target cannot be found, this yields a RefTargetDoesNotExist error kind.
    ///
    /// # Warning
    ///
    /// This option causes heavy I/O as it recursively searches the Filesystem.
    fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf> {
        self.refind_with_hasher(search_roots, DefaultHasher::new())
    }

    /// See documentation of `Ref::refind()`
    fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, mut h: H)
        -> Result<PathBuf>
    {
        use itertools::Itertools;
        use walkdir::WalkDir;

        self.get_stored_hash()
            .and_then(|stored_hash| {
                search_roots
                    .unwrap_or(vec![PathBuf::from("/")])
                    .into_iter()
                    .map(|root| {
                        WalkDir::new(root)
                            .follow_links(false)
                            .into_iter()
                            .map(|entry| {
                                entry
                                    .map_err_into(REK::IOError)
                                    .and_then(|entry| {
                                        let pb = PathBuf::from(entry.path());
                                        File::open(entry.path())
                                            .map_err_into(REK::IOError)
                                            .map(|f| (pb, f))
                                    })
                                    .and_then(|(p, mut f)|  h.create_hash(&p, &mut f).map(|h| (p, h)))
                                    .map(|(path, hash)| {
                                        if hash == stored_hash {
                                            Some(path)
                                        } else {
                                            None
                                        }
                                    })
                                    .map_err_into(REK::IOError)
                            })
                            .filter_map(|e| e.ok())
                            .filter_map(|e| e)
                            .next()
                    })
                    .flatten()
                    .next()
                    .ok_or(REK::RefTargetDoesNotExist.into_error())
            })
    }

    /// Get the permissions of the file which are present
    fn get_current_permissions(&self) -> Result<Permissions> {
        self.fs_file()
            .and_then(|pb| File::open(pb).map_err_into(REK::HeaderFieldReadError))
            .and_then(|file| {
                file
                    .metadata()
                    .map(|md| md.permissions())
                    .map_err_into(REK::RefTargetCannotReadPermissions)
            })
    }

}

<commit_msg>Remove unused imports<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

//! The Ref object is a helper over the link functionality, so one is able to create references to
//! files outside of the imag store.

use std::path::PathBuf;
use std::fs::File;
use std::fs::Permissions;

use libimagstore::store::Entry;
use libimagerror::into::IntoError;

use toml::Value;
use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt;

use error::RefErrorKind as REK;
use error::MapErrInto;
use result::Result;
use hasher::*;

pub trait Ref {

    /// Get the hash from the path of the ref
    fn get_path_hash(&self) -> Result<String>;

    /// Get the hash of the link target which is stored in the ref object
    fn get_stored_hash(&self) -> Result<String>;

    /// Get the hahs of the link target which is stored in the ref object, which is hashed with a
    /// custom Hasher instance.
    fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String>;

    /// Get the hash of the link target by reading the link target and hashing the contents
    fn get_current_hash(&self) -> Result<String>;

    /// Get the hash of the link target by reading the link target and hashing the contents with the
    /// custom hasher
    fn get_current_hash_with_hasher<H: Hasher>(&self, h: H) -> Result<String>;

    /// check whether the pointer the Ref represents still points to a file which exists
    fn fs_link_exists(&self) -> Result<bool>;

    /// Alias for `r.fs_link_exists() && r.deref().is_file()`
    fn is_ref_to_file(&self) -> Result<bool>;

    /// Alias for `r.fs_link_exists() && r.deref().is_dir()`
    fn is_ref_to_dir(&self) -> Result<bool>;

    /// Alias for `!Ref::fs_link_exists()`
    fn is_dangling(&self) -> Result<bool>;

    /// check whether the pointer the Ref represents is valid
    /// This includes:
    ///     - Hashsum of the file is still the same as stored in the Ref
    ///     - file permissions are still valid
    fn fs_link_valid(&self) -> Result<bool>;

    /// Check whether the file permissions of the referenced file are equal to the stored
    /// permissions
    fn fs_link_valid_permissions(&self) -> Result<bool>;

    /// Check whether the Hashsum of the referenced file is equal to the stored hashsum
    fn fs_link_valid_hash(&self) -> Result<bool>;

    /// Update the Ref by re-checking the file from FS
    /// This errors if the file is not present or cannot be read()
    fn update_ref(&mut self) -> Result<()>;

    /// Update the Ref by re-checking the file from FS using the passed Hasher instance
    /// This errors if the file is not present or cannot be read()
    fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()>;

    /// Get the path of the file which is reffered to by this Ref
    fn fs_file(&self) -> Result<PathBuf>;

    /// Re-find a referenced file
    ///
    /// This function tries to re-find a ref by searching all directories in `search_roots` recursively
    /// for a file which matches the hash of the Ref.
    ///
    /// If `search_roots` is `None`, it starts at the filesystem root `/`.
    ///
    /// If the target cannot be found, this yields a RefTargetDoesNotExist error kind.
    ///
    /// # Warning
    ///
    /// This option causes heavy I/O as it recursively searches the Filesystem.
    fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf>;

    /// See documentation of `Ref::refind()`
    fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, h: H)
        -> Result<PathBuf>;

    /// Get the permissions of the file which are present
    fn get_current_permissions(&self) -> Result<Permissions>;
}

impl Ref for Entry {

    /// Get the hash from the path of the ref
    fn get_path_hash(&self) -> Result<String> {
        self.get_location()
            .clone()
            .into_pathbuf()
            .map_err_into(REK::StoreIdError)
            .and_then(|pb| {
                pb.file_name()
                    .and_then(|osstr| osstr.to_str())
                    .and_then(|s| s.split("~").next())
                    .map(String::from)
                    .ok_or(REK::StoreIdError.into_error())
            })
    }

    /// Get the hash of the link target which is stored in the ref object
    fn get_stored_hash(&self) -> Result<String> {
        self.get_stored_hash_with_hasher(&DefaultHasher::new())
    }

    /// Get the hahs of the link target which is stored in the ref object, which is hashed with a
    /// custom Hasher instance.
    fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String> {
        match self.get_header().read(&format!("ref.content_hash.{}", h.hash_name())[..]) {
            // content hash stored...
            Ok(Some(&Value::String(ref s))) => Ok(s.clone()),

            // content hash header field has wrong type
            Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),

            // content hash not stored
            Ok(None) => Err(REK::HeaderFieldMissingError.into_error()),

            // Error
            Err(e) => Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
        }
    }

    /// Get the hash of the link target by reading the link target and hashing the contents
    fn get_current_hash(&self) -> Result<String> {
        self.get_current_hash_with_hasher(DefaultHasher::new())
    }

    /// Get the hash of the link target by reading the link target and hashing the contents with the
    /// custom hasher
    fn get_current_hash_with_hasher<H: Hasher>(&self, mut h: H) -> Result<String> {
        self.fs_file()
            .and_then(|pb| File::open(pb.clone()).map(|f| (pb, f)).map_err_into(REK::IOError))
            .and_then(|(path, mut file)| h.create_hash(&path, &mut file))
    }

    /// check whether the pointer the Ref represents still points to a file which exists
    fn fs_link_exists(&self) -> Result<bool> {
        self.fs_file().map(|pathbuf| pathbuf.exists())
    }

    /// Alias for `r.fs_link_exists() && r.deref().is_file()`
    fn is_ref_to_file(&self) -> Result<bool> {
        self.fs_file().map(|pathbuf| pathbuf.is_file())
    }

    /// Alias for `r.fs_link_exists() && r.deref().is_dir()`
    fn is_ref_to_dir(&self) -> Result<bool> {
        self.fs_file().map(|pathbuf| pathbuf.is_dir())
    }

    /// Alias for `!Ref::fs_link_exists()`
    fn is_dangling(&self) -> Result<bool> {
        self.fs_link_exists().map(|b| !b)
    }

    /// check whether the pointer the Ref represents is valid
    /// This includes:
    ///     - Hashsum of the file is still the same as stored in the Ref
    ///     - file permissions are still valid
    fn fs_link_valid(&self) -> Result<bool> {
        match (self.fs_link_valid_permissions(), self.fs_link_valid_hash()) {
            (Ok(true) , Ok(true)) => Ok(true),
            (Ok(_)    , Ok(_))    => Ok(false),
            (Err(e)   , _)        => Err(e),
            (_        , Err(e))   => Err(e),
        }
    }

    /// Check whether the file permissions of the referenced file are equal to the stored
    /// permissions
    fn fs_link_valid_permissions(&self) -> Result<bool> {
        self
            .get_header()
            .read("ref.permissions.ro")
            .map_err_into(REK::HeaderFieldReadError)
            .and_then(|ro| {
                match ro {
                    Some(&Value::Boolean(b)) => Ok(b),
                    Some(_)                 => Err(REK::HeaderTypeError.into_error()),
                    None                    => Err(REK::HeaderFieldMissingError.into_error()),
                }
            })
            .and_then(|ro| self.get_current_permissions().map(|perm| ro == perm.readonly()))
            .map_err_into(REK::RefTargetCannotReadPermissions)
    }

    /// Check whether the Hashsum of the referenced file is equal to the stored hashsum
    fn fs_link_valid_hash(&self) -> Result<bool> {
        let stored_hash  = try!(self.get_stored_hash());
        let current_hash = try!(self.get_current_hash());
        Ok(stored_hash == current_hash)
    }

    /// Update the Ref by re-checking the file from FS
    /// This errors if the file is not present or cannot be read()
    fn update_ref(&mut self) -> Result<()> {
        self.update_ref_with_hasher(&DefaultHasher::new())
    }

    /// Update the Ref by re-checking the file from FS using the passed Hasher instance
    /// This errors if the file is not present or cannot be read()
    fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()> {
        let current_hash = try!(self.get_current_hash()); // uses the default hasher
        let current_perm = try!(self.get_current_permissions());

        try!(self
            .get_header_mut()
            .set("ref.permissions.ro", Value::Boolean(current_perm.readonly()))
            .map_err_into(REK::StoreWriteError)
        );

        try!(self
            .get_header_mut()
            .set(&format!("ref.content_hash.{}", h.hash_name())[..], Value::String(current_hash))
            .map_err_into(REK::StoreWriteError)
        );

        Ok(())
    }

    /// Get the path of the file which is reffered to by this Ref
    fn fs_file(&self) -> Result<PathBuf> {
        match self.get_header().read("ref.path") {
            Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
            Ok(Some(_)) => Err(REK::HeaderTypeError.into_error()),
            Ok(None)    => Err(REK::HeaderFieldMissingError.into_error()),
            Err(e)      => Err(REK::StoreReadError.into_error_with_cause(Box::new(e))),
        }
    }

    /// Re-find a referenced file
    ///
    /// This function tries to re-find a ref by searching all directories in `search_roots` recursively
    /// for a file which matches the hash of the Ref.
    ///
    /// If `search_roots` is `None`, it starts at the filesystem root `/`.
    ///
    /// If the target cannot be found, this yields a RefTargetDoesNotExist error kind.
    ///
    /// # Warning
    ///
    /// This option causes heavy I/O as it recursively searches the Filesystem.
    fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf> {
        self.refind_with_hasher(search_roots, DefaultHasher::new())
    }

    /// See documentation of `Ref::refind()`
    fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, mut h: H)
        -> Result<PathBuf>
    {
        use itertools::Itertools;
        use walkdir::WalkDir;

        self.get_stored_hash()
            .and_then(|stored_hash| {
                search_roots
                    .unwrap_or(vec![PathBuf::from("/")])
                    .into_iter()
                    .map(|root| {
                        WalkDir::new(root)
                            .follow_links(false)
                            .into_iter()
                            .map(|entry| {
                                entry
                                    .map_err_into(REK::IOError)
                                    .and_then(|entry| {
                                        let pb = PathBuf::from(entry.path());
                                        File::open(entry.path())
                                            .map_err_into(REK::IOError)
                                            .map(|f| (pb, f))
                                    })
                                    .and_then(|(p, mut f)|  h.create_hash(&p, &mut f).map(|h| (p, h)))
                                    .map(|(path, hash)| {
                                        if hash == stored_hash {
                                            Some(path)
                                        } else {
                                            None
                                        }
                                    })
                                    .map_err_into(REK::IOError)
                            })
                            .filter_map(|e| e.ok())
                            .filter_map(|e| e)
                            .next()
                    })
                    .flatten()
                    .next()
                    .ok_or(REK::RefTargetDoesNotExist.into_error())
            })
    }

    /// Get the permissions of the file which are present
    fn get_current_permissions(&self) -> Result<Permissions> {
        self.fs_file()
            .and_then(|pb| File::open(pb).map_err_into(REK::HeaderFieldReadError))
            .and_then(|file| {
                file
                    .metadata()
                    .map(|md| md.permissions())
                    .map_err_into(REK::RefTargetCannotReadPermissions)
            })
    }

}

<|endoftext|>


<commit_before>// Copyright 2016 The Noise-rs Developers.
//
// 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 num_traits::Float;
use math::{Point2, Point3, Point4};
use NoiseModule;
use std::cell::{Cell, RefCell};

/// Noise module that caches the last output value generated by the source
/// module.
///
/// If the input coordinates pased to `Cache::get` are equal to the previous
/// call, the module returns the cached result of the previous call to
/// `Source::get`. Otherwise, `Source::get` is called with the new coordinates,
/// overwriting the cache with the result, and returning the result to the
/// caller.
///
/// Caching a noise module is useful if it is used as a source module for
/// multiple noise modules. If a source module is not cached, the source
/// module will redundantly calculate the same output value once for each
/// noise module in which it is included.
#[derive(Clone, Debug)]
pub struct Cache<Source, T>
    where T: Float,
{
    /// Outputs the value to be cached.
    pub source: Source,

    value: Cell<Option<T>>,

    point: RefCell<Vec<T>>,
}

impl<Source, T> Cache<Source, T>
    where T: Float,
{
    pub fn new(source: Source) -> Cache<Source, T> {
        Cache {
            source: source,
            value: Cell::new(None),
            point: RefCell::new(Vec::new()),
        }
    }
}

impl<Source, T> NoiseModule<Point2<T>> for Cache<Source, T>
    where Source: NoiseModule<Point2<T>, Output = T>,
          T: Float,
{
    type Output = T;

    fn get(&self, point: Point2<T>) -> Self::Output {
        match self.value.get() {
            Some(value) if *self.point.borrow() == point => value,
            Some(_) | None => {
                let value = self.source.get(point);
                self.value.set(Some(value));

                *self.point.borrow_mut() = point.to_vec();

                value
            }
        }
    }
}

impl<Source, T> NoiseModule<Point3<T>> for Cache<Source, T>
    where Source: NoiseModule<Point3<T>, Output = T>,
          T: Float,
{
    type Output = T;

    fn get(&self, point: Point3<T>) -> Self::Output {
        match self.value.get() {
            Some(value) if *self.point.borrow() == point => value,
            Some(_) | None => {
                let value = self.source.get(point);
                self.value.set(Some(value));

                *self.point.borrow_mut() = point.to_vec();

                value
            }
        }
    }
}

impl<Source, T> NoiseModule<Point4<T>> for Cache<Source, T>
    where Source: NoiseModule<Point4<T>, Output = T>,
          T: Float,
{
    type Output = T;

    fn get(&self, point: Point4<T>) -> Self::Output {
        match self.value.get() {
            Some(value) if *self.point.borrow() == point => value,
            Some(_) | None => {
                let value = self.source.get(point);
                self.value.set(Some(value));

                *self.point.borrow_mut() = point.to_vec();

                value
            }
        }
    }
}
<commit_msg>Remove Vec reallocation in Cache<commit_after>// Copyright 2016 The Noise-rs Developers.
//
// 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 num_traits::Float;
use math::{Point2, Point3, Point4};
use NoiseModule;
use std::cell::{Cell, RefCell};

/// Noise module that caches the last output value generated by the source
/// module.
///
/// If the input coordinates pased to `Cache::get` are equal to the previous
/// call, the module returns the cached result of the previous call to
/// `Source::get`. Otherwise, `Source::get` is called with the new coordinates,
/// overwriting the cache with the result, and returning the result to the
/// caller.
///
/// Caching a noise module is useful if it is used as a source module for
/// multiple noise modules. If a source module is not cached, the source
/// module will redundantly calculate the same output value once for each
/// noise module in which it is included.
#[derive(Clone, Debug)]
pub struct Cache<Source, T>
    where T: Float,
{
    /// Outputs the value to be cached.
    pub source: Source,

    value: Cell<Option<T>>,

    point: RefCell<Vec<T>>,
}

impl<Source, T> Cache<Source, T>
    where T: Float,
{
    pub fn new(source: Source) -> Cache<Source, T> {
        Cache {
            source: source,
            value: Cell::new(None),
            point: RefCell::new(Vec::new()),
        }
    }
}

impl<Source, T> NoiseModule<Point2<T>> for Cache<Source, T>
    where Source: NoiseModule<Point2<T>, Output = T>,
          T: Float,
{
    type Output = T;

    fn get(&self, point: Point2<T>) -> Self::Output {
        match self.value.get() {
            Some(value) if *self.point.borrow() == point => value,
            Some(_) | None => {
                let value = self.source.get(point);
                self.value.set(Some(value));

                let mut cached_point = self.point.borrow_mut();
                cached_point.clear();
                cached_point.extend_from_slice(&point);

                value
            },
        }
    }
}

impl<Source, T> NoiseModule<Point3<T>> for Cache<Source, T>
    where Source: NoiseModule<Point3<T>, Output = T>,
          T: Float,
{
    type Output = T;

    fn get(&self, point: Point3<T>) -> Self::Output {
        match self.value.get() {
            Some(value) if *self.point.borrow() == point => value,
            Some(_) | None => {
                let value = self.source.get(point);
                self.value.set(Some(value));

                let mut cached_point = self.point.borrow_mut();
                cached_point.clear();
                cached_point.extend_from_slice(&point);

                value
            },
        }
    }
}

impl<Source, T> NoiseModule<Point4<T>> for Cache<Source, T>
    where Source: NoiseModule<Point4<T>, Output = T>,
          T: Float,
{
    type Output = T;

    fn get(&self, point: Point4<T>) -> Self::Output {
        match self.value.get() {
            Some(value) if *self.point.borrow() == point => value,
            Some(_) | None => {
                let value = self.source.get(point);
                self.value.set(Some(value));

                let mut cached_point = self.point.borrow_mut();
                cached_point.clear();
                cached_point.extend_from_slice(&point);

                value
            },
        }
    }
}
<|endoftext|>


<commit_before>//!
//! https://tools.ietf.org/html/rfc4551
//!
//! IMAP Extension for Conditional STORE Operation
//! or Quick Flag Changes Resynchronization
//!

use nom::{bytes::streaming::tag_no_case, sequence::tuple, IResult};

use crate::{parser::core::{number_64, paren_delimited}, types::*};

// The highest mod-sequence value of all messages in the mailbox.
// Extends resp-test-code defined in rfc3501.
// [RFC4551 - 3.6 HIGHESTMODSEQ Status Data Items](https://tools.ietf.org/html/rfc4551#section-3.6)
// [RFC4551 - 4. Formal Syntax - resp-text-code](https://tools.ietf.org/html/rfc4551#section-4)
pub(crate) fn resp_text_code_highest_mod_seq(i: &[u8]) -> IResult<&[u8], ResponseCode> {
    let (i, (_, num)) = tuple((tag_no_case("HIGHESTMODSEQ "), number_64))(i)?;
    Ok((i, ResponseCode::HighestModSeq(num)))
}

// Extends status-att/status-att-list defined in rfc3501
// [RFC4551 - 3.6 - HIGHESTMODSEQ Status Data Items](https://tools.ietf.org/html/rfc4551#section-3.6)
// [RFC4551 - 4. Formal Syntax - status-att-val](https://tools.ietf.org/html/rfc4551#section-4)
pub(crate) fn status_att_val_highest_mod_seq(i: &[u8]) -> IResult<&[u8], StatusAttribute> {
    let (i, (_, num)) = tuple((tag_no_case("HIGHESTMODSEQ "), number_64))(i)?;
    Ok((i, StatusAttribute::HighestModSeq(num)))
}

// [RFC4551 - 4. Formal Syntax - fetch-mod-resp](https://tools.ietf.org/html/rfc4551#section-4)
pub(crate) fn msg_att_mod_seq(i: &[u8]) -> IResult<&[u8], AttributeValue> {
    let (i, (_, num)) = tuple((tag_no_case("MODSEQ "), paren_delimited(number_64)))(i)?;
    Ok((i, AttributeValue::ModSeq(num)))
}
<commit_msg>Fix formatting with cargo fmt<commit_after>//!
//! https://tools.ietf.org/html/rfc4551
//!
//! IMAP Extension for Conditional STORE Operation
//! or Quick Flag Changes Resynchronization
//!

use nom::{bytes::streaming::tag_no_case, sequence::tuple, IResult};

use crate::{
    parser::core::{number_64, paren_delimited},
    types::*,
};

// The highest mod-sequence value of all messages in the mailbox.
// Extends resp-test-code defined in rfc3501.
// [RFC4551 - 3.6 HIGHESTMODSEQ Status Data Items](https://tools.ietf.org/html/rfc4551#section-3.6)
// [RFC4551 - 4. Formal Syntax - resp-text-code](https://tools.ietf.org/html/rfc4551#section-4)
pub(crate) fn resp_text_code_highest_mod_seq(i: &[u8]) -> IResult<&[u8], ResponseCode> {
    let (i, (_, num)) = tuple((tag_no_case("HIGHESTMODSEQ "), number_64))(i)?;
    Ok((i, ResponseCode::HighestModSeq(num)))
}

// Extends status-att/status-att-list defined in rfc3501
// [RFC4551 - 3.6 - HIGHESTMODSEQ Status Data Items](https://tools.ietf.org/html/rfc4551#section-3.6)
// [RFC4551 - 4. Formal Syntax - status-att-val](https://tools.ietf.org/html/rfc4551#section-4)
pub(crate) fn status_att_val_highest_mod_seq(i: &[u8]) -> IResult<&[u8], StatusAttribute> {
    let (i, (_, num)) = tuple((tag_no_case("HIGHESTMODSEQ "), number_64))(i)?;
    Ok((i, StatusAttribute::HighestModSeq(num)))
}

// [RFC4551 - 4. Formal Syntax - fetch-mod-resp](https://tools.ietf.org/html/rfc4551#section-4)
pub(crate) fn msg_att_mod_seq(i: &[u8]) -> IResult<&[u8], AttributeValue> {
    let (i, (_, num)) = tuple((tag_no_case("MODSEQ "), paren_delimited(number_64)))(i)?;
    Ok((i, AttributeValue::ModSeq(num)))
}
<|endoftext|>


<commit_before>#![allow(dead_code, unused_must_use, unused_variables, unused_imports)]

use std::thread::{self,Thread,Builder};
use std::sync::mpsc::{self,channel,Receiver};
use mio::tcp::*;
use mio::*;
use bytes::{ByteBuf,MutByteBuf};
use std::collections::HashMap;
use std::io::{self,Read,ErrorKind};
use nom::HexDisplay;
use std::error::Error;
use mio::util::Slab;
use std::io::Write;
use std::str::FromStr;
use std::marker::PhantomData;
use std::fmt::Debug;
use time::precise_time_ns;
use rand::random;

use network::{ClientResult,ServerMessage,ConnectionError,SocketType,socket_type,ProxyOrder};
use network::metrics::{METRICS,ProxyMetrics};

use messages::{TcpFront,Command,Instance};

const SERVER: Token = Token(0);
const FRONT_TIMEOUT: u64 = 10000;
const BACK_TIMEOUT:  u64 = 10000;
type ClientToken = Token;

pub trait ProxyClient {
  fn front_socket(&self) -> &TcpStream;
  fn back_socket(&self)  -> Option<&TcpStream>;
  fn front_token(&self)  -> Option<Token>;
  fn back_token(&self)   -> Option<Token>;
  fn set_back_socket(&mut self, TcpStream);
  fn set_front_token(&mut self, token: Token);
  fn set_back_token(&mut self, token: Token);
  fn front_timeout(&mut self) -> Option<Timeout>;
  fn back_timeout(&mut self)  -> Option<Timeout>;
  fn set_front_timeout(&mut self, timeout: Timeout);
  fn set_back_timeout(&mut self, timeout: Timeout);
  fn set_tokens(&mut self, token: Token, backend: Token);
  fn front_hup(&mut self)     -> ClientResult;
  fn back_hup(&mut self)      -> ClientResult;
  fn readable(&mut self)      -> ClientResult;
  fn writable(&mut self)      -> ClientResult;
  fn back_readable(&mut self) -> ClientResult;
  fn back_writable(&mut self) -> ClientResult;
  fn remove_backend(&mut self);
}

pub trait ProxyConfiguration<Server:Handler,Client> {
  fn add_tcp_front(&mut self, port: u16, app_id: &str, event_loop: &mut EventLoop<Server>) -> Option<Token>;
  fn connect_to_backend(&mut self, client:&mut Client) ->Result<TcpStream,ConnectionError>;
  fn notify(&mut self, event_loop: &mut EventLoop<Server>, message: ProxyOrder);
  fn accept(&mut self, token: Token) -> Option<(Client, bool)>;
}

pub struct Server<ServerConfiguration,Client> {
  configuration:   ServerConfiguration,
  clients:         Slab<Client>,
  backend:         Slab<ClientToken>,
  max_listeners:   usize,
  max_connections: usize,
}

impl<ServerConfiguration:ProxyConfiguration<Server<ServerConfiguration,Client>, Client>,Client:ProxyClient> Server<ServerConfiguration,Client> {
  pub fn new(max_listeners: usize, max_connections: usize, configuration: ServerConfiguration) -> Self {
    Server {
      configuration:   configuration,
      clients:         Slab::new_starting_at(Token(max_listeners), max_connections),
      backend:         Slab::new_starting_at(Token(max_listeners+max_connections), max_connections),
      max_listeners:   max_listeners,
      max_connections: max_connections,
    }
  }

  pub fn configuration(&mut self) -> &mut ServerConfiguration {
    &mut self.configuration
  }

  pub fn close_client(&mut self, event_loop: &mut EventLoop<Self>, token: Token) {
    self.clients[token].front_socket().shutdown(Shutdown::Both);
    event_loop.deregister(self.clients[token].front_socket());
    if let Some(sock) = self.clients[token].back_socket() {
      sock.shutdown(Shutdown::Both);
      event_loop.deregister(sock);
    }

    if let Some(backend_token) = self.clients[token].back_token() {
      if self.backend.contains(backend_token) {
        self.backend.remove(backend_token);
      }
    }
    self.clients.remove(token);
  }

  pub fn close_backend(&mut self, event_loop: &mut EventLoop<Self>, token: Token) {
    if let Some(backend_token) = self.clients[token].back_token() {
      if self.backend.contains(backend_token) {
        self.backend.remove(backend_token);
        self.clients[token].remove_backend();
      }
    }
  }

  pub fn accept(&mut self, event_loop: &mut EventLoop<Self>, token: Token) {
    if let Some((client, should_connect)) = self.configuration.accept(token) {
      if let Ok(client_token) = self.clients.insert(client) {
        event_loop.register(self.clients[client_token].front_socket(), client_token, EventSet::readable(), PollOpt::edge());
        &self.clients[client_token].set_front_token(client_token);
        if let Ok(timeout) = event_loop.timeout_ms(client_token.as_usize(), FRONT_TIMEOUT) {
          &self.clients[client_token].set_front_timeout(timeout);
        }
        METRICS.lock().unwrap().gauge("accept", 1);
        if should_connect {
          self.connect_to_backend(event_loop, client_token);
        }
      } else {
        error!("could not add client to slab");
      }
    } else {
      error!("could not create a client");
    }
  }

  pub fn connect_to_backend(&mut self, event_loop: &mut EventLoop<Self>, token: Token) {
    if let Ok(socket) = self.configuration.connect_to_backend(&mut self.clients[token]) {
      if let Ok(backend_token) = self.backend.insert(token) {
        self.clients[token].set_back_socket(socket);
        self.clients[token].set_back_token(backend_token);

        if let Some(sock) = self.clients[token].back_socket() {
          event_loop.register(sock, backend_token, EventSet::writable(), PollOpt::edge());
        }
        if let Ok(timeout) = event_loop.timeout_ms(backend_token.as_usize(), BACK_TIMEOUT) {
          &self.clients[token].set_back_timeout(timeout);
        }
        return;
      }
    } else {
      // ToDo serve page explaining what happened
      // - "domain not found" (aka "404 clever") page
      // - 503 "service unavailable" (aka "your app is deploying") page
      // - check other failing conditions
    }
    self.close_client(event_loop, token);
  }

  pub fn get_client_token(&self, token: Token) -> Option<Token> {
    if token.as_usize() < self.max_listeners {
      None
    } else if token.as_usize() < self.max_listeners + self.max_connections && self.clients.contains(token) {
      Some(token)
    } else if token.as_usize() < self.max_listeners + 2 * self.max_connections && self.backend.contains(token) {
      if self.clients.contains(self.backend[token]) {
        Some(self.backend[token])
      } else {
        None
      }
    } else {
      None
    }
  }

  pub fn interpret_client_order(&mut self, event_loop: &mut EventLoop<Self>, token: Token, order: ClientResult) {
    match order {
      ClientResult::CloseClient      => self.close_client(event_loop, token),
      ClientResult::CloseBackend     => self.close_backend(event_loop, token),
      ClientResult::CloseBothSuccess => self.close_client(event_loop, token),
      ClientResult::CloseBothFailure => self.close_client(event_loop, token),
      ClientResult::ConnectBackend   => {
        self.reregister(event_loop, token);
        self.connect_to_backend(event_loop, token)
      },
      ClientResult::Continue         => self.reregister(event_loop, token)
    }
  }

  fn reregister(&self, event_loop: &mut EventLoop<Self>, token: Token) {
    let client = &self.clients[token];
    let mut front_interest = EventSet::hup();
    let mut back_interest  = EventSet::hup();
    front_interest.insert(EventSet::readable());
    front_interest.insert(EventSet::writable());
    back_interest.insert(EventSet::writable());
    back_interest.insert(EventSet::readable());

    if let Some(frontend_token) = client.front_token() {
      event_loop.reregister(client.front_socket(), frontend_token, front_interest, PollOpt::level() | PollOpt::oneshot());
    }
     if let Some(backend_token) = client.back_token() {
       if let Some(sock) = client.back_socket() {
         event_loop.reregister(sock, backend_token, back_interest, PollOpt::level() | PollOpt::oneshot());
       }
     }
  }
}

impl<ServerConfiguration:ProxyConfiguration<Server<ServerConfiguration,Client>, Client>,Client:ProxyClient> Handler for Server<ServerConfiguration,Client> {
  type Timeout = usize;
  type Message = ProxyOrder;

  fn ready(&mut self, event_loop: &mut EventLoop<Self>, token: Token, events: EventSet) {
    trace!("{:?} got events: {:?}", token, events);
    if events.is_readable() {
      trace!("{:?} is readable", token);

      match socket_type(token, self.max_listeners, self.max_connections) {
        Some(SocketType::Listener) => {
          self.accept(event_loop, token)
        }

        Some(SocketType::FrontClient) => {
          if self.clients.contains(token) {
            let order = self.clients[token].readable();
            self.interpret_client_order(event_loop, token, order);

            // FIXME: should clear the timeout only if data was consumed
            if let Some(timeout) = self.clients[token].front_timeout() {
              //println!("[{}] clearing timeout", token.as_usize());
              event_loop.clear_timeout(timeout);
            }
            if let Ok(timeout) = event_loop.timeout_ms(token.as_usize(), FRONT_TIMEOUT) {
              //println!("[{}] resetting timeout", token.as_usize());
              &self.clients[token].set_front_timeout(timeout);
            }
          } else {
            info!("client {:?} was removed", token);
          }
        }

        Some(SocketType::BackClient) => {
          if let Some(tok) = self.get_client_token(token) {
            let order = self.clients[tok].back_readable();
            self.interpret_client_order(event_loop, tok, order);

            // FIXME: should clear the timeout only if data was consumed
            if let Some(timeout) = self.clients[tok].back_timeout() {
              //println!("[{}] clearing timeout", token.as_usize());
              event_loop.clear_timeout(timeout);
            }
            if let Ok(timeout) = event_loop.timeout_ms(token.as_usize(), BACK_TIMEOUT) {
              //println!("[{}] resetting timeout", token.as_usize());
              &self.clients[tok].set_back_timeout(timeout);
            }
          }
        }

        None => {}
      }
    }

    if events.is_writable() {
      trace!("{:?} is writable", token);

      match socket_type(token, self.max_listeners, self.max_connections) {
        Some(SocketType::Listener) => {
          error!("received writable for listener {:?}, this should not happen", token);
        }

        Some(SocketType::FrontClient) => {
          if self.clients.contains(token) {
            let order = self.clients[token].writable();
            trace!("interpreting client order {:?}", order);
            self.interpret_client_order(event_loop, token, order);
          } else {
            info!("client {:?} was removed", token);
          }
        }

        Some(SocketType::BackClient) => {
          if let Some(tok) = self.get_client_token(token) {
            let order = self.clients[tok].back_writable();
            self.interpret_client_order(event_loop, tok, order);
          }
        }

        None => {}
      }
    }

    if events.is_hup() {
      match socket_type(token, self.max_listeners, self.max_connections) {
        Some(SocketType::Listener) => {
          error!("should not happen: server {:?} closed", token);
        }

        Some(SocketType::FrontClient) => {
          if self.clients.contains(token) {
            if self.clients[token].front_hup() == ClientResult::CloseClient {
              self.close_client(event_loop, token);
            }
          } else {
            info!("client {:?} was removed", token);
          }
        }

        Some(SocketType::BackClient) => {
          if let Some(tok) = self.get_client_token(token) {
            if self.clients[tok].front_hup() == ClientResult::CloseClient {
              self.close_client(event_loop, tok);
            }
          }
        }

        None => {}
      }
    }
  }

  fn notify(&mut self, event_loop: &mut EventLoop<Self>, message: Self::Message) {
    self.configuration.notify(event_loop, message);
  }

  fn timeout(&mut self, event_loop: &mut EventLoop<Self>, timeout: Self::Timeout) {
    let token = Token(timeout);
    match socket_type(token, self.max_listeners, self.max_connections) {
      Some(SocketType::Listener) => {
        error!("the listener socket should have no timeout set");
      },
      Some(SocketType::FrontClient) => {
        if self.clients.contains(token) {
          error!("frontend [{}] got timeout, closing", timeout);
          self.close_client(event_loop, token);
        }
      },
      Some(SocketType::BackClient) => {
        if let Some(tok) = self.get_client_token(token) {
          error!("backend [{}] got timeout, closing", timeout);
          self.close_client(event_loop, tok);
        }
      }
      None => {}
    }
  }

  fn interrupted(&mut self, event_loop: &mut EventLoop<Self>) {
    warn!("interrupted");
  }
}

<commit_msg>Reset timeout before interpreting the client order<commit_after>#![allow(dead_code, unused_must_use, unused_variables, unused_imports)]

use std::thread::{self,Thread,Builder};
use std::sync::mpsc::{self,channel,Receiver};
use mio::tcp::*;
use mio::*;
use bytes::{ByteBuf,MutByteBuf};
use std::collections::HashMap;
use std::io::{self,Read,ErrorKind};
use nom::HexDisplay;
use std::error::Error;
use mio::util::Slab;
use std::io::Write;
use std::str::FromStr;
use std::marker::PhantomData;
use std::fmt::Debug;
use time::precise_time_ns;
use rand::random;

use network::{ClientResult,ServerMessage,ConnectionError,SocketType,socket_type,ProxyOrder};
use network::metrics::{METRICS,ProxyMetrics};

use messages::{TcpFront,Command,Instance};

const SERVER: Token = Token(0);
const FRONT_TIMEOUT: u64 = 10000;
const BACK_TIMEOUT:  u64 = 10000;
type ClientToken = Token;

pub trait ProxyClient {
  fn front_socket(&self) -> &TcpStream;
  fn back_socket(&self)  -> Option<&TcpStream>;
  fn front_token(&self)  -> Option<Token>;
  fn back_token(&self)   -> Option<Token>;
  fn set_back_socket(&mut self, TcpStream);
  fn set_front_token(&mut self, token: Token);
  fn set_back_token(&mut self, token: Token);
  fn front_timeout(&mut self) -> Option<Timeout>;
  fn back_timeout(&mut self)  -> Option<Timeout>;
  fn set_front_timeout(&mut self, timeout: Timeout);
  fn set_back_timeout(&mut self, timeout: Timeout);
  fn set_tokens(&mut self, token: Token, backend: Token);
  fn front_hup(&mut self)     -> ClientResult;
  fn back_hup(&mut self)      -> ClientResult;
  fn readable(&mut self)      -> ClientResult;
  fn writable(&mut self)      -> ClientResult;
  fn back_readable(&mut self) -> ClientResult;
  fn back_writable(&mut self) -> ClientResult;
  fn remove_backend(&mut self);
}

pub trait ProxyConfiguration<Server:Handler,Client> {
  fn add_tcp_front(&mut self, port: u16, app_id: &str, event_loop: &mut EventLoop<Server>) -> Option<Token>;
  fn connect_to_backend(&mut self, client:&mut Client) ->Result<TcpStream,ConnectionError>;
  fn notify(&mut self, event_loop: &mut EventLoop<Server>, message: ProxyOrder);
  fn accept(&mut self, token: Token) -> Option<(Client, bool)>;
}

pub struct Server<ServerConfiguration,Client> {
  configuration:   ServerConfiguration,
  clients:         Slab<Client>,
  backend:         Slab<ClientToken>,
  max_listeners:   usize,
  max_connections: usize,
}

impl<ServerConfiguration:ProxyConfiguration<Server<ServerConfiguration,Client>, Client>,Client:ProxyClient> Server<ServerConfiguration,Client> {
  pub fn new(max_listeners: usize, max_connections: usize, configuration: ServerConfiguration) -> Self {
    Server {
      configuration:   configuration,
      clients:         Slab::new_starting_at(Token(max_listeners), max_connections),
      backend:         Slab::new_starting_at(Token(max_listeners+max_connections), max_connections),
      max_listeners:   max_listeners,
      max_connections: max_connections,
    }
  }

  pub fn configuration(&mut self) -> &mut ServerConfiguration {
    &mut self.configuration
  }

  pub fn close_client(&mut self, event_loop: &mut EventLoop<Self>, token: Token) {
    self.clients[token].front_socket().shutdown(Shutdown::Both);
    event_loop.deregister(self.clients[token].front_socket());
    if let Some(sock) = self.clients[token].back_socket() {
      sock.shutdown(Shutdown::Both);
      event_loop.deregister(sock);
    }

    if let Some(backend_token) = self.clients[token].back_token() {
      if self.backend.contains(backend_token) {
        self.backend.remove(backend_token);
      }
    }
    self.clients.remove(token);
  }

  pub fn close_backend(&mut self, event_loop: &mut EventLoop<Self>, token: Token) {
    if let Some(backend_token) = self.clients[token].back_token() {
      if self.backend.contains(backend_token) {
        self.backend.remove(backend_token);
        self.clients[token].remove_backend();
      }
    }
  }

  pub fn accept(&mut self, event_loop: &mut EventLoop<Self>, token: Token) {
    if let Some((client, should_connect)) = self.configuration.accept(token) {
      if let Ok(client_token) = self.clients.insert(client) {
        event_loop.register(self.clients[client_token].front_socket(), client_token, EventSet::readable(), PollOpt::edge());
        &self.clients[client_token].set_front_token(client_token);
        if let Ok(timeout) = event_loop.timeout_ms(client_token.as_usize(), FRONT_TIMEOUT) {
          &self.clients[client_token].set_front_timeout(timeout);
        }
        METRICS.lock().unwrap().gauge("accept", 1);
        if should_connect {
          self.connect_to_backend(event_loop, client_token);
        }
      } else {
        error!("could not add client to slab");
      }
    } else {
      error!("could not create a client");
    }
  }

  pub fn connect_to_backend(&mut self, event_loop: &mut EventLoop<Self>, token: Token) {
    if let Ok(socket) = self.configuration.connect_to_backend(&mut self.clients[token]) {
      if let Ok(backend_token) = self.backend.insert(token) {
        self.clients[token].set_back_socket(socket);
        self.clients[token].set_back_token(backend_token);

        if let Some(sock) = self.clients[token].back_socket() {
          event_loop.register(sock, backend_token, EventSet::writable(), PollOpt::edge());
        }
        if let Ok(timeout) = event_loop.timeout_ms(backend_token.as_usize(), BACK_TIMEOUT) {
          &self.clients[token].set_back_timeout(timeout);
        }
        return;
      }
    } else {
      // ToDo serve page explaining what happened
      // - "domain not found" (aka "404 clever") page
      // - 503 "service unavailable" (aka "your app is deploying") page
      // - check other failing conditions
    }
    self.close_client(event_loop, token);
  }

  pub fn get_client_token(&self, token: Token) -> Option<Token> {
    if token.as_usize() < self.max_listeners {
      None
    } else if token.as_usize() < self.max_listeners + self.max_connections && self.clients.contains(token) {
      Some(token)
    } else if token.as_usize() < self.max_listeners + 2 * self.max_connections && self.backend.contains(token) {
      if self.clients.contains(self.backend[token]) {
        Some(self.backend[token])
      } else {
        None
      }
    } else {
      None
    }
  }

  pub fn interpret_client_order(&mut self, event_loop: &mut EventLoop<Self>, token: Token, order: ClientResult) {
    match order {
      ClientResult::CloseClient      => self.close_client(event_loop, token),
      ClientResult::CloseBackend     => self.close_backend(event_loop, token),
      ClientResult::CloseBothSuccess => self.close_client(event_loop, token),
      ClientResult::CloseBothFailure => self.close_client(event_loop, token),
      ClientResult::ConnectBackend   => {
        self.reregister(event_loop, token);
        self.connect_to_backend(event_loop, token)
      },
      ClientResult::Continue         => self.reregister(event_loop, token)
    }
  }

  fn reregister(&self, event_loop: &mut EventLoop<Self>, token: Token) {
    let client = &self.clients[token];
    let mut front_interest = EventSet::hup();
    let mut back_interest  = EventSet::hup();
    front_interest.insert(EventSet::readable());
    front_interest.insert(EventSet::writable());
    back_interest.insert(EventSet::writable());
    back_interest.insert(EventSet::readable());

    if let Some(frontend_token) = client.front_token() {
      event_loop.reregister(client.front_socket(), frontend_token, front_interest, PollOpt::level() | PollOpt::oneshot());
    }
     if let Some(backend_token) = client.back_token() {
       if let Some(sock) = client.back_socket() {
         event_loop.reregister(sock, backend_token, back_interest, PollOpt::level() | PollOpt::oneshot());
       }
     }
  }
}

impl<ServerConfiguration:ProxyConfiguration<Server<ServerConfiguration,Client>, Client>,Client:ProxyClient> Handler for Server<ServerConfiguration,Client> {
  type Timeout = usize;
  type Message = ProxyOrder;

  fn ready(&mut self, event_loop: &mut EventLoop<Self>, token: Token, events: EventSet) {
    trace!("{:?} got events: {:?}", token, events);
    if events.is_readable() {
      trace!("{:?} is readable", token);

      match socket_type(token, self.max_listeners, self.max_connections) {
        Some(SocketType::Listener) => {
          self.accept(event_loop, token)
        }

        Some(SocketType::FrontClient) => {
          if self.clients.contains(token) {
            let order = self.clients[token].readable();

            // FIXME: should clear the timeout only if data was consumed
            if let Some(timeout) = self.clients[token].front_timeout() {
              //println!("[{}] clearing timeout", token.as_usize());
              event_loop.clear_timeout(timeout);
            }
            if let Ok(timeout) = event_loop.timeout_ms(token.as_usize(), FRONT_TIMEOUT) {
              //println!("[{}] resetting timeout", token.as_usize());
              &self.clients[token].set_front_timeout(timeout);
            }

            self.interpret_client_order(event_loop, token, order);
          } else {
            info!("client {:?} was removed", token);
          }
        }

        Some(SocketType::BackClient) => {
          if let Some(tok) = self.get_client_token(token) {
            let order = self.clients[tok].back_readable();

            // FIXME: should clear the timeout only if data was consumed
            if let Some(timeout) = self.clients[tok].back_timeout() {
              //println!("[{}] clearing timeout", token.as_usize());
              event_loop.clear_timeout(timeout);
            }
            if let Ok(timeout) = event_loop.timeout_ms(token.as_usize(), BACK_TIMEOUT) {
              //println!("[{}] resetting timeout", token.as_usize());
              &self.clients[tok].set_back_timeout(timeout);
            }

            self.interpret_client_order(event_loop, tok, order);
          }
        }

        None => {}
      }
    }

    if events.is_writable() {
      trace!("{:?} is writable", token);

      match socket_type(token, self.max_listeners, self.max_connections) {
        Some(SocketType::Listener) => {
          error!("received writable for listener {:?}, this should not happen", token);
        }

        Some(SocketType::FrontClient) => {
          if self.clients.contains(token) {
            let order = self.clients[token].writable();
            trace!("interpreting client order {:?}", order);
            self.interpret_client_order(event_loop, token, order);
          } else {
            info!("client {:?} was removed", token);
          }
        }

        Some(SocketType::BackClient) => {
          if let Some(tok) = self.get_client_token(token) {
            let order = self.clients[tok].back_writable();
            self.interpret_client_order(event_loop, tok, order);
          }
        }

        None => {}
      }
    }

    if events.is_hup() {
      match socket_type(token, self.max_listeners, self.max_connections) {
        Some(SocketType::Listener) => {
          error!("should not happen: server {:?} closed", token);
        }

        Some(SocketType::FrontClient) => {
          if self.clients.contains(token) {
            if self.clients[token].front_hup() == ClientResult::CloseClient {
              self.close_client(event_loop, token);
            }
          } else {
            info!("client {:?} was removed", token);
          }
        }

        Some(SocketType::BackClient) => {
          if let Some(tok) = self.get_client_token(token) {
            if self.clients[tok].front_hup() == ClientResult::CloseClient {
              self.close_client(event_loop, tok);
            }
          }
        }

        None => {}
      }
    }
  }

  fn notify(&mut self, event_loop: &mut EventLoop<Self>, message: Self::Message) {
    self.configuration.notify(event_loop, message);
  }

  fn timeout(&mut self, event_loop: &mut EventLoop<Self>, timeout: Self::Timeout) {
    let token = Token(timeout);
    match socket_type(token, self.max_listeners, self.max_connections) {
      Some(SocketType::Listener) => {
        error!("the listener socket should have no timeout set");
      },
      Some(SocketType::FrontClient) => {
        if self.clients.contains(token) {
          error!("frontend [{}] got timeout, closing", timeout);
          self.close_client(event_loop, token);
        }
      },
      Some(SocketType::BackClient) => {
        if let Some(tok) = self.get_client_token(token) {
          error!("backend [{}] got timeout, closing", timeout);
          self.close_client(event_loop, tok);
        }
      }
      None => {}
    }
  }

  fn interrupted(&mut self, event_loop: &mut EventLoop<Self>) {
    warn!("interrupted");
  }
}

<|endoftext|>