<commit_before>use std::io;
use std::io::net::tcp;
use std::io::net::ip::SocketAddr;
use std::io::BufferedReader;
use common::{Header, HTTPVersion, Method};
use Request;
use url::Path;
use sequential::{SequentialReader, SequentialReaderBuilder, SequentialWriterBuilder};

/// A ClientConnection is an object that will store a socket to a client
/// and return Request objects.
pub struct ClientConnection {
    remote_addr: io::IoResult<SocketAddr>,

    source: SequentialReaderBuilder<tcp::TcpStream>,
    sink: SequentialWriterBuilder<tcp::TcpStream>,

    // where to read the next header
	next_header_source: BufferedReader<SequentialReader<tcp::TcpStream>>,

    // set to true if the client sent a "Connection: close" in the previous request
    connection_must_close: bool,
}

impl ClientConnection {
    pub fn new(mut socket: tcp::TcpStream) -> ClientConnection {
        socket.set_timeout(Some(10000));

        let remote_addr = socket.peer_name();

        let mut source = SequentialReaderBuilder::new(socket.clone());
        let first_header = source.next().unwrap();

        ClientConnection {
            source: source,
            sink: SequentialWriterBuilder::new(socket),
            remote_addr: remote_addr,
            next_header_source: BufferedReader::new(first_header),
            connection_must_close: false,
        }
    }

    /// Reads a request from the stream.
    /// Blocks until the header has been read.
    fn read(&mut self) -> io::IoResult<Request> {
        let (method, path, version, headers) = {
            let mut lines = self.next_header_source.lines();

            // reading the request line
            let (method, path, version) =
                try!(parse_request_line(
                    match lines.next() {
                        Some(line) => try!(line),
                        None => return Err(gen_invalid_input(
                                    "Missing request line"))
                    }.as_slice().trim()
                ));

            // getting all headers
            let headers = {
                let mut headers = Vec::new();
                loop {
                    match lines.next() {
                        Some(line) => {
                            let line = try!(line);
                            if line.as_slice().trim().len() == 0 { break };
                            headers.push(
                                try!(parse_header(line.as_slice().trim()))
                            )
                        },
                        None => break
                    }
                }
                headers
            };

            (method, path, version, headers)
        };

        // finding length of body
        let body_length = headers.iter()
            .find(|h| h.field.equiv(&"Content-Length"))
            .and_then(|h| from_str::<uint>(h.value.as_slice()))
            .unwrap_or(0u);

        // building the next reader
        let data_reader = self.source.next().unwrap();
        self.next_header_source = BufferedReader::new(self.source.next().unwrap());

        // building the request
        Ok(Request {
            data_reader: box data_reader,
            response_writer: box self.sink.next().unwrap(),
            remote_addr: self.remote_addr.clone().unwrap(),     // TODO: could fail
            method: method,
            path: path,
            http_version: version,
            headers: headers,
            body_length: body_length,
        })
    }
}

impl Iterator<Request> for ClientConnection {
    /// Blocks until the next Request is available.
    /// Returns None when no new Requests will come from the client.
    fn next(&mut self) -> Option<Request> {
        // the client sent a "connection: close" header in this previous request
        //  or is using HTTP 1.0, meaning that no new request will come
        if self.connection_must_close {
            return None
        }

        // TODO: send back message to client in case of parsing error
        loop {
            let rq = match self.read() {
                Err(_) => return None,
                Ok(rq) => rq
            };

            // updating the status of the connection
            {
                let connection_header = rq.headers.iter()
                    .find(|h| h.field.equiv(&"Connection")).map(|h| h.value.as_slice());

                if connection_header == Some("close") {
                    self.connection_must_close = true;
                } else if rq.http_version == HTTPVersion(1, 0) &&
                        connection_header != Some("keep-alive")
                {
                    self.connection_must_close = true;
                }
            }

            // returning the request
            return Some(rq);
        }
    }
}

/// Generates an IoError with invalid input.
/// This function is here because it is incredibly annoying to create this error.
fn gen_invalid_input(desc: &'static str) -> io::IoError {
    io::IoError {
        kind: io::InvalidInput,
        desc: desc,
        detail: None
    }
}

/// Parses a "HTTP/1.1" string.
fn parse_http_version(version: &str) -> io::IoResult<HTTPVersion> {
    let elems = version.splitn('/', 2).map(|e| e.to_string()).collect::<Vec<String>>();
    if elems.len() != 2 {
        return Err(gen_invalid_input("Wrong HTTP version format"))
    }

    let elems = elems.get(1).as_slice().splitn('.', 2)
        .map(|e| e.to_string()).collect::<Vec<String>>();
    if elems.len() != 2 {
        return Err(gen_invalid_input("Wrong HTTP version format"))
    }

    match (from_str(elems.get(0).as_slice()), from_str(elems.get(1).as_slice())) {
        (Some(major), Some(minor)) =>
            Ok(HTTPVersion(major, minor)),
        _ => Err(gen_invalid_input("Wrong HTTP version format"))
    }
}

/// Parses the request line of the request.
/// eg. GET / HTTP/1.1
fn parse_request_line(line: &str) -> io::IoResult<(Method, Path, HTTPVersion)> {
    let mut words = line.words();

    let method = words.next();
    let path = words.next();
    let version = words.next();

    let (method, path, version) = match (method, path, version) {
        (Some(m), Some(p), Some(v)) => (m, p, v),
        _ => return Err(gen_invalid_input("Missing element in request line"))
    };

    let method = match from_str(method) {
        Some(method) => method,
        None => return Err(gen_invalid_input("Could not parse method"))
    };

    let path = match Path::parse(path) {
        Ok(p) => p,
        Err(_) => return Err(gen_invalid_input("Wrong requested URL"))
    };

    let version = try!(parse_http_version(version));

    Ok((method, path, version))
}

/// Parses a header line.
/// eg. Host: example.com
fn parse_header(line: &str) -> io::IoResult<Header> {
    let elems = line.splitn(':', 2).map(|e| e.to_string()).collect::<Vec<String>>();

    if elems.len() <= 1 {
        return Err(gen_invalid_input(
            "Wrong header format (no ':')"))
    }
    if elems.get(1).as_slice().chars().next() != Some(' ') {
        return Err(gen_invalid_input(
            "Wrong header format (missing space after ':')"))
    }

    let field = match from_str(elems.get(0).as_slice()) {
        None => return Err(gen_invalid_input("Could not parse header")),
        Some(f) => f
    };

    Ok(Header {
        field: field,
        value: elems.get(1).as_slice().slice_from(1).to_string()
    })
}

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

    #[test]
    fn test_parse_request_line() {
        let (method, path, ver) =
            super::parse_request_line("GET /hello HTTP/1.1").unwrap();

        assert!(method.equiv(&"get"));
        assert!(path == from_str("/hello").unwrap());
        assert!(ver == ::common::HTTPVersion(1, 1));

        assert!(super::parse_request_line("GET /hello").is_err());
        assert!(super::parse_request_line("qsd qsd qsd").is_err());
    }

    #[test]
    fn test_parse_header() {
        let header = super::parse_header("Content-Type: text/html").unwrap();

        assert!(header.field.equiv(&"content-type"));
        assert!(header.value.as_slice() == "text/html");

        assert!(super::parse_header("hello world").is_err());
    }
}
<commit_msg>Minor cleanup<commit_after>use std::io;
use std::io::net::tcp;
use std::io::net::ip::SocketAddr;
use std::io::BufferedReader;
use common::{Header, HTTPVersion, Method};
use Request;
use url::Path;
use sequential::{SequentialReader, SequentialReaderBuilder, SequentialWriterBuilder};

/// A ClientConnection is an object that will store a socket to a client
/// and return Request objects.
pub struct ClientConnection {
    // address of the client
    remote_addr: io::IoResult<SocketAddr>,

    // sequence of Readers to the stream, so that the data is not read in
    //  the wrong order
    source: SequentialReaderBuilder<tcp::TcpStream>,

    // sequence of Writers to the stream, to avoid writing response #2 before
    //  response #1
    sink: SequentialWriterBuilder<tcp::TcpStream>,

    // Reader to read the next header from
	next_header_source: BufferedReader<SequentialReader<tcp::TcpStream>>,

    // set to true if the client sent a "Connection: close" in the previous request
    connection_must_close: bool,
}

impl ClientConnection {
    /// Creates a new ClientConnection that takes ownership of the TcpStream.
    pub fn new(mut socket: tcp::TcpStream) -> ClientConnection {
        socket.set_timeout(Some(10000));

        let remote_addr = socket.peer_name();

        let mut source = SequentialReaderBuilder::new(socket.clone());
        let first_header = source.next().unwrap();

        ClientConnection {
            source: source,
            sink: SequentialWriterBuilder::new(socket),
            remote_addr: remote_addr,
            next_header_source: BufferedReader::new(first_header),
            connection_must_close: false,
        }
    }

    /// Reads a request from the stream.
    /// Blocks until the header has been read.
    fn read(&mut self) -> io::IoResult<Request> {
        let (method, path, version, headers) = {
            let mut lines = self.next_header_source.lines();

            // reading the request line
            let (method, path, version) =
                try!(parse_request_line(
                    match lines.next() {
                        Some(line) => try!(line),
                        None => return Err(gen_invalid_input(
                                    "Missing request line"))
                    }.as_slice().trim()
                ));

            // getting all headers
            let headers = {
                let mut headers = Vec::new();
                loop {
                    match lines.next() {
                        Some(line) => {
                            let line = try!(line);
                            if line.as_slice().trim().len() == 0 { break };
                            headers.push(
                                try!(parse_header(line.as_slice().trim()))
                            )
                        },
                        None => break
                    }
                }
                headers
            };

            (method, path, version, headers)
        };

        // finding length of body
        let body_length = headers.iter()
            .find(|h| h.field.equiv(&"Content-Length"))
            .and_then(|h| from_str::<uint>(h.value.as_slice()))
            .unwrap_or(0u);

        // building the next reader
        let data_reader = self.source.next().unwrap();
        self.next_header_source = BufferedReader::new(self.source.next().unwrap());

        // building the writer
        let writer = self.sink.next().unwrap();

        // building the request
        Ok(Request {
            data_reader: box data_reader,
            response_writer: box writer,
            remote_addr: self.remote_addr.clone().unwrap(),     // TODO: could fail
            method: method,
            path: path,
            http_version: version,
            headers: headers,
            body_length: body_length,
        })
    }
}

