use alloc::borrow::Cow;
use core::cell::RefCell;
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use crate::{Storage, StorageIndex, StorageResult};
pub struct FileStorage {
options: FileOptions,
file: RefCell<File>,
}
pub struct FileOptions {
pub word_size: usize,
pub page_size: usize,
pub num_pages: usize,
}
impl FileStorage {
pub fn new(path: &Path, options: FileOptions) -> StorageResult<FileStorage> {
let mut file_ref =
RefCell::new(OpenOptions::new().read(true).write(true).create(true).open(path)?);
let file = file_ref.get_mut();
let file_len = file.metadata()?.len();
let store_len: u64 = (options.page_size * options.num_pages) as u64;
if file_len == 0 {
file.seek(SeekFrom::Start(0))?;
let buf = vec![0xff; options.page_size];
for _ in 0 .. options.num_pages {
file.write_all(&buf)?;
}
} else if file_len != store_len {
panic!("Invalid file size {file_len}, should be {store_len}");
}
Ok(FileStorage { options, file: file_ref })
}
}
impl Storage for FileStorage {
fn word_size(&self) -> usize {
self.options.word_size
}
fn page_size(&self) -> usize {
self.options.page_size
}
fn num_pages(&self) -> usize {
self.options.num_pages
}
fn max_word_writes(&self) -> usize {
u32::MAX as usize
}
fn max_page_erases(&self) -> usize {
u16::MAX as usize
}
fn read_slice(&self, index: StorageIndex, length: usize) -> StorageResult<Cow<[u8]>> {
let mut file = self.file.borrow_mut();
file.seek(SeekFrom::Start(index.range(length, self)?.start as u64))?;
let mut buf = vec![0u8; length];
file.read_exact(&mut buf)?;
Ok(Cow::Owned(buf))
}
fn write_slice(&mut self, index: StorageIndex, value: &[u8]) -> StorageResult<()> {
let mut file = self.file.borrow_mut();
file.seek(SeekFrom::Start(index.range(value.len(), self)?.start as u64))?;
file.write_all(value)?;
Ok(())
}
fn erase_page(&mut self, page: usize) -> StorageResult<()> {
let mut file = self.file.borrow_mut();
let index = StorageIndex { page, byte: 0 };
file.seek(SeekFrom::Start(index.range(self.page_size(), self)?.start as u64))?;
file.write_all(&vec![0xff; self.page_size()][..])?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use tempfile::TempDir;
use super::*;
const BLANK_WORD: &[u8] = &[0xff, 0xff, 0xff, 0xff];
const DATA_WORD: &[u8] = &[0xee, 0xdd, 0xbb, 0x77];
const FILE_NAME: &str = "opensk_storage.bin";
const OPTIONS: FileOptions = FileOptions { word_size: 4, page_size: 0x1000, num_pages: 20 };
fn make_tmp_dir() -> PathBuf {
let tmp_dir = TempDir::new().unwrap();
tmp_dir.into_path()
}
fn remove_tmp_dir(tmp_dir: PathBuf) {
std::fs::remove_dir_all(tmp_dir).unwrap();
}
fn temp_storage(tmp_dir: &PathBuf) -> FileStorage {
let mut tmp_file = tmp_dir.clone();
tmp_file.push(FILE_NAME);
FileStorage::new(&tmp_file, OPTIONS).unwrap()
}
#[test]
fn read_write_persist_ok() {
let index = StorageIndex { page: 0, byte: 0 };
let next_index = StorageIndex { page: 0, byte: 4 };
let tmp_dir = make_tmp_dir();
{
let mut file_storage = temp_storage(&tmp_dir);
assert_eq!(file_storage.read_slice(index, 4).unwrap(), BLANK_WORD);
file_storage.write_slice(index, DATA_WORD).unwrap();
assert_eq!(file_storage.read_slice(index, 4).unwrap(), DATA_WORD);
assert_eq!(file_storage.read_slice(next_index, 4).unwrap(), BLANK_WORD);
}
{
let file_storage = temp_storage(&tmp_dir);
assert_eq!(file_storage.read_slice(index, 4).unwrap(), DATA_WORD);
assert_eq!(file_storage.read_slice(next_index, 4).unwrap(), BLANK_WORD);
}
remove_tmp_dir(tmp_dir);
}
}