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

use std::collections::HashMap;
use std::error;
use std::path::{Path, PathBuf};
use std::result;
use std::str::{self, FromStr};

use libarchive::writer;
use libarchive::reader::{self, Reader};
use libarchive::archive::{Entry, ReadFilter, ReadFormat, ExtractOption, ExtractOptions};
use regex::Regex;

use error::{Error, Result};
use crypto::{artifact, hash};
use package::{Identifiable, PackageIdent, MetaFile};

lazy_static! {
    static ref METAFILE_REGXS: HashMap<MetaFile, Regex> = {
        let mut map = HashMap::new();
        map.insert(MetaFile::CFlags, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::CFlags)).unwrap());
        map.insert(MetaFile::Config, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Config)).unwrap());
        map.insert(MetaFile::Deps, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Deps)).unwrap());
        map.insert(MetaFile::TDeps, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::TDeps)).unwrap());
        map.insert(MetaFile::Exposes, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Exposes)).unwrap());
        map.insert(MetaFile::Ident, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Ident)).unwrap());
        map.insert(MetaFile::LdRunPath, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::LdRunPath)).unwrap());
        map.insert(MetaFile::LdFlags, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::LdFlags)).unwrap());
        map.insert(MetaFile::Manifest, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Manifest)).unwrap());
        map.insert(MetaFile::Path, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Path)).unwrap());
        map
    };
}

type Metadata = HashMap<MetaFile, String>;

#[derive(Debug)]
pub struct PackageArchive {
    pub path: PathBuf,
    metadata: Option<Metadata>,
}

impl PackageArchive {
    pub fn new<P: Into<PathBuf>>(path: P) -> Self {
        PackageArchive {
            path: path.into(),
            metadata: None,
        }
    }

    /// Calculate and return the checksum of the package archive in base64 format.
    ///
    /// # Failures
    ///
    /// * If the archive cannot be read
    pub fn checksum(&self) -> Result<String> {
        hash::hash_file(&self.path)
    }

    pub fn cflags(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::CFlags) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    pub fn config(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::Config) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    /// Returns a list of package identifiers representing the runtime package dependencies for
    /// this archive.
    ///
    /// # Failures
    ///
    /// * If a `DEPS` metafile is not found in the archive
    /// * If the archive cannot be read
    /// * If the archive cannot be verified
    pub fn deps(&mut self) -> Result<Vec<PackageIdent>> {
        self.read_deps(MetaFile::Deps)
    }

    /// Returns a list of package identifiers representing the transitive runtime package
    /// dependencies for this archive.
    ///
    /// # Failures
    ///
    /// * If a `TDEPS` metafile is not found in the archive
    /// * If the archive cannot be read
    /// * If the archive cannot be verified
    pub fn tdeps(&mut self) -> Result<Vec<PackageIdent>> {
        self.read_deps(MetaFile::TDeps)
    }

    pub fn exposes(&mut self) -> Result<Vec<u16>> {
        match self.read_metadata(MetaFile::Exposes) {
            Ok(Some(data)) => {
                let ports: Vec<u16> = data.split(" ")
                    .filter_map(|port| port.parse::<u16>().ok())
                    .collect();
                Ok(ports)
            }
            Ok(None) => Ok(vec![]),
            Err(e) => Err(e),
        }
    }

    pub fn ident(&mut self) -> Result<PackageIdent> {
        match self.read_metadata(MetaFile::Ident) {
            Ok(None) => Err(Error::MetaFileNotFound(MetaFile::Ident)),
            Ok(Some(data)) => PackageIdent::from_str(&data),
            Err(e) => Err(e),
        }
    }

    pub fn ld_run_path(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::LdRunPath) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    pub fn ldflags(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::LdFlags) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    pub fn manifest(&mut self) -> Result<String> {
        match self.read_metadata(MetaFile::Manifest) {
            Ok(None) => Err(Error::MetaFileNotFound(MetaFile::Manifest)),
            Ok(Some(data)) => Ok(data.clone()),
            Err(e) => Err(e),
        }
    }

    pub fn path(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::Path) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    /// A plain string representation of the archive's file name.
    pub fn file_name(&self) -> String {
        self.path.file_name().unwrap().to_string_lossy().into_owned()
    }

    /// Given a package name and a path to a file as an `&str`, verify
    /// the files signature.
    ///
    /// # Failures
    ///
    /// * Fails if it cannot verify the signature for any reason
    pub fn verify<P: AsRef<Path>>(&self, cache_key_path: &P) -> Result<(String, String)> {
        artifact::verify(&self.path, cache_key_path)
    }

    /// Given a package name and a path to a file as an `&str`, unpack
    /// the package.
    ///
    /// # Failures
    ///
    /// * If the package cannot be unpacked
    pub fn unpack(&self, fs_root_path: Option<&Path>) -> Result<()> {
        let root = fs_root_path.unwrap_or(Path::new("/"));
        let tar_reader = try!(artifact::get_archive_reader(&self.path));
        let mut builder = reader::Builder::new();
        try!(builder.support_format(ReadFormat::Gnutar));
        try!(builder.support_filter(ReadFilter::Xz));
        let mut reader = try!(builder.open_stream(tar_reader));
        let writer = writer::Disk::new();
        let mut extract_options = ExtractOptions::new();
        extract_options.add(ExtractOption::Time);
        extract_options.add(ExtractOption::Owner);
        extract_options.add(ExtractOption::Permissions);
        try!(writer.set_options(&extract_options));
        try!(writer.set_standard_lookup());
        try!(writer.write(&mut reader, Some(root.to_string_lossy().as_ref())));
        try!(writer.close());
        Ok(())
    }

    fn read_deps(&mut self, file: MetaFile) -> Result<Vec<PackageIdent>> {
        let mut deps: Vec<PackageIdent> = vec![];
        match self.read_metadata(file) {
            Ok(Some(body)) => {
                debug!("body = [{}]", &body);
                let ids: Vec<String> = body.split("\n").map(|d| d.to_string()).collect();
                for id in &ids {
                    debug!("id = [{}]", &id);
                    let package = try!(PackageIdent::from_str(id));
                    if !package.fully_qualified() {
                        // JW TODO: use a more appropriate erorr to describe the invalid
                        // user input here. (user because a package was generated by a user
                        // and read into program)
                        return Err(Error::InvalidPackageIdent(package.to_string()));
                    }
                    deps.push(package);
                }
                Ok(deps)
            }
            Ok(None) => Ok(vec![]),
            Err(Error::MetaFileNotFound(_)) => Ok(deps),
            Err(e) => Err(e),
        }
    }

    fn read_metadata(&mut self, file: MetaFile) -> Result<Option<&String>> {
        if let Some(ref files) = self.metadata {
            return Ok(files.get(&file));
        }
        let mut metadata = Metadata::new();
        let mut matched_count = 0u8;
        let tar_reader = try!(artifact::get_archive_reader(&self.path));
        let mut builder = reader::Builder::new();
        try!(builder.support_format(ReadFormat::Gnutar));
        try!(builder.support_filter(ReadFilter::Xz));
        let mut reader = try!(builder.open_stream(tar_reader));
        loop {
            let mut matched_type: Option<MetaFile> = None;
            if let Some(entry) = reader.next_header() {
                for (matched, regx) in METAFILE_REGXS.iter() {
                    if regx.is_match(entry.pathname()) {
                        matched_type = Some((*matched).clone());
                        matched_count += 1;
                        break;
                    }
                }
            } else {
                break;
            }

            if matched_type.is_none() {
                continue;
            }

            let mut buf = String::new();
            loop {
                match reader.read_block() {
                    Ok(Some(bytes)) => {
                        match str::from_utf8(bytes) {
                            Ok(content) => {
                                buf.push_str(content.trim());
                            }
                            Err(_) => return Err(Error::MetaFileMalformed(matched_type.unwrap())),
                        }
                    }
                    Ok(None) => {
                        debug!("content = {}", &buf);
                        metadata.insert(matched_type.unwrap(), buf);
                        break;
                    }
                    Err(_) => return Err(Error::MetaFileMalformed(matched_type.unwrap())),
                }
            }//inner loop

            if matched_count == METAFILE_REGXS.len() as u8 {
                break;
            }
        }
        self.metadata = Some(metadata);
        Ok(self.metadata.as_ref().unwrap().get(&file))
    }
}

pub trait FromArchive: Sized {
    type Error: error::Error;

    fn from_archive(archive: &mut PackageArchive) -> result::Result<Self, Self::Error>;
}

#[cfg(test)]
mod test {
    use std::path::PathBuf;
    use super::*;

    #[test]
    fn reading_artifact_metadata() {
        let mut hart = PackageArchive::new(fixtures()
            .join("happyhumans-possums-8.1.4-20160427165340-x86_64-linux.hart"));
        let ident = hart.ident().unwrap();
        assert_eq!(ident.origin, "happyhumans");
        assert_eq!(ident.name, "possums");
        assert_eq!(ident.version, Some("8.1.4".to_string()));
        assert_eq!(ident.release, Some("20160427165340".to_string()));
    }

    pub fn root() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests")
    }

    pub fn fixtures() -> PathBuf {
        root().join("fixtures")
    }

    #[test]
    fn reading_artifact_deps() {
        let mut hart = PackageArchive::new(fixtures()
            .join("happyhumans-possums-8.1.4-20160427165340-x86_64-linux.hart"));
        let _ = hart.deps().unwrap();
        let _ = hart.tdeps().unwrap();
    }

    #[test]
    fn reading_artifact_large_tdeps() {
        let mut hart = PackageArchive::new(fixtures()
            .join("unhappyhumans-possums-8.1.4-20160427165340-x86_64-linux.hart"));
        let tdeps = hart.tdeps().unwrap();
        assert_eq!(1024, tdeps.len());
    }

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

use std::collections::HashMap;
use std::error;
use std::path::{Path, PathBuf};
use std::result;
use std::str::{self, FromStr};

use libarchive::writer;
use libarchive::reader::{self, Reader};
use libarchive::archive::{Entry, ReadFilter, ReadFormat, ExtractOption, ExtractOptions};
use regex::Regex;

use error::{Error, Result};
use crypto::{artifact, hash};
use package::{Identifiable, PackageIdent, MetaFile};

lazy_static! {
    static ref METAFILE_REGXS: HashMap<MetaFile, Regex> = {
        let mut map = HashMap::new();
        map.insert(MetaFile::CFlags, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::CFlags)).unwrap());
        map.insert(MetaFile::Config, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Config)).unwrap());
        map.insert(MetaFile::Deps, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Deps)).unwrap());
        map.insert(MetaFile::TDeps, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::TDeps)).unwrap());
        map.insert(MetaFile::Exposes, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Exposes)).unwrap());
        map.insert(MetaFile::Ident, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Ident)).unwrap());
        map.insert(MetaFile::LdRunPath, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::LdRunPath)).unwrap());
        map.insert(MetaFile::LdFlags, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::LdFlags)).unwrap());
        map.insert(MetaFile::Manifest, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Manifest)).unwrap());
        map.insert(MetaFile::Path, Regex::new(&format!(r"^hab/pkgs/([^/]+)/([^/]+)/([^/]+)/([^/]+)/{}$", MetaFile::Path)).unwrap());
        map
    };
}

type Metadata = HashMap<MetaFile, String>;

#[derive(Debug)]
pub struct PackageArchive {
    pub path: PathBuf,
    metadata: Option<Metadata>,
}

