<commit_before>use std::io;
use std::net::SocketAddr;

use futures::{Future, Sink, Stream};
use futures::sync::mpsc;

use tokio_core::net::TcpStream;
use tokio_core::reactor::Core;
use tokio_io::AsyncRead;

use super::resp;

/// TODO: comeback and optimise this number
const DEFAULT_BUFFER_SIZE:usize = 100;

fn connect(addr: &SocketAddr, core: &Core) -> Box<Future<Item=ClientConnection, Error=io::Error>> {
    let handle = core.handle();
    let client_con = TcpStream::connect(addr, &handle).map(move |socket| {
        let framed = socket.framed(resp::RespCodec);
        let (write_f, read_f) = framed.split();
        let write_b = write_f.buffer(DEFAULT_BUFFER_SIZE);
        ClientConnection {
            sender: ClientSink(Box::new(write_b)),
            receiver: ClientStream(Box::new(read_f))
        }
    });
    Box::new(client_con)
}

/// TODO - is the boxing necessary?  It makes the type signature much simpler
struct ClientSink(Box<Sink<SinkItem=resp::RespValue, SinkError=io::Error>>);
struct ClientStream(Box<Stream<Item=resp::RespValue, Error=io::Error>>);

/// A low-level client connection representing a sender and a receiver.
///
/// The two halves operate independently from one another
struct ClientConnection {
    sender: ClientSink,
    receiver: ClientStream
}

#[cfg(test)]
mod test {
    use std::io;

    use futures::{Future, Sink, Stream};
    use futures::stream;

    use tokio_core::reactor::Core;

    use super::resp;

    #[test]
    fn can_connect() {
        let mut core = Core::new().unwrap();
        let addr = "127.0.0.1:6379".parse().unwrap();

        let connection = super::connect(&addr, &core).and_then(|connection| {
            let a = connection.sender.0.send(["PING", "TEST"].as_ref().into());
            let b = connection.receiver.0.take(1).collect();
            a.join(b)
        });
        let (_, values) = core.run(connection).unwrap();
        assert_eq!(values.len(), 1);
        assert_eq!(values[0], "TEST".into());
    }

    #[test]
    fn complex_test() {
        let mut core = Core::new().unwrap();
        let addr = "127.0.0.1:6379".parse().unwrap();
        let connection = super::connect(&addr, &core).and_then(|connection| {
            let mut ops = Vec::<resp::RespValue>::new();
            ops.push(["FLUSH"].as_ref().into());
            ops.extend((0..1000).map(|i| ["SADD", "test_set", &format!("VALUE: {}", i)].as_ref().into()));
            ops.push(["SMEMBERS", "test_set"].as_ref().into());
            let ops_r:Vec<Result<resp::RespValue, io::Error>> = ops.into_iter().map(Result::Ok).collect();
            let send = connection.sender.0.send_all(stream::iter(ops_r));
            let receive = connection.receiver.0.skip(1001).take(1).collect();
            send.join(receive)
        });
        let (_, values) = core.run(connection).unwrap();
        assert_eq!(values.len(), 1);
        let values = match &values[0] {
            &resp::RespValue::Array(ref values) => values.clone(),
            _ => panic!("Not an array")
        };
        assert_eq!(values.len(), 1000);
    }
}<commit_msg>Code cleanup<commit_after>use std::io;
use std::net::SocketAddr;

use futures::{Future, Sink, Stream};

use tokio_core::net::TcpStream;
use tokio_core::reactor::Core;
use tokio_io::AsyncRead;

use super::resp;

/// TODO: comeback and optimise this number
const DEFAULT_BUFFER_SIZE:usize = 100;

fn connect(addr: &SocketAddr, core: &Core) -> Box<Future<Item=ClientConnection, Error=io::Error>> {
    let handle = core.handle();
    let client_con = TcpStream::connect(addr, &handle).map(move |socket| {
        let framed = socket.framed(resp::RespCodec);
        let (write_f, read_f) = framed.split();
        let write_b = write_f.buffer(DEFAULT_BUFFER_SIZE);
        ClientConnection {
            sender: ClientSink(Box::new(write_b)),
            receiver: ClientStream(Box::new(read_f))
        }
    });
    Box::new(client_con)
}

/// TODO - is the boxing necessary?  It makes the type signature much simpler
struct ClientSink(Box<Sink<SinkItem=resp::RespValue, SinkError=io::Error>>);
struct ClientStream(Box<Stream<Item=resp::RespValue, Error=io::Error>>);

/// A low-level client connection representing a sender and a receiver.
///
/// The two halves operate independently from one another
struct ClientConnection {
    sender: ClientSink,
    receiver: ClientStream
}

#[cfg(test)]
mod test {
    use std::io;

    use futures::{Future, Sink, Stream};
    use futures::stream;

    use tokio_core::reactor::Core;

    use super::resp;

    #[test]
    fn can_connect() {
        let mut core = Core::new().unwrap();
        let addr = "127.0.0.1:6379".parse().unwrap();

        let connection = super::connect(&addr, &core).and_then(|connection| {
            let a = connection.sender.0.send(["PING", "TEST"].as_ref().into());
            let b = connection.receiver.0.take(1).collect();
            a.join(b)
        });
        let (_, values) = core.run(connection).unwrap();
        assert_eq!(values.len(), 1);
        assert_eq!(values[0], "TEST".into());
    }

    #[test]
    fn complex_test() {
        let mut core = Core::new().unwrap();
        let addr = "127.0.0.1:6379".parse().unwrap();
        let connection = super::connect(&addr, &core).and_then(|connection| {
            let mut ops = Vec::<resp::RespValue>::new();
            ops.push(["FLUSH"].as_ref().into());
            ops.extend((0..1000).map(|i| ["SADD", "test_set", &format!("VALUE: {}", i)].as_ref().into()));
            ops.push(["SMEMBERS", "test_set"].as_ref().into());
            let ops_r:Vec<Result<resp::RespValue, io::Error>> = ops.into_iter().map(Result::Ok).collect();
            let send = connection.sender.0.send_all(stream::iter(ops_r));
            let receive = connection.receiver.0.skip(1001).take(1).collect();
            send.join(receive)
        });
        let (_, values) = core.run(connection).unwrap();
        assert_eq!(values.len(), 1);
        let values = match &values[0] {
            &resp::RespValue::Array(ref values) => values.clone(),
            _ => panic!("Not an array")
        };
        assert_eq!(values.len(), 1000);
    }
}<|endoftext|>


<commit_before>use websocket::{self, Sender, Receiver};
use websocket::client::request::Url;
use websocket::client;
use websocket::stream;
use websocket::message::{Message as WSMessage, Type};
use websocket::header;
use messages::{URI, Dict, List, ID, SubscribeOptions, PublishOptions, Message,  HelloDetails, Reason, ErrorDetails, ClientRoles};
use std::collections::HashMap;
use serde_json;
use serde::{Deserialize, Serialize};
use std::str::from_utf8;
use std::fmt;
use ::{WampResult, Error, ErrorKind};
use std::thread::{self, JoinHandle};
use std::sync::{Mutex, Arc};
use rmp_serde::Deserializer as RMPDeserializer;
use rmp_serde::Serializer;
use utils::StructMapWriter;
use std::io::Cursor;
use rmp_serde::encode::VariantWriter;

macro_rules! try_websocket {
    ($e: expr) => (
        match $e {
            Ok(result) => result,
            Err(e) => return Err(Error::new(ErrorKind::WebSocketError(e)))
        }
    );
}

pub struct Connection {
    // sender: client::Sender<stream::WebSocketStream>,
    // receiver: client::Receiver<stream::WebSocketStream>,
    realm: URI,
    url: String
}

struct Subscription {
    callback: Box<Fn(List, Dict)>
}

static WAMP_JSON:&'static str = "wamp.2.json";
static WAMP_MSGPACK:&'static str = "wamp.2.msgpack";

#[derive(PartialEq)]
enum ConnectionState {
    Connected,
    ShuttingDown,
    Disconnected
}

unsafe impl <'a> Send for Subscription {}

unsafe impl<'a> Sync for Subscription {}

pub struct Client {
    connection_info: Arc<ConnectionInfo>,
    max_session_id: ID,
    id: u64
}

struct ConnectionInfo {
    connection_state: Mutex<ConnectionState>,
    sender: Mutex<client::Sender<stream::WebSocketStream>>,
    subscription_requests: Mutex<HashMap<ID, Subscription>>,
    subscriptions: Mutex<HashMap<ID, Subscription>>,
    protocol: String,
    published_callback: Option<Box<fn(URI)>>
}

fn send_message(sender: &Mutex<client::Sender<stream::WebSocketStream>>, message: Message, protocol: &str) -> WampResult<()> {
    info!("Sending message {:?}", message);
    if protocol == WAMP_MSGPACK {
        send_message_msgpack(sender, message)
    } else {
        send_message_json(sender, message)
    }
}

fn send_message_json(sender: &Mutex<client::Sender<stream::WebSocketStream>>, message: Message) -> WampResult<()> {
    let mut sender = sender.lock().unwrap();
    // Send the message
    match sender.send_message(&WSMessage::text(serde_json::to_string(&message).unwrap())) {
        Ok(()) => Ok(()),
        Err(e) => {
            error!("Could not send messsage: {}", e.to_string());
            let _ = sender.send_message(&WSMessage::close());
            Err(Error::new(ErrorKind::WebSocketError(e)))
        }
    }
}

fn send_message_msgpack(sender: &Mutex<client::Sender<stream::WebSocketStream>>, message: Message) -> WampResult<()> {
    let mut sender = sender.lock().unwrap();

    // Send the message
    let mut buf: Vec<u8> = Vec::new();
    message.serialize(&mut Serializer::with(&mut buf, StructMapWriter)).unwrap();
    match sender.send_message(&WSMessage::binary(buf)) {
        Ok(()) => Ok(()),
        Err(e) => {
            error!("Could not send messsage: {}", e.to_string());
            let _ = sender.send_message(&WSMessage::close());
            Err(Error::new(ErrorKind::WebSocketError(e)))
        }
    }
}

