use std::{
fs::OpenOptions,
io::Read,
path::{Path, PathBuf},
sync::Arc,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_flow::{
encoder::{bincode, FlowEncoder},
flow::Bytes,
};
use crate::{
error::{OpsError, OpsResult},
operation::LoaderFn,
process::{constants::SERIALIZE_FILE_ID, Process},
};
const SER_REMOVE_FILE: &str = ".ser_rm";
#[derive(Serialize, Deserialize, Default)]
pub struct Op {
to: PathBuf,
content: Vec<u8>,
#[serde(skip)]
bytes: Vec<u8>,
}
impl Op {
pub fn new<T: Serialize + DeserializeOwned + Bytes<T>>(
to: &Path,
entity: &T,
) -> OpsResult<Self> {
let mut object = Self {
to: to.to_path_buf(),
content: entity
.encode::<bincode::Encoder>()
.map_err(|_| OpsError::SerializeFailed)?,
bytes: Vec::new(),
};
object.bytes =
bincode::Encoder::serialize(&object).map_err(|_| OpsError::SerializeFailed)?;
Ok(object)
}
pub fn from_bytes(bytes: &[u8]) -> OpsResult<Self> {
let mut decoded: Op =
bincode::Encoder::deserialize(bytes).map_err(|_| OpsError::SerializeFailed)?;
decoded.bytes = bytes.to_vec();
Ok(decoded)
}
#[must_use]
#[inline]
fn remove_name(&self) -> PathBuf {
let remove_filename = format!("{}{SER_REMOVE_FILE}", self.to.to_string_lossy());
PathBuf::from(remove_filename)
}
}
pub fn load(bytes: &[u8]) -> OpsResult<Box<dyn Process>> {
let value = Op::from_bytes(bytes)?;
Ok(Box::new(value))
}
pub fn loader() -> (u8, LoaderFn) {
(SERIALIZE_FILE_ID, Arc::new(load))
}
impl Process for Op {
fn prepare(&self) -> OpsResult<()> {
if self.to.exists() {
let remove_filename = self.remove_name();
std::fs::rename(self.to.as_path(), remove_filename)?;
}
Ok(())
}
fn run(&self) -> OpsResult<()> {
std::fs::write(self.to.as_path(), &self.content)?;
let checksum = serde_flow::encoder::CASTAGNOLI.checksum(&self.content);
let mut attempts = 3;
while attempts > 0 {
let mut written_bytes = Vec::new();
let mut to_file = OpenOptions::new().read(true).open(&self.to)?;
to_file.read_to_end(&mut written_bytes)?;
let written_checksum = serde_flow::encoder::CASTAGNOLI.checksum(&written_bytes);
if checksum == written_checksum {
return Ok(());
}
attempts -= 1;
}
Ok(())
}
fn clean(&self) -> OpsResult<()> {
let remove_filename = self.remove_name();
if remove_filename.exists() {
std::fs::remove_file(remove_filename)?;
}
Ok(())
}
fn revert_prepare(&self) -> OpsResult<()> {
let remove_filename = self.remove_name();
if remove_filename.exists() {
std::fs::rename(remove_filename, self.to.as_path())?;
}
Ok(())
}
fn revert_run(&self) -> OpsResult<()> {
let remove_filename = self.remove_name();
let _ = std::fs::remove_file(self.to.as_path());
if remove_filename.exists() {
std::fs::rename(remove_filename, self.to.as_path())?;
}
Ok(())
}
fn as_bytes(&self) -> &[u8] {
&self.bytes
}
fn id() -> u8
where
Self: Sized,
{
SERIALIZE_FILE_ID
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use serde_flow::Flow;
use tempfile::tempdir;
use super::*;
#[derive(Serialize, Deserialize, Flow)]
#[flow(variant = 1, bytes)]
pub struct TestNumber {
pub number: u32,
}
#[test]
fn test_prepare_none() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let saved_path = tempdir.path().join(format!("new_file{SER_REMOVE_FILE}"));
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
assert!(!path.exists());
assert!(!saved_path.exists());
}
#[test]
fn test_prepare_exists() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let saved_path = tempdir.path().join(format!("new_file{SER_REMOVE_FILE}"));
let old_entity = TestNumber { number: 5678 };
std::fs::write(&path, old_entity.encode::<bincode::Encoder>().unwrap()).unwrap();
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
assert!(saved_path.exists());
let entity_bytes = std::fs::read(saved_path).unwrap();
let entity_decoded = TestNumber::decode::<bincode::Encoder>(&entity_bytes).unwrap();
assert_eq!(5678, entity_decoded.number);
}
#[test]
fn test_run_none() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
ops.run().unwrap();
assert!(path.exists());
let entity_bytes = std::fs::read(&path).unwrap();
let entity_decoded = TestNumber::decode::<bincode::Encoder>(&entity_bytes).unwrap();
assert_eq!(1234, entity_decoded.number);
}
#[test]
fn test_run_exists() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let saved_path = tempdir.path().join(format!("new_file{SER_REMOVE_FILE}"));
let old_entity = TestNumber { number: 5678 };
std::fs::write(&path, old_entity.encode::<bincode::Encoder>().unwrap()).unwrap();
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
ops.run().unwrap();
assert!(saved_path.exists());
let entity_bytes = std::fs::read(saved_path).unwrap();
let entity_decoded = TestNumber::decode::<bincode::Encoder>(&entity_bytes).unwrap();
assert_eq!(5678, entity_decoded.number);
assert!(path.exists());
let entity_bytes = std::fs::read(&path).unwrap();
let entity_decoded = TestNumber::decode::<bincode::Encoder>(&entity_bytes).unwrap();
assert_eq!(1234, entity_decoded.number);
}
#[test]
fn test_clean_none() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let saved_path = tempdir.path().join(format!("new_file{SER_REMOVE_FILE}"));
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
ops.run().unwrap();
ops.clean().unwrap();
assert!(path.exists());
assert!(!saved_path.exists());
let entity_bytes = std::fs::read(&path).unwrap();
let entity_decoded = TestNumber::decode::<bincode::Encoder>(&entity_bytes).unwrap();
assert_eq!(1234, entity_decoded.number);
}
#[test]
fn test_clean_exists() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let saved_path = tempdir.path().join(format!("new_file{SER_REMOVE_FILE}"));
let old_entity = TestNumber { number: 5678 };
std::fs::write(&path, old_entity.encode::<bincode::Encoder>().unwrap()).unwrap();
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
ops.run().unwrap();
ops.clean().unwrap();
assert!(!saved_path.exists());
assert!(path.exists());
let entity_bytes = std::fs::read(&path).unwrap();
let entity_decoded = TestNumber::decode::<bincode::Encoder>(&entity_bytes).unwrap();
assert_eq!(1234, entity_decoded.number);
}
#[test]
fn test_revert_prepare_none() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let saved_path = tempdir.path().join(format!("new_file{SER_REMOVE_FILE}"));
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
assert!(!path.exists());
assert!(!saved_path.exists());
}
#[test]
fn test_revert_prepare_exists() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let saved_path = tempdir.path().join(format!("new_file{SER_REMOVE_FILE}"));
let old_entity = TestNumber { number: 5678 };
std::fs::write(&path, old_entity.encode::<bincode::Encoder>().unwrap()).unwrap();
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
ops.revert_prepare().unwrap();
assert!(!saved_path.exists());
let entity_bytes = std::fs::read(path).unwrap();
let entity_decoded = TestNumber::decode::<bincode::Encoder>(&entity_bytes).unwrap();
assert_eq!(5678, entity_decoded.number);
}
#[test]
fn test_revert_run_none() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
ops.run().unwrap();
ops.revert_run().unwrap();
assert!(!path.exists());
}
#[test]
fn test_revert_run_exists() {
let tempdir = tempdir().unwrap();
let path = tempdir.path().join("new_file");
let saved_path = tempdir.path().join(format!("new_file{SER_REMOVE_FILE}"));
let old_entity = TestNumber { number: 5678 };
std::fs::write(&path, old_entity.encode::<bincode::Encoder>().unwrap()).unwrap();
let entity = TestNumber { number: 1234 };
let ops = Op::new(path.as_path(), &entity).unwrap();
ops.prepare().unwrap();
ops.run().unwrap();
ops.revert_run().unwrap();
assert!(!saved_path.exists());
assert!(path.exists());
let entity_bytes = std::fs::read(&path).unwrap();
let entity_decoded = TestNumber::decode::<bincode::Encoder>(&entity_bytes).unwrap();
assert_eq!(5678, entity_decoded.number);
}
}