impl PackageArchive {
    pub fn new<P: Into<PathBuf>>(path: P) -> Self {
        PackageArchive {
            path: path.into(),
            metadata: None,
        }
    }

    /// Calculate and return the checksum of the package archive in base64 format.
    ///
    /// # Failures
    ///
    /// * If the archive cannot be read
    pub fn checksum(&self) -> Result<String> {
        hash::hash_file(&self.path)
    }

    pub fn cflags(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::CFlags) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    pub fn config(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::Config) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    /// Returns a list of package identifiers representing the runtime package dependencies for
    /// this archive.
    ///
    /// # Failures
    ///
    /// * If a `DEPS` metafile is not found in the archive
    /// * If the archive cannot be read
    /// * If the archive cannot be verified
    pub fn deps(&mut self) -> Result<Vec<PackageIdent>> {
        self.read_deps(MetaFile::Deps)
    }

    /// Returns a list of package identifiers representing the transitive runtime package
    /// dependencies for this archive.
    ///
    /// # Failures
    ///
    /// * If a `TDEPS` metafile is not found in the archive
    /// * If the archive cannot be read
    /// * If the archive cannot be verified
    pub fn tdeps(&mut self) -> Result<Vec<PackageIdent>> {
        self.read_deps(MetaFile::TDeps)
    }

    pub fn exposes(&mut self) -> Result<Vec<u16>> {
        match self.read_metadata(MetaFile::Exposes) {
            Ok(Some(data)) => {
                let ports: Vec<u16> = data.split(" ")
                    .filter_map(|port| port.parse::<u16>().ok())
                    .collect();
                Ok(ports)
            }
            Ok(None) => Ok(vec![]),
            Err(e) => Err(e),
        }
    }

    pub fn ident(&mut self) -> Result<PackageIdent> {
        match self.read_metadata(MetaFile::Ident) {
            Ok(None) => Err(Error::MetaFileNotFound(MetaFile::Ident)),
            Ok(Some(data)) => PackageIdent::from_str(&data),
            Err(e) => Err(e),
        }
    }

    pub fn ld_run_path(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::LdRunPath) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    pub fn ldflags(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::LdFlags) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    pub fn manifest(&mut self) -> Result<String> {
        match self.read_metadata(MetaFile::Manifest) {
            Ok(None) => Err(Error::MetaFileNotFound(MetaFile::Manifest)),
            Ok(Some(data)) => Ok(data.clone()),
            Err(e) => Err(e),
        }
    }

    pub fn path(&mut self) -> Result<Option<String>> {
        match self.read_metadata(MetaFile::Path) {
            Ok(data) => Ok(data.cloned()),
            Err(e) => Err(e),
        }
    }

    /// A plain string representation of the archive's file name.
    pub fn file_name(&self) -> String {
        self.path.file_name().unwrap().to_string_lossy().into_owned()
    }

    /// Given a package name and a path to a file as an `&str`, verify
    /// the files signature.
    ///
    /// # Failures
    ///
    /// * Fails if it cannot verify the signature for any reason
    pub fn verify<P: AsRef<Path>>(&self, cache_key_path: &P) -> Result<(String, String)> {
        artifact::verify(&self.path, cache_key_path)
    }

    /// Given a package name and a path to a file as an `&str`, unpack
    /// the package.
    ///
    /// # Failures
    ///
    /// * If the package cannot be unpacked
    pub fn unpack(&self, fs_root_path: Option<&Path>) -> Result<()> {
        let root = fs_root_path.unwrap_or(Path::new("/"));
        let tar_reader = try!(artifact::get_archive_reader(&self.path));
        let mut builder = reader::Builder::new();
        try!(builder.support_format(ReadFormat::Gnutar));
        try!(builder.support_filter(ReadFilter::Xz));
        let mut reader = try!(builder.open_stream(tar_reader));
        let writer = writer::Disk::new();
        let mut extract_options = ExtractOptions::new();
        extract_options.add(ExtractOption::Time);
        extract_options.add(ExtractOption::Owner);
        extract_options.add(ExtractOption::Permissions);
        try!(writer.set_options(&extract_options));
        try!(writer.set_standard_lookup());
        try!(writer.write(&mut reader, Some(root.to_string_lossy().as_ref())));
        try!(writer.close());
        Ok(())
    }

    fn read_deps(&mut self, file: MetaFile) -> Result<Vec<PackageIdent>> {
        let mut deps: Vec<PackageIdent> = vec![];
        let filename = format!("{}", &file);
        match self.read_metadata(file) {
            Ok(Some(body)) => {
                debug!("body {} = [{}]", &filename, &body);
                let ids: Vec<String> = body.lines().map(|d| d.to_string()).collect();
                for id in &ids {
                    debug!("id {} = [{}]", &filename, &id);
                    let package = try!(PackageIdent::from_str(id));
                    if !package.fully_qualified() {
                        // JW TODO: use a more appropriate erorr to describe the invalid
                        // user input here. (user because a package was generated by a user
                        // and read into program)
                        return Err(Error::InvalidPackageIdent(package.to_string()));
                    }
                    deps.push(package);
                }
                Ok(deps)
            }
            Ok(None) => Ok(vec![]),
            Err(Error::MetaFileNotFound(_)) => Ok(deps),
            Err(e) => Err(e),
        }
    }

    fn read_metadata(&mut self, file: MetaFile) -> Result<Option<&String>> {
        if let Some(ref files) = self.metadata {
            return Ok(files.get(&file));
        }
        let mut metadata = Metadata::new();
        let mut matched_count = 0u8;
        let tar_reader = try!(artifact::get_archive_reader(&self.path));
        let mut builder = reader::Builder::new();
        try!(builder.support_format(ReadFormat::Gnutar));
        try!(builder.support_filter(ReadFilter::Xz));
        let mut reader = try!(builder.open_stream(tar_reader));
        loop {
            let mut matched_type: Option<MetaFile> = None;
            if let Some(entry) = reader.next_header() {
                for (matched, regx) in METAFILE_REGXS.iter() {
                    if regx.is_match(entry.pathname()) {
                        matched_type = Some((*matched).clone());
                        matched_count += 1;
                        break;
                    }
                }
            } else {
                break;
            }

            if matched_type.is_none() {
                continue;
            }

            let mut buf = String::new();
            loop {
                match reader.read_block() {
                    Ok(Some(bytes)) => {
                        match str::from_utf8(bytes) {
                            Ok(content) => {
                                // You used to trim. Now you don't, because you were trimming
                                // in the wrong place. Sometimes a buffer ends (or starts!) with
                                // a newline.
                                buf.push_str(content);
                            }
                            Err(_) => return Err(Error::MetaFileMalformed(matched_type.unwrap())),
                        }
                    }
                    Ok(None) => {
                        debug!("content = {}", &buf);
                        // Hey, before you go - we are trimming whitespace for you. This
                        // is handy, because later on, you just want the string you want.
                        metadata.insert(matched_type.unwrap(), String::from(buf.trim()));
                        break;
                    }
                    Err(_) => return Err(Error::MetaFileMalformed(matched_type.unwrap())),
                }
            }//inner loop

            if matched_count == METAFILE_REGXS.len() as u8 {
                break;
            }
        }
        self.metadata = Some(metadata);
        Ok(self.metadata.as_ref().unwrap().get(&file))
    }
}

pub trait FromArchive: Sized {
    type Error: error::Error;

    fn from_archive(archive: &mut PackageArchive) -> result::Result<Self, Self::Error>;
}

#[cfg(test)]
mod test {
    use std::path::PathBuf;
    use super::*;

    #[test]
    fn reading_artifact_metadata() {
        let mut hart = PackageArchive::new(fixtures()
            .join("happyhumans-possums-8.1.4-20160427165340-x86_64-linux.hart"));
        let ident = hart.ident().unwrap();
        assert_eq!(ident.origin, "happyhumans");
        assert_eq!(ident.name, "possums");
        assert_eq!(ident.version, Some("8.1.4".to_string()));
        assert_eq!(ident.release, Some("20160427165340".to_string()));
    }

    pub fn root() -> PathBuf {
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests")
    }

    pub fn fixtures() -> PathBuf {
        root().join("fixtures")
    }

    #[test]
    fn reading_artifact_deps() {
        let mut hart = PackageArchive::new(fixtures()
            .join("happyhumans-possums-8.1.4-20160427165340-x86_64-linux.hart"));
        let _ = hart.deps().unwrap();
        let _ = hart.tdeps().unwrap();
    }

    #[test]
    fn reading_artifact_large_tdeps() {
        let mut hart = PackageArchive::new(fixtures()
            .join("unhappyhumans-possums-8.1.4-20160427165340-x86_64-linux.hart"));
        let tdeps = hart.tdeps().unwrap();
        assert_eq!(1024, tdeps.len());
    }

}
<|endoftext|>


<commit_before>use std::io::Write;
use std::os::unix::process::CommandExt;
use std::process::Command;

const SYSTEMD_RUN: &'static str = "/usr/bin/systemd-run";
const FIREJAIL: &'static str = "/usr/bin/firejail";

const FIREFOX: &'static str = "/usr/bin/firefox";
const FIREFOX_PROFILE: &'static str = "/etc/firejail/firefox.profile";
const FIREFOX_MOZILLA_SFW_PROFILE: &'static str = "default-1473025815439";

const FIREFOX_MEMORY_HIGH: i32 = 4420;
const FIREFOX_MEMORY_MAX: i32 = 5200;

const KEIRA: &'static str = "/home/kalvin/Downloads/.firefox-nightly/firefox";
const KEIRA_PROFILE: &'static str = "/etc/firejail/firefox-nightly.profile";

const TERM: &'static str = "/usr/bin/alacritty";
const TERM_PROFILE: &'static str = "/etc/firejail/x-terminal-emulator.profile";

const THUNDERBIRD: &'static str = "/home/kalvin/Downloads/.thunderbird-beta/thunderbird";
const THUNDERBIRD_PROFILE: &'static str = "/etc/firejail/thunderbird-beta.profile";

const QUODLIBET_FIFO: &'static str = "/home/kalvin/.config/quodlibet/control";

const SWAYMSG: &'static str = "/usr/bin/swaymsg";
const SWAY_EXTERNAL_DISPLAY: &'static str = "HDMI-A-1";

#[derive(Debug)]
struct CgroupedFirejailedCommandOptions<'a> {
    bin_path: &'a str,
    memory_high: Option<i32>,
    memory_max: Option<i32>,
    firejail_profile: Option<&'a str>,
    implicit_extra_args: Option<&'a [&'a str]>,
    argv_remainder: Vec<String>,
}

fn cgrouped_firejail_command(options: CgroupedFirejailedCommandOptions) -> Command {
    let mut command = Command::new(SYSTEMD_RUN);
    command.args(&["--user", "--scope"]);
    command.args(&["-p", "MemorySwapMax=0"]);

    if let Some(memory_high) = options.memory_high {
        let memory_high_owned_string = format!("MemoryHigh={}M", memory_high);
        command.args(&["-p", memory_high_owned_string.as_str()]);
    }
    if let Some(memory_max) = options.memory_max {
        let memory_max_owned_string = format!("MemoryMax={}M", memory_max);
        command.args(&["-p", memory_max_owned_string.as_str()]);
    }

    if let Some(firejail_profile) = options.firejail_profile {
        command.arg(FIREJAIL);
        command.arg("--ignore=seccomp");
        command.arg(format!("--profile={}", firejail_profile));
    }

    command.arg(options.bin_path);

    if let Some(implicit_extra_args) = options.implicit_extra_args {
        command.args(implicit_extra_args);
    }

    command.args(options.argv_remainder);

    command
}