fn handle_welcome_message(receiver: &mut client::Receiver<stream::WebSocketStream>, sender: &Mutex<client::Sender<stream::WebSocketStream>>) -> WampResult<Message> {

    for message in receiver.incoming_messages() {
        let message: WSMessage = try_websocket!(message);
        match message.opcode {
            Type::Close => {
                info!("Received close message, shutting down");
                return Err(Error::new(ErrorKind::ConnectionLost));
            },
            Type::Text => {
                debug!("Recieved welcome message in text form: {:?}", message.payload);
                match from_utf8(&message.payload) {
                    Ok(message_text) => {
                        match serde_json::from_str(message_text) {
                            Ok(message) => {
                                return Ok(message);
                            } Err(e) => {
                                return Err(Error::new(ErrorKind::JSONError(e)));
                            }
                        }
                    },
                    Err(_) => {
                        return Err(Error::new(ErrorKind::MalformedData));
                    }
                }
            },
            Type::Binary => {
                debug!("Recieved welcome message in binary form: {:?}", message.payload);
                let mut de = RMPDeserializer::new(Cursor::new(&*message.payload));
                match Deserialize::deserialize(&mut de) {
                    Ok(message) => {
                        return Ok(message);
                    },
                    Err(e) => {
                        return Err(Error::new(ErrorKind::MsgPackError(e)));
                    }
                }
            },
            Type::Ping => {
                info!("Receieved ping.  Ponging");
                let mut sender = sender.lock().unwrap();
                let _ = sender.send_message(&WSMessage::pong(message.payload));
            },
            Type::Pong => {
                info!("Receieved pong");
            }
        };
    }
    Err(Error::new(ErrorKind::ConnectionLost))
}

impl Connection {
    pub fn new(url: &str, realm: &str) -> Connection {
        Connection {
            realm: URI::new(realm),
            url: url.to_string()
        }
    }

    pub fn connect<'a>(&self) -> WampResult<Client> {
        let url = match Url::parse(&self.url) {
            Ok(url) => url,
            Err(e) => return Err(Error::new(ErrorKind::URLError(e)))
        };
        let mut request = try_websocket!(websocket::Client::connect(url)); // Connect to the server
        request.headers.set(header::WebSocketProtocol(vec![WAMP_MSGPACK.to_string(), WAMP_JSON.to_string()]));
        let response = try_websocket!(request.send()); // Send the request

        try_websocket!(response.validate()); // Ensure the response is valid
        let protocol = match response.protocol() {
            Some(protocols) => {
                if protocols.len() == 0 {
                    warn!("Router did not specify protocol. Defaulting to wamp.2.json");
                    WAMP_JSON.to_string()
                } else {
                    protocols[0].clone()
                }
            }
            None => {
                warn!("Router did not specify protocol. Defaulting to wamp.2.json");
                WAMP_JSON.to_string()
            }
        };
        let (sender, mut receiver)  = response.begin().split(); // Get a Client

        let info = Arc::new(ConnectionInfo {
            protocol: protocol,
            subscription_requests: Mutex::new(HashMap::new()),
            subscriptions: Mutex::new(HashMap::new()),
            sender: Mutex::new(sender),
            connection_state: Mutex::new(ConnectionState::Connected),
            published_callback: None
        });

        let hello_message = Message::Hello(self.realm.clone(), HelloDetails::new(ClientRoles::new()));
        debug!("Sending Hello message");
        send_message(&info.sender, hello_message, &info.protocol).unwrap();
        debug!("Awaiting welcome message");
        let welcome_message = try!(handle_welcome_message(&mut receiver, &info.sender));
        let session_id = match welcome_message {
            Message::Welcome(session_id, _) => session_id,
            Message::Abort(_, reason) => {
                error!("Recieved abort message.  Reason: {:?}", reason);
                return Err(Error::new(ErrorKind::ConnectionLost));
            },
            _ => return Err(Error::new(ErrorKind::UnexpectedMessage("Expected Welcome Message")))
        };

        self.start_recv_loop(receiver, info.clone());

        Ok(Client {
            connection_info: info,
            id: session_id,
            max_session_id: 0
        })
    }

    fn start_recv_loop(&self, mut receiver: client::Receiver<stream::WebSocketStream>, mut connection_info: Arc<ConnectionInfo>) -> JoinHandle<()> {
        thread::spawn(move || {
            // Receive loop
            for message in receiver.incoming_messages() {
                let message: WSMessage = match message {
                    Ok(m) => m,
                    Err(e) => {
                        error!("Could not receieve message: {:?}", e);
                        let _ = connection_info.sender.lock().unwrap().send_message(&WSMessage::close());
                        break;
                    }
                };
                match message.opcode {
                    Type::Close => {
                        info!("Received close message, shutting down");
                        let _ = connection_info.sender.lock().unwrap().send_message(&WSMessage::close());
                        break;
                    },
                    Type::Text => {
                        match from_utf8(&message.payload) {
                            Ok(message_text) => {
                                match serde_json::from_str(message_text) {
                                    Ok(message) => {
                                        if !Connection::handle_message(message, &mut connection_info) {
                                            break;
                                        }
                                    } Err(_) => {
                                        error!("Received unknown message: {}", message_text)
                                    }
                                }
                            },
                            Err(_) => {
                                error!("Receieved non-utf-8 json message.  Ignoring");
                            }
                        }
                    },
                    Type::Binary => {
                        let mut de = RMPDeserializer::new(Cursor::new(&*message.payload));
                        match Deserialize::deserialize(&mut de) {
                            Ok(message) => {
                                if !Connection::handle_message(message, &mut connection_info) {
                                    break;
                                }
                            },
                            Err(_) => {
                                error!("Could not understand MsgPack message");
                            }
                        }
                    },
                    Type::Ping => {
                        info!("Receieved ping.  Ponging");
                        let _ = connection_info.sender.lock().unwrap().send_message(&WSMessage::pong(message.payload));
                    },
                    Type::Pong => {
                        info!("Receieved pong");
                    }
                }
            }
            connection_info.sender.lock().unwrap().shutdown().ok();
            receiver.shutdown().ok();
            *connection_info.connection_state.lock().unwrap() = ConnectionState::Disconnected;
        })
    }

    fn handle_message(message: Message, connection_info: &mut Arc<ConnectionInfo>) -> bool {
        match message {
            Message::Subscribed(request_id, subscription_id) => {
                // TODO handle errors here
                match connection_info.subscription_requests.lock().unwrap().remove(&request_id) {
                    Some(subscription) => {
                        connection_info.subscriptions.lock().unwrap().insert(subscription_id, subscription);
                    },
                    None => {
                        warn!("Recieved a subscribed notification for a subscription we don't have.  ID: {}", subscription_id);
                    }
                }

            },
            Message::Event(subscription_id, _, _) => {
                match connection_info.subscriptions.lock().unwrap().get(&subscription_id) {
                    Some(subscription) => {
                        let ref callback = subscription.callback;
                        callback(Vec::new(), HashMap::new());
                    },
                    None => {
                        warn!("Recieved an event for a subscription we don't have.  ID: {}", subscription_id);
                    }
                }
            },
            Message::EventArgs(subscription_id, _, _, args) => {
                match connection_info.subscriptions.lock().unwrap().get(&subscription_id) {
                    Some(subscription) => {
                        let ref callback = subscription.callback;
                        callback(args, HashMap::new());
                    },
                    None => {
                        warn!("Recieved an event for a subscription we don't have.  ID: {}", subscription_id);
                    }
                }

            },
            Message::EventKwArgs(subscription_id, _, _, args, kwargs) => {
                match connection_info.subscriptions.lock().unwrap().get(&subscription_id) {
                    Some(subscription) => {
                        let ref callback = subscription.callback;
                        callback(args, kwargs);
                    },
                    None => {
                        warn!("Recieved an event for a subscription we don't have.  ID: {}", subscription_id);
                    }
                }
            },
            Message::Goodbye(_, reason) => {
                match *connection_info.connection_state.lock().unwrap() {
                    ConnectionState::Connected => {
                        info!("Router said goodbye.  Reason: {:?}", reason);
                        send_message(&connection_info.sender, Message::Goodbye(ErrorDetails::new(), Reason::GoodbyeAndOut), &connection_info.protocol).unwrap();
                        return false;
                    },
                    ConnectionState::ShuttingDown => {
                        // The router has seen our goodbye message and has responded in kind
                        return false;
                    },
                    ConnectionState::Disconnected => {
                        // Should never happen
                        return false;
                    }
                }
            }
            _ => {}
        }
        true
    }
}

impl Client {

    fn send_message(&self, message: Message) -> WampResult<()> {
        if self.connection_info.protocol == WAMP_MSGPACK {
            send_message_msgpack(&self.connection_info.sender, message)
        } else {
            send_message_json(&self.connection_info.sender, message)
        }
    }

    fn get_next_session_id(&mut self) -> ID {
        self.max_session_id += 1;
        self.max_session_id
    }

    pub fn subscribe(&mut self, topic: URI, callback: Box<Fn(List, Dict)>) -> WampResult<()> {
        // Send a subscribe messages
        let request_id = self.get_next_session_id();
        self.connection_info.subscription_requests.lock().unwrap().insert(request_id, Subscription{callback: callback});
        self.send_message(Message::Subscribe(request_id, SubscribeOptions::new(), topic))
    }

    pub fn publish(&mut self, topic: URI, args: List, kwargs: Dict) -> WampResult<()> {
        info!("Publishing to {:?} with {:?} | {:?}", topic, args, kwargs);
        let request_id = self.get_next_session_id();
        self.send_message(Message::PublishKwArgs(request_id, PublishOptions::new(self.connection_info.published_callback.is_some()), topic, args, kwargs))
    }

