fast-fs 0.2.1

High-speed async file system traversal library with batteries-included file browser component
Documentation
// <FILE>crates/fast-fs/src/nav/fnc_file_ops.rs</FILE> - <DESC>File operation helpers</DESC>
// <VERS>VERSION: 0.1.0</VERS>
// <WCTX>Implementing nav module PRD</WCTX>
// <CLOG>Initial creation with delete, rename, create_dir, create_file</CLOG>

//! File operation helpers
//!
//! Low-level file operations used by the Browser class.
//! These operations are synchronous and return NavError on failure.

use super::nav_error::NavError;
use std::fs;
use std::path::Path;

/// Delete a file or directory (recursively if directory)
///
/// # Arguments
///
/// * `path` - Path to delete
///
/// # Errors
///
/// Returns NavError::PermissionDenied if access is denied,
/// or NavError::Io for other I/O errors.
pub fn delete_path(path: &Path) -> Result<(), NavError> {
    if path.is_dir() {
        fs::remove_dir_all(path).map_err(|e| map_io_error(path, e))
    } else {
        fs::remove_file(path).map_err(|e| map_io_error(path, e))
    }
}

/// Rename a file or directory
///
/// # Arguments
///
/// * `from` - Source path
/// * `to` - Destination path
///
/// # Errors
///
/// Returns NavError::NotFound if source doesn't exist,
/// NavError::PermissionDenied if access is denied,
/// or NavError::Io for other errors.
pub fn rename_path(from: &Path, to: &Path) -> Result<(), NavError> {
    if !from.exists() {
        return Err(NavError::NotFound(from.to_path_buf()));
    }
    fs::rename(from, to).map_err(|e| map_io_error(from, e))
}

/// Create a new directory
///
/// # Arguments
///
/// * `path` - Path for the new directory
///
/// # Errors
///
/// Returns NavError::PermissionDenied if access is denied,
/// or NavError::Io if the directory already exists or other errors.
pub fn create_directory(path: &Path) -> Result<(), NavError> {
    fs::create_dir(path).map_err(|e| map_io_error(path, e))
}

/// Create a new empty file
///
/// # Arguments
///
/// * `path` - Path for the new file
///
/// # Errors
///
/// Returns NavError::PermissionDenied if access is denied,
/// or NavError::Io if the file already exists or other errors.
pub fn create_file(path: &Path) -> Result<(), NavError> {
    // Use create_new to fail if file exists
    fs::OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(path)
        .map_err(|e| map_io_error(path, e))?;
    Ok(())
}

/// Check if a path exists
#[allow(dead_code)] // May be useful for consumers
pub fn path_exists(path: &Path) -> bool {
    path.exists()
}

/// Map std::io::Error to NavError with path context
fn map_io_error(path: &Path, error: std::io::Error) -> NavError {
    match error.kind() {
        std::io::ErrorKind::NotFound => NavError::NotFound(path.to_path_buf()),
        std::io::ErrorKind::PermissionDenied => NavError::PermissionDenied(path.to_path_buf()),
        _ => NavError::Io {
            path: path.to_path_buf(),
            source: error,
        },
    }
}

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

    #[test]
    fn test_delete_file() {
        let dir = tempdir().unwrap();
        let file = dir.path().join("test.txt");
        File::create(&file).unwrap();

        assert!(file.exists());
        delete_path(&file).unwrap();
        assert!(!file.exists());
    }

    #[test]
    fn test_delete_directory() {
        let dir = tempdir().unwrap();
        let subdir = dir.path().join("subdir");
        fs::create_dir(&subdir).unwrap();
        File::create(subdir.join("file.txt")).unwrap();

        assert!(subdir.exists());
        delete_path(&subdir).unwrap();
        assert!(!subdir.exists());
    }

    #[test]
    fn test_rename_file() {
        let dir = tempdir().unwrap();
        let from = dir.path().join("old.txt");
        let to = dir.path().join("new.txt");
        File::create(&from).unwrap();

        rename_path(&from, &to).unwrap();
        assert!(!from.exists());
        assert!(to.exists());
    }

    #[test]
    fn test_rename_nonexistent() {
        let dir = tempdir().unwrap();
        let from = dir.path().join("nonexistent.txt");
        let to = dir.path().join("new.txt");

        let result = rename_path(&from, &to);
        assert!(matches!(result, Err(NavError::NotFound(_))));
    }

    #[test]
    fn test_create_directory() {
        let dir = tempdir().unwrap();
        let new_dir = dir.path().join("new_dir");

        create_directory(&new_dir).unwrap();
        assert!(new_dir.is_dir());
    }

    #[test]
    fn test_create_directory_exists() {
        let dir = tempdir().unwrap();
        let existing = dir.path().join("existing");
        fs::create_dir(&existing).unwrap();

        let result = create_directory(&existing);
        assert!(matches!(result, Err(NavError::Io { .. })));
    }

    #[test]
    fn test_create_file() {
        let dir = tempdir().unwrap();
        let new_file = dir.path().join("new.txt");

        create_file(&new_file).unwrap();
        assert!(new_file.is_file());
    }

    #[test]
    fn test_create_file_exists() {
        let dir = tempdir().unwrap();
        let existing = dir.path().join("existing.txt");
        File::create(&existing).unwrap();

        let result = create_file(&existing);
        assert!(matches!(result, Err(NavError::Io { .. })));
    }

    #[test]
    fn test_path_exists() {
        let dir = tempdir().unwrap();
        let file = dir.path().join("test.txt");

        assert!(!path_exists(&file));
        File::create(&file).unwrap();
        assert!(path_exists(&file));
    }
}

// <FILE>crates/fast-fs/src/nav/fnc_file_ops.rs</FILE>
// <VERS>END OF VERSION: 0.1.0</VERS>