<commit_before>//
// MSX CAS Packager
// Copyright (c) 2015 Alvaro Polo
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::io::Read;
use std::io::Error as IoError;
use std::str::from_utf8;

#[derive(Debug)]
pub struct Block { data: Vec<u8>, }

impl Block {

    pub fn is_bin_header(&self) -> bool {
        self.data.len() >= 16 &&
            self.data[..10] == [0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0]
    }

    pub fn file_name(&self) -> Option<&str> {
        if self.is_bin_header() { from_utf8(&self.data[10..16]).ok() }
        else { None }
    }
}

#[derive(Debug, PartialEq)]
pub enum File<'a> {
	Bin(String, usize, usize, usize, &'a[u8]),
    Custom(&'a[u8])
}

pub struct Files<'a> {
    tape: &'a Tape,
    i: usize,
}

impl<'a> Iterator for Files<'a> {

    type Item = File<'a>;

    fn next(&mut self) -> Option<File<'a>> {
        while self.i < self.tape.blocks.len() {
            let block = &self.tape.blocks[self.i];
            if block.is_bin_header() {
                let name = block.file_name().unwrap().to_string();
                let content = &self.tape.blocks[self.i+1].data;
                let begin = (content[0] as usize) | (content[1] as usize) << 8;
                let end = (content[2] as usize) | (content[3] as usize) << 8;
                let start = (content[4] as usize) | (content[5] as usize) << 8;
                let data = &content[6..];
                self.i = self.i + 2;
                return Some(File::Bin(name, begin, end, start, data));
            } else {
                self.i = self.i + 1;
                return Some(File::Custom(&block.data[..]));
            }
        }
        None
    }
}

#[derive(Debug)]
pub struct Tape {
    blocks: Vec<Block>,
}

#[derive(Debug)]
pub enum LoadError {
	Io(IoError),
}

impl From<IoError> for LoadError {
	fn from(e: IoError) -> LoadError { LoadError::Io(e) }
}

impl Tape {

	#[allow(dead_code)]
	pub fn read(input: &mut Read) -> Result<Tape, LoadError> {
		let mut bytes: Vec<u8> = vec![];
		try!(input.read_to_end(&mut bytes));
		Tape::from_bytes(&bytes[..])
	}

	pub fn from_bytes(bytes: &[u8]) -> Result<Tape, LoadError> {
		Ok(Tape { blocks: Tape::parse_blocks(bytes) })
	}

    pub fn files(&self) -> Files { Files { tape: self, i: 0 } }

    fn parse_blocks(bytes: &[u8]) -> Vec<Block> {
        let mut blocks: Vec<Block> = vec![];
        let mut hindex: Vec<usize> = vec![];
        let mut i = 0;

        // First of all, we compute the indices of all block headers.
        for win in bytes.windows(8) {
            if win == [0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74] {
                hindex.push(i);
            }
            i = i + 1;
        }

        // Now we use the block header indices to generate the blocks
        for i in 0..hindex.len() {
            let from = hindex[i] + 8;
            let to = if i == hindex.len() - 1 { bytes.len() } else { hindex[i+1] };
            let block = Block { data: Vec::from(&bytes[from..to]) };
            blocks.push(block);
        }
        blocks
    }
}

#[cfg(test)]
mod test {

    use super::*;

    macro_rules! assert_bin {
        ($f:expr, $n:expr, $b:expr, $e:expr, $s:expr, $d:expr) => {
            match $f {
            &File::Bin(ref name, begin, end, start, data) => {
                assert_eq!($n, name);
                assert_eq!($b, begin);
                assert_eq!($e, end);
                assert_eq!($s, start);
                assert_eq!($d, &data[..]);
            },
            _ => panic!("unexpected file"),
        }
        }
    }

    #[test]
    fn should_load_empty_tape() {
    	let bytes: Vec<u8> = vec![];
    	let tape = Tape::from_bytes(&bytes);
        assert!(tape.is_ok());
        assert_eq!(None, tape.unwrap().files().next());
    }

    #[test]
    fn should_load_bin_file() {
        let bytes: Vec<u8> = vec![
            0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74,
            0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
            0x46, 0x4f, 0x4f, 0x42, 0x41, 0x52,
            0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74,
            0x00, 0x80, 0x08, 0x80, 0x00, 0x00,
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
        ];
        let tape = Tape::from_bytes(&bytes).unwrap();
        for f in tape.files() {
            assert_bin!(&f,
                "FOOBAR", 0x8000, 0x8008, 0x0000, &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
        }
    }
}
<commit_msg>Introduce `Block::from_bytes()`<commit_after>//
// MSX CAS Packager
// Copyright (c) 2015 Alvaro Polo
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::io::Read;
use std::io::Error as IoError;
use std::str::from_utf8;

#[derive(Debug)]
pub struct Block { data: Vec<u8>, }

impl Block {

    pub fn from_bytes(bytes: &[u8]) -> Block {
        Block { data: Vec::from(bytes) }
    }

    pub fn is_bin_header(&self) -> bool {
        self.data.len() >= 16 &&
            self.data[..10] == [0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0]
    }

    pub fn file_name(&self) -> Option<&str> {
        if self.is_bin_header() {
            let name = &self.data[10..16];
            let mut last = 6;
            for i in (0..6).rev() {
                if name[i] == 0 { last = i }
            }
            from_utf8(&name[..last]).ok()
        }
        else { None }
    }
}

#[derive(Debug, PartialEq)]
pub enum File<'a> {
	Bin(String, usize, usize, usize, &'a[u8]),
    Custom(&'a[u8])
}

pub struct Files<'a> {
    tape: &'a Tape,
    i: usize,
}

impl<'a> Iterator for Files<'a> {

    type Item = File<'a>;

    fn next(&mut self) -> Option<File<'a>> {
        while self.i < self.tape.blocks.len() {
            let block = &self.tape.blocks[self.i];
            if block.is_bin_header() {
                let name = block.file_name().unwrap().to_string();
                let content = &self.tape.blocks[self.i+1].data;
                let begin = (content[0] as usize) | (content[1] as usize) << 8;
                let end = (content[2] as usize) | (content[3] as usize) << 8;
                let start = (content[4] as usize) | (content[5] as usize) << 8;
                let data = &content[6..];
                self.i = self.i + 2;
                return Some(File::Bin(name, begin, end, start, data));
            } else {
                self.i = self.i + 1;
                return Some(File::Custom(&block.data[..]));
            }
        }
        None
    }
}

#[derive(Debug)]
pub struct Tape {
    blocks: Vec<Block>,
}

#[derive(Debug)]
pub enum LoadError {
	Io(IoError),
}

impl From<IoError> for LoadError {
	fn from(e: IoError) -> LoadError { LoadError::Io(e) }
}

impl Tape {

	#[allow(dead_code)]
	pub fn read(input: &mut Read) -> Result<Tape, LoadError> {
		let mut bytes: Vec<u8> = vec![];
		try!(input.read_to_end(&mut bytes));
		Tape::from_bytes(&bytes[..])
	}

	pub fn from_bytes(bytes: &[u8]) -> Result<Tape, LoadError> {
		Ok(Tape { blocks: Tape::parse_blocks(bytes) })
	}

    pub fn files(&self) -> Files { Files { tape: self, i: 0 } }

    fn parse_blocks(bytes: &[u8]) -> Vec<Block> {
        let mut blocks: Vec<Block> = vec![];
        let mut hindex: Vec<usize> = vec![];
        let mut i = 0;

        // First of all, we compute the indices of all block headers.
        for win in bytes.windows(8) {
            if win == [0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74] {
                hindex.push(i);
            }
            i = i + 1;
        }

        // Now we use the block header indices to generate the blocks
        for i in 0..hindex.len() {
            let from = hindex[i] + 8;
            let to = if i == hindex.len() - 1 { bytes.len() } else { hindex[i+1] };
            let block = Block::from_bytes(&bytes[from..to]);
            blocks.push(block);
        }
        blocks
    }
}

#[cfg(test)]
mod test {

    use super::*;

    macro_rules! assert_bin {
        ($f:expr, $n:expr, $b:expr, $e:expr, $s:expr, $d:expr) => {
            match $f {
            &File::Bin(ref name, begin, end, start, data) => {
                assert_eq!($n, name);
                assert_eq!($b, begin);
                assert_eq!($e, end);
                assert_eq!($s, start);
                assert_eq!($d, &data[..]);
            },
            _ => panic!("unexpected file"),
        }
        }
    }

    #[test]
    fn should_detect_bin_header_block() {
        let bytes: Vec<u8> = vec![
            0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
            0x46, 0x4f, 0x4f, 0x42, 0x41, 0x52,
            0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74,
            0x00, 0x80, 0x08, 0x80, 0x00, 0x00,
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
        ];
        let block = Block::from_bytes(&bytes);
        assert!(block.is_bin_header());
        assert_eq!("FOOBAR", block.file_name().unwrap());
    }

    #[test]
    fn should_detect_block_with_short_name() {
        let bytes: Vec<u8> = vec![
            0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
            0x46, 0x4f, 0x4f, 0x00, 0x00, 0x00,
            0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74,
            0x00, 0x80, 0x08, 0x80, 0x00, 0x00,
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
        ];
        let block = Block::from_bytes(&bytes);
        assert!(block.is_bin_header());
        assert_eq!("FOO", block.file_name().unwrap());
    }

    #[test]
    fn should_load_empty_tape() {
    	let bytes: Vec<u8> = vec![];
    	let tape = Tape::from_bytes(&bytes);
        assert!(tape.is_ok());
        assert_eq!(None, tape.unwrap().files().next());
    }

    #[test]
    fn should_load_bin_file() {
        let bytes: Vec<u8> = vec![
            0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74,
            0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
            0x46, 0x4f, 0x4f, 0x42, 0x41, 0x52,
            0x1f, 0xa6, 0xde, 0xba, 0xcc, 0x13, 0x7d, 0x74,
            0x00, 0x80, 0x08, 0x80, 0x00, 0x00,
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
        ];
        let tape = Tape::from_bytes(&bytes).unwrap();
        for f in tape.files() {
            assert_bin!(&f,
                "FOOBAR", 0x8000, 0x8008, 0x0000, &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
        }
    }
}
<|endoftext|>


<commit_before>use crate::utils::schemas;
use eyre::Result;
use once_cell::sync::Lazy;
use schemars::JsonSchema;
use serde::Serialize;
use serde_with::skip_serializing_none;
use std::{collections::HashMap, path::Path};

/// Information about a document format
///
/// Used to determine various application behaviors
/// e.g. not reading binary formats into memory unnecessarily
#[skip_serializing_none]
#[derive(Clone, Debug, JsonSchema, Serialize)]
#[schemars(deny_unknown_fields)]
pub struct Format {
    /// The lowercase name of the format e.g. `md`, `docx`, `dockerfile`
    pub name: String,

