atomic-ops 0.1.1

Performs atomic operations in the filesystem
Documentation
use std::{
    path::{Path, PathBuf},
    sync::Arc,
};

use serde::{Deserialize, Serialize};
use serde_flow::encoder::{bincode, FlowEncoder};

use crate::{
    error::{OpsError, OpsResult},
    operation::LoaderFn,
    process::{constants::COPY_FILE_ID, Process},
};

const COPY_REMOVE_FILE: &str = "cp_rm";

#[derive(Serialize, Deserialize, Default)]
pub struct Op {
    from: PathBuf,
    to: PathBuf,

    #[serde(skip)]
    bytes: Vec<u8>,
}

impl Op {
    pub fn new(from: &Path, to: &Path) -> OpsResult<Self> {
        let mut object = Self {
            from: from.to_path_buf(),
            to: to.to_path_buf(),
            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)
    }
}

pub fn load(bytes: &[u8]) -> OpsResult<Box<dyn Process>> {
    let value = Op::from_bytes(bytes)?;
    Ok(Box::new(value))
}

pub fn loader() -> (u8, LoaderFn) {
    (COPY_FILE_ID, Arc::new(load))
}

impl Process for Op {
    #[inline]
    fn prepare(&self) -> OpsResult<()> {
        if self.to.exists() {
            let remove_filename = format!("{}.{COPY_REMOVE_FILE}", self.to.to_string_lossy());
            let remove_filename = Path::new(&remove_filename);
            std::fs::rename(self.to.as_path(), remove_filename)?;
        }
        println!("Copy: Prepare");
        Ok(())
    }

    #[inline]
    fn run(&self) -> OpsResult<()> {
        std::fs::copy(self.from.as_path(), self.to.as_path())?;
        println!("Copy: Run");
        Ok(())
    }

    #[inline]
    fn clean(&self) -> OpsResult<()> {
        let remove_filename = format!("{}.{COPY_REMOVE_FILE}", self.to.to_string_lossy());
        let remove_filename = Path::new(&remove_filename);
        if remove_filename.exists() {
            std::fs::remove_file(remove_filename)?;
        }
        println!("Copy: Clean {}", remove_filename.to_string_lossy());
        Ok(())
    }

    #[inline]
    fn revert_prepare(&self) -> OpsResult<()> {
        let remove_filename = format!("{}.{COPY_REMOVE_FILE}", self.to.to_string_lossy());
        let remove_filename = Path::new(&remove_filename);
        if remove_filename.exists() {
            std::fs::rename(remove_filename, self.to.as_path())?;
        }
        Ok(())
    }

    #[inline]
    fn revert_run(&self) -> OpsResult<()> {
        let remove_filename = format!("{}.{COPY_REMOVE_FILE}", self.to.to_string_lossy());
        let remove_filename = Path::new(&remove_filename);
        // tolerate this error, because it's not very important
        let _ = std::fs::remove_file(self.to.as_path());
        // this must be executed
        std::fs::rename(remove_filename, self.to.as_path())?;
        Ok(())
    }

    #[inline]
    fn id() -> u8 {
        COPY_FILE_ID
    }

    #[inline]
    fn as_bytes(&self) -> &[u8] {
        &self.bytes
    }
}