<commit_before>use std::ops::{Index, IndexMut};

/// A buffer structure
pub trait Buffer<'a> {
    type Line: 'a;
    type LineIter: Iterator<Item = &'a Self::Line>;

    /// Create a new empty split buffer
    fn new() -> Self;

    /// Convert a string to a split buffer
    fn from_str(s: &str) -> Self;

    /// Get the nth line in the buffer by option reference
    fn get_line(&self, n: usize) -> Option<&Self::Line>;

    /// Get the nth line in the buffer by optional mutable reference
    fn get_line_mut(&mut self, n: usize) -> Option<&mut Self::Line>;

    /// Remove the nth line and return it. Panics on out of bound.
    fn remove_line(&mut self, n: usize) -> Self::Line;

    /// Insert line at n. Panics on out of bound.
    fn insert_line(&mut self, n: usize, line: Self::Line);

    /// Convert a vector of lines to a split buffer
    fn from_lines(vec: &[Self::Line]) -> SplitBuffer;

    /// Give a hint on where the operations are most frequent (i.e. where the cursor is). X value.
    fn focus_hint_x(&mut self, x: usize);

    /// Give a hint on where the operations are most frequent (i.e. where the cursor is). Y value.
    fn focus_hint_y(&mut self, y: usize);

    /// Get the number of lines in the buffer
    fn len(&self) -> usize;

    /// Get an iterator over the lines in the buffer
    fn lines(&'a self) -> Self::LineIter;

    /// Get the leading whitespaces of the nth line. Used for autoindenting.
    fn get_indent(&self, n: usize) -> &str;
}

/// The buffer data structure, that Sodium is using.
///
/// This structure consists of two "subbuffers", which are just vectors over lines (defined by
/// Strings). The split is called a center.
///
/// The nearer a given operation is to the center, the better it performs.
///
/// The second buffer is in reverse order to get the particular efficiency we want.
pub struct SplitBuffer {
    before: Vec<String>,
    after: Vec<String>,
    #[cfg(debug)]
    _hinted_since_edit: bool,
}

impl SplitBuffer {
    fn cur_line(&self) -> &String {
        self.before.last().expect("Unexpected condition (the first part of the split buffer is empty)")
    }

    fn cur_line_mut(&mut self) -> &mut String {
        self.before.last_mut().expect("Unexpected condition (the first part of the split buffer is empty)")
    }

    fn up(&mut self) -> bool {
        if self.before.len() == 1 {
            false
        } else {
            self.after.push(self.before.pop().unwrap());
            true
        }
    }

    fn down(&mut self) -> bool {
        if self.after.len() == 0 {
            false
        } else {
            self.before.push(self.after.pop().unwrap());
            true
        }
    }

    fn y(&self) -> usize {
        self.before.len()
    }

    fn pop_line(&mut self) -> String {
        self.before.pop().expect("Unexpected condition (Popped the last line)")
    }

}

impl<'a> Buffer<'a> for SplitBuffer {
    type Line = String;
    type LineIter = SplitBufIter<'a>;

    /// Create a new empty split buffer
    fn new() -> Self {
        SplitBuffer {
            before: vec![String::new()],
            after: Vec::new(),
        }
    }

    /// Convert a string to a split buffer
    fn from_str(s: &str) -> Self {
        SplitBuffer {
            before: s.lines().map(str::to_string).collect(),
            after: Vec::new(),
        }
    }

    /// Get the nth line in the buffer by option reference
    fn get_line(&self, n: usize) -> Option<&String> {
        if n < self.before.len() {
            Some(&self.before[n])
        } else if n < self.len() {
            let n = self.len() + 1 - n;
            Some(&self.after[n])
        } else {
            None
        }
    }

    /// Get the nth line in the buffer by optional mutable reference
    fn get_line_mut(&mut self, n: usize) -> Option<&mut String> {
        if n < self.before.len() {
            Some(&mut self.before[n])
        } else if n < self.len() {
            let n = self.len() + 1 - n;
            Some(&mut self.after[n])
        } else {
            None
        }
    }

    /// Remove the nth line and return it. Panics on out of bound.
    fn remove_line(&mut self, n: usize) -> String {
        if n < self.before.len() {
            self.before.remove(n)
        } else if n < self.len() {
            let n = self.len() + 1 - n;
            self.after.remove(n)
        } else {
            panic!("Out of bound");
        }
    }

    /// Insert line at n. Panics on out of bound.
    fn insert_line(&mut self, n: usize, line: String) {
        if n < self.before.len() {
            self.before.insert(n, line);
        } else if n < self.len() {
            let n = self.len() + 1 - n;
            self.after.insert(n, line);
        } else {
            panic!("Out of bound");
        }
    }

    /// Convert a vector of lines to a split buffer
    fn from_lines(ln: &[String]) -> SplitBuffer {
        SplitBuffer {
            before: ln.to_owned(),
            after: Vec::new(),
        }
    }

    /// Move the center (i.e. efficient point/split) of the split buffer
    ///
    /// Panics on out of bound.
    fn focus_hint_y(&mut self, y: usize) {
        if y < self.y() {
            for _ in 0..self.y() - y {
                if !self.up() {
                    break;
                }
            }
        } else if y > self.y() {
            for _ in 0..y - self.y() {
                if !self.down() {
                    break;
                }
            }
        } else if y >= self.len() {
            panic!("Out of bound");
        }
    }

    fn focus_hint_x(&mut self, _: usize) {}

    /// Get the number of lines in the buffer
    fn len(&self) -> usize {
        self.before.len() + self.after.len()
    }

    /// Get an iterator over the lines in the buffer
    fn lines(&'a self) -> SplitBufIter<'a> {
        SplitBufIter {
            buffer: self,
            line: 0,
        }
    }

    /// Get the leading whitespaces of the nth line. Used for autoindenting.
    fn get_indent(&self, n: usize) -> &str {
        if let Some(ln) = self.get_line(n) {
            let mut len = 0;
            for c in ln.chars() {
                match c {
                    '\t' | ' ' => len += 1,
                    _          => break,
                }
            }
            &ln[..len]
        } else {
            ""
        }
    }
}

impl Index<usize> for SplitBuffer {
    type Output = String;

    fn index<'a>(&'a self, index: usize) -> &'a String {
        self.get_line(index).expect("Out of bound")
    }
}
impl IndexMut<usize> for SplitBuffer {

    fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut String {
        #[cfg(debug)]
        fn debug_check(b: &mut SplitBuffer) {
            if b._hinted_since_edit {
                b._hinted_since_edit = false;
            } else {
                panic!("No focus hint given since last edit!");
            }
        }

        #[cfg(not(debug))]
        fn debug_check(_: &mut SplitBuffer) {}

        debug_check(&mut *self);

        self.get_line_mut(index).expect("Out of bound")
    }
}

/// A iterator over the lines of a split buffer
pub struct SplitBufIter<'a> {
    buffer: &'a SplitBuffer,
    line: usize,
}

impl<'a> Iterator for SplitBufIter<'a> {
    type Item = &'a String;

    fn next(&mut self) -> Option<&'a String> {
        self.nth(1)
    }

    fn nth(&mut self, n: usize) -> Option<&'a String> {
        self.line += n;
        self.buffer.get_line(self.line)
    }

    fn count(self) -> usize {
        self.buffer.len()
    }
}

impl<'a> DoubleEndedIterator for SplitBufIter<'a> {
    fn next_back(&mut self) -> Option<&'a String> {
        if self.line == 0 {
            None
        } else {
            self.line -= 1;
            self.buffer.get_line(self.line)
        }
    }
}
<commit_msg>Aaaand it works!<commit_after>use std::ops::{Index, IndexMut};
use std::cmp::min;
use std::str::Chars;

pub trait Line<'a> {
    type Iter: Iterator<Item = char> + 'a;

    fn chars_iter(&'a self) -> Self::Iter;
}

impl<'a, T: AsRef<str>> Line<'a> for T {
    type Iter = Chars<'a>;

    fn chars_iter(&self) -> Chars {
        self.as_ref().chars()
    }
}

/// A buffer structure
pub trait Buffer<'a> {
    type Line: 'a + Line<'a>;
    type LineIter: Iterator<Item = &'a Self::Line>;

    /// Create a new empty split buffer
    fn new() -> Self;

    /// Convert a string to a split buffer
    fn from_str(s: &str) -> Self;

    /// Get the nth line in the buffer by option reference
    fn get_line(&self, n: usize) -> Option<&Self::Line>;

    /// Get the nth line in the buffer by optional mutable reference
    fn get_line_mut(&mut self, n: usize) -> Option<&mut Self::Line>;

    /// Remove the nth line and return it. Panics on out of bound.
    fn remove_line(&mut self, n: usize) -> Self::Line;

    /// Insert line at n. Panics on out of bound.
    fn insert_line(&mut self, n: usize, line: Self::Line);

    /// Convert a vector of lines to a split buffer
    fn from_lines(vec: &[Self::Line]) -> SplitBuffer;

    /// Give a hint on where the operations are most frequent (i.e. where the cursor is). X value.
    fn focus_hint_x(&mut self, x: usize);

    /// Give a hint on where the operations are most frequent (i.e. where the cursor is). Y value.
    fn focus_hint_y(&mut self, y: usize);

    /// Get the number of lines in the buffer
    fn len(&self) -> usize;

    /// Get an iterator over the lines in the buffer
    fn lines(&'a self) -> Self::LineIter;

    /// Get the leading whitespaces of the nth line. Used for autoindenting.
    fn get_indent(&self, n: usize) -> &str;
}

/// The buffer data structure, that Sodium is using.
///
/// This structure consists of two "subbuffers", which are just vectors over lines (defined by
/// Strings). The split is called a center.
///
/// The nearer a given operation is to the center, the better it performs.
///
/// The second buffer is in reverse order to get the particular efficiency we want.
pub struct SplitBuffer {
    before: Vec<String>,
    after: Vec<String>,
    #[cfg(debug)]
    _hinted_since_edit: bool,
}

impl SplitBuffer {
    fn cur_line(&self) -> &String {
        self.before.last().expect("Unexpected condition (the first part of the split buffer is empty)")
    }

