use core::fmt;
use std::{
error::Error,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
sync::Mutex,
};
pub type GenericError = Box<dyn Error + Send + Sync + 'static>;
pub type FireflyResult<T> = Result<T, GenericError>;
pub type OptResult = FireflyResult<()>;
pub type StringResult = FireflyResult<String>;
pub struct FireflyStream {
tcp_stream: Arc<Mutex<TcpStream>>,
max_buffer_size: usize,
pub default_ttl: usize,
}
#[derive(Debug)]
pub enum FireflyError {
UnexpectedResponseError,
}
impl Error for FireflyError {}
impl fmt::Display for FireflyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
fn current_epoch() -> usize {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as usize
}
impl FireflyStream {
pub async fn connect(address: &str) -> FireflyResult<Self> {
Self::connect_with_max_buffer(address, 512).await
}
pub async fn connect_with_max_buffer(
address: &str,
max_buffer_size: usize,
) -> FireflyResult<Self> {
let tcp_stream = Arc::new(Mutex::new(TcpStream::connect(address).await?));
let client = Self {
max_buffer_size,
tcp_stream,
default_ttl: 0,
};
client.send_ok("QUERY TYPE BITWISE;".as_bytes()).await?;
Ok(client)
}
async fn send_no_check(&self, data: &[u8]) -> StringResult {
let mut stream = self.tcp_stream.lock().await;
stream.write(data).await?;
let mut buffer = vec![0; self.max_buffer_size];
let response_size = stream.read(&mut buffer).await?;
Ok(String::from_utf8(buffer[..response_size].to_vec()).unwrap())
}
async fn send(&self, data: &[u8], expected: fn(&str) -> bool) -> StringResult {
let response = self.send_no_check(data).await?;
if expected(&response) {
return Ok(response);
}
Err(FireflyError::UnexpectedResponseError.into())
}
async fn send_ok(&self, data: &[u8]) -> StringResult {
self.send(data, |expected| {
expected.contains("Ok") || !expected.contains("Error")
})
.await
}
pub async fn new(&self, key: &str, value: &str) -> OptResult {
let mut ttl = self.default_ttl;
if ttl != 0 {
ttl += current_epoch();
}
self.new_with_ttl(key, value, ttl).await
}
pub async fn new_with_ttl(&self, key: &str, value: &str, ttl: usize) -> OptResult {
let query = format!("0{key}\0{value}\0{ttl}");
self.send_ok(query.as_bytes()).await?;
Ok(())
}
pub async fn get(&self, key: &str) -> FireflyResult<(String, usize)> {
let query = format!("1{key}");
let data = self
.send(query.as_bytes(), |response| response.contains(0 as char))
.await?;
match data.split_once(0 as char) {
Some((value, ttl)) => Ok((value.to_string(), ttl.parse().unwrap())),
None => Err(FireflyError::UnexpectedResponseError.into()),
}
}
pub async fn get_value(&self, key: &str) -> StringResult {
self.send_ok(format!("2{key}").as_bytes()).await
}
pub async fn get_ttl(&self, key: &str) -> FireflyResult<usize> {
let ttl = self.send_ok(format!("3{key}").as_bytes()).await?;
Ok(ttl.parse()?)
}
pub async fn drop(&self, key: &str) -> OptResult {
self.send_ok(format!("4{key}").as_bytes()).await?;
Ok(())
}
pub async fn drop_values(&self, value: &str) -> OptResult {
self.send_ok(format!("5{value}").as_bytes()).await?;
Ok(())
}
}