    /// Whether or not the format should be considered binary
    /// e.g. not to be displayed in a text / code editor
    pub binary: bool,

    /// Whether or not previews should be generated for files of
    /// this format. e.g. a `.py` is not binary, but should not
    /// necessarily have a preview opened for it.
    pub preview: bool,

    /// The type of `CreativeWork` that this format is expected to be.
    /// This will be `None` for data serialization formats such as
    /// JSON or YAML which have no expected type (the actual type is
    /// embedded in the data).
    #[serde(rename = "type")]
    pub type_: Option<String>,
}

impl Format {
    /// Create a new file format
    pub fn new(name: &str, binary: bool, preview: bool, type_: &str) -> Format {
        Format {
            name: name.into(),
            binary,
            preview,
            type_: if type_.is_empty() {
                None
            } else {
                Some(type_.to_string())
            },
        }
    }

    /// Create the special `directory` format used on `File` objects
    /// that are directories
    pub fn directory() -> Format {
        Format {
            name: "dir".into(),
            binary: true,
            preview: false,
            type_: Some("Collection".into()),
        }
    }

    /// Create the special `unregistered` file format where all we
    /// have is the name e.g. from a file extension
    pub fn unregistered(name: &str) -> Format {
        Format {
            name: name.into(),
            binary: true,
            preview: false,
            type_: None,
        }
    }

    /// Create the special `unknown` file format where we do not
    /// even know the name.
    pub fn unknown() -> Format {
        Format::unregistered("unknown")
    }
}

/// List of known document formats
#[derive(Serialize)]
#[serde(transparent)]
pub struct Formats {
    /// Document formats keyed by their name
    formats: HashMap<String, Format>,
}

impl Default for Formats {
    fn default() -> Formats {
        let formats = vec![
            // Data serialization formats. These may be used
            // to store documents so `preview: true`.
            Format::new("json", false, true, ""),
            Format::new("json5", false, true, ""),
            Format::new("toml", false, true, ""),
            Format::new("xml", false, true, ""),
            Format::new("yaml", false, true, ""),
            // Code formats
            Format::new("dockerfile", false, false, ""),
            Format::new("js", false, false, ""),
            Format::new("makefile", false, false, ""),
            Format::new("py", false, false, ""),
            Format::new("r", false, false, ""),
            Format::new("sh", false, false, ""),
            Format::new("ts", false, false, ""),
            // Article formats
            Format::new("docx", true, true, "Article"),
            Format::new("html", false, true, "Article"),
            Format::new("ipynb", false, true, "Article"),
            Format::new("md", false, true, "Article"),
            Format::new("odt", true, true, "Article"),
            Format::new("rmd", false, true, "Article"),
            Format::new("tex", false, true, "Article"),
            Format::new("txt", false, true, "Article"),
            // Audio formats
            Format::new("flac", true, true, "AudioObject"),
            Format::new("mp3", true, true, "AudioObject"),
            Format::new("ogg", true, true, "AudioObject"),
            // Image formats
            Format::new("gif", true, true, "ImageObject"),
            Format::new("jpeg", true, true, "ImageObject"),
            Format::new("jpg", true, true, "ImageObject"),
            Format::new("png", true, true, "ImageObject"),
            // Video formats
            Format::new("3gp", true, true, "VideoObject"),
            Format::new("mp4", true, true, "VideoObject"),
            Format::new("ogv", true, true, "VideoObject"),
            Format::new("webm", true, true, "VideoObject"),
            // Specials
            Format::directory(),
            Format::unknown(),
        ];

        let formats = formats
            .into_iter()
            .map(|format| (format.name.clone(), format))
            .collect();

        Formats { formats }
    }
}

impl Formats {
    /// Match a format name to a `Format`
    pub fn match_name(&self, name: &str) -> Format {
        match self.formats.get(&name.to_lowercase()) {
            Some(format) => format.clone(),
            None => Format::unregistered(name),
        }
    }

    /// Match a file path to a `Format`
    pub fn match_path<P: AsRef<Path>>(&self, path: &P) -> Format {
        let path = path.as_ref();
        // Use file extension
        let name = match path.extension() {
            Some(ext) => ext,
            // Fallback to the filename if no extension
            None => match path.file_name() {
                Some(name) => name,
                // Fallback to the provided "path"
                None => path.as_os_str(),
            },
        };

        self.match_name(&name.to_string_lossy().to_string())
    }
}

pub static FORMATS: Lazy<Formats> = Lazy::new(Formats::default);

/// Get JSON Schemas for this modules
pub fn schemas() -> Result<serde_json::Value> {
    let schemas = serde_json::Value::Array(vec![schemas::generate::<Format>()?]);
    Ok(schemas)
}
<commit_msg>feat(Formats): Allow additional extensions for a format<commit_after>use crate::utils::schemas;
use defaults::Defaults;
use eyre::Result;
use once_cell::sync::Lazy;
use schemars::JsonSchema;
use serde::Serialize;
use serde_with::skip_serializing_none;
use std::{collections::HashMap, path::Path};

/// Information about a document format
///
/// Used to determine various application behaviors
/// e.g. not reading binary formats into memory unnecessarily
#[skip_serializing_none]
#[derive(Clone, Debug, Defaults, JsonSchema, Serialize)]
#[schemars(deny_unknown_fields)]
pub struct Format {
    /// The lowercase name of the format e.g. `md`, `docx`, `dockerfile`
    pub name: String,

    /// Whether or not the format should be considered binary
    /// e.g. not to be displayed in a text / code editor
    pub binary: bool,

    /// Whether or not previews should be generated for files of
    /// this format. e.g. a `.py` is not binary, but should not
    /// necessarily have a preview opened for it.
    pub preview: bool,

    /// The type of `CreativeWork` that this format is expected to be.
    /// This will be `None` for data serialization formats such as
    /// JSON or YAML which have no expected type (the actual type is
    /// embedded in the data).
    #[serde(rename = "type")]
    pub type_: Option<String>,

    /// Any additional extensions (other than it's name) that this format
    /// should match against.
    pub extensions: Vec<String>,
}

impl Format {
    /// Create a new file format
    pub fn new(name: &str, binary: bool, preview: bool, type_: &str) -> Format {
        Format {
            name: name.into(),
            binary,
            preview,
            type_: if type_.is_empty() {
                None
            } else {
                Some(type_.to_string())
            },
            ..Default::default()
        }
    }

    /// Create a new file format with mutliple matching extensions
    pub fn new_extensions(
        name: &str,
        binary: bool,
        preview: bool,
        type_: &str,
        extensions: &[&str],
    ) -> Format {
        let mut format = Format::new(name, binary, preview, type_);
        format.extensions = extensions.iter().map(|s| s.to_string()).collect();
        format
    }

    /// Create the special `directory` format used on `File` objects
    /// that are directories
    pub fn directory() -> Format {
        Format {
            name: "dir".into(),
            binary: true,
            preview: false,
            type_: Some("Collection".into()),
            ..Default::default()
        }
    }

    /// Create the special `unregistered` file format where all we
    /// have is the name e.g. from a file extension
    pub fn unregistered(name: &str) -> Format {
        Format {
            name: name.into(),
            binary: true,
            preview: false,
            type_: None,
            ..Default::default()
        }
    }

    /// Create the special `unknown` file format where we do not
    /// even know the name.
    pub fn unknown() -> Format {
        Format::unregistered("unknown")
    }
}

/// List of known document formats
#[derive(Serialize)]
#[serde(transparent)]
pub struct Formats {
    /// Document formats keyed by their name
    formats: HashMap<String, Format>,
}

impl Default for Formats {
    fn default() -> Formats {
        let formats = vec![
            // Data serialization formats. These may be used
            // to store documents so `preview: true`.
            Format::new("json", false, true, ""),
            Format::new("json5", false, true, ""),
            Format::new("toml", false, true, ""),
            Format::new("xml", false, true, ""),
            Format::new("yaml", false, true, ""),
            // Code formats
            Format::new("dockerfile", false, false, ""),
            Format::new("js", false, false, ""),
            Format::new("makefile", false, false, ""),
            Format::new("py", false, false, ""),
            Format::new("r", false, false, ""),
            Format::new("sh", false, false, ""),
            Format::new("ts", false, false, ""),
            // Article formats
            Format::new("docx", true, true, "Article"),
            Format::new("html", false, true, "Article"),
            Format::new("ipynb", false, true, "Article"),
            Format::new("md", false, true, "Article"),
            Format::new("odt", true, true, "Article"),
            Format::new("rmd", false, true, "Article"),
            Format::new_extensions("latex", false, true, "Article", &["tex"]),
            Format::new("txt", false, true, "Article"),
            // Audio formats
            Format::new("flac", true, true, "AudioObject"),
            Format::new("mp3", true, true, "AudioObject"),
            Format::new("ogg", true, true, "AudioObject"),
            // Image formats
            Format::new("gif", true, true, "ImageObject"),
            Format::new("jpeg", true, true, "ImageObject"),
            Format::new("jpg", true, true, "ImageObject"),
            Format::new("png", true, true, "ImageObject"),
            // Video formats
            Format::new("3gp", true, true, "VideoObject"),
            Format::new("mp4", true, true, "VideoObject"),
            Format::new("ogv", true, true, "VideoObject"),
            Format::new("webm", true, true, "VideoObject"),
            // Specials
            Format::directory(),
            Format::unknown(),
        ];

        let formats = formats
            .into_iter()
            .map(|format| (format.name.clone(), format))
            .collect();

        Formats { formats }
    }
}

impl Formats {
    /// Match a format name to a `Format`
    pub fn match_name(&self, name: &str) -> Format {
        match self.formats.get(&name.to_lowercase()) {
            Some(format) => format.clone(),
            None => Format::unregistered(name),
        }
    }

