use std::{
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
path::PathBuf,
};
use crate::storage::{
error::StorageError,
page::{PAGE_SIZE, Page},
};
pub struct HeapFile {
file: File,
path: PathBuf,
pub num_pages: u32,
}
impl HeapFile {
pub fn open(path: impl Into<PathBuf>) -> Result<Self, StorageError> {
let path = path.into();
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true) .open(&path)
.map_err(|e| StorageError::io(&path, e))?;
let file_len = file
.metadata()
.map_err(|e| StorageError::io(&path, e))?
.len();
let num_pages = (file_len / PAGE_SIZE as u64) as u32;
Ok(Self {
file,
path,
num_pages,
})
}
pub fn allocate_page(&mut self) -> Result<u32, StorageError> {
let page_id = self.num_pages;
let page = Page::new(page_id);
self.seek_to(page_id)?;
self.file
.write_all(page.as_bytes())
.map_err(|e| StorageError::io(&self.path, e))?;
self.file
.flush()
.map_err(|e| StorageError::io(&self.path, e))?;
self.num_pages += 1;
Ok(page_id)
}
pub fn read_page(&mut self, page_id: u32) -> Result<Page, StorageError> {
if page_id >= self.num_pages {
return Err(StorageError::PageOutOfBounds {
page_id,
num_pages: self.num_pages,
});
}
self.seek_to(page_id)?;
let mut buf = [0u8; PAGE_SIZE];
self.file
.read_exact(&mut buf)
.map_err(|e| StorageError::io(&self.path, e))?;
Ok(Page::from_bytes(buf))
}
pub fn write_page(&mut self, page: &Page) -> Result<(), StorageError> {
let page_id = page.page_id();
if page_id >= self.num_pages {
return Err(StorageError::PageOutOfBounds {
page_id,
num_pages: self.num_pages,
});
}
self.seek_to(page_id)?;
self.file
.write_all(page.as_bytes())
.map_err(|e| StorageError::io(&self.path, e))?;
self.file
.flush()
.map_err(|e| StorageError::io(&self.path, e))?;
Ok(())
}
fn seek_to(&mut self, page_id: u32) -> Result<(), StorageError> {
let offset = page_id as u64 * PAGE_SIZE as u64;
self.file
.seek(SeekFrom::Start(offset))
.map_err(|e| StorageError::io(&self.path, e))?;
Ok(())
}
}