    fn cur_line_mut(&mut self) -> &mut String {
        self.before.last_mut().expect("Unexpected condition (the first part of the split buffer is empty)")
    }

    fn up(&mut self) {
        self.after.push(self.before.pop().expect("Popped last element"));
    }

    fn down(&mut self) {
        self.before.push(self.after.pop().expect("Popped last element"));
    }

    fn y(&self) -> usize {
        self.before.len()
    }

    fn pop_line(&mut self) -> String {
        self.before.pop().expect("Unexpected condition (Popped the last line)")
    }

}

impl<'a> Buffer<'a> for SplitBuffer {
    type Line = String;
    type LineIter = SplitBufIter<'a>;

    /// Create a new empty split buffer
    fn new() -> Self {
        SplitBuffer {
            before: vec![String::new()],
            after: Vec::new(),
        }
    }

    /// Convert a string to a split buffer
    fn from_str(s: &str) -> Self {
        SplitBuffer {
            before: s.lines().map(str::to_string).collect(),
            after: Vec::new(),
        }
    }

    /// Get the nth line in the buffer by option reference
    fn get_line(&self, n: usize) -> Option<&String> {
        if n < self.before.len() {
            Some(&self.before[n])
        } else if n < self.len() {
            let n = self.len() - 1 - n;
            Some(&self.after[n])
        } else {
            None
        }
    }

    /// Get the nth line in the buffer by optional mutable reference
    fn get_line_mut(&mut self, n: usize) -> Option<&mut String> {
        if n < self.before.len() {
            Some(&mut self.before[n])
        } else if n < self.len() {
            let n = self.len() - 1 - n;
            Some(&mut self.after[n])
        } else {
            None
        }
    }

    /// Remove the nth line and return it. Panics on out of bound.
    fn remove_line(&mut self, n: usize) -> String {
        if n < self.before.len() {
            self.before.remove(n)
        } else if n < self.len() {
            let n = self.len() - 1 - n;
            self.after.remove(n)
        } else {
            panic!("Out of bound");
        }
    }

    /// Insert line at n. Panics on out of bound.
    fn insert_line(&mut self, n: usize, line: String) {
        if n < self.before.len() {
            self.before.insert(n, line);
        } else if n < self.len() {
            let n = self.len() - 1 - n;
            self.after.insert(n, line);
        } else {
            panic!("Out of bound");
        }
    }

    /// Convert a vector of lines to a split buffer
    fn from_lines(ln: &[String]) -> SplitBuffer {
        SplitBuffer {
            before: ln.to_owned(),
            after: Vec::new(),
        }
    }

    /// Move the center (i.e. efficient point/split) of the split buffer
    ///
    /// Panics on out of bound.
    fn focus_hint_y(&mut self, y: usize) {
        if y < self.y() {
            for _ in 0..min(self.y() - y, self.before.len()) {
                self.up();
            }
        } else if y > self.y() {
            for _ in 0..min(y - self.y(), self.after.len()) {
                self.down();
            }
        } else if y >= self.len() {
            panic!("Out of bound");
        }
    }

    fn focus_hint_x(&mut self, _: usize) {}

    /// Get the number of lines in the buffer
    fn len(&self) -> usize {
        self.before.len() + self.after.len()
    }

    /// Get an iterator over the lines in the buffer
    fn lines(&'a self) -> SplitBufIter<'a> {
        SplitBufIter {
            buffer: self,
            line: 0,
        }
    }

    /// Get the leading whitespaces of the nth line. Used for autoindenting.
    fn get_indent(&self, n: usize) -> &str {
        if let Some(ln) = self.get_line(n) {
            let mut len = 0;
            for c in ln.chars() {
                match c {
                    '\t' | ' ' => len += 1,
                    _          => break,
                }
            }
            &ln[..len]
        } else {
            ""
        }
    }
}

impl Index<usize> for SplitBuffer {
    type Output = String;

    fn index<'a>(&'a self, index: usize) -> &'a String {
        self.get_line(index).expect("Out of bound")
    }
}
impl IndexMut<usize> for SplitBuffer {

    fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut String {
        #[cfg(debug)]
        fn debug_check(b: &mut SplitBuffer) {
            if b._hinted_since_edit {
                b._hinted_since_edit = false;
            } else {
                panic!("No focus hint given since last edit!");
            }
        }

        #[cfg(not(debug))]
        fn debug_check(_: &mut SplitBuffer) {}

        debug_check(&mut *self);

        self.get_line_mut(index).expect("Out of bound")
    }
}

/// A iterator over the lines of a split buffer
pub struct SplitBufIter<'a> {
    buffer: &'a SplitBuffer,
    line: usize,
}

impl<'a> Iterator for SplitBufIter<'a> {
    type Item = &'a String;

    fn next(&mut self) -> Option<&'a String> {
        self.nth(1)
    }

    fn nth(&mut self, n: usize) -> Option<&'a String> {
        let res = self.buffer.get_line(self.line);
        self.line += n;

        res
    }

    fn count(self) -> usize {
        self.buffer.len()
    }
}

impl<'a> DoubleEndedIterator for SplitBufIter<'a> {
    fn next_back(&mut self) -> Option<&'a String> {
        if self.line == 0 {
            None
        } else {
            self.line -= 1;
            self.buffer.get_line(self.line)
        }
    }
}
<|endoftext|>


<commit_before>#[cfg(test)]
use mockito;

use error::CDCResult;
use request::rq_get;
use std::fmt;

#[cfg(not(test))]
const LIVE_URL: &'static str = "https://media-qa.services.pbs.org/api/v1";
#[cfg(not(test))]
const QA_URL: &'static str = "https://media-qa.services.pbs.org/api/v1";

#[cfg(test)]
const LIVE_URL: &'static str = mockito::SERVER_URL;
#[cfg(test)]
const QA_URL: &'static str = mockito::SERVER_URL;

pub struct Client<'a> {
    key: &'a str,
    secret: &'a str,
    base: &'a str,
}

pub type Params<'a> = Vec<(&'a str, &'a str)>;

pub enum Endpoints {
    Asset,
    Changelog,
    Collection,
    Episode,
    Franchise,
    Season,
    Show,
    Special,
}

impl fmt::Display for Endpoints {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let string_form = match *self {
            Endpoints::Asset => "assets",
            Endpoints::Changelog => "changelog",
            Endpoints::Collection => "collections",
            Endpoints::Episode => "episodes",
            Endpoints::Franchise => "franchises",
            Endpoints::Season => "seasons",
            Endpoints::Show => "shows",
            Endpoints::Special => "specials",
        };

        write!(f, "{}", string_form)
    }
}

impl<'a> Client<'a> {
    pub fn new(key: &'a str, secret: &'a str) -> Client<'a> {
        Client {
            key: key,
            secret: secret,
            base: LIVE_URL,
        }
    }

    pub fn qa(key: &'a str, secret: &'a str) -> Client<'a> {
        Client {
            key: key,
            secret: secret,
            base: QA_URL,
        }
    }

    pub fn get(&self, endpoint: Endpoints, id: &str) -> CDCResult<String> {

        rq_get(vec![self.base, "/", endpoint.to_string().as_str(), "/", id, "/"]
                   .join("")
                   .as_str(),
               vec![])
    }

    pub fn list(&self, endpoint: Endpoints, params: Params) -> CDCResult<String> {

        rq_get(vec![self.base, "/", endpoint.to_string().as_str(), "/"]
                   .join("")
                   .as_str(),
               params)
    }
}
<commit_msg>Add raw request method<commit_after>#[cfg(test)]
use mockito;

use error::CDCResult;
use request::rq_get;
use std::fmt;

#[cfg(not(test))]
const LIVE_URL: &'static str = "https://media-qa.services.pbs.org/api/v1";
#[cfg(not(test))]
const QA_URL: &'static str = "https://media-qa.services.pbs.org/api/v1";

#[cfg(test)]
const LIVE_URL: &'static str = mockito::SERVER_URL;
#[cfg(test)]
const QA_URL: &'static str = mockito::SERVER_URL;

pub struct Client<'a> {
    key: &'a str,
    secret: &'a str,
    base: &'a str,
}

pub type Params<'a> = Vec<(&'a str, &'a str)>;

pub enum Endpoints {
    Asset,
    Changelog,
    Collection,
    Episode,
    Franchise,
    Season,
    Show,
    Special,
}

impl fmt::Display for Endpoints {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let string_form = match *self {
            Endpoints::Asset => "assets",
            Endpoints::Changelog => "changelog",
            Endpoints::Collection => "collections",
            Endpoints::Episode => "episodes",
            Endpoints::Franchise => "franchises",
            Endpoints::Season => "seasons",
            Endpoints::Show => "shows",
            Endpoints::Special => "specials",
        };

        write!(f, "{}", string_form)
    }
}