    /// Match a file path to a `Format`
    pub fn match_path<P: AsRef<Path>>(&self, path: &P) -> Format {
        let path = path.as_ref();

        // Get name from file extension, or filename if no extension
        let name = match path.extension() {
            Some(ext) => ext,
            None => match path.file_name() {
                Some(name) => name,
                // Fallback to the provided "path"
                None => path.as_os_str(),
            },
        };
        let name = name.to_string_lossy().to_string();
        if let Some(format) = self.formats.get(&name.to_lowercase()) {
            return format.clone();
        }

        // Try matching against "extra" extensions
        if let Some(ext) = path.extension() {
            let ext = ext.to_string_lossy().to_string();
            for format in self.formats.values() {
                if format.extensions.contains(&ext) {
                    return format.clone();
                }
            }
        }

        Format::unregistered(&name)
    }
}

pub static FORMATS: Lazy<Formats> = Lazy::new(Formats::default);

/// Get JSON Schemas for this modules
pub fn schemas() -> Result<serde_json::Value> {
    let schemas = serde_json::Value::Array(vec![schemas::generate::<Format>()?]);
    Ok(schemas)
}
<|endoftext|>


<commit_before>//! The ctrl module should be the general controller of the program.
//! Right now, most controlling is in the net module, and here
//! is only a light facade to the tui messages.

pub mod tui; // todo: pub is not recommended, I use it for doctest
mod webui;

use self::tui::Tui;
use self::webui::WebUI;
use super::config;

use async_std::task;
use crossbeam::sync::WaitGroup;
use libp2p_core::PeerId;
use std::{
    io,
    sync::{
        mpsc::{channel, Receiver, Sender},
        Arc, Mutex,
    },
    thread,
};

type PeerRepresentation = [u8; 16];

/// alive Signal for path from collector
/// or net search alive
#[derive(Clone, Serialize, Deserialize)]
pub enum CollectionPathAlive {
    BusyPath(usize),
    HostSearch,
}

#[derive(Clone, Serialize)]
pub enum Status {
    ON,
    OFF,
}

#[derive(Clone)]
pub enum NetMessages {
    Debug,
    ShowNewHost,
    ShowStats { show: NetStats },
}

#[derive(Clone)]
pub struct ForwardNetMessage {
    net: NetMessages,
    cnt: String,
}

impl ForwardNetMessage {
    pub fn new(net: NetMessages, cnt: String) -> Self {
        Self { net, cnt }
    }
}

/// internal messages inside ui
pub enum InternalUiMsg {
    Update(ForwardNetMessage),
    StartAnimate(CollectionPathAlive, Status),
    StepAndAnimate(CollectionPathAlive),
    Terminate,
}

#[derive(Clone)]
pub enum UiUpdateMsg {
    NetUpdate(ForwardNetMessage),
    CollectionUpdate(CollectionPathAlive, Status),
    StopUI,
}

#[derive(Copy, Clone)]
pub struct NetStats {
    pub line: usize,
    pub max: usize,
}

enum Finisher {
    TUI,
    WEBUI,
}

pub struct Ctrl {
    peer_id: PeerId,
    paths: Vec<String>,
    with_net: bool,
}

impl Ctrl {
    /// Create a new controller if everything fits.
    ///
    /// # Arguments
    /// * 'peer_id' - The peer_id this client/server uses
    /// * 'paths' - The paths that will be searched
    /// * 'receiver' - The receiver that takes incoming ctrl messages
    /// * 'with_net' - If ctrl should consider net messages
    /// * 'sync_sender' - For start up that main can be informed, "I'm ready"
    pub fn new(new_id: PeerId, paths: &Vec<String>, with_net: bool) -> Self {
        Self {
            peer_id: new_id,
            paths: paths.clone(),
            with_net,
        }
    }

    pub fn run(
        new_id: PeerId,
        paths: &Vec<String>,
        receiver: Receiver<UiUpdateMsg>,
        with_net: bool,
        wait_main: WaitGroup,
        has_webui: bool,
        has_tui: bool,
    ) -> Result<(), std::io::Error> {
        // sync both sub uis
        let wait_all_uis = WaitGroup::new();

        let (thread_finisher, finish_threads) = channel::<Finisher>();

        // create instance which will be passed into the different uis
        let instance = Ctrl::new(new_id, paths, with_net);

        let arc_self_tui = Arc::new(Mutex::new(instance));
        let arc_self_webui = arc_self_tui.clone();

        // all senders that UiUpdateMessages will be forwarded to
        let mut internal_senders: Vec<Sender<InternalUiMsg>> = vec![];

        // 1) tui thread
        let (sender_tui_to_register, receiver_to_tui_thread) = channel::<InternalUiMsg>();
        let sender_tui_only_to_finish = sender_tui_to_register.clone();
        let thread_tui = if has_tui {
            let resender = sender_tui_to_register.clone();
            let tui_waitgroup = wait_all_uis.clone();
            let thread_finisher_tui = thread_finisher.clone();

            internal_senders.push(sender_tui_to_register);
            Self::spawn_tui(
                arc_self_tui,
                resender,
                receiver_to_tui_thread,
                tui_waitgroup,
                thread_finisher_tui,
            )?
        } else {
            std::thread::spawn(|| Ok(()))
        };

        // 2) webui thread
        let (sender_wui, receiver_to_web_ui_thread) = channel::<InternalUiMsg>();
        let thread_webui = if has_webui {
            let sender_to_register = sender_wui.clone();
            let wui_waitgroup = wait_all_uis.clone();
            let thread_finisher_tui = thread_finisher.clone();

            internal_senders.push(sender_to_register);
            Self::spawn_webui(
                arc_self_webui,
                receiver_to_web_ui_thread,
                wui_waitgroup,
                thread_finisher_tui,
            )?
        } else {
            // empty thread
            std::thread::spawn(|| Ok(()))
        };

        // 3) ui message forwarding loop thread
        let forwarding_message_loop = Self::spawn_message_loop(receiver, internal_senders);

        // A) wait for sub syncs in order ...
        info!("syncing with 2 other sub threads webui and tui");
        wait_all_uis.wait();
        info!("synced with 2 other sub threads webui and tui");
        // B) ... to unlock sync/block startup with main thread
        // we are ready: up and listening!!
        info!("waiting for main thread sync");
        wait_main.wait();
        info!("synced with main thread");

        // either of these can finish and we want to block!
        match finish_threads.recv() {
            Ok(finished) => match finished {
                Finisher::TUI => {
                    info!("TUI finished first, so send to terminate WEBUI!");
                    sender_wui.send(InternalUiMsg::Terminate).unwrap();
                    let to_pass_through = thread_tui.join().unwrap();
                    drop(forwarding_message_loop); // let drop message loop only after joining!!!
                    to_pass_through
                }
                Finisher::WEBUI => {
                    info!("WEBUI finished first, so send to terminate TUI!");
                    sender_tui_only_to_finish
                        .send(InternalUiMsg::Terminate)
                        .unwrap();
                    let to_pass_through = thread_webui.join().unwrap();
                    drop(forwarding_message_loop); // let drop forwarding message loop only after joining!!!!
                    to_pass_through
                }
            },
            Err(e) => {
                error!("something really bad happenend: {}!!", e);
                drop(thread_webui);
                drop(thread_tui);
                drop(forwarding_message_loop);
                // todo: make a new error
                Ok::<(), std::io::Error>(())
            }
        }
    }

    fn spawn_webui(
        this: Arc<Mutex<Self>>,
        receiver: Receiver<InternalUiMsg>,
        wait_ui_sync: WaitGroup,
        thread_finisher: Sender<Finisher>,
    ) -> Result<thread::JoinHandle<Result<(), std::io::Error>>, std::io::Error> {
        let mut peer_representation: PeerRepresentation = [0 as u8; 16];
        let with_net;
        let paths;
        // lock block
        {
            let unlocker = this.lock().unwrap();
            paths = unlocker.paths.clone();
            peer_representation.copy_from_slice(&unlocker.peer_id.as_bytes()[..16]);
            with_net = unlocker.with_net;
        }

        thread::Builder::new().name("webui".into()).spawn(move || {
            info!("start webui");
            Self::run_webui(receiver, with_net, peer_representation, paths, wait_ui_sync).or_else(
                |forward| {
                    error!("error from webui-server: {}", forward);
                    Err(forward)
                },
            )?;
            info!("stopped webui");

            // send finish
            thread_finisher.send(Finisher::WEBUI).unwrap_or_else(|_| {
                info!("probably receiver got tui finisher first!");
            });

            Ok::<(), std::io::Error>(())
        })
    }

    fn spawn_tui(
        this: Arc<Mutex<Self>>,
        resender: Sender<InternalUiMsg>,
        receiver: Receiver<InternalUiMsg>,
        sync_startup: WaitGroup,
        thread_finisher: Sender<Finisher>,
    ) -> Result<thread::JoinHandle<Result<(), std::io::Error>>, std::io::Error> {
        let title;
        let paths;
        let with_net;
        // lock block
        {
            let unlocker = this.lock().unwrap();
            title = unlocker.peer_id.to_string().clone();
            paths = unlocker.paths.clone();
            with_net = unlocker.with_net.clone();
        }

        std::thread::Builder::new()
            .name("tui".into())
            .spawn(move || {
                trace!("tui waits for sync");
                // synchronizing
                sync_startup.wait();
                trace!("tui starts");
                // do finally the necessary
                // this blocks this async future
                Self::run_tui(title, paths, with_net, receiver, resender).map_err(
                    |error_text| std::io::Error::new(std::io::ErrorKind::Other, error_text),
                )?;
                info!("stopped tui");

                // send finisher since it should also stop webui
                thread_finisher.send(Finisher::TUI).unwrap_or_else(|_| {
                    info!("probably receiver got webui finisher first!");
                });

                Ok::<(), std::io::Error>(())
            })
    }

    fn spawn_message_loop(
        receiver: Receiver<UiUpdateMsg>,
        multiplex_send: Vec<Sender<InternalUiMsg>>,
    ) -> Result<thread::JoinHandle<()>, std::io::Error> {
        thread::Builder::new()
            .name("ui msg".into())
            .spawn(move || loop {
                if !Self::run_message_forwarding(&receiver, &multiplex_send) {
                    break;
                }
            })
    }

    /// Run the UIs - there is less controlling rather than showing
    fn run_tui(
        title: String,
        paths: Vec<String>,
        with_net: bool,
        tui_receiver: Receiver<InternalUiMsg>,
        resender: Sender<InternalUiMsg>,
    ) -> Result<(), String> {
        info!("tui about to run");

        // set up communication for tui messages
        info!("spawning tui async thread");
        let mut tui = Tui::new(title, &paths, with_net)?;

        task::block_on(async move {
            // message and refresh tui loop
            loop {
                // due to pressing 'q' tui will stop and hence also the loop
                if !tui.refresh().await {
                    break;
                }
                tui.run_cursive(&resender, &tui_receiver).await;
            }
        });
        Ok(())
    }

