<commit_before>use std::io::{BufRead, BufReader, Write};
use std::net::{Shutdown, TcpStream};
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;

// use command::Command;
use message::Message;
use server::Server;

// use command::parse_command;

extern crate time;

pub fn create_client(stream: TcpStream, server: &Arc<Mutex<Server>>) {
    println!("New client");
    welcome_user(clone_stream(&stream));
    let username = choose_username(clone_stream(&stream), server);
    display_instructions(clone_stream(&stream));
    chat(stream, server, username);
}

fn welcome_user(stream: TcpStream) {
    let mut stream = stream;
    let welcome_msg = "Welcome to Smazy\nPlease enter a username:\n".to_string();
    stream.write(&welcome_msg.into_bytes()).expect("Error writing to stream");
    stream.flush().unwrap();
}

fn display_instructions(stream: TcpStream) {
    let mut stream = stream;
    let instructions = "Instructions:\nThis will be filled in later\n".to_string();
    stream.write(&instructions.into_bytes()).expect("Error writing to stream");
    stream.flush().unwrap();
}

fn choose_username(stream: TcpStream, server: &Arc<Mutex<Server>>) -> String {
    let mut reader = BufReader::new(clone_stream(&stream));
    let mut username = "".to_string();

    loop {
        match reader.read_line(&mut username) {
            Ok(_) => {
                username = username.trim().to_string().to_lowercase();

                if server.lock().unwrap().add_user(username.to_string()) {
                    break
                }
                else {
                    let mut stream = clone_stream(&stream);
                    let invalid_msg = "Invalid username. Please try again.\n".to_string();
                    stream.write(&invalid_msg.into_bytes()).expect("Error writing to stream");
                    username = "".to_string();
                }
            },
            Err(e) => println!("{}", e)
        }
    }
    username.to_string()
}

fn choose_chatroom(stream: TcpStream, server: &Arc<Mutex<Server>>, sndr: Sender<String>) -> usize {
    // Display rooms and give option to create new room
    server.lock().unwrap().display_rooms(clone_stream(&stream));
    // If create new room, call server.create_room
    // Call server.join_room
    unimplemented!();
}

fn chat(stream: TcpStream, server: &Arc<Mutex<Server>>, username: String) {
    let (sndr, rcvr) = channel();

    let room_num = choose_chatroom(clone_stream(&stream), server, sndr);

    // Send messages
    {
        let username = username.to_string();
        let reader = BufReader::new(clone_stream(&stream));
        let server = server.clone();

        thread::spawn(move|| {
            let mut lines = reader.lines();
            while let Some(Ok(line)) = lines.next() {
                if line.len() > 0 {
                    let msg = Message::new(time::now().asctime().to_string(),
                                           username.to_string(),
                                           room_num,
                                           line.to_string());
                    server.lock().unwrap().send_message(msg);
                }
            }
        });
    }

    // Receive Messages
    {
        thread::spawn(move|| {
            loop {
                match rcvr.recv() {
                    Ok(msg) => {
                        receive_message(clone_stream(&stream), msg);
                    },

                    Err(e) => {
                        println!("Error receiving message {}", e);
                        break;
                    }
                }
            }
        });
    }
}

fn receive_message(stream: TcpStream, msg: String) {
    let mut stream = stream;
    stream.write(&msg.into_bytes()).expect("Error writing to stream");
}

fn clone_stream(stream: &TcpStream) -> TcpStream {
    stream.try_clone().expect("Error cloning stream")
}

#[cfg(test)]
mod client_tests {

}
<commit_msg>First go at writing choose_chatroom<commit_after>use std::io::{BufRead, BufReader, Write};
use std::net::{Shutdown, TcpStream};
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;

// use command::Command;
use message::Message;
use server::Server;

// use command::parse_command;

extern crate time;

pub fn create_client(stream: TcpStream, server: &Arc<Mutex<Server>>) {
    println!("New client");
    welcome_user(clone_stream(&stream));
    let username = choose_username(clone_stream(&stream), server);
    display_instructions(clone_stream(&stream));
    chat(stream, server, username);
}

fn welcome_user(stream: TcpStream) {
    let mut stream = stream;
    let welcome_msg = "Welcome to Smazy\nPlease enter a username:\n".to_string();
    stream.write(&welcome_msg.into_bytes()).expect("Error writing to stream");
    stream.flush().unwrap();
}

fn display_instructions(stream: TcpStream) {
    let mut stream = stream;
    let instructions = "Instructions:\nThis will be filled in later\n".to_string();
    stream.write(&instructions.into_bytes()).expect("Error writing to stream");
    stream.flush().unwrap();
}

fn choose_username(stream: TcpStream, server: &Arc<Mutex<Server>>) -> String {
    let mut reader = BufReader::new(clone_stream(&stream));
    let mut username = "".to_string();

    loop {
        match reader.read_line(&mut username) {
            Ok(_) => {
                username = username.trim().to_string().to_lowercase();

                if server.lock().unwrap().add_user(username.to_string()) {
                    break
                }
                else {
                    let mut stream = clone_stream(&stream);
                    let invalid_msg = "Invalid username. Please try again.\n".to_string();
                    stream.write(&invalid_msg.into_bytes()).expect("Error writing to stream");
                    username = "".to_string();
                }
            },
            Err(e) => println!("{}", e)
        }
    }
    username.to_string()
}

fn choose_chatroom(stream: TcpStream, server: &Arc<Mutex<Server>>, username: String, sndr: Sender<String>) -> usize {
    // Display rooms and give option to create new room
    let mut stream = stream;
    let msg = "Available rooms:\n".to_string();
    stream.write(&msg.into_bytes()).expect("Error writing to stream");

    server.lock().unwrap().display_rooms(clone_stream(&stream));

    let msg = "\nEnter the room number of the room you wish to join, or type \"new\".\n".to_string();
    stream.write(&msg.into_bytes()).expect("Error writing to stream");

    // If create new room, call server.create_room
    let mut reader = BufReader::new(clone_stream(&stream));
    let mut choice = "".to_string();
    loop {
        match reader.read_line(&mut choice) {
            Ok(_) => {
                choice = choice.trim().to_string().to_lowercase();

                if choice == "new".to_string() {
                    let msg = "Enter the name of your room: ".to_string();
                    stream.write(&msg.into_bytes()).expect("Error writing to stream");
                    let mut room_name = "".to_string();
                    if let Ok(_) = reader.read_line(&mut room_name) {
                        let mut server = server.lock().unwrap();
                        let room_num = server.create_room(room_name.to_string());
                        server.join_room(room_num, username.to_string(), sndr);
                        return room_num;
                    }
                }
                else if let Ok(room_num) = choice.parse() {
                    server.lock().unwrap().join_room(room_num, username.to_string(), sndr);
                    return room_num;
                }
                else {
                    let msg = "Try again\n".to_string();
                    stream.write(&msg.into_bytes()).expect("Error writing to stream");
                }

            },
            Err(e) => println!("{}", e)
        }
    }
}

fn chat(stream: TcpStream, server: &Arc<Mutex<Server>>, username: String) {
    let (sndr, rcvr) = channel();

    let room_num = choose_chatroom(clone_stream(&stream), server, username.to_string(), sndr);

    // Send messages
    {
        let username = username.to_string();
        let reader = BufReader::new(clone_stream(&stream));
        let server = server.clone();

        thread::spawn(move|| {
            let mut lines = reader.lines();
            while let Some(Ok(line)) = lines.next() {
                if line.len() > 0 {
                    let msg = Message::new(time::now().asctime().to_string(),
                                           username.to_string(),
                                           room_num,
                                           line.to_string());
                    server.lock().unwrap().send_message(msg);
                }
            }
        });
    }

    // Receive Messages
    {
        thread::spawn(move|| {
            loop {
                match rcvr.recv() {
                    Ok(msg) => {
                        receive_message(clone_stream(&stream), msg);
                    },

                    Err(e) => {
                        println!("Error receiving message {}", e);
                        break;
                    }
                }
            }
        });
    }
}

fn receive_message(stream: TcpStream, msg: String) {
    let mut stream = stream;
    stream.write(&msg.into_bytes()).expect("Error writing to stream");
}

fn clone_stream(stream: &TcpStream) -> TcpStream {
    stream.try_clone().expect("Error cloning stream")
}

