klemmbrett 0.1.1

Minimalist, dumb and fast wayland native clipboard history manager
use std::fmt;
use std::io::{self, Read, Write};

use super::{read_bytes, read_u8, read_u32, read_uuid};

pub type Uuid = [u8; 16];
pub const MAGIC: u32 = 0xDEADAFFE;

#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum Command {
    Get = 0,
    List = 1,
    Set = 2,
}

impl TryFrom<u8> for Command {
    type Error = u8;

    fn try_from(b: u8) -> Result<Self, u8> {
        Ok(match b {
            0 => Self::Get,
            1 => Self::List,
            2 => Self::Set,
            _ => return Err(b),
        })
    }
}

#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum Code {
    Ok = 0,
    BadMagic = 1,
    UnknownCommand = 2,
    NotFound = 3,
    Internal = 4,
}

impl TryFrom<u8> for Code {
    type Error = u8;

    fn try_from(b: u8) -> Result<Self, u8> {
        Ok(match b {
            0 => Self::Ok,
            1 => Self::BadMagic,
            2 => Self::UnknownCommand,
            3 => Self::NotFound,
            4 => Self::Internal,
            _ => return Err(b),
        })
    }
}

#[derive(Debug)]
pub struct Error {
    pub code: Code,
    pub text: String,
}

pub type Response<T> = Result<T, Error>;

impl Error {
    pub fn new(code: Code, text: impl Into<String>) -> Self {
        Self {
            code,
            text: text.into(),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}: {}", self.code, self.text)
    }
}

impl std::error::Error for Error {}

pub fn write_status(w: &mut impl Write, status: &Response<()>) -> io::Result<()> {
    let (code, text) = match status {
        Ok(()) => (Code::Ok, ""),
        Err(e) => (e.code, e.text.as_str()),
    };
    let text = &text.as_bytes()[..text.len().min(u8::MAX as usize)];
    w.write_all(&[code as u8, text.len() as u8])?;
    w.write_all(text)
}

pub fn read_status(r: &mut impl Read) -> io::Result<Response<()>> {
    let code = read_u8(r)?;
    let len = read_u8(r)?;
    let text = String::from_utf8_lossy(&read_bytes(r, len as usize)?).into_owned();
    Ok(match Code::try_from(code) {
        Ok(Code::Ok) => Ok(()),
        Ok(code) => Err(Error::new(code, text)),
        Err(code) => Err(Error::new(
            Code::Internal,
            format!("unknown response code {code}"),
        )),
    })
}

#[derive(Clone)]
pub struct Entry {
    pub id: Uuid,
    pub mime: String,
    pub data: Vec<u8>,
}

impl Entry {
    pub fn write(&self, w: &mut impl Write) -> io::Result<()> {
        w.write_all(&self.id)?;
        w.write_all(&[self.mime.len() as u8])?;
        w.write_all(self.mime.as_bytes())?;
        w.write_all(&(self.data.len() as u32).to_le_bytes())?;
        w.write_all(&self.data)
    }

    pub fn read(r: &mut impl Read) -> io::Result<Self> {
        let id = read_uuid(r)?;
        let mime_len = read_u8(r)? as usize;
        let mime = String::from_utf8_lossy(&read_bytes(r, mime_len)?).into_owned();
        let data_len = read_u32(r)? as usize;
        let data = read_bytes(r, data_len)?;
        Ok(Self { id, mime, data })
    }
}