    /// Run the controller
    fn run_webui(
        webui_receiver: Receiver<InternalUiMsg>,
        net_support: bool,
        peer_representation: PeerRepresentation,
        paths: Vec<String>,
        wait_ui_sync: WaitGroup,
    ) -> io::Result<()> {
        if webbrowser::open(&["http://", config::net::WEBSOCKET_ADDR].concat()).is_err() {
            info!("Could not open browser!");
        }

        task::block_on(async move {
            info!("spawning webui async thread");
            let webui = WebUI::new(peer_representation, net_support, paths);
            webui.run(webui_receiver, wait_ui_sync).await
        })
    }

    /// This basically wraps incoming UiUpdateMsg to InternalUiMsg
    /// which kind of defines an extra layer for convenience, and to
    /// be extended and so on.
    fn run_message_forwarding(
        receiver: &Receiver<UiUpdateMsg>,
        multiplex_send: &Vec<Sender<InternalUiMsg>>,
    ) -> bool {
        if let Ok(forward_sys_message) = receiver.recv() {
            match forward_sys_message {
                UiUpdateMsg::NetUpdate(ForwardNetMessage {
                    net: recv_dialog,
                    cnt: text,
                }) => {
                    trace!("net update forwarding");
                    // todo: create a closure/fn to do a multiple send
                    let outter_containment = ForwardNetMessage::new(recv_dialog, text);
                    for forward_sender in multiplex_send {
                        forward_sender
                            .send(InternalUiMsg::Update(outter_containment.clone()))
                            .unwrap_or_else(|_| {
                                warn!("forwarding message cancelled probably due to quitting!");
                            });
                    }
                    true
                }
                UiUpdateMsg::CollectionUpdate(signal, on_off) => {
                    trace!(
                        "forwarding collection message to turn '{}'",
                        match on_off {
                            Status::ON => "on",
                            Status::OFF => "off",
                        }
                    );
                    for forward_sender in multiplex_send {
                        forward_sender
                            .send(InternalUiMsg::StartAnimate(signal.clone(), on_off.clone()))
                            .unwrap_or_else(|_| {
                                warn!("forwarding message cancelled probably due to quitting!");
                            });
                    }
                    true
                }
                UiUpdateMsg::StopUI => {
                    // if error something or Ok(false) results in the same
                    trace!("stop all message forwarding to ui");
                    false
                }
            }
        } else {
            // couldn't find a message yet (trying) but that is fine
            true
        }
    }
}
<commit_msg>[clean-up] remove net update debug trace, just spams now<commit_after>//! The ctrl module should be the general controller of the program.
//! Right now, most controlling is in the net module, and here
//! is only a light facade to the tui messages.

pub mod tui; // todo: pub is not recommended, I use it for doctest
mod webui;

use self::tui::Tui;
use self::webui::WebUI;
use super::config;

use async_std::task;
use crossbeam::sync::WaitGroup;
use libp2p_core::PeerId;
use std::{
    io,
    sync::{
        mpsc::{channel, Receiver, Sender},
        Arc, Mutex,
    },
    thread,
};

type PeerRepresentation = [u8; 16];

/// alive Signal for path from collector
/// or net search alive
#[derive(Clone, Serialize, Deserialize)]
pub enum CollectionPathAlive {
    BusyPath(usize),
    HostSearch,
}

#[derive(Clone, Serialize)]
pub enum Status {
    ON,
    OFF,
}

#[derive(Clone)]
pub enum NetMessages {
    Debug,
    ShowNewHost,
    ShowStats { show: NetStats },
}

#[derive(Clone)]
pub struct ForwardNetMessage {
    net: NetMessages,
    cnt: String,
}

impl ForwardNetMessage {
    pub fn new(net: NetMessages, cnt: String) -> Self {
        Self { net, cnt }
    }
}

/// internal messages inside ui
pub enum InternalUiMsg {
    Update(ForwardNetMessage),
    StartAnimate(CollectionPathAlive, Status),
    StepAndAnimate(CollectionPathAlive),
    Terminate,
}

#[derive(Clone)]
pub enum UiUpdateMsg {
    NetUpdate(ForwardNetMessage),
    CollectionUpdate(CollectionPathAlive, Status),
    StopUI,
}

#[derive(Copy, Clone)]
pub struct NetStats {
    pub line: usize,
    pub max: usize,
}

enum Finisher {
    TUI,
    WEBUI,
}

pub struct Ctrl {
    peer_id: PeerId,
    paths: Vec<String>,
    with_net: bool,
}

impl Ctrl {
    /// Create a new controller if everything fits.
    ///
    /// # Arguments
    /// * 'peer_id' - The peer_id this client/server uses
    /// * 'paths' - The paths that will be searched
    /// * 'receiver' - The receiver that takes incoming ctrl messages
    /// * 'with_net' - If ctrl should consider net messages
    /// * 'sync_sender' - For start up that main can be informed, "I'm ready"
    pub fn new(new_id: PeerId, paths: &Vec<String>, with_net: bool) -> Self {
        Self {
            peer_id: new_id,
            paths: paths.clone(),
            with_net,
        }
    }

    pub fn run(
        new_id: PeerId,
        paths: &Vec<String>,
        receiver: Receiver<UiUpdateMsg>,
        with_net: bool,
        wait_main: WaitGroup,
        has_webui: bool,
        has_tui: bool,
    ) -> Result<(), std::io::Error> {
        // sync both sub uis
        let wait_all_uis = WaitGroup::new();

        let (thread_finisher, finish_threads) = channel::<Finisher>();

        // create instance which will be passed into the different uis
        let instance = Ctrl::new(new_id, paths, with_net);

        let arc_self_tui = Arc::new(Mutex::new(instance));
        let arc_self_webui = arc_self_tui.clone();

        // all senders that UiUpdateMessages will be forwarded to
        let mut internal_senders: Vec<Sender<InternalUiMsg>> = vec![];

        // 1) tui thread
        let (sender_tui_to_register, receiver_to_tui_thread) = channel::<InternalUiMsg>();
        let sender_tui_only_to_finish = sender_tui_to_register.clone();
        let thread_tui = if has_tui {
            let resender = sender_tui_to_register.clone();
            let tui_waitgroup = wait_all_uis.clone();
            let thread_finisher_tui = thread_finisher.clone();

            internal_senders.push(sender_tui_to_register);
            Self::spawn_tui(
                arc_self_tui,
                resender,
                receiver_to_tui_thread,
                tui_waitgroup,
                thread_finisher_tui,
            )?
        } else {
            std::thread::spawn(|| Ok(()))
        };

        // 2) webui thread
        let (sender_wui, receiver_to_web_ui_thread) = channel::<InternalUiMsg>();
        let thread_webui = if has_webui {
            let sender_to_register = sender_wui.clone();
            let wui_waitgroup = wait_all_uis.clone();
            let thread_finisher_tui = thread_finisher.clone();

            internal_senders.push(sender_to_register);
            Self::spawn_webui(
                arc_self_webui,
                receiver_to_web_ui_thread,
                wui_waitgroup,
                thread_finisher_tui,
            )?
        } else {
            // empty thread
            std::thread::spawn(|| Ok(()))
        };

        // 3) ui message forwarding loop thread
        let forwarding_message_loop = Self::spawn_message_loop(receiver, internal_senders);

        // A) wait for sub syncs in order ...
        info!("syncing with 2 other sub threads webui and tui");
        wait_all_uis.wait();
        info!("synced with 2 other sub threads webui and tui");
        // B) ... to unlock sync/block startup with main thread
        // we are ready: up and listening!!
        info!("waiting for main thread sync");
        wait_main.wait();
        info!("synced with main thread");

        // either of these can finish and we want to block!
        match finish_threads.recv() {
            Ok(finished) => match finished {
                Finisher::TUI => {
                    info!("TUI finished first, so send to terminate WEBUI!");
                    sender_wui.send(InternalUiMsg::Terminate).unwrap();
                    let to_pass_through = thread_tui.join().unwrap();
                    drop(forwarding_message_loop); // let drop message loop only after joining!!!
                    to_pass_through
                }
                Finisher::WEBUI => {
                    info!("WEBUI finished first, so send to terminate TUI!");
                    sender_tui_only_to_finish
                        .send(InternalUiMsg::Terminate)
                        .unwrap();
                    let to_pass_through = thread_webui.join().unwrap();
                    drop(forwarding_message_loop); // let drop forwarding message loop only after joining!!!!
                    to_pass_through
                }
            },
            Err(e) => {
                error!("something really bad happenend: {}!!", e);
                drop(thread_webui);
                drop(thread_tui);
                drop(forwarding_message_loop);
                // todo: make a new error
                Ok::<(), std::io::Error>(())
            }
        }
    }

    fn spawn_webui(
        this: Arc<Mutex<Self>>,
        receiver: Receiver<InternalUiMsg>,
        wait_ui_sync: WaitGroup,
        thread_finisher: Sender<Finisher>,
    ) -> Result<thread::JoinHandle<Result<(), std::io::Error>>, std::io::Error> {
        let mut peer_representation: PeerRepresentation = [0 as u8; 16];
        let with_net;
        let paths;
        // lock block
        {
            let unlocker = this.lock().unwrap();
            paths = unlocker.paths.clone();
            peer_representation.copy_from_slice(&unlocker.peer_id.as_bytes()[..16]);
            with_net = unlocker.with_net;
        }

        thread::Builder::new().name("webui".into()).spawn(move || {
            info!("start webui");
            Self::run_webui(receiver, with_net, peer_representation, paths, wait_ui_sync).or_else(
                |forward| {
                    error!("error from webui-server: {}", forward);
                    Err(forward)
                },
            )?;
            info!("stopped webui");

            // send finish
            thread_finisher.send(Finisher::WEBUI).unwrap_or_else(|_| {
                info!("probably receiver got tui finisher first!");
            });

            Ok::<(), std::io::Error>(())
        })
    }

    fn spawn_tui(
        this: Arc<Mutex<Self>>,
        resender: Sender<InternalUiMsg>,
        receiver: Receiver<InternalUiMsg>,
        sync_startup: WaitGroup,
        thread_finisher: Sender<Finisher>,
    ) -> Result<thread::JoinHandle<Result<(), std::io::Error>>, std::io::Error> {
        let title;
        let paths;
        let with_net;
        // lock block
        {
            let unlocker = this.lock().unwrap();
            title = unlocker.peer_id.to_string().clone();
            paths = unlocker.paths.clone();
            with_net = unlocker.with_net.clone();
        }

        std::thread::Builder::new()
            .name("tui".into())
            .spawn(move || {
                trace!("tui waits for sync");
                // synchronizing
                sync_startup.wait();
                trace!("tui starts");
                // do finally the necessary
                // this blocks this async future
                Self::run_tui(title, paths, with_net, receiver, resender).map_err(
                    |error_text| std::io::Error::new(std::io::ErrorKind::Other, error_text),
                )?;
                info!("stopped tui");

                // send finisher since it should also stop webui
                thread_finisher.send(Finisher::TUI).unwrap_or_else(|_| {
                    info!("probably receiver got webui finisher first!");
                });

                Ok::<(), std::io::Error>(())
            })
    }