#[cfg(test)]
mod client_tests {

}
<|endoftext|>


<commit_before>use std::io::{Error, ErrorKind, Result};
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::time::Duration;
use std::thread;
use std::sync::mpsc;
use url::{SchemeType, UrlParser};
use num;
use rand::{random, thread_rng, Rng};
use message::packet::{Packet, ObserveOption};
use message::header::MessageType;
use message::response::{CoAPResponse, Status};
use message::request::{CoAPRequest, Method};
use message::IsMessage;
use regex::Regex;

const DEFAULT_RECEIVE_TIMEOUT: u64 = 1; // 1s

enum ObserveMessage {
    Terminate,
}

pub struct CoAPClient {
    socket: UdpSocket,
    peer_addr: SocketAddr,
    observe_sender: Option<mpsc::Sender<ObserveMessage>>,
    observe_thread: Option<thread::JoinHandle<()>>,
}

impl CoAPClient {
    /// Create a CoAP client with the specific source and peer address.
    pub fn new_with_specific_source<A: ToSocketAddrs, B: ToSocketAddrs>(
        bind_addr: A,
        peer_addr: B,
    ) -> Result<CoAPClient> {
        peer_addr
            .to_socket_addrs()
            .and_then(|mut iter| match iter.next() {
                Some(paddr) => UdpSocket::bind(bind_addr).and_then(|s| {
                    s.set_read_timeout(Some(Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0)))
                        .and_then(|_| {
                            Ok(CoAPClient {
                                socket: s,
                                peer_addr: paddr,
                                observe_sender: None,
                                observe_thread: None,
                            })
                        })
                }),
                None => Err(Error::new(ErrorKind::Other, "no address")),
            })
    }

    /// Create a CoAP client with the peer address.
    pub fn new<A: ToSocketAddrs>(addr: A) -> Result<CoAPClient> {
        addr.to_socket_addrs()
            .and_then(|mut iter| match iter.next() {
                Some(SocketAddr::V4(_)) => Self::new_with_specific_source("0.0.0.0:0", addr),
                Some(SocketAddr::V6(_)) => Self::new_with_specific_source(":::0", addr),
                None => Err(Error::new(ErrorKind::Other, "no address")),
            })
    }

    /// Execute a get request
    pub fn get(url: &str) -> Result<CoAPResponse> {
        Self::get_with_timeout(url, Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0))
    }

    /// Execute a get request with the coap url and a specific timeout.
    pub fn get_with_timeout(url: &str, timeout: Duration) -> Result<CoAPResponse> {
        let (domain, port, path) = Self::parse_coap_url(url)?;

        let mut packet = CoAPRequest::new();
        packet.set_path(path.as_str());

        let client = Self::new((domain.as_str(), port))?;
        client.send(&packet)?;

        client.set_receive_timeout(Some(timeout))?;
        match client.receive() {
            Ok(receive_packet) => Ok(receive_packet),
            Err(e) => Err(e),
        }
    }

    /// Observe a resource with the handler
    pub fn observe<H: FnMut(Packet) + Send + 'static>(&mut self, resource_path: &str, mut handler: H) -> Result<()> {
        // TODO: support observe multi resources at the same time
        let mut message_id: u16 = 0;
        let mut register_packet = CoAPRequest::new();
        register_packet.set_observe(vec![ObserveOption::Register as u8]);
        register_packet.set_message_id(Self::gen_message_id(&mut message_id));
        register_packet.set_path(resource_path);

        self.send(&register_packet)?;

        self.set_receive_timeout(Some(Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0)))?;
        let response = self.receive()?;
        if *response.get_status() != Status::Content {
            return Err(Error::new(ErrorKind::NotFound, "the resource not found"));
        }

        handler(response.message);

        let socket;
        match self.socket.try_clone() {
            Ok(good_socket) => socket = good_socket,
            Err(_) => return Err(Error::new(ErrorKind::Other, "network error")),
        }
        let peer_addr = self.peer_addr.clone();
        let (observe_sender, observe_receiver) = mpsc::channel();
        let observe_path = String::from(resource_path);

        let observe_thread = thread::spawn(move || loop {
            match Self::receive_from_socket(&socket) {
                Ok(packet) => {
                    let receive_packet = CoAPRequest::from_packet(packet, &peer_addr);

                    handler(receive_packet.message);

                    if let Some(response) = receive_packet.response {
                        let mut packet = Packet::new();
                        packet.header.set_type(response.message.header.get_type());
                        packet.header.set_message_id(response.message.header.get_message_id());
                        packet.set_token(response.message.get_token().clone());

                        match Self::send_with_socket(&socket, &peer_addr, &packet) {
                            Ok(_) => (),
                            Err(e) => {
                                warn!("reply ack failed {}", e)
                            }
                        }
                    }
                },
                Err(e) => {
                    match e.kind() {
                        ErrorKind::WouldBlock => (),                          // timeout
                        _ => warn!("observe failed {:?}", e),
                    }
                },
            };

            match observe_receiver.try_recv() {
                Ok(ObserveMessage::Terminate) => {
                    let mut deregister_packet = CoAPRequest::new();
                    deregister_packet.set_message_id(Self::gen_message_id(&mut message_id));
                    deregister_packet.set_observe(vec![ObserveOption::Deregister as u8]);
                    deregister_packet.set_path(observe_path.as_str());

                    Self::send_with_socket(&socket, &peer_addr, &deregister_packet.message).unwrap();
                    Self::receive_from_socket(&socket).unwrap();
                    break;
                },
                _ => continue,
            }
        });
        self.observe_sender = Some(observe_sender);
        self.observe_thread = Some(observe_thread);

        return Ok(());
    }

    /// Stop observing
    pub fn unobserve(&mut self) {
        match self.observe_sender.take() {
            Some(ref sender) => {
                sender.send(ObserveMessage::Terminate).unwrap();

                self.observe_thread.take().map(|g| g.join().unwrap());
            }
            _ => {}
        }
    }

    /// Execute a request with the coap url and a specific timeout. Default timeout is 5s.
    #[deprecated(since = "0.6.0", note = "please use `get_with_timeout` instead")]
    pub fn request_with_timeout(url: &str, timeout: Option<Duration>) -> Result<CoAPResponse> {
        let (domain, port, path) = Self::parse_coap_url(url)?;

        let mut packet = CoAPRequest::new();
        packet.set_version(1);
        packet.set_type(MessageType::Confirmable);
        packet.set_method(Method::Get);

        let message_id = thread_rng().gen_range(0, num::pow(2u32, 16)) as u16;
        packet.set_message_id(message_id);

        let mut token: Vec<u8> = vec![1, 1, 1, 1];
        for x in token.iter_mut() {
            *x = random()
        }
        packet.set_token(token.clone());
        packet.set_path(path.as_str());

        let client = try!(Self::new((domain.as_str(), port)));
        try!(client.send(&packet));

        try!(client.set_receive_timeout(timeout));
        match client.receive() {
            Ok(receive_packet) => {
                if receive_packet.get_message_id() == message_id
                    && *receive_packet.get_token() == token
                {
                    return Ok(receive_packet);
                } else {
                    return Err(Error::new(ErrorKind::Other, "receive invalid data"));
                }
            }
            Err(e) => Err(e),
        }
    }

    /// Execute a request with the coap url.
    #[deprecated(since = "0.6.0", note = "please use `get` instead")]
    pub fn request(url: &str) -> Result<CoAPResponse> {
        Self::get_with_timeout(url, Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0))
    }

    /// Execute a request.
    pub fn send(&self, request: &CoAPRequest) -> Result<()> {
        Self::send_with_socket(&self.socket, &self.peer_addr, &request.message)
    }

    /// Receive a response.
    pub fn receive(&self) -> Result<CoAPResponse> {
        let packet = Self::receive_from_socket(&self.socket)?;
        Ok(CoAPResponse { message: packet })
    }

    /// Set the receive timeout.
    pub fn set_receive_timeout(&self, dur: Option<Duration>) -> Result<()> {
        self.socket.set_read_timeout(dur)
    }

    fn send_with_socket(socket: &UdpSocket, peer_addr: &SocketAddr, message: &Packet) -> Result<()> {
        match message.to_bytes() {
            Ok(bytes) => {
                let size = try!(socket.send_to(&bytes[..], peer_addr));
                if size == bytes.len() {
                    Ok(())
                } else {
                    Err(Error::new(ErrorKind::Other, "send length error"))
                }
            }
            Err(_) => Err(Error::new(ErrorKind::InvalidInput, "packet error")),
        }
    }

    fn receive_from_socket(socket: &UdpSocket) -> Result<Packet> {
        let mut buf = [0; 1500];

        let (nread, _src) = try!(socket.recv_from(&mut buf));
        match Packet::from_bytes(&buf[..nread]) {
            Ok(packet) => Ok(packet),
            Err(_) => Err(Error::new(ErrorKind::InvalidInput, "packet error")),
        }
    }

    fn parse_coap_url(url: &str) -> Result<(String, u16, String)> {
        let mut url_parser = UrlParser::new();
        url_parser.scheme_type_mapper(Self::coap_scheme_type_mapper);

        let url_params = match url_parser.parse(url) {
            Ok(url_params) => url_params,
            Err(_) => return Err(Error::new(ErrorKind::InvalidInput, "url error")),
        };

        let host = match url_params.host() {
            Some(h) => h,
            None => return Err(Error::new(ErrorKind::InvalidInput, "host error")),
        };
        let host = host.to_string();
        let host = Regex::new(r"^\[(.*?)]$").unwrap().replace(&host, "$1").to_string();

        let port = match url_params.port_or_default() {
            Some(p) => p,
            None => return Err(Error::new(ErrorKind::InvalidInput, "port error")),
        };

        let path = match url_params.serialize_path() {
            Some(path) => path,
            None => return Err(Error::new(ErrorKind::InvalidInput, "uri error")),
        };

        return Ok((host.to_string(), port, path));
    }

    fn coap_scheme_type_mapper(scheme: &str) -> SchemeType {
        match scheme {
            "coap" => SchemeType::Relative(5683),
            _ => SchemeType::NonRelative,
        }
    }

    fn gen_message_id(message_id: &mut u16) -> u16 {
        (*message_id) += 1;
        return *message_id;
    }
}