impl<'a> Client<'a> {
    pub fn new(key: &'a str, secret: &'a str) -> Client<'a> {
        Client {
            key: key,
            secret: secret,
            base: LIVE_URL,
        }
    }

    pub fn qa(key: &'a str, secret: &'a str) -> Client<'a> {
        Client {
            key: key,
            secret: secret,
            base: QA_URL,
        }
    }

    pub fn get(&self, endpoint: Endpoints, id: &str) -> CDCResult<String> {
        rq_get(vec![self.base, "/", endpoint.to_string().as_str(), "/", id, "/"]
                   .join("")
                   .as_str(),
               vec![])
    }

    pub fn list(&self, endpoint: Endpoints, params: Params) -> CDCResult<String> {
        rq_get(vec![self.base, "/", endpoint.to_string().as_str(), "/"]
                   .join("")
                   .as_str(),
               params)
    }

    pub fn url(&self, url: &str) -> CDCResult<String> {
        rq_get(url, vec![])
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use fleetspeak_proto::common::{Message, Address};
use fleetspeak_proto::channel::{StartupData};
use lazy_static::lazy_static;
use prost;
use prost_types;
use std::io::{Read, Write, Result};
use std::marker::{Send, Sync};
use std::sync::Mutex;

pub struct Connection<R, W> {
    pub input: R,
    pub output: W,
}

impl<R: Read, W: Write> Connection<R, W> {

    pub fn new(input: R, output: W) -> Result<Self> {
        let mut conn = Connection {
            input: input,
            output: output,
        };
        conn.handshake()?;

        Ok(conn)
    }

    pub fn heartbeat(&mut self) -> Result<()> {
        let msg = Message {
            message_type: "Heartbeat".to_string(),
            destination: Some(Address {
                service_name: "system".to_string(),
                ..Default::default()
            }),
            ..Default::default()
        };

        self.emit(msg)
    }

    pub fn startup(&mut self, version: &str) -> Result<()> {
        let data = StartupData {
            pid: std::process::id() as i64,
            version: version.to_string(),
        };

        let mut buf = Vec::new();
        prost::Message::encode(&data, &mut buf).map_err(invalid_data_error)?;

        let msg = Message {
            message_type: "StartupData".to_string(),
            destination: Some(Address {
                service_name: "system".to_string(),
                ..Default::default()
            }),
            data: Some(prost_types::Any {
                value: buf,
                type_url: "type.googleapis.com/fleetspeak.channel.StartupData".to_string(),
            }),
            ..Default::default()
        };

        self.emit(msg)
    }

    pub fn send<M>(&mut self, service: &str, kind: &str, data: M) -> Result<()>
    where
        M: prost::Message,
    {
        let mut buf = Vec::new();
        prost::Message::encode(&data, &mut buf).map_err(invalid_data_error)?;

        let msg = Message {
            message_type: kind.to_string(),
            destination: Some(Address {
                service_name: service.to_string(),
                ..Default::default()
            }),
            data: Some(prost_types::Any {
                value: buf.to_vec(),
                ..Default::default()
            }),
            ..Default::default()
        };

        self.emit(msg)
    }

    pub fn receive<M>(&mut self) -> Result<M>
    where
        M: prost::Message + Default,
    {
        let msg = self.collect()?;

        // It is not clear what is the best approach here. If there is no data,
        // should we error-out or return a default value? For the time being we
        // stick to the default approach, but if this proves to be not working
        // well in practice, it might be reconsidered.
        let data = match msg.data {
            Some(data) => data,
            None => return Ok(Default::default()),
        };

        prost::Message::decode(&data.value[..]).map_err(invalid_data_error)
    }

    fn emit(&mut self, msg: Message) -> Result<()> {
        let mut buf = Vec::new();
        prost::Message::encode(&msg, &mut buf).map_err(invalid_data_error)?;

        self.output.write_u32::<LittleEndian>(buf.len() as u32)?;
        self.output.write(&buf)?;
        self.output.write_u32::<LittleEndian>(MAGIC)?;
        self.output.flush()?;

        Ok(())
    }

    fn collect(&mut self) -> Result<Message> {
        let len = self.input.read_u32::<LittleEndian>()? as usize;
        let mut buf = vec!(0; len);
        self.input.read_exact(&mut buf[..])?;

        prost::Message::decode(&buf[..]).map_err(invalid_data_error)
    }

    fn handshake(&mut self) -> Result<()> {
        self.output.write_u32::<LittleEndian>(MAGIC)?;
        self.output.flush()?;

        let magic = self.input.read_u32::<LittleEndian>()?;
        if magic != MAGIC {
            let err = invalid_data_error(format!("invalid magic `{}`", magic));
            return Err(err);
        }

        Ok(())
    }

}

fn invalid_data_error<E>(err: E) -> std::io::Error
where
    E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    return std::io::Error::new(std::io::ErrorKind::InvalidData, err);
}

const MAGIC: u32 = 0xf1ee1001;

fn open(var: &str) -> std::fs::File {
    let fd = match std::env::var(var) {
        Ok(fd) => fd,
        Err(err) => panic!("invalid variable `{}`: {}", var, err),
    };

    let fd = match fd.parse() {
        Ok(fd) => fd,
        Err(err) => panic!("failed to parse a file descriptor: {}", err),
    };

    // TODO: Add support for Windows.
    unsafe {
        std::os::unix::io::FromRawFd::from_raw_fd(fd)
    }
}

lazy_static! {
    static ref CONNECTION: Mutex<Connection<std::fs::File, std::fs::File>> = {
        let input = open("FLEETSPEAK_COMMS_CHANNEL_INFD");
        let output = open("FLEETSPEAK_COMMS_CHANNEL_OUTFD");

        let conn = Connection::new(input, output).expect("handshake failure");
        Mutex::new(conn)
    };
}

pub fn heartbeat() -> Result<()> {
    connected(|conn| conn.heartbeat())
}

pub fn startup(version: &str) -> Result<()> {
    connected(|conn| conn.startup(version))
}

pub fn send<M>(service: &str, kind: &str, data: M) -> Result<()>
where
    M: prost::Message,
{
    connected(|conn| conn.send(service, kind, data))
}

pub fn receive<M>() -> Result<M>
where
    M: prost::Message + Default,
{
    connected(|conn| conn.receive())
}

fn connected<F, T>(f: F) -> Result<T>
where
    F: FnOnce(&mut Connection<std::fs::File, std::fs::File>) -> Result<T>
{
    let mut conn = CONNECTION.lock().expect("poisoned connection mutex");
    f(&mut conn)
}

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

    #[test]
    fn handshake_good_magic() {
        let mut buf_in = [0; 1024];
        let mut buf_out = [0; 1024];

        let mut cur = Cursor::new(&mut buf_in[..]);
        assert!(cur.write_u32::<LittleEndian>(MAGIC).is_ok());

        let cur_in = Cursor::new(&mut buf_in[..]);
        let cur_out = Cursor::new(&mut buf_out[..]);
        assert!(Connection::new(cur_in, cur_out).is_ok());

        let mut cur = Cursor::new(&mut buf_out[..]);
        assert_eq!(cur.read_u32::<LittleEndian>().unwrap(), MAGIC);
    }

    #[test]
    fn handshake_bad_magic() {
        let mut buf_in = [0; 1024];
        let mut buf_out = [0; 1024];

        let mut cur = Cursor::new(&mut buf_in[..]);
        assert!(cur.write_u32::<LittleEndian>(0xf1ee1337).is_ok());

        let cur_in = Cursor::new(&mut buf_in[..]);
        let cur_out = Cursor::new(&mut buf_out[..]);
        assert!(Connection::new(cur_in, cur_out).is_err());
    }
}
<commit_msg>Fix an issue with not reading post-message magic.<commit_after>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use fleetspeak_proto::common::{Message, Address};
use fleetspeak_proto::channel::{StartupData};
use lazy_static::lazy_static;
use prost;
use prost_types;
use std::io::{Read, Write, Result};
use std::marker::{Send, Sync};
use std::sync::Mutex;

pub struct Connection<R, W> {
    pub input: R,
    pub output: W,
}

impl<R: Read, W: Write> Connection<R, W> {

    pub fn new(input: R, output: W) -> Result<Self> {
        let mut conn = Connection {
            input: input,
            output: output,
        };
        conn.handshake()?;

        Ok(conn)
    }

    pub fn heartbeat(&mut self) -> Result<()> {
        let msg = Message {
            message_type: "Heartbeat".to_string(),
            destination: Some(Address {
                service_name: "system".to_string(),
                ..Default::default()
            }),
            ..Default::default()
        };

        self.emit(msg)
    }

    pub fn startup(&mut self, version: &str) -> Result<()> {
        let data = StartupData {
            pid: std::process::id() as i64,
            version: version.to_string(),
        };

        let mut buf = Vec::new();
        prost::Message::encode(&data, &mut buf).map_err(invalid_data_error)?;

        let msg = Message {
            message_type: "StartupData".to_string(),
            destination: Some(Address {
                service_name: "system".to_string(),
                ..Default::default()
            }),
            data: Some(prost_types::Any {
                value: buf,
                type_url: "type.googleapis.com/fleetspeak.channel.StartupData".to_string(),
            }),
            ..Default::default()
        };

        self.emit(msg)
    }

    pub fn send<M>(&mut self, service: &str, kind: &str, data: M) -> Result<()>
    where
        M: prost::Message,
    {
        let mut buf = Vec::new();
        prost::Message::encode(&data, &mut buf).map_err(invalid_data_error)?;

        let msg = Message {
            message_type: kind.to_string(),
            destination: Some(Address {
                service_name: service.to_string(),
                ..Default::default()
            }),
            data: Some(prost_types::Any {
                value: buf.to_vec(),
                ..Default::default()
            }),
            ..Default::default()
        };

        self.emit(msg)
    }

    pub fn receive<M>(&mut self) -> Result<M>
    where
        M: prost::Message + Default,
    {
        let msg = self.collect()?;

        // It is not clear what is the best approach here. If there is no data,
        // should we error-out or return a default value? For the time being we
        // stick to the default approach, but if this proves to be not working
        // well in practice, it might be reconsidered.
        let data = match msg.data {
            Some(data) => data,
            None => return Ok(Default::default()),
        };

        prost::Message::decode(&data.value[..]).map_err(invalid_data_error)
    }

    fn emit(&mut self, msg: Message) -> Result<()> {
        let mut buf = Vec::new();
        prost::Message::encode(&msg, &mut buf).map_err(invalid_data_error)?;

        self.output.write_u32::<LittleEndian>(buf.len() as u32)?;
        self.output.write(&buf)?;
        self.output.write_u32::<LittleEndian>(MAGIC)?;
        self.output.flush()?;

        Ok(())
    }

    fn collect(&mut self) -> Result<Message> {
        let len = self.input.read_u32::<LittleEndian>()? as usize;
        let mut buf = vec!(0; len);
        self.input.read_exact(&mut buf[..])?;

        let magic = self.input.read_u32::<LittleEndian>()?;
        if magic != MAGIC {
            let err = invalid_data_error(format!("invalid magic: `{}`", magic));
            return Err(err);
        }

        prost::Message::decode(&buf[..]).map_err(invalid_data_error)
    }