impl Iterator<Request> for ClientConnection {
    /// Blocks until the next Request is available.
    /// Returns None when no new Requests will come from the client.
    fn next(&mut self) -> Option<Request> {
        // the client sent a "connection: close" header in this previous request
        //  or is using HTTP 1.0, meaning that no new request will come
        if self.connection_must_close {
            return None
        }

        // TODO: send back message to client in case of parsing error
        loop {
            let rq = match self.read() {
                Err(_) => return None,
                Ok(rq) => rq
            };

            // updating the status of the connection
            {
                let connection_header = rq.headers.iter()
                    .find(|h| h.field.equiv(&"Connection")).map(|h| h.value.as_slice());

                if connection_header == Some("close") {
                    self.connection_must_close = true;
                } else if rq.http_version == HTTPVersion(1, 0) &&
                        connection_header != Some("keep-alive")
                {
                    self.connection_must_close = true;
                }
            }

            // returning the request
            return Some(rq);
        }
    }
}

/// Generates an IoError with invalid input.
/// This function is here because it is incredibly annoying to create this error.
fn gen_invalid_input(desc: &'static str) -> io::IoError {
    io::IoError {
        kind: io::InvalidInput,
        desc: desc,
        detail: None
    }
}

/// Parses a "HTTP/1.1" string.
fn parse_http_version(version: &str) -> io::IoResult<HTTPVersion> {
    let elems = version.splitn('/', 2).map(|e| e.to_string()).collect::<Vec<String>>();
    if elems.len() != 2 {
        return Err(gen_invalid_input("Wrong HTTP version format"))
    }

    let elems = elems.get(1).as_slice().splitn('.', 2)
        .map(|e| e.to_string()).collect::<Vec<String>>();
    if elems.len() != 2 {
        return Err(gen_invalid_input("Wrong HTTP version format"))
    }

    match (from_str(elems.get(0).as_slice()), from_str(elems.get(1).as_slice())) {
        (Some(major), Some(minor)) =>
            Ok(HTTPVersion(major, minor)),
        _ => Err(gen_invalid_input("Wrong HTTP version format"))
    }
}

/// Parses the request line of the request.
/// eg. GET / HTTP/1.1
fn parse_request_line(line: &str) -> io::IoResult<(Method, Path, HTTPVersion)> {
    let mut words = line.words();

    let method = words.next();
    let path = words.next();
    let version = words.next();

    let (method, path, version) = match (method, path, version) {
        (Some(m), Some(p), Some(v)) => (m, p, v),
        _ => return Err(gen_invalid_input("Missing element in request line"))
    };

    let method = match from_str(method) {
        Some(method) => method,
        None => return Err(gen_invalid_input("Could not parse method"))
    };

    let path = match Path::parse(path) {
        Ok(p) => p,
        Err(_) => return Err(gen_invalid_input("Wrong requested URL"))
    };

    let version = try!(parse_http_version(version));

    Ok((method, path, version))
}

/// Parses a header line.
/// eg. Host: example.com
fn parse_header(line: &str) -> io::IoResult<Header> {
    let elems = line.splitn(':', 2).map(|e| e.to_string()).collect::<Vec<String>>();

    if elems.len() <= 1 {
        return Err(gen_invalid_input(
            "Wrong header format (no ':')"))
    }
    if elems.get(1).as_slice().chars().next() != Some(' ') {
        return Err(gen_invalid_input(
            "Wrong header format (missing space after ':')"))
    }

    let field = match from_str(elems.get(0).as_slice()) {
        None => return Err(gen_invalid_input("Could not parse header")),
        Some(f) => f
    };

    Ok(Header {
        field: field,
        value: elems.get(1).as_slice().slice_from(1).to_string()
    })
}

#[cfg(test)]
mod test {
    #[test]
    fn test_parse_request_line() {
        let (method, path, ver) =
            super::parse_request_line("GET /hello HTTP/1.1").unwrap();

        assert!(method.equiv(&"get"));
        assert!(path == from_str("/hello").unwrap());
        assert!(ver == ::common::HTTPVersion(1, 1));

        assert!(super::parse_request_line("GET /hello").is_err());
        assert!(super::parse_request_line("qsd qsd qsd").is_err());
    }

    #[test]
    fn test_parse_header() {
        let header = super::parse_header("Content-Type: text/html").unwrap();

        assert!(header.field.equiv(&"content-type"));
        assert!(header.value.as_slice() == "text/html");

        assert!(super::parse_header("hello world").is_err());
    }
}
<|endoftext|>


<commit_before>use crossbeam;

use std::fs::{self, File};
use std::io::{self, Write, ErrorKind};
use std::path::{Path, PathBuf};
use std::ffi::OsStr;
use liquid::Value;
use walkdir::{WalkDir, DirEntry, WalkDirIterator};
use document::Document;
use error::{Error, Result};
use config::Config;
use chrono::{UTC, FixedOffset};
use chrono::offset::TimeZone;
use rss::{Channel, Rss};
use std::sync::Arc;
use glob::Pattern;

fn ignore_filter(entry: &DirEntry, source: &Path, ignore: &[Pattern]) -> bool {
    let path = entry.path().strip_prefix(&source).unwrap_or(entry.path());
    let file_name = entry.file_name().to_str().unwrap_or("");
    if file_name != "./" && (file_name.starts_with("_") || file_name.starts_with(".")) {
        return false;
    }
    !ignore.iter().any(|p| p.matches_path(path))
}

fn compare_paths(a: &Path, b: &Path) -> bool {
    match (fs::canonicalize(a), fs::canonicalize(b)) {
        (Ok(p), Ok(p2)) => p == p2,
        _ => false
    }
}

/// The primary build function that tranforms a directory into a site
pub fn build(config: &Config) -> Result<()> {
    trace!("Build configuration: {:?}", config);

    // join("") makes sure path has a trailing slash
    let source = PathBuf::from(&config.source).join("");
    let source = source.as_path();
    let dest = PathBuf::from(&config.dest).join("");
    let dest = dest.as_path();

    let template_extensions: Vec<&OsStr> = config.template_extensions
        .iter()
        .map(OsStr::new)
        .collect();

    let layouts = source.join(&config.layouts);
    let layouts = layouts.as_path();
    let posts = source.join(&config.posts);
    let posts = posts.as_path();

    debug!("Layouts directory: {:?}", layouts);
    debug!("posts directory: {:?}", posts);

    let mut documents = vec![];

    let walker = WalkDir::new(&source)
        .into_iter()
        .filter_entry(|e| {
            (ignore_filter(e, &source, &config.ignore) || compare_paths(e.path(), posts))
            && !compare_paths(e.path(), dest)
        })
        .filter_map(|e| e.ok());

    for entry in walker {
        let extension = &entry.path().extension().unwrap_or(OsStr::new(""));
        if template_extensions.contains(extension) {
            // if the document is in the posts folder it's considered a post
            let is_post = entry.path().parent().map(|p| compare_paths(p, posts)).unwrap_or(false);

            let doc = try!(Document::parse(&entry.path(), &source, is_post, &config.post_path));
            documents.push(doc);
        }
    }

    // January 1, 1970 0:00:00 UTC, the beginning of time
    let default_date = UTC.timestamp(0, 0).with_timezone(&FixedOffset::east(0));

    // sort documents by date, if there's no date (none was provided or it couldn't be read) then
    // fall back to the default date
    documents.sort_by(|a, b| {
        b.date.unwrap_or(default_date.clone()).cmp(&a.date.unwrap_or(default_date.clone()))
    });

    // check if we should create an RSS file and create it!
    if let &Some(ref path) = &config.rss {
        try!(create_rss(path, dest, &config, &documents));
    }

    // these are the attributes of all documents that are posts, so that they can be
    // passed to the renderer
    // TODO: do we have to clone these?
    let post_data: Vec<Value> = documents.iter()
        .filter(|x| x.is_post)
        .map(|x| Value::Object(x.attributes.clone()))
        .collect();

    // thread handles to join later
    let mut handles = vec![];

    // generate documents (in parallel)
    crossbeam::scope(|scope| {
        let post_data = Arc::new(post_data);

        for doc in &documents {
            trace!("Generating {}", doc.path);
            let post_data = post_data.clone();

            let handle = scope.spawn(move || {
                let content = try!(doc.as_html(&source, &post_data, &layouts));
                create_document_file(content, &doc.path, &dest)
            });
            handles.push(handle);
        }
    });

    for handle in handles {
        try!(handle.join());
    }

    // copy all remaining files in the source to the destination
    if !compare_paths(source, dest) {
        info!("Copying remaining assets");
        let source_str = try!(source.to_str()
            .ok_or(format!("Cannot convert pathname {:?} to UTF-8", source)));

        let walker = WalkDir::new(&source)
            .into_iter()
            .filter_entry(|e| {
                ignore_filter(e, &source, &config.ignore) &&
                !template_extensions.contains(&e.path()
                    .extension()
                    .unwrap_or(OsStr::new("")))
                && !compare_paths(e.path(), dest)
            })
            .filter_map(|e| e.ok());

        for entry in walker {
            let entry_path = try!(entry.path()
                .to_str()
                .ok_or(format!("Cannot convert pathname {:?} to UTF-8", entry.path())));

            let relative = try!(entry_path.split(source_str)
                .last()
                .ok_or(format!("Empty path")));

            if try!(entry.metadata()).is_dir() {
                try!(fs::create_dir_all(&dest.join(relative)));
                debug!("Created new directory {:?}", dest.join(relative));
            } else {
                if let Some(ref parent) = Path::new(relative).parent() {
                    try!(fs::create_dir_all(&dest.join(parent)));
                }

                try!(fs::copy(entry.path(), &dest.join(relative))
                    .map_err(|e| format!("Could not copy {:?}: {}", entry.path(), e)));
                debug!("Copied {:?} to {:?}", entry.path(), dest.join(relative));
            }
        }
    }

    Ok(())
}