impl Drop for CoAPClient {
    fn drop(&mut self) {
        self.unobserve();
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::time::Duration;
    use std::io::ErrorKind;
    use message::request::CoAPRequest;
    use message::response::CoAPResponse;
    use server::CoAPServer;

    #[test]
    fn test_get_error_url() {
        assert!(CoAPClient::get("http://127.0.0.1").is_err());
        assert!(CoAPClient::get("coap://127.0.0.").is_err());
        assert!(CoAPClient::get("127.0.0.1").is_err());
    }

    fn request_handler(_: CoAPRequest) -> Option<CoAPResponse> {
        None
    }

    #[test]
    fn test_get_timeout() {
        let mut server = CoAPServer::new("127.0.0.1:5684").unwrap();
        server.handle(request_handler).unwrap();

        let error = CoAPClient::get_with_timeout("coap://127.0.0.1:5684/Rust", Duration::new(1, 0))
            .unwrap_err();
        if cfg!(windows) {
            assert_eq!(error.kind(), ErrorKind::TimedOut);
        } else {
            assert_eq!(error.kind(), ErrorKind::WouldBlock);
        }
    }
}
<commit_msg>Tests for the IPv6 fix.<commit_after>use std::io::{Error, ErrorKind, Result};
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::time::Duration;
use std::thread;
use std::sync::mpsc;
use url::{SchemeType, UrlParser};
use num;
use rand::{random, thread_rng, Rng};
use message::packet::{Packet, ObserveOption};
use message::header::MessageType;
use message::response::{CoAPResponse, Status};
use message::request::{CoAPRequest, Method};
use message::IsMessage;
use regex::Regex;

const DEFAULT_RECEIVE_TIMEOUT: u64 = 1; // 1s

enum ObserveMessage {
    Terminate,
}

pub struct CoAPClient {
    socket: UdpSocket,
    peer_addr: SocketAddr,
    observe_sender: Option<mpsc::Sender<ObserveMessage>>,
    observe_thread: Option<thread::JoinHandle<()>>,
}

impl CoAPClient {
    /// Create a CoAP client with the specific source and peer address.
    pub fn new_with_specific_source<A: ToSocketAddrs, B: ToSocketAddrs>(
        bind_addr: A,
        peer_addr: B,
    ) -> Result<CoAPClient> {
        peer_addr
            .to_socket_addrs()
            .and_then(|mut iter| match iter.next() {
                Some(paddr) => UdpSocket::bind(bind_addr).and_then(|s| {
                    s.set_read_timeout(Some(Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0)))
                        .and_then(|_| {
                            Ok(CoAPClient {
                                socket: s,
                                peer_addr: paddr,
                                observe_sender: None,
                                observe_thread: None,
                            })
                        })
                }),
                None => Err(Error::new(ErrorKind::Other, "no address")),
            })
    }

    /// Create a CoAP client with the peer address.
    pub fn new<A: ToSocketAddrs>(addr: A) -> Result<CoAPClient> {
        addr.to_socket_addrs()
            .and_then(|mut iter| match iter.next() {
                Some(SocketAddr::V4(_)) => Self::new_with_specific_source("0.0.0.0:0", addr),
                Some(SocketAddr::V6(_)) => Self::new_with_specific_source(":::0", addr),
                None => Err(Error::new(ErrorKind::Other, "no address")),
            })
    }

