<commit_before>/*
 * Copyright (c) 2015, Hewlett Packard Development Company L.P.
 *
 * 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.
 */
#![feature(mpsc_select)]
#![feature(scoped)]
#![feature(ip_addr)]
extern crate byteorder;
extern crate hash_ring;
extern crate uuid;
#[macro_use]
extern crate log;
extern crate env_logger;

use std::option::Option;
use std::io;
use std::net::{TcpListener, SocketAddr, SocketAddrV4, SocketAddrV6, IpAddr, Ipv4Addr};
use std::collections::HashMap;
use std::thread;
use std::thread::{JoinHandle, sleep_ms};
use std::sync::{Mutex, MutexGuard, Condvar, Arc};
use std::sync::mpsc::channel;
use std::sync::mpsc::{Receiver, Sender, RecvError};
use std::fmt;

use hash_ring::HashRing;

pub mod packet;
use packet::Packet;
pub mod connection;
use connection::Connection;
pub mod constants;
use constants::*;
pub mod util;
use util::LoggingRwLock as RwLock;

fn run_test_server() -> (Arc<RwLock<Box<BaseClientServer>>>, u16) {
    match env_logger::init() {
        Ok(_) => { },
        Err(e) => {
            info!("{}", e);
        }
    }
    let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0 , 1), 0));
    let (mut bcs, threads) = BaseClientServer::run("runserver".to_string().into_bytes(), Some(vec![addr]));
    println!("Waiting 100ms for server");
    let mut port: u16;
    thread::sleep_ms(100);
    {
        let binds;
        {
            let bcs2 = bcs.clone();
            let bcs2 = bcs2.read(line!()).unwrap();
            binds = bcs2.binds.clone();
        }
        println!("Checking binds for actual port");
        match binds {
            None => unreachable!(),
            Some(binds) => {
                loop {
                    port = binds.lock().unwrap()[0].port();
                    if port != 0 {
                        println!("First bind port is {}", port);
                        break;
                    }
                    // condvar or channel is probably a better idea
                    thread::sleep_ms(100);
                }
            }
        }
    }
    (bcs, port)
}

#[test]
fn send_packet() {
    let (bcs, port) = run_test_server();
    let mut conn = Connection::new("127.0.0.1", port);
    conn.connect().unwrap();
    let data: Box<[u8]> = Box::new([0x00u8]);
    let p = Packet::new(PacketCode::REQ, ECHO_REQ, data);
    match conn.send_packet(p) {
        Ok(_) => {},
        Err(_) => panic!("Error in send_packet."),
    }
    let bcs = bcs.read(line!()).unwrap();
    bcs.stop();
}

#[test]
#[should_panic]  // Still no echo implementation in server
fn echo() {
    let (bcs, port) = run_test_server();
    let mut test: Vec<u8> = Vec::new();
    test.extend("abc123".to_string().bytes());
    let mut conn = Connection::new("127.0.0.1", port);
    conn.connect().unwrap();
    let result = String::from_utf8(conn.echo(test, 0)).unwrap();
    assert_eq!("abc123", result);
    let mut test2: Vec<u8> = Vec::new();
    let mut output = String::from_utf8(conn.echo(test2, 0)).unwrap();
    println!("UUID = {}", output);
    let bcs = bcs.read(line!()).unwrap();
    bcs.stop();
}

#[test]
fn bcs_constructor() {
    let bcs = BaseClientServer::new("clientid".to_string().into_bytes());
}

#[test]
fn run_server_client_can_reach() {
    let (bcs, port) = run_test_server();
    // Is it working?
    let (mut bcs_client, client_threads) = BaseClientServer::run("servers_client".to_string().into_bytes(), None);
    {
        let bcs_client = bcs_client.write(line!()).unwrap();
        bcs_client.add_server("127.0.0.1".to_string(), port);
    }
    {
        //let bcs_client = bcs_client.read(line!()).unwrap();
        BaseClientServer::wait_for_connection(bcs_client.clone(), Some(5000)).unwrap();
    }
    {
        let bcs_client = bcs_client.write(line!()).unwrap();
        bcs_client.stop();
    }
    // Now shut server down
    let mut bcs = bcs.read(line!()).unwrap();
    println!("Stopping bcs...");
    bcs.stop();
}

#[test]
#[should_panic]
fn bcs_select_connection() {
    let (mut bcs, threads) = BaseClientServer::run("selconn".to_string().into_bytes(), None);
    println!("Waiting 100ms for connections");
    BaseClientServer::wait_for_connection(bcs.clone(), Some(100)).unwrap();
    let mut bcs = bcs.write(line!()).unwrap();
    bcs.select_connection("some string".to_string());
    bcs.stop();
    for thread in threads {
        thread.join();
    }
}

#[test]
fn bcs_run() {
    println!("Begin");
    let (mut bcs, threads) = BaseClientServer::run("clientid".to_string().into_bytes(), None);
    println!("Started");
    let mut bcs0 = bcs.clone();
    let mut bcs1 = bcs.clone();
    println!("Waiting 100ms");
    thread::sleep_ms(100);
    {
        let bcs = &bcs.read(line!()).unwrap();
        bcs.stop();
    }
    for thread in threads {
        thread.join();
    }
}

#[derive(Clone)]
#[derive(Eq)]
#[derive(PartialEq)]
#[derive(Hash)]
struct NodeInfo {
    host: String,
    port: u16,
}

impl fmt::Display for NodeInfo {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_fmt(format_args!("{}:{}", self.host, self.port))
    }
}

trait HandlePacket: Send {
    fn handle_packet(&self, packet: Packet);
}

struct BaseClientServer {
    client_id: Vec<u8>,
    active_connections: HashMap<NodeInfo, Arc<Mutex<Box<Connection>>>>,
    active_connections_cond: Arc<Box<(Mutex<bool>, Condvar)>>,
    active_ring: HashRing<NodeInfo>,
    hosts: Vec<String>,
    stop_cm_tx: Option<Arc<Mutex<Box<Sender<()>>>>>,
    stop_pm_tx: Option<Arc<Mutex<Box<Sender<()>>>>>,
    host_tx: Option<Arc<Mutex<Box<Sender<Arc<Mutex<Box<(String, u16)>>>>>>>>,
    handler: Option<Arc<Mutex<Box<HandlePacket + Send>>>>,
    binds: Option<Arc<Mutex<Box<Vec<SocketAddr>>>>>,
}

impl BaseClientServer {

    fn new(client_id: Vec<u8>) -> BaseClientServer {
        BaseClientServer {
            client_id: client_id,
            active_connections: HashMap::new(),
            active_connections_cond: Arc::new(Box::new((Mutex::new(false), Condvar::new()))),
            active_ring: HashRing::new(Vec::new(), 1),
            hosts: Vec::new(),
            stop_cm_tx: None,
            stop_pm_tx: None,
            host_tx: None,
            handler: None,
            binds: None,
        }
    }

    /// Runs the client or server threads associated with this BCS
    fn run(client_id: Vec<u8>, binds: Option<Vec<SocketAddr>>) -> (Arc<RwLock<Box<BaseClientServer>>>, Vec<JoinHandle<()>>) {
        let mut bcs = Box::new(BaseClientServer::new(client_id));
        let (host_tx, host_rx): (Sender<Arc<Mutex<Box<(String, u16)>>>>, Receiver<Arc<Mutex<Box<(String, u16)>>>>) = channel();
        let (conn_tx, conn_rx) = channel();
        let (stop_cm_tx, stop_cm_rx) = channel();
        let (stop_pm_tx, stop_pm_rx) = channel();
        bcs.stop_cm_tx = Some(Arc::new(Mutex::new(Box::new(stop_cm_tx.clone()))));
        bcs.stop_pm_tx = Some(Arc::new(Mutex::new(Box::new(stop_pm_tx.clone()))));
        bcs.host_tx = Some(Arc::new(Mutex::new(Box::new(host_tx.clone()))));
        let mut bcs: Arc<RwLock<Box<BaseClientServer>>> = Arc::new(RwLock::new(bcs));
        let bcs0 = bcs.clone();
        let bcs1 = bcs.clone();
        let bcs2 = bcs.clone();
        let conn_tx = conn_tx.clone();
        let host_tx = host_tx.clone();
        let mut threads = Vec::new();
        {
            match binds {
                Some(binds) => {
                    let listen_binds = bcs0.read(line!()).unwrap().binds.clone();
                    match listen_binds {
                        Some(_) => unreachable!(),
                        None => {
                            bcs0.write(line!()).unwrap().binds = Some(Arc::new(Mutex::new(Box::new(binds))))
                        },
                    }
                    threads.push(thread::spawn(move || {
                        BaseClientServer::listen_manager(bcs0, stop_cm_rx, conn_tx);
                    }));
                    threads.push(thread::spawn(move || {
                        BaseClientServer::disconnect_manager(bcs2, host_rx);
                    }));
                },
                None => {
                    threads.push(thread::spawn(move || {
                        BaseClientServer::connection_manager(bcs0, stop_cm_rx, host_rx, conn_tx);
                    }));
                }
            };
            threads.push(thread::spawn(move || {
                BaseClientServer::polling_manager(bcs1, stop_pm_rx, host_tx, conn_rx);
            }));
        }
        (bcs, threads)
    }

    fn stop(&self) {
        {
            let stop_cm_tx: Option<Arc<Mutex<Box<Sender<()>>>>> = self.stop_cm_tx.clone();
            match stop_cm_tx {
                Some(stop_cm_tx) => { stop_cm_tx.lock().unwrap().send(()); },
                None => { panic!("No stop cm channel"); },
            }
        }
        {
            let stop_pm_tx: Option<Arc<Mutex<Box<Sender<()>>>>> = self.stop_cm_tx.clone();
            match stop_pm_tx {
                Some(stop_pm_tx) => { stop_pm_tx.lock().unwrap().send(()); },
                None => { panic!("No stop pm channel"); },
            }
        }
    }