    pub fn shutdown(&mut self) {
        let mut state = self.connection_info.connection_state.lock().unwrap();
        if *state == ConnectionState::Connected {
            self.send_message(Message::Goodbye(ErrorDetails::new(), Reason::SystemShutdown)).ok();
            *state = ConnectionState::ShuttingDown;
        }
    }
}

impl fmt::Debug for Client {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{{Connection id: {}}}", self.id)
    }
}
<commit_msg>Included a published callback<commit_after>use websocket::{self, Sender, Receiver};
use websocket::client::request::Url;
use websocket::client;
use websocket::stream;
use websocket::message::{Message as WSMessage, Type};
use websocket::header;
use messages::{URI, Dict, List, ID, SubscribeOptions, PublishOptions, Message,  HelloDetails, Reason, ErrorDetails, ClientRoles};
use std::collections::HashMap;
use serde_json;
use serde::{Deserialize, Serialize};
use std::str::from_utf8;
use std::fmt;
use ::{WampResult, Error, ErrorKind};
use std::thread::{self, JoinHandle};
use std::sync::{Mutex, Arc};
use rmp_serde::Deserializer as RMPDeserializer;
use rmp_serde::Serializer;
use utils::StructMapWriter;
use std::io::Cursor;
use rmp_serde::encode::VariantWriter;

macro_rules! try_websocket {
    ($e: expr) => (
        match $e {
            Ok(result) => result,
            Err(e) => return Err(Error::new(ErrorKind::WebSocketError(e)))
        }
    );
}

pub struct Connection {
    // sender: client::Sender<stream::WebSocketStream>,
    // receiver: client::Receiver<stream::WebSocketStream>,
    realm: URI,
    url: String
}

struct Subscription {
    callback: Box<Fn(List, Dict)>
}

static WAMP_JSON:&'static str = "wamp.2.json";
static WAMP_MSGPACK:&'static str = "wamp.2.msgpack";

#[derive(PartialEq)]
enum ConnectionState {
    Connected,
    ShuttingDown,
    Disconnected
}

unsafe impl <'a> Send for Subscription {}

unsafe impl<'a> Sync for Subscription {}

pub struct Client {
    connection_info: Arc<ConnectionInfo>,
    max_session_id: ID,
    id: u64
}

struct ConnectionInfo {
    connection_state: Mutex<ConnectionState>,
    sender: Mutex<client::Sender<stream::WebSocketStream>>,
    subscription_requests: Mutex<HashMap<ID, Subscription>>,
    subscriptions: Mutex<HashMap<ID, Subscription>>,
    publish_ids: Mutex<HashMap<ID, URI>>,
    protocol: String,
    published_callback: Option<Box<fn(&URI)>>
}

fn send_message(sender: &Mutex<client::Sender<stream::WebSocketStream>>, message: Message, protocol: &str) -> WampResult<()> {
    info!("Sending message {:?}", message);
    if protocol == WAMP_MSGPACK {
        send_message_msgpack(sender, message)
    } else {
        send_message_json(sender, message)
    }
}

fn send_message_json(sender: &Mutex<client::Sender<stream::WebSocketStream>>, message: Message) -> WampResult<()> {
    let mut sender = sender.lock().unwrap();
    // Send the message
    match sender.send_message(&WSMessage::text(serde_json::to_string(&message).unwrap())) {
        Ok(()) => Ok(()),
        Err(e) => {
            error!("Could not send messsage: {}", e.to_string());
            let _ = sender.send_message(&WSMessage::close());
            Err(Error::new(ErrorKind::WebSocketError(e)))
        }
    }
}

fn send_message_msgpack(sender: &Mutex<client::Sender<stream::WebSocketStream>>, message: Message) -> WampResult<()> {
    let mut sender = sender.lock().unwrap();

    // Send the message
    let mut buf: Vec<u8> = Vec::new();
    message.serialize(&mut Serializer::with(&mut buf, StructMapWriter)).unwrap();
    match sender.send_message(&WSMessage::binary(buf)) {
        Ok(()) => Ok(()),
        Err(e) => {
            error!("Could not send messsage: {}", e.to_string());
            let _ = sender.send_message(&WSMessage::close());
            Err(Error::new(ErrorKind::WebSocketError(e)))
        }
    }
}

fn handle_welcome_message(receiver: &mut client::Receiver<stream::WebSocketStream>, sender: &Mutex<client::Sender<stream::WebSocketStream>>) -> WampResult<Message> {

    for message in receiver.incoming_messages() {
        let message: WSMessage = try_websocket!(message);
        match message.opcode {
            Type::Close => {
                info!("Received close message, shutting down");
                return Err(Error::new(ErrorKind::ConnectionLost));
            },
            Type::Text => {
                debug!("Recieved welcome message in text form: {:?}", message.payload);
                match from_utf8(&message.payload) {
                    Ok(message_text) => {
                        match serde_json::from_str(message_text) {
                            Ok(message) => {
                                return Ok(message);
                            } Err(e) => {
                                return Err(Error::new(ErrorKind::JSONError(e)));
                            }
                        }
                    },
                    Err(_) => {
                        return Err(Error::new(ErrorKind::MalformedData));
                    }
                }
            },
            Type::Binary => {
                debug!("Recieved welcome message in binary form: {:?}", message.payload);
                let mut de = RMPDeserializer::new(Cursor::new(&*message.payload));
                match Deserialize::deserialize(&mut de) {
                    Ok(message) => {
                        return Ok(message);
                    },
                    Err(e) => {
                        return Err(Error::new(ErrorKind::MsgPackError(e)));
                    }
                }
            },
            Type::Ping => {
                info!("Receieved ping.  Ponging");
                let mut sender = sender.lock().unwrap();
                let _ = sender.send_message(&WSMessage::pong(message.payload));
            },
            Type::Pong => {
                info!("Receieved pong");
            }
        };
    }
    Err(Error::new(ErrorKind::ConnectionLost))
}

impl Connection {
    pub fn new(url: &str, realm: &str) -> Connection {
        Connection {
            realm: URI::new(realm),
            url: url.to_string()
        }
    }

    pub fn connect<'a>(&self) -> WampResult<Client> {
        let url = match Url::parse(&self.url) {
            Ok(url) => url,
            Err(e) => return Err(Error::new(ErrorKind::URLError(e)))
        };
        let mut request = try_websocket!(websocket::Client::connect(url)); // Connect to the server
        request.headers.set(header::WebSocketProtocol(vec![WAMP_MSGPACK.to_string(), WAMP_JSON.to_string()]));
        let response = try_websocket!(request.send()); // Send the request

        try_websocket!(response.validate()); // Ensure the response is valid
        let protocol = match response.protocol() {
            Some(protocols) => {
                if protocols.len() == 0 {
                    warn!("Router did not specify protocol. Defaulting to wamp.2.json");
                    WAMP_JSON.to_string()
                } else {
                    protocols[0].clone()
                }
            }
            None => {
                warn!("Router did not specify protocol. Defaulting to wamp.2.json");
                WAMP_JSON.to_string()
            }
        };
        let (sender, mut receiver)  = response.begin().split(); // Get a Client

        let info = Arc::new(ConnectionInfo {
            protocol: protocol,
            subscription_requests: Mutex::new(HashMap::new()),
            subscriptions: Mutex::new(HashMap::new()),
            sender: Mutex::new(sender),
            publish_ids: Mutex::new(HashMap::new()),
            connection_state: Mutex::new(ConnectionState::Connected),
            published_callback: None
        });

        let hello_message = Message::Hello(self.realm.clone(), HelloDetails::new(ClientRoles::new()));
        debug!("Sending Hello message");
        send_message(&info.sender, hello_message, &info.protocol).unwrap();
        debug!("Awaiting welcome message");
        let welcome_message = try!(handle_welcome_message(&mut receiver, &info.sender));
        let session_id = match welcome_message {
            Message::Welcome(session_id, _) => session_id,
            Message::Abort(_, reason) => {
                error!("Recieved abort message.  Reason: {:?}", reason);
                return Err(Error::new(ErrorKind::ConnectionLost));
            },
            _ => return Err(Error::new(ErrorKind::UnexpectedMessage("Expected Welcome Message")))
        };

        self.start_recv_loop(receiver, info.clone());

