use crate::{Command, Error, Message, Response, Result};
use directories::ProjectDirs;
use std::io::{BufRead, BufReader, ErrorKind, Write};
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
pub struct Connection {
stream: UnixStream,
reader: BufReader<UnixStream>,
buffer: Vec<u8>,
}
impl Connection {
pub fn new(stream: UnixStream) -> Result<Self> {
let reader_stream = match stream.try_clone() {
Ok(s) => s,
Err(e) => return Err(Error::SocketError(e)),
};
let reader = BufReader::new(reader_stream);
let buffer = Vec::new();
Ok(Self {
stream,
reader,
buffer,
})
}
pub fn send(&mut self, cmd: &Message) -> Result<()> {
let data = match serde_json::to_vec(cmd) {
Ok(d) => d,
Err(e) => return Err(Error::SerializeError(e)),
};
let data = [data, vec![b'\n']].concat();
match self.stream.write(&data) {
Ok(n) => {
if n == 0 {
return Err(Error::ConnectionClosed);
}
}
Err(e) => return Err(Error::SocketError(e)),
}
match self.stream.flush() {
Ok(_) => Ok(()),
Err(e) => match e.kind() {
ErrorKind::WriteZero => Err(Error::ConnectionClosed),
_ => Err(Error::SocketError(e)),
},
}
}
pub fn recv(&mut self) -> Result<Message> {
match self.reader.read_until(b'\n', &mut self.buffer) {
Ok(n) => {
if n == 0 {
return Err(Error::ConnectionClosed);
};
}
Err(e) => {
return Err(Error::SocketError(e));
}
}
let cmd: Message = match serde_json::from_slice(&self.buffer) {
Ok(cmd) => cmd,
Err(e) => return Err(Error::DeserializeError(e)),
};
Ok(cmd)
}
pub fn recv_command(&mut self) -> Result<Command> {
let msg = self.recv()?;
if let Message::Command(cmd) = msg {
Ok(cmd)
} else {
Err(Error::WrongMessageKind)
}
}
pub fn recv_response(&mut self) -> Result<Response> {
let msg = self.recv()?;
if let Message::Response(cmd) = msg {
Ok(cmd)
} else {
Err(Error::WrongMessageKind)
}
}
}
pub fn create_client_connection() -> Result<Connection> {
let fp = socket_file_path()?;
let stream = match UnixStream::connect(fp) {
Ok(stream) => stream,
Err(e) => return Err(Error::SocketError(e)),
};
Connection::new(stream)
}
pub fn socket_file_path() -> Result<PathBuf> {
let dirs = match ProjectDirs::from("", "", "sopht") {
Some(d) => d,
None => return Err(Error::NoHomeDir),
};
Ok(dirs.data_dir().join("sopht.sock"))
}