    fn spawn_message_loop(
        receiver: Receiver<UiUpdateMsg>,
        multiplex_send: Vec<Sender<InternalUiMsg>>,
    ) -> Result<thread::JoinHandle<()>, std::io::Error> {
        thread::Builder::new()
            .name("ui msg".into())
            .spawn(move || loop {
                if !Self::run_message_forwarding(&receiver, &multiplex_send) {
                    break;
                }
            })
    }

    /// Run the UIs - there is less controlling rather than showing
    fn run_tui(
        title: String,
        paths: Vec<String>,
        with_net: bool,
        tui_receiver: Receiver<InternalUiMsg>,
        resender: Sender<InternalUiMsg>,
    ) -> Result<(), String> {
        info!("tui about to run");

        // set up communication for tui messages
        info!("spawning tui async thread");
        let mut tui = Tui::new(title, &paths, with_net)?;

        task::block_on(async move {
            // message and refresh tui loop
            loop {
                // due to pressing 'q' tui will stop and hence also the loop
                if !tui.refresh().await {
                    break;
                }
                tui.run_cursive(&resender, &tui_receiver).await;
            }
        });
        Ok(())
    }

    /// Run the controller
    fn run_webui(
        webui_receiver: Receiver<InternalUiMsg>,
        net_support: bool,
        peer_representation: PeerRepresentation,
        paths: Vec<String>,
        wait_ui_sync: WaitGroup,
    ) -> io::Result<()> {
        if webbrowser::open(&["http://", config::net::WEBSOCKET_ADDR].concat()).is_err() {
            info!("Could not open browser!");
        }

        task::block_on(async move {
            info!("spawning webui async thread");
            let webui = WebUI::new(peer_representation, net_support, paths);
            webui.run(webui_receiver, wait_ui_sync).await
        })
    }

    /// This basically wraps incoming UiUpdateMsg to InternalUiMsg
    /// which kind of defines an extra layer for convenience, and to
    /// be extended and so on.
    fn run_message_forwarding(
        receiver: &Receiver<UiUpdateMsg>,
        multiplex_send: &Vec<Sender<InternalUiMsg>>,
    ) -> bool {
        if let Ok(forward_sys_message) = receiver.recv() {
            match forward_sys_message {
                UiUpdateMsg::NetUpdate(ForwardNetMessage {
                    net: recv_dialog,
                    cnt: text,
                }) => {
                    // todo: create a closure/fn to do a multiple send
                    let outter_containment = ForwardNetMessage::new(recv_dialog, text);
                    for forward_sender in multiplex_send {
                        forward_sender
                            .send(InternalUiMsg::Update(outter_containment.clone()))
                            .unwrap_or_else(|_| {
                                warn!("forwarding message cancelled probably due to quitting!");
                            });
                    }
                    true
                }
                UiUpdateMsg::CollectionUpdate(signal, on_off) => {
                    trace!(
                        "forwarding collection message to turn '{}'",
                        match on_off {
                            Status::ON => "on",
                            Status::OFF => "off",
                        }
                    );
                    for forward_sender in multiplex_send {
                        forward_sender
                            .send(InternalUiMsg::StartAnimate(signal.clone(), on_off.clone()))
                            .unwrap_or_else(|_| {
                                warn!("forwarding message cancelled probably due to quitting!");
                            });
                    }
                    true
                }
                UiUpdateMsg::StopUI => {
                    // if error something or Ok(false) results in the same
                    trace!("stop all message forwarding to ui");
                    false
                }
            }
        } else {
            // couldn't find a message yet (trying) but that is fine
            true
        }
    }
}
<|endoftext|>


<commit_before>use std::old_io::File;
use std::old_io::SeekStyle::SeekSet;
use elem::{PerElem, Molecule};
use error::{CTError, CTResult};
use error::CTErrorKind::{SyntaxError, DatabaseError};

macro_rules! read_err (
    () => (Err(CTError {
        kind: DatabaseError,
        desc: "Error reading the database".to_string(),
        pos: None
    }));
);

#[derive(Show, PartialEq)]
pub struct ElemData {
    pub short_name: String,
    pub long_name: String,
    pub mass: f64,
    pub atomic_num: u16,
}

pub struct ElemDatabase {
    db: File,
}

impl ElemDatabase {
    pub fn open(path: &Path) -> CTResult<ElemDatabase> {
        match File::open(path) {
            Ok(db_file) => Ok(ElemDatabase { db: db_file }),
            Err(_) => Err(CTError {
                kind: DatabaseError,
                desc: format!("Could not open database file. Expected at: {:?}",
                              path.as_str().unwrap_or("same directory as the program")),
                pos: None,
            }),
        }
    }

    pub fn get_single_data(&mut self, elem: &PerElem) -> CTResult<ElemData> {
        // we return to the beginning of the underlying file, since the data
        // might lie on a line we have previously read past
        self.db.seek(0, SeekSet).ok().expect("Internal error reading database");

        loop {
            let line = try!(self.read_line());
            if line.starts_with(elem.name.as_slice()) {
                return decode_line(&line);
            } else if self.db.eof() {
                return Err(CTError {
                    kind: SyntaxError,
                    desc: format!("Could not find element: {:?}", elem.name),
                    pos: Some((elem.pos, elem.len)),
                });
            }
        }
    }

    pub fn get_data(&mut self, elems: &Molecule) -> CTResult<Vec<ElemData>> {
        let mut out = Vec::new();
        for elem in elems.iter() {
           match self.get_single_data(elem) {
                Ok(data) => out.push(data),
                Err(e) => return Err(e),
            }
        }
        Ok(out)
    }

    fn read_line(&mut self) -> CTResult<String> {
        // we know that no line in the database is more than 30 characters long
        let mut buf = Vec::with_capacity(30);
        loop {
            match self.db.read_byte() {
                Ok(b) if b == b'\n' => break,
                Ok(b) => buf.push(b),
                Err(_) => return read_err!()
            }
        }
        String::from_utf8(buf).or_else(|_| read_err!())
    }
}

fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(CTError {
            kind: DatabaseError,
            desc: "Missing field in database".to_string(),
            pos: None
        })
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(CTError {
                kind: DatabaseError,
                desc: "Field in database corrupted".to_string(),
                pos: None,
            })
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::old_io::File;
    use std::old_io::fs;
    use elem::PerElem;

    fn make_dummy_db(name: &str, contents: &str) -> ElemDatabase {
        if let Err(e) = File::create(&Path::new(name)).and_then(|mut f| f.write_str(contents)) {
            // if we can't make the database we can't test, so just abort here
            panic!("Could not create dummy database: {:?}", e.desc);
        }
        ElemDatabase::open(&Path::new(name)).unwrap()
    }

    fn remove_dummy_db(name: &str) {
        if let Err(e) = fs::unlink(&Path::new(name)) {
            // if we can't remove the database something is wrong, and we abort the test
            panic!("Could not remove dummy database: {:?}", e.desc);
        }
    }