// creates a new RSS file with the contents of the site blog
fn create_rss(path: &str, dest: &Path, config: &Config, documents: &[Document]) -> Result<()> {
    match (&config.name, &config.description, &config.link) {
        // these three fields are mandatory in the RSS standard
        (&Some(ref name), &Some(ref description), &Some(ref link)) => {
            trace!("Generating RSS data");

            let items = documents.iter()
                .filter(|x| x.is_post)
                .map(|doc| doc.to_rss(link))
                .collect();

            let channel = Channel {
                title: name.to_owned(),
                link: link.to_owned(),
                description: description.to_owned(),
                items: items,
                ..Default::default()
            };

            let rss = Rss(channel);
            let rss_string = rss.to_string();
            trace!("RSS data: {}", rss_string);

            let rss_path = dest.join(path);

            let mut rss_file = try!(File::create(&rss_path));
            try!(rss_file.write_all(&rss_string.into_bytes()));

            info!("Created RSS file at {}", rss_path.display());
            Ok(())
        }
        _ => {
            Err(Error::from("name, description and link need to be defined in the config file to \
                             generate RSS"))
        }
    }
}

/// A slightly less efficient implementation of fs::create_dir_all
/// that eliminates the race condition problems of the original
fn create_dir_all(path: &Path) -> io::Result<()> {
    let mut new_path = PathBuf::new();
    for component in path {
        new_path.push(component);
        match fs::create_dir(&new_path) {
            Ok(_) => {}
            Err(ref e) if e.kind() == ErrorKind::AlreadyExists => {}
            Err(e) => return Err(e),
        }
    }
    Ok(())
}

fn create_document_file<T: AsRef<Path>>(content: String, path: T, dest: &Path) -> Result<()> {
    // construct target path
    let file_path_buf = dest.join(path);
    let file_path = file_path_buf.as_path();

    // create target directories if any exist
    if let Some(parent) = file_path.parent() {
        try!(create_dir_all(parent).map_err(|e| format!("Could not create {:?}: {}", parent, e)));
    }

    let mut file = try!(File::create(&file_path)
        .map_err(|e| format!("Could not create {:?}: {}", file_path, e)));

    try!(file.write_all(&content.into_bytes()));
    info!("Created {}", file_path.display());
    Ok(())
}
<commit_msg>Fix ignore_filter ignoring source<commit_after>use crossbeam;

use std::fs::{self, File};
use std::io::{self, Write, ErrorKind};
use std::path::{Path, PathBuf};
use std::ffi::OsStr;
use liquid::Value;
use walkdir::{WalkDir, DirEntry, WalkDirIterator};
use document::Document;
use error::{Error, Result};
use config::Config;
use chrono::{UTC, FixedOffset};
use chrono::offset::TimeZone;
use rss::{Channel, Rss};
use std::sync::Arc;
use glob::Pattern;

fn ignore_filter(entry: &DirEntry, source: &Path, ignore: &[Pattern]) -> bool {
    if compare_paths(entry.path(), source) {
        return true;
    }
    let path = entry.path().strip_prefix(&source).unwrap_or(entry.path());
    let file_name = entry.file_name().to_str().unwrap_or("");
    if file_name.starts_with("_") || file_name.starts_with(".") {
        return false;
    }
    !ignore.iter().any(|p| p.matches_path(path))
}

fn compare_paths(a: &Path, b: &Path) -> bool {
    match (fs::canonicalize(a), fs::canonicalize(b)) {
        (Ok(p), Ok(p2)) => p == p2,
        _ => false
    }
}

/// The primary build function that tranforms a directory into a site
pub fn build(config: &Config) -> Result<()> {
    trace!("Build configuration: {:?}", config);

    // join("") makes sure path has a trailing slash
    let source = PathBuf::from(&config.source).join("");
    let source = source.as_path();
    let dest = PathBuf::from(&config.dest).join("");
    let dest = dest.as_path();

    let template_extensions: Vec<&OsStr> = config.template_extensions
        .iter()
        .map(OsStr::new)
        .collect();

    let layouts = source.join(&config.layouts);
    let layouts = layouts.as_path();
    let posts = source.join(&config.posts);
    let posts = posts.as_path();

    debug!("Layouts directory: {:?}", layouts);
    debug!("posts directory: {:?}", posts);

    let mut documents = vec![];

    let walker = WalkDir::new(&source)
        .into_iter()
        .filter_entry(|e| {
            (ignore_filter(e, &source, &config.ignore) || compare_paths(e.path(), posts))
            && !compare_paths(e.path(), dest)
        })
        .filter_map(|e| e.ok());

    for entry in walker {
        let extension = &entry.path().extension().unwrap_or(OsStr::new(""));
        if template_extensions.contains(extension) {
            // if the document is in the posts folder it's considered a post
            let is_post = entry.path().parent().map(|p| compare_paths(p, posts)).unwrap_or(false);

            let doc = try!(Document::parse(&entry.path(), &source, is_post, &config.post_path));
            documents.push(doc);
        }
    }

    // January 1, 1970 0:00:00 UTC, the beginning of time
    let default_date = UTC.timestamp(0, 0).with_timezone(&FixedOffset::east(0));

    // sort documents by date, if there's no date (none was provided or it couldn't be read) then
    // fall back to the default date
    documents.sort_by(|a, b| {
        b.date.unwrap_or(default_date.clone()).cmp(&a.date.unwrap_or(default_date.clone()))
    });

    // check if we should create an RSS file and create it!
    if let &Some(ref path) = &config.rss {
        try!(create_rss(path, dest, &config, &documents));
    }

    // these are the attributes of all documents that are posts, so that they can be
    // passed to the renderer
    // TODO: do we have to clone these?
    let post_data: Vec<Value> = documents.iter()
        .filter(|x| x.is_post)
        .map(|x| Value::Object(x.attributes.clone()))
        .collect();

    // thread handles to join later
    let mut handles = vec![];

    // generate documents (in parallel)
    crossbeam::scope(|scope| {
        let post_data = Arc::new(post_data);

        for doc in &documents {
            trace!("Generating {}", doc.path);
            let post_data = post_data.clone();

            let handle = scope.spawn(move || {
                let content = try!(doc.as_html(&source, &post_data, &layouts));
                create_document_file(content, &doc.path, &dest)
            });
            handles.push(handle);
        }
    });

    for handle in handles {
        try!(handle.join());
    }

    // copy all remaining files in the source to the destination
    if !compare_paths(source, dest) {
        info!("Copying remaining assets");
        let source_str = try!(source.to_str()
            .ok_or(format!("Cannot convert pathname {:?} to UTF-8", source)));

        let walker = WalkDir::new(&source)
            .into_iter()
            .filter_entry(|e| {
                ignore_filter(e, &source, &config.ignore) &&
                !template_extensions.contains(&e.path()
                    .extension()
                    .unwrap_or(OsStr::new("")))
                && !compare_paths(e.path(), dest)
            })
            .filter_map(|e| e.ok());

        for entry in walker {
            let entry_path = try!(entry.path()
                .to_str()
                .ok_or(format!("Cannot convert pathname {:?} to UTF-8", entry.path())));

            let relative = try!(entry_path.split(source_str)
                .last()
                .ok_or(format!("Empty path")));

            if try!(entry.metadata()).is_dir() {
                try!(fs::create_dir_all(&dest.join(relative)));
                debug!("Created new directory {:?}", dest.join(relative));
            } else {
                if let Some(ref parent) = Path::new(relative).parent() {
                    try!(fs::create_dir_all(&dest.join(parent)));
                }

                try!(fs::copy(entry.path(), &dest.join(relative))
                    .map_err(|e| format!("Could not copy {:?}: {}", entry.path(), e)));
                debug!("Copied {:?} to {:?}", entry.path(), dest.join(relative));
            }
        }
    }

    Ok(())
}

// creates a new RSS file with the contents of the site blog
fn create_rss(path: &str, dest: &Path, config: &Config, documents: &[Document]) -> Result<()> {
    match (&config.name, &config.description, &config.link) {
        // these three fields are mandatory in the RSS standard
        (&Some(ref name), &Some(ref description), &Some(ref link)) => {
            trace!("Generating RSS data");

            let items = documents.iter()
                .filter(|x| x.is_post)
                .map(|doc| doc.to_rss(link))
                .collect();

            let channel = Channel {
                title: name.to_owned(),
                link: link.to_owned(),
                description: description.to_owned(),
                items: items,
                ..Default::default()
            };

            let rss = Rss(channel);
            let rss_string = rss.to_string();
            trace!("RSS data: {}", rss_string);

            let rss_path = dest.join(path);

            let mut rss_file = try!(File::create(&rss_path));
            try!(rss_file.write_all(&rss_string.into_bytes()));

            info!("Created RSS file at {}", rss_path.display());
            Ok(())
        }
        _ => {
            Err(Error::from("name, description and link need to be defined in the config file to \
                             generate RSS"))
        }
    }
}