        Ok(Client {
            connection_info: info,
            id: session_id,
            max_session_id: 0
        })
    }

    fn start_recv_loop(&self, mut receiver: client::Receiver<stream::WebSocketStream>, mut connection_info: Arc<ConnectionInfo>) -> JoinHandle<()> {
        thread::spawn(move || {
            // Receive loop
            for message in receiver.incoming_messages() {
                let message: WSMessage = match message {
                    Ok(m) => m,
                    Err(e) => {
                        error!("Could not receieve message: {:?}", e);
                        let _ = connection_info.sender.lock().unwrap().send_message(&WSMessage::close());
                        break;
                    }
                };
                match message.opcode {
                    Type::Close => {
                        info!("Received close message, shutting down");
                        let _ = connection_info.sender.lock().unwrap().send_message(&WSMessage::close());
                        break;
                    },
                    Type::Text => {
                        match from_utf8(&message.payload) {
                            Ok(message_text) => {
                                match serde_json::from_str(message_text) {
                                    Ok(message) => {
                                        if !Connection::handle_message(message, &mut connection_info) {
                                            break;
                                        }
                                    } Err(_) => {
                                        error!("Received unknown message: {}", message_text)
                                    }
                                }
                            },
                            Err(_) => {
                                error!("Receieved non-utf-8 json message.  Ignoring");
                            }
                        }
                    },
                    Type::Binary => {
                        let mut de = RMPDeserializer::new(Cursor::new(&*message.payload));
                        match Deserialize::deserialize(&mut de) {
                            Ok(message) => {
                                if !Connection::handle_message(message, &mut connection_info) {
                                    break;
                                }
                            },
                            Err(_) => {
                                error!("Could not understand MsgPack message");
                            }
                        }
                    },
                    Type::Ping => {
                        info!("Receieved ping.  Ponging");
                        let _ = connection_info.sender.lock().unwrap().send_message(&WSMessage::pong(message.payload));
                    },
                    Type::Pong => {
                        info!("Receieved pong");
                    }
                }
            }
            connection_info.sender.lock().unwrap().shutdown().ok();
            receiver.shutdown().ok();
            *connection_info.connection_state.lock().unwrap() = ConnectionState::Disconnected;
        })
    }

    fn handle_message(message: Message, connection_info: &mut Arc<ConnectionInfo>) -> bool {
        match message {
            Message::Subscribed(request_id, subscription_id) => {
                // TODO handle errors here
                match connection_info.subscription_requests.lock().unwrap().remove(&request_id) {
                    Some(subscription) => {
                        connection_info.subscriptions.lock().unwrap().insert(subscription_id, subscription);
                    },
                    None => {
                        warn!("Recieved a subscribed notification for a subscription we don't have.  ID: {}", subscription_id);
                    }
                }

            },
            Message::Event(subscription_id, _, _) => {
                match connection_info.subscriptions.lock().unwrap().get(&subscription_id) {
                    Some(subscription) => {
                        let ref callback = subscription.callback;
                        callback(Vec::new(), HashMap::new());
                    },
                    None => {
                        warn!("Recieved an event for a subscription we don't have.  ID: {}", subscription_id);
                    }
                }
            },
            Message::EventArgs(subscription_id, _, _, args) => {
                match connection_info.subscriptions.lock().unwrap().get(&subscription_id) {
                    Some(subscription) => {
                        let ref callback = subscription.callback;
                        callback(args, HashMap::new());
                    },
                    None => {
                        warn!("Recieved an event for a subscription we don't have.  ID: {}", subscription_id);
                    }
                }

            },
            Message::EventKwArgs(subscription_id, _, _, args, kwargs) => {
                match connection_info.subscriptions.lock().unwrap().get(&subscription_id) {
                    Some(subscription) => {
                        let ref callback = subscription.callback;
                        callback(args, kwargs);
                    },
                    None => {
                        warn!("Recieved an event for a subscription we don't have.  ID: {}", subscription_id);
                    }
                }
            },
            Message::Published(request_id, _publication_id) => {
                let ids = connection_info.publish_ids.lock().unwrap();
                match ids.get(&request_id) {
                    Some(ref topic) => {
                        match connection_info.published_callback {
                            Some(ref callback) => {
                                callback(topic);
                            },
                            None => {}
                        }
                    },
                    None => {}
                }

            }
            Message::Goodbye(_, reason) => {
                match *connection_info.connection_state.lock().unwrap() {
                    ConnectionState::Connected => {
                        info!("Router said goodbye.  Reason: {:?}", reason);
                        send_message(&connection_info.sender, Message::Goodbye(ErrorDetails::new(), Reason::GoodbyeAndOut), &connection_info.protocol).unwrap();
                        return false;
                    },
                    ConnectionState::ShuttingDown => {
                        // The router has seen our goodbye message and has responded in kind
                        return false;
                    },
                    ConnectionState::Disconnected => {
                        // Should never happen
                        return false;
                    }
                }
            }
            _ => {}
        }
        true
    }
}

impl Client {

    fn send_message(&self, message: Message) -> WampResult<()> {
        if self.connection_info.protocol == WAMP_MSGPACK {
            send_message_msgpack(&self.connection_info.sender, message)
        } else {
            send_message_json(&self.connection_info.sender, message)
        }
    }

    fn get_next_session_id(&mut self) -> ID {
        self.max_session_id += 1;
        self.max_session_id
    }

    pub fn subscribe(&mut self, topic: URI, callback: Box<Fn(List, Dict)>) -> WampResult<()> {
        // Send a subscribe messages
        let request_id = self.get_next_session_id();
        self.connection_info.subscription_requests.lock().unwrap().insert(request_id, Subscription{callback: callback});
        self.send_message(Message::Subscribe(request_id, SubscribeOptions::new(), topic))
    }

    pub fn publish(&mut self, topic: URI, args: List, kwargs: Dict) -> WampResult<()> {
        info!("Publishing to {:?} with {:?} | {:?}", topic, args, kwargs);
        let request_id = self.get_next_session_id();
        let request_acknowledge = self.connection_info.published_callback.is_some();
        if request_acknowledge {
            let mut ids = self.connection_info.publish_ids.lock().unwrap();
            ids.insert(request_id, topic.clone());
        }
        self.send_message(Message::PublishKwArgs(request_id, PublishOptions::new(request_acknowledge), topic, args, kwargs))
    }

    pub fn shutdown(&mut self) {
        let mut state = self.connection_info.connection_state.lock().unwrap();
        if *state == ConnectionState::Connected {
            self.send_message(Message::Goodbye(ErrorDetails::new(), Reason::SystemShutdown)).ok();
            *state = ConnectionState::ShuttingDown;
        }
    }
}

impl fmt::Debug for Client {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{{Connection id: {}}}", self.id)
    }
}
<|endoftext|>


<commit_before>use compositor::{self, Action, Server, Shell, View};
use std::time::Duration;
use compositor::seat::Seat;
use wlroots::types::Cursor;
use wlroots::{CompositorHandle, HandleResult, KeyboardHandle, Origin, PointerHandle,
              PointerHandler, XdgV6ShellState::*, pointer_events::*, WLR_BUTTON_RELEASED};

#[derive(Debug, Default)]
pub struct Pointer;

impl PointerHandler for Pointer {

    fn on_motion_absolute(&mut self, compositor: CompositorHandle, _: PointerHandle, event: &AbsoluteMotionEvent) {
        with_handles!([(compositor: {compositor})] => {
            let server: &mut Server = compositor.data.downcast_mut().unwrap();
            let Server { ref mut cursor,
                         ref mut seat,
                         ref mut views,
                         .. } = *server;
            with_handles!([(cursor: {&mut *cursor})] => {
                let (x, y) = event.pos();
                cursor.warp_absolute(event.device(), x, y);
                update_view_position(cursor, seat, views);
            }).expect("Cursor was destroyed");
        }).unwrap();
    }

    fn on_motion(&mut self, compositor: CompositorHandle, _: PointerHandle, event: &MotionEvent) {
        with_handles!([(compositor: {compositor})] => {
            let server: &mut Server = compositor.into();
            let Server { ref mut cursor,
                         ref mut seat,
                         ref mut views,
                         .. } = *server;
            with_handles!([(cursor: {&mut *cursor})] => {
                let (x, y) = event.delta();
                cursor.move_to(event.device(), x, y);
                update_view_position(cursor, seat, views);
            }).expect("Cursor was destroyed");
        }).unwrap();
    }

    fn on_button(&mut self, compositor: CompositorHandle, _: PointerHandle, event: &ButtonEvent) {
        with_handles!([(compositor: {compositor})] => {
            let server: &mut Server = compositor.into();
            let Server { ref mut cursor,
                         ref mut views,
                         ref mut seat,
                         ref mut keyboards,
                         .. } = *server;
            with_handles!([(cursor: {&mut *cursor})] => {
                if event.state() == WLR_BUTTON_RELEASED {
                    seat.action = None;
                    send_pointer_button(seat, event).expect("Could not send pointer button");
                    return
                }
                if let Some(view) = view_at_pointer(views, cursor) {
                    focus_under_pointer(seat, &mut **keyboards, { &mut *view }).expect("Could not focus \
                                                                                view");
                    let meta_held_down = seat.meta;
                    if meta_held_down && event.button() == BTN_LEFT {
                        move_view(seat, cursor, view, None);
                    }
                    send_pointer_button(seat, event).expect("Could not send pointer button");
                } else {
                    focus_under_pointer(seat, &mut **keyboards, None).expect("Could not focus view");
                }
            }).unwrap()
        }).unwrap()
    }
}

/// After the cursor has been warped, update the position of a view that might have been
/// affected by an ongoing interactive move/resize operation
fn update_view_position(cursor: &mut Cursor, seat: &mut Seat, views: &mut [View]) {
    match seat.action {
        Some(Action::Moving { start }) => {
            if let Some(mut view) = view_at_pointer(views, cursor) {
                let meta_held_down = seat.meta;
                if meta_held_down {
                    move_view(seat, cursor, &mut view, start);
                }
            }
        }
        _ => { /* TODO */ }
    }
}

fn view_at_pointer<'view>(views: &'view mut [View],
                          cursor: &mut Cursor)
                          -> Option<&'view mut View> {
    for view in views {
        match view.shell.clone() {
            Shell::XdgV6(ref mut shell) => {
                let (mut sx, mut sy) = (0.0, 0.0);
                let seen = with_handles!([(shell: {&mut *shell})] => {
                    let (lx, ly) = cursor.coords();
                    let Origin {x: shell_x, y: shell_y} = view.origin;
                    let (view_sx, view_sy) = (lx - shell_x as f64, ly - shell_y as f64);
                    shell.surface_at(view_sx, view_sy, &mut sx, &mut sy).is_some()
                }).ok()?;
                if seen {
                    return Some(view)
                }
            }
        }
    }
    None
}

/// Focus the view under the pointer.
fn focus_under_pointer<'view, V>(seat: &mut compositor::Seat,
                                 keyboards: &mut [KeyboardHandle],
                                 view: V)
                                 -> HandleResult<()>
    where V: Into<Option<&'view mut View>>
{
    // TODO Use those surface level coordinates to send events and shit
    match view.into() {
        None => {
            if let Some(mut focused_view) = seat.focused.take() {
                match focused_view.shell {
                    Shell::XdgV6(ref mut shell) => {
                        with_handles!([(shell: {shell})] => {
                            match shell.state() {
                                Some(&mut TopLevel(ref mut toplevel)) => {
                                    toplevel.set_activated(false)
                                },
                                _ => unimplemented!()
                            }
                        })?;
                    }
                }
            }
            with_handles!([(seat: {&mut seat.seat})] => {
                seat.keyboard_clear_focus()
            })
        }
        Some(view) => {
            seat.focused = Some(view.clone());
            for keyboard in { &mut *keyboards } {
                match &mut view.shell {
                    &mut Shell::XdgV6(ref mut shell) => {
                        with_handles!([(seat: {&mut seat.seat}),
                                      (shell: {&mut *shell}),
                                      (surface: {shell.surface()}),
                                      (keyboard: {keyboard})] => {
                    match shell.state() {
                        Some(&mut TopLevel(ref mut toplevel)) => {
                            // TODO Don't send this for each keyboard!
                            toplevel.set_activated(true);
                        },
                        _ => unimplemented!()
                    }
                    seat.keyboard_notify_enter(surface,
                                               &mut keyboard.keycodes(),
                                               &mut keyboard.get_modifier_masks())
                })?;
                    }
                }
            }
            Ok(())
        }
    }
}