    fn connection_manager(bcs: Arc<RwLock<Box<BaseClientServer>>>,
                         stop_rx: Receiver<()>,
                         host_rx: Receiver<Arc<Mutex<Box<(String, u16)>>>>,
                         conn_tx: Sender<Arc<Mutex<Box<Connection>>>>) {
        /* Run as a dedicated thread to manage the list of active/inactive connections */
        let (new_tx, new_rx) = channel();
        let mut reconn_thread;
        let mut conn_thread;
        let mut host: String;
        let mut port: u16;
        loop {
            let container: Result<Arc<Mutex<Box<(String, u16)>>>, RecvError>;
            /* either a new conn is requested, or a new conn is connected */
            select!(
                container = host_rx.recv() => {
                    let container = match container {
                        Ok(container) => container,
                        Err(_) => panic!("Could not receive in connection manager"),
                    };
                    let container = container.clone();
                    {
                        let ref mut hostport = **container.lock().unwrap();
                        host = hostport.0.clone();
                        port = hostport.1;
                    }
                    debug!("received {}:{}", host, port);
                    let conn_tx = conn_tx.clone();
                    let new_tx = new_tx.clone();
                    {
                        let nodeinfo = NodeInfo { host: host.clone(), port: port };
                        debug!("searching for {}:{}", host, port);
                        let found = {
                            let bcs = bcs.read(line!()).unwrap();
                            match bcs.active_connections.get(&nodeinfo) {
                                Some(conn) => Some(conn.clone()),
                                None => None,
                            }
                        };
                        match found {
                            Some(conn) => {
                                debug!("found {}:{}", host, port);
                                // stop sending things to it
                                {
                                    bcs.write(line!()).unwrap().active_ring.remove_node(&NodeInfo{host: host.clone(), port: port});
                                }
                                let conn = conn.clone();
                                reconn_thread = thread::scoped(move || {
                                    let mut real_conn = conn.lock().unwrap();
                                    loop {
                                        match real_conn.reconnect() {
                                            Ok(_) => break,
                                            Err(err) => {
                                                info!("Connection to {} failed with ({}). Retry in 1s", *real_conn, err);
                                                thread::sleep_ms(1000);
                                            },
                                        }
                                    };
                                    conn_tx.send(conn.clone());
                                });
                            },
                            None => {
                                debug!("NEW {}:{}", host, port);
                                conn_thread = thread::scoped(move || {
                                    let conn: Arc<Mutex<Box<Connection>>> = Arc::new(Mutex::new(Box::new(Connection::new(&host, port)))).clone();
                                    {
                                        let mut real_conn = conn.lock().unwrap();
                                        loop {
                                            debug!("trying to connect {}", *real_conn);
                                            match real_conn.connect() {
                                                Ok(_) => break,
                                                Err(err) => {
                                                    info!("Connection to {} failed with ({}). Retry in 1s", *real_conn, err);
                                                    thread::sleep_ms(1000);
                                                }
                                            }
                                        };
                                    }
                                    debug!("sending {}", *conn.lock().unwrap());
                                    new_tx.send(conn.clone());
                                });
                            }
                        };
                    } // unlock bcs
                },
                conn = new_rx.recv() => {
                    let conn = match conn {
                        Ok(conn) => conn,
                        Err(_) => panic!("Could not receive in connection manager"),
                    };
                    debug!("received {}", *conn.lock().unwrap());
                    {
                        let ref real_conn = **conn.lock().unwrap();
                        let host = real_conn.host.clone();
                        {
                            debug!("locking bcs");
                            let bcs = &mut bcs.write(line!()).unwrap();
                            debug!("locked bcs");
                            let host = host.clone();
                            let nodeinfo = NodeInfo{ host: host, port: real_conn.port };
                            bcs.active_connections.insert(nodeinfo.clone(), conn.clone());
                            bcs.active_ring.add_node(&nodeinfo);
                            let &(ref lock, ref cvar) = &**bcs.active_connections_cond;
                            let mut available = lock.lock().unwrap();
                            *available = true;
                            cvar.notify_all();
                        }
                    }
                    conn_tx.send(conn.clone());
                },
                _ = stop_rx.recv() => { break }
            )
        }
        for (_, conn) in bcs.read(line!()).unwrap().active_connections.iter() {
            let mut conn = conn.lock().unwrap();
            conn.disconnect();
        }
        // scoped threads should join here
    }

    /// Hosts that have been disconnected go through here for cleanup
    fn disconnect_manager(bcs: Arc<RwLock<Box<BaseClientServer>>>,
                          host_rx: Receiver<Arc<Mutex<Box<(String, u16)>>>>)
    {
        loop {
            let host = host_rx.recv().unwrap();
            let host = host.lock().unwrap();
            let nodeinfo = NodeInfo { host: host.0.clone(), port: host.1 };
            info!("{} disconnected", nodeinfo);
            let mut delete = false;
            match bcs.read(line!()).unwrap().active_connections.get(&nodeinfo) {
                Some(conn) => {
                    conn.lock().unwrap().disconnect();
                    /* TODO Handler for reassigning jobs will need to go here */
                    delete = true;
                },
                None => {
                    warn!("Disconnected host {} not found in active connections", nodeinfo);
                },
            }
            if delete {
                bcs.write(line!()).unwrap().active_connections.remove(&nodeinfo);
            }
        }
    }

    /// Listens for new connections. Server complement to connection_manager
    fn listen_manager(bcs: Arc<RwLock<Box<BaseClientServer>>>,
                      stop_rx: Receiver<()>,
                      conn_tx: Sender<Arc<Mutex<Box<Connection>>>>) {
        // This block here is intended to just copy the vector. Seems like maybe
        // this should be turned into a parameter for simplicity sake.
        let binds = bcs.read(line!()).unwrap().binds.clone();
        let binds = match binds {
            None => panic!("No binds for server!"),
            Some(binds) => binds,
        };
        let real_binds = binds.clone();
        let binds: Vec<SocketAddr>;
        {
            binds = *real_binds.lock().unwrap().clone();
        }
        let mut offset = 0;
        for bind in binds.iter() {
            info!("Binding to {}", bind);
            let bind = bind.clone();
            let conn_tx = conn_tx.clone();
            let bcs = bcs.clone();
            let listener_thread = thread::spawn(move || {
                let listener = TcpListener::bind(bind).unwrap();
                let assignedport = listener.local_addr().unwrap().port();
                if bind.port() == 0 {
                    let bcs = bcs.clone();
                    let bcs = bcs.read(line!()).unwrap();
                    debug!("port == 0, reading assigned from socket");
                    let binds = bcs.binds.clone();
                    let binds = match binds {
                        None => panic!("Somehow got no binds despite being in binds for loop."),
                        Some(binds) => binds,
                    };
                    binds.lock().unwrap()[offset] = match bind {
                        SocketAddr::V4(bind) => {
                            SocketAddr::V4(SocketAddrV4::new(bind.ip().clone(), assignedport))
                        },
                        SocketAddr::V6(bind) => {
                            SocketAddr::V6(SocketAddrV6::new(bind.ip().clone(), assignedport, bind.flowinfo(), bind.scope_id()))
                        },
                    };
                } else {
                    debug!("port != 0 but == {}", bind.port());
                }
                info!("Bound to {}", bind);
                for stream in listener.incoming() {
                    debug!("incoming connection!");
                    let conn_tx = conn_tx.clone();
                    let bcs = bcs.clone();
                    let in_thread = thread::scoped(move || {
                        let stream = stream.unwrap();
                        let peer_addr = stream.peer_addr().unwrap();
                        let host = format!("{}", peer_addr.ip());
                        let nodeinfo = NodeInfo{ host: host.clone(), port: peer_addr.port() };
                        let conn = Arc::new(Mutex::new(Box::new(Connection::new_incoming(&host[..], peer_addr.port(), stream))));
                        {
                            let bcs = &mut bcs.write(line!()).unwrap();
                            bcs.active_connections.insert(nodeinfo.clone(), conn.clone());
                        }
                        let bcs = bcs.read(line!()).unwrap();
                        // no need for filling active_ring it's not used in servers
                        let &(ref lock, ref cvar) = &**bcs.active_connections_cond;
                        let mut available = lock.lock().unwrap();
                        *available = true;
                        cvar.notify_all();
                        conn_tx.send(conn.clone());
                    });
                }
            });
            offset += 1;
        }
        // No joining, just accept the explosion because TcpListener has no
        // real way to be interrupted.
        info!("Waiting for stop");
        let _ = stop_rx.recv().unwrap();
        info!("Got stop");
    }

    fn add_server(&self, host: String, port: u16) {
        let host_tx = self.host_tx.clone();
        match host_tx {
            None => panic!("adding server to broken BaseClientServer object."),
            Some(host_tx) => {
                debug!("Sending {}:{}", host, port);
                host_tx.lock().unwrap().send(Arc::new(Mutex::new(Box::new((host, port)))));
            },
        }
    }

    fn polling_manager(bcs: Arc<RwLock<Box<BaseClientServer>>>,
                      stop_rx: Receiver<()>,
                      host_tx: Sender<Arc<Mutex<Box<(String, u16)>>>>,
                      conn_rx: Receiver<Arc<Mutex<Box<Connection>>>>) {
        let mut threads: Vec<JoinHandle<()>> = Vec::new();
        let bcs = bcs.clone();
        loop {
            let conn: Result<Arc<Mutex<Box<Connection>>>, RecvError>;
            select!(
                conn = conn_rx.recv() => {
                    let conn = conn.unwrap();
                    let host_tx = host_tx.clone();
                    // poll this conn
                    let bcs = bcs.clone();
                    threads.push(thread::spawn(move || {
                        loop {
                            let mut conn = conn.lock().unwrap();
                            match conn.read_packet() {
                                Ok(p) => {
                                    bcs.read(line!()).unwrap().handle_packet(p);
                                }
                                Err(_) => {
                                    warn!("{} failed to read packet", *conn);
                                    host_tx.send(Arc::new(Mutex::new(Box::new((conn.host.clone(), conn.port)))));
                                    break;
                                }
                            }
                        }
                    }));
                },
                _ = stop_rx.recv() => { break }
            );
        };
        // scoped threads all joined here
        for thread in threads {
            thread.join();
        }
    }

    fn handle_packet(&self, packet: Packet) {
        match self.handler {
            None => return,
            Some(ref handler) => {
                let handler = handler.clone();
                handler.lock().unwrap().handle_packet(packet);
            }
        }
    }

    /// Wait for connection condition from connection_manager.
    ///
    /// We need the rwlock, and not just an unlocked structure, so that we can
    /// just peek at the structure to see the condition variable, and then let
    /// other threads write lock.
    fn wait_for_connection(bcs: Arc<RwLock<Box<BaseClientServer>>>, timeout: Option<u32>) -> Result<bool, &'static str> {
        loop {
            let mut active_connections_cond: Arc<Box<(Mutex<bool>, Condvar)>>;
            {
                let bcs = bcs.read(line!()).unwrap();
                active_connections_cond = bcs.active_connections_cond.clone();
            }
            let lock = &active_connections_cond.clone().0;
            let cvar = &active_connections_cond.clone().1;
            let mut available = lock.lock().unwrap();
            if *available {
                break;
            }
            // will wait until there are perceived active connections
            match timeout {
                Some(timeout) => {
                    let wait_result = cvar.wait_timeout_ms(available, timeout).unwrap();
                    if !wait_result.1 {
                        return Err("Timed out waiting for connections")
                    }
                    available = wait_result.0;
                },
                None => {
                    available = cvar.wait(available).unwrap();
                }
            }
        }
        Ok(true)
    }

    // mutable self because active_ring.get_node wants mutable
    fn select_connection(&mut self, key: String) -> Arc<Mutex<Box<Connection>>> {
        let nodeinfo = self.active_ring.get_node(key);
        match self.active_connections.get(nodeinfo) {
            Some(conn) => { conn.clone() },
            None => panic!("Hash ring contains non-existant node!"),
        }
    }

}
<commit_msg>Reorganizing into modules: base<commit_after>/*
 * Copyright (c) 2015, Hewlett Packard Development Company L.P.
 *
 * 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.
 */