    /// Execute a get request
    pub fn get(url: &str) -> Result<CoAPResponse> {
        Self::get_with_timeout(url, Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0))
    }

    /// Execute a get request with the coap url and a specific timeout.
    pub fn get_with_timeout(url: &str, timeout: Duration) -> Result<CoAPResponse> {
        let (domain, port, path) = Self::parse_coap_url(url)?;

        let mut packet = CoAPRequest::new();
        packet.set_path(path.as_str());

        let client = Self::new((domain.as_str(), port))?;
        client.send(&packet)?;

        client.set_receive_timeout(Some(timeout))?;
        match client.receive() {
            Ok(receive_packet) => Ok(receive_packet),
            Err(e) => Err(e),
        }
    }

    /// Observe a resource with the handler
    pub fn observe<H: FnMut(Packet) + Send + 'static>(&mut self, resource_path: &str, mut handler: H) -> Result<()> {
        // TODO: support observe multi resources at the same time
        let mut message_id: u16 = 0;
        let mut register_packet = CoAPRequest::new();
        register_packet.set_observe(vec![ObserveOption::Register as u8]);
        register_packet.set_message_id(Self::gen_message_id(&mut message_id));
        register_packet.set_path(resource_path);

        self.send(&register_packet)?;

        self.set_receive_timeout(Some(Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0)))?;
        let response = self.receive()?;
        if *response.get_status() != Status::Content {
            return Err(Error::new(ErrorKind::NotFound, "the resource not found"));
        }

        handler(response.message);

        let socket;
        match self.socket.try_clone() {
            Ok(good_socket) => socket = good_socket,
            Err(_) => return Err(Error::new(ErrorKind::Other, "network error")),
        }
        let peer_addr = self.peer_addr.clone();
        let (observe_sender, observe_receiver) = mpsc::channel();
        let observe_path = String::from(resource_path);

        let observe_thread = thread::spawn(move || loop {
            match Self::receive_from_socket(&socket) {
                Ok(packet) => {
                    let receive_packet = CoAPRequest::from_packet(packet, &peer_addr);

                    handler(receive_packet.message);

                    if let Some(response) = receive_packet.response {
                        let mut packet = Packet::new();
                        packet.header.set_type(response.message.header.get_type());
                        packet.header.set_message_id(response.message.header.get_message_id());
                        packet.set_token(response.message.get_token().clone());

                        match Self::send_with_socket(&socket, &peer_addr, &packet) {
                            Ok(_) => (),
                            Err(e) => {
                                warn!("reply ack failed {}", e)
                            }
                        }
                    }
                },
                Err(e) => {
                    match e.kind() {
                        ErrorKind::WouldBlock => (),                          // timeout
                        _ => warn!("observe failed {:?}", e),
                    }
                },
            };

            match observe_receiver.try_recv() {
                Ok(ObserveMessage::Terminate) => {
                    let mut deregister_packet = CoAPRequest::new();
                    deregister_packet.set_message_id(Self::gen_message_id(&mut message_id));
                    deregister_packet.set_observe(vec![ObserveOption::Deregister as u8]);
                    deregister_packet.set_path(observe_path.as_str());

                    Self::send_with_socket(&socket, &peer_addr, &deregister_packet.message).unwrap();
                    Self::receive_from_socket(&socket).unwrap();
                    break;
                },
                _ => continue,
            }
        });
        self.observe_sender = Some(observe_sender);
        self.observe_thread = Some(observe_thread);

        return Ok(());
    }

    /// Stop observing
    pub fn unobserve(&mut self) {
        match self.observe_sender.take() {
            Some(ref sender) => {
                sender.send(ObserveMessage::Terminate).unwrap();

                self.observe_thread.take().map(|g| g.join().unwrap());
            }
            _ => {}
        }
    }

    /// Execute a request with the coap url and a specific timeout. Default timeout is 5s.
    #[deprecated(since = "0.6.0", note = "please use `get_with_timeout` instead")]
    pub fn request_with_timeout(url: &str, timeout: Option<Duration>) -> Result<CoAPResponse> {
        let (domain, port, path) = Self::parse_coap_url(url)?;

        let mut packet = CoAPRequest::new();
        packet.set_version(1);
        packet.set_type(MessageType::Confirmable);
        packet.set_method(Method::Get);

        let message_id = thread_rng().gen_range(0, num::pow(2u32, 16)) as u16;
        packet.set_message_id(message_id);

        let mut token: Vec<u8> = vec![1, 1, 1, 1];
        for x in token.iter_mut() {
            *x = random()
        }
        packet.set_token(token.clone());
        packet.set_path(path.as_str());

        let client = try!(Self::new((domain.as_str(), port)));
        try!(client.send(&packet));

        try!(client.set_receive_timeout(timeout));
        match client.receive() {
            Ok(receive_packet) => {
                if receive_packet.get_message_id() == message_id
                    && *receive_packet.get_token() == token
                {
                    return Ok(receive_packet);
                } else {
                    return Err(Error::new(ErrorKind::Other, "receive invalid data"));
                }
            }
            Err(e) => Err(e),
        }
    }

    /// Execute a request with the coap url.
    #[deprecated(since = "0.6.0", note = "please use `get` instead")]
    pub fn request(url: &str) -> Result<CoAPResponse> {
        Self::get_with_timeout(url, Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0))
    }

    /// Execute a request.
    pub fn send(&self, request: &CoAPRequest) -> Result<()> {
        Self::send_with_socket(&self.socket, &self.peer_addr, &request.message)
    }

    /// Receive a response.
    pub fn receive(&self) -> Result<CoAPResponse> {
        let packet = Self::receive_from_socket(&self.socket)?;
        Ok(CoAPResponse { message: packet })
    }

    /// Set the receive timeout.
    pub fn set_receive_timeout(&self, dur: Option<Duration>) -> Result<()> {
        self.socket.set_read_timeout(dur)
    }

    fn send_with_socket(socket: &UdpSocket, peer_addr: &SocketAddr, message: &Packet) -> Result<()> {
        match message.to_bytes() {
            Ok(bytes) => {
                let size = try!(socket.send_to(&bytes[..], peer_addr));
                if size == bytes.len() {
                    Ok(())
                } else {
                    Err(Error::new(ErrorKind::Other, "send length error"))
                }
            }
            Err(_) => Err(Error::new(ErrorKind::InvalidInput, "packet error")),
        }
    }

    fn receive_from_socket(socket: &UdpSocket) -> Result<Packet> {
        let mut buf = [0; 1500];

        let (nread, _src) = try!(socket.recv_from(&mut buf));
        match Packet::from_bytes(&buf[..nread]) {
            Ok(packet) => Ok(packet),
            Err(_) => Err(Error::new(ErrorKind::InvalidInput, "packet error")),
        }
    }

    fn parse_coap_url(url: &str) -> Result<(String, u16, String)> {
        let mut url_parser = UrlParser::new();
        url_parser.scheme_type_mapper(Self::coap_scheme_type_mapper);

        let url_params = match url_parser.parse(url) {
            Ok(url_params) => url_params,
            Err(_) => return Err(Error::new(ErrorKind::InvalidInput, "url error")),
        };

        let host = match url_params.host() {
            Some(h) => h,
            None => return Err(Error::new(ErrorKind::InvalidInput, "host error")),
        };
        let host = host.to_string();
        let host = Regex::new(r"^\[(.*?)]$").unwrap().replace(&host, "$1").to_string();

        let port = match url_params.port_or_default() {
            Some(p) => p,
            None => return Err(Error::new(ErrorKind::InvalidInput, "port error")),
        };

        let path = match url_params.serialize_path() {
            Some(path) => path,
            None => return Err(Error::new(ErrorKind::InvalidInput, "uri error")),
        };

        return Ok((host.to_string(), port, path));
    }

    fn coap_scheme_type_mapper(scheme: &str) -> SchemeType {
        match scheme {
            "coap" => SchemeType::Relative(5683),
            _ => SchemeType::NonRelative,
        }
    }

    fn gen_message_id(message_id: &mut u16) -> u16 {
        (*message_id) += 1;
        return *message_id;
    }
}