    fn handshake(&mut self) -> Result<()> {
        self.output.write_u32::<LittleEndian>(MAGIC)?;
        self.output.flush()?;

        let magic = self.input.read_u32::<LittleEndian>()?;
        if magic != MAGIC {
            let err = invalid_data_error(format!("invalid magic `{}`", magic));
            return Err(err);
        }

        Ok(())
    }

}

fn invalid_data_error<E>(err: E) -> std::io::Error
where
    E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    return std::io::Error::new(std::io::ErrorKind::InvalidData, err);
}

const MAGIC: u32 = 0xf1ee1001;

fn open(var: &str) -> std::fs::File {
    let fd = match std::env::var(var) {
        Ok(fd) => fd,
        Err(err) => panic!("invalid variable `{}`: {}", var, err),
    };

    let fd = match fd.parse() {
        Ok(fd) => fd,
        Err(err) => panic!("failed to parse a file descriptor: {}", err),
    };

    // TODO: Add support for Windows.
    unsafe {
        std::os::unix::io::FromRawFd::from_raw_fd(fd)
    }
}

lazy_static! {
    static ref CONNECTION: Mutex<Connection<std::fs::File, std::fs::File>> = {
        let input = open("FLEETSPEAK_COMMS_CHANNEL_INFD");
        let output = open("FLEETSPEAK_COMMS_CHANNEL_OUTFD");

        let conn = Connection::new(input, output).expect("handshake failure");
        Mutex::new(conn)
    };
}

pub fn heartbeat() -> Result<()> {
    connected(|conn| conn.heartbeat())
}

pub fn startup(version: &str) -> Result<()> {
    connected(|conn| conn.startup(version))
}

pub fn send<M>(service: &str, kind: &str, data: M) -> Result<()>
where
    M: prost::Message,
{
    connected(|conn| conn.send(service, kind, data))
}

pub fn receive<M>() -> Result<M>
where
    M: prost::Message + Default,
{
    connected(|conn| conn.receive())
}

