~jeffa/filo

566ce1124dae96b93a53479ea12c0200d56ba989 — Jeff a month ago f5e641d master
Expand API ideas
2 files changed, 154 insertions(+), 22 deletions(-)

M src/filo.rs
M src/filo_rw.rs
M src/filo.rs => src/filo.rs +149 -17
@@ 4,29 4,130 @@ use io::{BufRead, BufReader};
///
/// Filo manages an open file and a buffer. Filo can read, write and remove instances of a type that
/// implements [FiloRw](crate::filo_rw::FiloRw).

use crate::filo_rw::FiloRw;
use std::{fs::File, io, io::{Seek, SeekFrom, Write}, path::PathBuf};
use std::{
    fs::File,
    io,
    io::{BufWriter, Seek, SeekFrom, Write},
    marker::PhantomData,
    path::PathBuf,
};

pub struct Filo {
pub struct Filo<T> {
    file: File,
    reader: BufReader<File>,
    buf: Vec<u8>,
    writer: BufWriter<File>,
    len: usize,
    _item: PhantomData<T>,
}
impl Filo {
    pub fn new<T: FiloRw>(path: PathBuf) -> Result<Self, io::Error> {
impl<T: FiloRw> Filo<T> {
    /// Create a new Filo instance.
    pub fn new(path: PathBuf) -> Result<Self, io::Error> {
        let file = File::with_options()
            .read(true)
            .write(true)
            .create(true)
            .open(path)?;
        let buf = Vec::with_capacity(T::SIZE);
        let reader = BufReader::with_capacity(T::SIZE, file.try_clone()?);
        let writer = BufWriter::new(file.try_clone()?);
        let len = 0;
        let _item = PhantomData;

        Ok(Filo {
            file,
            reader,
            writer,
            len,
            _item,
        })
    }

    /// Returns the number of items stored by the Filo.
    pub fn len(&self) -> usize {
        self.len
    }

    /// Adds a single item to the end of the filo.
    ///
    /// This method should only be used when there is a single item to be added. If applicable, use
    /// [extend] with an iterator to take advantage of the writer's internal buffer for improved
    /// performance.
    pub fn push(&mut self, item: T) -> Result<(), io::Error>
    where
        [u8; T::SIZE]: Sized,
    {
        let new_index = self.len + 1;
        let write_result = self.write_index(item, new_index);

        if write_result.is_ok() {
            self.len += 1;
        }

        write_result
    }

    /// Reads the entire underlying file and sets the Filo's "len" field to the actual number of
    /// items that are stored. This will also clean up any trailing bytes caused by a partially
    /// failed write.
    ///
    /// This method can potentially take a long time, depending on the number of items. By extending
    /// the buffer to count many items at once, performance is improved compared to reading one item
    /// at a time. However, the time complexity is still linear (i.e. O(n)).
    pub fn sync_len(&self) {
        unimplemented!();
    }

    /// Deletes bytes that were left by a failed write operation.
    fn clean_trailing_byes(&mut self) {
        unimplemented!();
    }

    /// Extends a Filo with the contents of an iterator.
    ///
    /// This method will short circuit, stopping on the first operation that errors.
    ///
    /// You should call [sync_len] if this method fails.
    ///
    /// This method uses [BufWriter]'s intenal buffering to improve performance. It is possible that
    /// this method can fail when adding bytes to that buffer. It can also fail when flushing that
    /// buffer to ensure that every byte is written to the file.
    pub fn extend(&mut self, items: impl Iterator<Item = T>) -> Result<(), io::Error>
    where
        [u8; T::SIZE]: Sized,
    {
        let mut extend_len = 0;

        Ok(Filo { file, buf, reader })
        for item in items {
            let bytes = item.to_storable();
            let write_result = self.writer.write(&bytes);

            match write_result {
                Ok(_) => extend_len += 1,
                Err(err) => {
                    self.sync_len();
                    return Err(err);
                }
            }
        }

        let flush_result = self.writer.flush();

        match flush_result {
            Ok(_) => {},
            Err(err) => {
                self.sync_len();
                return Err(err);
            },
        }

        self.len += extend_len;

        Ok(())
    }

    pub fn read_index<T: FiloRw>(&mut self, index: usize) -> Result<T, io::Error> {
    /// Returns an [Option] containing the item at the given index or None if the index does not
    /// exist.
    pub fn read_index(&mut self, index: usize) -> Result<T, io::Error> {
        let byte_position = (index * T::SIZE) as u64;

        self.reader.seek(SeekFrom::Start(byte_position))?;


@@ 43,18 144,49 @@ impl Filo {
        Ok(item)
    }

    pub fn write_index<T: FiloRw>(&mut self, item: T, index: usize) -> Result<(), io::Error>
    /// Writes an item at the given index.
    ///
    /// If the given index is greater than the
    pub fn write_index(&mut self, item: T, index: usize) -> Result<(), io::Error>
    where
        [u8; T::SIZE]: Sized,
    {
        let byte_position = (index * T::SIZE) as u64;
        unimplemented!();
    }
}

        self.buf.clear();
        self.buf.extend(T::to_storable(item).iter());
        self.file.seek(SeekFrom::Start(byte_position))?;
        self.file.write_all(&mut self.buf)?;
        self.file.flush()?;
#[cfg(test)]
mod test {
    use super::*;
    use std::{fs, str::FromStr};

        Ok(())
    const TEST_DATA_PATH: &'static str = "./target/filo.test.dat";

    fn setup_test_file() -> PathBuf {
        let path = PathBuf::from_str(TEST_DATA_PATH).unwrap();

        fs::remove_file(path.clone()).unwrap();

        File::with_options()
            .read(true)
            .write(true)
            .create(true)
            .open(path.clone())
            .expect("Failed to create test data file.")
            .set_len(0)
            .unwrap();

        path
    }

    #[test]
    fn measures_length() {
        let path = setup_test_file();
        let filo = Filo::new::<u8>(path).unwrap();

        for i in 0..=9 {
            assert_eq!(i, filo.len());
            filo.push(i);
        }
    }
}

M src/filo_rw.rs => src/filo_rw.rs +5 -5
@@ 52,7 52,7 @@ mod tests {
    use crate::filo::Filo;
    use std::{fs, fs::File, str::FromStr, path::PathBuf};

    const TEST_DATA_PATH: &'static str = "./target/test_data";
    const TEST_DATA_PATH: &'static str = "./target/filo_rw.test.dat";

    fn setup_test_file() -> PathBuf {
        let path = PathBuf::from_str(TEST_DATA_PATH).unwrap();


@@ 84,7 84,7 @@ mod tests {
        }

        fn write_index(path: PathBuf) {
            let mut filo = Filo::new::<char>(path).unwrap();
            let mut filo = Filo::new(path).unwrap();

            for (i, c) in TEST_ARR.iter().enumerate() {
                println!("i = {}", i);


@@ 94,7 94,7 @@ mod tests {
        }

        fn read_index(path: PathBuf) {
            let mut filo = Filo::new::<char>(path).unwrap();
            let mut filo = Filo::new(path).unwrap();

            for (i, c) in TEST_ARR.iter().enumerate() {
                println!("i = {}", i);


@@ 120,7 120,7 @@ mod tests {
        }

        fn write_index(path: PathBuf) {
            let mut filo = Filo::new::<char>(path).unwrap();
            let mut filo = Filo::new(path).unwrap();

            for (i, x) in TEST_ARR.iter().enumerate() {
                println!("i = {}", i);


@@ 130,7 130,7 @@ mod tests {
        }

        fn read_index(path: PathBuf) {
            let mut filo = Filo::new::<char>(path).unwrap();
            let mut filo = Filo::new(path).unwrap();

            for (i, x) in TEST_ARR.iter().enumerate() {
                println!("i = {}", i);