fn simple_firejail_command(target: &str, args: Vec<String>) -> Command {
    let mut command = Command::new(FIREJAIL);

    command.arg(format!("--profile=/etc/firejail/{}.profile", target));
    command.arg(format!("/usr/bin/{}", target));
    command.args(args);

    command
}

fn swaymsg_display_command(args: Vec<String>) -> Command {
    let mut command = Command::new(SWAYMSG);
    command.arg(format!(
        "output {} {}",
        SWAY_EXTERNAL_DISPLAY,
        match args[0].as_str() {
            "e" => "enable",
            "d" => "disable",
            _ => panic!(
                "bad argument for swaymsg ``output'' command: ``{}''",
                args[0].as_str()
            ),
        }
    ));

    command
}

fn quodlibet_command(args: Vec<String>) -> Command {
    let mut file = std::fs::OpenOptions::new()
        .append(true)
        .open(QUODLIBET_FIFO)
        .unwrap();
    file.write_all(match args[0].as_str() {
        "pp" => "play-pause".as_bytes(),
        "sa" => "stop-after 1".as_bytes(),
        _ => panic!(
            "bad argument for quodlibet command: ``{}''",
            args[0].as_str()
        ),
    })
    .unwrap();

    Command::new("/bin/true")
}

fn init_command() -> Command {
    let mut args_less_the_first = std::env::args();
    // Pops the name of this executable.
    args_less_the_first.next();

    // Pops the name of the unner target.
    let target = args_less_the_first.next().unwrap();

    // Copies and collects the remaining argv.
    let args: Vec<String> = args_less_the_first.collect();

    match target.as_str() {
        "d" => return swaymsg_display_command(args),
        "ff" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: FIREFOX,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(FIREFOX_PROFILE),
                implicit_extra_args: Some(&["-P", FIREFOX_MOZILLA_SFW_PROFILE]),
                argv_remainder: args,
            });
        }
        "keira" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: KEIRA,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(KEIRA_PROFILE),
                implicit_extra_args: Some(&["-P", "nightly"]),
                argv_remainder: args,
            });
        }
        "npv" => return simple_firejail_command("mpv", args),
        "q" => return quodlibet_command(args),
        "read" => {
            let formatted_args: Vec<String> = vec![format!("about:reader?url={}", args[0])];
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: FIREFOX,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(FIREFOX_PROFILE),
                implicit_extra_args: Some(&["-P", FIREFOX_MOZILLA_SFW_PROFILE]),
                argv_remainder: formatted_args,
            });
        }
        "t" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: TERM,
                memory_high: None,
                memory_max: None,
                firejail_profile: Some(TERM_PROFILE),
                implicit_extra_args: None,
                argv_remainder: args,
            })
        }
        "tbb" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: THUNDERBIRD,
                memory_high: None,
                memory_max: None,
                firejail_profile: Some(THUNDERBIRD_PROFILE),
                implicit_extra_args: None,
                argv_remainder: args,
            });
        }
        "vlk" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: "/usr/bin/vlc",
                memory_high: None,
                memory_max: None,
                firejail_profile: Some("/etc/firejail/vlc.profile"),
                implicit_extra_args: None,
                argv_remainder: args,
            })
        }
        "e" => return simple_firejail_command("evince", args),
        _ => panic!("no handler for ``{}''", target),
    }
}

fn main() {
    let mut command = init_command();
    println!("{:#?}", command);
    command.exec();
}
<commit_msg>unner: Erase swaymsg commands<commit_after>use std::io::Write;
use std::os::unix::process::CommandExt;
use std::process::Command;

const SYSTEMD_RUN: &'static str = "/usr/bin/systemd-run";
const FIREJAIL: &'static str = "/usr/bin/firejail";

const FIREFOX: &'static str = "/usr/bin/firefox";
const FIREFOX_PROFILE: &'static str = "/etc/firejail/firefox.profile";
const FIREFOX_MOZILLA_SFW_PROFILE: &'static str = "default-1473025815439";

const FIREFOX_MEMORY_HIGH: i32 = 4420;
const FIREFOX_MEMORY_MAX: i32 = 5200;

const KEIRA: &'static str = "/home/kalvin/Downloads/.firefox-nightly/firefox";
const KEIRA_PROFILE: &'static str = "/etc/firejail/firefox-nightly.profile";

const TERM: &'static str = "/usr/bin/alacritty";
const TERM_PROFILE: &'static str = "/etc/firejail/x-terminal-emulator.profile";

const THUNDERBIRD: &'static str = "/home/kalvin/Downloads/.thunderbird-beta/thunderbird";
const THUNDERBIRD_PROFILE: &'static str = "/etc/firejail/thunderbird-beta.profile";

const QUODLIBET_FIFO: &'static str = "/home/kalvin/.config/quodlibet/control";

#[derive(Debug)]
struct CgroupedFirejailedCommandOptions<'a> {
    bin_path: &'a str,
    memory_high: Option<i32>,
    memory_max: Option<i32>,
    firejail_profile: Option<&'a str>,
    implicit_extra_args: Option<&'a [&'a str]>,
    argv_remainder: Vec<String>,
}

fn cgrouped_firejail_command(options: CgroupedFirejailedCommandOptions) -> Command {
    let mut command = Command::new(SYSTEMD_RUN);
    command.args(&["--user", "--scope"]);
    command.args(&["-p", "MemorySwapMax=0"]);

    if let Some(memory_high) = options.memory_high {
        let memory_high_owned_string = format!("MemoryHigh={}M", memory_high);
        command.args(&["-p", memory_high_owned_string.as_str()]);
    }
    if let Some(memory_max) = options.memory_max {
        let memory_max_owned_string = format!("MemoryMax={}M", memory_max);
        command.args(&["-p", memory_max_owned_string.as_str()]);
    }

    if let Some(firejail_profile) = options.firejail_profile {
        command.arg(FIREJAIL);
        command.arg("--ignore=seccomp");
        command.arg(format!("--profile={}", firejail_profile));
    }

    command.arg(options.bin_path);

    if let Some(implicit_extra_args) = options.implicit_extra_args {
        command.args(implicit_extra_args);
    }

    command.args(options.argv_remainder);

    command
}

fn simple_firejail_command(target: &str, args: Vec<String>) -> Command {
    let mut command = Command::new(FIREJAIL);

    command.arg(format!("--profile=/etc/firejail/{}.profile", target));
    command.arg(format!("/usr/bin/{}", target));
    command.args(args);

    command
}

fn quodlibet_command(args: Vec<String>) -> Command {
    let mut file = std::fs::OpenOptions::new()
        .append(true)
        .open(QUODLIBET_FIFO)
        .unwrap();
    file.write_all(match args[0].as_str() {
        "pp" => "play-pause".as_bytes(),
        "sa" => "stop-after 1".as_bytes(),
        _ => panic!(
            "bad argument for quodlibet command: ``{}''",
            args[0].as_str()
        ),
    })
    .unwrap();

    Command::new("/bin/true")
}

fn init_command() -> Command {
    let mut args_less_the_first = std::env::args();
    // Pops the name of this executable.
    args_less_the_first.next();

    // Pops the name of the unner target.
    let target = args_less_the_first.next().unwrap();

    // Copies and collects the remaining argv.
    let args: Vec<String> = args_less_the_first.collect();

    match target.as_str() {
        "ff" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: FIREFOX,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(FIREFOX_PROFILE),
                implicit_extra_args: Some(&["-P", FIREFOX_MOZILLA_SFW_PROFILE]),
                argv_remainder: args,
            });
        }
        "keira" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: KEIRA,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(KEIRA_PROFILE),
                implicit_extra_args: Some(&["-P", "nightly"]),
                argv_remainder: args,
            });
        }
        "npv" => return simple_firejail_command("mpv", args),
        "q" => return quodlibet_command(args),
        "read" => {
            let formatted_args: Vec<String> = vec![format!("about:reader?url={}", args[0])];
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: FIREFOX,
                memory_high: Some(FIREFOX_MEMORY_HIGH),
                memory_max: Some(FIREFOX_MEMORY_MAX),
                firejail_profile: Some(FIREFOX_PROFILE),
                implicit_extra_args: Some(&["-P", FIREFOX_MOZILLA_SFW_PROFILE]),
                argv_remainder: formatted_args,
            });
        }
        "t" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: TERM,
                memory_high: None,
                memory_max: None,
                firejail_profile: Some(TERM_PROFILE),
                implicit_extra_args: None,
                argv_remainder: args,
            })
        }
        "tbb" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: THUNDERBIRD,
                memory_high: None,
                memory_max: None,
                firejail_profile: Some(THUNDERBIRD_PROFILE),
                implicit_extra_args: None,
                argv_remainder: args,
            });
        }
        "vlk" => {
            return cgrouped_firejail_command(CgroupedFirejailedCommandOptions {
                bin_path: "/usr/bin/vlc",
                memory_high: None,
                memory_max: None,
                firejail_profile: Some("/etc/firejail/vlc.profile"),
                implicit_extra_args: None,
                argv_remainder: args,
            })
        }
        "e" => return simple_firejail_command("evince", args),
        _ => panic!("no handler for ``{}''", target),
    }
}

fn main() {
    let mut command = init_command();
    println!("{:#?}", command);
    command.exec();
}
<|endoftext|>


<commit_before>use api::join;
use std;
use std::marker::PhantomData;
use super::ParallelIterator;
use super::len::*;
use super::state::*;
use super::util::PhantomType;

/// Specifies a "reduce operator". This is the combination of a start
/// value and a reduce function. The reduce function takes two items
/// and computes a reduced version. The start value `S` is a kind of
/// "zero" or "identity" value that may be intermingled as needed;
/// idealy, `reduce(S, X)` for any item `X` yields `X`.
///
/// Example: to sum up the values, use a `start_value` of `0` and a
/// reduce function of `reduce(a, b) = a + b`.
///
/// The order in which the reduce function will be applied is not
/// specified. For example, the input `[ 0 1 2 ]` might be reduced in a
/// sequential fashion:
///
/// ```ignore
/// reduce(reduce(reduce(S, 0), 1), 2)
/// ```
///
/// or it might be reduced in a tree-like way:
///
/// ```ignore
/// reduce(reduce(0, 1), reduce(S, 2))
/// ```
///
/// etc.
pub trait ReduceOp<T>: Sync {
    fn start_value(&self) -> T;
    fn reduce(&self, value1: T, value2: T) -> T;
}

pub fn reduce<PAR_ITER,REDUCE_OP,T>(pi: PAR_ITER, reduce_op: &REDUCE_OP) -> T
    where PAR_ITER: ParallelIterator<Item=T>,
          PAR_ITER::State: Send,
          REDUCE_OP: ReduceOp<T>,
          T: Send,
{
    let consumer: ReduceConsumer<PAR_ITER, REDUCE_OP> = ReduceConsumer { data: PhantomType::new() };
    pi.drive(consumer, reduce_op)
}

struct ReduceConsumer<PAR_ITER, REDUCE_OP>
    where PAR_ITER: ParallelIterator,
          REDUCE_OP: ReduceOp<PAR_ITER::Item>,
{
    data: PhantomType<(PAR_ITER, REDUCE_OP)>
}

