hdiff-update-core 0.2.0

Core library for signed, transactional HDiffPatch directory updates.
Documentation
use std::path::Path;

#[cfg(not(windows))]
use std::fs;

use crate::{error::io_path, Error, Result};

#[cfg(windows)]
use std::{ffi::OsStr, os::windows::ffi::OsStrExt};

#[cfg(windows)]
use windows_sys::Win32::Storage::FileSystem::{
    MoveFileExW, MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH,
};

pub(crate) fn replace_file(source: &Path, destination: &Path) -> Result<()> {
    #[cfg(windows)]
    {
        let source_wide = wide_null(source.as_os_str()).map_err(|error| io_path(source, error))?;
        let destination_wide =
            wide_null(destination.as_os_str()).map_err(|error| io_path(destination, error))?;
        let result = unsafe {
            MoveFileExW(
                source_wide.as_ptr(),
                destination_wide.as_ptr(),
                MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
            )
        };
        if result == 0 {
            return Err(io_path(destination, std::io::Error::last_os_error()));
        }
        Ok(())
    }

    #[cfg(not(windows))]
    {
        fs::rename(source, destination).map_err(|error| io_path(destination, error))
    }
}

pub fn ensure_available_space(path: &Path, required: u64) -> Result<u64> {
    let available = fs2::available_space(path).map_err(|error| io_path(path, error))?;
    if available < required {
        return Err(Error::InsufficientDiskSpace {
            path: path.to_path_buf(),
            required,
            available,
        });
    }
    Ok(available)
}

#[cfg(windows)]
fn wide_null(value: &OsStr) -> std::io::Result<Vec<u16>> {
    let mut wide = Vec::new();
    for unit in value.encode_wide() {
        if unit == 0 {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "path contains an embedded null character",
            ));
        }
        wide.push(unit);
    }
    wide.push(0);
    Ok(wide)
}

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

    use super::ensure_available_space;
    use crate::Error;

    #[test]
    fn disk_preflight_rejects_impossible_requirements() {
        let dir = tempdir().unwrap();
        let error = ensure_available_space(dir.path(), u64::MAX).unwrap_err();
        assert!(matches!(error, Error::InsufficientDiskSpace { .. }));
    }
}