#![feature(mpsc_select)]
#![feature(scoped)]
#![feature(ip_addr)]
extern crate byteorder;
extern crate hash_ring;
extern crate uuid;
#[macro_use]
extern crate log;
extern crate env_logger;

use std::option::Option;
use std::io;
use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr};
use std::sync::Arc;
use std::thread;

use hash_ring::HashRing;

pub mod packet;
use packet::Packet;
pub mod connection;
use connection::Connection;
pub mod constants;
use constants::*;
pub mod base;
use base::BaseClientServer;
pub mod util;
use util::LoggingRwLock as RwLock;

fn run_test_server() -> (Arc<RwLock<Box<BaseClientServer>>>, u16) {
    match env_logger::init() {
        Ok(_) => { },
        Err(e) => {
            info!("{}", e);
        }
    }
    let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0 , 1), 0));
    let (mut bcs, threads) = BaseClientServer::run("runserver".to_string().into_bytes(), Some(vec![addr]));
    println!("Waiting 100ms for server");
    let mut port: u16;
    thread::sleep_ms(100);
    {
        let binds;
        {
            let bcs2 = bcs.clone();
            let bcs2 = bcs2.read(line!()).unwrap();
            binds = bcs2.binds.clone();
        }
        println!("Checking binds for actual port");
        match binds {
            None => unreachable!(),
            Some(binds) => {
                loop {
                    port = binds.lock().unwrap()[0].port();
                    if port != 0 {
                        println!("First bind port is {}", port);
                        break;
                    }
                    // condvar or channel is probably a better idea
                    thread::sleep_ms(100);
                }
            }
        }
    }
    (bcs, port)
}

#[test]
fn send_packet() {
    let (bcs, port) = run_test_server();
    let mut conn = Connection::new("127.0.0.1", port);
    conn.connect().unwrap();
    let data: Box<[u8]> = Box::new([0x00u8]);
    let p = Packet::new(PacketCode::REQ, ECHO_REQ, data);
    match conn.send_packet(p) {
        Ok(_) => {},
        Err(_) => panic!("Error in send_packet."),
    }
    let bcs = bcs.read(line!()).unwrap();
    bcs.stop();
}

#[test]
#[should_panic]  // Still no echo implementation in server
fn echo() {
    let (bcs, port) = run_test_server();
    let mut test: Vec<u8> = Vec::new();
    test.extend("abc123".to_string().bytes());
    let mut conn = Connection::new("127.0.0.1", port);
    conn.connect().unwrap();
    let result = String::from_utf8(conn.echo(test, 0)).unwrap();
    assert_eq!("abc123", result);
    let mut test2: Vec<u8> = Vec::new();
    let mut output = String::from_utf8(conn.echo(test2, 0)).unwrap();
    println!("UUID = {}", output);
    let bcs = bcs.read(line!()).unwrap();
    bcs.stop();
}

#[test]
fn bcs_constructor() {
    let bcs = BaseClientServer::new("clientid".to_string().into_bytes());
}

#[test]
fn run_server_client_can_reach() {
    let (bcs, port) = run_test_server();
    // Is it working?
    let (mut bcs_client, client_threads) = BaseClientServer::run("servers_client".to_string().into_bytes(), None);
    {
        let bcs_client = bcs_client.write(line!()).unwrap();
        bcs_client.add_server("127.0.0.1".to_string(), port);
    }
    {
        //let bcs_client = bcs_client.read(line!()).unwrap();
        BaseClientServer::wait_for_connection(bcs_client.clone(), Some(5000)).unwrap();
    }
    {
        let bcs_client = bcs_client.write(line!()).unwrap();
        bcs_client.stop();
    }
    // Now shut server down
    let mut bcs = bcs.read(line!()).unwrap();
    println!("Stopping bcs...");
    bcs.stop();
}

#[test]
#[should_panic]
fn bcs_select_connection() {
    let (mut bcs, threads) = BaseClientServer::run("selconn".to_string().into_bytes(), None);
    println!("Waiting 100ms for connections");
    BaseClientServer::wait_for_connection(bcs.clone(), Some(100)).unwrap();
    let mut bcs = bcs.write(line!()).unwrap();
    bcs.select_connection("some string".to_string());
    bcs.stop();
    for thread in threads {
        thread.join();
    }
}

#[test]
fn bcs_run() {
    println!("Begin");
    let (mut bcs, threads) = BaseClientServer::run("clientid".to_string().into_bytes(), None);
    println!("Started");
    let mut bcs0 = bcs.clone();
    let mut bcs1 = bcs.clone();
    println!("Waiting 100ms");
    thread::sleep_ms(100);
    {
        let bcs = &bcs.read(line!()).unwrap();
        bcs.stop();
    }
    for thread in threads {
        thread.join();
    }
}
<|endoftext|>


<commit_before>//! This is the wasm web-gui.
//! This file is only compiled for the wasm32 target.

#![cfg(target_arch = "wasm32")]
#![recursion_limit = "512"]

mod application;
mod dictionary;
mod game;
mod image;
mod secret;

use crate::application::Application;
use crate::application::HangmanBackend;
use crate::application::{AUTHOR, CONF_TEMPLATE, TITLE, VERSION};
use crate::game::State;
use wasm_bindgen::prelude::*;
use yew::events::KeyboardEvent;
use yew::prelude::*;
use yew::services::reader::{File, FileData, ReaderService, ReaderTask};
// Disable debugging code.
//use yew::services::ConsoleService;
use yew::services::DialogService;
use yew::{html, Component, ComponentLink, Html, InputData, ShouldRender};

#[derive(Debug)]
pub enum Scene {
    Playground(Application),
    ConfigureGame,
    GameOver,
}

pub struct GuiState {
    config_text: String,
    guess: String,
}

pub struct Model {
    link: ComponentLink<Self>,
    reader: ReaderService,
    // Disable debugging code.
    //console: ConsoleService,
    filereader_tasks: Vec<ReaderTask>,
    dialog: DialogService,
    scene: Scene,
    state: GuiState,
}

#[derive(Debug)]
pub enum Msg {
    SwitchTo(Scene),
    ConfigTextDelete,
    ConfigTextUpdate(String),
    ConfigReady,
    Files(Vec<File>),
    Loaded(FileData),
    UpdateGuess(String),
    Guess,
    Nope,
    NextRound,
}

impl Component for Model {
    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        let state = GuiState {
            config_text: String::from(CONF_TEMPLATE),
            guess: String::new(),
        };

        Model {
            link,
            reader: ReaderService::new(),
            // Disable debugging code.
            //console: ConsoleService::new(),
            filereader_tasks: vec![],
            dialog: DialogService::new(),
            scene: Scene::ConfigureGame,
            state,
        }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        let mut new_scene = None;
        match &mut self.scene {
            Scene::Playground(ref mut app) => match msg {
                Msg::SwitchTo(Scene::ConfigureGame) => {
                    new_scene = Some(Scene::ConfigureGame);
                }
                Msg::SwitchTo(Scene::GameOver) => {
                    new_scene = Some(Scene::GameOver);
                }
                Msg::UpdateGuess(val) => {
                    self.state.guess = val.chars().rev().take(1).collect();
                    // Disable debugging code.
                    //self.console.debug(&self.state.guess);
                }
                Msg::Guess => {
                    app.process_user_input(&self.state.guess);
                    self.state.guess = String::new();
                }
                Msg::Nope => {}
                unexpected => {
                    panic!(
                        "Unexpected message when configurations list shown: {:?}",
                        unexpected
                    );
                }
            },
            Scene::ConfigureGame => match msg {
                Msg::ConfigTextUpdate(val) => {
                    self.state.config_text = val;
                }
                Msg::ConfigTextDelete => {
                    self.state.config_text.clear();
                }
                Msg::SwitchTo(Scene::Playground(app)) => {
                    new_scene = Some(Scene::Playground(app));
                }

                Msg::SwitchTo(Scene::GameOver) => {
                    if self.dialog.confirm("Do you really want to quit this game?") {
                        new_scene = Some(Scene::GameOver);
                    }
                }

                Msg::Loaded(file) => {
                    if let Ok(s) = std::str::from_utf8(&file.content) {
                        self.state.config_text.push_str(s);
                    } else {
                        self.dialog
                            .alert(&format!("Can not read text file: {}", file.name));
                    }
                }
                Msg::Files(files) => {
                    for file in files.into_iter() {
                        let task = {
                            let callback = self.link.callback(Msg::Loaded);
                            self.reader.read_file(file, callback).unwrap()
                        };
                        self.filereader_tasks.push(task);
                    }
                }
                Msg::ConfigReady => {
                    match Application::new(self.state.config_text.as_str()) {
                        Ok(app) => {
                            self.link
                                .send_message(Msg::SwitchTo(Scene::Playground(app)));
                        }
                        Err(e) => self
                            .dialog
                            .alert(&format!("Can not parse configuration:\n {}", e)),
                    };
                }
                unexpected => {
                    panic!(
                        "Unexpected message during new config editing: {:?}",
                        unexpected
                    );
                }
            },
            Scene::GameOver => match msg {
                Msg::SwitchTo(Scene::ConfigureGame) => {
                    new_scene = Some(Scene::ConfigureGame);
                }
                unexpected => {
                    panic!("Unexpected message for settings scene: {:?}", unexpected);
                }
            },
        }
        if let Some(new_scene) = new_scene.take() {
            self.scene = new_scene;
        }
        true
    }

    fn change(&mut self, _: Self::Properties) -> ShouldRender {
        false
    }

    fn view(&self) -> Html {
        let header = move || -> Html {
            html! {
 {TITLE}
 }
        };
        let footer = move || -> Html {
            html! {
             {"User Manual"}
            {", "}  {"Desktop Version"}
            {", "}  {"Documentation"}
            {", "}  {"Source Code"}
            {", Version "} {VERSION.unwrap()} {", "} {AUTHOR}   }
        };
        match self.scene {
            Scene::ConfigureGame => html! { <>
                {header()}

 {"Enter your secrets here:"}

                    <textarea class="conf-text"
                        placeholder=CONF_TEMPLATE
                        cols=80
                        rows=25
                        value=&self.state.config_text
                        oninput=self.link.callback(|e: InputData| Msg::ConfigTextUpdate(e.value)) />

 { "or load secrets from files: "}
                        {"Upload Files ..."}
                        <input class="custom-file-input" type="file" id="upload" multiple=true onchange=self.link.callback(move |value| {
                                let mut result = Vec::new();
                                if let ChangeData::Files(files) = value {
                                    let files = js_sys::try_iter(&files)
                                        .unwrap()
                                        .unwrap()
                                        .into_iter()
                                        .map(|v| File::from(v.unwrap()));
                                    result.extend(files);
                                }
                                Msg::Files(result)
                            })/>

                    <button disabled=self.state.config_text.is_empty()
                            onclick=self.link.callback(|_| Msg::ConfigTextDelete)>{ "Delete Secrets" }</button>
                    <button disabled=self.state.config_text.is_empty()
                            onclick=self.link.callback(|_| Msg::ConfigReady)>{ "Start Game" }</button>

                {footer()}
                </>
            },
            Scene::Playground(ref app) => {
                let secret = app.render_secret();
                let (cols, rows) = dimensions(&secret);
                let secret = secret.trim_end_matches("\n");
                let image = app.render_image();
                let image = image.trim_end_matches("\n");
                html! { <>
                    {header()}

                            <textarea class=("image")
                                placeholder="Image"
                                cols=format!("{}", &app.get_image_dimension().0)
                                rows=format!("{}", &app.get_image_dimension().1)
                                value=image
                                readonly=true
                            />

                            { app.render_game_lifes() } { " " }

                            { app.render_game_last_guess() }

                            <textarea class=("secret")
                                cols=cols+1
                                rows=rows
                                value=secret
                                readonly=true
                            />

                            { app.render_instructions() }
                            <input class="guess"
                                type="text"
                                autofocus=true
                                size=1
                                value=self.state.guess
                                oninput=self.link.callback(|e: InputData| Msg::UpdateGuess(e.value))
                                onkeypress=self.link.callback(|e: KeyboardEvent| {
                                   if e.key() == "Enter" { Msg::Guess } else { Msg::Nope }
                                }) />

                        <button disabled={app.get_state() == State::Ongoing || app.get_state() == State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::Guess)>{ "Continue Game" }</button>
                        <button disabled={app.get_state() != State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::SwitchTo(Scene::ConfigureGame))>{ "Reset Game" }</button>
                        <button disabled={app.get_state() != State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::SwitchTo(Scene::GameOver))>{ "End Game" }</button>

                    {footer()}
                    </>
                }
            }
            Scene::GameOver => html! { <>
                {header()}

 { "Bye bye!" }

                    <button onclick=self.link.callback(|_| Msg::SwitchTo(Scene::ConfigureGame))>{ "No, Continue Playing" }</button>

                {footer()}
                </>
            },
        }
    }
}