impl Drop for CoAPClient {
    fn drop(&mut self) {
        self.unobserve();
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::time::Duration;
    use std::io::ErrorKind;
    use message::request::CoAPRequest;
    use message::response::CoAPResponse;
    use server::CoAPServer;

    #[test]
    fn test_parse_coap_url_good_url() {
        assert!(CoAPClient::parse_coap_url("coap://127.0.0.1").is_ok());
        assert!(CoAPClient::parse_coap_url("coap://127.0.0.1:5683").is_ok());
        assert!(CoAPClient::parse_coap_url("coap://[::1]").is_ok());
        assert!(CoAPClient::parse_coap_url("coap://[::1]:5683").is_ok());
        assert!(CoAPClient::parse_coap_url("coap://[bbbb::9329:f033:f558:7418]").is_ok());
        assert!(CoAPClient::parse_coap_url("coap://[bbbb::9329:f033:f558:7418]:5683").is_ok());
    }

    #[test]
    fn test_parse_coap_url_bad_url() {
        assert!(CoAPClient::parse_coap_url("http://127.0.0.1").is_err());
        assert!(CoAPClient::parse_coap_url("coap://127.0.0.1:65536").is_err());
        assert!(CoAPClient::parse_coap_url("coap://").is_err());
        assert!(CoAPClient::parse_coap_url("coap://:5683").is_err());
        assert!(CoAPClient::parse_coap_url("127.0.0.1").is_err());
    }

    fn request_handler(_: CoAPRequest) -> Option<CoAPResponse> {
        None
    }

    #[test]
    fn test_get_timeout() {
        let mut server = CoAPServer::new("127.0.0.1:5684").unwrap();
        server.handle(request_handler).unwrap();

        let error = CoAPClient::get_with_timeout("coap://127.0.0.1:5684/Rust", Duration::new(1, 0))
            .unwrap_err();
        if cfg!(windows) {
            assert_eq!(error.kind(), ErrorKind::TimedOut);
        } else {
            assert_eq!(error.kind(), ErrorKind::WouldBlock);
        }
    }
}
<|endoftext|>


<commit_before>use crate::publish_rate_limit::PublishRateLimit;
use crate::{env, uploaders::Uploader, Env, Replica};

#[derive(Clone, Debug)]
pub struct Config {
    pub uploader: Uploader,
    pub session_key: String,
    pub gh_client_id: String,
    pub gh_client_secret: String,
    pub db_url: String,
    pub replica_db_url: Option<String>,
    pub env: Env,
    pub max_upload_size: u64,
    pub max_unpack_size: u64,
    pub mirror: Replica,
    pub api_protocol: String,
    pub publish_rate_limit: PublishRateLimit,
    pub blocked_traffic: Vec<(String, Vec<String>)>,
    pub domain_name: String,
    pub allowed_origins: Vec<String>,
}

impl Default for Config {
    /// Returns a default value for the application's config
    ///
    /// Sets the following default values:
    ///
    /// - `Config::max_upload_size`: 10MiB
    /// - `Config::api_protocol`: `https`
    ///
    /// Pulls values from the following environment variables:
    ///
    /// - `MIRROR`: Is this instance of cargo_registry a mirror of crates.io.
    /// - `HEROKU`: Is this instance of cargo_registry currently running on Heroku.
    /// - `S3_BUCKET`: The S3 bucket used to store crate files. If not present during development,
    ///    cargo_registry will fall back to a local uploader.
    /// - `S3_REGION`: The region in which the bucket was created. Optional if US standard.
    /// - `S3_ACCESS_KEY`: The access key to interact with S3. Optional if running a mirror.
    /// - `S3_SECRET_KEY`: The secret key to interact with S3. Optional if running a mirror.
    /// - `SESSION_KEY`: The key used to sign and encrypt session cookies.
    /// - `GH_CLIENT_ID`: The client ID of the associated GitHub application.
    /// - `GH_CLIENT_SECRET`: The client secret of the associated GitHub application.
    /// - `DATABASE_URL`: The URL of the postgres database to use.
    /// - `READ_ONLY_REPLICA_URL`: The URL of an optional postgres read-only replica database.
    /// - `BLOCKED_TRAFFIC`: A list of headers and environment variables to use for blocking
    ///.  traffic. See the `block_traffic` module for more documentation.
    fn default() -> Config {
        let api_protocol = String::from("https");
        let mirror = if dotenv::var("MIRROR").is_ok() {
            Replica::ReadOnlyMirror
        } else {
            Replica::Primary
        };
        let heroku = dotenv::var("HEROKU").is_ok();
        let cargo_env = if heroku {
            Env::Production
        } else {
            Env::Development
        };
        let uploader = match (cargo_env, mirror) {
            (Env::Production, Replica::Primary) => {
                // `env` panics if these vars are not set, and in production for a primary instance,
                // that's what we want since we don't want to be able to start the server if the
                // server doesn't know where to upload crates.
                Uploader::S3 {
                    bucket: s3::Bucket::new(
                        env("S3_BUCKET"),
                        dotenv::var("S3_REGION").ok(),
                        env("S3_ACCESS_KEY"),
                        env("S3_SECRET_KEY"),
                        &api_protocol,
                    ),
                    cdn: dotenv::var("S3_CDN").ok(),
                }
            }
            (Env::Production, Replica::ReadOnlyMirror) => {
                // Read-only mirrors don't need access key or secret key since by definition,
                // they'll only need to read from a bucket, not upload.
                //
                // Read-only mirrors might have access key or secret key, so use them if those
                // environment variables are set.
                //
                // Read-only mirrors definitely need bucket though, so that they know where
                // to serve crate files from.
                Uploader::S3 {
                    bucket: s3::Bucket::new(
                        env("S3_BUCKET"),
                        dotenv::var("S3_REGION").ok(),
                        dotenv::var("S3_ACCESS_KEY").unwrap_or_default(),
                        dotenv::var("S3_SECRET_KEY").unwrap_or_default(),
                        &api_protocol,
                    ),
                    cdn: dotenv::var("S3_CDN").ok(),
                }
            }
            // In Development mode, either running as a primary instance or a read-only mirror
            _ => {
                if dotenv::var("S3_BUCKET").is_ok() {
                    // If we've set the `S3_BUCKET` variable to any value, use all of the values
                    // for the related S3 environment variables and configure the app to upload to
                    // and read from S3 like production does. All values except for bucket are
                    // optional, like production read-only mirrors.
                    println!("Using S3 uploader");
                    Uploader::S3 {
                        bucket: s3::Bucket::new(
                            env("S3_BUCKET"),
                            dotenv::var("S3_REGION").ok(),
                            dotenv::var("S3_ACCESS_KEY").unwrap_or_default(),
                            dotenv::var("S3_SECRET_KEY").unwrap_or_default(),
                            &api_protocol,
                        ),
                        cdn: dotenv::var("S3_CDN").ok(),
                    }
                } else {
                    // If we don't set the `S3_BUCKET` variable, we'll use a development-only
                    // uploader that makes it possible to run and publish to a locally-running
                    // crates.io instance without needing to set up an account and a bucket in S3.
                    println!(
                        "Using local uploader, crate files will be in the local_uploads directory"
                    );
                    Uploader::Local
                }
            }
        };
        let allowed_origins = env("WEB_ALLOWED_ORIGINS")
            .split(',')
            .map(ToString::to_string)
            .collect();
        Config {
            uploader,
            session_key: env("SESSION_KEY"),
            gh_client_id: env("GH_CLIENT_ID"),
            gh_client_secret: env("GH_CLIENT_SECRET"),
            db_url: env("DATABASE_URL"),
            replica_db_url: dotenv::var("READ_ONLY_REPLICA_URL").ok(),
            env: cargo_env,
            max_upload_size: 10 * 1024 * 1024, // 10 MB default file upload size limit
            max_unpack_size: 512 * 1024 * 1024, // 512 MB max when decompressed
            mirror,
            api_protocol,
            publish_rate_limit: Default::default(),
            blocked_traffic: blocked_traffic(),
            domain_name: domain_name(),
            allowed_origins,
        }
    }
}

pub(crate) fn domain_name() -> String {
    dotenv::var("DOMAIN_NAME").unwrap_or_else(|_| "crates.io".into())
}

fn blocked_traffic() -> Vec<(String, Vec<String>)> {
    let pattern_list = dotenv::var("BLOCKED_TRAFFIC").unwrap_or_default();
    parse_traffic_patterns(&pattern_list)
        .map(|(header, value_env_var)| {
            let value_list = dotenv::var(value_env_var).unwrap_or_default();
            let values = value_list.split(',').map(String::from).collect();
            (header.into(), values)
        })
        .collect()
}

fn parse_traffic_patterns(patterns: &str) -> impl Iterator<Item = (&str, &str)> {
    patterns.split_terminator(',').map(|pattern| {
        if let Some(idx) = pattern.find('=') {
            (&pattern[..idx], &pattern[(idx + 1)..])
        } else {
            panic!(
                "BLOCKED_TRAFFIC must be in the form HEADER=VALUE_ENV_VAR, \
                 got invalid pattern {}",
                pattern
            )
        }
    })
}

#[test]
fn parse_traffic_patterns_splits_on_comma_and_looks_for_equal_sign() {
    let pattern_string_1 = "Foo=BAR,Bar=BAZ";
    let pattern_string_2 = "Baz=QUX";
    let pattern_string_3 = "";

    let patterns_1 = parse_traffic_patterns(pattern_string_1).collect::<Vec<_>>();
    assert_eq!(vec![("Foo", "BAR"), ("Bar", "BAZ")], patterns_1);

    let patterns_2 = parse_traffic_patterns(pattern_string_2).collect::<Vec<_>>();
    assert_eq!(vec![("Baz", "QUX")], patterns_2);

    let patterns_3 = parse_traffic_patterns(pattern_string_3).collect::<Vec<_>>();
    assert!(patterns_3.is_empty());
}
<commit_msg>Address new clippy suggestion<commit_after>use crate::publish_rate_limit::PublishRateLimit;
use crate::{env, uploaders::Uploader, Env, Replica};

#[derive(Clone, Debug)]
pub struct Config {
    pub uploader: Uploader,
    pub session_key: String,
    pub gh_client_id: String,
    pub gh_client_secret: String,
    pub db_url: String,
    pub replica_db_url: Option<String>,
    pub env: Env,
    pub max_upload_size: u64,
    pub max_unpack_size: u64,
    pub mirror: Replica,
    pub api_protocol: String,
    pub publish_rate_limit: PublishRateLimit,
    pub blocked_traffic: Vec<(String, Vec<String>)>,
    pub domain_name: String,
    pub allowed_origins: Vec<String>,
}

impl Default for Config {
    /// Returns a default value for the application's config
    ///
    /// Sets the following default values:
    ///
    /// - `Config::max_upload_size`: 10MiB
    /// - `Config::api_protocol`: `https`
    ///
    /// Pulls values from the following environment variables:
    ///
    /// - `MIRROR`: Is this instance of cargo_registry a mirror of crates.io.
    /// - `HEROKU`: Is this instance of cargo_registry currently running on Heroku.
    /// - `S3_BUCKET`: The S3 bucket used to store crate files. If not present during development,
    ///    cargo_registry will fall back to a local uploader.
    /// - `S3_REGION`: The region in which the bucket was created. Optional if US standard.
    /// - `S3_ACCESS_KEY`: The access key to interact with S3. Optional if running a mirror.
    /// - `S3_SECRET_KEY`: The secret key to interact with S3. Optional if running a mirror.
    /// - `SESSION_KEY`: The key used to sign and encrypt session cookies.
    /// - `GH_CLIENT_ID`: The client ID of the associated GitHub application.
    /// - `GH_CLIENT_SECRET`: The client secret of the associated GitHub application.
    /// - `DATABASE_URL`: The URL of the postgres database to use.
    /// - `READ_ONLY_REPLICA_URL`: The URL of an optional postgres read-only replica database.
    /// - `BLOCKED_TRAFFIC`: A list of headers and environment variables to use for blocking
    ///.  traffic. See the `block_traffic` module for more documentation.
    fn default() -> Config {
        let api_protocol = String::from("https");
        let mirror = if dotenv::var("MIRROR").is_ok() {
            Replica::ReadOnlyMirror
        } else {
            Replica::Primary
        };
        let heroku = dotenv::var("HEROKU").is_ok();
        let cargo_env = if heroku {
            Env::Production
        } else {
            Env::Development
        };
        let uploader = match (cargo_env, mirror) {
            (Env::Production, Replica::Primary) => {
                // `env` panics if these vars are not set, and in production for a primary instance,
                // that's what we want since we don't want to be able to start the server if the
                // server doesn't know where to upload crates.
                Uploader::S3 {
                    bucket: s3::Bucket::new(
                        env("S3_BUCKET"),
                        dotenv::var("S3_REGION").ok(),
                        env("S3_ACCESS_KEY"),
                        env("S3_SECRET_KEY"),
                        &api_protocol,
                    ),
                    cdn: dotenv::var("S3_CDN").ok(),
                }
            }
            (Env::Production, Replica::ReadOnlyMirror) => {
                // Read-only mirrors don't need access key or secret key since by definition,
                // they'll only need to read from a bucket, not upload.
                //
                // Read-only mirrors might have access key or secret key, so use them if those
                // environment variables are set.
                //
                // Read-only mirrors definitely need bucket though, so that they know where
                // to serve crate files from.
                Uploader::S3 {
                    bucket: s3::Bucket::new(
                        env("S3_BUCKET"),
                        dotenv::var("S3_REGION").ok(),
                        dotenv::var("S3_ACCESS_KEY").unwrap_or_default(),
                        dotenv::var("S3_SECRET_KEY").unwrap_or_default(),
                        &api_protocol,
                    ),
                    cdn: dotenv::var("S3_CDN").ok(),
                }
            }
            // In Development mode, either running as a primary instance or a read-only mirror
            _ => {
                if dotenv::var("S3_BUCKET").is_ok() {
                    // If we've set the `S3_BUCKET` variable to any value, use all of the values
                    // for the related S3 environment variables and configure the app to upload to
                    // and read from S3 like production does. All values except for bucket are
                    // optional, like production read-only mirrors.
                    println!("Using S3 uploader");
                    Uploader::S3 {
                        bucket: s3::Bucket::new(
                            env("S3_BUCKET"),
                            dotenv::var("S3_REGION").ok(),
                            dotenv::var("S3_ACCESS_KEY").unwrap_or_default(),
                            dotenv::var("S3_SECRET_KEY").unwrap_or_default(),
                            &api_protocol,
                        ),
                        cdn: dotenv::var("S3_CDN").ok(),
                    }
                } else {
                    // If we don't set the `S3_BUCKET` variable, we'll use a development-only
                    // uploader that makes it possible to run and publish to a locally-running
                    // crates.io instance without needing to set up an account and a bucket in S3.
                    println!(
                        "Using local uploader, crate files will be in the local_uploads directory"
                    );
                    Uploader::Local
                }
            }
        };
        let allowed_origins = env("WEB_ALLOWED_ORIGINS")
            .split(',')
            .map(ToString::to_string)
            .collect();
        Config {
            uploader,
            session_key: env("SESSION_KEY"),
            gh_client_id: env("GH_CLIENT_ID"),
            gh_client_secret: env("GH_CLIENT_SECRET"),
            db_url: env("DATABASE_URL"),
            replica_db_url: dotenv::var("READ_ONLY_REPLICA_URL").ok(),
            env: cargo_env,
            max_upload_size: 10 * 1024 * 1024, // 10 MB default file upload size limit
            max_unpack_size: 512 * 1024 * 1024, // 512 MB max when decompressed
            mirror,
            api_protocol,
            publish_rate_limit: Default::default(),
            blocked_traffic: blocked_traffic(),
            domain_name: domain_name(),
            allowed_origins,
        }
    }
}

pub(crate) fn domain_name() -> String {
    dotenv::var("DOMAIN_NAME").unwrap_or_else(|_| "crates.io".into())
}

fn blocked_traffic() -> Vec<(String, Vec<String>)> {
    let pattern_list = dotenv::var("BLOCKED_TRAFFIC").unwrap_or_default();
    parse_traffic_patterns(&pattern_list)
        .map(|(header, value_env_var)| {
            let value_list = dotenv::var(value_env_var).unwrap_or_default();
            let values = value_list.split(',').map(String::from).collect();
            (header.into(), values)
        })
        .collect()
}

fn parse_traffic_patterns(patterns: &str) -> impl Iterator<Item = (&str, &str)> {
    patterns.split_terminator(',').map(|pattern| {
        if let Some(idx) = pattern.find('=') {
            (&pattern[..idx], &pattern[(idx + 1)..])
        } else {
            panic!(
                "BLOCKED_TRAFFIC must be in the form HEADER=VALUE_ENV_VAR, \
                 got invalid pattern {}",
                pattern
            )
        }
    })
}

#[test]
fn parse_traffic_patterns_splits_on_comma_and_looks_for_equal_sign() {
    let pattern_string_1 = "Foo=BAR,Bar=BAZ";
    let pattern_string_2 = "Baz=QUX";
    let pattern_string_3 = "";

    let patterns_1 = parse_traffic_patterns(pattern_string_1).collect::<Vec<_>>();
    assert_eq!(vec![("Foo", "BAR"), ("Bar", "BAZ")], patterns_1);

    let patterns_2 = parse_traffic_patterns(pattern_string_2).collect::<Vec<_>>();
    assert_eq!(vec![("Baz", "QUX")], patterns_2);

    assert!(parse_traffic_patterns(pattern_string_3).next().is_none());
}
<|endoftext|>


<commit_before>#![allow(dead_code)]

use std::default::Default;
use std::io::Write;
use std::path::Path;
use std::fs::{File, create_dir};
use std::process::{Command, Child, Stdio};
use std::mem;
use layout::*;
use keycode::*;
use workspaces::{Workspaces, WorkspaceConfig};
use xlib_window_system::XlibWindowSystem;
use commands::{Cmd, ManageHook};
use dylib::DynamicLibrary;

pub struct Keybinding {
    pub mods: u8,
    pub key: String,
    pub cmd: Cmd,
}

pub struct WorkspaceInfo {
    pub id: usize,
    pub tag: String,
    pub screen: usize,
    pub current: bool,
    pub visible: bool,
    pub urgent: bool,
}

pub struct LogInfo {
    pub workspaces: Vec<WorkspaceInfo>,
    pub layout_name: String,
    pub window_title: String,
}

pub struct Statusbar {
    child: Option<Child>,
    executable: String,
    args: Option<Vec<String>>,
    fn_format: Box<Fn(LogInfo) -> String>,
}

impl Statusbar {
    pub fn new(executable: String,
               args: Option<Vec<String>>,
               fn_format: Box<Fn(LogInfo) -> String>)
               -> Statusbar {
        Statusbar {
            child: None,
            executable: executable,
            args: args,
            fn_format: fn_format,
        }
    }

