diff --git a/file_linked/Cargo.toml b/file_linked/Cargo.toml index 44a3315..abf3367 100644 --- a/file_linked/Cargo.toml +++ b/file_linked/Cargo.toml @@ -18,4 +18,5 @@ categories = ["filesystem", "data-structures"] serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" anyhow = "1.0" -bincode = "1.3.3" \ No newline at end of file +bincode = "1.3.3" +log = "0.4.14" \ No newline at end of file diff --git a/file_linked/src/error.rs b/file_linked/src/error.rs new file mode 100644 index 0000000..44b7972 --- /dev/null +++ b/file_linked/src/error.rs @@ -0,0 +1,17 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + Serialization(bincode::Error), + #[error(transparent)] + IO(std::io::Error), + #[error(transparent)] + Other(#[from] anyhow::Error), +} + +impl From for Error { + fn from(error: std::io::Error) -> Error { + Error::IO(error) + } +} diff --git a/file_linked/src/lib.rs b/file_linked/src/lib.rs index af3d529..debe7e3 100644 --- a/file_linked/src/lib.rs +++ b/file_linked/src/lib.rs @@ -1,23 +1,15 @@ //! A wrapper around an object that ties it to a physical file -extern crate serde; +pub mod error; -use anyhow::Context; +use anyhow::{anyhow, Context}; +use error::Error; +use log::info; use serde::de::DeserializeOwned; use serde::Serialize; -use std::fs::File; +use std::fs::{copy, remove_file, File}; +use std::io::ErrorKind; use std::path::{Path, PathBuf}; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum Error { - #[error(transparent)] - Serialization(bincode::Error), - #[error(transparent)] - IO(std::io::Error), - #[error(transparent)] - Other(#[from] anyhow::Error), -} /// A wrapper around an object `T` that ties the object to a physical file #[derive(Debug)] @@ -27,6 +19,7 @@ where { val: T, path: PathBuf, + temp_file_path: PathBuf, } impl FileLinked @@ -106,20 +99,56 @@ where /// # } /// ``` pub fn new(val: T, path: &Path) -> Result, Error> { + let mut temp_file_path = path.to_path_buf(); + temp_file_path.set_file_name(format!( + ".temp{}", + path.file_name() + .ok_or_else(|| anyhow!("Unable to get filename for tempfile {}", path.display()))? + .to_str() + .ok_or_else(|| anyhow!("Unable to get filename for tempfile {}", path.display()))? + )); + let result = FileLinked { val, path: path.to_path_buf(), + temp_file_path, }; + result.write_data()?; Ok(result) } fn write_data(&self) -> Result<(), Error> { - let file = File::create(&self.path) - .with_context(|| format!("Unable to open path {}", self.path.display()))?; + match File::open(&self.path) { + Ok(_) => { + copy(&self.path, &self.temp_file_path).with_context(|| { + format!( + "Unable to copy temp file from {} to {}", + self.path.display(), + self.temp_file_path.display() + ) + })?; - bincode::serialize_into(file, &self.val) - .with_context(|| format!("Unable to write to file {}", self.path.display()))?; + let file = File::create(&self.path)?; + + bincode::serialize_into(file, &self.val) + .with_context(|| format!("Unable to write to file {}", self.path.display()))?; + + remove_file(&self.temp_file_path).with_context(|| { + format!( + "Unable to remove temp file {}", + self.temp_file_path.display() + ) + })?; + } + Err(error) if error.kind() == ErrorKind::NotFound => { + let file = File::create(&self.path)?; + + bincode::serialize_into(file, &self.val) + .with_context(|| format!("Unable to write to file {}", self.path.display()))?; + } + Err(error) => return Err(Error::IO(error)), + } Ok(()) } @@ -130,6 +159,7 @@ where /// # Examples /// ``` /// # use file_linked::*; + /// # use file_linked::error::Error; /// # use serde::{Deserialize, Serialize}; /// # use std::fmt; /// # use std::string::ToString; @@ -176,6 +206,7 @@ where /// # Examples /// ``` /// # use file_linked::*; + /// # use file_linked::error::Error; /// # use serde::{Deserialize, Serialize}; /// # use std::fmt; /// # use std::string::ToString; @@ -229,6 +260,7 @@ where /// # Examples /// ``` /// # use file_linked::*; + /// # use file_linked::error::Error; /// # use serde::{Deserialize, Serialize}; /// # use std::fmt; /// # use std::string::ToString; @@ -274,16 +306,64 @@ where /// # } /// ``` pub fn from_file(path: &Path) -> Result, Error> { - let file = - File::open(path).with_context(|| format!("Unable to open file {}", path.display()))?; + let mut temp_file_path = path.to_path_buf(); + temp_file_path.set_file_name(format!( + ".temp{}", + path.file_name() + .ok_or_else(|| anyhow!("Unable to get filename for tempfile {}", path.display()))? + .to_str() + .ok_or_else(|| anyhow!("Unable to get filename for tempfile {}", path.display()))? + )); - let val = bincode::deserialize_from(file) - .with_context(|| String::from("Unable to parse value from file."))?; + match File::open(path).map_err(Error::from).and_then(|file| { + bincode::deserialize_from::(file) + .with_context(|| format!("Unable to deserialize file {}", path.display())) + .map_err(Error::from) + }) { + Ok(val) => Ok(FileLinked { + val, + path: path.to_path_buf(), + temp_file_path, + }), + Err(err) => { + info!( + "Unable to read/deserialize file {} attempting to open temp file {}", + path.display(), + temp_file_path.display() + ); - Ok(FileLinked { - val, - path: path.to_path_buf(), - }) + // Try to use temp file instead and see if that file exists and is serializable + let val = FileLinked::from_temp_file(&temp_file_path, path) + .map_err(|_| err) + .with_context(|| format!("Failed to read/deserialize the object from the file {} and temp file {}", path.display(), temp_file_path.display()))?; + + Ok(FileLinked { + val, + path: path.to_path_buf(), + temp_file_path, + }) + } + } + } + + fn from_temp_file(temp_file_path: &Path, path: &Path) -> Result { + let file = File::open(temp_file_path) + .with_context(|| format!("Unable to open file {}", temp_file_path.display()))?; + + let val = bincode::deserialize_from(file).with_context(|| { + format!( + "Could not deserialize from temp file {}", + temp_file_path.display() + ) + })?; + + info!("Successfully deserialized value from temp file"); + + copy(temp_file_path, path)?; + remove_file(temp_file_path) + .with_context(|| format!("Unable to remove temp file {}", temp_file_path.display()))?; + + Ok(val) } } diff --git a/gemla/src/bin/bin.rs b/gemla/src/bin/bin.rs index c97e406..90df1bd 100644 --- a/gemla/src/bin/bin.rs +++ b/gemla/src/bin/bin.rs @@ -10,8 +10,8 @@ use clap::App; use gemla::core::{Gemla, GemlaConfig}; use gemla::error::log_error; use std::path::PathBuf; -use test_state::TestState; use std::time::Instant; +use test_state::TestState; // use std::io::Write; /// Runs a simluation of a genetic algorithm against a dataset. @@ -21,7 +21,7 @@ use std::time::Instant; fn main() -> anyhow::Result<()> { env_logger::init(); info!("Starting"); - + let now = Instant::now(); // Command line arguments are parsed with the clap crate. And this program uses diff --git a/gemla/src/core/mod.rs b/gemla/src/core/mod.rs index 3b4b0f6..c8947f7 100644 --- a/gemla/src/core/mod.rs +++ b/gemla/src/core/mod.rs @@ -8,9 +8,9 @@ use crate::tree::Tree; use anyhow::anyhow; use file_linked::FileLinked; use genetic_node::{GeneticNode, GeneticNodeWrapper, GeneticState}; +use log::{info, trace}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use log::{info, trace}; use std::fmt::Debug; use std::fs::File; use std::io::ErrorKind; @@ -67,7 +67,10 @@ where self.data .mutate(|(d, c)| Gemla::increase_height(d, c, steps))??; - info!("Height of simulation tree increased to {}", self.data.readonly().0.as_ref().unwrap().height()); + info!( + "Height of simulation tree increased to {}", + self.data.readonly().0.as_ref().unwrap().height() + ); self.data .mutate(|(d, _c)| Gemla::process_tree(d.as_mut().unwrap()))??; @@ -146,7 +149,11 @@ where break; } - trace!("{:?} completed in {:?}", node_state, node_state_time.elapsed()); + trace!( + "{:?} completed in {:?}", + node_state, + node_state_time.elapsed() + ); } info!("Processed node in {:?}", node_time.elapsed()); diff --git a/gemla/src/error.rs b/gemla/src/error.rs index c9b1277..3b8b6a7 100644 --- a/gemla/src/error.rs +++ b/gemla/src/error.rs @@ -1,20 +1,20 @@ -use thiserror::Error; use log::error; +use thiserror::Error; #[derive(Error, Debug)] pub enum Error { #[error(transparent)] - FileLinked(file_linked::Error), + FileLinked(file_linked::error::Error), #[error(transparent)] IO(std::io::Error), #[error(transparent)] Other(#[from] anyhow::Error), } -impl From for Error { - fn from(error: file_linked::Error) -> Error { +impl From for Error { + fn from(error: file_linked::error::Error) -> Error { match error { - file_linked::Error::Other(e) => Error::Other(e), + file_linked::error::Error::Other(e) => Error::Other(e), _ => Error::FileLinked(error), } } @@ -32,4 +32,3 @@ pub fn log_error(result: Result) -> Result { e }) } -