pulith-fs 0.1.0

Cross-platform atomic filesystem primitives
Documentation
use crate::{Error, Result};
use std::path::Path;

pub struct Options {
    pub retry_count: u32,
    pub retry_delay: std::time::Duration,
}

impl Default for Options {
    fn default() -> Self {
        Self {
            retry_count: 5,
            retry_delay: std::time::Duration::from_millis(100),
        }
    }
}

impl Options {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn retry_count(mut self, count: u32) -> Self {
        self.retry_count = count;
        self
    }
    pub fn retry_delay(mut self, delay: std::time::Duration) -> Self {
        self.retry_delay = delay;
        self
    }
}

pub fn replace_dir(src: impl AsRef<Path>, dest: impl AsRef<Path>, _options: Options) -> Result<()> {
    let src = src.as_ref();
    let dest = dest.as_ref();

    #[cfg(unix)]
    {
        std::fs::rename(src, dest).map_err(|e| Error::ReplaceDir {
            path: dest.to_path_buf(),
            source: e,
        })
    }

    #[cfg(windows)]
    {
        use std::thread;
        let options = _options;
        let mut attempts = 0;
        loop {
            if dest.exists()
                && let Err(e) = std::fs::remove_dir_all(dest)
            {
                attempts += 1;
                if attempts >= options.retry_count {
                    return Err(Error::ReplaceDir {
                        path: dest.to_path_buf(),
                        source: e,
                    });
                }
                thread::sleep(options.retry_delay * attempts);
                continue;
            }

            match std::fs::rename(src, dest) {
                Ok(_) => return Ok(()),
                Err(e) => {
                    attempts += 1;
                    if attempts >= options.retry_count {
                        return Err(Error::ReplaceDir {
                            path: dest.to_path_buf(),
                            source: e,
                        });
                    }
                    thread::sleep(options.retry_delay * attempts);
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;

    #[test]
    fn test_replace_dir() {
        let dir = tempdir().unwrap();
        let src = dir.path().join("src");
        let dest = dir.path().join("dest");
        std::fs::create_dir_all(&src).unwrap();
        std::fs::write(src.join("file.txt"), "data").unwrap();

        replace_dir(&src, &dest, Options::new()).unwrap();
        assert!(dest.exists());
        assert!(dest.join("file.txt").exists());
    }
}