    #[test]
    fn multiple_elems() {
        let db_name = "multiple_elems_db";
        let mut db = make_dummy_db(db_name,
            "A;1;Abba;2\n\
            B;3;Beta;4\n");
        let raw_result = db.get_data(&vec!(
            PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 1 },
            PerElem { name: "A".to_string(), coef: 1, pos: 1, len: 1 }
        ));
        let expected = vec!(
            ElemData {
                short_name: "B".to_string(),
                long_name: "Beta".to_string(),
                mass: 3.0,
                atomic_num: 4,
            },
            ElemData {
                short_name: "A".to_string(),
                long_name: "Abba".to_string(),
                mass: 1.0,
                atomic_num: 2,
            }
        );
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn find_elem() {
        let db_name = "find_elem_db";
        let mut db = make_dummy_db(db_name,
            "A;0;Abba;0\n\
            B;123.456789;Beta;12\n\
            C;0;Coop;0\n");
        let raw_result = db.get_single_data(
            &PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 2 }
        );
        let expected = ElemData {
            short_name: "B".to_string(),
            long_name: "Beta".to_string(),
            mass: 123.456789,
            atomic_num: 12,
        };
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn missing_elem() {
        let db_name = "missing_elem_db";
        let mut db = make_dummy_db(db_name, "A;123.456789;Abba;12\n");
        let result = db.get_single_data(
            &PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn missing_field() {
        let db_name = "missing_field_db";
        let mut db = make_dummy_db(db_name, "A;");
        let result = db.get_single_data(
            &PerElem { name: "A".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn field_corrupted() {
        let db_name = "field_corrupted_db";
        let mut db = make_dummy_db(db_name, "A;not a number;Abba;12\n");
        let result = db.get_single_data(
            &PerElem { name: "A".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }
}<commit_msg>Fix bug in database searcher<commit_after>use std::old_io::File;
use std::old_io::SeekStyle::SeekSet;
use std::old_io::IoErrorKind::EndOfFile;
use elem::{PerElem, Molecule};
use error::{CTError, CTResult};
use error::CTErrorKind::{SyntaxError, DatabaseError};

macro_rules! read_err (
    () => (Err(CTError {
        kind: DatabaseError,
        desc: "Error reading the database".to_string(),
        pos: None
    }));
);

#[derive(Show, PartialEq)]
pub struct ElemData {
    pub short_name: String,
    pub long_name: String,
    pub mass: f64,
    pub atomic_num: u16,
}

pub struct ElemDatabase {
    db: File,
}

impl ElemDatabase {
    pub fn open(path: &Path) -> CTResult<ElemDatabase> {
        match File::open(path) {
            Ok(db_file) => Ok(ElemDatabase { db: db_file }),
            Err(_) => Err(CTError {
                kind: DatabaseError,
                desc: format!("Could not open database file. Expected at: {:?}",
                              path.as_str().unwrap_or("same directory as the program")),
                pos: None,
            }),
        }
    }

    pub fn get_single_data(&mut self, elem: &PerElem) -> CTResult<ElemData> {
        // we return to the beginning of the underlying file, since the data
        // might lie on a line we have previously read past
        self.db.seek(0, SeekSet).ok().expect("Internal error reading database");

        loop {
            // TODO: make so this function returns the 'not found' error
            let line = try!(self.read_line(elem));
            if line.starts_with(elem.name.as_slice()) {
                return decode_line(&line);
            }
        }
    }

    pub fn get_data(&mut self, elems: &Molecule) -> CTResult<Vec<ElemData>> {
        let mut out = Vec::new();
        for elem in elems.iter() {
           match self.get_single_data(elem) {
                Ok(data) => out.push(data),
                Err(e) => return Err(e),
            }
        }
        Ok(out)
    }

    fn read_line(&mut self, elem: &PerElem) -> CTResult<String> {
        // we know that no line in the database is more than 30 characters long
        let mut buf = Vec::with_capacity(30);
        loop {
            match self.db.read_byte() {
                Ok(b) if b == b'\n' => break,
                Ok(b) => buf.push(b),
                // we let EOF error return without error, so we can check for it further up
                Err(ref e) if e.kind == EndOfFile => return Err(CTError {
                    kind: SyntaxError,
                    desc: format!("Could not find element: {:?}", elem.name),
                    pos: Some((elem.pos, elem.len)),
                }),
                Err(_) => return read_err!()
            }
        }
        String::from_utf8(buf).or_else(|_| read_err!())
    }
}

fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(CTError {
            kind: DatabaseError,
            desc: "Missing field in database".to_string(),
            pos: None
        })
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(CTError {
                kind: DatabaseError,
                desc: "Field in database corrupted".to_string(),
                pos: None,
            })
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::old_io::File;
    use std::old_io::fs;
    use elem::PerElem;

    fn make_dummy_db(name: &str, contents: &str) -> ElemDatabase {
        if let Err(e) = File::create(&Path::new(name)).and_then(|mut f| f.write_str(contents)) {
            // if we can't make the database we can't test, so just abort here
            panic!("Could not create dummy database: {:?}", e.desc);
        }
        ElemDatabase::open(&Path::new(name)).unwrap()
    }

    fn remove_dummy_db(name: &str) {
        if let Err(e) = fs::unlink(&Path::new(name)) {
            // if we can't remove the database something is wrong, and we abort the test
            panic!("Could not remove dummy database: {:?}", e.desc);
        }
    }

    #[test]
    fn multiple_elems() {
        let db_name = "multiple_elems_db";
        let mut db = make_dummy_db(db_name,
            "A;1;Abba;2\n\
            B;3;Beta;4\n");
        let raw_result = db.get_data(&vec!(
            PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 1 },
            PerElem { name: "A".to_string(), coef: 1, pos: 1, len: 1 }
        ));
        let expected = vec!(
            ElemData {
                short_name: "B".to_string(),
                long_name: "Beta".to_string(),
                mass: 3.0,
                atomic_num: 4,
            },
            ElemData {
                short_name: "A".to_string(),
                long_name: "Abba".to_string(),
                mass: 1.0,
                atomic_num: 2,
            }
        );
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn find_elem() {
        let db_name = "find_elem_db";
        let mut db = make_dummy_db(db_name,
            "A;0;Abba;0\n\
            B;123.456789;Beta;12\n\
            C;0;Coop;0\n");
        let raw_result = db.get_single_data(
            &PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 2 }
        );
        let expected = ElemData {
            short_name: "B".to_string(),
            long_name: "Beta".to_string(),
            mass: 123.456789,
            atomic_num: 12,
        };
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn missing_elem() {
        let db_name = "missing_elem_db";
        let mut db = make_dummy_db(db_name, "A;123.456789;Abba;12\n");
        let result = db.get_single_data(
            &PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn missing_field() {
        let db_name = "missing_field_db";
        let mut db = make_dummy_db(db_name, "A;");
        let result = db.get_single_data(
            &PerElem { name: "A".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn field_corrupted() {
        let db_name = "field_corrupted_db";
        let mut db = make_dummy_db(db_name, "A;not a number;Abba;12\n");
        let result = db.get_single_data(
            &PerElem { name: "A".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }
}<|endoftext|>


<commit_before>use std::io::{File, BufferedReader};
use error::{CTResult, CTDatabaseError, CTSyntaxError};
use error::CTError::{DatabaseError, SyntaxError};
use token::Token;
use token::TokenKind::Elem;

#[derive(Show)]
pub struct ElemData {
    short_name: String,
    long_name: String,
    mass: f64,
    atomic_num: u16,
}

pub struct ElemDatabase {
    db: BufferedReader<File>,
}

impl ElemDatabase {
    pub fn open(path: &str) -> CTResult<ElemDatabase> {
        match File::open(&Path::new(path)) {
            Ok(db_file) => Ok(ElemDatabase { db: BufferedReader::new(db_file) }),
            Err(_) => Err(DatabaseError(CTDatabaseError {
                desc: format!("Could not open database file. Expected at: {:?}", path)
            })),
        }
    }

    pub fn get_elem_data(&mut self, elem: &Token) -> CTResult<ElemData> {
        let mut line_iter = self.db.lines();
        let short_name = match elem.tok {
            Elem(ref name) => name.as_slice(),
            _ => unreachable!(),
        };
        // TODO: Make this cleaner, and avoid returns
        loop {
            match line_iter.next() {
                Some(Ok(ref line)) if line.starts_with(short_name) => return decode_line(line),
                Some(Err(_)) => return Err(DatabaseError(CTDatabaseError {
                    desc: "Error reading the database".to_string()
                })),
                None => return Err(SyntaxError(CTSyntaxError {
                    desc: format!("Could not find element: {:?}", short_name),
                    pos: elem.pos,
                    len: elem.len,
                })),
                _ => (),
            }
        }
    }
}

fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(DatabaseError(CTDatabaseError {
            desc: "Missing field in database".to_string()
        }))
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(DatabaseError(CTDatabaseError {
                desc: "Field in database corrupted".to_string()
            }))
        }
    }
}<commit_msg>Make the decode_line function public<commit_after>use std::io::{File, BufferedReader};
use error::{CTResult, CTDatabaseError, CTSyntaxError};
use error::CTError::{DatabaseError, SyntaxError};
use token::Token;
use token::TokenKind::Elem;

#[derive(Show)]
pub struct ElemData {
    short_name: String,
    long_name: String,
    mass: f64,
    atomic_num: u16,
}

pub struct ElemDatabase {
    db: BufferedReader<File>,
}

impl ElemDatabase {
    pub fn open(path: &str) -> CTResult<ElemDatabase> {
        match File::open(&Path::new(path)) {
            Ok(db_file) => Ok(ElemDatabase { db: BufferedReader::new(db_file) }),
            Err(_) => Err(DatabaseError(CTDatabaseError {
                desc: format!("Could not open database file. Expected at: {:?}", path)
            })),
        }
    }

    pub fn get_elem_data(&mut self, elem: &Token) -> CTResult<ElemData> {
        let mut line_iter = self.db.lines();
        let short_name = match elem.tok {
            Elem(ref name) => name.as_slice(),
            _ => unreachable!(),
        };
        // TODO: Make this cleaner, and avoid returns
        loop {
            match line_iter.next() {
                Some(Ok(ref line)) if line.starts_with(short_name) => return decode_line(line),
                Some(Err(_)) => return Err(DatabaseError(CTDatabaseError {
                    desc: "Error reading the database".to_string()
                })),
                None => return Err(SyntaxError(CTSyntaxError {
                    desc: format!("Could not find element: {:?}", short_name),
                    pos: elem.pos,
                    len: elem.len,
                })),
                _ => (),
            }
        }
    }
}

pub fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(DatabaseError(CTDatabaseError {
            desc: "Missing field in database".to_string()
        }))
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(DatabaseError(CTDatabaseError {
                desc: "Field in database corrupted".to_string()
            }))
        }
    }
}<|endoftext|>


<commit_before>use std::fmt;
use std::ops::Index;
use std::slice::Iter;
use std::vec::Vec;

use itertools::Itertools;

use types::*;

pub type ColumnId = usize;

#[derive(Clone, PartialEq, Debug)]
pub struct Field {
  pub name: String,
  pub decl: FieldDecl
}

impl Field {
  pub fn new<T: AsRef<str>>(name: T, decl: FieldDecl) -> Field {
    Field {
      name: name.as_ref().to_string(),
      decl: decl
    }
  }

  pub fn scalar<T: AsRef<str>>(name: T, ty: Ty) -> Field {
    Field::new(name, FieldDecl::Scalar(ty))
  }

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

pub fn array<T: AsRef<str>>(name: T, decl: FieldDecl) -> Field {
  Field::new(name, FieldDecl::Array(Box::new(decl)))
}

pub fn scalar_decl(ty: Ty) -> FieldDecl {
  FieldDecl::Scalar(ty)
}

pub fn record<T: AsRef<str>>(name: T, r: Record) -> Field {
  Field::new(name, FieldDecl::Record(r))
}

pub fn record_from_vec<T: AsRef<str>>(name: T, fields: Vec<Field>) -> Field {
  Field {
    name: name.as_ref().to_string(),
    decl: FieldDecl::Record(Record::new(fields))
  }
}

pub fn map<T: AsRef<str>>(name: T, key_ty: FieldDecl, val_ty: FieldDecl) -> Field {
  Field::new(name, FieldDecl::Map(Box::new(key_ty), Box::new(val_ty)))
}

impl fmt::Display for Field {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", display_field(self))
  }
}

fn display_field(f: &Field) -> String {
  format!("{} {}", f.name, f.decl)
}

#[derive(Clone, PartialEq, Debug)]
pub enum FieldDecl {
  Scalar (Ty),
  Record (Record),
  Array  (Box<FieldDecl>),
  Map    (Box<FieldDecl>, Box<FieldDecl>)
}

impl FieldDecl {
  pub fn from_fields(fields: Vec<Field>) -> FieldDecl {
    FieldDecl::Record(Record::new(fields))
  }
}

impl fmt::Display for FieldDecl {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", display_field_decl(self))
  }
}

fn display_field_decl(decl: &FieldDecl) -> String {
  match *decl {
    FieldDecl::Scalar(ref ty)         => ty.name().to_owned(),
    FieldDecl::Record(ref r)          => format!("record ({})", r),
    FieldDecl::Array (ref vt)         => format!("array<{}>", vt),
    FieldDecl::Map   (ref kt, ref vt) => format!("map<{},{}>", kt, vt)
  }
}

#[derive(Clone, PartialEq, Debug)]
pub struct Record {
  fields: Vec<Field>
}

impl Record {
  pub fn new(fields: Vec<Field>) -> Record {
    Record {fields: fields}
  }

  pub fn size(&self) -> usize {
    self.fields.len()
  }

  pub fn find_id(&self, name: &str) -> Option<ColumnId> {
    self.fields.iter().position(|f| f.name == name)
  }

  pub fn find_by_name(&self, name : &str) -> Option<&Field> {
    self.fields.iter().filter(|&f| f.name == name).next()
  }

  pub fn iter(&self) -> Iter<Field> {
    self.fields.iter()
  }
}

impl Index<ColumnId> for Record {
  type Output = Field;
  fn index<'a>(&'a self, id: ColumnId) -> &'a Field {
    debug_assert!(id < self.fields.len(), "Field index is out of range");
    &self.fields[id]
  }
}

impl fmt::Display for Record {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", display_fields(&self.fields))
  }
}

#[inline]
fn display_fields(fields: &Vec<Field>) -> String {
  fields.iter().map(|f| display_field(f)).join(", ")
}

#[allow(dead_code)]
fn create_test_schema() -> Record {
	Record::new(vec![
    Field::scalar("col1", *INT4_TY),
    record_from_vec("col2", vec![
        Field::scalar("col3", *INT4_TY),
        Field::scalar("col4", *INT4_TY)
      ]
    ),
    record_from_vec(
        "col5", vec![
        array("col6", scalar_decl(*INT8_TY)),
        array("col7", scalar_decl(*INT8_TY)),
      ]
    ),
    map("col8", scalar_decl(*INT4_TY), FieldDecl::from_fields(
      vec![
        Field::scalar("col9",  *TEXT_TY),
        Field::scalar("col10", *TEXT_TY),
      ])
    )
  ])
}

#[test]
fn test_schema_creation() {
  let schema = create_test_schema();
  assert_eq!(
    "col1 int4, col2 record (col3 int4, col4 int4), col5 record (col6 array<int8>, col7 array<int8>), col8 map<int4,record (col9 text, col10 text)>".to_string(),
    format!("{}", schema));
}
<commit_msg>Refactor the constroctor of Field and FieldDecl.<commit_after>use std::fmt;
use std::ops::Index;
use std::slice::Iter;
use std::vec::Vec;

use itertools::Itertools;

use types::*;

pub type ColumnId = usize;

#[derive(Clone, PartialEq, Debug)]
pub struct Field {
  pub name: String,
  pub decl: FieldDecl
}

impl Field {
  pub fn new<T: AsRef<str>>(name: T, decl: FieldDecl) -> Field {
    Field {
      name: name.as_ref().to_string(),
      decl: decl
    }
  }

  pub fn scalar<T: AsRef<str>>(name: T, ty: Ty) -> Field {
    Field::new(name, FieldDecl::Scalar(ty))
  }

  pub fn array<T: AsRef<str>>(name: T, decl: FieldDecl) -> Field {
    Field::new(name, FieldDecl::Array(Box::new(decl)))
  }

  pub fn map<T: AsRef<str>>(name: T, key_ty: FieldDecl, val_ty: FieldDecl) -> Field {
    Field::new(name, FieldDecl::Map(Box::new(key_ty), Box::new(val_ty)))
  }

  pub fn record<T: AsRef<str>>(name: T, r: Record) -> Field {
    Field::new(name, FieldDecl::Record(r))
  }

  pub fn record_from_vec<T: AsRef<str>>(name: T, fields: Vec<Field>) -> Field {
    Field {
      name: name.as_ref().to_string(),
      decl: FieldDecl::Record(Record::new(fields))
    }
  }

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

impl fmt::Display for Field {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", display_field(self))
  }
}

fn display_field(f: &Field) -> String {
  format!("{} {}", f.name, f.decl)
}

#[derive(Clone, PartialEq, Debug)]
pub enum FieldDecl {
  Scalar (Ty),
  Record (Record),
  Array  (Box<FieldDecl>),
  Map    (Box<FieldDecl>, Box<FieldDecl>)
}

impl FieldDecl {
  pub fn scalar(ty: Ty) -> FieldDecl {
    FieldDecl::Scalar(ty)
  }

  pub fn record_from_fields(fields: Vec<Field>) -> FieldDecl {
    FieldDecl::Record(Record::new(fields))
  }
}

impl fmt::Display for FieldDecl {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", display_field_decl(self))
  }
}

fn display_field_decl(decl: &FieldDecl) -> String {
  match *decl {
    FieldDecl::Scalar(ref ty)         => ty.name().to_owned(),
    FieldDecl::Record(ref r)          => format!("record ({})", r),
    FieldDecl::Array (ref vt)         => format!("array<{}>", vt),
    FieldDecl::Map   (ref kt, ref vt) => format!("map<{},{}>", kt, vt)
  }
}

#[derive(Clone, PartialEq, Debug)]
pub struct Record {
  fields: Vec<Field>
}

impl Record {
  pub fn new(fields: Vec<Field>) -> Record {
    Record {fields: fields}
  }

  pub fn size(&self) -> usize {
    self.fields.len()
  }

  pub fn find_id(&self, name: &str) -> Option<ColumnId> {
    self.fields.iter().position(|f| f.name == name)
  }

  pub fn find_by_name(&self, name : &str) -> Option<&Field> {
    self.fields.iter().filter(|&f| f.name == name).next()
  }

  pub fn iter(&self) -> Iter<Field> {
    self.fields.iter()
  }
}

impl Index<ColumnId> for Record {
  type Output = Field;
  fn index<'a>(&'a self, id: ColumnId) -> &'a Field {
    debug_assert!(id < self.fields.len(), "Field index is out of range");
    &self.fields[id]
  }
}