/// Returns the columns and lines of the smallest
/// grid that can display this multi-line string `s`.
pub fn dimensions(s: &str) -> (usize, usize) {
    let mut row = 0;
    let mut col = 0;
    for l in s.lines() {
        let c = l.chars().count();
        if c > col {
            col = c;
        };
        row += 1;
    }
    (col, row)
}

#[wasm_bindgen(start)]
pub fn run_app() {
    App::<Model>::new().mount_to_body();
}

#[cfg(test)]
mod tests {
    use super::*;
    use secret::Secret;
    #[test]
    fn test_dimensions() {
        let secret = Secret::new("Lorem ipsum dolor sit amet, consectetur adipiscing\
         elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\
         quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure\
         dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur\
         sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est\
         laborum.");

        //secret.disclose_all();

        assert_eq!(dimensions(format!("{}", secret).as_str()), (68, 22));
        //assert_eq!(format!("{}", secret), String::new());
    }
}
<commit_msg>DialogService became stateless<commit_after>//! This is the wasm web-gui.
//! This file is only compiled for the wasm32 target.

#![cfg(target_arch = "wasm32")]
#![recursion_limit = "512"]

mod application;
mod dictionary;
mod game;
mod image;
mod secret;

use crate::application::Application;
use crate::application::HangmanBackend;
use crate::application::{AUTHOR, CONF_TEMPLATE, TITLE, VERSION};
use crate::game::State;
use wasm_bindgen::prelude::*;
use yew::events::KeyboardEvent;
use yew::prelude::*;
use yew::services::reader::{File, FileData, ReaderService, ReaderTask};
// Disable debugging code.
//use yew::services::ConsoleService;
use yew::services::DialogService;
use yew::{html, Component, ComponentLink, Html, InputData, ShouldRender};

#[derive(Debug)]
pub enum Scene {
    Playground(Application),
    ConfigureGame,
    GameOver,
}

pub struct GuiState {
    config_text: String,
    guess: String,
}

pub struct Model {
    link: ComponentLink<Self>,
    reader: ReaderService,
    // Disable debugging code.
    //console: ConsoleService,
    filereader_tasks: Vec<ReaderTask>,
    scene: Scene,
    state: GuiState,
}

#[derive(Debug)]
pub enum Msg {
    SwitchTo(Scene),
    ConfigTextDelete,
    ConfigTextUpdate(String),
    ConfigReady,
    Files(Vec<File>),
    Loaded(FileData),
    UpdateGuess(String),
    Guess,
    Nope,
    NextRound,
}

impl Component for Model {
    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        let state = GuiState {
            config_text: String::from(CONF_TEMPLATE),
            guess: String::new(),
        };

        Model {
            link,
            reader: ReaderService::new(),
            // Disable debugging code.
            //console: ConsoleService::new(),
            filereader_tasks: vec![],
            scene: Scene::ConfigureGame,
            state,
        }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        let mut new_scene = None;
        match &mut self.scene {
            Scene::Playground(ref mut app) => match msg {
                Msg::SwitchTo(Scene::ConfigureGame) => {
                    new_scene = Some(Scene::ConfigureGame);
                }
                Msg::SwitchTo(Scene::GameOver) => {
                    new_scene = Some(Scene::GameOver);
                }
                Msg::UpdateGuess(val) => {
                    self.state.guess = val.chars().rev().take(1).collect();
                    // Disable debugging code.
                    //self.console.debug(&self.state.guess);
                }
                Msg::Guess => {
                    app.process_user_input(&self.state.guess);
                    self.state.guess = String::new();
                }
                Msg::Nope => {}
                unexpected => {
                    panic!(
                        "Unexpected message when configurations list shown: {:?}",
                        unexpected
                    );
                }
            },
            Scene::ConfigureGame => match msg {
                Msg::ConfigTextUpdate(val) => {
                    self.state.config_text = val;
                }
                Msg::ConfigTextDelete => {
                    self.state.config_text.clear();
                }
                Msg::SwitchTo(Scene::Playground(app)) => {
                    new_scene = Some(Scene::Playground(app));
                }

                Msg::SwitchTo(Scene::GameOver) => {
                    if DialogService::confirm("Do you really want to quit this game?") {
                        new_scene = Some(Scene::GameOver);
                    }
                }

                Msg::Loaded(file) => {
                    if let Ok(s) = std::str::from_utf8(&file.content) {
                        self.state.config_text.push_str(s);
                    } else {
                        DialogService::alert(&format!("Can not read text file: {}", file.name));
                    }
                }
                Msg::Files(files) => {
                    for file in files.into_iter() {
                        let task = {
                            let callback = self.link.callback(Msg::Loaded);
                            self.reader.read_file(file, callback).unwrap()
                        };
                        self.filereader_tasks.push(task);
                    }
                }
                Msg::ConfigReady => {
                    match Application::new(self.state.config_text.as_str()) {
                        Ok(app) => {
                            self.link
                                .send_message(Msg::SwitchTo(Scene::Playground(app)));
                        }
                        Err(e) => {
                            DialogService::alert(&format!("Can not parse configuration:\n {}", e))
                        }
                    };
                }
                unexpected => {
                    panic!(
                        "Unexpected message during new config editing: {:?}",
                        unexpected
                    );
                }
            },
            Scene::GameOver => match msg {
                Msg::SwitchTo(Scene::ConfigureGame) => {
                    new_scene = Some(Scene::ConfigureGame);
                }
                unexpected => {
                    panic!("Unexpected message for settings scene: {:?}", unexpected);
                }
            },
        }
        if let Some(new_scene) = new_scene.take() {
            self.scene = new_scene;
        }
        true
    }

    fn change(&mut self, _: Self::Properties) -> ShouldRender {
        false
    }

    fn view(&self) -> Html {
        let header = move || -> Html {
            html! {
 {TITLE}
 }
        };
        let footer = move || -> Html {
            html! {
             {"User Manual"}
            {", "}  {"Desktop Version"}
            {", "}  {"Documentation"}
            {", "}  {"Source Code"}
            {", Version "} {VERSION.unwrap()} {", "} {AUTHOR}   }
        };
        match self.scene {
            Scene::ConfigureGame => html! { <>
                {header()}

 {"Enter your secrets here:"}

                    <textarea class="conf-text"
                        placeholder=CONF_TEMPLATE
                        cols=80
                        rows=25
                        value=&self.state.config_text
                        oninput=self.link.callback(|e: InputData| Msg::ConfigTextUpdate(e.value)) />

 { "or load secrets from files: "}
                        {"Upload Files ..."}
                        <input class="custom-file-input" type="file" id="upload" multiple=true onchange=self.link.callback(move |value| {
                                let mut result = Vec::new();
                                if let ChangeData::Files(files) = value {
                                    let files = js_sys::try_iter(&files)
                                        .unwrap()
                                        .unwrap()
                                        .into_iter()
                                        .map(|v| File::from(v.unwrap()));
                                    result.extend(files);
                                }
                                Msg::Files(result)
                            })/>

                    <button disabled=self.state.config_text.is_empty()
                            onclick=self.link.callback(|_| Msg::ConfigTextDelete)>{ "Delete Secrets" }</button>
                    <button disabled=self.state.config_text.is_empty()
                            onclick=self.link.callback(|_| Msg::ConfigReady)>{ "Start Game" }</button>

                {footer()}
                </>
            },
            Scene::Playground(ref app) => {
                let secret = app.render_secret();
                let (cols, rows) = dimensions(&secret);
                let secret = secret.trim_end_matches("\n");
                let image = app.render_image();
                let image = image.trim_end_matches("\n");
                html! { <>
                    {header()}

                            <textarea class=("image")
                                placeholder="Image"
                                cols=format!("{}", &app.get_image_dimension().0)
                                rows=format!("{}", &app.get_image_dimension().1)
                                value=image
                                readonly=true
                            />

                            { app.render_game_lifes() } { " " }

                            { app.render_game_last_guess() }

                            <textarea class=("secret")
                                cols=cols+1
                                rows=rows
                                value=secret
                                readonly=true
                            />

                            { app.render_instructions() }
                            <input class="guess"
                                type="text"
                                autofocus=true
                                size=1
                                value=self.state.guess
                                oninput=self.link.callback(|e: InputData| Msg::UpdateGuess(e.value))
                                onkeypress=self.link.callback(|e: KeyboardEvent| {
                                   if e.key() == "Enter" { Msg::Guess } else { Msg::Nope }
                                }) />

                        <button disabled={app.get_state() == State::Ongoing || app.get_state() == State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::Guess)>{ "Continue Game" }</button>
                        <button disabled={app.get_state() != State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::SwitchTo(Scene::ConfigureGame))>{ "Reset Game" }</button>
                        <button disabled={app.get_state() != State::VictoryGameOver}
                                onclick=self.link.callback(|_| Msg::SwitchTo(Scene::GameOver))>{ "End Game" }</button>

                    {footer()}
                    </>
                }
            }
            Scene::GameOver => html! { <>
                {header()}

 { "Bye bye!" }

                    <button onclick=self.link.callback(|_| Msg::SwitchTo(Scene::ConfigureGame))>{ "No, Continue Playing" }</button>

                {footer()}
                </>
            },
        }
    }
}