/// A slightly less efficient implementation of fs::create_dir_all
/// that eliminates the race condition problems of the original
fn create_dir_all(path: &Path) -> io::Result<()> {
    let mut new_path = PathBuf::new();
    for component in path {
        new_path.push(component);
        match fs::create_dir(&new_path) {
            Ok(_) => {}
            Err(ref e) if e.kind() == ErrorKind::AlreadyExists => {}
            Err(e) => return Err(e),
        }
    }
    Ok(())
}

fn create_document_file<T: AsRef<Path>>(content: String, path: T, dest: &Path) -> Result<()> {
    // construct target path
    let file_path_buf = dest.join(path);
    let file_path = file_path_buf.as_path();

    // create target directories if any exist
    if let Some(parent) = file_path.parent() {
        try!(create_dir_all(parent).map_err(|e| format!("Could not create {:?}: {}", parent, e)));
    }

    let mut file = try!(File::create(&file_path)
        .map_err(|e| format!("Could not create {:?}: {}", file_path, e)));

    try!(file.write_all(&content.into_bytes()));
    info!("Created {}", file_path.display());
    Ok(())
}
<|endoftext|>


<commit_before>//! Provides access to the symbolserver config
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::borrow::Cow;
use std::io::BufReader;

use num_cpus;
use serde_yaml;
use url::Url;
use rusoto::Region;
use chrono::Duration;
use log::LogLevelFilter;

use super::{Result, ErrorKind};

#[derive(Deserialize, Debug, Default, Clone)]
struct AwsConfig {
    access_key: Option<String>,
    secret_key: Option<String>,
    bucket_url: Option<String>,
    region: Option<String>,
}

#[derive(Deserialize, Debug, Default, Clone)]
struct ServerConfig {
    host: Option<String>,
    port: Option<u16>,
    healthcheck_ttl: Option<u32>,
    sync_interval: Option<u32>,
    threads: Option<usize>,
}

#[derive(Deserialize, Debug, Default, Clone)]
struct LogConfig {
    level: Option<String>,
    file: Option<PathBuf>,
}

/// Central config object that exposes the information from
/// the symbolserver yaml config.
#[derive(Deserialize, Debug, Default, Clone)]
pub struct Config {
    #[serde(default)]
    aws: AwsConfig,
    #[serde(default)]
    server: ServerConfig,
    #[serde(default)]
    log: LogConfig,
    symbol_dir: Option<PathBuf>,
}

impl Config {
    /// Loads a config from a given file
    pub fn load_file<P: AsRef<Path>>(path: P) -> Result<Config> {
        let f = fs::File::open(path)?;
        serde_yaml::from_reader(BufReader::new(f)).map_err(|err| {
            ErrorKind::ConfigError(err).into()
        })
    }

    /// Loads a config from the default location
    pub fn load_default() -> Result<Config> {
        let mut home = match env::home_dir() {
            Some(home) => home,
            None => { return Ok(Default::default()) },
        };
        home.push(".sentry-symbolserver.yml");

        Ok(if let Ok(_) = fs::metadata(&home) {
            Config::load_file(&home)?
        } else {
            Default::default()
        })
    }

    /// Return the AWS access key
    pub fn get_aws_access_key<'a>(&'a self) -> Option<&str> {
        self.aws.access_key.as_ref().map(|x| &**x)
    }

    /// Return the AWS secret key
    pub fn get_aws_secret_key<'a>(&'a self) -> Option<&str> {
        self.aws.secret_key.as_ref().map(|x| &**x)
    }

    /// Return the AWS S3 bucket URL
    pub fn get_aws_bucket_url<'a>(&'a self) -> Result<Url> {
        let url = if let Some(value) = self.aws.bucket_url.as_ref() {
            Url::parse(value)?
        } else {
            return Err(ErrorKind::MissingConfigKey(
                "aws.bucket_url").into());
        };
        if url.scheme() != "s3" {
            return Err(ErrorKind::BadConfigKey(
                "aws.bucket_url", "The scheme for the bucket URL needs to be s3").into());
        } else if url.host_str().is_none() {
            return Err(ErrorKind::BadConfigKey(
                "aws.bucket_url", "The bucket URL is missing a name").into());
        }
        Ok(url)
    }

    /// Overrides the AWS bucket URL.
    pub fn set_aws_bucket_url(&mut self, value: &str) {
        self.aws.bucket_url = Some(value.to_string());
    }

    /// Return the AWS region
    pub fn get_aws_region(&self) -> Result<Region> {
        let region_opt = self.aws.region
            .as_ref()
            .map(|x| x.to_string())
            .or_else(|| env::var("AWS_DEFAULT_REGION").ok());

        if let Some(region) = region_opt {
            if let Ok(rv) = region.parse() {
                Ok(rv)
            } else {
                Err(ErrorKind::BadConfigKey(
                    "aws.region", "An unknown AWS region was provided").into())
            }
        } else {
            Ok(Region::UsEast1)
        }
    }

    /// Overrides the AWS region
    pub fn set_aws_region(&mut self, value: Region) {
        self.aws.region = Some(value.to_string());
    }

    /// Return the path where symbols are stored.
    pub fn get_symbol_dir<'a>(&'a self) -> Result<Cow<'a, Path>> {
        if let Some(ref path) = self.symbol_dir {
            Ok(Cow::Borrowed(path.as_path()))
        } else if let Ok(dir) = env::var("SYMBOLSERVER_SYMBOL_DIR") {
            Ok(Cow::Owned(PathBuf::from(dir)))
        } else {
            Err(ErrorKind::MissingConfigKey("symbol_dir").into())
        }
    }

    /// Override the symbol dir.
    pub fn set_symbol_dir<P: AsRef<Path>>(&mut self, value: P) {
        self.symbol_dir = Some(value.as_ref().to_path_buf());
    }

    /// Return the bind target for the http server
    pub fn get_server_socket_addr(&self) -> Result<(&str, u16)> {
        let host = self.server.host.as_ref().map(|x| x.as_str()).unwrap_or("127.0.0.1");
        let port = self.server.port.unwrap_or(3000);
        Ok((host, port))
    }

    /// Return the server healthcheck ttl
    pub fn get_server_healthcheck_ttl(&self) -> Result<Duration> {
        let ttl = self.server.healthcheck_ttl.unwrap_or(60);
        Ok(Duration::seconds(ttl as i64))
    }

    /// Return the server sync interval
    pub fn get_server_sync_interval(&self) -> Result<Duration> {
        let ttl = self.server.sync_interval.unwrap_or(60);
        if ttl <= 0 {
            Err(ErrorKind::BadConfigKey(
                "server.sync_interval", "Sync interval has to be positive").into())
        } else {
            Ok(Duration::seconds(ttl as i64))
        }
    }

    /// Return the number of threads to listen on
    pub fn get_server_threads(&self) -> Result<usize> {
        Ok(self.server.threads.unwrap_or_else(|| {
            num_cpus::get() * 5 / 4
        }))
    }

    /// Return the log level filter
    pub fn get_log_level_filter(&self) -> Result<LogLevelFilter> {
        if let Some(ref lvl) = self.log.level {
            lvl.parse().map_err(|_| ErrorKind::BadConfigKey(
                "log.level", "unknown log level").into())
        } else {
            Ok(LogLevelFilter::Info)
        }
    }

    /// Override the log level filter in the config
    pub fn set_log_level_filter(&mut self, value: LogLevelFilter) {
        self.log.level = Some(value.to_string());
    }

    /// Return the log filename
    pub fn get_log_filename(&self) -> Result<Option<&Path>> {
        if let Some(ref path) = self.log.file {
            Ok(Some(&*path))
        } else {
            Ok(None)
        }
    }
}
<commit_msg>Added AWS_BUCKET_URL<commit_after>//! Provides access to the symbolserver config
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::borrow::Cow;
use std::io::BufReader;

use num_cpus;
use serde_yaml;
use url::Url;
use rusoto::Region;
use chrono::Duration;
use log::LogLevelFilter;

use super::{Result, ErrorKind};

#[derive(Deserialize, Debug, Default, Clone)]
struct AwsConfig {
    access_key: Option<String>,
    secret_key: Option<String>,
    bucket_url: Option<String>,
    region: Option<String>,
}

#[derive(Deserialize, Debug, Default, Clone)]
struct ServerConfig {
    host: Option<String>,
    port: Option<u16>,
    healthcheck_ttl: Option<u32>,
    sync_interval: Option<u32>,
    threads: Option<usize>,
}

#[derive(Deserialize, Debug, Default, Clone)]
struct LogConfig {
    level: Option<String>,
    file: Option<PathBuf>,
}

/// Central config object that exposes the information from
/// the symbolserver yaml config.
#[derive(Deserialize, Debug, Default, Clone)]
pub struct Config {
    #[serde(default)]
    aws: AwsConfig,
    #[serde(default)]
    server: ServerConfig,
    #[serde(default)]
    log: LogConfig,
    symbol_dir: Option<PathBuf>,
}

impl Config {
    /// Loads a config from a given file
    pub fn load_file<P: AsRef<Path>>(path: P) -> Result<Config> {
        let f = fs::File::open(path)?;
        serde_yaml::from_reader(BufReader::new(f)).map_err(|err| {
            ErrorKind::ConfigError(err).into()
        })
    }

    /// Loads a config from the default location
    pub fn load_default() -> Result<Config> {
        let mut home = match env::home_dir() {
            Some(home) => home,
            None => { return Ok(Default::default()) },
        };
        home.push(".sentry-symbolserver.yml");

        Ok(if let Ok(_) = fs::metadata(&home) {
            Config::load_file(&home)?
        } else {
            Default::default()
        })
    }