impl fmt::Display for Record {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", display_fields(&self.fields))
  }
}

#[inline]
fn display_fields(fields: &Vec<Field>) -> String {
  fields.iter().map(|f| display_field(f)).join(", ")
}

#[allow(dead_code)]
fn create_test_schema() -> Record {
	Record::new(vec![
    Field::scalar("col1", *INT4_TY),
    Field::record_from_vec("col2", vec![
        Field::scalar("col3", *INT4_TY),
        Field::scalar("col4", *INT4_TY)
      ]
    ),
    Field::record_from_vec("col5", vec![
        Field::array("col6", FieldDecl::scalar(*INT8_TY)),
        Field::array("col7", FieldDecl::scalar(*INT8_TY)),
      ]
    ),
    Field::map("col8", FieldDecl::scalar(*INT4_TY), FieldDecl::record_from_fields(
      vec![
        Field::scalar("col9",  *TEXT_TY),
        Field::scalar("col10", *TEXT_TY),
      ])
    )
  ])
}

#[test]
fn test_schema_creation() {
  let schema = create_test_schema();
  assert_eq!(
    "col1 int4, col2 record (col3 int4, col4 int4), col5 record (col6 array<int8>, col7 array<int8>), col8 map<int4,record (col9 text, col10 text)>".to_string(),
    format!("{}", schema));
}
<|endoftext|>


<commit_before>use hyper::Client;
use hyper::Result as HyperResult;
use hyper::client::Response as HyperResponse;
use hyper::header::{Headers, ContentType};
use hyper::mime::{Mime, TopLevel, SubLevel};
use hyper::status::StatusCode;

use event::Event;
use types::Result;
use expected_version::ExpectedVersion;
use error::HesError;
use error::UserErrorKind;
use connection::ConnectionInfo;

use api::ESExpectedVersion;
use api::to_error::*;

pub struct Appender<'a> {
    connection_info: &'a ConnectionInfo,
}

impl<'a> Appender<'a> {
    pub fn new(connection_info: &'a ConnectionInfo) -> Appender {
        Appender { connection_info: connection_info }
    }

    pub fn append<I>(&self, stream_name: &str,
                     expected_version: ExpectedVersion,
                     events: I) -> Result<()>
        where I: IntoIterator<Item = Event> {
        let http_client = Client::default();

        let result = http_client.post(&self.url(stream_name))
            .headers(build_headers(expected_version))
            .body(&request_body(events))
            .send();

        to_hes_result(result)
    }

    fn url(&self, stream_name: &str) -> String {
        format!("http://{}:{}/streams/{}",
                self.connection_info.host,
                self.connection_info.port,
                stream_name)
    }
}

fn build_headers(expected_version: ExpectedVersion) -> Headers {
    let mut headers = Headers::new();
    headers.set(
        ContentType(Mime(TopLevel::Application,
                         SubLevel::Ext("vnd.eventstore.events+json".to_owned()), vec![]))
    );
    headers.set(ESExpectedVersion(expected_version.into()));

    headers
}