impl<PAR_ITER, REDUCE_OP> Copy for ReduceConsumer<PAR_ITER, REDUCE_OP>
    where PAR_ITER: ParallelIterator,
          REDUCE_OP: ReduceOp<PAR_ITER::Item>,
{
}

impl<PAR_ITER, REDUCE_OP> Clone for ReduceConsumer<PAR_ITER, REDUCE_OP>
    where PAR_ITER: ParallelIterator,
          REDUCE_OP: ReduceOp<PAR_ITER::Item>,
{
    fn clone(&self) -> Self { *self }
}

impl<'c, PAR_ITER, REDUCE_OP> Consumer<'c> for ReduceConsumer<PAR_ITER, REDUCE_OP>
    where PAR_ITER: ParallelIterator,
          REDUCE_OP: ReduceOp<PAR_ITER::Item>,
          PAR_ITER::Item: 'c,
          REDUCE_OP: 'c,
{
    type Item = PAR_ITER::Item;
    type Shared = REDUCE_OP;
    type SeqState = PAR_ITER::Item;
    type Result = PAR_ITER::Item;

    fn cost(&mut self, shared: &Self::Shared, cost: f64) -> f64 {
        // This isn't quite right, as we will do more than O(n) reductions, but whatever.
        cost * FUNC_ADJUSTMENT
    }

    unsafe fn split_at(self, _: &Self::Shared, _index: usize) -> (Self, Self) {
        (self, self)
    }

    unsafe fn start(&mut self, reduce_op: &REDUCE_OP) -> PAR_ITER::Item {
        reduce_op.start_value()
    }

    unsafe fn consume(&mut self,
                      reduce_op: &REDUCE_OP,
                      prev_value: PAR_ITER::Item,
                      item: PAR_ITER::Item)
                      -> PAR_ITER::Item {
        reduce_op.reduce(prev_value, item)
    }

    unsafe fn complete(self,
                       _reduce_op: &REDUCE_OP,
                       state: PAR_ITER::Item)
                       -> PAR_ITER::Item {
        state
    }

    unsafe fn reduce(reduce_op: &REDUCE_OP,
                     a: PAR_ITER::Item,
                     b: PAR_ITER::Item)
                     -> PAR_ITER::Item {
        reduce_op.reduce(a, b)
    }
}

///////////////////////////////////////////////////////////////////////////
// Specific operations

pub struct SumOp;

pub const SUM: &'static SumOp = &SumOp;

macro_rules! sum_rule {
    ($i:ty, $z:expr) => {
        impl ReduceOp<$i> for SumOp {
            fn start_value(&self) -> $i {
                $z
            }
            fn reduce(&self, value1: $i, value2: $i) -> $i {
                value1 + value2
            }
        }
    }
}

sum_rule!(i8, 0);
sum_rule!(i16, 0);
sum_rule!(i32, 0);
sum_rule!(i64, 0);
sum_rule!(isize, 0);
sum_rule!(u8, 0);
sum_rule!(u16, 0);
sum_rule!(u32, 0);
sum_rule!(u64, 0);
sum_rule!(usize, 0);
sum_rule!(f32, 0.0);
sum_rule!(f64, 0.0);

pub struct MulOp;

pub const MUL: &'static MulOp = &MulOp;

macro_rules! mul_rule {
    ($i:ty, $z:expr) => {
        impl ReduceOp<$i> for MulOp {
            fn start_value(&self) -> $i {
                $z
            }
            fn reduce(&self, value1: $i, value2: $i) -> $i {
                value1 * value2
            }
        }
    }
}

mul_rule!(i8, 1);
mul_rule!(i16, 1);
mul_rule!(i32, 1);
mul_rule!(i64, 1);
mul_rule!(isize, 1);
mul_rule!(u8, 1);
mul_rule!(u16, 1);
mul_rule!(u32, 1);
mul_rule!(u64, 1);
mul_rule!(usize, 1);
mul_rule!(f32, 1.0);
mul_rule!(f64, 1.0);

pub struct MinOp;

pub const MIN: &'static MinOp = &MinOp;

macro_rules! min_rule {
    ($i:ty, $z:expr, $f:expr) => {
        impl ReduceOp<$i> for MinOp {
            fn start_value(&self) -> $i {
                $z
            }
            fn reduce(&self, value1: $i, value2: $i) -> $i {
                $f(value1, value2)
            }
        }
    }
}

min_rule!(i8, std::i8::MAX, std::cmp::min);
min_rule!(i16, std::i16::MAX, std::cmp::min);
min_rule!(i32, std::i32::MAX, std::cmp::min);
min_rule!(i64, std::i64::MAX, std::cmp::min);
min_rule!(isize, std::isize::MAX, std::cmp::min);
min_rule!(u8, std::u8::MAX, std::cmp::min);
min_rule!(u16, std::u16::MAX, std::cmp::min);
min_rule!(u32, std::u32::MAX, std::cmp::min);
min_rule!(u64, std::u64::MAX, std::cmp::min);
min_rule!(usize, std::usize::MAX, std::cmp::min);
min_rule!(f32, std::f32::INFINITY, f32::min);
min_rule!(f64, std::f64::INFINITY, f64::min);

pub struct MaxOp;

pub const MAX: &'static MaxOp = &MaxOp;

macro_rules! max_rule {
    ($i:ty, $z:expr, $f:expr) => {
        impl ReduceOp<$i> for MaxOp {
            fn start_value(&self) -> $i {
                $z
            }
            fn reduce(&self, value1: $i, value2: $i) -> $i {
                $f(value1, value2)
            }
        }
    }
}

max_rule!(i8, std::i8::MIN, std::cmp::max);
max_rule!(i16, std::i16::MIN, std::cmp::max);
max_rule!(i32, std::i32::MIN, std::cmp::max);
max_rule!(i64, std::i64::MIN, std::cmp::max);
max_rule!(isize, std::isize::MIN, std::cmp::max);
max_rule!(u8, std::u8::MIN, std::cmp::max);
max_rule!(u16, std::u16::MIN, std::cmp::max);
max_rule!(u32, std::u32::MIN, std::cmp::max);
max_rule!(u64, std::u64::MIN, std::cmp::max);
max_rule!(usize, std::usize::MIN, std::cmp::max);
max_rule!(f32, std::f32::NEG_INFINITY, f32::max);
max_rule!(f64, std::f64::NEG_INFINITY, f64::max);

pub struct ReduceWithOp<OP> {
    op: OP
}

impl<OP> ReduceWithOp<OP> {
    pub fn new(op: OP) -> ReduceWithOp<OP> {
        ReduceWithOp {
            op: op
        }
    }
}

impl<ITEM,OP> ReduceOp<Option<ITEM>> for ReduceWithOp<OP>
    where OP: Fn(ITEM, ITEM) -> ITEM + Sync
{
    fn start_value(&self) -> Option<ITEM> {
        None
    }

    fn reduce(&self, value1: Option<ITEM>, value2: Option<ITEM>) -> Option<ITEM> {
        if let Some(value1) = value1 {
            if let Some(value2) = value2 {
                Some((self.op)(value1, value2))
            } else {
                Some(value1)
            }
        } else {
            value2
        }
    }
}
<commit_msg>Simplify reduce Consumer<commit_after>use api::join;
use std;
use std::marker::PhantomData;
use super::ParallelIterator;
use super::len::*;
use super::state::*;
use super::util::PhantomType;

/// Specifies a "reduce operator". This is the combination of a start
/// value and a reduce function. The reduce function takes two items
/// and computes a reduced version. The start value `S` is a kind of
/// "zero" or "identity" value that may be intermingled as needed;
/// idealy, `reduce(S, X)` for any item `X` yields `X`.
///
/// Example: to sum up the values, use a `start_value` of `0` and a
/// reduce function of `reduce(a, b) = a + b`.
///
/// The order in which the reduce function will be applied is not
/// specified. For example, the input `[ 0 1 2 ]` might be reduced in a
/// sequential fashion:
///
/// ```ignore
/// reduce(reduce(reduce(S, 0), 1), 2)
/// ```
///
/// or it might be reduced in a tree-like way:
///
/// ```ignore
/// reduce(reduce(0, 1), reduce(S, 2))
/// ```
///
/// etc.
pub trait ReduceOp<T>: Sync {
    fn start_value(&self) -> T;
    fn reduce(&self, value1: T, value2: T) -> T;
}

pub fn reduce<PAR_ITER,REDUCE_OP,T>(pi: PAR_ITER, reduce_op: &REDUCE_OP) -> T
    where PAR_ITER: ParallelIterator<Item=T>,
          PAR_ITER::State: Send,
          REDUCE_OP: ReduceOp<T>,
          T: Send,
{
    let consumer: ReduceConsumer<PAR_ITER::Item, REDUCE_OP> =
        ReduceConsumer { data: PhantomType::new() };
    pi.drive(consumer, reduce_op)
}

struct ReduceConsumer<ITEM, REDUCE_OP>
    where REDUCE_OP: ReduceOp<ITEM>,
{
    data: PhantomType<(ITEM, REDUCE_OP)>
}

impl<ITEM, REDUCE_OP> Copy for ReduceConsumer<ITEM, REDUCE_OP>
    where REDUCE_OP: ReduceOp<ITEM>,
{
}

impl<ITEM, REDUCE_OP> Clone for ReduceConsumer<ITEM, REDUCE_OP>
    where REDUCE_OP: ReduceOp<ITEM>,
{
    fn clone(&self) -> Self { *self }
}

impl<'c, ITEM, REDUCE_OP> Consumer<'c> for ReduceConsumer<ITEM, REDUCE_OP>
    where REDUCE_OP: ReduceOp<ITEM> + 'c,
          ITEM: Send + 'c,
{
    type Item = ITEM;
    type Shared = REDUCE_OP;
    type SeqState = ITEM;
    type Result = ITEM;

    fn cost(&mut self, shared: &Self::Shared, cost: f64) -> f64 {
        // This isn't quite right, as we will do more than O(n) reductions, but whatever.
        cost * FUNC_ADJUSTMENT
    }

    unsafe fn split_at(self, _: &Self::Shared, _index: usize) -> (Self, Self) {
        (self, self)
    }

    unsafe fn start(&mut self, reduce_op: &REDUCE_OP) -> ITEM {
        reduce_op.start_value()
    }

    unsafe fn consume(&mut self,
                      reduce_op: &REDUCE_OP,
                      prev_value: ITEM,
                      item: ITEM)
                      -> ITEM {
        reduce_op.reduce(prev_value, item)
    }

    unsafe fn complete(self,
                       _reduce_op: &REDUCE_OP,
                       state: ITEM)
                       -> ITEM {
        state
    }

    unsafe fn reduce(reduce_op: &REDUCE_OP,
                     a: ITEM,
                     b: ITEM)
                     -> ITEM {
        reduce_op.reduce(a, b)
    }
}

///////////////////////////////////////////////////////////////////////////
// Specific operations

pub struct SumOp;

pub const SUM: &'static SumOp = &SumOp;

macro_rules! sum_rule {
    ($i:ty, $z:expr) => {
        impl ReduceOp<$i> for SumOp {
            fn start_value(&self) -> $i {
                $z
            }
            fn reduce(&self, value1: $i, value2: $i) -> $i {
                value1 + value2
            }
        }
    }
}