/// Start moving a view if you haven't already by passing `start: None`.
///
/// Otherwise, update the view position relative to where the move started,
/// which is provided by Action::Moving.
fn move_view<O>(seat: &mut compositor::Seat,
                cursor: &mut Cursor,
                view: &mut View,
                start: O)
    where O: Into<Option<Origin>>
{
    let Origin { x: shell_x,
    y: shell_y } = view.origin;
    let (lx, ly) = cursor.coords();
    match start.into() {
        None => {
            let (view_sx, view_sy) = (lx - shell_x as f64, ly - shell_y as f64);
            let start = Origin::new(view_sx as _, view_sy as _);
            seat.action = Some(Action::Moving{start});
        }
        Some(start) => {
            let pos = Origin::new(lx as i32 - start.x, ly as i32 - start.y);
            view.origin = pos;
        }
    };
}

fn send_pointer_button(seat: &mut compositor::Seat, event: &ButtonEvent) -> HandleResult<()> {
    with_handles!([(seat: {&mut seat.seat})] => {
        seat.pointer_notify_button(Duration::from_millis(event.time_msec() as _),
                                   event.button(),
                                   event.state() as u32);
    })
}
<commit_msg>basic pointer motion<commit_after>use compositor::{self, Action, Server, Shell, View};
use std::time::Duration;
use compositor::seat::Seat;
use wlroots::types::Cursor;
use wlroots::{CompositorHandle, HandleResult, KeyboardHandle, Origin, PointerHandle,
              PointerHandler, XdgV6ShellState::*, pointer_events::*, WLR_BUTTON_RELEASED, SurfaceHandle};

#[derive(Debug, Default)]
pub struct Pointer;

impl PointerHandler for Pointer {

    fn on_motion_absolute(&mut self, compositor: CompositorHandle, _: PointerHandle, event: &AbsoluteMotionEvent) {
        with_handles!([(compositor: {compositor})] => {
            let server: &mut Server = compositor.data.downcast_mut().unwrap();
            let Server { ref mut cursor,
                         ref mut seat,
                         ref mut views,
                         .. } = *server;
            with_handles!([(cursor: {&mut *cursor})] => {
                let (x, y) = event.pos();
                cursor.warp_absolute(event.device(), x, y);
                update_view_position(cursor, seat, views, event.time_msec());
            }).expect("Cursor was destroyed");
        }).unwrap();
    }

    fn on_motion(&mut self, compositor: CompositorHandle, _: PointerHandle, event: &MotionEvent) {
        with_handles!([(compositor: {compositor})] => {
            let server: &mut Server = compositor.into();
            let Server { ref mut cursor,
                         ref mut seat,
                         ref mut views,
                         .. } = *server;
            with_handles!([(cursor: {&mut *cursor})] => {
                let (x, y) = event.delta();
                cursor.move_to(event.device(), x, y);
                update_view_position(cursor, seat, views, event.time_msec());
            }).expect("Cursor was destroyed");
        }).unwrap();
    }

    fn on_button(&mut self, compositor: CompositorHandle, _: PointerHandle, event: &ButtonEvent) {
        with_handles!([(compositor: {compositor})] => {
            let server: &mut Server = compositor.into();
            let Server { ref mut cursor,
                         ref mut views,
                         ref mut seat,
                         ref mut keyboards,
                         .. } = *server;
            with_handles!([(cursor: {&mut *cursor})] => {
                if event.state() == WLR_BUTTON_RELEASED {
                    seat.action = None;
                    send_pointer_button(seat, event).expect("Could not send pointer button");
                    return
                }
                let (view, _surface, _sx, _sy) = view_at_pointer(views, cursor);
                match view {
                    Some(view) => {
                        focus_under_pointer(seat, &mut **keyboards, { &mut *view }).expect("Could not focus \
                                                                                view");
                        let meta_held_down = seat.meta;
                        if meta_held_down && event.button() == BTN_LEFT {
                            move_view(seat, cursor, view, None);
                        }
                        send_pointer_button(seat, event).expect("Could not send pointer button");
                    },
                    None => {
                        focus_under_pointer(seat, &mut **keyboards, None).expect("Could not focus view");
                    }
                }
            }).unwrap()
        }).unwrap()
    }
}

/// After the cursor has been warped, send pointer motion events to the view under the pointer or
/// update the position of a view that might have been affected by an ongoing interactive
/// move/resize operation
fn update_view_position(cursor: &mut Cursor, seat: &mut Seat, views: &mut [View], time_msec: u32) {
    match seat.action {
        Some(Action::Moving { start }) => {
            let (view, _surface, _sx, _sy) = view_at_pointer(views, cursor);
            match view {
                Some(mut view) => {
                    let meta_held_down = seat.meta;
                    if meta_held_down {
                        move_view(seat, cursor, &mut view, start);
                    }
                },
                None => ()
            }
        }
        _ => {
            let (_view, surface, sx, sy) = view_at_pointer(views, cursor);
            let ref mut seat = seat.seat;
            match surface {
                Some(surface) => {
                    with_handles!([(surface: {surface}), (seat: {seat})] => {
                        seat.pointer_notify_enter(surface, sx, sy);
                        seat.pointer_notify_motion(Duration::from_millis(time_msec as u64), sx, sy);
                    }).unwrap();
                },
                None => {
                    with_handles!([(seat: {seat})] => {
                        seat.pointer_clear_focus();
                    }).unwrap();
                }
            }
		}
    }
}

fn view_at_pointer<'view>(views: &'view mut [View],
                          cursor: &mut Cursor)
                          -> (Option<&'view mut View>, Option<SurfaceHandle>, f64, f64) {
    for view in views {
        match view.shell.clone() {
            Shell::XdgV6(ref mut shell) => {
                let (mut sx, mut sy) = (0.0, 0.0);
                let surface = with_handles!([(shell: {&mut *shell})] => {
                    let (lx, ly) = cursor.coords();
                    let Origin {x: shell_x, y: shell_y} = view.origin;
                    let (view_sx, view_sy) = (lx - shell_x as f64, ly - shell_y as f64);
                    shell.surface_at(view_sx, view_sy, &mut sx, &mut sy)
                }).unwrap();
                if surface.is_some() {
                    return (Some(view), surface, sx, sy);
                }
            }
        }
    }
    (None, None, 0.0, 0.0)
}

/// Focus the view under the pointer.
fn focus_under_pointer<'view, V>(seat: &mut compositor::Seat,
                                 keyboards: &mut [KeyboardHandle],
                                 view: V)
                                 -> HandleResult<()>
    where V: Into<Option<&'view mut View>>
{
    match view.into() {
        None => {
            if let Some(mut focused_view) = seat.focused.take() {
                match focused_view.shell {
                    Shell::XdgV6(ref mut shell) => {
                        with_handles!([(shell: {shell})] => {
                            match shell.state() {
                                Some(&mut TopLevel(ref mut toplevel)) => {
                                    toplevel.set_activated(false)
                                },
                                _ => unimplemented!()
                            }
                        })?;
                    }
                }
            }
            with_handles!([(seat: {&mut seat.seat})] => {
                seat.keyboard_clear_focus()
            })
        }
        Some(view) => {
            seat.focused = Some(view.clone());
            for keyboard in { &mut *keyboards } {
                match &mut view.shell {
                    &mut Shell::XdgV6(ref mut shell) => {
                        with_handles!([(seat: {&mut seat.seat}),
                                      (shell: {&mut *shell}),
                                      (surface: {shell.surface()}),
                                      (keyboard: {keyboard})] => {
                    match shell.state() {
                        Some(&mut TopLevel(ref mut toplevel)) => {
                            // TODO Don't send this for each keyboard!
                            toplevel.set_activated(true);
                        },
                        _ => unimplemented!()
                    }
                    seat.keyboard_notify_enter(surface,
                                               &mut keyboard.keycodes(),
                                               &mut keyboard.get_modifier_masks())
                })?;
                    }
                }
            }
            Ok(())
        }
    }
}

/// Start moving a view if you haven't already by passing `start: None`.
///
/// Otherwise, update the view position relative to where the move started,
/// which is provided by Action::Moving.
fn move_view<O>(seat: &mut compositor::Seat,
                cursor: &mut Cursor,
                view: &mut View,
                start: O)
    where O: Into<Option<Origin>>
{
    let Origin { x: shell_x,
    y: shell_y } = view.origin;
    let (lx, ly) = cursor.coords();
    match start.into() {
        None => {
            let (view_sx, view_sy) = (lx - shell_x as f64, ly - shell_y as f64);
            let start = Origin::new(view_sx as _, view_sy as _);
            seat.action = Some(Action::Moving{start});
        }
        Some(start) => {
            let pos = Origin::new(lx as i32 - start.x, ly as i32 - start.y);
            view.origin = pos;
        }
    };
}

fn send_pointer_button(seat: &mut compositor::Seat, event: &ButtonEvent) -> HandleResult<()> {
    with_handles!([(seat: {&mut seat.seat})] => {
        seat.pointer_notify_button(Duration::from_millis(event.time_msec() as _),
                                   event.button(),
                                   event.state() as u32);
    })
}
<|endoftext|>


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

use std::{
    fs::{File, OpenOptions},
    io::{Read, Write},
    path::{Path, PathBuf},
    thread::sleep,
    time::Duration,
};

