freneng 0.1.2

A useful, async-first file renaming library
Documentation
//! History management for undo operations.
//! 
//! This module handles saving and loading rename history to enable
//! undo functionality. History is stored in `.fren_history.json` in
//! the current working directory. All operations are async and non-blocking.

use serde::{Serialize, Deserialize};
use tokio::fs;
use std::path::PathBuf;
use chrono::{DateTime, Local};

/// Represents a single rename action in the history.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RenameAction {
    /// Original file path before rename
    pub old_path: PathBuf,
    /// New file path after rename
    pub new_path: PathBuf,
}

/// History of rename operations for undo support.
#[derive(Serialize, Deserialize, Debug)]
pub struct History {
    /// Timestamp when the history was created
    pub timestamp: DateTime<Local>,
    /// List of rename actions in this history entry
    pub actions: Vec<RenameAction>,
}

const HISTORY_FILE: &str = ".fren_history.json";

/// Saves rename history to disk asynchronously.
/// 
/// If the actions list is empty, no file is created.
/// 
/// # Arguments
/// 
/// * `actions` - List of rename actions to save
/// 
/// # Returns
/// 
/// * `Ok(())` - History saved successfully
/// * `Err(Box<dyn Error>)` - If file I/O fails
/// 
/// # Examples
/// 
/// ```
/// # tokio_test::block_on(async {
/// use freneng::history::{save_history, RenameAction};
/// use std::path::PathBuf;
/// 
/// let actions = vec![RenameAction {
///     old_path: PathBuf::from("old.txt"),
///     new_path: PathBuf::from("new.txt"),
/// }];
/// save_history(actions).await.unwrap();
/// # })
/// ```
pub async fn save_history(actions: Vec<RenameAction>) -> Result<(), Box<dyn std::error::Error>> {
    if actions.is_empty() {
        return Ok(());
    }

    let history = History {
        timestamp: Local::now(),
        actions,
    };

    let json = serde_json::to_string_pretty(&history)?;
    fs::write(HISTORY_FILE, json).await?;
    Ok(())
}

/// Loads rename history from disk asynchronously.
/// 
/// # Returns
/// 
/// * `Ok(Some(History))` - History loaded successfully
/// * `Ok(None)` - No history file exists
/// * `Err(Box<dyn Error>)` - If file I/O or parsing fails
/// 
/// # Examples
/// 
/// ```
/// # tokio_test::block_on(async {
/// use freneng::history::load_history;
/// 
/// match load_history().await.unwrap() {
///     Some(history) => println!("Loaded {} actions", history.actions.len()),
///     None => println!("No history found"),
/// }
/// # })
/// ```
pub async fn load_history() -> Result<Option<History>, Box<dyn std::error::Error>> {
    if fs::metadata(HISTORY_FILE).await.is_err() {
        return Ok(None);
    }

    let json = fs::read_to_string(HISTORY_FILE).await?;
    let history: History = serde_json::from_str(&json)?;
    Ok(Some(history))
}

/// Clears the rename history by deleting the history file asynchronously.
/// 
/// # Returns
/// 
/// * `Ok(())` - History cleared (or didn't exist)
/// * `Err(Box<dyn Error>)` - If file deletion fails
/// 
/// # Examples
/// 
/// ```
/// # tokio_test::block_on(async {
/// use freneng::history::clear_history;
/// 
/// clear_history().await.unwrap();
/// # })
/// ```
pub async fn clear_history() -> Result<(), Box<dyn std::error::Error>> {
    if fs::metadata(HISTORY_FILE).await.is_ok() {
        fs::remove_file(HISTORY_FILE).await?;
    }
    Ok(())
}