sum_rule!(i8, 0);
sum_rule!(i16, 0);
sum_rule!(i32, 0);
sum_rule!(i64, 0);
sum_rule!(isize, 0);
sum_rule!(u8, 0);
sum_rule!(u16, 0);
sum_rule!(u32, 0);
sum_rule!(u64, 0);
sum_rule!(usize, 0);
sum_rule!(f32, 0.0);
sum_rule!(f64, 0.0);

pub struct MulOp;

pub const MUL: &'static MulOp = &MulOp;

macro_rules! mul_rule {
    ($i:ty, $z:expr) => {
        impl ReduceOp<$i> for MulOp {
            fn start_value(&self) -> $i {
                $z
            }
            fn reduce(&self, value1: $i, value2: $i) -> $i {
                value1 * value2
            }
        }
    }
}

mul_rule!(i8, 1);
mul_rule!(i16, 1);
mul_rule!(i32, 1);
mul_rule!(i64, 1);
mul_rule!(isize, 1);
mul_rule!(u8, 1);
mul_rule!(u16, 1);
mul_rule!(u32, 1);
mul_rule!(u64, 1);
mul_rule!(usize, 1);
mul_rule!(f32, 1.0);
mul_rule!(f64, 1.0);

pub struct MinOp;

pub const MIN: &'static MinOp = &MinOp;

macro_rules! min_rule {
    ($i:ty, $z:expr, $f:expr) => {
        impl ReduceOp<$i> for MinOp {
            fn start_value(&self) -> $i {
                $z
            }
            fn reduce(&self, value1: $i, value2: $i) -> $i {
                $f(value1, value2)
            }
        }
    }
}

min_rule!(i8, std::i8::MAX, std::cmp::min);
min_rule!(i16, std::i16::MAX, std::cmp::min);
min_rule!(i32, std::i32::MAX, std::cmp::min);
min_rule!(i64, std::i64::MAX, std::cmp::min);
min_rule!(isize, std::isize::MAX, std::cmp::min);
min_rule!(u8, std::u8::MAX, std::cmp::min);
min_rule!(u16, std::u16::MAX, std::cmp::min);
min_rule!(u32, std::u32::MAX, std::cmp::min);
min_rule!(u64, std::u64::MAX, std::cmp::min);
min_rule!(usize, std::usize::MAX, std::cmp::min);
min_rule!(f32, std::f32::INFINITY, f32::min);
min_rule!(f64, std::f64::INFINITY, f64::min);

pub struct MaxOp;

pub const MAX: &'static MaxOp = &MaxOp;

macro_rules! max_rule {
    ($i:ty, $z:expr, $f:expr) => {
        impl ReduceOp<$i> for MaxOp {
            fn start_value(&self) -> $i {
                $z
            }
            fn reduce(&self, value1: $i, value2: $i) -> $i {
                $f(value1, value2)
            }
        }
    }
}

max_rule!(i8, std::i8::MIN, std::cmp::max);
max_rule!(i16, std::i16::MIN, std::cmp::max);
max_rule!(i32, std::i32::MIN, std::cmp::max);
max_rule!(i64, std::i64::MIN, std::cmp::max);
max_rule!(isize, std::isize::MIN, std::cmp::max);
max_rule!(u8, std::u8::MIN, std::cmp::max);
max_rule!(u16, std::u16::MIN, std::cmp::max);
max_rule!(u32, std::u32::MIN, std::cmp::max);
max_rule!(u64, std::u64::MIN, std::cmp::max);
max_rule!(usize, std::usize::MIN, std::cmp::max);
max_rule!(f32, std::f32::NEG_INFINITY, f32::max);
max_rule!(f64, std::f64::NEG_INFINITY, f64::max);

pub struct ReduceWithOp<OP> {
    op: OP
}

impl<OP> ReduceWithOp<OP> {
    pub fn new(op: OP) -> ReduceWithOp<OP> {
        ReduceWithOp {
            op: op
        }
    }
}