fn connected<F, T>(f: F) -> Result<T>
where
    F: FnOnce(&mut Connection<std::fs::File, std::fs::File>) -> Result<T>
{
    let mut conn = CONNECTION.lock().expect("poisoned connection mutex");
    f(&mut conn)
}

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

    #[test]
    fn handshake_good_magic() {
        let mut buf_in = [0; 1024];
        let mut buf_out = [0; 1024];

        let mut cur = Cursor::new(&mut buf_in[..]);
        assert!(cur.write_u32::<LittleEndian>(MAGIC).is_ok());

        let cur_in = Cursor::new(&mut buf_in[..]);
        let cur_out = Cursor::new(&mut buf_out[..]);
        assert!(Connection::new(cur_in, cur_out).is_ok());

        let mut cur = Cursor::new(&mut buf_out[..]);
        assert_eq!(cur.read_u32::<LittleEndian>().unwrap(), MAGIC);
    }

    #[test]
    fn handshake_bad_magic() {
        let mut buf_in = [0; 1024];
        let mut buf_out = [0; 1024];

        let mut cur = Cursor::new(&mut buf_in[..]);
        assert!(cur.write_u32::<LittleEndian>(0xf1ee1337).is_ok());

        let cur_in = Cursor::new(&mut buf_in[..]);
        let cur_out = Cursor::new(&mut buf_out[..]);
        assert!(Connection::new(cur_in, cur_out).is_err());
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, Mikkel Kroman <mk@uplink.io>
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use url::Url;
use hyper;

use std::result;
use std::borrow::Borrow;
use std::io::{self, Write};

use track::{Track, TrackRequestBuilder, SingleTrackRequestBuilder};
use error::{Error, Result};

pub type Params<'a, K, V> = &'a [(K, V)];

#[derive(Debug)]
pub struct Client {
    client_id: String,
    http_client: hyper::Client,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct App {
    pub id: usize,
    pub uri: String,
    pub permalink_url: String,
    pub external_url: String,
    pub creator: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Comment {
    pub id: usize,
    pub uri: String,
    pub created_at: String,
    pub body: String,
    pub timestamp: Option<usize>,
    pub user_id: usize,
    pub user: User,
    pub track_id: usize,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct User {
    pub id: usize,
    pub permalink: String,
    pub username: String,
    pub uri: String,
    pub permalink_url: String,
    pub avatar_url: String,
    pub country: Option<String>,
    pub city: Option<String>,
    pub description: Option<String>,
    #[serde(rename="discogs-name")]
    pub discogs_name: Option<String>, // discogs-name
    #[serde(rename="myspace-name")]
    pub myspace_name: Option<String>, // myspace-name
    pub website: Option<String>,
    #[serde(rename="website-title")]
    pub website_title: Option<String>, // website-title
    pub online: Option<bool>,
    pub track_count: Option<usize>,
    pub playlist_count: Option<usize>,
    pub followers_count: Option<usize>,
    pub followings_count: Option<usize>,
    pub public_favorites_count: Option<usize>,
    // pub avatar_data …
}

impl Client {
    /// Constructs a new `Client` with the provided `client_id`.
    ///
    /// # Examples
    ///
    /// ```
    /// use soundcloud::Client;
    ///
    /// let client = Client::new(env!("SOUNDCLOUD_CLIENT_ID"));
    /// ```
    pub fn new(client_id: &str) -> Client {
        let mut client = hyper::Client::new();
        client.set_redirect_policy(hyper::client::RedirectPolicy::FollowNone);

        Client {
            client_id: client_id.to_owned(),
            http_client: client,
        }
    }

    /// Returns the client id.
    pub fn client_id(&self) -> &str {
        &self.client_id
    }

    /// Creates and sends a HTTP GET request to the API endpoint.
    ///
    /// A `client_id` parameter will automatically be added to the request.
    ///
    /// Returns the HTTP response on success, an error otherwise.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::Read;
    /// use soundcloud::Client;
    /// let client = Client::new(env!("SOUNDCLOUD_CLIENT_ID"));
    /// let response = client.get("/resolve", Some(&[("url",
    /// "https://soundcloud.com/firepowerrecs/afk-shellshock-kamikaze-promo-mix-lock-load-series-vol-20")]));
    ///
    /// let mut buffer = String::new();
    ///
    /// response.unwrap().read_to_string(&mut buffer);
    ///
    /// assert!(!buffer.is_empty());
    /// ```
    pub fn get<I, K, V>(&self, path: &str, params: Option<I>)
        -> result::Result<hyper::client::Response, hyper::Error>
    where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
        let mut url = Url::parse(&format!("https://{}{}", super::API_HOST, path)).unwrap();

        {
            let mut query_pairs = url.query_pairs_mut();
            query_pairs.append_pair("client_id", &self.client_id);

            if let Some(params) = params {
                query_pairs.extend_pairs(params);
            }
        }

        debug!("get {}", url);

        let response = self.http_client.get(url).send();
        response
    }

    pub fn download<W: Write>(&self, track: &Track, mut writer: W) -> Result<usize> {
        use hyper::header::Location;

        if !track.downloadable || !track.download_url.is_some() {
            return Err(Error::TrackNotDownloadable);
        }

        let url = self.parse_url(track.download_url.as_ref().unwrap());
        let mut response = try!(self.http_client.get(url).send());

        // Follow the redirect just this once.
        if let Some(header) = response.headers.get::<Location>().cloned() {
            response = try!(self.http_client.get(Url::parse(&header).unwrap()).send());
        }

        try!(io::copy(&mut response, &mut writer).map(|len| Ok(len as usize)))
    }

    /// Starts streaming the track provided in the tracks `stream_url` to the `writer` if the track
    /// is streamable via the API.
    pub fn stream<W: Write>(&self, track: &Track, mut writer: W) -> Result<usize> {
        use hyper::header::Location;

        if !track.streamable || !track.stream_url.is_some() {
            return Err(Error::TrackNotStreamable);
        }

        let url = self.parse_url(track.stream_url.as_ref().unwrap());
        let mut response = try!(self.http_client.get(url).send());

        // Follow the redirect just this once.
        if let Some(header) = response.headers.get::<Location>().cloned() {
            response = try!(self.http_client.get(Url::parse(&header).unwrap()).send());
        }

        try!(io::copy(&mut response, &mut writer).map(|len| Ok(len as usize)))
    }

    /// Resolves any soundcloud resource and returns it as a `Url`.
    pub fn resolve(&self, url: &str) -> Result<Url> {
        use hyper::header::Location;
        let response = self.get("/resolve", Some(&[("url", url)]));

        match response {
            Ok(response) => {
                if let Some(header) = response.headers.get::<Location>() {
                    return Ok(Url::parse(header).unwrap());
                } else {
                    return Err(Error::ApiError("expected location header".to_owned()));
                }
            }
            Err(e) => return Err(e.into()),
        }
    }

    /// Returns a builder for a single track-by-id request.
    ///
    /// # Examples
    ///
    /// ```
    /// use soundcloud::client::Client;
    ///
    /// let client = Client::new(env!("SOUNDCLOUD_CLIENT_ID"));
    /// let track = client.track(262681089).get();
    ///
    /// assert_eq!(track.unwrap().id, 262681089);
    /// ```
    pub fn track(&self, id: usize) -> SingleTrackRequestBuilder {
        SingleTrackRequestBuilder::new(self, id)
    }

    /// Returns a builder for searching tracks with multiple criteria.
    ///
    /// # Examples
    ///
    /// ```
    /// use soundcloud::client::Client;
    ///
    /// let client = Client::new(env!("SOUNDCLOUD_CLIENT_ID"));
    /// let tracks = client.tracks().genres(Some(["HipHop"])).get();
    ///
    /// assert!(tracks.unwrap().expect("no tracks found").len() > 0);
    /// ```
    pub fn tracks(&self) -> TrackRequestBuilder {
        TrackRequestBuilder::new(self)
    }

    /// Parses a string and returns a url with the client_id query parameter set.
    fn parse_url<S: AsRef<str>>(&self, url: S) -> Url {
        let mut url = Url::parse(url.as_ref()).unwrap();
        url.query_pairs_mut().append_pair("client_id", &self.client_id);
        url
    }
}

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

    fn client() -> Client {
        Client::new(env!("SOUNDCLOUD_CLIENT_ID"))
    }

    #[test]
    fn test_resolve_track() {
        let result = client().resolve("https://soundcloud.com/isqa/tree-eater-1");

        assert_eq!(result.unwrap(),
            Url::parse(&format!("https://api.soundcloud.com/tracks/262976655?client_id={}",
                                env!("SOUNDCLOUD_CLIENT_ID"))).unwrap());
    }

    #[test]
    fn test_get_tracks() {
        let result = client().tracks().query(Some("d0df0dt snuffx")).get();

        assert!(result.unwrap().is_some());
    }

    #[test]
    fn test_get_track() {
        let track = client().tracks().id(18201932).get().unwrap();

        assert_eq!(track.id, 18201932);
    }

    #[test]
    fn test_download_track() {
        use std::fs::File;

        let client = client();
        let track = client.tracks().id(263801976).get().unwrap();
        let mut file = File::create("hi.mp3").unwrap();
        let ret = client.download(&track, &mut file);

        assert!(ret.unwrap() > 0);
    }

    #[test]
    fn test_stream_track() {
        use std::io::BufWriter;
        let client = client();
        let track = client.tracks().id(262681089).get().unwrap();
        let mut buffer = BufWriter::new(vec![]);
        let len = client.stream(&track, &mut buffer);

        assert!(len.unwrap() > 0);
        assert!(buffer.get_ref().len() > 0);
    }
}
<commit_msg>Tidy up some code<commit_after>// Copyright (c) 2016, Mikkel Kroman <mk@uplink.io>
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use url::Url;
use hyper;

use std::result;
use std::borrow::Borrow;
use std::io::{self, Write};

use track::{Track, TrackRequestBuilder, SingleTrackRequestBuilder};
use error::{Error, Result};

pub type Params<'a, K, V> = &'a [(K, V)];

#[derive(Debug)]
pub struct Client {
    client_id: String,
    http_client: hyper::Client,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct App {
    pub id: usize,
    pub uri: String,
    pub permalink_url: String,
    pub external_url: String,
    pub creator: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Comment {
    pub id: usize,
    pub uri: String,
    pub created_at: String,
    pub body: String,
    pub timestamp: Option<usize>,
    pub user_id: usize,
    pub user: User,
    pub track_id: usize,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct User {
    pub id: usize,
    pub permalink: String,
    pub username: String,
    pub uri: String,
    pub permalink_url: String,
    pub avatar_url: String,
    pub country: Option<String>,
    pub city: Option<String>,
    pub description: Option<String>,
    #[serde(rename="discogs-name")]
    pub discogs_name: Option<String>, // discogs-name
    #[serde(rename="myspace-name")]
    pub myspace_name: Option<String>, // myspace-name
    pub website: Option<String>,
    #[serde(rename="website-title")]
    pub website_title: Option<String>, // website-title
    pub online: Option<bool>,
    pub track_count: Option<usize>,
    pub playlist_count: Option<usize>,
    pub followers_count: Option<usize>,
    pub followings_count: Option<usize>,
    pub public_favorites_count: Option<usize>,
    // pub avatar_data …
}

impl Client {
    /// Constructs a new `Client` with the provided `client_id`.
    ///
    /// # Examples
    ///
    /// ```
    /// use soundcloud::Client;
    ///
    /// let client = Client::new(env!("SOUNDCLOUD_CLIENT_ID"));
    /// ```
    pub fn new(client_id: &str) -> Client {
        let mut client = hyper::Client::new();
        client.set_redirect_policy(hyper::client::RedirectPolicy::FollowNone);

        Client {
            client_id: client_id.to_owned(),
            http_client: client,
        }
    }

    /// Returns the client id.
    pub fn client_id(&self) -> &str {
        &self.client_id
    }

    /// Creates and sends a HTTP GET request to the API endpoint.
    ///
    /// A `client_id` parameter will automatically be added to the request.
    ///
    /// Returns the HTTP response on success, an error otherwise.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::Read;
    /// use soundcloud::Client;
    /// let client = Client::new(env!("SOUNDCLOUD_CLIENT_ID"));
    /// let response = client.get("/resolve", Some(&[("url",
    /// "https://soundcloud.com/firepowerrecs/afk-shellshock-kamikaze-promo-mix-lock-load-series-vol-20")]));
    ///
    /// let mut buffer = String::new();
    /// response.unwrap().read_to_string(&mut buffer);
    ///
    /// assert!(!buffer.is_empty());
    /// ```
    pub fn get<I, K, V>(&self, path: &str, params: Option<I>)
        -> result::Result<hyper::client::Response, hyper::Error>
    where I: IntoIterator, I::Item: Borrow<(K, V)>, K: AsRef<str>, V: AsRef<str> {
        let mut url = Url::parse(&format!("https://{}{}", super::API_HOST, path)).unwrap();

        {
            let mut query_pairs = url.query_pairs_mut();
            query_pairs.append_pair("client_id", &self.client_id);

            if let Some(params) = params {
                query_pairs.extend_pairs(params);
            }
        }

        let response = self.http_client.get(url).send();
        response
    }

    pub fn download<W: Write>(&self, track: &Track, mut writer: W) -> Result<usize> {
        use hyper::header::Location;

        if !track.downloadable || !track.download_url.is_some() {
            return Err(Error::TrackNotDownloadable);
        }

        let url = self.parse_url(track.download_url.as_ref().unwrap());
        let mut response = try!(self.http_client.get(url).send());

        // Follow the redirect just this once.
        if let Some(header) = response.headers.get::<Location>().cloned() {
            let url = Url::parse(&header).unwrap();
            response = try!(self.http_client.get(url).send());
        }

        try!(io::copy(&mut response, &mut writer).map(|n| Ok(n as usize)))
    }

    /// Starts streaming the track provided in the tracks `stream_url` to the `writer` if the track
    /// is streamable via the API.
    pub fn stream<W: Write>(&self, track: &Track, mut writer: W) -> Result<usize> {
        use hyper::header::Location;

        if !track.streamable || !track.stream_url.is_some() {
            return Err(Error::TrackNotStreamable);
        }

        let url = self.parse_url(track.stream_url.as_ref().unwrap());
        let mut response = try!(self.http_client.get(url).send());

        // Follow the redirect just this once.
        if let Some(header) = response.headers.get::<Location>().cloned() {
            let url = Url::parse(&header).unwrap();
            response = try!(self.http_client.get(url).send());
        }

        try!(io::copy(&mut response, &mut writer).map(|n| Ok(n as usize)))
    }

    /// Resolves any soundcloud resource and returns it as a `Url`.
    pub fn resolve(&self, url: &str) -> Result<Url> {
        use hyper::header::Location;
        let response = try!(self.get("/resolve", Some(&[("url", url)])));

        if let Some(header) = response.headers.get::<Location>() {
            Ok(Url::parse(header).unwrap())
        } else {
            Err(Error::ApiError("expected location header".to_owned()))
        }
    }

    /// Returns a builder for a single track-by-id request.
    ///
    /// # Examples
    ///
    /// ```
    /// use soundcloud::Client;
    ///
    /// let client = Client::new(env!("SOUNDCLOUD_CLIENT_ID"));
    /// let track = client.track(262681089).get();
    ///
    /// assert_eq!(track.unwrap().id, 262681089);
    /// ```
    pub fn track(&self, id: usize) -> SingleTrackRequestBuilder {
        SingleTrackRequestBuilder::new(self, id)
    }

    /// Returns a builder for searching tracks with multiple criteria.
    ///
    /// # Examples
    ///
    /// ```
    /// use soundcloud::Client;
    ///
    /// let client = Client::new(env!("SOUNDCLOUD_CLIENT_ID"));
    /// let tracks = client.tracks().genres(Some(["HipHop"])).get();
    ///
    /// assert!(tracks.unwrap().expect("no tracks found").len() > 0);
    /// ```
    pub fn tracks(&self) -> TrackRequestBuilder {
        TrackRequestBuilder::new(self)
    }

    /// Parses a string and returns a url with the client_id query parameter set.
    fn parse_url<S: AsRef<str>>(&self, url: S) -> Url {
        let mut url = Url::parse(url.as_ref()).unwrap();
        url.query_pairs_mut().append_pair("client_id", &self.client_id);
        url
    }
}

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

    fn client() -> Client {
        Client::new(env!("SOUNDCLOUD_CLIENT_ID"))
    }

    #[test]
    fn test_resolve_track() {
        let result = client().resolve("https://soundcloud.com/isqa/tree-eater-1");

        assert_eq!(result.unwrap(),
            Url::parse(&format!("https://api.soundcloud.com/tracks/262976655?client_id={}",
                                env!("SOUNDCLOUD_CLIENT_ID"))).unwrap());
    }

    #[test]
    fn test_get_tracks() {
        let result = client().tracks().query(Some("d0df0dt snuffx")).get();

        assert!(result.unwrap().is_some());
    }

    #[test]
    fn test_get_track() {
        let track = client().tracks().id(18201932).get().unwrap();

        assert_eq!(track.id, 18201932);
    }

    #[test]
    fn test_download_track() {
        use std::fs;
        use std::path::Path;

        let client = client();
        let path = Path::new("hi.mp3");
        let track = client.tracks().id(263801976).get().unwrap();
        let mut file = fs::File::create(path).unwrap();
        let ret = client.download(&track, &mut file);

        assert!(ret.unwrap() > 0);
        fs::remove_file(path).unwrap();
    }

    #[test]
    fn test_stream_track() {
        use std::io::BufWriter;
        let client = client();
        let track = client.tracks().id(262681089).get().unwrap();
        let mut buffer = BufWriter::new(vec![]);
        let len = client.stream(&track, &mut buffer);

        assert!(len.unwrap() > 0);
        assert!(buffer.get_ref().len() > 0);
    }
}
<|endoftext|>


<commit_before>use std::fs::{self, File};
use std::collections::HashMap;
use std::io::Write;
use std::path::Path;
use std::ffi::OsStr;
use liquid::Value;
use walkdir::{WalkDir, DirEntry, WalkDirIterator};
use document::Document;
use error::{ErrorKind, Result};
use config::Config;
use chrono::{UTC, FixedOffset};
use chrono::offset::TimeZone;
use rss::{Channel, Rss};
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();
    let path = path.strip_prefix(&source).unwrap_or(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,
    }
}

/// Checks if one path is the starting point of another path.
fn starts_with_path(this: &Path, starts_with: &Path) -> bool {
    match (fs::canonicalize(this), fs::canonicalize(starts_with)) {
        (Ok(p), Ok(p2)) => p.starts_with(p2),
        _ => false,
    }
}

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

    let source = Path::new(&config.source);
    let dest = Path::new(&config.dest);

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

    let layouts = source.join(&config.layouts);
    let mut layouts_cache = HashMap::new();
    let posts_path = source.join(&config.posts);

    debug!("Layouts directory: {:?}", layouts);
    debug!("Posts directory: {:?}", posts_path);
    debug!("Draft mode enabled: {}", config.include_drafts);
    if config.include_drafts {
        debug!("Draft directory: {:?}", config.drafts);
    }

    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_path)) &&
            !compare_paths(e.path(), dest)
        })
        .filter_map(|e| e.ok());

    for entry in walker {
        let entry_path = entry.path();
        let extension = &entry_path.extension().unwrap_or_else(|| 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| starts_with_path(p, &posts_path)).unwrap_or(false);

            let new_path = entry_path.strip_prefix(source).expect("Entry not in source folder");

            let doc = try!(Document::parse(&entry_path, new_path, is_post, &config.post_path));
            if !doc.is_draft || config.include_drafts {
                documents.push(doc);
            }
        }
    }

    if config.include_drafts {
        let drafts = source.join(&config.drafts);

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

        for entry in walker {
            let entry_path = entry.path();
            let extension = &entry_path.extension().unwrap_or_else(|| OsStr::new(""));
            let new_path = posts_path
                .join(entry_path.strip_prefix(&drafts).expect("Draft not in draft folder!"));
            let new_path = new_path.strip_prefix(source).expect("Entry not in source folder");
            if template_extensions.contains(extension) {
                let doc = try!(Document::parse(&entry_path, new_path, true, &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));

    let (mut posts, documents): (Vec<Document>, Vec<Document>) = documents.into_iter()
        .partition(|x| x.is_post);

    // 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
    posts.sort_by(|a, b| b.date.unwrap_or(default_date).cmp(&a.date.unwrap_or(default_date)));

    // collect all posts attributes to pass them to other posts for rendering
    let simple_posts_data: Vec<Value> = posts.iter()
        .map(|x| Value::Object(x.attributes.clone()))
        .collect();

    trace!("Generating posts");
    for mut post in &mut posts {
        trace!("Generating {}", post.path);

        let mut context = post.get_render_context(&simple_posts_data);

        try!(post.render_excerpt(&mut context, &source, &config.excerpt_separator));
        let post_html = try!(post.render(&mut context, &source, &layouts, &mut layouts_cache));
        try!(create_document_file(&post_html, &post.path, dest));
    }

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

    // during post rendering additional attributes such as content were
    // added to posts. collect them so that non-post documents can access them
    let posts_data: Vec<Value> = posts.into_iter()
        .map(|x| Value::Object(x.attributes))
        .collect();

    trace!("Generating other documents");
    for mut doc in documents {
        trace!("Generating {}", doc.path);

        let mut context = doc.get_render_context(&posts_data);
        let doc_html = try!(doc.render(&mut context, &source, &layouts, &mut layouts_cache));
        try!(create_document_file(&doc_html, &doc.path, dest));
    }

    // 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_else(|| 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 = if source_str == "." {
                entry_path
            } else {
                try!(entry_path.split(source_str)
                    .last()
                    .map(|s| s.trim_left_matches("/"))
                    .ok_or("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(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 {:?} into {:?}: {}",
                            entry.path(),
                            dest.join(relative),
                            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, posts: &[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 = posts.iter()
                .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(ErrorKind::ConfigFileMissingFields.into()),
    }
}

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

    // create target directories if any exist
    if let Some(parent) = file_path.parent() {
        try!(fs::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.as_bytes()));
    info!("Created {}", file_path.display());
    Ok(())
}

// The tests are taken from tests/fixtures/`posts_in_subfolder`/
#[test]
fn test_starts_with_path() {
    let posts_folder = Path::new("tests/fixtures/posts_in_subfolder/posts");

    assert!(!starts_with_path(Path::new("tests/fixtures/posts_in_subfolder"), posts_folder));
    assert!(starts_with_path(Path::new("tests/fixtures/posts_in_subfolder/posts"),
                             posts_folder));
    assert!(starts_with_path(Path::new("tests/fixtures/posts_in_subfolder/posts/20170103"),
                             posts_folder));
    assert!(starts_with_path(Path::new("tests/fixtures/posts_in_subfolder/posts/2017/01/08"),
                             posts_folder));
}
<commit_msg>Use the system dependent path separator<commit_after>use std::fs::{self, File};
use std::collections::HashMap;
use std::io::Write;
use std::path::{self, Path};
use std::ffi::OsStr;
use liquid::Value;
use walkdir::{WalkDir, DirEntry, WalkDirIterator};
use document::Document;
use error::{ErrorKind, Result};
use config::Config;
use chrono::{UTC, FixedOffset};
use chrono::offset::TimeZone;
use rss::{Channel, Rss};
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();
    let path = path.strip_prefix(&source).unwrap_or(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,
    }
}

/// Checks if one path is the starting point of another path.
fn starts_with_path(this: &Path, starts_with: &Path) -> bool {
    match (fs::canonicalize(this), fs::canonicalize(starts_with)) {
        (Ok(p), Ok(p2)) => p.starts_with(p2),
        _ => false,
    }
}

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

    let source = Path::new(&config.source);
    let dest = Path::new(&config.dest);

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

    let layouts = source.join(&config.layouts);
    let mut layouts_cache = HashMap::new();
    let posts_path = source.join(&config.posts);

    debug!("Layouts directory: {:?}", layouts);
    debug!("Posts directory: {:?}", posts_path);
    debug!("Draft mode enabled: {}", config.include_drafts);
    if config.include_drafts {
        debug!("Draft directory: {:?}", config.drafts);
    }

    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_path)) &&
            !compare_paths(e.path(), dest)
        })
        .filter_map(|e| e.ok());

    for entry in walker {
        let entry_path = entry.path();
        let extension = &entry_path.extension().unwrap_or_else(|| 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| starts_with_path(p, &posts_path)).unwrap_or(false);

            let new_path = entry_path.strip_prefix(source).expect("Entry not in source folder");

            let doc = try!(Document::parse(&entry_path, new_path, is_post, &config.post_path));
            if !doc.is_draft || config.include_drafts {
                documents.push(doc);
            }
        }
    }

    if config.include_drafts {
        let drafts = source.join(&config.drafts);

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

        for entry in walker {
            let entry_path = entry.path();
            let extension = &entry_path.extension().unwrap_or_else(|| OsStr::new(""));
            let new_path = posts_path
                .join(entry_path.strip_prefix(&drafts).expect("Draft not in draft folder!"));
            let new_path = new_path.strip_prefix(source).expect("Entry not in source folder");
            if template_extensions.contains(extension) {
                let doc = try!(Document::parse(&entry_path, new_path, true, &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));

    let (mut posts, documents): (Vec<Document>, Vec<Document>) = documents.into_iter()
        .partition(|x| x.is_post);

    // 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
    posts.sort_by(|a, b| b.date.unwrap_or(default_date).cmp(&a.date.unwrap_or(default_date)));

    // collect all posts attributes to pass them to other posts for rendering
    let simple_posts_data: Vec<Value> = posts.iter()
        .map(|x| Value::Object(x.attributes.clone()))
        .collect();

    trace!("Generating posts");
    for mut post in &mut posts {
        trace!("Generating {}", post.path);

        let mut context = post.get_render_context(&simple_posts_data);

        try!(post.render_excerpt(&mut context, &source, &config.excerpt_separator));
        let post_html = try!(post.render(&mut context, &source, &layouts, &mut layouts_cache));
        try!(create_document_file(&post_html, &post.path, dest));
    }

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

    // during post rendering additional attributes such as content were
    // added to posts. collect them so that non-post documents can access them
    let posts_data: Vec<Value> = posts.into_iter()
        .map(|x| Value::Object(x.attributes))
        .collect();

    trace!("Generating other documents");
    for mut doc in documents {
        trace!("Generating {}", doc.path);

        let mut context = doc.get_render_context(&posts_data);
        let doc_html = try!(doc.render(&mut context, &source, &layouts, &mut layouts_cache));
        try!(create_document_file(&doc_html, &doc.path, dest));
    }

    // 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_else(|| 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 = if source_str == "." {
                entry_path
            } else {
                try!(entry_path.split(source_str)
                    .last()
                    .map(|s| s.trim_left_matches(path::MAIN_SEPARATOR))
                    .ok_or("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(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 {:?} into {:?}: {}",
                            entry.path(),
                            dest.join(relative),
                            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, posts: &[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 = posts.iter()
                .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(ErrorKind::ConfigFileMissingFields.into()),
    }
}

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

    // create target directories if any exist
    if let Some(parent) = file_path.parent() {
        try!(fs::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.as_bytes()));
    info!("Created {}", file_path.display());
    Ok(())
}

// The tests are taken from tests/fixtures/`posts_in_subfolder`/
#[test]
fn test_starts_with_path() {
    let posts_folder = Path::new("tests/fixtures/posts_in_subfolder/posts");

    assert!(!starts_with_path(Path::new("tests/fixtures/posts_in_subfolder"), posts_folder));
    assert!(starts_with_path(Path::new("tests/fixtures/posts_in_subfolder/posts"),
                             posts_folder));
    assert!(starts_with_path(Path::new("tests/fixtures/posts_in_subfolder/posts/20170103"),
                             posts_folder));
    assert!(starts_with_path(Path::new("tests/fixtures/posts_in_subfolder/posts/2017/01/08"),
                             posts_folder));
}
<|endoftext|>


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

use ascii::{AsciiString, AsciiStr};
use std::ascii::AsciiExt;
use std::fmt::{self, Display, Formatter};
use std::str::{FromStr};
use std::cmp::Ordering;

/// Status code of a request or response.
#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
pub struct StatusCode(pub u16);

impl StatusCode {
    /// Returns the status code as a number.
    pub fn as_u16(&self) -> u16 {
        match *self { StatusCode(n) => n }
    }

    pub fn from_u16(in_code: u16) -> StatusCode {
        StatusCode(in_code)
    }

    /// Returns the default reason phrase for this status code.
    /// For example the status code 404 corresponds to "Not Found".
    pub fn get_default_reason_phrase(&self) -> &'static str {
        match self.as_u16() {
            100 => "Continue",
            101 => "Switching Protocols",
            102 => "Processing",
            118 => "Connection timed out",
            200 => "OK",
            201 => "Created",
            202 => "Accepted",
            203 => "Non-Authoritative Information",
            204 => "No Content",
            205 => "Reset Content",
            206 => "Partial Content",
            207 => "Multi-Status",
            210 => "Content Different",
            300 => "Multiple Choices",
            301 => "Moved Permanently",
            302 => "Found",
            303 => "See Other",
            304 => "Not Modified",
            305 => "Use Proxy",
            307 => "Temporary Redirect",
            400 => "Bad Request",
            401 => "Unauthorized",
            402 => "Payment Required",
            403 => "Forbidden",
            404 => "Not Found",
            405 => "Method Not Allowed",
            406 => "Not Acceptable",
            407 => "Proxy Authentication Required",
            408 => "Request Time-out",
            409 => "Conflict",
            410 => "Gone",
            411 => "Length Required",
            412 => "Precondition Failed",
            413 => "Request Entity Too Large",
            414 => "Reques-URI Too Large",
            415 => "Unsupported Media Type",
            416 => "Request range not satisfiable",
            417 => "Expectation Failed",
            500 => "Internal Server Error",
            501 => "Not Implemented",
            502 => "Bad Gateway",
            503 => "Service Unavailable",
            504 => "Gateway Time-out",
            505 => "HTTP Version not supported",
            _ => "Unknown"
        }
    }

    pub fn equiv(&self, other: &u16) -> bool {
        self.as_u16() == *other
    }
}

impl From<i8> for StatusCode {
    fn from(in_code: i8) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u8> for StatusCode {
    fn from(in_code: u8) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<i16> for StatusCode {
    fn from(in_code: i16) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u16> for StatusCode {
    fn from(in_code: u16) -> StatusCode {
        StatusCode(in_code)
    }
}

impl From<i32> for StatusCode {
    fn from(in_code: i32) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u32> for StatusCode {
    fn from(in_code: u32) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

/// Represents a HTTP header.
///
/// The easiest way to create a `Header` object is to call `parse`.
///
/// ```
/// let header: tiny_http::Header = "Content-Type: text/plain".parse().unwrap();
/// ```
#[derive(Debug, Clone)]
pub struct Header {
    pub field: HeaderField,
    pub value: AsciiString,
}

impl FromStr for Header {
    type Err = ();

    fn from_str(input: &str) -> Result<Header, ()> {
        let mut elems = input.splitn(2, ':');

        let field = elems.next();
        let value = elems.next();

        let (field, value) = match (field, value) {
            (Some(f), Some(v)) => (f, v),
            _ => return Err(())
        };

        let field = match FromStr::from_str(field) {
            Ok(f) => f,
            _ => return Err(())
        };

        let value = match AsciiStr::from_str(value.trim()) {
            Some(v) => v.to_ascii_string(),
            None => return Err(())
        };

        Ok(Header {
            field: field,
            value: value,
        })
    }
}

impl Display for Header {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "{}: {}", self.field, self.value.as_str())
    }
}

/// Field of a header (eg. `Content-Type`, `Content-Length`, etc.)
///
/// Comparaison between two `HeaderField`s ignores case.
#[derive(Debug, Clone)]
pub struct HeaderField(AsciiString);

impl HeaderField {
    pub fn as_str<'a>(&'a self) -> &'a AsciiStr {
        match self { &HeaderField(ref s) => s }
    }

    pub fn equiv(&self, other: &'static str) -> bool {
        other.eq_ignore_ascii_case(self.as_str().as_str())
    }
}

impl FromStr for HeaderField {
    type Err = ();

    fn from_str(s: &str) -> Result<HeaderField, ()> {
        AsciiStr::from_str(s.trim()).map(|s| HeaderField(s.to_ascii_string())).ok_or(())
    }
}

impl Display for HeaderField {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        let method = self.as_str();
        write!(formatter, "{}", method.as_str())
    }
}

impl PartialEq for HeaderField {
    fn eq(&self, other: &HeaderField) -> bool {
        self.as_str().eq_ignore_ascii_case(other.as_str())
    }
}

impl Eq for HeaderField {}

/// HTTP method (eg. `GET`, `POST`, etc.)
///
/// The user chooses the method he wants.
///
/// Comparaison between two `Method`s ignores case.
#[derive(Debug, Clone)]
pub struct Method(AsciiString);

impl Method {
    fn as_str(&self) -> &AsciiStr {
        match self { &Method(ref s) => s }
    }

    pub fn equiv(&self, other: &'static str) -> bool {
        other.eq_ignore_ascii_case(self.as_str().as_str())
    }
}

impl FromStr for Method {
    type Err = ();

    fn from_str(s: &str) -> Result<Method, ()> {
        <AsciiString as FromStr>::from_str(s).map(|s| Method(s))
    }
}

impl Display for Method {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "{}", self.0)
    }
}