use chrono::{DateTime, TimeZone, Utc};
use data_encoding::BASE32_NOPAD;
use serde_json::{Map, Value};

use devicemapper::{
    Bytes, DmDevice, DmName, DmOptions, DmUuid, Sectors, ThinDev, ThinDevId, ThinPoolDev,
    ThinStatus,
};

use nix::{
    mount::{mount, umount, MsFlags},
    sys::statvfs::statvfs,
};

use crate::{
    engine::{
        engine::{DumpState, Filesystem, StateDiff},
        strat_engine::{
            cmd::{create_fs, set_uuid, udev_settle, xfs_growfs},
            devlinks,
            dm::get_dm,
            names::{format_thin_ids, ThinRole},
            serde_structs::FilesystemSave,
            thinpool::{thinpool::DATA_LOWATER, DATA_BLOCK_SIZE},
        },
        types::{
            ActionAvailability, FilesystemUuid, Name, PoolUuid, StratFilesystemDiff, StratisUuid,
        },
    },
    stratis::{StratisError, StratisResult},
};

const TEMP_MNT_POINT_PREFIX: &str = "stratis_mp_";

/// Set the low water mark on the filesystem at 4 times the data low water.  The filesystem
/// expansion check is triggered by crossing the data low water mark for the thin pool.
pub const FILESYSTEM_LOWATER: Sectors = Sectors(4 * (DATA_LOWATER.0 * DATA_BLOCK_SIZE.0));

#[derive(Debug)]
pub struct StratFilesystem {
    thin_dev: ThinDev,
    created: DateTime<Utc>,
    used: Option<Bytes>,
}

fn init_used(thin_dev: &ThinDev) -> Option<Bytes> {
    thin_dev
        .status(get_dm(), DmOptions::default())
        .ok()
        .and_then(|status| {
            if let ThinStatus::Working(s) = status {
                Some(s.nr_mapped_sectors.bytes())
            } else {
                None
            }
        })
}

impl StratFilesystem {
    /// Create a StratFilesystem on top of the given ThinDev.
    pub fn initialize(
        pool_uuid: PoolUuid,
        thinpool_dev: &ThinPoolDev,
        size: Sectors,
        id: ThinDevId,
    ) -> StratisResult<(FilesystemUuid, StratFilesystem)> {
        let fs_uuid = FilesystemUuid::new_v4();
        let (dm_name, dm_uuid) = format_thin_ids(pool_uuid, ThinRole::Filesystem(fs_uuid));
        let mut thin_dev =
            ThinDev::new(get_dm(), &dm_name, Some(&dm_uuid), size, thinpool_dev, id)?;

        if let Err(err) = create_fs(&thin_dev.devnode(), Some(StratisUuid::Fs(fs_uuid)), false) {
            udev_settle().unwrap_or_else(|err| {
                warn!("{}", err);
                sleep(Duration::from_secs(5));
            });
            if let Err(err2) = thin_dev.destroy(get_dm(), thinpool_dev) {
                error!(
                    "While handling create_fs error, thin_dev.destroy() failed: {}",
                    err2
                );
                // This will result in a dangling DM device that will prevent
                // the thinpool from being destroyed, and wasted space in the
                // thinpool.
                // TODO: Recover. But how?
            }
            return Err(err);
        }

        Ok((
            fs_uuid,
            StratFilesystem {
                used: init_used(&thin_dev),
                thin_dev,
                created: Utc::now(),
            },
        ))
    }

    /// Build a StratFilesystem that includes the ThinDev and related info.
    pub fn setup(
        pool_uuid: PoolUuid,
        thinpool_dev: &ThinPoolDev,
        fssave: &FilesystemSave,
    ) -> StratisResult<StratFilesystem> {
        let (dm_name, dm_uuid) = format_thin_ids(pool_uuid, ThinRole::Filesystem(fssave.uuid));
        let thin_dev = ThinDev::setup(
            get_dm(),
            &dm_name,
            Some(&dm_uuid),
            fssave.size,
            thinpool_dev,
            fssave.thin_id,
        )?;
        Ok(StratFilesystem {
            used: init_used(&thin_dev),
            thin_dev,
            created: Utc.timestamp(fssave.created as i64, 0),
        })
    }

    /// Send a synthetic udev change event to the devicemapper device representing
    /// the filesystem.
    pub fn udev_fs_change(&self, pool_name: &str, fs_uuid: FilesystemUuid, fs_name: &str) {
        fn udev_change_event(
            thin_dev: &ThinDev,
            pool_name: &str,
            fs_uuid: FilesystemUuid,
            fs_name: &str,
        ) -> StratisResult<()> {
            let device = thin_dev.device();
            let uevent_file = [
                "/sys/dev/block",
                &format!("{}:{}", device.major, device.minor),
                "uevent",
            ]
            .iter()
            .collect::<PathBuf>();
            OpenOptions::new()
                .write(true)
                .open(&uevent_file)?
                .write_all(
                    format!(
                        "{} {} STRATISPOOLNAME={} STRATISFSNAME={}",
                        devlinks::UEVENT_CHANGE_EVENT,
                        fs_uuid,
                        BASE32_NOPAD.encode(pool_name.as_bytes()),
                        BASE32_NOPAD.encode(fs_name.as_bytes()),
                    )
                    .as_bytes(),
                )?;
            Ok(())
        }

        if let Err(e) = udev_change_event(&self.thin_dev, pool_name, fs_uuid, fs_name) {
            warn!("Failed to notify udev to perform symlink operation: {}", e);
        }
    }

    /// Create a snapshot of the filesystem. Return the resulting filesystem/ThinDev
    /// to the caller.  Use snapshot_name for the Stratis filesystem name.  Use
    /// snapshot_dmname for the new name of the ThinDev allocated for the snapshot.
    /// Mounting a filesystem with a duplicate UUID would require special handling,
    /// so snapshot_fs_uuid is used to update the new snapshot filesystem so it has
    /// a unique UUID.
    #[allow(clippy::too_many_arguments)]
    pub fn snapshot(
        &self,
        thin_pool: &ThinPoolDev,
        snapshot_name: &str,
        snapshot_dm_name: &DmName,
        snapshot_dm_uuid: Option<&DmUuid>,
        snapshot_fs_name: &Name,
        snapshot_fs_uuid: FilesystemUuid,
        snapshot_thin_id: ThinDevId,
    ) -> StratisResult<StratFilesystem> {
        match self.thin_dev.snapshot(
            get_dm(),
            snapshot_dm_name,
            snapshot_dm_uuid,
            thin_pool,
            snapshot_thin_id,
        ) {
            Ok(thin_dev) => {
                // If the source is mounted, XFS puts a dummy record in the
                // log to enforce replay of the snapshot to deal with any
                // orphaned inodes. The dummy record put the log in a dirty
                // state. xfs_admin won't allow a filesystem UUID
                // to be updated when the log is dirty.  To clear the log
                // we mount/unmount the filesystem before updating the UUID.
                //
                // If the source is unmounted the XFS log will be clean so
                // we can skip the mount/unmount.
                if !self.mount_points()?.is_empty() {
                    let tmp_dir = tempfile::Builder::new()
                        .prefix(TEMP_MNT_POINT_PREFIX)
                        .tempdir()?;
                    // Mount the snapshot with the "nouuid" option. mount
                    // will fail due to duplicate UUID otherwise.
                    mount(
                        Some(&thin_dev.devnode()),
                        tmp_dir.path(),
                        Some("xfs"),
                        MsFlags::empty(),
                        Some("nouuid"),
                    )?;
                    umount(tmp_dir.path())?;
                }

                set_uuid(&thin_dev.devnode(), snapshot_fs_uuid)?;
                Ok(StratFilesystem {
                    used: init_used(&thin_dev),
                    thin_dev,
                    created: Utc::now(),
                })
            }
            Err(e) => Err(StratisError::Msg(format!(
                "failed to create {} snapshot for {} - {}",
                snapshot_name, snapshot_fs_name, e
            ))),
        }
    }

    /// Check if filesystem is getting full and needs to be extended.
    ///
    /// Returns:
    /// * Some(_) if metadata should be saved.
    /// * None if metadata should not be saved.
    /// TODO: deal with the thindev in a Fail state.
    pub fn check(&mut self) -> StratisResult<(bool, StratFilesystemDiff)> {
        let mut needs_save = false;
        let original_state = self.cached(|fs| StratFilesystemState {
            size: fs.size(),
            used: fs.used().ok(),
        });
        match self.thin_dev.status(get_dm(), DmOptions::default())? {
            ThinStatus::Working(_) => {
                if let Some(mount_point) = self.mount_points()?.first() {
                    let (fs_total_bytes, fs_total_used_bytes) = fs_usage(mount_point)?;
                    let free_bytes = fs_total_bytes - fs_total_used_bytes;
                    if free_bytes.sectors() < FILESYSTEM_LOWATER {
                        let old_table = self.thin_dev.table().table.clone();
                        let mut new_table = old_table.clone();
                        new_table.length =
                            original_state.size.sectors() + Self::extend_size(self.thin_dev.size());
                        self.thin_dev.set_table(get_dm(), new_table)?;
                        if let Err(causal) = xfs_growfs(mount_point) {
                            if let Err(rollback) = self.thin_dev.set_table(get_dm(), old_table) {
                                return Err(StratisError::RollbackError {
                                    causal_error: Box::new(causal),
                                    rollback_error: Box::new(StratisError::from(rollback)),
                                    level: ActionAvailability::NoPoolChanges,
                                });
                            } else {
                                return Err(causal);
                            }
                        }
                        needs_save = true;
                    }
                }
            }
            ThinStatus::Error => {
                let error_msg = format!(
                    "Unable to get status for filesystem thin device {}",
                    self.thin_dev.device()
                );
                return Err(StratisError::Msg(error_msg));
            }
            _ => (),
        };
        Ok((
            needs_save,
            original_state.diff(&self.dump(|fs| StratFilesystemState {
                used: fs.used().ok(),
                size: fs.size(),
            })),
        ))
    }