impl<ITEM,OP> ReduceOp<Option<ITEM>> for ReduceWithOp<OP>
    where OP: Fn(ITEM, ITEM) -> ITEM + Sync
{
    fn start_value(&self) -> Option<ITEM> {
        None
    }

    fn reduce(&self, value1: Option<ITEM>, value2: Option<ITEM>) -> Option<ITEM> {
        if let Some(value1) = value1 {
            if let Some(value2) = value2 {
                Some((self.op)(value1, value2))
            } else {
                Some(value1)
            }
        } else {
            value2
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::error::Error;
use std::path::Path;
use std::process::Command;

use lazy_static::lazy_static;
use regex::Regex;
use uuid::Uuid;

lazy_static! {
    static ref ATTACHMENT_PATTERN: Regex = Regex::new(r"Attachment ID (\d+): .* file name '(.+)'").unwrap();
    static ref SEGMENT_UUID_REGEX: Regex = Regex::new(
        r"Segment UID: 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2})"
    ).unwrap();
    static ref FPS_PATTERN: Regex = Regex::new(r"Default duration:.+\((\d+\.\d+) frames/fields per second")
        .unwrap();
    static ref EDITION_FLAG_ORDERED_REGEX: Regex = Regex::new(r"Edition ?[fF]lag ?[oO]rdered: 1").unwrap();
    static ref CHAPTER_ATOM_REGEX: Regex = Regex::new(r"Chapter ?[aA]tom").unwrap();
    static ref TIME_START_REGEX: Regex = Regex::new(r"Chapter ?[tT]ime ?[sS]tart: (\d{2}):(\d{2}):(\d{2}).(\d{9})")
        .unwrap();
    static ref TIME_END_REGEX: Regex = Regex::new(r"Chapter ?[tT]ime ?[eE]nd: (\d{2}):(\d{2}):(\d{2}).(\d{9})").unwrap();
    static ref FOREIGN_UUID_REGEX: Regex = Regex::new(
        r"Chapter ?[sS]egment ?UID: length 16, data: 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2})"
    ).unwrap();
    static ref EBML_VOID_REGEX: Regex = Regex::new(r"(?:Ebml|EBML) ?[vV]oid").unwrap();
}

pub fn get_fonts_list(path: &Path) -> Result<HashMap<usize, String>, String> {
    let output = match Command::new("mkvmerge")
        .args(&["-i", path.to_str().unwrap()])
        .output()
    {
        Ok(x) => x,
        Err(x) => return Err(format!("{}", x)),
    };

    let mut attachments: HashMap<usize, String> = HashMap::new();
    for line in String::from_utf8(output.stdout).unwrap().lines() {
        if line.starts_with("Attachment")
            && (line.to_lowercase().contains(".ttf") || line.to_lowercase().contains(".otf"))
        {
            let captures = ATTACHMENT_PATTERN.captures(line).unwrap();
            attachments.insert(
                captures[1].parse::<usize>().unwrap(),
                captures[2].to_owned(),
            );
        }
    }

    Ok(attachments)
}

pub fn get_file_uuid(path: &Path) -> Result<Uuid, String> {
    let output = match Command::new("mkvinfo")
        .args(&[path.to_str().unwrap()])
        .output()
    {
        Ok(x) => x,
        Err(x) => return Err(x.description().to_owned()),
    };

    let output = String::from_utf8(output.stdout).unwrap();
    for line in output.lines() {
        if let Some(captures) = SEGMENT_UUID_REGEX.captures(line) {
            return Ok(Uuid::parse_str(
                &captures
                    .iter()
                    .skip(1)
                    .take(16)
                    .map(|m| m.unwrap().as_str())
                    .collect::<Vec<&str>>()
                    .concat(),
            )
            .unwrap());
        }
    }

    Err(format!(
        "No uuid found in {}, is this a valid Matroska file?",
        path.to_str().unwrap()
    ))
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum SectionType {
    Video,
    Chapters,
}

#[derive(Clone, Copy, Debug)]
pub struct BreakPoint {
    pub start_frame: u64,
    pub end_frame: u64,
    pub foreign_uuid: Option<Uuid>,
}

pub fn get_ordered_chapters_list(
    path: &Path,
    force_120_fps: bool,
) -> Result<Option<Vec<BreakPoint>>, String> {
    let output = match Command::new("mkvinfo")
        .args(&[path.to_str().unwrap()])
        .output()
    {
        Ok(x) => x,
        Err(x) => return Err(x.description().to_owned()),
    };

    let output = String::from_utf8(output.stdout).unwrap();
    let mut chapters: Vec<BreakPoint> = Vec::new();
    let mut video_fps: Option<f64> = if force_120_fps {
        Some(120_000. / 1_001.)
    } else {
        None
    };
    let mut current_section: Option<SectionType> = None;
    let mut current_chapter: Option<BreakPoint> = None;
    let mut ordered_chapters = false;
    for line in output.lines() {
        // Find video_fps
        if video_fps.is_none() {
            if current_section == Some(SectionType::Video) {
                if let Some(captures) = FPS_PATTERN.captures(line) {
                    video_fps = Some(captures[1].parse::<f64>().unwrap());
                }
            } else if line == "|  + Track type: video" {
                current_section = Some(SectionType::Video);
            }
            continue;
        }
        if current_section == Some(SectionType::Chapters) {
            if EDITION_FLAG_ORDERED_REGEX.is_match(line) {
                ordered_chapters = true;
                continue;
            }
            if CHAPTER_ATOM_REGEX.is_match(line) {
                if current_chapter.is_some() {
                    chapters.push(current_chapter.unwrap());
                }
                current_chapter = Some(BreakPoint {
                    start_frame: 0,
                    end_frame: 0,
                    foreign_uuid: None,
                });
                continue;
            }
            if current_chapter.is_some() {
                if let Some(captures) = TIME_START_REGEX.captures(line) {
                    current_chapter.as_mut().unwrap().start_frame = timestamp_to_frame_number(
                        captures[1].parse::<u64>().unwrap(),
                        captures[2].parse::<u64>().unwrap(),
                        captures[3].parse::<f64>().unwrap()
                            + captures[4].parse::<f64>().unwrap() / 1_000_000_000f64,
                        video_fps.unwrap(),
                    );
                    continue;
                }
                if let Some(captures) = TIME_END_REGEX.captures(line) {
                    current_chapter.as_mut().unwrap().end_frame = timestamp_to_frame_number(
                        captures[1].parse::<u64>().unwrap(),
                        captures[2].parse::<u64>().unwrap(),
                        captures[3].parse::<f64>().unwrap()
                            + captures[4].parse::<f64>().unwrap() / 1_000_000_000f64,
                        video_fps.unwrap(),
                    ) - 1;
                    continue;
                }
                if let Some(captures) = FOREIGN_UUID_REGEX.captures(line) {
                    current_chapter.as_mut().unwrap().foreign_uuid = Some(
                        Uuid::parse_str(
                            &captures
                                .iter()
                                .skip(1)
                                .take(16)
                                .map(|m| m.unwrap().as_str())
                                .collect::<Vec<&str>>()
                                .concat(),
                        )
                        .unwrap(),
                    );
                    continue;
                }
            }
            if EBML_VOID_REGEX.is_match(line) {
                if current_chapter.is_some() {
                    chapters.push(current_chapter.unwrap());
                }
                break;
            }
        } else if line == "|+ Chapters" {
            current_section = Some(SectionType::Chapters);
            continue;
        }
    }

    if !ordered_chapters {
        return Ok(None);
    }

    // Merge chapters
    let mut breakpoints: Vec<BreakPoint> = Vec::new();
    let mut iter = chapters.iter().peekable();
    let mut merging = BreakPoint {
        start_frame: 0,
        end_frame: 0,
        foreign_uuid: None,
    };
    while let Some(chapter) = iter.next() {
        if chapter.foreign_uuid.is_some() {
            breakpoints.push(chapter.clone());
            continue;
        }
        if merging.end_frame == 0 {
            merging.start_frame = chapter.start_frame;
        }
        merging.end_frame = chapter.end_frame;
        if let Some(next_chapter) = iter.peek() {
            if next_chapter.foreign_uuid.is_some() && merging.end_frame > 0 {
                breakpoints.push(merging);
                merging = BreakPoint {
                    start_frame: 0,
                    end_frame: 0,
                    foreign_uuid: None,
                };
            }
        } else {
            if merging.end_frame > 0 {
                breakpoints.push(merging);
            }
            break;
        }
    }

    Ok(Some(breakpoints))
}

fn timestamp_to_frame_number(hours: u64, minutes: u64, seconds: f64, fps: f64) -> u64 {
    ((seconds + 60f64 * minutes as f64 + 3600f64 * hours as f64) * fps).floor() as u64
}
<commit_msg>Fix new clippy warnings<commit_after>use std::collections::HashMap;
use std::error::Error;
use std::path::Path;
use std::process::Command;

use lazy_static::lazy_static;
use regex::Regex;
use uuid::Uuid;

lazy_static! {
    static ref ATTACHMENT_PATTERN: Regex = Regex::new(r"Attachment ID (\d+): .* file name '(.+)'").unwrap();
    static ref SEGMENT_UUID_REGEX: Regex = Regex::new(
        r"Segment UID: 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2})"
    ).unwrap();
    static ref FPS_PATTERN: Regex = Regex::new(r"Default duration:.+\((\d+\.\d+) frames/fields per second")
        .unwrap();
    static ref EDITION_FLAG_ORDERED_REGEX: Regex = Regex::new(r"Edition ?[fF]lag ?[oO]rdered: 1").unwrap();
    static ref CHAPTER_ATOM_REGEX: Regex = Regex::new(r"Chapter ?[aA]tom").unwrap();
    static ref TIME_START_REGEX: Regex = Regex::new(r"Chapter ?[tT]ime ?[sS]tart: (\d{2}):(\d{2}):(\d{2}).(\d{9})")
        .unwrap();
    static ref TIME_END_REGEX: Regex = Regex::new(r"Chapter ?[tT]ime ?[eE]nd: (\d{2}):(\d{2}):(\d{2}).(\d{9})").unwrap();
    static ref FOREIGN_UUID_REGEX: Regex = Regex::new(
        r"Chapter ?[sS]egment ?UID: length 16, data: 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2}) 0x([0-9a-f]{2})"
    ).unwrap();
    static ref EBML_VOID_REGEX: Regex = Regex::new(r"(?:Ebml|EBML) ?[vV]oid").unwrap();
}

pub fn get_fonts_list(path: &Path) -> Result<HashMap<usize, String>, String> {
    let output = match Command::new("mkvmerge")
        .args(&["-i", path.to_str().unwrap()])
        .output()
    {
        Ok(x) => x,
        Err(x) => return Err(format!("{}", x)),
    };

    let mut attachments: HashMap<usize, String> = HashMap::new();
    for line in String::from_utf8(output.stdout).unwrap().lines() {
        if line.starts_with("Attachment")
            && (line.to_lowercase().contains(".ttf") || line.to_lowercase().contains(".otf"))
        {
            let captures = ATTACHMENT_PATTERN.captures(line).unwrap();
            attachments.insert(
                captures[1].parse::<usize>().unwrap(),
                captures[2].to_owned(),
            );
        }
    }

    Ok(attachments)
}

pub fn get_file_uuid(path: &Path) -> Result<Uuid, String> {
    let output = match Command::new("mkvinfo")
        .args(&[path.to_str().unwrap()])
        .output()
    {
        Ok(x) => x,
        Err(x) => return Err(x.description().to_owned()),
    };

    let output = String::from_utf8(output.stdout).unwrap();
    for line in output.lines() {
        if let Some(captures) = SEGMENT_UUID_REGEX.captures(line) {
            return Ok(Uuid::parse_str(
                &captures
                    .iter()
                    .skip(1)
                    .take(16)
                    .map(|m| m.unwrap().as_str())
                    .collect::<Vec<&str>>()
                    .concat(),
            )
            .unwrap());
        }
    }

    Err(format!(
        "No uuid found in {}, is this a valid Matroska file?",
        path.to_str().unwrap()
    ))
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum SectionType {
    Video,
    Chapters,
}

#[derive(Clone, Copy, Debug)]
pub struct BreakPoint {
    pub start_frame: u64,
    pub end_frame: u64,
    pub foreign_uuid: Option<Uuid>,
}

pub fn get_ordered_chapters_list(
    path: &Path,
    force_120_fps: bool,
) -> Result<Option<Vec<BreakPoint>>, String> {
    let output = match Command::new("mkvinfo")
        .args(&[path.to_str().unwrap()])
        .output()
    {
        Ok(x) => x,
        Err(x) => return Err(x.description().to_owned()),
    };

    let output = String::from_utf8(output.stdout).unwrap();
    let mut chapters: Vec<BreakPoint> = Vec::new();
    let mut video_fps: Option<f64> = if force_120_fps {
        Some(120_000. / 1_001.)
    } else {
        None
    };
    let mut current_section: Option<SectionType> = None;
    let mut current_chapter: Option<BreakPoint> = None;
    let mut ordered_chapters = false;
    for line in output.lines() {
        // Find video_fps
        if video_fps.is_none() {
            if current_section == Some(SectionType::Video) {
                if let Some(captures) = FPS_PATTERN.captures(line) {
                    video_fps = Some(captures[1].parse::<f64>().unwrap());
                }
            } else if line == "|  + Track type: video" {
                current_section = Some(SectionType::Video);
            }
            continue;
        }
        if current_section == Some(SectionType::Chapters) {
            if EDITION_FLAG_ORDERED_REGEX.is_match(line) {
                ordered_chapters = true;
                continue;
            }
            if CHAPTER_ATOM_REGEX.is_match(line) {
                if let Some(chapter) = current_chapter {
                    chapters.push(chapter);
                }
                current_chapter = Some(BreakPoint {
                    start_frame: 0,
                    end_frame: 0,
                    foreign_uuid: None,
                });
                continue;
            }
            if current_chapter.is_some() {
                if let Some(captures) = TIME_START_REGEX.captures(line) {
                    current_chapter.as_mut().unwrap().start_frame = timestamp_to_frame_number(
                        captures[1].parse::<u64>().unwrap(),
                        captures[2].parse::<u64>().unwrap(),
                        captures[3].parse::<f64>().unwrap()
                            + captures[4].parse::<f64>().unwrap() / 1_000_000_000f64,
                        video_fps.unwrap(),
                    );
                    continue;
                }
                if let Some(captures) = TIME_END_REGEX.captures(line) {
                    current_chapter.as_mut().unwrap().end_frame = timestamp_to_frame_number(
                        captures[1].parse::<u64>().unwrap(),
                        captures[2].parse::<u64>().unwrap(),
                        captures[3].parse::<f64>().unwrap()
                            + captures[4].parse::<f64>().unwrap() / 1_000_000_000f64,
                        video_fps.unwrap(),
                    ) - 1;
                    continue;
                }
                if let Some(captures) = FOREIGN_UUID_REGEX.captures(line) {
                    current_chapter.as_mut().unwrap().foreign_uuid = Some(
                        Uuid::parse_str(
                            &captures
                                .iter()
                                .skip(1)
                                .take(16)
                                .map(|m| m.unwrap().as_str())
                                .collect::<Vec<&str>>()
                                .concat(),
                        )
                        .unwrap(),
                    );
                    continue;
                }
            }
            if EBML_VOID_REGEX.is_match(line) {
                if let Some(chapter) = current_chapter {
                    chapters.push(chapter);
                }
                break;
            }
        } else if line == "|+ Chapters" {
            current_section = Some(SectionType::Chapters);
            continue;
        }
    }

    if !ordered_chapters {
        return Ok(None);
    }

    // Merge chapters
    let mut breakpoints: Vec<BreakPoint> = Vec::new();
    let mut iter = chapters.iter().peekable();
    let mut merging = BreakPoint {
        start_frame: 0,
        end_frame: 0,
        foreign_uuid: None,
    };
    while let Some(chapter) = iter.next() {
        if chapter.foreign_uuid.is_some() {
            breakpoints.push(chapter.clone());
            continue;
        }
        if merging.end_frame == 0 {
            merging.start_frame = chapter.start_frame;
        }
        merging.end_frame = chapter.end_frame;
        if let Some(next_chapter) = iter.peek() {
            if next_chapter.foreign_uuid.is_some() && merging.end_frame > 0 {
                breakpoints.push(merging);
                merging = BreakPoint {
                    start_frame: 0,
                    end_frame: 0,
                    foreign_uuid: None,
                };
            }
        } else {
            if merging.end_frame > 0 {
                breakpoints.push(merging);
            }
            break;
        }
    }

    Ok(Some(breakpoints))
}

fn timestamp_to_frame_number(hours: u64, minutes: u64, seconds: f64, fps: f64) -> u64 {
    ((seconds + 60f64 * minutes as f64 + 3600f64 * hours as f64) * fps).floor() as u64
}
<|endoftext|>


<commit_before>use prelude::{define_built_in, apply_program};

use math::rich_number::RichNumber;

use eval::context_item::ContextItem;
use eval::eval_context::{EvalContext, EvalError};
use eval::built_in_function::BuiltInFunction;

const UNARY_PARAMS_LENGTH: usize = 1;
const BINARY_PARAMS_LENGTH: usize = 2;

pub fn define_math(context: &mut EvalContext) {
    context.set_boolean("true", true);
    context.set_boolean("false", false);

    define_numeric_binary_op(context, "pow", |a, b| a.pow(b), |_, _| false);
    define_numeric_binary_op(context, "mul", |a, b| a * b, |_, _| false);

    define_numeric_binary_op(context, "add", |a, b| a + b, |_, _| true);
    define_numeric_binary_op(context, "sub", |a, b| a - b, |_, _| true);
    define_numeric_binary_op(context, "div", |a, b| a / b,
        |a, b| a.has_units() && b.has_units());
    define_numeric_binary_op(context, "mod", |a, b| a % b,
        |a, b| a.has_units() && b.has_units());

    define_boolean_binary_op(context, "eq", |a, b| a == b, true);
    define_boolean_binary_op(context, "ne", |a, b| a != b, true);

    define_boolean_binary_op(context, "gt",
        |a, b| a.unwrap_number() > b.unwrap_number(), true);
    define_boolean_binary_op(context, "ge",
        |a, b| a.unwrap_number() >= b.unwrap_number(), true);
    define_boolean_binary_op(context, "lt",
        |a, b| a.unwrap_number() < b.unwrap_number(), true);
    define_boolean_binary_op(context, "le",
        |a, b| a.unwrap_number() <= b.unwrap_number(), true);

    define_unary_operators(context);

    apply_program(context, include_str!("math.lion"));

    //TODO: sin, cos, tan, etc.
}

fn define_unary_operators(context: &mut EvalContext) {
    define_built_in(context, "not", UNARY_PARAMS_LENGTH,
        BuiltInFunction::new(move |_, mut params| {
            if params.len() != UNARY_PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(UNARY_PARAMS_LENGTH));
            }

            if !params[0].is_boolean() {
                return Err(EvalError::InvalidParam("Can only boolean negate a boolean value".to_owned()));
            }

            let arg = params.pop().unwrap().unwrap_boolean();
            debug_assert!(params.is_empty(), "Not all parameters used");

            Ok(ContextItem::Boolean(!arg))
        })
    );

    define_built_in(context, "neg", UNARY_PARAMS_LENGTH,
        BuiltInFunction::new(move |_, mut params| {
            if params.len() != UNARY_PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(UNARY_PARAMS_LENGTH));
            }

            if !params[0].is_number() {
                return Err(EvalError::InvalidParam("Can only negate a numeric value".to_owned()));
            }

            let arg = params.pop().unwrap().unwrap_number();
            debug_assert!(params.is_empty(), "Not all parameters used");

            Ok(ContextItem::Number(-arg))
        })
    );
}

/// Defines an operator that can take either booleans or numbers
/// Optionally coerces numbers into the same unit
/// Ensures that both arguments are numbers or both are booleans
fn define_boolean_binary_op<F: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: bool)
    where F: Fn(ContextItem, ContextItem) -> bool {

    define_built_in(context, name, BINARY_PARAMS_LENGTH,
        BuiltInFunction::new(move |context, mut params| {
            if params.len() != BINARY_PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(BINARY_PARAMS_LENGTH))
            }

            // params can be either number or boolean but not both
            if !((params[0].is_number() && params[1].is_number())
                || (params[0].is_boolean() && params[1].is_boolean())) {
                return Err(EvalError::InvalidParam("Expecting numeric or boolean argument, cannot compare numeric values to boolean values".to_owned()));
            }

            let mut rhs = params.remove(0);
            let lhs = params.remove(0);
            debug_assert!(params.is_empty(), "Not all parameters used");

            if coerce_first && lhs.is_number() && rhs.is_number() {
                // May possibly attempt to convert in the other
                // direction too in the future
                let lhs_unit = match lhs {
                    ContextItem::Number(RichNumber { ref unit, .. }) => unit,
                    _ => unreachable!(),
                };
                rhs = try!(context.convert(rhs.unwrap_number(), *lhs_unit));
            }

            Ok(ContextItem::Boolean(operator(lhs, rhs)))
        })
    );
}