impl PartialEq for Method {
    fn eq(&self, other: &Method) -> bool {
        self.0.eq_ignore_ascii_case(&other.0)
    }
}

impl Eq for Method {}

/// HTTP version (usually 1.0 or 1.1).
#[derive(Debug, Clone, PartialEq, Eq, Ord)]
pub struct HTTPVersion(pub usize, pub usize);

impl Display for HTTPVersion {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        let (major, minor) = match self { &HTTPVersion(m, n) => (m, n) };
        write!(formatter, "{}.{}", major, minor)
    }
}

impl PartialOrd for HTTPVersion {
    fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
        let (my_major, my_minor) = match self { &HTTPVersion(m, n) => (m, n) };
        let (other_major, other_minor) = match other { &HTTPVersion(m, n) => (m, n) };

        if my_major != other_major {
            return my_major.partial_cmp(&other_major)
        }

        my_minor.partial_cmp(&other_minor)
    }
}

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

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

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

        assert!("hello world".parse::<Header>().is_err());
    }

    #[test]
    fn test_parse_header_with_doublecolon() {
        let header: Header = "Time: 20: 34".parse().unwrap();

        assert!(header.field.equiv(&"time"));
        assert!(header.value.as_str() == "20: 34");
    }
}
<commit_msg>Use u8s for HTTPVersion instead of usize<commit_after>// Copyright 2015 The tiny-http Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use ascii::{AsciiString, AsciiStr};
use std::ascii::AsciiExt;
use std::fmt::{self, Display, Formatter};
use std::str::{FromStr};
use std::cmp::Ordering;