/// Returns the columns and lines of the smallest
/// grid that can display this multi-line string `s`.
pub fn dimensions(s: &str) -> (usize, usize) {
    let mut row = 0;
    let mut col = 0;
    for l in s.lines() {
        let c = l.chars().count();
        if c > col {
            col = c;
        };
        row += 1;
    }
    (col, row)
}

#[wasm_bindgen(start)]
pub fn run_app() {
    App::<Model>::new().mount_to_body();
}

#[cfg(test)]
mod tests {
    use super::*;
    use secret::Secret;
    #[test]
    fn test_dimensions() {
        let secret = Secret::new("Lorem ipsum dolor sit amet, consectetur adipiscing\
         elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\
         quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure\
         dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur\
         sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est\
         laborum.");

        //secret.disclose_all();

        assert_eq!(dimensions(format!("{}", secret).as_str()), (68, 22));
        //assert_eq!(format!("{}", secret), String::new());
    }
}
<|endoftext|>


<commit_before>#![feature(test)]
extern crate crc;
extern crate memmap;
extern crate byteorder;
extern crate test;

use std::path::{Path, PathBuf};
use std::fs::{OpenOptions, File};
use crc::crc32::checksum_ieee;
use memmap::{Mmap, Protection};
use byteorder::{BigEndian, ByteOrder};
use std::io::{self, Write};

// TODO: ...
// pub type Offset(u64);

pub struct Message {
    crc: u32,
    payload: Vec<u8>,
}

impl Message {
    pub fn new(payload: Vec<u8>) -> Message {
        let payload_crc = checksum_ieee(&payload);
        Message {
            crc: payload_crc,
            payload: payload,
        }
    }

    #[inline]
    pub fn payload(&self) -> &[u8] {
        &self.payload
    }

    #[inline]
    pub fn crc(&self) -> u32 {
        self.crc
    }
}

pub struct MessageSet {
    bytes: Vec<u8>,
}

impl MessageSet {
    pub fn new(msgs: Vec<Message>, offset: u64) -> MessageSet {
        // encode bytes with
        // 8: offset
        // 4: size
        // .: messages
        let size: usize = msgs.iter().map(|m| 4 + m.payload().len()).sum();
        let mut bytes = vec![0; 12 + size];
        BigEndian::write_u64(&mut bytes[0..8], offset);
        BigEndian::write_u32(&mut bytes[8..12], size as u32);

        let mut pos = 12;
        for m in msgs.iter() {
            // serialize the message CRC32
            BigEndian::write_u32(&mut bytes[pos..pos+4], m.crc());
            pos += 4;

            let mut copy_slice = &mut bytes[pos..pos+m.payload.len()];

            // copy the bytes of the message payload
            copy_slice.write_all(m.payload()).unwrap();
            pos += m.payload.len();
        }

        MessageSet {
            bytes: bytes,
        }
    }

    #[inline]
    pub fn bytes(&self) -> &[u8] {
        &self.bytes
    }
}

/// An index is a file with pairs of relative offset to file position offset
/// messages. Both are stored as 4 bytes.
pub struct Index {
    file: File,
    mmap: Mmap,
    /// next starting byte in index file offset to write
    next_write_offset: usize,
    base_offset: u64,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct IndexEntry {
    rel_offset: u32,
    base_offset: u64,
    file_pos: u32,
}

impl IndexEntry {
    pub fn relative_offset(&self) -> u32 {
        self.rel_offset
    }

    #[inline]
    pub fn offset(&self) -> u64 {
        self.base_offset + (self.rel_offset as u64)
    }