/// Defines an operator that can only take numeric arguments
/// Optionally takes a function used to define whether the numeric arguments should be coerced into the same unit
fn define_numeric_binary_op<F: 'static, G: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: G)
    where F: Fn(RichNumber, RichNumber) -> RichNumber,
          G: Fn(RichNumber, RichNumber) -> bool {

    define_built_in(context, name, BINARY_PARAMS_LENGTH,
        BuiltInFunction::new(move |context, mut params| {
            if params.len() != BINARY_PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(BINARY_PARAMS_LENGTH))
            }

            if !params[0].is_number() || !params[1].is_number() {
                return Err(EvalError::InvalidParam("Expecting numeric argument".to_owned()));
            }

            let mut rhs = params.pop().unwrap().unwrap_number();
            let lhs = params.pop().unwrap().unwrap_number();
            debug_assert!(params.is_empty(), "Not all parameters used");

            if coerce_first(lhs, rhs) {
                // May possibly attempt to convert in the other
                // direction too in the future
                rhs = try!(context.convert(rhs, lhs.unit)).unwrap_number();
            }

            Ok(ContextItem::Number(operator(lhs, rhs)))
        })
    );
}

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

    use eval::context_item::ContextItem;
    use eval::eval_context::EvalContext;
    use math::rich_number::{RichNumber, Unit};

    #[test]
    fn math_operators() {
        let mut context = EvalContext::prelude();

        // add 1 (sub (mul 2 (pow 4 3)) (div (mod 7 3) 10))
        // 1 + ((2 * (4 ** 3)) - ((7 % 3) / 10))
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Symbol("add".to_owned())),
            ExprItem::SingleTerm(Term::Number(1f64)),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Symbol("sub".to_owned())),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Symbol("mul".to_owned())),
                    ExprItem::SingleTerm(Term::Number(2f64)),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(Term::Symbol("pow".to_owned())),
                        ExprItem::SingleTerm(Term::Number(4f64)),
                        ExprItem::SingleTerm(Term::Number(3f64)),
                    ]),
                ]),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Symbol("div".to_owned())),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(Term::Symbol("mod".to_owned())),
                        ExprItem::SingleTerm(Term::Number(7f64)),
                        ExprItem::SingleTerm(Term::Number(3f64)),
                    ]),
                    ExprItem::SingleTerm(Term::Number(10f64)),
                ]),
            ]),
        ])).unwrap().unwrap_number(), RichNumber::from(128.9f64));
    }

    #[test]
    fn division_with_or_without_units() {
        let mut context = EvalContext::prelude();

        let fake_unit = "foounit";
        assert_eq!(context.call("defineUnit", vec![
            ContextItem::Constant(fake_unit.to_owned()),
        ]).unwrap().unwrap(), ContextItem::Nothing);

        let unit = context.lookup_unit_name(fake_unit).unwrap();

        // 3 foounit / 2 should equal 1.5 foounit
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Number(3f64)),
            ExprItem::SingleTerm(Term::Symbol(fake_unit.to_owned())),
            ExprItem::SingleTerm(Term::Symbol("/".to_owned())),
            ExprItem::SingleTerm(Term::Number(2f64)),
        ])).unwrap().unwrap_number(), RichNumber::from_unit(1.5f64, unit));

        // 3 foounit / 2 foounit should equal 1.5
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Number(3f64)),
            ExprItem::SingleTerm(Term::Symbol(fake_unit.to_owned())),
            ExprItem::SingleTerm(Term::Symbol("/".to_owned())),
            ExprItem::SingleTerm(Term::Number(2f64)),
            ExprItem::SingleTerm(Term::Symbol(fake_unit.to_owned())),
        ])).unwrap().unwrap_number(), RichNumber::from(1.5f64));
    }

    #[test]
    fn can_compare_booleans_to_booleans() {
        let mut context = EvalContext::prelude();

        // true == false should be false and should not error
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Symbol("true".to_owned())),
            ExprItem::SingleTerm(Term::Symbol("==".to_owned())),
            ExprItem::SingleTerm(Term::Symbol("false".to_owned())),
        ])).unwrap().unwrap_boolean(), false);
    }

    #[test]
    fn can_compare_numbers_to_numbers() {
        let mut context = EvalContext::prelude();

        // 2 == 2 should be true and should not error
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Number(2f64)),
            ExprItem::SingleTerm(Term::Symbol("==".to_owned())),
            ExprItem::SingleTerm(Term::Number(2f64)),
        ])).unwrap().unwrap_boolean(), true);
    }

    #[test]
    fn can_negate_numbers() {
        let mut context = EvalContext::prelude();

        let fake_unit: Unit = 2;
        let number: f64 = 34.314f64;

        assert_eq!(
            context.call("neg", vec![
                ContextItem::Number(RichNumber::from_unit(number, fake_unit)),
            ]).unwrap().unwrap().unwrap_number(),
            RichNumber::from_unit(-number, fake_unit)
        );
    }

    #[test]
    fn can_negate_booleans() {
        let mut context = EvalContext::prelude();

        assert_eq!(context.call("not", vec![
            ContextItem::Boolean(true),
        ]).unwrap().unwrap().unwrap_boolean(), false);
    }
}

<commit_msg>Abstracting unary numeric operators into a function<commit_after>use prelude::{define_built_in, apply_program};

use math::rich_number::RichNumber;

use eval::context_item::ContextItem;
use eval::eval_context::{EvalContext, EvalError};
use eval::built_in_function::BuiltInFunction;

const UNARY_PARAMS_LENGTH: usize = 1;
const BINARY_PARAMS_LENGTH: usize = 2;

pub fn define_math(context: &mut EvalContext) {
    context.set_boolean("true", true);
    context.set_boolean("false", false);

    define_numeric_binary_op(context, "pow", |a, b| a.pow(b), |_, _| false);
    define_numeric_binary_op(context, "mul", |a, b| a * b, |_, _| false);

    define_numeric_binary_op(context, "add", |a, b| a + b, |_, _| true);
    define_numeric_binary_op(context, "sub", |a, b| a - b, |_, _| true);
    define_numeric_binary_op(context, "div", |a, b| a / b,
        |a, b| a.has_units() && b.has_units());
    define_numeric_binary_op(context, "mod", |a, b| a % b,
        |a, b| a.has_units() && b.has_units());

    define_boolean_binary_op(context, "eq", |a, b| a == b, true);
    define_boolean_binary_op(context, "ne", |a, b| a != b, true);

    define_boolean_binary_op(context, "gt",
        |a, b| a.unwrap_number() > b.unwrap_number(), true);
    define_boolean_binary_op(context, "ge",
        |a, b| a.unwrap_number() >= b.unwrap_number(), true);
    define_boolean_binary_op(context, "lt",
        |a, b| a.unwrap_number() < b.unwrap_number(), true);
    define_boolean_binary_op(context, "le",
        |a, b| a.unwrap_number() <= b.unwrap_number(), true);

    define_unary_operators(context);

    apply_program(context, include_str!("math.lion"));
}

fn define_unary_operators(context: &mut EvalContext) {
    define_built_in(context, "not", UNARY_PARAMS_LENGTH,
        BuiltInFunction::new(move |_, mut params| {
            if params.len() != UNARY_PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(UNARY_PARAMS_LENGTH));
            }

            if !params[0].is_boolean() {
                return Err(EvalError::InvalidParam("Can only boolean negate a boolean value".to_owned()));
            }

            let arg = params.pop().unwrap().unwrap_boolean();
            debug_assert!(params.is_empty(), "Not all parameters used");

            Ok(ContextItem::Boolean(!arg))
        })
    );

    define_numeric_unary_op(context, "neg", |a| -a)
    //TODO: rad (unit), deg (unit), PI (constant), sin, cos, tan, etc.
}

/// Defines an operator that can take either booleans or numbers
/// Optionally coerces numbers into the same unit
/// Ensures that both arguments are numbers or both are booleans
fn define_boolean_binary_op<F: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: bool)
    where F: Fn(ContextItem, ContextItem) -> bool {

    define_built_in(context, name, BINARY_PARAMS_LENGTH,
        BuiltInFunction::new(move |context, mut params| {
            if params.len() != BINARY_PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(BINARY_PARAMS_LENGTH))
            }

            // params can be either number or boolean but not both
            if !((params[0].is_number() && params[1].is_number())
                || (params[0].is_boolean() && params[1].is_boolean())) {
                return Err(EvalError::InvalidParam("Expecting numeric or boolean argument, cannot compare numeric values to boolean values".to_owned()));
            }

            let mut rhs = params.remove(0);
            let lhs = params.remove(0);
            debug_assert!(params.is_empty(), "Not all parameters used");

            if coerce_first && lhs.is_number() && rhs.is_number() {
                // May possibly attempt to convert in the other
                // direction too in the future
                let lhs_unit = match lhs {
                    ContextItem::Number(RichNumber { ref unit, .. }) => unit,
                    _ => unreachable!(),
                };
                rhs = try!(context.convert(rhs.unwrap_number(), *lhs_unit));
            }

            Ok(ContextItem::Boolean(operator(lhs, rhs)))
        })
    );
}