    /// Return the AWS access key
    pub fn get_aws_access_key<'a>(&'a self) -> Option<&str> {
        self.aws.access_key.as_ref().map(|x| &**x)
    }

    /// Return the AWS secret key
    pub fn get_aws_secret_key<'a>(&'a self) -> Option<&str> {
        self.aws.secret_key.as_ref().map(|x| &**x)
    }

    /// Return the AWS S3 bucket URL
    pub fn get_aws_bucket_url<'a>(&'a self) -> Result<Url> {
        let url = if let Some(value) = self.aws.bucket_url.as_ref() {
            Url::parse(value)?
        } else if let Ok(value) = env::var("AWS_BUCKET_URL") {
            Url::parse(&value)?
        } else {
            return Err(ErrorKind::MissingConfigKey(
                "aws.bucket_url").into());
        };
        if url.scheme() != "s3" {
            return Err(ErrorKind::BadConfigKey(
                "aws.bucket_url", "The scheme for the bucket URL needs to be s3").into());
        } else if url.host_str().is_none() {
            return Err(ErrorKind::BadConfigKey(
                "aws.bucket_url", "The bucket URL is missing a name").into());
        }
        Ok(url)
    }

    /// Overrides the AWS bucket URL.
    pub fn set_aws_bucket_url(&mut self, value: &str) {
        self.aws.bucket_url = Some(value.to_string());
    }

    /// Return the AWS region
    pub fn get_aws_region(&self) -> Result<Region> {
        let region_opt = self.aws.region
            .as_ref()
            .map(|x| x.to_string())
            .or_else(|| env::var("AWS_DEFAULT_REGION").ok());

        if let Some(region) = region_opt {
            if let Ok(rv) = region.parse() {
                Ok(rv)
            } else {
                Err(ErrorKind::BadConfigKey(
                    "aws.region", "An unknown AWS region was provided").into())
            }
        } else {
            Ok(Region::UsEast1)
        }
    }

    /// Overrides the AWS region
    pub fn set_aws_region(&mut self, value: Region) {
        self.aws.region = Some(value.to_string());
    }

    /// Return the path where symbols are stored.
    pub fn get_symbol_dir<'a>(&'a self) -> Result<Cow<'a, Path>> {
        if let Some(ref path) = self.symbol_dir {
            Ok(Cow::Borrowed(path.as_path()))
        } else if let Ok(dir) = env::var("SYMBOLSERVER_SYMBOL_DIR") {
            Ok(Cow::Owned(PathBuf::from(dir)))
        } else {
            Err(ErrorKind::MissingConfigKey("symbol_dir").into())
        }
    }

    /// Override the symbol dir.
    pub fn set_symbol_dir<P: AsRef<Path>>(&mut self, value: P) {
        self.symbol_dir = Some(value.as_ref().to_path_buf());
    }

    /// Return the bind target for the http server
    pub fn get_server_socket_addr(&self) -> Result<(&str, u16)> {
        let host = self.server.host.as_ref().map(|x| x.as_str()).unwrap_or("127.0.0.1");
        let port = self.server.port.unwrap_or(3000);
        Ok((host, port))
    }

    /// Return the server healthcheck ttl
    pub fn get_server_healthcheck_ttl(&self) -> Result<Duration> {
        let ttl = self.server.healthcheck_ttl.unwrap_or(60);
        Ok(Duration::seconds(ttl as i64))
    }

    /// Return the server sync interval
    pub fn get_server_sync_interval(&self) -> Result<Duration> {
        let ttl = self.server.sync_interval.unwrap_or(60);
        if ttl <= 0 {
            Err(ErrorKind::BadConfigKey(
                "server.sync_interval", "Sync interval has to be positive").into())
        } else {
            Ok(Duration::seconds(ttl as i64))
        }
    }

    /// Return the number of threads to listen on
    pub fn get_server_threads(&self) -> Result<usize> {
        Ok(self.server.threads.unwrap_or_else(|| {
            num_cpus::get() * 5 / 4
        }))
    }

    /// Return the log level filter
    pub fn get_log_level_filter(&self) -> Result<LogLevelFilter> {
        if let Some(ref lvl) = self.log.level {
            lvl.parse().map_err(|_| ErrorKind::BadConfigKey(
                "log.level", "unknown log level").into())
        } else {
            Ok(LogLevelFilter::Info)
        }
    }

    /// Override the log level filter in the config
    pub fn set_log_level_filter(&mut self, value: LogLevelFilter) {
        self.log.level = Some(value.to_string());
    }

    /// Return the log filename
    pub fn get_log_filename(&self) -> Result<Option<&Path>> {
        if let Some(ref path) = self.log.file {
            Ok(Some(&*path))
        } else {
            Ok(None)
        }
    }
}
<|endoftext|>


<commit_before>use std::io::prelude::*;
use std::fs::File;
use std::str::FromStr;
use docopt::Docopt;
use toml;

use gobjects;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WorkMode {
    Normal,     //generate widgets etc.
    Sys,        //generate -sys with ffi
}

impl Default for WorkMode {
    fn default() -> WorkMode { WorkMode::Normal }
}

impl FromStr for WorkMode {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "normal" => Ok(WorkMode::Normal),
            "sys" => Ok(WorkMode::Sys),
            _ => Err("Wrong work mode".into())
        }
    }
}

static USAGE: &'static str = "
Usage: gir [options] [<library> <version>]
       gir --help

Options:
    -h, --help          Show this message.
    -d GIRSPATH         Directory for girs
    -m MODE             Work mode: normal or sys
    -o PATH             Target root path
";

#[derive(Debug)]
pub struct Config {
    pub work_mode: WorkMode,
    pub girs_dir: String,
    pub library_name: String,
    pub library_version: String,
    pub target_path: String,
    pub objects: gobjects::GObjects,
}

impl Config {
    pub fn new() -> Config {
        let args = Docopt::new(USAGE)
            .and_then(|dopt| dopt.parse())
            .unwrap_or_else(|e| e.exit());

        let toml = read_toml("Gir.toml");

        let work_mode_str = match args.get_str("-m") {
            "" => toml.lookup("options.work_mode")
                    .expect("No options.work_mode in config")
                    .as_str().unwrap(),
            a => a,
        };
        let work_mode = WorkMode::from_str(work_mode_str)
            .unwrap_or_else(|e| panic!(e));

        let girs_dir = match args.get_str("-d") {
            "" => toml.lookup("options.girs_dir")
                      .expect("No options.girs_dir in config")
                      .as_str().unwrap(),
            a => a
        };

        let (library_name, library_version) =
            match (args.get_str("<library>"), args.get_str("<version>")) {
            ("", "") => (
                toml.lookup("options.library")
                    .expect("No options.library in config")
                    .as_str().unwrap(),
                toml.lookup("options.version")
                    .expect("No options.version in config")
                    .as_str().unwrap()
            ),
            ("", _) | (_, "") => panic!("Library and version can not be specified separately"),
            (a, b) => (a, b)
        };

        let target_path = match args.get_str("-o") {
            "" => toml.lookup("options.target_path")
                    .expect("No options.target_path in config")
                    .as_str().unwrap(),
            a => a
        };

        let objects = gobjects::parse_toml(toml.lookup("object").unwrap());

        Config {
            work_mode: work_mode,
            girs_dir: girs_dir.into(),
            library_name: library_name.into(),
            library_version: library_version.into(),
            target_path: target_path.into(),
            objects: objects,
        }
    }

    pub fn library_full_name(&self) -> String {
        format!("{}-{}", self.library_name, self.library_version)
    }
}

fn read_toml(filename: &str) -> toml::Value {
    let mut input = String::new();
    File::open(filename).and_then(|mut f| {
        f.read_to_string(&mut input)
    }).unwrap();
    let mut parser = toml::Parser::new(&input);
    match parser.parse() {
        Some(toml) => toml::Value::Table(toml),
        None => {
            for err in &parser.errors {
                let (loline, locol) = parser.to_linecol(err.lo);
                let (hiline, hicol) = parser.to_linecol(err.hi);
                println!("{}:{}:{}-{}:{} error: {}",
                         filename, loline, locol, hiline, hicol, err.desc);
            }
            panic!("Errors in config")
        }
    }
}
<commit_msg>Add config path option<commit_after>use std::io::prelude::*;
use std::fs::File;
use std::str::FromStr;
use docopt::Docopt;
use toml;

use gobjects;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WorkMode {
    Normal,     //generate widgets etc.
    Sys,        //generate -sys with ffi
}

impl Default for WorkMode {
    fn default() -> WorkMode { WorkMode::Normal }
}

impl FromStr for WorkMode {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "normal" => Ok(WorkMode::Normal),
            "sys" => Ok(WorkMode::Sys),
            _ => Err("Wrong work mode".into())
        }
    }
}

static USAGE: &'static str = "
Usage: gir [options] [<library> <version>]
       gir --help

Options:
    -h, --help          Show this message.
    -c CONFIG           Config file path (default: Gir.toml)
    -d GIRSPATH         Directory for girs
    -m MODE             Work mode: normal or sys
    -o PATH             Target root path
";

#[derive(Debug)]
pub struct Config {
    pub work_mode: WorkMode,
    pub girs_dir: String,
    pub library_name: String,
    pub library_version: String,
    pub target_path: String,
    pub objects: gobjects::GObjects,
}