    pub fn xmobar() -> Statusbar {
        Statusbar::new("xmobar".to_string(),
                       None,
                       Box::new(move |info: LogInfo| -> String {
            let workspaces = info.workspaces
                .iter()
                .map(|x| {
                    let (fg, bg) = if x.current {
                        ("#00ff00", "#000000")
                    } else if x.visible {
                        ("#009900", "#000000")
                    } else if x.urgent {
                        ("#ff0000", "#000000")
                    } else {
                        ("#ffffff", "#000000")
                    };
                    format!("<fc={},{}>[{}]</fc>", fg, bg, x.tag)
                })
                .collect::<Vec<String>>()
                .join(" ");

            format!("{} | {} | {}\n",
                    workspaces,
                    info.layout_name,
                    info.window_title)
        }))
    }

    pub fn start(&mut self) {
        match self.child {
            Some(_) => warn!("'{}' is already running", self.executable),
            None => {
                let mut cmd = Command::new(self.executable.clone());
                if self.args.is_some() {
                    cmd.args(self.args.clone().unwrap().as_slice());
                }
                match cmd.stdin(Stdio::piped()).spawn() {
                    Ok(child) => self.child = Some(child),
                    Err(err) => error!("failed to execute '{}': {}", self.executable, err),
                }
            }
        }
    }

