lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
// Copyright 2025 LunaOS Contributors
// SPDX-License-Identifier: Apache-2.0

//! Type definitions for trash/recycle bin

use alloc::string::String;
use alloc::vec::Vec;
use thiserror_no_std::Error;

/// Error type for trash operations
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum TrashError {
    /// Dataset not found
    #[error("Dataset not found: {0}")]
    DatasetNotFound(String),

    /// Trash entry not found
    #[error("Trash entry not found: {0}")]
    EntryNotFound(u64),

    /// Target path already exists
    #[error("Target path already exists: {0}")]
    TargetExists(String),

    /// IO error
    #[error("IO error: {0}")]
    IoError(String),

    /// Path not found
    #[error("Path not found: {0}")]
    PathNotFound(String),

    /// Trash is disabled for this dataset
    #[error("Trash is disabled")]
    TrashDisabled,

    /// Permission denied
    #[error("Permission denied")]
    PermissionDenied,
}

/// Trash entry metadata
#[derive(Debug, Clone)]
pub struct TrashEntry {
    /// Original path of the file
    pub original_path: String,
    /// Unique trash item ID
    pub trash_id: u64,
    /// Deletion timestamp (Unix epoch)
    pub deleted_at: u64,
    /// File size in bytes
    pub size: u64,
    /// UID of user who deleted
    pub deleted_by: u32,
    /// Expiration timestamp (0 = never expires)
    pub expires_at: u64,
    /// Is this a directory?
    pub is_directory: bool,
}

/// Trash configuration per dataset
#[derive(Debug, Clone)]
pub struct TrashConfig {
    /// Enable trash (default: true)
    pub enabled: bool,
    /// Retention period in seconds (default: 30 days)
    pub retention_seconds: u64,
    /// Maximum trash size in bytes (0 = unlimited)
    pub max_size: u64,
    /// Maximum trash as percentage of dataset quota (0-100)
    pub max_percent: u8,
    /// Auto-purge when low on space
    pub auto_purge: bool,
    /// Purge threshold (percentage of quota)
    pub purge_threshold: u8,
    /// Securely erase data on permanent delete (overwrite before unlink)
    pub secure_delete: bool,
}

impl Default for TrashConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            retention_seconds: 30 * 24 * 3600, // 30 days
            max_size: 0,                       // Unlimited
            max_percent: 10,                   // 10% of quota
            auto_purge: true,
            purge_threshold: 90,  // Purge when 90% full
            secure_delete: false, // Don't secure erase by default (performance)
        }
    }
}

/// Filter for emptying trash
#[derive(Debug, Clone)]
pub enum TrashFilter {
    /// Delete all items
    All,
    /// Delete items older than N seconds
    OlderThan(u64),
    /// Delete expired items only
    Expired,
    /// Delete items larger than N bytes
    LargerThan(u64),
    /// Delete a specific item by ID
    ById(u64),
    /// Delete items matching path pattern
    ByPattern(String),
}

/// Result of emptying trash
#[derive(Debug, Clone, Default)]
pub struct TrashEmptyResult {
    /// Number of items deleted
    pub deleted_count: u64,
    /// Total bytes freed
    pub deleted_bytes: u64,
    /// Number of items that failed to delete
    pub failed_count: u64,
}

/// Stored trash metadata (in-memory representation)
#[derive(Debug, Clone, Default)]
pub struct TrashMetadata {
    /// All trash entries
    pub entries: Vec<TrashEntry>,
    /// Next available trash ID
    pub next_id: u64,
    /// Total size of trash
    pub total_size: u64,
}

impl TrashMetadata {
    /// Create new empty metadata
    pub fn new() -> Self {
        Self {
            entries: Vec::new(),
            next_id: 1,
            total_size: 0,
        }
    }

    /// Add an entry to trash
    pub fn add_entry(&mut self, entry: TrashEntry) {
        self.total_size += entry.size;
        self.entries.push(entry);
    }

    /// Remove an entry from trash
    pub fn remove_entry(&mut self, trash_id: u64) -> Option<TrashEntry> {
        if let Some(pos) = self.entries.iter().position(|e| e.trash_id == trash_id) {
            let entry = self.entries.remove(pos);
            self.total_size = self.total_size.saturating_sub(entry.size);
            Some(entry)
        } else {
            None
        }
    }

    /// Find entry by ID
    pub fn find_entry(&self, trash_id: u64) -> Option<&TrashEntry> {
        self.entries.iter().find(|e| e.trash_id == trash_id)
    }

    /// Get expired entries
    pub fn expired_entries(&self, current_time: u64) -> Vec<&TrashEntry> {
        self.entries
            .iter()
            .filter(|e| e.expires_at > 0 && e.expires_at <= current_time)
            .collect()
    }

    /// Get oldest entries (for auto-purge)
    pub fn oldest_entries(&self) -> Vec<&TrashEntry> {
        let mut entries: Vec<&TrashEntry> = self.entries.iter().collect();
        entries.sort_by_key(|e| e.deleted_at);
        entries
    }

    /// Allocate next trash ID
    pub fn allocate_id(&mut self) -> u64 {
        let id = self.next_id;
        self.next_id += 1;
        id
    }
}