Moved entry into separate file.
This commit is contained in:
		@@ -1,4 +1,4 @@
 | 
			
		||||
use super::{DBError, ErrorCode, FileData, SessionData, Store};
 | 
			
		||||
use super::{DBError, FileData, SessionData};
 | 
			
		||||
use std::slice;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
@@ -16,21 +16,21 @@ impl FileData<Self> for Database {
 | 
			
		||||
        output
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn from_bytes(data: &mut slice::Iter<u8>) -> Result<Self, DBError> {
 | 
			
		||||
    fn from_bytes(_data: &mut slice::Iter<u8>) -> Result<Self, DBError> {
 | 
			
		||||
        Ok(Self {})
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SessionData for Database {
 | 
			
		||||
    fn add(&mut self, key: &str, value: &str, data: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
    fn add(&mut self, _key: &str, _value: &str, _data: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        Ok(Vec::new())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn eq(&self, key: &str, value: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
    fn eq(&self, _key: &str, _value: &str) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        Ok(Vec::new())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn list(&self, keys: Vec<&str>) -> Result<Vec<String>, DBError> {
 | 
			
		||||
    fn list(&self, _keys: Vec<&str>) -> Result<Vec<String>, DBError> {
 | 
			
		||||
        Ok(Vec::new())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										375
									
								
								src/morethantext/entry.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								src/morethantext/entry.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,375 @@
 | 
			
		||||
use async_std::{fs::{read, remove_file, write}, path::PathBuf};
 | 
			
		||||
use super::{DataType, DBError, ErrorCode, FileData, SessionData};
 | 
			
		||||
use std::{cell::Cell, time::{Duration, Instant}};
 | 
			
		||||
 | 
			
		||||
pub struct Entry {
 | 
			
		||||
    data: DataType,
 | 
			
		||||
    filename: PathBuf,
 | 
			
		||||
    last_used: Cell<Instant>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Entry {
 | 
			
		||||
    pub async fn new<P>(filename: P, data: DataType) -> Result<Self, DBError>
 | 
			
		||||
    where
 | 
			
		||||
        P: Into<PathBuf>,
 | 
			
		||||
    {
 | 
			
		||||
        let pathbuf = filename.into();
 | 
			
		||||
        if pathbuf.as_path().exists().await {
 | 
			
		||||
            return Err(DBError::from_code(ErrorCode::EntryExists(pathbuf)));
 | 
			
		||||
        } else {
 | 
			
		||||
            match write(&pathbuf, data.to_bytes()).await {
 | 
			
		||||
                Ok(_) => (),
 | 
			
		||||
                Err(err) => {
 | 
			
		||||
                    let mut error = DBError::from_code(ErrorCode::EntryWriteFailure(pathbuf));
 | 
			
		||||
                    error.add_source(err);
 | 
			
		||||
                    return Err(error);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            data: data,
 | 
			
		||||
            filename: pathbuf,
 | 
			
		||||
            last_used: Cell::new(Instant::now()),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get<P>(filename: P) -> Result<Self, DBError>
 | 
			
		||||
    where
 | 
			
		||||
        P: Into<PathBuf>,
 | 
			
		||||
    {
 | 
			
		||||
        let pathbuf = filename.into();
 | 
			
		||||
        let content = match read(&pathbuf).await {
 | 
			
		||||
            Ok(text) => text,
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                let mut error = DBError::from_code(ErrorCode::EntryReadFailure(pathbuf));
 | 
			
		||||
                error.add_source(err);
 | 
			
		||||
                return Err(error);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        let data = match DataType::from_bytes(&mut content.iter()) {
 | 
			
		||||
            Ok(raw) => raw,
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                let mut error = DBError::from_code(ErrorCode::EntryReadFailure(pathbuf));
 | 
			
		||||
                error.add_source(err);
 | 
			
		||||
                return Err(error);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            data: data,
 | 
			
		||||
            filename: pathbuf,
 | 
			
		||||
            last_used: Cell::new(Instant::now()),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn elapsed(&self) -> Duration {
 | 
			
		||||
        self.last_used.get().elapsed()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn data(&self) -> DataType {
 | 
			
		||||
        self.last_used.set(Instant::now());
 | 
			
		||||
        self.data.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn update(&mut self, data: DataType) -> Result<(), DBError> {
 | 
			
		||||
        self.last_used.set(Instant::now());
 | 
			
		||||
        match write(&self.filename, data.to_bytes()).await {
 | 
			
		||||
            Ok(_) => (),
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                let mut error =
 | 
			
		||||
                    DBError::from_code(ErrorCode::EntryWriteFailure(self.filename.clone()));
 | 
			
		||||
                error.add_source(err);
 | 
			
		||||
                return Err(error);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        self.data = data;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn remove(&self) -> Result<(), DBError> {
 | 
			
		||||
        match remove_file(&self.filename).await {
 | 
			
		||||
            Ok(_) => Ok(()),
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                let mut error =
 | 
			
		||||
                    DBError::from_code(ErrorCode::EntryDeleteFailure(self.filename.clone()));
 | 
			
		||||
                error.add_source(err);
 | 
			
		||||
                Err(error)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod entry {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use std::error::Error;
 | 
			
		||||
    use tempfile::tempdir;
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn get_elapsed_time() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("count");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data).await.unwrap();
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been now."
 | 
			
		||||
        );
 | 
			
		||||
        item.last_used
 | 
			
		||||
            .set(Instant::now() - Duration::from_secs(500));
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(499) < item.elapsed(),
 | 
			
		||||
            "The duration should have increased."
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn create() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let mut data = DataType::new("store").unwrap();
 | 
			
		||||
        data.add("database", "roger", "moore").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("wiliam");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been now."
 | 
			
		||||
        );
 | 
			
		||||
        let output = item.data();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            data.list(["database"].to_vec()).unwrap(),
 | 
			
		||||
            output.list(["database"].to_vec()).unwrap()
 | 
			
		||||
        );
 | 
			
		||||
        assert!(filepath.is_file(), "Should have created the entry file.");
 | 
			
		||||
        let content = read(&filepath).await.unwrap();
 | 
			
		||||
        assert_eq!(content, data.to_bytes());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn create_errors_on_bad_files() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("bad").join("path");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        match Entry::new(filename.to_string(), data).await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("bad file names should raise an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryWriteFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Must include the source error.");
 | 
			
		||||
                    assert!(err
 | 
			
		||||
                        .source()
 | 
			
		||||
                        .unwrap()
 | 
			
		||||
                        .to_string()
 | 
			
		||||
                        .contains("could not write to file"));
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn create_does_not_over_writes() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let id = "wicked";
 | 
			
		||||
        let file = dir.path().join(id);
 | 
			
		||||
        let filename = file.to_str().unwrap();
 | 
			
		||||
        write(&file, b"previous").await.unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        match Entry::new(filename.to_string(), data).await {
 | 
			
		||||
            Ok(_) => {
 | 
			
		||||
                return Err(DBError::new(
 | 
			
		||||
                    "Should produce an error for an existing Entry",
 | 
			
		||||
                ))
 | 
			
		||||
            }
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryExists(_) => Ok(()),
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn get_updates_last_used() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("holder");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data).await.unwrap();
 | 
			
		||||
        item.last_used
 | 
			
		||||
            .set(Instant::now() - Duration::from_secs(300));
 | 
			
		||||
        item.data();
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been reset."
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn update_entry() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let mut data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("changing");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let mut item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        item.last_used
 | 
			
		||||
            .set(Instant::now() - Duration::from_secs(500));
 | 
			
		||||
        data.add("database", "new", "stuff").unwrap();
 | 
			
		||||
        item.update(data.clone()).await.unwrap();
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been reset."
 | 
			
		||||
        );
 | 
			
		||||
        let output = item.data();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            data.list(["database"].to_vec()).unwrap(),
 | 
			
		||||
            output.list(["database"].to_vec()).unwrap()
 | 
			
		||||
        );
 | 
			
		||||
        let content = read(&filepath).await.unwrap();
 | 
			
		||||
        assert_eq!(content, data.to_bytes());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn update_write_errors() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("changing");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let mut item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        drop(dir);
 | 
			
		||||
        match item.update(data).await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("file writes should return an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryWriteFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Must include the source error.");
 | 
			
		||||
                    assert!(err
 | 
			
		||||
                        .source()
 | 
			
		||||
                        .unwrap()
 | 
			
		||||
                        .to_string()
 | 
			
		||||
                        .contains("could not write to file"));
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn retrieve() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let mut data = DataType::new("store").unwrap();
 | 
			
		||||
        data.add("database", "something_old", "3.14159").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("existing");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        let output = Entry::get(filename).await.unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            output.data().list(["database"].to_vec()).unwrap(),
 | 
			
		||||
            data.list(["database"].to_vec()).unwrap()
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(output.filename.to_str().unwrap(), filename);
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been reset."
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn retrieve_file_missing() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let filepath = dir.path().join("justnotthere");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        match Entry::get(filename).await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("should have returned an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryReadFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Error should have a source.");
 | 
			
		||||
                    assert!(
 | 
			
		||||
                        err.source()
 | 
			
		||||
                            .unwrap()
 | 
			
		||||
                            .to_string()
 | 
			
		||||
                            .contains("could not read file"),
 | 
			
		||||
                        "Source Error Message: {}",
 | 
			
		||||
                        err.source().unwrap().to_string()
 | 
			
		||||
                    );
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn retrieve_corrupt_file() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let filepath = dir.path().join("garbage");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        write(&filepath, b"jhsdfghlsdf").await.unwrap();
 | 
			
		||||
        match Entry::get(filename).await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("should have returned an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryReadFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Error should have a source.");
 | 
			
		||||
                    assert!(
 | 
			
		||||
                        err.source().unwrap().to_string().contains("corrupt file"),
 | 
			
		||||
                        "Source Error Message: {}",
 | 
			
		||||
                        err.source().unwrap().to_string()
 | 
			
		||||
                    );
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn delete() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let filepath = dir.path().join("byebye");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        item.remove().await.unwrap();
 | 
			
		||||
        assert!(!filepath.exists(), "Entry file should be removed.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn delete_bad_file() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let filepath = dir.path().join("itsnotthere");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        remove_file(filename).await.unwrap();
 | 
			
		||||
        match item.remove().await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("should have produced an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryDeleteFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Error should have a source.");
 | 
			
		||||
                    assert!(
 | 
			
		||||
                        err.source()
 | 
			
		||||
                            .unwrap()
 | 
			
		||||
                            .to_string()
 | 
			
		||||
                            .contains("could not remove file"),
 | 
			
		||||
                        "Source Error Message: {}",
 | 
			
		||||
                        err.source().unwrap().to_string()
 | 
			
		||||
                    );
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -158,11 +158,11 @@ mod codes {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use async_std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
    const items: [&str; 2] = ["first", "second"];
 | 
			
		||||
    const ITEMS: [&str; 2] = ["first", "second"];
 | 
			
		||||
 | 
			
		||||
    fn create_path_buffer() -> Vec<PathBuf> {
 | 
			
		||||
        let mut output = Vec::new();
 | 
			
		||||
        for item in items {
 | 
			
		||||
        for item in ITEMS {
 | 
			
		||||
            let mut path = PathBuf::new();
 | 
			
		||||
            path.push("thepath");
 | 
			
		||||
            path.push(item);
 | 
			
		||||
@@ -173,7 +173,7 @@ mod codes {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn undefined_display() {
 | 
			
		||||
        for item in items {
 | 
			
		||||
        for item in ITEMS {
 | 
			
		||||
            let err = ErrorCode::Undefined(item.to_string());
 | 
			
		||||
            assert_eq!(err.to_string(), item);
 | 
			
		||||
        }
 | 
			
		||||
@@ -181,7 +181,7 @@ mod codes {
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn incorrect_data_type() {
 | 
			
		||||
        for item in items {
 | 
			
		||||
        for item in ITEMS {
 | 
			
		||||
            let err = ErrorCode::DataTypeIncorrect(item.to_string());
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
                err.to_string(),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,16 @@
 | 
			
		||||
mod database;
 | 
			
		||||
mod entry;
 | 
			
		||||
mod error;
 | 
			
		||||
mod store;
 | 
			
		||||
 | 
			
		||||
use async_std::{
 | 
			
		||||
    fs::{read, remove_file, write},
 | 
			
		||||
    path::{Path, PathBuf},
 | 
			
		||||
    fs::{write},
 | 
			
		||||
    path::PathBuf,
 | 
			
		||||
};
 | 
			
		||||
use database::Database;
 | 
			
		||||
use entry::Entry;
 | 
			
		||||
use error::{DBError, ErrorCode};
 | 
			
		||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
 | 
			
		||||
use std::{
 | 
			
		||||
    cell::Cell,
 | 
			
		||||
    slice, str,
 | 
			
		||||
    time::{Duration, Instant},
 | 
			
		||||
};
 | 
			
		||||
use std::{slice, str};
 | 
			
		||||
use store::Store;
 | 
			
		||||
 | 
			
		||||
const ENTRY: &str = "EntryPoint";
 | 
			
		||||
@@ -30,7 +27,7 @@ trait SessionData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
enum DataType {
 | 
			
		||||
pub enum DataType {
 | 
			
		||||
    DBMap(Store),
 | 
			
		||||
    TableMap(Database),
 | 
			
		||||
}
 | 
			
		||||
@@ -80,7 +77,7 @@ impl FileData<Self> for DataType {
 | 
			
		||||
        output.push(0);
 | 
			
		||||
        match self {
 | 
			
		||||
            DataType::DBMap(store) => output.append(&mut store.to_bytes()),
 | 
			
		||||
            DataType::TableMap(db) => (),
 | 
			
		||||
            DataType::TableMap(_) => (),
 | 
			
		||||
        }
 | 
			
		||||
        output
 | 
			
		||||
    }
 | 
			
		||||
@@ -113,102 +110,6 @@ impl FileData<Self> for DataType {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Entry {
 | 
			
		||||
    data: DataType,
 | 
			
		||||
    filename: PathBuf,
 | 
			
		||||
    last_used: Cell<Instant>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Entry {
 | 
			
		||||
    async fn new<P>(filename: P, data: DataType) -> Result<Self, DBError>
 | 
			
		||||
    where
 | 
			
		||||
        P: Into<PathBuf>,
 | 
			
		||||
    {
 | 
			
		||||
        let pathbuf = filename.into();
 | 
			
		||||
        if pathbuf.as_path().exists().await {
 | 
			
		||||
            return Err(DBError::from_code(ErrorCode::EntryExists(pathbuf)));
 | 
			
		||||
        } else {
 | 
			
		||||
            match write(&pathbuf, data.to_bytes()).await {
 | 
			
		||||
                Ok(_) => (),
 | 
			
		||||
                Err(err) => {
 | 
			
		||||
                    let mut error = DBError::from_code(ErrorCode::EntryWriteFailure(pathbuf));
 | 
			
		||||
                    error.add_source(err);
 | 
			
		||||
                    return Err(error);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            data: data,
 | 
			
		||||
            filename: pathbuf,
 | 
			
		||||
            last_used: Cell::new(Instant::now()),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn get<P>(filename: P) -> Result<Self, DBError>
 | 
			
		||||
    where
 | 
			
		||||
        P: Into<PathBuf>,
 | 
			
		||||
    {
 | 
			
		||||
        let pathbuf = filename.into();
 | 
			
		||||
        let content = match read(&pathbuf).await {
 | 
			
		||||
            Ok(text) => text,
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                let mut error = DBError::from_code(ErrorCode::EntryReadFailure(pathbuf));
 | 
			
		||||
                error.add_source(err);
 | 
			
		||||
                return Err(error);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        let data = match DataType::from_bytes(&mut content.iter()) {
 | 
			
		||||
            Ok(raw) => raw,
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                let mut error = DBError::from_code(ErrorCode::EntryReadFailure(pathbuf));
 | 
			
		||||
                error.add_source(err);
 | 
			
		||||
                return Err(error);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            data: data,
 | 
			
		||||
            filename: pathbuf,
 | 
			
		||||
            last_used: Cell::new(Instant::now()),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn elapsed(&self) -> Duration {
 | 
			
		||||
        self.last_used.get().elapsed()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn data(&self) -> DataType {
 | 
			
		||||
        self.last_used.set(Instant::now());
 | 
			
		||||
        self.data.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn update(&mut self, data: DataType) -> Result<(), DBError> {
 | 
			
		||||
        self.last_used.set(Instant::now());
 | 
			
		||||
        match write(&self.filename, data.to_bytes()).await {
 | 
			
		||||
            Ok(_) => (),
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                let mut error =
 | 
			
		||||
                    DBError::from_code(ErrorCode::EntryWriteFailure(self.filename.clone()));
 | 
			
		||||
                error.add_source(err);
 | 
			
		||||
                return Err(error);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        self.data = data;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn remove(&self) -> Result<(), DBError> {
 | 
			
		||||
        match remove_file(&self.filename).await {
 | 
			
		||||
            Ok(_) => Ok(()),
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                let mut error =
 | 
			
		||||
                    DBError::from_code(ErrorCode::EntryDeleteFailure(self.filename.clone()));
 | 
			
		||||
                error.add_source(err);
 | 
			
		||||
                Err(error)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct MoreThanText;
 | 
			
		||||
 | 
			
		||||
@@ -395,282 +296,6 @@ mod datatype_file {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod entry {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use std::error::Error;
 | 
			
		||||
    use tempfile::tempdir;
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn get_elapsed_time() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("count");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data).await.unwrap();
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been now."
 | 
			
		||||
        );
 | 
			
		||||
        item.last_used
 | 
			
		||||
            .set(Instant::now() - Duration::from_secs(500));
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(499) < item.elapsed(),
 | 
			
		||||
            "The duration should have increased."
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn create() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let mut data = DataType::new("store").unwrap();
 | 
			
		||||
        data.add("database", "roger", "moore").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("wiliam");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been now."
 | 
			
		||||
        );
 | 
			
		||||
        let output = item.data();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            data.list(["database"].to_vec()).unwrap(),
 | 
			
		||||
            output.list(["database"].to_vec()).unwrap()
 | 
			
		||||
        );
 | 
			
		||||
        assert!(filepath.is_file(), "Should have created the entry file.");
 | 
			
		||||
        let content = read(&filepath).await.unwrap();
 | 
			
		||||
        assert_eq!(content, data.to_bytes());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn create_errors_on_bad_files() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("bad").join("path");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        match Entry::new(filename.to_string(), data).await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("bad file names should raise an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryWriteFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Must include the source error.");
 | 
			
		||||
                    assert!(err
 | 
			
		||||
                        .source()
 | 
			
		||||
                        .unwrap()
 | 
			
		||||
                        .to_string()
 | 
			
		||||
                        .contains("could not write to file"));
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn create_does_not_over_writes() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let id = "wicked";
 | 
			
		||||
        let file = dir.path().join(id);
 | 
			
		||||
        let filename = file.to_str().unwrap();
 | 
			
		||||
        write(&file, b"previous").await.unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        match Entry::new(filename.to_string(), data).await {
 | 
			
		||||
            Ok(_) => {
 | 
			
		||||
                return Err(DBError::new(
 | 
			
		||||
                    "Should produce an error for an existing Entry",
 | 
			
		||||
                ))
 | 
			
		||||
            }
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryExists(_) => Ok(()),
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn get_updates_last_used() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("holder");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data).await.unwrap();
 | 
			
		||||
        item.last_used
 | 
			
		||||
            .set(Instant::now() - Duration::from_secs(300));
 | 
			
		||||
        item.data();
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been reset."
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn update_entry() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let mut data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("changing");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let mut item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        item.last_used
 | 
			
		||||
            .set(Instant::now() - Duration::from_secs(500));
 | 
			
		||||
        data.add("database", "new", "stuff").unwrap();
 | 
			
		||||
        item.update(data.clone()).await.unwrap();
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been reset."
 | 
			
		||||
        );
 | 
			
		||||
        let output = item.data();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            data.list(["database"].to_vec()).unwrap(),
 | 
			
		||||
            output.list(["database"].to_vec()).unwrap()
 | 
			
		||||
        );
 | 
			
		||||
        let content = read(&filepath).await.unwrap();
 | 
			
		||||
        assert_eq!(content, data.to_bytes());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn update_write_errors() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("changing");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let mut item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        drop(dir);
 | 
			
		||||
        match item.update(data).await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("file writes should return an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryWriteFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Must include the source error.");
 | 
			
		||||
                    assert!(err
 | 
			
		||||
                        .source()
 | 
			
		||||
                        .unwrap()
 | 
			
		||||
                        .to_string()
 | 
			
		||||
                        .contains("could not write to file"));
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn retrieve() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let mut data = DataType::new("store").unwrap();
 | 
			
		||||
        data.add("database", "something_old", "3.14159").unwrap();
 | 
			
		||||
        let filepath = dir.path().join("existing");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        let output = Entry::get(filename).await.unwrap();
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            output.data().list(["database"].to_vec()).unwrap(),
 | 
			
		||||
            data.list(["database"].to_vec()).unwrap()
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(output.filename.to_str().unwrap(), filename);
 | 
			
		||||
        assert!(
 | 
			
		||||
            Duration::from_secs(1) > item.elapsed(),
 | 
			
		||||
            "last_used should have been reset."
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn retrieve_file_missing() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let filepath = dir.path().join("justnotthere");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        match Entry::get(filename).await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("should have returned an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryReadFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Error should have a source.");
 | 
			
		||||
                    assert!(
 | 
			
		||||
                        err.source()
 | 
			
		||||
                            .unwrap()
 | 
			
		||||
                            .to_string()
 | 
			
		||||
                            .contains("could not read file"),
 | 
			
		||||
                        "Source Error Message: {}",
 | 
			
		||||
                        err.source().unwrap().to_string()
 | 
			
		||||
                    );
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn retrieve_corrupt_file() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let filepath = dir.path().join("garbage");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        write(&filepath, b"jhsdfghlsdf").await.unwrap();
 | 
			
		||||
        match Entry::get(filename).await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("should have returned an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryReadFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Error should have a source.");
 | 
			
		||||
                    assert!(
 | 
			
		||||
                        err.source().unwrap().to_string().contains("corrupt file"),
 | 
			
		||||
                        "Source Error Message: {}",
 | 
			
		||||
                        err.source().unwrap().to_string()
 | 
			
		||||
                    );
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn delete() {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let filepath = dir.path().join("byebye");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        item.remove().await.unwrap();
 | 
			
		||||
        assert!(!filepath.exists(), "Entry file should be removed.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
    async fn delete_bad_file() -> Result<(), DBError> {
 | 
			
		||||
        let dir = tempdir().unwrap();
 | 
			
		||||
        let filepath = dir.path().join("itsnotthere");
 | 
			
		||||
        let filename = filepath.to_str().unwrap();
 | 
			
		||||
        let data = DataType::new("store").unwrap();
 | 
			
		||||
        let item = Entry::new(filename.to_string(), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        remove_file(filename).await.unwrap();
 | 
			
		||||
        match item.remove().await {
 | 
			
		||||
            Ok(_) => Err(DBError::new("should have produced an error")),
 | 
			
		||||
            Err(err) => match err.code {
 | 
			
		||||
                ErrorCode::EntryDeleteFailure(_) => {
 | 
			
		||||
                    assert!(err.source().is_some(), "Error should have a source.");
 | 
			
		||||
                    assert!(
 | 
			
		||||
                        err.source()
 | 
			
		||||
                            .unwrap()
 | 
			
		||||
                            .to_string()
 | 
			
		||||
                            .contains("could not remove file"),
 | 
			
		||||
                        "Source Error Message: {}",
 | 
			
		||||
                        err.source().unwrap().to_string()
 | 
			
		||||
                    );
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
                _ => Err(DBError::new("incorrect error code")),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod cache {
 | 
			
		||||
    use super::*;
 | 
			
		||||
@@ -724,7 +349,7 @@ mod cache {
 | 
			
		||||
        Entry::new(dir.path().join(ENTRY), data.clone())
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        let cache = MoreThanText::new(dir.path()).await.unwrap();
 | 
			
		||||
        MoreThanText::new(dir.path()).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[async_std::test]
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,6 @@ impl FileData<Self> for Store {
 | 
			
		||||
        let mut id: Vec<u8> = Vec::new();
 | 
			
		||||
        let mut get_id = false;
 | 
			
		||||
        let mut letter: u8;
 | 
			
		||||
        let err_msg = "file corruption";
 | 
			
		||||
        loop {
 | 
			
		||||
            match data.next() {
 | 
			
		||||
                Some(a) => letter = a.clone(),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user