impl Config {
    pub fn new() -> Config {
        let args = Docopt::new(USAGE)
            .and_then(|dopt| dopt.parse())
            .unwrap_or_else(|e| e.exit());

        let config_file = match args.get_str("-c") {
            "" => "Gir.toml",
            a => a,
        };

        //TODO: add check file existence when stable std::fs::PathExt
        let toml = read_toml(config_file);

        let work_mode_str = match args.get_str("-m") {
            "" => toml.lookup("options.work_mode")
                    .expect("No options.work_mode in config")
                    .as_str().unwrap(),
            a => a,
        };
        let work_mode = WorkMode::from_str(work_mode_str)
            .unwrap_or_else(|e| panic!(e));

        let girs_dir = match args.get_str("-d") {
            "" => toml.lookup("options.girs_dir")
                      .expect("No options.girs_dir in config")
                      .as_str().unwrap(),
            a => a
        };

        let (library_name, library_version) =
            match (args.get_str("<library>"), args.get_str("<version>")) {
            ("", "") => (
                toml.lookup("options.library")
                    .expect("No options.library in config")
                    .as_str().unwrap(),
                toml.lookup("options.version")
                    .expect("No options.version in config")
                    .as_str().unwrap()
            ),
            ("", _) | (_, "") => panic!("Library and version can not be specified separately"),
            (a, b) => (a, b)
        };

        let target_path = match args.get_str("-o") {
            "" => toml.lookup("options.target_path")
                    .expect("No options.target_path in config")
                    .as_str().unwrap(),
            a => a
        };

        let objects = gobjects::parse_toml(toml.lookup("object").unwrap());

        Config {
            work_mode: work_mode,
            girs_dir: girs_dir.into(),
            library_name: library_name.into(),
            library_version: library_version.into(),
            target_path: target_path.into(),
            objects: objects,
        }
    }

    pub fn library_full_name(&self) -> String {
        format!("{}-{}", self.library_name, self.library_version)
    }
}

fn read_toml(filename: &str) -> toml::Value {
    let mut input = String::new();
    File::open(filename).and_then(|mut f| {
        f.read_to_string(&mut input)
    }).unwrap();
    let mut parser = toml::Parser::new(&input);
    match parser.parse() {
        Some(toml) => toml::Value::Table(toml),
        None => {
            for err in &parser.errors {
                let (loline, locol) = parser.to_linecol(err.lo);
                let (hiline, hicol) = parser.to_linecol(err.hi);
                println!("{}:{}:{}-{}:{} error: {}",
                         filename, loline, locol, hiline, hicol, err.desc);
            }
            panic!("Errors in config")
        }
    }
}
<|endoftext|>


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

use jsapi::{JSCLASS_GLOBAL_APPLICATION_SLOTS, JSCLASS_RESERVED_SLOTS_SHIFT};
use jsapi::{JSCLASS_RESERVED_SLOTS_WIDTH, JSProtoKey};
use libc::c_uint;

pub const default_heapsize: u32 = 32_u32 * 1024_u32 * 1024_u32;

pub const JSCLASS_IS_DOMJSCLASS: c_uint = 1 << 4;
pub const JSCLASS_USERBIT1: c_uint = 1 << 7;

pub const JSCLASS_RESERVED_SLOTS_MASK: c_uint =
    (1 << JSCLASS_RESERVED_SLOTS_WIDTH) - 1;

pub const JSCLASS_HIGH_FLAGS_SHIFT: c_uint =
    (JSCLASS_RESERVED_SLOTS_SHIFT as c_uint) + JSCLASS_RESERVED_SLOTS_WIDTH;

pub const JSCLASS_IS_GLOBAL: c_uint =
    1 << (JSCLASS_HIGH_FLAGS_SHIFT + 1);

pub const JSCLASS_IS_PROXY: c_uint =
    1 << (JSCLASS_HIGH_FLAGS_SHIFT + 4);

pub const JSCLASS_GLOBAL_SLOT_COUNT: c_uint =
    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProtoKey::JSProto_LIMIT as c_uint * 3 + 36;
<commit_msg>Auto merge of #479 - romankl:r-change-slot-count, r=jdm<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use jsapi::{JSCLASS_GLOBAL_APPLICATION_SLOTS, JSCLASS_RESERVED_SLOTS_SHIFT};
use jsapi::{JSCLASS_RESERVED_SLOTS_WIDTH, JSProtoKey};
use libc::c_uint;

pub const default_heapsize: u32 = 32_u32 * 1024_u32 * 1024_u32;

pub const JSCLASS_IS_DOMJSCLASS: c_uint = 1 << 4;
pub const JSCLASS_USERBIT1: c_uint = 1 << 7;

pub const JSCLASS_RESERVED_SLOTS_MASK: c_uint =
    (1 << JSCLASS_RESERVED_SLOTS_WIDTH) - 1;

pub const JSCLASS_HIGH_FLAGS_SHIFT: c_uint =
    (JSCLASS_RESERVED_SLOTS_SHIFT as c_uint) + JSCLASS_RESERVED_SLOTS_WIDTH;

pub const JSCLASS_IS_GLOBAL: c_uint =
    1 << (JSCLASS_HIGH_FLAGS_SHIFT + 1);

pub const JSCLASS_IS_PROXY: c_uint =
    1 << (JSCLASS_HIGH_FLAGS_SHIFT + 4);

pub const JSCLASS_GLOBAL_SLOT_COUNT: c_uint =
    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProtoKey::JSProto_LIMIT as c_uint * 2 + 38;
<|endoftext|>


<commit_before>use apm::{CommandStarted, CommandResult, EventRunner};
use bson::{self, Bson};
use time;
use command_type::CommandType;
use {Client, Error, ErrorCode, Result, ThreadedClient};
use wire_protocol::flags::OpQueryFlags;
use wire_protocol::operations::Message;
use std::collections::vec_deque::VecDeque;
use std::io::{Read, Write};

pub const DEFAULT_BATCH_SIZE: i32 = 20;

/// Maintains a connection to the server and lazily returns documents from a
/// query.
///
/// # Fields
///
/// `client` - The client to read from.
/// `namespace` - The namespace to read and write from.
/// `batch_size` - How many documents to fetch at a given time from the server.
/// `cursor_id` - Uniquely identifies the cursor being returned by the reply.
/// `limit` - An upper bound on the total number of documents this cursor
///           should return.
/// `count` - How many documents have been returned so far.
/// `buffer` - A cache for documents received from the query that have not
///            yet been returned.
pub struct Cursor {
    client: Client,
    namespace: String,
    batch_size: i32,
    cursor_id: i64,
    limit: i32,
    count: i32,
    buffer: VecDeque<bson::Document>,
}

macro_rules! try_or_emit {
    ($cmd_name:expr, $req_id:expr, $connstring:expr, $result:expr, $client:expr) => {
        match $result {
            Ok(val) => val,
            Err(e) => {
                let hook_result = $client.run_completion_hooks(&CommandResult::Failure {
                    duration: 0,
                    command_name: $cmd_name.to_owned(),
                    failure: &e,
                    request_id: $req_id as i64,
                    connection_string: $connstring,
                });

                return match hook_result {
                    Ok(_) => Err(e),
                    Err(_) => Err(Error::EventListenerError(Some(Box::new(e))))
                }
            }
        }
    };
}

impl Cursor {
    /// Construcs a new Cursor for a database command.
    ///
    /// # Arguments
    ///
    /// `client` - Client making the request.
    /// `db` - Which database the command is being sent to.
    /// `doc` - Specifies the command that is being run.
    ///
    /// # Return value
    ///
    /// Returns the newly created Cursor on success, or an Error on failure.
    pub fn command_cursor(client: Client, db: &str,
                          doc: bson::Document, cmd_type: CommandType) -> Result<Cursor> {
        Cursor::query(client.clone(), format!("{}.$cmd", db), 1, OpQueryFlags::no_flags(), 0, 0,
                      doc, None, cmd_type, true)
    }

    fn get_bson_and_cid_from_message(message: Message) -> Result<(bson::Document, VecDeque<bson::Document>, i64)> {
        match message {
            Message::OpReply { header: _, flags: _, cursor_id: cid,
                               starting_from: _, number_returned: _,
                               documents: docs } => {
                let mut v = VecDeque::new();
                let mut out_doc = doc! {};

                if !docs.is_empty() {
                    out_doc = docs[0].clone();
                    if let Some(&Bson::I32(ref code)) = docs[0].get("code") {
                        // If command doesn't exist or namespace not found, return
                        // an empty array instead of throwing an error.
                        if *code == ErrorCode::CommandNotFound as i32 ||
                           *code == ErrorCode::NamespaceNotFound as i32 {
                            return Ok((docs[0].clone(), v, cid));
                        } else if let Some(&Bson::String(ref msg)) = docs[0].get("errmsg") {
                            return Err(Error::OperationError(msg.to_owned()));
                        }
                    }
                }

                for doc in docs {
                    v.push_back(doc.clone());
                }

                Ok((out_doc, v, cid))
            },
            _ => Err(Error::CursorNotFoundError)
        }
    }

    fn get_bson_and_cursor_info_from_command_message(message: Message) ->
           Result<(bson::Document, VecDeque<bson::Document>, i64, String)> {
        let (first, v, _) = try!(Cursor::get_bson_and_cid_from_message(message));
        if v.len() != 1 {
            return Err(Error::CursorNotFoundError);
        }

        let ref doc = v[0];

        // Extract cursor information
        if let Some(&Bson::Document(ref cursor)) = doc.get("cursor") {
            if let Some(&Bson::I64(ref id)) = cursor.get("id") {
                if let Some(&Bson::String(ref ns)) = cursor.get("ns") {
                    if let Some(&Bson::Array(ref batch)) = cursor.get("firstBatch") {

                        // Extract first batch documents
                        let map = batch.iter().filter_map(|bdoc| {
                            if let &Bson::Document(ref doc) = bdoc {
                                Some(doc.clone())
                            } else {
                                None
                            }
                        }).collect();

                        return Ok((first, map, *id, ns.to_owned()))
                    }
                }
            }
        }

        Err(Error::CursorNotFoundError)
    }