    /// Return an extend size for the thindev under the filesystem
    /// TODO: returning the current size will double the space provisioned to
    /// the thin device.  We should determine if this is a reasonable value.
    fn extend_size(current_size: Sectors) -> Sectors {
        current_size
    }

    /// Tear down the filesystem.
    pub fn teardown(&mut self) -> StratisResult<()> {
        self.thin_dev.teardown(get_dm())?;
        Ok(())
    }

    /// Destroy the filesystem.
    pub fn destroy(&mut self, thin_pool: &ThinPoolDev) -> StratisResult<()> {
        self.thin_dev.destroy(get_dm(), thin_pool)?;
        Ok(())
    }

    pub fn record(&self, name: &Name, uuid: FilesystemUuid) -> FilesystemSave {
        FilesystemSave {
            name: name.to_owned(),
            uuid,
            thin_id: self.thin_dev.id(),
            size: self.thin_dev.size(),
            created: self.created.timestamp() as u64,
        }
    }

    /// Find places where this filesystem is mounted.
    fn mount_points(&self) -> StratisResult<Vec<PathBuf>> {
        // Use major:minor values to find mounts for this filesystem
        let major = u64::from(self.thin_dev.device().major);
        let minor = u64::from(self.thin_dev.device().minor);

        let mut mount_data = String::new();
        File::open("/proc/self/mountinfo")?.read_to_string(&mut mount_data)?;
        let parser = libmount::mountinfo::Parser::new(mount_data.as_bytes());

        let mut ret_vec = Vec::new();
        for mp in parser {
            match mp {
                Ok(mount) => {
                    if mount.major as u64 == major && mount.minor as u64 == minor {
                        ret_vec.push(PathBuf::from(&mount.mount_point));
                    }
                }
                Err(e) => {
                    let error_msg = format!("Error during parsing {:?}: {:?}", *self, e);
                    return Err(StratisError::Msg(error_msg));
                }
            }
        }

        Ok(ret_vec)
    }

    pub fn thindev_size(&self) -> Sectors {
        self.thin_dev.size()
    }
}

impl Filesystem for StratFilesystem {
    fn devnode(&self) -> PathBuf {
        self.thin_dev.devnode()
    }

    fn created(&self) -> DateTime<Utc> {
        self.created
    }

    fn path_to_mount_filesystem(&self, pool_name: &str, fs_name: &str) -> PathBuf {
        devlinks::filesystem_mount_path(pool_name, fs_name)
    }

    fn used(&self) -> StratisResult<Bytes> {
        match self.thin_dev.status(get_dm(), DmOptions::default())? {
            ThinStatus::Working(wk_status) => Ok(wk_status.nr_mapped_sectors.bytes()),
            ThinStatus::Error => {
                let error_msg = format!(
                    "Unable to get status for filesystem thin device {}",
                    self.thin_dev.device()
                );
                Err(StratisError::Msg(error_msg))
            }
            ThinStatus::Fail => {
                let error_msg = format!("ThinDev {} is in a failed state", self.thin_dev.device());
                Err(StratisError::Msg(error_msg))
            }
        }
    }

    fn size(&self) -> Bytes {
        self.thin_dev.size().bytes()
    }
}

/// Represents the state of the Stratis filesystem at a given moment in time.
pub struct StratFilesystemState {
    size: Bytes,
    used: Option<Bytes>,
}

impl StateDiff for StratFilesystemState {
    type Diff = StratFilesystemDiff;

    fn diff(&self, new_state: &Self) -> Self::Diff {
        StratFilesystemDiff {
            size: if self.size != new_state.size {
                Some(new_state.size)
            } else {
                None
            },
            used: if self.used != new_state.used {
                Some(new_state.used)
            } else {
                None
            },
        }
    }
}
impl DumpState for StratFilesystem {
    type State = StratFilesystemState;
}

/// Return total bytes allocated to the filesystem, total bytes used by data/metadata
pub fn fs_usage(mount_point: &Path) -> StratisResult<(Bytes, Bytes)> {
    let stat = statvfs(mount_point)?;

    // Upcast all arch-dependent variable width values to u64
    let (block_size, blocks, blocks_free) = (
        stat.block_size() as u64,
        stat.blocks() as u64,
        stat.blocks_free() as u64,
    );
    Ok((
        Bytes::from(block_size * blocks),
        Bytes::from(block_size * (blocks - blocks_free)),
    ))
}

impl<'a> Into<Value> for &'a StratFilesystem {
    fn into(self) -> Value {
        let mut json = Map::new();
        json.insert(
            "size".to_string(),
            Value::from(self.thindev_size().to_string()),
        );
        json.insert(
            "used".to_string(),
            Value::from(
                self.used()
                    .map(|v| v.to_string())
                    .unwrap_or_else(|_| "Unavailable".to_string()),
            ),
        );
        Value::from(json)
    }
}
<commit_msg>Get cached used value for filesystem in check()<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::{
    fs::{File, OpenOptions},
    io::{Read, Write},
    path::{Path, PathBuf},
    thread::sleep,
    time::Duration,
};

use chrono::{DateTime, TimeZone, Utc};
use data_encoding::BASE32_NOPAD;
use serde_json::{Map, Value};

use devicemapper::{
    Bytes, DmDevice, DmName, DmOptions, DmUuid, Sectors, ThinDev, ThinDevId, ThinPoolDev,
    ThinStatus,
};

use nix::{
    mount::{mount, umount, MsFlags},
    sys::statvfs::statvfs,
};

use crate::{
    engine::{
        engine::{DumpState, Filesystem, StateDiff},
        strat_engine::{
            cmd::{create_fs, set_uuid, udev_settle, xfs_growfs},
            devlinks,
            dm::get_dm,
            names::{format_thin_ids, ThinRole},
            serde_structs::FilesystemSave,
            thinpool::{thinpool::DATA_LOWATER, DATA_BLOCK_SIZE},
        },
        types::{
            ActionAvailability, FilesystemUuid, Name, PoolUuid, StratFilesystemDiff, StratisUuid,
        },
    },
    stratis::{StratisError, StratisResult},
};

const TEMP_MNT_POINT_PREFIX: &str = "stratis_mp_";

/// Set the low water mark on the filesystem at 4 times the data low water.  The filesystem
/// expansion check is triggered by crossing the data low water mark for the thin pool.
pub const FILESYSTEM_LOWATER: Sectors = Sectors(4 * (DATA_LOWATER.0 * DATA_BLOCK_SIZE.0));

#[derive(Debug)]
pub struct StratFilesystem {
    thin_dev: ThinDev,
    created: DateTime<Utc>,
    used: Option<Bytes>,
}

fn init_used(thin_dev: &ThinDev) -> Option<Bytes> {
    thin_dev
        .status(get_dm(), DmOptions::default())
        .ok()
        .and_then(|status| {
            if let ThinStatus::Working(s) = status {
                Some(s.nr_mapped_sectors.bytes())
            } else {
                None
            }
        })
}

impl StratFilesystem {
    /// Create a StratFilesystem on top of the given ThinDev.
    pub fn initialize(
        pool_uuid: PoolUuid,
        thinpool_dev: &ThinPoolDev,
        size: Sectors,
        id: ThinDevId,
    ) -> StratisResult<(FilesystemUuid, StratFilesystem)> {
        let fs_uuid = FilesystemUuid::new_v4();
        let (dm_name, dm_uuid) = format_thin_ids(pool_uuid, ThinRole::Filesystem(fs_uuid));
        let mut thin_dev =
            ThinDev::new(get_dm(), &dm_name, Some(&dm_uuid), size, thinpool_dev, id)?;

        if let Err(err) = create_fs(&thin_dev.devnode(), Some(StratisUuid::Fs(fs_uuid)), false) {
            udev_settle().unwrap_or_else(|err| {
                warn!("{}", err);
                sleep(Duration::from_secs(5));
            });
            if let Err(err2) = thin_dev.destroy(get_dm(), thinpool_dev) {
                error!(
                    "While handling create_fs error, thin_dev.destroy() failed: {}",
                    err2
                );
                // This will result in a dangling DM device that will prevent
                // the thinpool from being destroyed, and wasted space in the
                // thinpool.
                // TODO: Recover. But how?
            }
            return Err(err);
        }

        Ok((
            fs_uuid,
            StratFilesystem {
                used: init_used(&thin_dev),
                thin_dev,
                created: Utc::now(),
            },
        ))
    }

    /// Build a StratFilesystem that includes the ThinDev and related info.
    pub fn setup(
        pool_uuid: PoolUuid,
        thinpool_dev: &ThinPoolDev,
        fssave: &FilesystemSave,
    ) -> StratisResult<StratFilesystem> {
        let (dm_name, dm_uuid) = format_thin_ids(pool_uuid, ThinRole::Filesystem(fssave.uuid));
        let thin_dev = ThinDev::setup(
            get_dm(),
            &dm_name,
            Some(&dm_uuid),
            fssave.size,
            thinpool_dev,
            fssave.thin_id,
        )?;
        Ok(StratFilesystem {
            used: init_used(&thin_dev),
            thin_dev,
            created: Utc.timestamp(fssave.created as i64, 0),
        })
    }

    /// Send a synthetic udev change event to the devicemapper device representing
    /// the filesystem.
    pub fn udev_fs_change(&self, pool_name: &str, fs_uuid: FilesystemUuid, fs_name: &str) {
        fn udev_change_event(
            thin_dev: &ThinDev,
            pool_name: &str,
            fs_uuid: FilesystemUuid,
            fs_name: &str,
        ) -> StratisResult<()> {
            let device = thin_dev.device();
            let uevent_file = [
                "/sys/dev/block",
                &format!("{}:{}", device.major, device.minor),
                "uevent",
            ]
            .iter()
            .collect::<PathBuf>();
            OpenOptions::new()
                .write(true)
                .open(&uevent_file)?
                .write_all(
                    format!(
                        "{} {} STRATISPOOLNAME={} STRATISFSNAME={}",
                        devlinks::UEVENT_CHANGE_EVENT,
                        fs_uuid,
                        BASE32_NOPAD.encode(pool_name.as_bytes()),
                        BASE32_NOPAD.encode(fs_name.as_bytes()),
                    )
                    .as_bytes(),
                )?;
            Ok(())
        }

        if let Err(e) = udev_change_event(&self.thin_dev, pool_name, fs_uuid, fs_name) {
            warn!("Failed to notify udev to perform symlink operation: {}", e);
        }
    }