/// Status code of a request or response.
#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
pub struct StatusCode(pub u16);

impl StatusCode {
    /// Returns the status code as a number.
    pub fn as_u16(&self) -> u16 {
        match *self { StatusCode(n) => n }
    }

    pub fn from_u16(in_code: u16) -> StatusCode {
        StatusCode(in_code)
    }

    /// Returns the default reason phrase for this status code.
    /// For example the status code 404 corresponds to "Not Found".
    pub fn get_default_reason_phrase(&self) -> &'static str {
        match self.as_u16() {
            100 => "Continue",
            101 => "Switching Protocols",
            102 => "Processing",
            118 => "Connection timed out",
            200 => "OK",
            201 => "Created",
            202 => "Accepted",
            203 => "Non-Authoritative Information",
            204 => "No Content",
            205 => "Reset Content",
            206 => "Partial Content",
            207 => "Multi-Status",
            210 => "Content Different",
            300 => "Multiple Choices",
            301 => "Moved Permanently",
            302 => "Found",
            303 => "See Other",
            304 => "Not Modified",
            305 => "Use Proxy",
            307 => "Temporary Redirect",
            400 => "Bad Request",
            401 => "Unauthorized",
            402 => "Payment Required",
            403 => "Forbidden",
            404 => "Not Found",
            405 => "Method Not Allowed",
            406 => "Not Acceptable",
            407 => "Proxy Authentication Required",
            408 => "Request Time-out",
            409 => "Conflict",
            410 => "Gone",
            411 => "Length Required",
            412 => "Precondition Failed",
            413 => "Request Entity Too Large",
            414 => "Reques-URI Too Large",
            415 => "Unsupported Media Type",
            416 => "Request range not satisfiable",
            417 => "Expectation Failed",
            500 => "Internal Server Error",
            501 => "Not Implemented",
            502 => "Bad Gateway",
            503 => "Service Unavailable",
            504 => "Gateway Time-out",
            505 => "HTTP Version not supported",
            _ => "Unknown"
        }
    }

    pub fn equiv(&self, other: &u16) -> bool {
        self.as_u16() == *other
    }
}

