persy 0.7.0

Transactional Persistence Engine
Documentation
use crate::error::PRes;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::{
    cmp,
    fs::File,
    io::{self, Read, Seek, SeekFrom, Write},
    sync::Mutex,
};
pub const PAGE_METADATA_SIZE: u32 = 2;

pub struct DiscRef {
    file: Mutex<File>,
}
#[derive(Clone)]
pub struct Page {
    buff: Vec<u8>,
    index: u64,
    exp: u8,
    pos: usize,
}

pub trait PageSeek {
    fn seek(&mut self, pos: u32) -> PRes<()>;
}

pub trait PageIndex {
    fn get_index(&self) -> u64;
}

impl Page {
    pub fn new(buff: Vec<u8>, pos: usize, index: u64, exp: u8) -> Page {
        Page { buff, index, exp, pos }
    }
    pub fn clone_resetted(&self) -> Page {
        let mut page = self.clone();
        page.pos = 2;
        page
    }
}

impl Read for Page {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let len = self.buff.len();
        let amt = cmp::min(self.pos, len);
        let read = Read::read(&mut &self.buff[(amt as usize)..len], buf)?;
        self.pos += read;
        Ok(read)
    }
}

impl Write for Page {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let pre = self.pos;
        let len = self.buff.len();
        if pre + (*buf).len() > len {
            panic!(
                "Over page allowed content size:{}, data size: {}",
                len,
                pre + (*buf).len()
            );
        }
        let pos = cmp::min(self.pos, len);
        let amt = (&mut self.buff[(pos as usize)..len]).write(buf)?;
        self.pos += amt;
        Ok(amt)
    }

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

impl PageSeek for Page {
    fn seek(&mut self, pos: u32) -> PRes<()> {
        self.pos = (pos + 2) as usize;
        Ok(())
    }
}

impl PageIndex for Page {
    fn get_index(&self) -> u64 {
        self.index
    }
}

impl Page {
    pub fn get_index(&self) -> u64 {
        self.index
    }

    pub fn get_size_exp(&self) -> u8 {
        self.exp
    }

    pub fn set_free(&mut self, free: bool) -> PRes<()> {
        if free {
            self.buff[1] |= 0b1000_0000;
        } else {
            self.buff[1] &= !0b1000_0000;
        }
        Ok(())
    }

    pub fn is_free(&self) -> PRes<bool> {
        Ok((self.buff[1] & 0b1000_0000) != 0)
    }

    pub fn set_next_free(&mut self, next: u64) -> PRes<()> {
        let pre = self.pos;
        self.pos = 2;
        self.write_u64::<BigEndian>(next)?;
        self.pos = pre;
        Ok(())
    }

    pub fn get_next_free(&mut self) -> PRes<u64> {
        let pre = self.pos;
        self.pos = 2;
        let val = self.read_u64::<BigEndian>()?;
        self.pos = pre;
        Ok(val)
    }

    pub fn reset(&mut self) -> PRes<()> {
        self.buff = vec![0; self.buff.len()];
        self.buff[0] = self.exp;
        Ok(())
    }
}

impl DiscRef {
    pub fn new(file: File) -> DiscRef {
        DiscRef { file: Mutex::new(file) }
    }

    pub fn load_page(&self, page: u64) -> PRes<Page> {
        // add 2 to skip the metadata
        let mut ve;
        let exp;
        {
            let fl = &mut self.file.lock()?;
            fl.seek(SeekFrom::Start(page))?;
            exp = fl.read_u8()?;
            let size = (1 << exp) as u64; //EXP - (size_exp+size_mitigator);
            fl.seek(SeekFrom::Current(-1))?;
            ve = vec![0 as u8; size as usize];
            fl.read_exact(&mut ve[0..size as usize])?;
        }
        Ok(Page::new(ve, 2, page, exp))
    }

    /// Load a page avoiding to skip base page metadata, used for root page or metadata
    /// manipulation.
    ///
    pub fn load_page_raw(&self, page: u64, size_exp: u8) -> PRes<Page> {
        let mut ve;
        let size;
        {
            let fl = &mut self.file.lock()?;
            fl.seek(SeekFrom::Start(page))?;
            size = (1 << size_exp) as u64; //EXP - (size_exp+size_mitigator);
            ve = vec![0 as u8; size as usize];
            fl.read_exact(&mut ve[0..size as usize])?;
        }
        Ok(Page::new(ve, 0, page, size_exp))
    }

    pub fn flush_page(&self, page: &Page) -> PRes<()> {
        let fl = &mut self.file.lock()?;
        fl.seek(SeekFrom::Start(page.index))?;
        fl.write_all(&page.buff)?;
        Ok(())
    }

    /// Create a page without setting metadata, used by root page
    pub fn create_page_raw(&self, exp: u8) -> PRes<u64> {
        let fl = &mut self.file.lock()?;
        let offset = fl.seek(SeekFrom::End(0))?;
        let size: u64 = (1 << exp) as u64; //EXP - (size_exp+size_mitigator);
        fl.set_len(offset + size)?;
        Ok(offset)
    }