    #[inline]
    pub fn file_position(&self) -> u32 {
        self.file_pos
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum IndexWriteError {
    IndexFull,
    OffsetLessThanBase,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum IndexReadError {
    OutOfBounds
}

impl Index {
    pub fn new<P>(path: P, file_bytes: u64, base_offset: u64) -> io::Result<Index>
        where P: AsRef<Path>
    {
        // open the file, expecting to create it
        let index_file = try!(OpenOptions::new()
            .read(true)
            .write(true)
            .append(true)
            .create(true)
            .open(path));

        // read the metadata and truncate
        let meta = try!(index_file.metadata());
        let len = meta.len();
        if len == 0 {
            try!(index_file.set_len(file_bytes));
        }

        let mmap = try!(Mmap::open(&index_file, Protection::ReadWrite));

        Ok(Index {
            file: index_file,
            mmap: mmap,
            next_write_offset: len as usize,
            base_offset: base_offset,
        })
    }

    #[inline]
    pub fn can_write(&self) -> bool {
        self.len() >= (self.next_write_offset + 8)
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.mmap.len()
    }

    pub fn append(&mut self, abs_offset: u64, position: u32) -> Result<(), IndexWriteError> {
        // TODO: test to ensure abs_offset is < last offset written (invariant of the index)
        if !self.can_write() {
            return Err(IndexWriteError::IndexFull);
        }

        if abs_offset < self.base_offset {
            return Err(IndexWriteError::OffsetLessThanBase);
        }

        unsafe {
            let mem_slice: &mut [u8] = self.mmap.as_mut_slice();
            let offset = (abs_offset - self.base_offset) as u32;
            let buf_pos = self.next_write_offset;

            BigEndian::write_u32(&mut mem_slice[buf_pos..buf_pos+4], offset);
            BigEndian::write_u32(&mut mem_slice[buf_pos+4..buf_pos+8], position);

            self.next_write_offset += 8;

            Ok(())
        }
    }

    pub fn flush_sync(&mut self) -> io::Result<()> {
        try!(self.mmap.flush());
        self.file.flush()
    }

    pub fn read_entry(&self, i: usize) -> Result<Option<IndexEntry>, IndexReadError> {
        if self.len() < (i+1)*8 {
            return Err(IndexReadError::OutOfBounds);
        }

        unsafe {
            let mem_slice = self.mmap.as_slice();
            let start = i*8;
            let offset = BigEndian::read_u32(&mem_slice[start..start+4]);
            if offset == 0 {
                Ok(None)
            } else {
                let pos = BigEndian::read_u32(&mem_slice[start+4..start+8]);
                Ok(Some(IndexEntry {
                    rel_offset: offset,
                    base_offset: self.base_offset,
                    file_pos: pos,
                }))
            }
        }
    }
}

pub struct Log {
    f: File,
    max_bytes: usize,
    pos: usize,
}

#[derive(Debug)]
pub enum LogAppendError {
    LogFull,
    IoError(io::Error),
}

impl From<std::io::Error> for LogAppendError {
    #[inline]
    fn from(e: std::io::Error) -> LogAppendError {
        LogAppendError::IoError(e)
    }
}

impl Log {
    pub fn new<P>(p: P, max_bytes: usize) -> io::Result<Log>
        where P: AsRef<Path>
    {
        let f = try!(OpenOptions::new()
                     .write(true)
                     .read(true)
                     .create(true)
                     .append(true)
                     .open(p));
        Ok(Log {
            f: f,
            max_bytes: max_bytes,
            pos: 0,
        })
    }

    pub fn can_write(&self) -> bool {
        self.pos < self.max_bytes
    }

    pub fn append(&mut self, msgs: MessageSet) -> Result<u32, LogAppendError> {
        // ensure we won't go over the max
        if msgs.bytes().len() + self.pos > self.max_bytes {
            return Err(LogAppendError::LogFull);
        }

        try!(self.f.write_all(msgs.bytes()));
        let write_pos = self.pos;
        self.pos += msgs.bytes().len();
        Ok(write_pos as u32)
    }

    pub fn flush(&mut self) -> io::Result<()> {
        self.f.flush()
    }
}

pub struct Segment {
    log: Log,
    index: Index,
    next_offset: u64,
}

#[derive(Debug)]
pub enum SegmentAppendError {
    LogFull,
    IndexError(IndexWriteError),
    IoError(io::Error),
}

impl From<io::Error> for SegmentAppendError {
    #[inline]
    fn from(e: io::Error) -> SegmentAppendError {
        SegmentAppendError::IoError(e)
    }
}

impl From<LogAppendError> for SegmentAppendError {
    #[inline]
    fn from(e: LogAppendError) -> SegmentAppendError {
        match e {
            LogAppendError::LogFull => SegmentAppendError::LogFull,
            LogAppendError::IoError(e) => SegmentAppendError::IoError(e),
        }
    }
}

impl From<IndexWriteError> for SegmentAppendError {
    #[inline]
    fn from(e: IndexWriteError) -> SegmentAppendError {
        SegmentAppendError::IndexError(e)
    }
}

impl Segment {
    // TODO: open variant for reading

    pub fn new<P>(log_dir: P, base_offset: u64, max_bytes: usize, index_bytes: usize) -> io::Result<Segment>
        where P: AsRef<Path>
    {
        let log = {
            let mut path_buf = PathBuf::new();
            path_buf.push(&log_dir);
            path_buf.push(format!("{:020}", base_offset));
            path_buf.set_extension("log");
            try!(Log::new(&path_buf, max_bytes))
        };

        let index = {
            let mut path_buf = PathBuf::new();
            path_buf.push(&log_dir);
            path_buf.push(format!("{:020}", base_offset));
            path_buf.set_extension("index");
            try!(Index::new(&path_buf, index_bytes as u64, base_offset))
        };

        Ok(Segment {
            log: log,
            index: index,
            next_offset: base_offset,
        })
    }

    #[inline]
    pub fn next_offset(&self) -> u64{
        self.next_offset
    }

    pub fn append(&mut self, msgs: Vec<Message>) -> Result<u64, SegmentAppendError> {
        if !self.index.can_write() || !self.log.can_write() {
            return Err(SegmentAppendError::LogFull);
        }

        let off = self.next_offset;
        let pos = try!(self.log.append(MessageSet::new(msgs, self.next_offset)));
        try!(self.index.append(off, pos));

        self.next_offset += 1;
        Ok(off)
    }

    // TODO: async flush strategy
    pub fn flush_sync(&mut self) -> io::Result<()> {
        try!(self.index.flush_sync());
        self.log.flush()
    }

}

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

    #[test]
    fn message_construction() {
        let msg = Message::new(b"123456789".to_vec());
        assert_eq!(msg.payload(), b"123456789");
        assert_eq!(msg.crc(), 0xcbf43926);
    }

    #[test]
    fn index() {
        let index_path = "target/.test-index";

        // remove before running (may be around from failing test run)
        fs::remove_file(index_path).unwrap_or(());

        {
            let mut index = Index::new(index_path, 1000u64, 10u64).unwrap();
            assert_eq!(1000, index.len());
            index.append(11u64, 0xffff).unwrap();
            index.append(12u64, 0xeeee).unwrap();
            index.flush_sync().unwrap();
        }

        // reopen it for read
        {
            let index = Index::new(index_path, 1000u64, 10u64).unwrap();

            let e0 = index.read_entry(0).unwrap().unwrap();
            assert_eq!(1u32, e0.relative_offset());
            assert_eq!(11u64, e0.offset());
            assert_eq!(0xffff, e0.file_position());

            let e1 = index.read_entry(1).unwrap().unwrap();
            assert_eq!(2u32, e1.relative_offset());
            assert_eq!(12u64, e1.offset());
            assert_eq!(0xeeee, e1.file_position());

            // read an entry that does not exist
            let e2 = index.read_entry(2).unwrap();
            assert_eq!(None, e2);
        }
        fs::remove_file(index_path).unwrap();
    }

    #[test]
    pub fn log() {
        let log_path = "target/.test-log";
        fs::remove_file(log_path);
        {
            let mut f = Log::new(log_path, 1024).unwrap();

            let m0 = Message::new(b"12345".to_vec());
            let ms0 = MessageSet::new(vec![m0], 1000);
            assert_eq!(ms0.bytes().len(), 21);
            let p0 = f.append(ms0).unwrap();
            assert_eq!(p0, 0);

            let m1 = Message::new(b"66666".to_vec());
            let m2 = Message::new(b"77777".to_vec());
            let p1 = f.append(MessageSet::new(vec![m1, m2], 1001)).unwrap();
            assert_eq!(p1, 21);

            f.flush().unwrap();
        }
        fs::remove_file(log_path);
    }

    #[bench]
    fn bench_segment_append(b: &mut Bencher) {
        let log_path = "target/test-log";
        fs::remove_dir_all(log_path).unwrap_or(());
        fs::create_dir_all(log_path).unwrap_or(());

        let mut seg = Segment::new(log_path, 100u64, 100*1024*1024, 10*1024*1024).unwrap();
        let buf = b"01234567891011121314151617181920";

        b.iter(|| {
            seg.append(vec![Message::new(buf.to_vec())]).unwrap();
        });

        fs::remove_dir_all(log_path).unwrap_or(());
    }
}
<commit_msg>Removing MessageSet<commit_after>#![feature(test)]
extern crate crc;
extern crate memmap;
extern crate byteorder;
extern crate test;

use std::path::{Path, PathBuf};
use std::fs::{OpenOptions, File};
use crc::crc32::checksum_ieee;
use memmap::{Mmap, Protection};
use byteorder::{BigEndian, ByteOrder};
use std::io::{self, Write};

// TODO: ...
// pub type Offset(u64);

/// Messages are appended to the log with the following encoding:
///
/// +-----------+--------------+
/// | Bytes     | Value        |
/// +-----------+--------------+
/// | 0-7       | Offset       |
/// | 8-11      | Payload Size |
/// | 12-15     | CRC32 (IEEE) |
/// | 16+       | Payload      |
/// +-----------+--------------+
pub struct Message {
    bytes: Vec<u8>,
}

impl Message {
    pub fn new(payload: &[u8], offset: u64) -> Message {
        let mut bytes = vec![0; 16 + payload.len()];
        BigEndian::write_u64(&mut bytes[0..8], offset);
        BigEndian::write_u32(&mut bytes[8..12], payload.len() as u32);
        BigEndian::write_u32(&mut bytes[12..16], checksum_ieee(&payload));
        bytes[16..].copy_from_slice(payload);
        Message {
            bytes: bytes,
        }
    }

    #[inline]
    pub fn bytes(&self) -> &[u8] {
        &self.bytes
    }

    #[inline]
    pub fn crc(&self) -> u32 {
        BigEndian::read_u32(&self.bytes[12..16])
    }

    #[inline]
    pub fn size(&self) -> u32 {
        BigEndian::read_u32(&self.bytes[8..12])
    }

    #[inline]
    pub fn offset(&self) -> u64 {
        BigEndian::read_u64(&self.bytes[0..8])
    }

    #[inline]
    pub fn payload(&self) -> &[u8] {
        &self.bytes[16..]
    }
}

/// An index is a file with pairs of relative offset to file position offset
/// messages. Both are stored as 4 bytes.
pub struct Index {
    file: File,
    mmap: Mmap,

    /// next starting byte in index file offset to write
    next_write_offset: usize,
    base_offset: u64,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct IndexEntry {
    rel_offset: u32,
    base_offset: u64,
    file_pos: u32,
}

impl IndexEntry {
    pub fn relative_offset(&self) -> u32 {
        self.rel_offset
    }

    #[inline]
    pub fn offset(&self) -> u64 {
        self.base_offset + (self.rel_offset as u64)
    }

    #[inline]
    pub fn file_position(&self) -> u32 {
        self.file_pos
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum IndexWriteError {
    IndexFull,
    OffsetLessThanBase,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum IndexReadError {
    OutOfBounds
}

impl Index {
    pub fn new<P>(path: P, file_bytes: u64, base_offset: u64) -> io::Result<Index>
        where P: AsRef<Path>
    {
        // open the file, expecting to create it
        let index_file = try!(OpenOptions::new()
            .read(true)
            .write(true)
            .append(true)
            .create(true)
            .open(path));

        // read the metadata and truncate
        let meta = try!(index_file.metadata());
        let len = meta.len();
        if len == 0 {
            try!(index_file.set_len(file_bytes));
        }

        let mmap = try!(Mmap::open(&index_file, Protection::ReadWrite));

        Ok(Index {
            file: index_file,
            mmap: mmap,
            next_write_offset: len as usize,
            base_offset: base_offset,
        })
    }

    #[inline]
    pub fn can_write(&self) -> bool {
        self.len() >= (self.next_write_offset + 8)
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.mmap.len()
    }

    pub fn append(&mut self, abs_offset: u64, position: u32) -> Result<(), IndexWriteError> {
        // TODO: test to ensure abs_offset is < last offset written (invariant of the index)
        if !self.can_write() {
            return Err(IndexWriteError::IndexFull);
        }

        if abs_offset < self.base_offset {
            return Err(IndexWriteError::OffsetLessThanBase);
        }

        unsafe {
            let mem_slice: &mut [u8] = self.mmap.as_mut_slice();
            let offset = (abs_offset - self.base_offset) as u32;
            let buf_pos = self.next_write_offset;

            BigEndian::write_u32(&mut mem_slice[buf_pos..buf_pos+4], offset);
            BigEndian::write_u32(&mut mem_slice[buf_pos+4..buf_pos+8], position);

            self.next_write_offset += 8;

            Ok(())
        }
    }

    pub fn flush_sync(&mut self) -> io::Result<()> {
        try!(self.mmap.flush());
        self.file.flush()
    }

    pub fn read_entry(&self, i: usize) -> Result<Option<IndexEntry>, IndexReadError> {
        if self.len() < (i+1)*8 {
            return Err(IndexReadError::OutOfBounds);
        }

        unsafe {
            let mem_slice = self.mmap.as_slice();
            let start = i*8;
            let offset = BigEndian::read_u32(&mem_slice[start..start+4]);
            if offset == 0 && i > 0 {
                Ok(None)
            } else {
                let pos = BigEndian::read_u32(&mem_slice[start+4..start+8]);
                Ok(Some(IndexEntry {
                    rel_offset: offset,
                    base_offset: self.base_offset,
                    file_pos: pos,
                }))
            }
        }
    }
}

pub struct Log {
    f: File,
    max_bytes: usize,
    pos: usize,
}

#[derive(Debug)]
pub enum LogAppendError {
    LogFull,
    IoError(io::Error),
}

impl From<std::io::Error> for LogAppendError {
    #[inline]
    fn from(e: std::io::Error) -> LogAppendError {
        LogAppendError::IoError(e)
    }
}

impl Log {
    pub fn new<P>(p: P, max_bytes: usize) -> io::Result<Log>
        where P: AsRef<Path>
    {
        let f = try!(OpenOptions::new()
                     .write(true)
                     .read(true)
                     .create(true)
                     .append(true)
                     .open(p));
        Ok(Log {
            f: f,
            max_bytes: max_bytes,
            pos: 0,
        })
    }

    pub fn can_write(&self) -> bool {
        self.pos < self.max_bytes
    }

    pub fn append(&mut self, msg: Message) -> Result<u32, LogAppendError> {
        // ensure we won't go over the max
        if msg.bytes().len() + self.pos > self.max_bytes {
            return Err(LogAppendError::LogFull);
        }

        try!(self.f.write_all(msg.bytes()));
        let write_pos = self.pos;
        self.pos += msg.bytes().len();
        Ok(write_pos as u32)
    }

    pub fn flush(&mut self) -> io::Result<()> {
        self.f.flush()
    }
}

pub struct Segment {
    log: Log,
    index: Index,
    next_offset: u64,
}

#[derive(Debug)]
pub enum SegmentAppendError {
    LogFull,
    IndexError(IndexWriteError),
    IoError(io::Error),
}

impl From<io::Error> for SegmentAppendError {
    #[inline]
    fn from(e: io::Error) -> SegmentAppendError {
        SegmentAppendError::IoError(e)
    }
}

impl From<LogAppendError> for SegmentAppendError {
    #[inline]
    fn from(e: LogAppendError) -> SegmentAppendError {
        match e {
            LogAppendError::LogFull => SegmentAppendError::LogFull,
            LogAppendError::IoError(e) => SegmentAppendError::IoError(e),
        }
    }
}

impl From<IndexWriteError> for SegmentAppendError {
    #[inline]
    fn from(e: IndexWriteError) -> SegmentAppendError {
        SegmentAppendError::IndexError(e)
    }
}

impl Segment {
    // TODO: open variant for reading

    pub fn new<P>(log_dir: P, base_offset: u64, max_bytes: usize, index_bytes: usize) -> io::Result<Segment>
        where P: AsRef<Path>
    {
        let log = {
            let mut path_buf = PathBuf::new();
            path_buf.push(&log_dir);
            path_buf.push(format!("{:020}", base_offset));
            path_buf.set_extension("log");
            try!(Log::new(&path_buf, max_bytes))
        };

        let index = {
            let mut path_buf = PathBuf::new();
            path_buf.push(&log_dir);
            path_buf.push(format!("{:020}", base_offset));
            path_buf.set_extension("index");
            try!(Index::new(&path_buf, index_bytes as u64, base_offset))
        };

        Ok(Segment {
            log: log,
            index: index,
            next_offset: base_offset,
        })
    }

    #[inline]
    pub fn next_offset(&self) -> u64{
        self.next_offset
    }

    pub fn append(&mut self, msg: &[u8]) -> Result<u64, SegmentAppendError> {
        if !self.index.can_write() || !self.log.can_write() {
            return Err(SegmentAppendError::LogFull);
        }

        let off = self.next_offset;
        let pos = try!(self.log.append(Message::new(msg, off)));
        try!(self.index.append(off, pos));

        self.next_offset += 1;
        Ok(off)
    }

    // TODO: async flush strategy
    pub fn flush_sync(&mut self) -> io::Result<()> {
        try!(self.index.flush_sync());
        self.log.flush()
    }

}

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

    #[test]
    fn message_construction() {
        let msg = Message::new(b"123456789", 1234567u64);
        assert_eq!(msg.offset(), 1234567u64);
        assert_eq!(msg.payload(), b"123456789");
        assert_eq!(msg.crc(), 0xcbf43926);
        assert_eq!(msg.size(), 9u32);
    }

    #[test]
    fn index() {
        let index_path = "target/.test-index";

        // remove before running (may be around from failing test run)
        fs::remove_file(index_path).unwrap_or(());

        {
            let mut index = Index::new(index_path, 1000u64, 10u64).unwrap();
            assert_eq!(1000, index.len());
            index.append(11u64, 0xffff).unwrap();
            index.append(12u64, 0xeeee).unwrap();
            index.flush_sync().unwrap();
        }

        // reopen it for read
        {
            let index = Index::new(index_path, 1000u64, 10u64).unwrap();

            let e0 = index.read_entry(0).unwrap().unwrap();
            assert_eq!(1u32, e0.relative_offset());
            assert_eq!(11u64, e0.offset());
            assert_eq!(0xffff, e0.file_position());

            let e1 = index.read_entry(1).unwrap().unwrap();
            assert_eq!(2u32, e1.relative_offset());
            assert_eq!(12u64, e1.offset());
            assert_eq!(0xeeee, e1.file_position());

            // read an entry that does not exist
            let e2 = index.read_entry(2).unwrap();
            assert_eq!(None, e2);
        }
        fs::remove_file(index_path).unwrap();
    }

    #[test]
    pub fn log() {
        let log_path = "target/.test-log";
        fs::remove_file(log_path).unwrap_or(());
        {
            let mut f = Log::new(log_path, 1024).unwrap();

            let m0 = Message::new(b"12345", 1000);
            assert_eq!(m0.bytes().len(), 21);
            let p0 = f.append(m0).unwrap();
            assert_eq!(p0, 0);

            let m1 = Message::new(b"66666", 1001);
            let p1 = f.append(m1).unwrap();
            assert_eq!(p1, 21);

            f.flush().unwrap();
        }
        fs::remove_file(log_path).unwrap();
    }

    #[bench]
    fn bench_segment_append(b: &mut Bencher) {
        let log_path = "target/test-log";
        fs::remove_dir_all(log_path).unwrap_or(());
        fs::create_dir_all(log_path).unwrap_or(());

        let mut seg = Segment::new(log_path, 100u64, 100*1024*1024, 10*1024*1024).unwrap();
        let buf = b"01234567891011121314151617181920";

        b.iter(|| {
            seg.append(buf).unwrap();
        });

        fs::remove_dir_all(log_path).unwrap_or(());
    }
}
<|endoftext|>


<commit_before>//! Use this library to open a path or URL using the program configured on the system.
//!
//! # Usage
//!
//! Open the given URL in the default web browser.
//!
//! ```no_run
//! open::that("http://rust-lang.org").unwrap();
//! ```
//!
//! Alternatively, specify the program to be used to open the path or URL.
//!
//! ```no_run
//! open::with("http://rust-lang.org", "firefox").unwrap();
//! ```
//!
//! # Notes
//!
//! As an operating system program is used, the open operation can fail.
//! Therefore, you are advised to at least check the result and behave
//! accordingly, e.g. by letting the user know that the open operation failed.
//!
//! ```no_run
//! let path = "http://rust-lang.org";
//!
//! match open::that(path) {
//!     Ok(()) => println!("Opened '{}' successfully.", path),
//!     Err(err) => eprintln!("An error occurred when opening '{}': {}", path, err),
//! }
//! ```

#[cfg(target_os = "windows")]
use windows as os;

#[cfg(target_os = "macos")]
use macos as os;

#[cfg(target_os = "ios")]
use ios as os;

#[cfg(any(
    target_os = "linux",
    target_os = "android",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "illumos",
    target_os = "solaris"
))]
use unix as os;

#[cfg(not(any(
    target_os = "linux",
    target_os = "android",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "illumos",
    target_os = "solaris",
    target_os = "ios",
    target_os = "macos",
    target_os = "windows",
)))]
compile_error!("open is not supported on this platform");