impl From<i8> for StatusCode {
    fn from(in_code: i8) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u8> for StatusCode {
    fn from(in_code: u8) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<i16> for StatusCode {
    fn from(in_code: i16) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u16> for StatusCode {
    fn from(in_code: u16) -> StatusCode {
        StatusCode(in_code)
    }
}

impl From<i32> for StatusCode {
    fn from(in_code: i32) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

impl From<u32> for StatusCode {
    fn from(in_code: u32) -> StatusCode {
        StatusCode(in_code as u16)
    }
}

/// Represents a HTTP header.
///
/// The easiest way to create a `Header` object is to call `parse`.
///
/// ```
/// let header: tiny_http::Header = "Content-Type: text/plain".parse().unwrap();
/// ```
#[derive(Debug, Clone)]
pub struct Header {
    pub field: HeaderField,
    pub value: AsciiString,
}

impl FromStr for Header {
    type Err = ();

    fn from_str(input: &str) -> Result<Header, ()> {
        let mut elems = input.splitn(2, ':');

        let field = elems.next();
        let value = elems.next();

        let (field, value) = match (field, value) {
            (Some(f), Some(v)) => (f, v),
            _ => return Err(())
        };

        let field = match FromStr::from_str(field) {
            Ok(f) => f,
            _ => return Err(())
        };

        let value = match AsciiStr::from_str(value.trim()) {
            Some(v) => v.to_ascii_string(),
            None => return Err(())
        };

        Ok(Header {
            field: field,
            value: value,
        })
    }
}

impl Display for Header {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "{}: {}", self.field, self.value.as_str())
    }
}

/// Field of a header (eg. `Content-Type`, `Content-Length`, etc.)
///
/// Comparaison between two `HeaderField`s ignores case.
#[derive(Debug, Clone)]
pub struct HeaderField(AsciiString);

impl HeaderField {
    pub fn as_str<'a>(&'a self) -> &'a AsciiStr {
        match self { &HeaderField(ref s) => s }
    }

    pub fn equiv(&self, other: &'static str) -> bool {
        other.eq_ignore_ascii_case(self.as_str().as_str())
    }
}

impl FromStr for HeaderField {
    type Err = ();

    fn from_str(s: &str) -> Result<HeaderField, ()> {
        AsciiStr::from_str(s.trim()).map(|s| HeaderField(s.to_ascii_string())).ok_or(())
    }
}

impl Display for HeaderField {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        let method = self.as_str();
        write!(formatter, "{}", method.as_str())
    }
}

impl PartialEq for HeaderField {
    fn eq(&self, other: &HeaderField) -> bool {
        self.as_str().eq_ignore_ascii_case(other.as_str())
    }
}

impl Eq for HeaderField {}

/// HTTP method (eg. `GET`, `POST`, etc.)
///
/// The user chooses the method he wants.
///
/// Comparaison between two `Method`s ignores case.
#[derive(Debug, Clone)]
pub struct Method(AsciiString);

impl Method {
    fn as_str(&self) -> &AsciiStr {
        match self { &Method(ref s) => s }
    }

    pub fn equiv(&self, other: &'static str) -> bool {
        other.eq_ignore_ascii_case(self.as_str().as_str())
    }
}

impl FromStr for Method {
    type Err = ();

    fn from_str(s: &str) -> Result<Method, ()> {
        <AsciiString as FromStr>::from_str(s).map(|s| Method(s))
    }
}

impl Display for Method {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        write!(formatter, "{}", self.0)
    }
}

impl PartialEq for Method {
    fn eq(&self, other: &Method) -> bool {
        self.0.eq_ignore_ascii_case(&other.0)
    }
}

impl Eq for Method {}

/// HTTP version (usually 1.0 or 1.1).
#[derive(Debug, Clone, PartialEq, Eq, Ord)]
pub struct HTTPVersion(pub u8, pub u8);

impl Display for HTTPVersion {
    fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
        let (major, minor) = match self { &HTTPVersion(m, n) => (m, n) };
        write!(formatter, "{}.{}", major, minor)
    }
}

impl PartialOrd for HTTPVersion {
    fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
        let (my_major, my_minor) = match self { &HTTPVersion(m, n) => (m, n) };
        let (other_major, other_minor) = match other { &HTTPVersion(m, n) => (m, n) };

        if my_major != other_major {
            return my_major.partial_cmp(&other_major)
        }

        my_minor.partial_cmp(&other_minor)
    }
}

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

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

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

        assert!("hello world".parse::<Header>().is_err());
    }

    #[test]
    fn test_parse_header_with_doublecolon() {
        let header: Header = "Time: 20: 34".parse().unwrap();

        assert!(header.field.equiv(&"time"));
        assert!(header.value.as_str() == "20: 34");
    }
}
<|endoftext|>


<commit_before>extern crate crypto;
extern crate rustc_serialize as serialize;
extern crate rand;

use std::iter::repeat;
use std::fs;
use std::io;
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::fs::PermissionsExt;

use self::crypto::salsa20::Salsa20;
use self::crypto::symmetriccipher::SynchronousStreamCipher;
use self::serialize::base64;
use self::serialize::base64::{FromBase64, ToBase64};
use self::rand::{OsRng, Rng};

pub const DEFAULT_LENGTH: &'static str = "32";
const SALT_LENGTH: usize = 24;
const KEY_LENGTH: usize = 32;

pub fn generate_password(key: &[u8], meat: Vec<u8>, salt: Vec<u8>, i: usize) -> Vec<u8> {
    let mut cipher = Salsa20::new_xsalsa20(&key, &salt);
    let mut buf: Vec<u8> = repeat(0).take(i).collect();
    cipher.process(&meat, &mut buf);
    buf
}

pub fn generate_salt() -> Vec<u8> {
    let mut rng = OsRng::new().expect("OsRng init failed");
    rng.gen_iter::<u8>().take(SALT_LENGTH).collect()
}

pub fn generate_meat(i: usize) -> Vec<u8> {
    let mut rng = OsRng::new().expect("OsRng init failed");
    rng.gen_iter::<u8>().take(i).collect()
}

pub fn load_or_create_key(filename: &str) -> Vec<u8> {
    match load_file(filename) {
        Ok(s) => s.from_base64().expect("Key base64 decoding failed"),
        Err(_) => {
            println!("Creating a new key in {}", filename);
            let mut rng = OsRng::new().expect("OsRng init failed");
            let new_key: Vec<u8> = rng.gen_iter::<u8>().take(KEY_LENGTH).collect();
            let key_base64 = new_key.to_base64(base64::STANDARD);
            save_data(&key_base64, filename);
            set_file_perms(filename, 0o400);
            new_key
        }
    }
}

pub fn create_data_dir(data_dir: &str) {
    fs::create_dir_all(data_dir.to_string())
        .expect(&format!("Creating data directory {} failed", data_dir));
    set_file_perms(&data_dir, 0o700);
}

pub fn load_file(path: &str) -> Result<String, io::Error> {
    let mut f = try!(File::open(path));
    let mut s = String::new();
    try!(f.read_to_string(&mut s));
    Ok(s)
}

pub fn save_data(data: &str, filename: &str) {
    let mut f = File::create(filename).unwrap();
    f.write_all(data.as_bytes()).expect("Data file write failed");
    f.write_all(b"\n").expect("Newline write failed!?");
    f.sync_all().expect("Sync failed");
}

pub fn set_file_perms(filename: &str, mode: u32) {
    let mut perms = fs::metadata(filename).expect("Gettings perms failed").permissions();
    perms.set_mode(mode);
    fs::set_permissions(filename, perms).expect("Setting permission failed");
}

<commit_msg>Sillf test for generates<commit_after>extern crate crypto;
extern crate rustc_serialize as serialize;
extern crate rand;

use std::iter::repeat;
use std::fs;
use std::io;
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::fs::PermissionsExt;

use self::crypto::salsa20::Salsa20;
use self::crypto::symmetriccipher::SynchronousStreamCipher;
use self::serialize::base64;
use self::serialize::base64::{FromBase64, ToBase64};
use self::rand::{OsRng, Rng};

pub const DEFAULT_LENGTH: &'static str = "32";
const SALT_LENGTH: usize = 24;
const KEY_LENGTH: usize = 32;

pub fn generate_password(key: &[u8], meat: Vec<u8>, salt: Vec<u8>, i: usize) -> Vec<u8> {
    let mut cipher = Salsa20::new_xsalsa20(&key, &salt);
    let mut buf: Vec<u8> = repeat(0).take(i).collect();
    cipher.process(&meat, &mut buf);
    buf
}

pub fn generate_salt() -> Vec<u8> {
    let mut rng = OsRng::new().expect("OsRng init failed");
    rng.gen_iter::<u8>().take(SALT_LENGTH).collect()
}

pub fn generate_meat(i: usize) -> Vec<u8> {
    let mut rng = OsRng::new().expect("OsRng init failed");
    rng.gen_iter::<u8>().take(i).collect()
}

pub fn load_or_create_key(filename: &str) -> Vec<u8> {
    match load_file(filename) {
        Ok(s) => s.from_base64().expect("Key base64 decoding failed"),
        Err(_) => {
            println!("Creating a new key in {}", filename);
            let mut rng = OsRng::new().expect("OsRng init failed");
            let new_key: Vec<u8> = rng.gen_iter::<u8>().take(KEY_LENGTH).collect();
            let key_base64 = new_key.to_base64(base64::STANDARD);
            save_data(&key_base64, filename);
            set_file_perms(filename, 0o400);
            new_key
        }
    }
}

pub fn create_data_dir(data_dir: &str) {
    fs::create_dir_all(data_dir.to_string())
        .expect(&format!("Creating data directory {} failed", data_dir));
    set_file_perms(&data_dir, 0o700);
}

pub fn load_file(path: &str) -> Result<String, io::Error> {
    let mut f = try!(File::open(path));
    let mut s = String::new();
    try!(f.read_to_string(&mut s));
    Ok(s)
}

pub fn save_data(data: &str, filename: &str) {
    let mut f = File::create(filename).unwrap();
    f.write_all(data.as_bytes()).expect("Data file write failed");
    f.write_all(b"\n").expect("Newline write failed!?");
    f.sync_all().expect("Sync failed");
}

pub fn set_file_perms(filename: &str, mode: u32) {
    let mut perms = fs::metadata(filename).expect("Gettings perms failed").permissions();
    perms.set_mode(mode);
    fs::set_permissions(filename, perms).expect("Setting permission failed");
}

#[test]
fn test_generate_salt() {
    let salt1 = generate_salt();
    let salt2 = generate_salt();
    assert!(salt1 != salt2);
}

#[test]
fn test_generate_meat() {
    let meat1 = generate_meat(8);
    let meat2 = generate_meat(8);
    assert!(meat1 != meat2);
    assert_eq!(8, meat1.len());
    assert_eq!(8, meat2.len());
}

#[test]
fn test_generate_password() {
    let pass1 = generate_password(&[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32], vec!(1, 2), vec!(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24), 2);
    assert_eq!(vec!(230, 6), pass1);

}
<|endoftext|>