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> {
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; 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))
}
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; 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(())
}
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; 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; fl.write_u8(exp)?; fl.write_u8(0)?; 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; 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());
}
}