    /// Create a snapshot of the filesystem. Return the resulting filesystem/ThinDev
    /// to the caller.  Use snapshot_name for the Stratis filesystem name.  Use
    /// snapshot_dmname for the new name of the ThinDev allocated for the snapshot.
    /// Mounting a filesystem with a duplicate UUID would require special handling,
    /// so snapshot_fs_uuid is used to update the new snapshot filesystem so it has
    /// a unique UUID.
    #[allow(clippy::too_many_arguments)]
    pub fn snapshot(
        &self,
        thin_pool: &ThinPoolDev,
        snapshot_name: &str,
        snapshot_dm_name: &DmName,
        snapshot_dm_uuid: Option<&DmUuid>,
        snapshot_fs_name: &Name,
        snapshot_fs_uuid: FilesystemUuid,
        snapshot_thin_id: ThinDevId,
    ) -> StratisResult<StratFilesystem> {
        match self.thin_dev.snapshot(
            get_dm(),
            snapshot_dm_name,
            snapshot_dm_uuid,
            thin_pool,
            snapshot_thin_id,
        ) {
            Ok(thin_dev) => {
                // If the source is mounted, XFS puts a dummy record in the
                // log to enforce replay of the snapshot to deal with any
                // orphaned inodes. The dummy record put the log in a dirty
                // state. xfs_admin won't allow a filesystem UUID
                // to be updated when the log is dirty.  To clear the log
                // we mount/unmount the filesystem before updating the UUID.
                //
                // If the source is unmounted the XFS log will be clean so
                // we can skip the mount/unmount.
                if !self.mount_points()?.is_empty() {
                    let tmp_dir = tempfile::Builder::new()
                        .prefix(TEMP_MNT_POINT_PREFIX)
                        .tempdir()?;
                    // Mount the snapshot with the "nouuid" option. mount
                    // will fail due to duplicate UUID otherwise.
                    mount(
                        Some(&thin_dev.devnode()),
                        tmp_dir.path(),
                        Some("xfs"),
                        MsFlags::empty(),
                        Some("nouuid"),
                    )?;
                    umount(tmp_dir.path())?;
                }

                set_uuid(&thin_dev.devnode(), snapshot_fs_uuid)?;
                Ok(StratFilesystem {
                    used: init_used(&thin_dev),
                    thin_dev,
                    created: Utc::now(),
                })
            }
            Err(e) => Err(StratisError::Msg(format!(
                "failed to create {} snapshot for {} - {}",
                snapshot_name, snapshot_fs_name, e
            ))),
        }
    }

    /// Check if filesystem is getting full and needs to be extended.
    ///
    /// Returns:
    /// * Some(_) if metadata should be saved.
    /// * None if metadata should not be saved.
    /// TODO: deal with the thindev in a Fail state.
    pub fn check(&mut self) -> StratisResult<(bool, StratFilesystemDiff)> {
        let mut needs_save = false;
        let original_state = self.cached(|fs| StratFilesystemState {
            size: fs.size(),
            used: fs.used,
        });
        match self.thin_dev.status(get_dm(), DmOptions::default())? {
            ThinStatus::Working(_) => {
                if let Some(mount_point) = self.mount_points()?.first() {
                    let (fs_total_bytes, fs_total_used_bytes) = fs_usage(mount_point)?;
                    let free_bytes = fs_total_bytes - fs_total_used_bytes;
                    if free_bytes.sectors() < FILESYSTEM_LOWATER {
                        let old_table = self.thin_dev.table().table.clone();
                        let mut new_table = old_table.clone();
                        new_table.length =
                            original_state.size.sectors() + Self::extend_size(self.thin_dev.size());
                        self.thin_dev.set_table(get_dm(), new_table)?;
                        if let Err(causal) = xfs_growfs(mount_point) {
                            if let Err(rollback) = self.thin_dev.set_table(get_dm(), old_table) {
                                return Err(StratisError::RollbackError {
                                    causal_error: Box::new(causal),
                                    rollback_error: Box::new(StratisError::from(rollback)),
                                    level: ActionAvailability::NoPoolChanges,
                                });
                            } else {
                                return Err(causal);
                            }
                        }
                        needs_save = true;
                    }
                }
            }
            ThinStatus::Error => {
                let error_msg = format!(
                    "Unable to get status for filesystem thin device {}",
                    self.thin_dev.device()
                );
                return Err(StratisError::Msg(error_msg));
            }
            _ => (),
        };
        Ok((
            needs_save,
            original_state.diff(&self.dump(|fs| StratFilesystemState {
                used: fs.used().ok(),
                size: fs.size(),
            })),
        ))
    }

    /// Return an extend size for the thindev under the filesystem
    /// TODO: returning the current size will double the space provisioned to
    /// the thin device.  We should determine if this is a reasonable value.
    fn extend_size(current_size: Sectors) -> Sectors {
        current_size
    }

    /// Tear down the filesystem.
    pub fn teardown(&mut self) -> StratisResult<()> {
        self.thin_dev.teardown(get_dm())?;
        Ok(())
    }

    /// Destroy the filesystem.
    pub fn destroy(&mut self, thin_pool: &ThinPoolDev) -> StratisResult<()> {
        self.thin_dev.destroy(get_dm(), thin_pool)?;
        Ok(())
    }

    pub fn record(&self, name: &Name, uuid: FilesystemUuid) -> FilesystemSave {
        FilesystemSave {
            name: name.to_owned(),
            uuid,
            thin_id: self.thin_dev.id(),
            size: self.thin_dev.size(),
            created: self.created.timestamp() as u64,
        }
    }

    /// Find places where this filesystem is mounted.
    fn mount_points(&self) -> StratisResult<Vec<PathBuf>> {
        // Use major:minor values to find mounts for this filesystem
        let major = u64::from(self.thin_dev.device().major);
        let minor = u64::from(self.thin_dev.device().minor);

        let mut mount_data = String::new();
        File::open("/proc/self/mountinfo")?.read_to_string(&mut mount_data)?;
        let parser = libmount::mountinfo::Parser::new(mount_data.as_bytes());

        let mut ret_vec = Vec::new();
        for mp in parser {
            match mp {
                Ok(mount) => {
                    if mount.major as u64 == major && mount.minor as u64 == minor {
                        ret_vec.push(PathBuf::from(&mount.mount_point));
                    }
                }
                Err(e) => {
                    let error_msg = format!("Error during parsing {:?}: {:?}", *self, e);
                    return Err(StratisError::Msg(error_msg));
                }
            }
        }

        Ok(ret_vec)
    }

    pub fn thindev_size(&self) -> Sectors {
        self.thin_dev.size()
    }
}

impl Filesystem for StratFilesystem {
    fn devnode(&self) -> PathBuf {
        self.thin_dev.devnode()
    }

    fn created(&self) -> DateTime<Utc> {
        self.created
    }

    fn path_to_mount_filesystem(&self, pool_name: &str, fs_name: &str) -> PathBuf {
        devlinks::filesystem_mount_path(pool_name, fs_name)
    }

    fn used(&self) -> StratisResult<Bytes> {
        match self.thin_dev.status(get_dm(), DmOptions::default())? {
            ThinStatus::Working(wk_status) => Ok(wk_status.nr_mapped_sectors.bytes()),
            ThinStatus::Error => {
                let error_msg = format!(
                    "Unable to get status for filesystem thin device {}",
                    self.thin_dev.device()
                );
                Err(StratisError::Msg(error_msg))
            }
            ThinStatus::Fail => {
                let error_msg = format!("ThinDev {} is in a failed state", self.thin_dev.device());
                Err(StratisError::Msg(error_msg))
            }
        }
    }

    fn size(&self) -> Bytes {
        self.thin_dev.size().bytes()
    }
}

/// Represents the state of the Stratis filesystem at a given moment in time.
pub struct StratFilesystemState {
    size: Bytes,
    used: Option<Bytes>,
}

impl StateDiff for StratFilesystemState {
    type Diff = StratFilesystemDiff;

    fn diff(&self, new_state: &Self) -> Self::Diff {
        StratFilesystemDiff {
            size: if self.size != new_state.size {
                Some(new_state.size)
            } else {
                None
            },
            used: if self.used != new_state.used {
                Some(new_state.used)
            } else {
                None
            },
        }
    }
}
impl DumpState for StratFilesystem {
    type State = StratFilesystemState;
}

/// Return total bytes allocated to the filesystem, total bytes used by data/metadata
pub fn fs_usage(mount_point: &Path) -> StratisResult<(Bytes, Bytes)> {
    let stat = statvfs(mount_point)?;

    // Upcast all arch-dependent variable width values to u64
    let (block_size, blocks, blocks_free) = (
        stat.block_size() as u64,
        stat.blocks() as u64,
        stat.blocks_free() as u64,
    );
    Ok((
        Bytes::from(block_size * blocks),
        Bytes::from(block_size * (blocks - blocks_free)),
    ))
}

impl<'a> Into<Value> for &'a StratFilesystem {
    fn into(self) -> Value {
        let mut json = Map::new();
        json.insert(
            "size".to_string(),
            Value::from(self.thindev_size().to_string()),
        );
        json.insert(
            "used".to_string(),
            Value::from(
                self.used()
                    .map(|v| v.to_string())
                    .unwrap_or_else(|_| "Unavailable".to_string()),
            ),
        );
        Value::from(json)
    }
}
<|endoftext|>