    /// Executes a query where the batch size of the returned cursor is
    /// specified.
    ///
    /// # Arguments
    ///
    /// `client` - The client to read from.
    /// `namespace` - The namespace to read and write from.
    /// `batch_size` - How many documents the cursor should return at a time.
    /// `flags` - Bit vector of query options.
    /// `number_to_skip` - The number of initial documents to skip over in the
    ///                    query results.
    /// `number_to_return - The total number of documents that should be
    ///                     returned by the query.
    /// `return_field_selector - An optional projection of which fields should
    ///                          be present in the documents to be returned by
    ///                          the query.
    /// `is_cmd_cursor` - Whether or not the Cursor is for a database command.
    ///
    /// # Return value
    ///
    /// Returns the cursor for the query results on success, or an Error on
    /// failure.
    pub fn query(client: Client, namespace: String, batch_size: i32, flags: OpQueryFlags,
                 number_to_skip: i32, number_to_return: i32, query: bson::Document,
                 return_field_selector: Option<bson::Document>, cmd_type: CommandType,
                 is_cmd_cursor: bool) -> Result<Cursor> {
        let req_id = client.get_req_id();

        let stream = try!(client.acquire_stream());
        let mut socket = stream.get_socket();

        let index = namespace.rfind(".").unwrap_or(namespace.len());
        let db_name = namespace[..index].to_owned();
        let coll_name = namespace[index + 1..].to_owned();
        let cmd_name = cmd_type.to_str();
        let connstring = format!("{}", try!(socket.peer_addr()));

        let filter : bson::Document = match query.get("$query") {
            Some(&Bson::Document(ref doc)) => doc.clone(),
            _ => doc! {}
        };

        let projection = match return_field_selector {
            Some(ref doc) => doc.clone(),
            None => doc! {}
        };

        let sort = match query.get("$orderby") {
            Some(&Bson::Document(ref doc)) => doc.clone(),
            _ => doc! {}
        };

        let command = match cmd_type {
            CommandType::Find => doc! {
                "find" => coll_name,
                "filter" => filter,
                "projection" => projection,
                "skip" => number_to_skip,
                "limit" => number_to_return,
                "batchSize" => batch_size,
                "sort" => sort
            },
            _ => query.clone()
        };

        let init_time = time::precise_time_ns();
        let result = Message::new_query(req_id, flags,
                                        namespace.to_owned(),
                                        number_to_skip, batch_size,
                                        query.clone(), return_field_selector);

        let message = try!(result);

        let hook_result = client.run_start_hooks(&CommandStarted {
            command: command,
            database_name: db_name,
            command_name: cmd_name.to_owned(),
            request_id: req_id as i64,
            connection_string: connstring.clone(),
        });

        match hook_result {
            Ok(_) => (),
            Err(_) => return Err(Error::EventListenerError(None))
        };

        try_or_emit!(cmd_name, req_id, connstring, message.write(&mut socket), client);
        let reply = try_or_emit!(cmd_name, req_id, connstring, Message::read(&mut socket),
                                 client);

        let fin_time = time::precise_time_ns();

        let (doc, buf, cursor_id, namespace) = if is_cmd_cursor {
            try_or_emit!(cmd_name, req_id, connstring,
                         Cursor::get_bson_and_cursor_info_from_command_message(reply), client)
        } else {
            let (doc, buf, id) = try_or_emit!(cmd_name, req_id, connstring,
                                            Cursor::get_bson_and_cid_from_message(reply),
                                            client);
            (doc, buf, id, namespace)
        };

        let _hook_result = client.run_completion_hooks(&CommandResult::Success {
            duration: fin_time - init_time,
            reply: doc,
            command_name: cmd_name.to_owned(),
            request_id: req_id as i64,
            connection_string: connstring,
        });

        Ok(Cursor { client: client, namespace: namespace,
                    batch_size: batch_size, cursor_id: cursor_id,
                    limit: number_to_return, count: 0, buffer: buf, })
    }

    fn get_from_stream(&mut self) -> Result<()> {
        let stream = try!(self.client.acquire_stream());
        let mut socket = stream.get_socket();

        let req_id = self.client.get_req_id();

        let get_more = Message::new_get_more(req_id, self.namespace.to_owned(), self.batch_size,
                              self.cursor_id);

        let index = self.namespace.rfind(".").unwrap_or(self.namespace.len());
        let db_name = self.namespace[..index].to_owned();
        let cmd_name = "get_more".to_owned();
        let connstring = format!("{}", try!(socket.peer_addr()));

        let hook_result = self.client.run_start_hooks(&CommandStarted {
            command: doc! { "cursor_id" => (self.cursor_id) },
            database_name: db_name,
            command_name: cmd_name.clone(),
            request_id: req_id as i64,
            connection_string: connstring.clone(),
        });

        match hook_result {
            Ok(_) => (),
            Err(_) => return Err(Error::EventListenerError(None))
        };

        try_or_emit!(cmd_name, req_id, connstring, get_more.write(&mut socket), self.client);
        let reply = try!(Message::read(&mut socket));

        let (_, v, _) = try!(Cursor::get_bson_and_cid_from_message(reply));
        self.buffer.extend(v);
        Ok(())
    }

    /// Attempts to read a specified number of BSON documents from the cursor.
    ///
    /// # Arguments
    ///
    /// `n` - The number of documents to read.
    ///
    /// # Return value
    ///
    /// Returns a vector containing the BSON documents that were read.
    pub fn next_n(&mut self, n: i32) -> Result<Vec<bson::Document>> {
        let mut vec = vec![];

        for _ in 0..n {
            let bson_option = self.next();

            match bson_option {
                Some(Ok(bson)) => vec.push(bson),
                Some(Err(err)) => return Err(err),
                None => break,
            };
        }

        Ok(vec)
    }

    /// Attempts to read a batch of BSON documents from the cursor.
    ///
    /// # Return value
    ///
    /// Returns a vector containing the BSON documents that were read.
    pub fn next_batch(&mut self) -> Result<Vec<bson::Document>> {
        let n = self.batch_size;
        self.next_n(n)
    }

    /// Checks whether there are any more documents for the cursor to return.
    ///
    /// # Return value
    ///
    /// Returns `true` if the cursor is not yet exhausted, or `false` if it is.
    pub fn has_next(&mut self) -> Result<bool> {
        if self.limit > 0 && self.count >= self.limit {
            Ok(false)
        } else {
            if self.buffer.is_empty() && self.limit != 1 && self.cursor_id != 0 {
                try!(self.get_from_stream());
            }
            Ok(!self.buffer.is_empty())
        }
    }
}

impl Iterator for Cursor {
    type Item = Result<bson::Document>;

    /// Attempts to read a BSON document from the cursor.
    ///
    /// # Return value
    ///
    /// Returns a BSON document if there is another one to return; `None` if
    /// there are no more documents to return; or an Error if the request for
    /// another document fails.
    fn next(&mut self) -> Option<Result<bson::Document>> {
        match self.has_next() {
            Ok(true) => {
                self.count += 1;
                match self.buffer.pop_front() {
                    Some(bson) => Some(Ok(bson)),
                    None => None,
                }
            },
            Ok(false) => None,
            Err(err) => Some(Err(err)),
        }
    }
}
<commit_msg>completed find commands log correctly now<commit_after>use apm::{CommandStarted, CommandResult, EventRunner};
use bson::{self, Bson};
use time;
use command_type::CommandType;
use {Client, Error, ErrorCode, Result, ThreadedClient};
use wire_protocol::flags::OpQueryFlags;
use wire_protocol::operations::Message;
use std::collections::vec_deque::VecDeque;
use std::io::{Read, Write};

pub const DEFAULT_BATCH_SIZE: i32 = 20;

/// Maintains a connection to the server and lazily returns documents from a
/// query.
///
/// # Fields
///
/// `client` - The client to read from.
/// `namespace` - The namespace to read and write from.
/// `batch_size` - How many documents to fetch at a given time from the server.
/// `cursor_id` - Uniquely identifies the cursor being returned by the reply.
/// `limit` - An upper bound on the total number of documents this cursor
///           should return.
/// `count` - How many documents have been returned so far.
/// `buffer` - A cache for documents received from the query that have not
///            yet been returned.
pub struct Cursor {
    client: Client,
    namespace: String,
    batch_size: i32,
    cursor_id: i64,
    limit: i32,
    count: i32,
    buffer: VecDeque<bson::Document>,
}

macro_rules! try_or_emit {
    ($cmd_name:expr, $req_id:expr, $connstring:expr, $result:expr, $client:expr) => {
        match $result {
            Ok(val) => val,
            Err(e) => {
                let hook_result = $client.run_completion_hooks(&CommandResult::Failure {
                    duration: 0,
                    command_name: $cmd_name.to_owned(),
                    failure: &e,
                    request_id: $req_id as i64,
                    connection_string: $connstring,
                });

                return match hook_result {
                    Ok(_) => Err(e),
                    Err(_) => Err(Error::EventListenerError(Some(Box::new(e))))
                }
            }
        }
    };
}

impl Cursor {
    /// Construcs a new Cursor for a database command.
    ///
    /// # Arguments
    ///
    /// `client` - Client making the request.
    /// `db` - Which database the command is being sent to.
    /// `doc` - Specifies the command that is being run.
    ///
    /// # Return value
    ///
    /// Returns the newly created Cursor on success, or an Error on failure.
    pub fn command_cursor(client: Client, db: &str,
                          doc: bson::Document, cmd_type: CommandType) -> Result<Cursor> {
        Cursor::query(client.clone(), format!("{}.$cmd", db), 1, OpQueryFlags::no_flags(), 0, 0,
                      doc, None, cmd_type, true)
    }