    pub fn update(&mut self, ws: &XlibWindowSystem, workspaces: &Workspaces) {
        if self.child.is_none() {
            return;
        }

        let output = (self.fn_format)(LogInfo {
            workspaces: workspaces.all()
                .iter()
                .enumerate()
                .map(|(i, x)| {
                    WorkspaceInfo {
                        id: i,
                        tag: x.get_tag(),
                        screen: 0,
                        current: i == workspaces.get_index(),
                        visible: x.is_visible(),
                        urgent: x.is_urgent(),
                    }
                })
                .collect(),
            layout_name: workspaces.current().get_layout().name(),
            window_title: ws.get_window_title(workspaces.current().focused_window()),
        });

        let mut stdin = self.child.as_mut().unwrap().stdin.as_mut().unwrap();
        stdin.write_all(output.as_bytes()).ok();
    }
}

pub struct Config {
    lib: Option<DynamicLibrary>,
    pub workspaces: Vec<WorkspaceConfig>,
    pub mod_key: u8,
    pub border_width: u32,
    pub border_color: u32,
    pub border_focus_color: u32,
    pub border_urgent_color: u32,
    pub greedy_view: bool,
    pub keybindings: Vec<Keybinding>,
    pub manage_hooks: Vec<ManageHook>,
    pub statusbar: Option<Statusbar>,
}

impl Default for Config {
    fn default() -> Config {
        let mut config = Config {
            lib: None,
            workspaces: (1usize..10)
                .map(|idx| {
                    WorkspaceConfig {
                        tag: idx.to_string(),
                        screen: 0,
                        layout: StrutLayout::new(TallLayout::new(1, 0.5, 0.05)),
                    }
                })
                .collect(),
            mod_key: MOD_4,
            border_width: 2,
            border_color: 0x002e2e2e,
            border_focus_color: 0x002a82e6,
            border_urgent_color: 0x00ff0000,
            greedy_view: false,
            keybindings: vec![Keybinding {
                                  mods: 0,
                                  key: "Return".to_string(),
                                  cmd: Cmd::Exec("xterm -u8".to_string()),
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "d".to_string(),
                                  cmd: Cmd::Exec("dmenu_run".to_string()),
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "q".to_string(),
                                  cmd: Cmd::KillClient,
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "j".to_string(),
                                  cmd: Cmd::FocusDown,
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "k".to_string(),
                                  cmd: Cmd::FocusUp,
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "m".to_string(),
                                  cmd: Cmd::FocusMaster,
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "j".to_string(),
                                  cmd: Cmd::SwapDown,
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "k".to_string(),
                                  cmd: Cmd::SwapUp,
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "Return".to_string(),
                                  cmd: Cmd::SwapMaster,
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "comma".to_string(),
                                  cmd: Cmd::SendLayoutMsg(LayoutMsg::IncreaseMaster),
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "period".to_string(),
                                  cmd: Cmd::SendLayoutMsg(LayoutMsg::DecreaseMaster),
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "l".to_string(),
                                  cmd: Cmd::SendLayoutMsg(LayoutMsg::Increase),
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "h".to_string(),
                                  cmd: Cmd::SendLayoutMsg(LayoutMsg::Decrease),
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "c".to_string(),
                                  cmd: Cmd::Exit,
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "x".to_string(),
                                  cmd: Cmd::Reload,
                              }],
            manage_hooks: Vec::new(),
            statusbar: None,
        };

        for i in 1..10 {
            config.keybindings.push(Keybinding {
                mods: 0,
                key: i.to_string(),
                cmd: Cmd::SwitchWorkspace(i),
            });

            config.keybindings.push(Keybinding {
                mods: MOD_SHIFT,
                key: i.to_string(),
                cmd: Cmd::MoveToWorkspace(i),
            });
        }

        for &(i, key) in vec![(1, "w"), (2, "e"), (3, "r")].iter() {
            config.keybindings.push(Keybinding {
                mods: 0,
                key: key.to_string(),
                cmd: Cmd::SwitchScreen(i),
            });

            config.keybindings.push(Keybinding {
                mods: MOD_SHIFT,
                key: key.to_string(),
                cmd: Cmd::MoveToScreen(i),
            });
        }

        config
    }
}

impl Config {
    fn compile() -> Result<(), String> {
        let dst = Path::new(concat!(env!("HOME"), "/.xr3wm/.build"));
        if !dst.exists() {
            create_dir(dst)
                .unwrap_or_else(|e| panic!("Failed to create config build directory: {}", e));
        }

        if !dst.join("Cargo.toml").exists() {
            let mut f = File::create(dst.join("Cargo.toml")).unwrap();
            f.write_all(b"[project]
name = \"config\"
version = \"0.0.1\"
authors = [\"xr3wm\"]

[dependencies.xr3wm]
git = \"https://github.com/tsurai/xr3wm.git\"

[lib]
name = \"config\"
path = \"../config.rs\"
crate-type = [\"dylib\"]")
                .unwrap();
        }

        let output = Command::new("cargo")
            .arg("build")
            .current_dir(dst)
            .output()
            .unwrap_or_else(|e| panic!("Failed to run cargo: {}", e));
        if output.status.success() {
            Ok(())
        } else {
            unsafe { Err(String::from_utf8_unchecked(output.stderr)) }
        }
    }

    pub fn load() -> Config {
        let mut cfg: Config = Default::default();

        let mut xmsg = Command::new("xmessage")
            .arg("-center")
            .arg("Compiling config...").spawn().unwrap();

        match Config::compile() {
            Ok(_) => {
                xmsg.kill();
                match DynamicLibrary::open(
                    Some(&Path::new(concat!(env!("HOME"),
                        "/.xr3wm/.build/target/debug/libconfig.so")))) {
                    Ok(lib) => unsafe {
                        match lib.symbol("config") {
                            Ok(symbol) => {
                                let f = mem::transmute::<*mut u8,
                                                         extern "C" fn(&mut Config)>(symbol);
                                cfg.lib = Some(lib);
                                f(&mut cfg);
                            }
                            Err(e) => error!("Failed to load symbol: {}", e),
                        }
                    },
                    Err(e) => error!("Failed to load libconfig: {}", e),
                }
            }
            Err(e) => {
                xmsg.kill();
                Command::new("xmessage")
                    .arg("-center")
                    .arg(format!("Failed to compile config:\n{}\nUsing default config", e)).spawn().unwrap();

                error!("Failed to compile config: {}", e);
            },
        }
        cfg
    }
}
<commit_msg>ignore process termination errors for xmsg<commit_after>#![allow(dead_code)]

use std::default::Default;
use std::io::Write;
use std::path::Path;
use std::fs::{File, create_dir};
use std::process::{Command, Child, Stdio};
use std::mem;
use layout::*;
use keycode::*;
use workspaces::{Workspaces, WorkspaceConfig};
use xlib_window_system::XlibWindowSystem;
use commands::{Cmd, ManageHook};
use dylib::DynamicLibrary;

pub struct Keybinding {
    pub mods: u8,
    pub key: String,
    pub cmd: Cmd,
}

pub struct WorkspaceInfo {
    pub id: usize,
    pub tag: String,
    pub screen: usize,
    pub current: bool,
    pub visible: bool,
    pub urgent: bool,
}

pub struct LogInfo {
    pub workspaces: Vec<WorkspaceInfo>,
    pub layout_name: String,
    pub window_title: String,
}

pub struct Statusbar {
    child: Option<Child>,
    executable: String,
    args: Option<Vec<String>>,
    fn_format: Box<Fn(LogInfo) -> String>,
}

impl Statusbar {
    pub fn new(executable: String,
               args: Option<Vec<String>>,
               fn_format: Box<Fn(LogInfo) -> String>)
               -> Statusbar {
        Statusbar {
            child: None,
            executable: executable,
            args: args,
            fn_format: fn_format,
        }
    }

    pub fn xmobar() -> Statusbar {
        Statusbar::new("xmobar".to_string(),
                       None,
                       Box::new(move |info: LogInfo| -> String {
            let workspaces = info.workspaces
                .iter()
                .map(|x| {
                    let (fg, bg) = if x.current {
                        ("#00ff00", "#000000")
                    } else if x.visible {
                        ("#009900", "#000000")
                    } else if x.urgent {
                        ("#ff0000", "#000000")
                    } else {
                        ("#ffffff", "#000000")
                    };
                    format!("<fc={},{}>[{}]</fc>", fg, bg, x.tag)
                })
                .collect::<Vec<String>>()
                .join(" ");

            format!("{} | {} | {}\n",
                    workspaces,
                    info.layout_name,
                    info.window_title)
        }))
    }

    pub fn start(&mut self) {
        match self.child {
            Some(_) => warn!("'{}' is already running", self.executable),
            None => {
                let mut cmd = Command::new(self.executable.clone());
                if self.args.is_some() {
                    cmd.args(self.args.clone().unwrap().as_slice());
                }
                match cmd.stdin(Stdio::piped()).spawn() {
                    Ok(child) => self.child = Some(child),
                    Err(err) => error!("failed to execute '{}': {}", self.executable, err),
                }
            }
        }
    }

    pub fn update(&mut self, ws: &XlibWindowSystem, workspaces: &Workspaces) {
        if self.child.is_none() {
            return;
        }

        let output = (self.fn_format)(LogInfo {
            workspaces: workspaces.all()
                .iter()
                .enumerate()
                .map(|(i, x)| {
                    WorkspaceInfo {
                        id: i,
                        tag: x.get_tag(),
                        screen: 0,
                        current: i == workspaces.get_index(),
                        visible: x.is_visible(),
                        urgent: x.is_urgent(),
                    }
                })
                .collect(),
            layout_name: workspaces.current().get_layout().name(),
            window_title: ws.get_window_title(workspaces.current().focused_window()),
        });

        let mut stdin = self.child.as_mut().unwrap().stdin.as_mut().unwrap();
        stdin.write_all(output.as_bytes()).ok();
    }
}

pub struct Config {
    lib: Option<DynamicLibrary>,
    pub workspaces: Vec<WorkspaceConfig>,
    pub mod_key: u8,
    pub border_width: u32,
    pub border_color: u32,
    pub border_focus_color: u32,
    pub border_urgent_color: u32,
    pub greedy_view: bool,
    pub keybindings: Vec<Keybinding>,
    pub manage_hooks: Vec<ManageHook>,
    pub statusbar: Option<Statusbar>,
}

impl Default for Config {
    fn default() -> Config {
        let mut config = Config {
            lib: None,
            workspaces: (1usize..10)
                .map(|idx| {
                    WorkspaceConfig {
                        tag: idx.to_string(),
                        screen: 0,
                        layout: StrutLayout::new(TallLayout::new(1, 0.5, 0.05)),
                    }
                })
                .collect(),
            mod_key: MOD_4,
            border_width: 2,
            border_color: 0x002e2e2e,
            border_focus_color: 0x002a82e6,
            border_urgent_color: 0x00ff0000,
            greedy_view: false,
            keybindings: vec![Keybinding {
                                  mods: 0,
                                  key: "Return".to_string(),
                                  cmd: Cmd::Exec("xterm -u8".to_string()),
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "d".to_string(),
                                  cmd: Cmd::Exec("dmenu_run".to_string()),
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "q".to_string(),
                                  cmd: Cmd::KillClient,
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "j".to_string(),
                                  cmd: Cmd::FocusDown,
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "k".to_string(),
                                  cmd: Cmd::FocusUp,
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "m".to_string(),
                                  cmd: Cmd::FocusMaster,
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "j".to_string(),
                                  cmd: Cmd::SwapDown,
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "k".to_string(),
                                  cmd: Cmd::SwapUp,
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "Return".to_string(),
                                  cmd: Cmd::SwapMaster,
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "comma".to_string(),
                                  cmd: Cmd::SendLayoutMsg(LayoutMsg::IncreaseMaster),
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "period".to_string(),
                                  cmd: Cmd::SendLayoutMsg(LayoutMsg::DecreaseMaster),
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "l".to_string(),
                                  cmd: Cmd::SendLayoutMsg(LayoutMsg::Increase),
                              },
                              Keybinding {
                                  mods: 0,
                                  key: "h".to_string(),
                                  cmd: Cmd::SendLayoutMsg(LayoutMsg::Decrease),
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "c".to_string(),
                                  cmd: Cmd::Exit,
                              },
                              Keybinding {
                                  mods: MOD_SHIFT,
                                  key: "x".to_string(),
                                  cmd: Cmd::Reload,
                              }],
            manage_hooks: Vec::new(),
            statusbar: None,
        };

        for i in 1..10 {
            config.keybindings.push(Keybinding {
                mods: 0,
                key: i.to_string(),
                cmd: Cmd::SwitchWorkspace(i),
            });

            config.keybindings.push(Keybinding {
                mods: MOD_SHIFT,
                key: i.to_string(),
                cmd: Cmd::MoveToWorkspace(i),
            });
        }

        for &(i, key) in vec![(1, "w"), (2, "e"), (3, "r")].iter() {
            config.keybindings.push(Keybinding {
                mods: 0,
                key: key.to_string(),
                cmd: Cmd::SwitchScreen(i),
            });

            config.keybindings.push(Keybinding {
                mods: MOD_SHIFT,
                key: key.to_string(),
                cmd: Cmd::MoveToScreen(i),
            });
        }

        config
    }
}

impl Config {
    fn compile() -> Result<(), String> {
        let dst = Path::new(concat!(env!("HOME"), "/.xr3wm/.build"));
        if !dst.exists() {
            create_dir(dst)
                .unwrap_or_else(|e| panic!("Failed to create config build directory: {}", e));
        }

        if !dst.join("Cargo.toml").exists() {
            let mut f = File::create(dst.join("Cargo.toml")).unwrap();
            f.write_all(b"[project]
name = \"config\"
version = \"0.0.1\"
authors = [\"xr3wm\"]

[dependencies.xr3wm]
git = \"https://github.com/tsurai/xr3wm.git\"

[lib]
name = \"config\"
path = \"../config.rs\"
crate-type = [\"dylib\"]")
                .unwrap();
        }

        let output = Command::new("cargo")
            .arg("build")
            .current_dir(dst)
            .output()
            .unwrap_or_else(|e| panic!("Failed to run cargo: {}", e));
        if output.status.success() {
            Ok(())
        } else {
            unsafe { Err(String::from_utf8_unchecked(output.stderr)) }
        }
    }

    pub fn load() -> Config {
        let mut cfg: Config = Default::default();

        let mut xmsg = Command::new("xmessage")
            .arg("-center")
            .arg("Compiling config...").spawn().unwrap();

        match Config::compile() {
            Ok(_) => {
                xmsg.kill().ok();
                match DynamicLibrary::open(
                    Some(&Path::new(concat!(env!("HOME"),
                        "/.xr3wm/.build/target/debug/libconfig.so")))) {
                    Ok(lib) => unsafe {
                        match lib.symbol("config") {
                            Ok(symbol) => {
                                let f = mem::transmute::<*mut u8,
                                                         extern "C" fn(&mut Config)>(symbol);
                                cfg.lib = Some(lib);
                                f(&mut cfg);
                            }
                            Err(e) => error!("Failed to load symbol: {}", e),
                        }
                    },
                    Err(e) => error!("Failed to load libconfig: {}", e),
                }
            }
            Err(e) => {
                xmsg.kill().ok();
                Command::new("xmessage")
                    .arg("-center")
                    .arg(format!("Failed to compile config:\n{}\nUsing default config", e)).spawn().unwrap();

                error!("Failed to compile config: {}", e);
            },
        }
        cfg
    }
}
<|endoftext|>