use std::{
    ffi::OsStr,
    io,
    process::{Command, Output, Stdio},
    thread,
};

type Result = io::Result<()>;

/// Open path with the default application.
///
/// # Examples
///
/// ```no_run
/// let path = "http://rust-lang.org";
///
/// match open::that(path) {
///     Ok(()) => println!("Opened '{}' successfully.", path),
///     Err(err) => panic!("An error occurred when opening '{}': {}", path, err),
/// }
/// ```
///
/// # Errors
///
/// A [`std::io::Error`] is returned on failure. Because different operating systems
/// handle errors differently it is recommend to not match on a certain error.
pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
    os::that(path)
}

/// Open path with the given application.
///
/// # Examples
///
/// ```no_run
/// let path = "http://rust-lang.org";
/// let app = "firefox";
///
/// match open::with(path, app) {
///     Ok(()) => println!("Opened '{}' successfully.", path),
///     Err(err) => panic!("An error occurred when opening '{}': {}", path, err),
/// }
/// ```
///
/// # Errors
///
/// A [`std::io::Error`] is returned on failure. Because different operating systems
/// handle errors differently it is recommend to not match on a certain error.
pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
    os::with(path, app)
}

/// Open path with the default application in a new thread.
///
/// See documentation of [`that`] for more details.
pub fn that_in_background<T: AsRef<OsStr>>(path: T) -> thread::JoinHandle<Result> {
    let path = path.as_ref().to_os_string();
    thread::spawn(|| that(path))
}

/// Open path with the given application in a new thread.
///
/// See documentation of [`with`] for more details.
pub fn with_in_background<T: AsRef<OsStr>>(
    path: T,
    app: impl Into<String>,
) -> thread::JoinHandle<Result> {
    let path = path.as_ref().to_os_string();
    let app = app.into();
    thread::spawn(|| with(path, app))
}

trait IntoResult<T> {
    fn into_result(self) -> T;
}

impl IntoResult<Result> for io::Result<Output> {
    fn into_result(self) -> Result {
        match self {
            Ok(o) if o.status.success() => Ok(()),
            Ok(o) => Err(from_output(o)),
            Err(err) => Err(err),
        }
    }
}

#[cfg(windows)]
impl IntoResult<Result> for winapi::ctypes::c_int {
    fn into_result(self) -> Result {
        match self {
            i if i > 32 => Ok(()),
            _ => Err(io::Error::last_os_error()),
        }
    }
}

fn from_output(output: Output) -> io::Error {
    let error_msg = match output.stderr.is_empty() {
        true => output.status.to_string(),
        false => format!(
            "{} ({})",
            String::from_utf8_lossy(&output.stderr).trim(),
            output.status
        ),
    };

    io::Error::new(io::ErrorKind::Other, error_msg)
}

trait CommandExt {
    fn output_stderr(&mut self) -> io::Result<Output>;
}

impl CommandExt for Command {
    fn output_stderr(&mut self) -> io::Result<Output> {
        let mut process = self
            .stdin(Stdio::null())
            .stdout(Stdio::null())
            .stderr(Stdio::piped())
            .spawn()?;

        // Consume all stderr - it's open just for a few programs which can't handle it being closed.
        use std::io::Read;
        let mut stderr = vec![0; 256];
        let mut stderr_src = process.stderr.take().expect("piped stderr");

        let len = stderr_src.read(&mut stderr).unwrap_or(0);
        stderr.truncate(len);

        // consume the rest to avoid blocking
        std::io::copy(&mut stderr_src, &mut std::io::sink()).ok();

        let status = process.wait()?;
        Ok(Output {
            status,
            stderr,
            stdout: vec![],
        })
    }
}

#[cfg(windows)]
mod windows {
    use std::{ffi::OsStr, io, os::windows::ffi::OsStrExt, ptr};

    use winapi::ctypes::c_int;
    use winapi::um::shellapi::ShellExecuteW;

    use crate::{IntoResult, Result};

    fn convert_path(path: &OsStr) -> io::Result<Vec<u16>> {
        let mut maybe_result: Vec<_> = path.encode_wide().collect();
        if maybe_result.iter().any(|&u| u == 0) {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "path contains NUL byte(s)",
            ));
        }
        maybe_result.push(0);
        Ok(maybe_result)
    }

    pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
        const SW_SHOW: c_int = 5;

        let path = convert_path(path.as_ref())?;
        let operation: Vec<u16> = OsStr::new("open\0").encode_wide().collect();
        let result = unsafe {
            ShellExecuteW(
                ptr::null_mut(),
                operation.as_ptr(),
                path.as_ptr(),
                ptr::null(),
                ptr::null(),
                SW_SHOW,
            )
        };
        (result as c_int).into_result()
    }

    pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
        const SW_SHOW: c_int = 5;

        let path = convert_path(path.as_ref())?;
        let operation: Vec<u16> = OsStr::new("open\0").encode_wide().collect();
        let app_name: Vec<u16> = OsStr::new(&format!("{}\0", app.into()))
            .encode_wide()
            .collect();
        let result = unsafe {
            ShellExecuteW(
                ptr::null_mut(),
                operation.as_ptr(),
                app_name.as_ptr(),
                path.as_ptr(),
                ptr::null(),
                SW_SHOW,
            )
        };
        (result as c_int).into_result()
    }
}

#[cfg(target_os = "macos")]
mod macos {
    use std::{ffi::OsStr, process::Command};

    use crate::{CommandExt, IntoResult, Result};

    pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
        Command::new("/usr/bin/open")
            .arg(path.as_ref())
            .output_stderr()
            .into_result()
    }

    pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
        Command::new("/usr/bin/open")
            .arg(path.as_ref())
            .arg("-a")
            .arg(app.into())
            .output_stderr()
            .into_result()
    }
}

#[cfg(target_os = "ios")]
mod ios {
    use std::{ffi::OsStr, process::Command};

    use crate::{CommandExt, IntoResult, Result};

    pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
        Command::new("uiopen")
            .arg("--url")
            .arg(path.as_ref())
            .output_stderr()
            .into_result()
    }

    pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
        Command::new("uiopen")
            .arg("--url")
            .arg(path.as_ref())
            .arg("--bundleid")
            .arg(app.into())
            .output_stderr()
            .into_result()
    }
}

#[cfg(any(
    target_os = "linux",
    target_os = "android",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "illumos",
    target_os = "solaris"
))]
mod unix {
    use std::{
        env,
        ffi::{OsStr, OsString},
        path::{Path, PathBuf},
        process::Command,
    };

    use crate::{CommandExt, IntoResult, Result};

    pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
        let path = path.as_ref();
        let open_handlers = [
            ("xdg-open", &[path] as &[_]),
            ("gio", &[OsStr::new("open"), path]),
            ("gnome-open", &[path]),
            ("kde-open", &[path]),
            ("wslview", &[&wsl_path(path)]),
        ];

        let mut unsuccessful = None;
        let mut io_error = None;

        for (command, args) in &open_handlers {
            let result = Command::new(command).args(*args).output_stderr();

            match result {
                Ok(o) if o.status.success() => return Ok(()),
                Ok(o) => unsuccessful = unsuccessful.or_else(|| Some(crate::from_output(o))),
                Err(err) => io_error = io_error.or(Some(err)),
            }
        }