    pub fn create_page(&self, exp: u8) -> PRes<u64> {
        let fl = &mut self.file.lock()?;
        let offset = fl.seek(SeekFrom::End(0))?;
        let size: u64 = (1 << exp) as u64; //EXP - (size_exp+size_mitigator);
        fl.write_u8(exp)?; //exp
        fl.write_u8(0)?; //mitigator
        fl.set_len(offset + size)?;
        Ok(offset)
    }

    pub fn sync(&self) -> PRes<()> {
        let to_sync;
        {
            to_sync = self.file.lock()?.try_clone()?;
        }
        to_sync.sync_all()?;
        Ok(())
    }

    pub fn trim_or_load_page(&self, page: u64) -> PRes<Option<Page>> {
        let fl = &mut self.file.lock()?;
        fl.seek(SeekFrom::Start(page))?;
        let exp = fl.read_u8()?;
        let size = (1 << exp) as u64; //EXP - (size_exp+size_mitigator);
        Ok(if page + size == fl.metadata()?.len() {
            fl.set_len(page + 1)?;
            None
        } else {
            fl.seek(SeekFrom::Current(-1))?;
            let mut ve = vec![0 as u8; size as usize];
            fl.read_exact(&mut ve[0..size as usize])?;
            Some(Page::new(ve, 2, page, exp))
        })
    }
}

#[cfg(test)]
mod tests {
    use super::DiscRef;
    use byteorder::{ReadBytesExt, WriteBytesExt};
    use tempfile::Builder;

    #[test]
    fn create_load_flush_page() {
        let file = Builder::new()
            .prefix("disc_ref.raw")
            .suffix(".persy")
            .tempfile()
            .unwrap()
            .reopen()
            .unwrap();
        let disc = DiscRef::new(file);
        let page = disc.create_page(5).unwrap();
        let pg = &mut disc.load_page(page).unwrap();
        disc.flush_page(pg).unwrap();
    }

    #[test]
    fn set_get_next_free() {
        let file = Builder::new()
            .prefix("set_free.raw")
            .suffix(".persy")
            .tempfile()
            .unwrap()
            .reopen()
            .unwrap();
        let disc = DiscRef::new(file);
        let page = disc.create_page(5).unwrap();
        let pg = &mut disc.load_page(page).unwrap();
        pg.set_next_free(30).unwrap();
        disc.flush_page(pg).unwrap();
        let pg1 = &mut disc.load_page(page).unwrap();
        let val = pg1.get_next_free().unwrap();
        assert_eq!(val, 30);
    }

    #[test]
    fn get_size_page() {
        let file = Builder::new()
            .prefix("get_size.raw")
            .suffix(".persy")
            .tempfile()
            .unwrap()
            .reopen()
            .unwrap();
        let disc = DiscRef::new(file);
        let page = disc.create_page(5).unwrap();
        let pg = &mut disc.load_page(page).unwrap();
        let sz = pg.get_size_exp();
        assert_eq!(sz, 5);
    }

    #[test]
    fn write_read_page() {
        let file = Builder::new()
            .prefix("write_read.raw")
            .suffix(".persy")
            .tempfile()
            .unwrap()
            .reopen()
            .unwrap();
        let disc = DiscRef::new(file);
        let page = disc.create_page(5).unwrap();
        {
            let pg = &mut disc.load_page(page).unwrap();
            pg.write_u8(10).unwrap();
            disc.flush_page(pg).unwrap();
        }
        {
            let pg = &mut disc.load_page(page).unwrap();
            let va = pg.read_u8().unwrap();
            assert_eq!(va, 10);
            let sz = pg.get_size_exp();
            assert_eq!(sz, 5);
        }
    }

    #[test]
    fn create_load_trim() {
        let file = Builder::new()
            .prefix("disc_ref_trim.raw")
            .suffix(".persy")
            .tempfile()
            .unwrap()
            .reopen()
            .unwrap();
        let disc = DiscRef::new(file);
        let page = disc.create_page(5).unwrap();
        let pg = &mut disc.load_page(page).unwrap();
        disc.flush_page(pg).unwrap();
        assert!(disc.trim_or_load_page(page).unwrap().is_none());
        assert!(disc.load_page(page).is_err());
    }

    #[test]
    fn create_not_trim_not_last() {
        let file = Builder::new()
            .prefix("disc_ref_no_trim.raw")
            .suffix(".persy")
            .tempfile()
            .unwrap()
            .reopen()
            .unwrap();
        let disc = DiscRef::new(file);
        let page = disc.create_page(5).unwrap();
        let _page_after = disc.create_page(5).unwrap();
        let pg = &mut disc.load_page(page).unwrap();
        disc.flush_page(pg).unwrap();
        assert!(disc.trim_or_load_page(page).unwrap().is_some());
        assert!(disc.load_page(page).is_ok());
    }
}