/// Defines an operator that can only take numeric arguments
/// Optionally takes a function used to define whether the numeric arguments should be coerced into the same unit
fn define_numeric_binary_op<F: 'static, G: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: G)
    where F: Fn(RichNumber, RichNumber) -> RichNumber,
          G: Fn(RichNumber, RichNumber) -> bool {

    define_built_in(context, name, BINARY_PARAMS_LENGTH,
        BuiltInFunction::new(move |context, mut params| {
            if params.len() != BINARY_PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(BINARY_PARAMS_LENGTH))
            }

            if !params[0].is_number() || !params[1].is_number() {
                return Err(EvalError::InvalidParam("Expecting numeric argument".to_owned()));
            }

            let mut rhs = params.pop().unwrap().unwrap_number();
            let lhs = params.pop().unwrap().unwrap_number();
            debug_assert!(params.is_empty(), "Not all parameters used");

            if coerce_first(lhs, rhs) {
                // May possibly attempt to convert in the other
                // direction too in the future
                rhs = try!(context.convert(rhs, lhs.unit)).unwrap_number();
            }

            Ok(ContextItem::Number(operator(lhs, rhs)))
        })
    );
}

/// Defines an unary operator that can only take a numeric argument
fn define_numeric_unary_op<F: 'static>(context: &mut EvalContext, name: &'static str, operator: F)
    where F: Fn(RichNumber) -> RichNumber {

    define_built_in(context, name, UNARY_PARAMS_LENGTH,
        BuiltInFunction::new(move |_, mut params| {
            if params.len() != UNARY_PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(UNARY_PARAMS_LENGTH));
            }

            if !params[0].is_number() {
                return Err(EvalError::InvalidParam(format!("Can only apply {} to a numeric value", name)));
            }

            let arg = params.pop().unwrap().unwrap_number();
            debug_assert!(params.is_empty(), "Not all parameters used");

            Ok(ContextItem::Number(operator(arg)))
        })
    );
}

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

    use eval::context_item::ContextItem;
    use eval::eval_context::EvalContext;
    use math::rich_number::{RichNumber, Unit};

    #[test]
    fn math_operators() {
        let mut context = EvalContext::prelude();

        // add 1 (sub (mul 2 (pow 4 3)) (div (mod 7 3) 10))
        // 1 + ((2 * (4 ** 3)) - ((7 % 3) / 10))
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Symbol("add".to_owned())),
            ExprItem::SingleTerm(Term::Number(1f64)),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Symbol("sub".to_owned())),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Symbol("mul".to_owned())),
                    ExprItem::SingleTerm(Term::Number(2f64)),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(Term::Symbol("pow".to_owned())),
                        ExprItem::SingleTerm(Term::Number(4f64)),
                        ExprItem::SingleTerm(Term::Number(3f64)),
                    ]),
                ]),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Symbol("div".to_owned())),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(Term::Symbol("mod".to_owned())),
                        ExprItem::SingleTerm(Term::Number(7f64)),
                        ExprItem::SingleTerm(Term::Number(3f64)),
                    ]),
                    ExprItem::SingleTerm(Term::Number(10f64)),
                ]),
            ]),
        ])).unwrap().unwrap_number(), RichNumber::from(128.9f64));
    }

    #[test]
    fn division_with_or_without_units() {
        let mut context = EvalContext::prelude();

        let fake_unit = "foounit";
        assert_eq!(context.call("defineUnit", vec![
            ContextItem::Constant(fake_unit.to_owned()),
        ]).unwrap().unwrap(), ContextItem::Nothing);

        let unit = context.lookup_unit_name(fake_unit).unwrap();

        // 3 foounit / 2 should equal 1.5 foounit
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Number(3f64)),
            ExprItem::SingleTerm(Term::Symbol(fake_unit.to_owned())),
            ExprItem::SingleTerm(Term::Symbol("/".to_owned())),
            ExprItem::SingleTerm(Term::Number(2f64)),
        ])).unwrap().unwrap_number(), RichNumber::from_unit(1.5f64, unit));

        // 3 foounit / 2 foounit should equal 1.5
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Number(3f64)),
            ExprItem::SingleTerm(Term::Symbol(fake_unit.to_owned())),
            ExprItem::SingleTerm(Term::Symbol("/".to_owned())),
            ExprItem::SingleTerm(Term::Number(2f64)),
            ExprItem::SingleTerm(Term::Symbol(fake_unit.to_owned())),
        ])).unwrap().unwrap_number(), RichNumber::from(1.5f64));
    }

    #[test]
    fn can_compare_booleans_to_booleans() {
        let mut context = EvalContext::prelude();

        // true == false should be false and should not error
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Symbol("true".to_owned())),
            ExprItem::SingleTerm(Term::Symbol("==".to_owned())),
            ExprItem::SingleTerm(Term::Symbol("false".to_owned())),
        ])).unwrap().unwrap_boolean(), false);
    }

    #[test]
    fn can_compare_numbers_to_numbers() {
        let mut context = EvalContext::prelude();

        // 2 == 2 should be true and should not error
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Number(2f64)),
            ExprItem::SingleTerm(Term::Symbol("==".to_owned())),
            ExprItem::SingleTerm(Term::Number(2f64)),
        ])).unwrap().unwrap_boolean(), true);
    }

    #[test]
    fn can_negate_numbers() {
        let mut context = EvalContext::prelude();

        let fake_unit: Unit = 2;
        let number: f64 = 34.314f64;

        assert_eq!(
            context.call("neg", vec![
                ContextItem::Number(RichNumber::from_unit(number, fake_unit)),
            ]).unwrap().unwrap().unwrap_number(),
            RichNumber::from_unit(-number, fake_unit)
        );
    }

    #[test]
    fn can_negate_booleans() {
        let mut context = EvalContext::prelude();

        assert_eq!(context.call("not", vec![
            ContextItem::Boolean(true),
        ]).unwrap().unwrap().unwrap_boolean(), false);
    }
}

<|endoftext|>


<commit_before>use chrono::{Utc};
pub use error::{Error, ErrorCode, ErrorType, RequestError, WebhookError};
use resources::{Charge, Invoice, Subscription};
use hmac::{Hmac, Mac, MacResult};
use serde_json;
use sha2::Sha256;
use std::str;

#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum EventType {
    #[serde(rename = "charge.succeeded")]
    ChargeSucceeded,
    #[serde(rename = "customer.subscription.created")]
    CustomerSubscriptionCreated,
    #[serde(rename = "invoice.created")]
    InvoiceCreated,
    #[serde(rename = "invoice.updated")]
    InvoiceUpdated,
    #[serde(rename = "invoice.upcoming")]
    InvoiceUpcoming,
}

#[derive(Debug, Deserialize)]
pub struct Event {
    #[serde(rename = "type")]
    pub event_type: EventType,
    pub data: EventData,
    // ...
}

#[derive(Debug, Deserialize)]
pub struct EventData {
    pub object: EventObject,
    // previous_attributes: ...
}

#[derive(Debug, Deserialize)]
#[serde(tag = "object")]
pub enum EventObject {
    #[serde(rename = "charge")]
    Charge(Charge),
    #[serde(rename = "invoice")]
    Invoice(Invoice),
    #[serde(rename = "subscription")]
    Subscription(Subscription),
}

pub struct Webhook {}

impl Webhook {
    pub fn construct_event(payload: String, sig: String, secret: String) -> Result<Event, WebhookError> {
        let mut headers: Vec<String> = sig.split(",").map(|s| s.trim().to_string()).collect();

        // Prepare the signed payload
        let ref mut timestamp: Vec<String> = headers[0].split("=").map(|s| s.to_string()).collect();
        let signed_payload = format!("{}{}{}", timestamp[1], ".", payload);

        // Get Stripe signature from header
        let ref mut signature: Vec<String> = headers[1].split("=").map(|s| s.to_string()).collect();

        // Compute HMAC with the SHA256 hash function, using endpoing secret as key and signed_payload string as the message
        let mut mac = Hmac::<Sha256>::new(secret.as_bytes());
        mac.input(signed_payload.as_bytes());

        let result = mac.result();

        let bytes_signature = MacResult::from_slice(signature[1].as_bytes());

        // Get current timestamp to compare to signature timestamp
        let current = Utc::now().timestamp();
        let num_timestamp = timestamp[1].parse::<i64>()
            .map_err(|err| WebhookError::BadHeader(err))?;

        if bytes_signature != result {
            return Err(WebhookError::BadSignature);
        }

        if current - num_timestamp > 300 {
            return Err(WebhookError::BadTimestamp(num_timestamp));
        }

        // return Event
        return serde_json::from_str(&payload).map_err(|err| WebhookError::BadParse(err));
    }
}
<commit_msg>Update event.rs<commit_after>use chrono::{Utc};
use error::{Error, ErrorCode, ErrorType, RequestError, WebhookError};
use resources::{Charge, Invoice, Subscription};
use hmac::{Hmac, Mac, MacResult};
use serde_json as json;
use sha2::Sha256;
use std::str;

#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum EventType {
    #[serde(rename = "charge.succeeded")]
    ChargeSucceeded,
    #[serde(rename = "customer.subscription.created")]
    CustomerSubscriptionCreated,
    #[serde(rename = "invoice.created")]
    InvoiceCreated,
    #[serde(rename = "invoice.updated")]
    InvoiceUpdated,
    #[serde(rename = "invoice.upcoming")]
    InvoiceUpcoming,
}

#[derive(Debug, Deserialize)]
pub struct Event {
    #[serde(rename = "type")]
    pub event_type: EventType,
    pub data: EventData,
    // ...
}

#[derive(Debug, Deserialize)]
pub struct EventData {
    pub object: EventObject,
    // previous_attributes: ...
}

#[derive(Debug, Deserialize)]
#[serde(tag = "object")]
pub enum EventObject {
    #[serde(rename = "charge")]
    Charge(Charge),
    #[serde(rename = "invoice")]
    Invoice(Invoice),
    #[serde(rename = "subscription")]
    Subscription(Subscription),
}

pub struct Webhook {}

impl Webhook {
    pub fn construct_event(payload: String, sig: String, secret: String) -> Result<Event, WebhookError> {
        let mut headers: Vec<String> = sig.split(",").map(|s| s.trim().to_string()).collect();

        // Prepare the signed payload
        let ref mut timestamp: Vec<String> = headers[0].split("=").map(|s| s.to_string()).collect();
        let signed_payload = format!("{}{}{}", timestamp[1], ".", payload);

        // Get Stripe signature from header
        let ref mut signature: Vec<String> = headers[1].split("=").map(|s| s.to_string()).collect();

        // Compute HMAC with the SHA256 hash function, using endpoing secret as key and signed_payload string as the message
        let mut mac = Hmac::<Sha256>::new(secret.as_bytes());
        mac.input(signed_payload.as_bytes());

        let result = mac.result();

        let bytes_signature = MacResult::from_slice(signature[1].as_bytes());

        // Get current timestamp to compare to signature timestamp
        let current = Utc::now().timestamp();
        let num_timestamp = timestamp[1].parse::<i64>()
            .map_err(|err| WebhookError::BadHeader(err))?;

        if bytes_signature != result {
            return Err(WebhookError::BadSignature);
        }

        if current - num_timestamp > 300 {
            return Err(WebhookError::BadTimestamp(num_timestamp));
        }

        // return Event
        return json::from_str(&payload).map_err(|err| WebhookError::BadParse(err));
    }
}
<|endoftext|>