fn request_body<I>(events: I) -> String where I: IntoIterator<Item = Event> {
    let events_as_json: Vec<String> = events.into_iter().map(|e| {
        let mut result: String = format!(r#"{{"eventType":"{}""#, e.event_type);

        if let Some(id) = e.event_id {
            let id_pair = &format!(r#","eventId":"{}""#, id);
            result.push_str(id_pair)
        }

        if let Some(ref data) = e.data {
            let data_pair = &format!(r#","data":{}"#, data);
            result.push_str(data_pair)
        }

        result.push_str(r#"}"#);

        result
    }).collect::<_>();

    format!("[{}]", events_as_json.join(","))
}

fn to_hes_result(result: HyperResult<HyperResponse>) -> Result<()> {
    match result {
        Ok(response) => {
            match response.status {
                StatusCode::Created => Ok(()),
                _ => stream_deleted_error(response)
                    .and_then(event_number_mismatch_error)
                    .map_err(|kind| HesError::UserError(kind))
                    .and_then(default_error)
            }
        },
        Err(err) => Err(HesError::UserError(UserErrorKind::Http(err)))
    }
}
<commit_msg>Use `impl From<hyper::error::Error> for HesError` to simplify api::append_to_stream::Appender<commit_after>use hyper::Client;
use hyper::client::Response as HyperResponse;
use hyper::header::{Headers, ContentType};
use hyper::mime::{Mime, TopLevel, SubLevel};
use hyper::status::StatusCode;

use event::Event;
use types::Result;
use expected_version::ExpectedVersion;
use error::HesError;
use connection::ConnectionInfo;

use api::ESExpectedVersion;
use api::to_error::*;

pub struct Appender<'a> {
    connection_info: &'a ConnectionInfo,
}

impl<'a> Appender<'a> {
    pub fn new(connection_info: &'a ConnectionInfo) -> Appender {
        Appender { connection_info: connection_info }
    }

    pub fn append<I>(&self, stream_name: &str,
                     expected_version: ExpectedVersion,
                     events: I) -> Result<()>
        where I: IntoIterator<Item = Event> {
        let http_client = Client::default();

        let response = try!(http_client.post(&self.url(stream_name))
            .headers(build_headers(expected_version))
            .body(&request_body(events))
            .send());

        to_hes_result(response)
    }

    fn url(&self, stream_name: &str) -> String {
        format!("http://{}:{}/streams/{}",
                self.connection_info.host,
                self.connection_info.port,
                stream_name)
    }
}

fn build_headers(expected_version: ExpectedVersion) -> Headers {
    let mut headers = Headers::new();
    headers.set(
        ContentType(Mime(TopLevel::Application,
                         SubLevel::Ext("vnd.eventstore.events+json".to_owned()), vec![]))
    );
    headers.set(ESExpectedVersion(expected_version.into()));

    headers
}

fn request_body<I>(events: I) -> String where I: IntoIterator<Item = Event> {
    let events_as_json: Vec<String> = events.into_iter().map(|e| {
        let mut result: String = format!(r#"{{"eventType":"{}""#, e.event_type);

        if let Some(id) = e.event_id {
            let id_pair = &format!(r#","eventId":"{}""#, id);
            result.push_str(id_pair)
        }

        if let Some(ref data) = e.data {
            let data_pair = &format!(r#","data":{}"#, data);
            result.push_str(data_pair)
        }

        result.push_str(r#"}"#);

        result
    }).collect::<_>();

    format!("[{}]", events_as_json.join(","))
}

fn to_hes_result(response: HyperResponse) -> Result<()> {
    match response.status {
        StatusCode::Created => Ok(()),
        _ => stream_deleted_error(response)
            .and_then(event_number_mismatch_error)
            .map_err(|kind| HesError::UserError(kind))
            .and_then(default_error)
    }
}
<|endoftext|>


<commit_before>//! FIXME: write short doc here

pub mod codegen;

use anyhow::Context;
pub use anyhow::Result;
use std::{
    env, fs,
    io::{Error as IoError, ErrorKind},
    path::{Path, PathBuf},
    process::{Command, Output, Stdio},
};

use crate::codegen::Mode;

const TOOLCHAIN: &str = "stable";

pub fn project_root() -> PathBuf {
    Path::new(
        &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()),
    )
    .ancestors()
    .nth(1)
    .unwrap()
    .to_path_buf()
}

pub struct Cmd<'a> {
    pub unix: &'a str,
    pub windows: &'a str,
    pub work_dir: &'a str,
}

impl Cmd<'_> {
    pub fn run(self) -> Result<()> {
        if cfg!(windows) {
            run(self.windows, self.work_dir)
        } else {
            run(self.unix, self.work_dir)
        }
    }
    pub fn run_with_output(self) -> Result<Output> {
        if cfg!(windows) {
            run_with_output(self.windows, self.work_dir)
        } else {
            run_with_output(self.unix, self.work_dir)
        }
    }
}

pub fn run(cmdline: &str, dir: &str) -> Result<()> {
    do_run(cmdline, dir, |c| {
        c.stdout(Stdio::inherit());
    })
    .map(|_| ())
}

pub fn run_with_output(cmdline: &str, dir: &str) -> Result<Output> {
    do_run(cmdline, dir, |_| {})
}

pub fn run_rustfmt(mode: Mode) -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_rustfmt().context("install rustfmt")?,
    };

    if mode == Mode::Verify {
        run(&format!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN), ".")?;
    } else {
        run(&format!("rustup run {} -- cargo fmt", TOOLCHAIN), ".")?;
    }
    Ok(())
}

pub fn install_rustfmt() -> Result<()> {
    run(&format!("rustup install {}", TOOLCHAIN), ".")?;
    run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".")
}

pub fn install_pre_commit_hook() -> Result<()> {
    let result_path =
        PathBuf::from(format!("./.git/hooks/pre-commit{}", std::env::consts::EXE_SUFFIX));
    if !result_path.exists() {
        let me = std::env::current_exe()?;
        fs::copy(me, result_path)?;
    } else {
        Err(IoError::new(ErrorKind::AlreadyExists, "Git hook already created"))?;
    }
    Ok(())
}

pub fn run_clippy() -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_clippy().context("install clippy")?,
    };

    let allowed_lints = [
        "clippy::collapsible_if",
        "clippy::map_clone", // FIXME: remove when Iterator::copied stabilizes (1.36.0)
        "clippy::needless_pass_by_value",
        "clippy::nonminimal_bool",
        "clippy::redundant_pattern_matching",
    ];
    run(
        &format!(
            "rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
            TOOLCHAIN,
            allowed_lints.join(" -A ")
        ),
        ".",
    )?;
    Ok(())
}

pub fn install_clippy() -> Result<()> {
    run(&format!("rustup install {}", TOOLCHAIN), ".")?;
    run(&format!("rustup component add clippy --toolchain {}", TOOLCHAIN), ".")
}

pub fn run_fuzzer() -> Result<()> {
    match Command::new("cargo")
        .args(&["fuzz", "--help"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => run("cargo install cargo-fuzz", ".")?,
    };

    run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax")
}

pub fn reformat_staged_files() -> Result<()> {
    let root = project_root();
    let output = Command::new("git")
        .arg("diff")
        .arg("--diff-filter=MAR")
        .arg("--name-only")
        .arg("--cached")
        .current_dir(&root)
        .output()?;
    if !output.status.success() {
        anyhow::bail!(
            "`git diff --diff-filter=MAR --name-only --cached` exited with {}",
            output.status
        );
    }
    for line in String::from_utf8(output.stdout)?.lines() {
        run(&format!("git update-index --add {}", root.join(line).to_string_lossy()), ".")?;
    }
    Ok(())
}

fn do_run<F>(cmdline: &str, dir: &str, mut f: F) -> Result<Output>
where
    F: FnMut(&mut Command),
{
    eprintln!("\nwill run: {}", cmdline);
    let proj_dir = project_root().join(dir);
    let mut args = cmdline.split_whitespace();
    let exec = args.next().unwrap();
    let mut cmd = Command::new(exec);
    f(cmd.args(args).current_dir(proj_dir).stderr(Stdio::inherit()));
    let output = cmd.output().with_context(|| format!("running `{}`", cmdline))?;
    if !output.status.success() {
        anyhow::bail!("`{}` exited with {}", cmdline, output.status);
    }
    Ok(output)
}
<commit_msg>Use rustup toolchain instead of rustup install<commit_after>//! FIXME: write short doc here

pub mod codegen;

use anyhow::Context;
pub use anyhow::Result;
use std::{
    env, fs,
    io::{Error as IoError, ErrorKind},
    path::{Path, PathBuf},
    process::{Command, Output, Stdio},
};

use crate::codegen::Mode;

const TOOLCHAIN: &str = "stable";

pub fn project_root() -> PathBuf {
    Path::new(
        &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()),
    )
    .ancestors()
    .nth(1)
    .unwrap()
    .to_path_buf()
}

pub struct Cmd<'a> {
    pub unix: &'a str,
    pub windows: &'a str,
    pub work_dir: &'a str,
}

impl Cmd<'_> {
    pub fn run(self) -> Result<()> {
        if cfg!(windows) {
            run(self.windows, self.work_dir)
        } else {
            run(self.unix, self.work_dir)
        }
    }
    pub fn run_with_output(self) -> Result<Output> {
        if cfg!(windows) {
            run_with_output(self.windows, self.work_dir)
        } else {
            run_with_output(self.unix, self.work_dir)
        }
    }
}

pub fn run(cmdline: &str, dir: &str) -> Result<()> {
    do_run(cmdline, dir, |c| {
        c.stdout(Stdio::inherit());
    })
    .map(|_| ())
}

pub fn run_with_output(cmdline: &str, dir: &str) -> Result<Output> {
    do_run(cmdline, dir, |_| {})
}

pub fn run_rustfmt(mode: Mode) -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_rustfmt().context("install rustfmt")?,
    };

    if mode == Mode::Verify {
        run(&format!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN), ".")?;
    } else {
        run(&format!("rustup run {} -- cargo fmt", TOOLCHAIN), ".")?;
    }
    Ok(())
}

pub fn install_rustfmt() -> Result<()> {
    run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?;
    run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".")
}

pub fn install_pre_commit_hook() -> Result<()> {
    let result_path =
        PathBuf::from(format!("./.git/hooks/pre-commit{}", std::env::consts::EXE_SUFFIX));
    if !result_path.exists() {
        let me = std::env::current_exe()?;
        fs::copy(me, result_path)?;
    } else {
        Err(IoError::new(ErrorKind::AlreadyExists, "Git hook already created"))?;
    }
    Ok(())
}

pub fn run_clippy() -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_clippy().context("install clippy")?,
    };

    let allowed_lints = [
        "clippy::collapsible_if",
        "clippy::map_clone", // FIXME: remove when Iterator::copied stabilizes (1.36.0)
        "clippy::needless_pass_by_value",
        "clippy::nonminimal_bool",
        "clippy::redundant_pattern_matching",
    ];
    run(
        &format!(
            "rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
            TOOLCHAIN,
            allowed_lints.join(" -A ")
        ),
        ".",
    )?;
    Ok(())
}

pub fn install_clippy() -> Result<()> {
    run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?;
    run(&format!("rustup component add clippy --toolchain {}", TOOLCHAIN), ".")
}

pub fn run_fuzzer() -> Result<()> {
    match Command::new("cargo")
        .args(&["fuzz", "--help"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => run("cargo install cargo-fuzz", ".")?,
    };

    run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax")
}

pub fn reformat_staged_files() -> Result<()> {
    let root = project_root();
    let output = Command::new("git")
        .arg("diff")
        .arg("--diff-filter=MAR")
        .arg("--name-only")
        .arg("--cached")
        .current_dir(&root)
        .output()?;
    if !output.status.success() {
        anyhow::bail!(
            "`git diff --diff-filter=MAR --name-only --cached` exited with {}",
            output.status
        );
    }
    for line in String::from_utf8(output.stdout)?.lines() {
        run(&format!("git update-index --add {}", root.join(line).to_string_lossy()), ".")?;
    }
    Ok(())
}

fn do_run<F>(cmdline: &str, dir: &str, mut f: F) -> Result<Output>
where
    F: FnMut(&mut Command),
{
    eprintln!("\nwill run: {}", cmdline);
    let proj_dir = project_root().join(dir);
    let mut args = cmdline.split_whitespace();
    let exec = args.next().unwrap();
    let mut cmd = Command::new(exec);
    f(cmd.args(args).current_dir(proj_dir).stderr(Stdio::inherit()));
    let output = cmd.output().with_context(|| format!("running `{}`", cmdline))?;
    if !output.status.success() {
        anyhow::bail!("`{}` exited with {}", cmdline, output.status);
    }
    Ok(output)
}
<|endoftext|>