        Err(unsuccessful
            .or(io_error)
            .expect("successful cases don't get here"))
    }

    pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
        Command::new(app.into())
            .arg(path.as_ref())
            .output_stderr()
            .into_result()
    }

    // Polyfill to workaround absolute path bug in wslu(wslview). In versions before
    // v3.1.1, wslview is unable to find absolute paths. `wsl_path` converts an
    // absolute path into a relative path starting from the current directory. If
    // the path is already a relative path or the conversion fails the original path
    // is returned.
    fn wsl_path<T: AsRef<OsStr>>(path: T) -> OsString {
        fn path_relative_to_current_dir<T: AsRef<OsStr>>(path: T) -> Option<PathBuf> {
            let path = Path::new(&path);

            if path.is_relative() {
                return None;
            }

            let base = env::current_dir().ok()?;
            pathdiff::diff_paths(path, base)
        }

        match path_relative_to_current_dir(&path) {
            None => OsString::from(&path),
            Some(relative) => OsString::from(relative),
        }
    }
}
<commit_msg>haiku: add platform support<commit_after>//! Use this library to open a path or URL using the program configured on the system.
//!
//! # Usage
//!
//! Open the given URL in the default web browser.
//!
//! ```no_run
//! open::that("http://rust-lang.org").unwrap();
//! ```
//!
//! Alternatively, specify the program to be used to open the path or URL.
//!
//! ```no_run
//! open::with("http://rust-lang.org", "firefox").unwrap();
//! ```
//!
//! # Notes
//!
//! As an operating system program is used, the open operation can fail.
//! Therefore, you are advised to at least check the result and behave
//! accordingly, e.g. by letting the user know that the open operation failed.
//!
//! ```no_run
//! let path = "http://rust-lang.org";
//!
//! match open::that(path) {
//!     Ok(()) => println!("Opened '{}' successfully.", path),
//!     Err(err) => eprintln!("An error occurred when opening '{}': {}", path, err),
//! }
//! ```

#[cfg(target_os = "windows")]
use windows as os;

#[cfg(target_os = "macos")]
use macos as os;

#[cfg(target_os = "ios")]
use ios as os;

#[cfg(target_os = "haiku")]
use haiku as os;

#[cfg(any(
    target_os = "linux",
    target_os = "android",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "illumos",
    target_os = "solaris"
))]
use unix as os;

#[cfg(not(any(
    target_os = "linux",
    target_os = "android",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "illumos",
    target_os = "solaris",
    target_os = "ios",
    target_os = "macos",
    target_os = "windows",
    target_os = "haiku"
)))]
compile_error!("open is not supported on this platform");

use std::{
    ffi::OsStr,
    io,
    process::{Command, Output, Stdio},
    thread,
};

type Result = io::Result<()>;

/// Open path with the default application.
///
/// # Examples
///
/// ```no_run
/// let path = "http://rust-lang.org";
///
/// match open::that(path) {
///     Ok(()) => println!("Opened '{}' successfully.", path),
///     Err(err) => panic!("An error occurred when opening '{}': {}", path, err),
/// }
/// ```
///
/// # Errors
///
/// A [`std::io::Error`] is returned on failure. Because different operating systems
/// handle errors differently it is recommend to not match on a certain error.
pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
    os::that(path)
}

/// Open path with the given application.
///
/// # Examples
///
/// ```no_run
/// let path = "http://rust-lang.org";
/// let app = "firefox";
///
/// match open::with(path, app) {
///     Ok(()) => println!("Opened '{}' successfully.", path),
///     Err(err) => panic!("An error occurred when opening '{}': {}", path, err),
/// }
/// ```
///
/// # Errors
///
/// A [`std::io::Error`] is returned on failure. Because different operating systems
/// handle errors differently it is recommend to not match on a certain error.
pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
    os::with(path, app)
}

/// Open path with the default application in a new thread.
///
/// See documentation of [`that`] for more details.
pub fn that_in_background<T: AsRef<OsStr>>(path: T) -> thread::JoinHandle<Result> {
    let path = path.as_ref().to_os_string();
    thread::spawn(|| that(path))
}

/// Open path with the given application in a new thread.
///
/// See documentation of [`with`] for more details.
pub fn with_in_background<T: AsRef<OsStr>>(
    path: T,
    app: impl Into<String>,
) -> thread::JoinHandle<Result> {
    let path = path.as_ref().to_os_string();
    let app = app.into();
    thread::spawn(|| with(path, app))
}

trait IntoResult<T> {
    fn into_result(self) -> T;
}

impl IntoResult<Result> for io::Result<Output> {
    fn into_result(self) -> Result {
        match self {
            Ok(o) if o.status.success() => Ok(()),
            Ok(o) => Err(from_output(o)),
            Err(err) => Err(err),
        }
    }
}

#[cfg(windows)]
impl IntoResult<Result> for winapi::ctypes::c_int {
    fn into_result(self) -> Result {
        match self {
            i if i > 32 => Ok(()),
            _ => Err(io::Error::last_os_error()),
        }
    }
}

fn from_output(output: Output) -> io::Error {
    let error_msg = match output.stderr.is_empty() {
        true => output.status.to_string(),
        false => format!(
            "{} ({})",
            String::from_utf8_lossy(&output.stderr).trim(),
            output.status
        ),
    };

    io::Error::new(io::ErrorKind::Other, error_msg)
}

trait CommandExt {
    fn output_stderr(&mut self) -> io::Result<Output>;
}

impl CommandExt for Command {
    fn output_stderr(&mut self) -> io::Result<Output> {
        let mut process = self
            .stdin(Stdio::null())
            .stdout(Stdio::null())
            .stderr(Stdio::piped())
            .spawn()?;

        // Consume all stderr - it's open just for a few programs which can't handle it being closed.
        use std::io::Read;
        let mut stderr = vec![0; 256];
        let mut stderr_src = process.stderr.take().expect("piped stderr");

        let len = stderr_src.read(&mut stderr).unwrap_or(0);
        stderr.truncate(len);

        // consume the rest to avoid blocking
        std::io::copy(&mut stderr_src, &mut std::io::sink()).ok();

        let status = process.wait()?;
        Ok(Output {
            status,
            stderr,
            stdout: vec![],
        })
    }
}

#[cfg(windows)]
mod windows {
    use std::{ffi::OsStr, io, os::windows::ffi::OsStrExt, ptr};

    use winapi::ctypes::c_int;
    use winapi::um::shellapi::ShellExecuteW;

    use crate::{IntoResult, Result};

    fn convert_path(path: &OsStr) -> io::Result<Vec<u16>> {
        let mut maybe_result: Vec<_> = path.encode_wide().collect();
        if maybe_result.iter().any(|&u| u == 0) {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "path contains NUL byte(s)",
            ));
        }
        maybe_result.push(0);
        Ok(maybe_result)
    }

    pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
        const SW_SHOW: c_int = 5;

        let path = convert_path(path.as_ref())?;
        let operation: Vec<u16> = OsStr::new("open\0").encode_wide().collect();
        let result = unsafe {
            ShellExecuteW(
                ptr::null_mut(),
                operation.as_ptr(),
                path.as_ptr(),
                ptr::null(),
                ptr::null(),
                SW_SHOW,
            )
        };
        (result as c_int).into_result()
    }

    pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
        const SW_SHOW: c_int = 5;

        let path = convert_path(path.as_ref())?;
        let operation: Vec<u16> = OsStr::new("open\0").encode_wide().collect();
        let app_name: Vec<u16> = OsStr::new(&format!("{}\0", app.into()))
            .encode_wide()
            .collect();
        let result = unsafe {
            ShellExecuteW(
                ptr::null_mut(),
                operation.as_ptr(),
                app_name.as_ptr(),
                path.as_ptr(),
                ptr::null(),
                SW_SHOW,
            )
        };
        (result as c_int).into_result()
    }
}

#[cfg(target_os = "macos")]
mod macos {
    use std::{ffi::OsStr, process::Command};

    use crate::{CommandExt, IntoResult, Result};

    pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
        Command::new("/usr/bin/open")
            .arg(path.as_ref())
            .output_stderr()
            .into_result()
    }

    pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
        Command::new("/usr/bin/open")
            .arg(path.as_ref())
            .arg("-a")
            .arg(app.into())
            .output_stderr()
            .into_result()
    }
}

#[cfg(target_os = "ios")]
mod ios {
    use std::{ffi::OsStr, process::Command};

    use crate::{CommandExt, IntoResult, Result};

    pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
        Command::new("uiopen")
            .arg("--url")
            .arg(path.as_ref())
            .output_stderr()
            .into_result()
    }

    pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
        Command::new("uiopen")
            .arg("--url")
            .arg(path.as_ref())
            .arg("--bundleid")
            .arg(app.into())
            .output_stderr()
            .into_result()
    }
}

#[cfg(target_os = "haiku")]
mod haiku {
    use std::{ffi::OsStr, process::Command};

    use crate::{CommandExt, IntoResult, Result};

    pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
        Command::new("/bin/open")
            .arg(path.as_ref())
            .output_stderr()
            .into_result()
    }

    pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
        Command::new(app.into())
            .arg(path.as_ref())
            .output_stderr()
            .into_result()
    }
}

#[cfg(any(
    target_os = "linux",
    target_os = "android",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "illumos",
    target_os = "solaris"
))]
mod unix {
    use std::{
        env,
        ffi::{OsStr, OsString},
        path::{Path, PathBuf},
        process::Command,
    };

    use crate::{CommandExt, IntoResult, Result};

    pub fn that<T: AsRef<OsStr>>(path: T) -> Result {
        let path = path.as_ref();
        let open_handlers = [
            ("xdg-open", &[path] as &[_]),
            ("gio", &[OsStr::new("open"), path]),
            ("gnome-open", &[path]),
            ("kde-open", &[path]),
            ("wslview", &[&wsl_path(path)]),
        ];

        let mut unsuccessful = None;
        let mut io_error = None;

        for (command, args) in &open_handlers {
            let result = Command::new(command).args(*args).output_stderr();

            match result {
                Ok(o) if o.status.success() => return Ok(()),
                Ok(o) => unsuccessful = unsuccessful.or_else(|| Some(crate::from_output(o))),
                Err(err) => io_error = io_error.or(Some(err)),
            }
        }

        Err(unsuccessful
            .or(io_error)
            .expect("successful cases don't get here"))
    }

    pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Result {
        Command::new(app.into())
            .arg(path.as_ref())
            .output_stderr()
            .into_result()
    }

    // Polyfill to workaround absolute path bug in wslu(wslview). In versions before
    // v3.1.1, wslview is unable to find absolute paths. `wsl_path` converts an
    // absolute path into a relative path starting from the current directory. If
    // the path is already a relative path or the conversion fails the original path
    // is returned.
    fn wsl_path<T: AsRef<OsStr>>(path: T) -> OsString {
        fn path_relative_to_current_dir<T: AsRef<OsStr>>(path: T) -> Option<PathBuf> {
            let path = Path::new(&path);

            if path.is_relative() {
                return None;
            }

            let base = env::current_dir().ok()?;
            pathdiff::diff_paths(path, base)
        }

        match path_relative_to_current_dir(&path) {
            None => OsString::from(&path),
            Some(relative) => OsString::from(relative),
        }
    }
}
<|endoftext|>