    fn get_bson_and_cid_from_message(message: Message) -> Result<(bson::Document, VecDeque<bson::Document>, i64)> {
        match message {
            Message::OpReply { header: _, flags: _, cursor_id: cid,
                               starting_from: _, number_returned: _,
                               documents: docs } => {
                let mut v = VecDeque::new();
                let mut out_doc = doc! {};

                if !docs.is_empty() {
                    out_doc = docs[0].clone();
                    if let Some(&Bson::I32(ref code)) = docs[0].get("code") {
                        // If command doesn't exist or namespace not found, return
                        // an empty array instead of throwing an error.
                        if *code == ErrorCode::CommandNotFound as i32 ||
                           *code == ErrorCode::NamespaceNotFound as i32 {
                            return Ok((docs[0].clone(), v, cid));
                        } else if let Some(&Bson::String(ref msg)) = docs[0].get("errmsg") {
                            return Err(Error::OperationError(msg.to_owned()));
                        }
                    }
                }

                for doc in docs {
                    v.push_back(doc.clone());
                }

                Ok((out_doc, v, cid))
            },
            _ => Err(Error::CursorNotFoundError)
        }
    }

    fn get_bson_and_cursor_info_from_command_message(message: Message) ->
           Result<(bson::Document, VecDeque<bson::Document>, i64, String)> {
        let (first, v, _) = try!(Cursor::get_bson_and_cid_from_message(message));
        if v.len() != 1 {
            return Err(Error::CursorNotFoundError);
        }

        let ref doc = v[0];

        // Extract cursor information
        if let Some(&Bson::Document(ref cursor)) = doc.get("cursor") {
            if let Some(&Bson::I64(ref id)) = cursor.get("id") {
                if let Some(&Bson::String(ref ns)) = cursor.get("ns") {
                    if let Some(&Bson::Array(ref batch)) = cursor.get("firstBatch") {

                        // Extract first batch documents
                        let map = batch.iter().filter_map(|bdoc| {
                            if let &Bson::Document(ref doc) = bdoc {
                                Some(doc.clone())
                            } else {
                                None
                            }
                        }).collect();

                        return Ok((first, map, *id, ns.to_owned()))
                    }
                }
            }
        }

        Err(Error::CursorNotFoundError)
    }

    /// Executes a query where the batch size of the returned cursor is
    /// specified.
    ///
    /// # Arguments
    ///
    /// `client` - The client to read from.
    /// `namespace` - The namespace to read and write from.
    /// `batch_size` - How many documents the cursor should return at a time.
    /// `flags` - Bit vector of query options.
    /// `number_to_skip` - The number of initial documents to skip over in the
    ///                    query results.
    /// `number_to_return - The total number of documents that should be
    ///                     returned by the query.
    /// `return_field_selector - An optional projection of which fields should
    ///                          be present in the documents to be returned by
    ///                          the query.
    /// `is_cmd_cursor` - Whether or not the Cursor is for a database command.
    ///
    /// # Return value
    ///
    /// Returns the cursor for the query results on success, or an Error on
    /// failure.
    pub fn query(client: Client, namespace: String, batch_size: i32, flags: OpQueryFlags,
                 number_to_skip: i32, number_to_return: i32, query: bson::Document,
                 return_field_selector: Option<bson::Document>, cmd_type: CommandType,
                 is_cmd_cursor: bool) -> Result<Cursor> {
        let req_id = client.get_req_id();

        let stream = try!(client.acquire_stream());
        let mut socket = stream.get_socket();

        let index = namespace.rfind(".").unwrap_or(namespace.len());
        let db_name = namespace[..index].to_owned();
        let coll_name = namespace[index + 1..].to_owned();
        let cmd_name = cmd_type.to_str();
        let connstring = format!("{}", try!(socket.peer_addr()));

        let filter : bson::Document = match query.get("$query") {
            Some(&Bson::Document(ref doc)) => doc.clone(),
            _ => doc! {}
        };

        let projection = match return_field_selector {
            Some(ref doc) => doc.clone(),
            None => doc! {}
        };

        let sort = match query.get("$orderby") {
            Some(&Bson::Document(ref doc)) => doc.clone(),
            _ => doc! {}
        };

        let command = match cmd_type {
            CommandType::Find => doc! {
                "find" => coll_name,
                "filter" => filter,
                "projection" => projection,
                "skip" => number_to_skip,
                "limit" => number_to_return,
                "batchSize" => batch_size,
                "sort" => sort
            },
            _ => query.clone()
        };

        let init_time = time::precise_time_ns();
        let result = Message::new_query(req_id, flags,
                                        namespace.to_owned(),
                                        number_to_skip, batch_size,
                                        query.clone(), return_field_selector);

        let message = try!(result);

        let hook_result = client.run_start_hooks(&CommandStarted {
            command: command,
            database_name: db_name,
            command_name: cmd_name.to_owned(),
            request_id: req_id as i64,
            connection_string: connstring.clone(),
        });

        match hook_result {
            Ok(_) => (),
            Err(_) => return Err(Error::EventListenerError(None))
        };

        try_or_emit!(cmd_name, req_id, connstring, message.write(&mut socket), client);
        let reply = try_or_emit!(cmd_name, req_id, connstring, Message::read(&mut socket),
                                 client);

        let fin_time = time::precise_time_ns();

        let (doc, buf, cursor_id, namespace) = if is_cmd_cursor {
            try_or_emit!(cmd_name, req_id, connstring,
                         Cursor::get_bson_and_cursor_info_from_command_message(reply), client)
        } else {
            let (doc, buf, id) = try_or_emit!(cmd_name, req_id, connstring,
                                            Cursor::get_bson_and_cid_from_message(reply),
                                            client);
            (doc, buf, id, namespace)
        };

        let vec : Vec<_> = buf.iter().map(|doc| Bson::Document(doc.clone())).collect();

        let reply = match cmd_type {
            CommandType::Find => doc! {
                "cursor" => {
                    "id" => cursor_id,
                    "ns" => (&namespace[..]),
                    "firstBatch" => (Bson::Array(vec))
                },
                "ok" => 1
            },
            _ => doc
        };

        let _hook_result = client.run_completion_hooks(&CommandResult::Success {
            duration: fin_time - init_time,
            reply: reply,
            command_name: cmd_name.to_owned(),
            request_id: req_id as i64,
            connection_string: connstring,
        });

        Ok(Cursor { client: client, namespace: namespace,
                    batch_size: batch_size, cursor_id: cursor_id,
                    limit: number_to_return, count: 0, buffer: buf, })
    }

    fn get_from_stream(&mut self) -> Result<()> {
        let stream = try!(self.client.acquire_stream());
        let mut socket = stream.get_socket();

        let req_id = self.client.get_req_id();

        let get_more = Message::new_get_more(req_id, self.namespace.to_owned(), self.batch_size,
                              self.cursor_id);

        let index = self.namespace.rfind(".").unwrap_or(self.namespace.len());
        let db_name = self.namespace[..index].to_owned();
        let cmd_name = "get_more".to_owned();
        let connstring = format!("{}", try!(socket.peer_addr()));

        let hook_result = self.client.run_start_hooks(&CommandStarted {
            command: doc! { "cursor_id" => (self.cursor_id) },
            database_name: db_name,
            command_name: cmd_name.clone(),
            request_id: req_id as i64,
            connection_string: connstring.clone(),
        });

        match hook_result {
            Ok(_) => (),
            Err(_) => return Err(Error::EventListenerError(None))
        };

        try_or_emit!(cmd_name, req_id, connstring, get_more.write(&mut socket), self.client);
        let reply = try!(Message::read(&mut socket));

        let (_, v, _) = try!(Cursor::get_bson_and_cid_from_message(reply));
        self.buffer.extend(v);
        Ok(())
    }

    /// Attempts to read a specified number of BSON documents from the cursor.
    ///
    /// # Arguments
    ///
    /// `n` - The number of documents to read.
    ///
    /// # Return value
    ///
    /// Returns a vector containing the BSON documents that were read.
    pub fn next_n(&mut self, n: i32) -> Result<Vec<bson::Document>> {
        let mut vec = vec![];

        for _ in 0..n {
            let bson_option = self.next();

            match bson_option {
                Some(Ok(bson)) => vec.push(bson),
                Some(Err(err)) => return Err(err),
                None => break,
            };
        }

        Ok(vec)
    }

    /// Attempts to read a batch of BSON documents from the cursor.
    ///
    /// # Return value
    ///
    /// Returns a vector containing the BSON documents that were read.
    pub fn next_batch(&mut self) -> Result<Vec<bson::Document>> {
        let n = self.batch_size;
        self.next_n(n)
    }

    /// Checks whether there are any more documents for the cursor to return.
    ///
    /// # Return value
    ///
    /// Returns `true` if the cursor is not yet exhausted, or `false` if it is.
    pub fn has_next(&mut self) -> Result<bool> {
        if self.limit > 0 && self.count >= self.limit {
            Ok(false)
        } else {
            if self.buffer.is_empty() && self.limit != 1 && self.cursor_id != 0 {
                try!(self.get_from_stream());
            }
            Ok(!self.buffer.is_empty())
        }
    }
}

impl Iterator for Cursor {
    type Item = Result<bson::Document>;

    /// Attempts to read a BSON document from the cursor.
    ///
    /// # Return value
    ///
    /// Returns a BSON document if there is another one to return; `None` if
    /// there are no more documents to return; or an Error if the request for
    /// another document fails.
    fn next(&mut self) -> Option<Result<bson::Document>> {
        match self.has_next() {
            Ok(true) => {
                self.count += 1;
                match self.buffer.pop_front() {
                    Some(bson) => Some(Ok(bson)),
                    None => None,
                }
            },
            Ok(false) => None,
            Err(err) => Some(Err(err)),
        }
    }
}
<|endoftext|>