dl-cleaner 0.1.0

A tool to clean up the contents of your Downloads folder.
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Result, Context};

/// ファイル情報を表す構造体
#[derive(Debug, Clone)]
pub struct FileInfo {
    pub path: PathBuf,
    pub name: String,
    pub size: u64,
    pub modified: std::time::SystemTime,
}

pub fn scan_downloads(download_dir: &Path) -> Result<Vec<FileInfo>> {
    if !download_dir.exists() {
        anyhow::bail!("Downloads directory not found: {:?}", download_dir);
    }

    if !download_dir.is_dir() {
        anyhow::bail!("Specified path is not a directory: {:?}", download_dir);
    }

    let mut files = Vec::new();

    // Enumerate files in the directory
    for entry in fs::read_dir(download_dir)
        .context("Failed to read downloads directory")?
    {
        let entry = entry.context("Failed to read directory entry")?;
        let path = entry.path();

        // Process files only (skip directories)
        if !path.is_dir() {
            let metadata = fs::metadata(&path)
                .context(format!("Failed to read file metadata: {:?}", path))?;

            let name = path
                .file_name()
                .and_then(|n| n.to_str())
                .map(|s| s.to_string())
                .unwrap_or_else(|| "unknown".to_string());

            files.push(FileInfo {
                path: path.clone(),
                name,
                size: metadata.len(),
                modified: metadata.modified()?,
            });
        }
    }

    // Sort files by name
    files.sort_by(|a, b| a.name.cmp(&b.name));

    Ok(files)
}

/// Display scan results
pub fn display_scan_results(files: &[FileInfo]) {
    if files.is_empty() {
        println!("No files found.");
        return;
    }

    println!("Scan results: {} file(s) found\n", files.len());
    println!("{:<50} {:<15} {:<20}", "Filename", "Size (B)", "Modified");
    println!("{}", "-".repeat(85));

    let mut total_size = 0u64;
    for file in files {
        let size_str = format!("{}", file.size);
        let modified_str = match file.modified.duration_since(std::time::UNIX_EPOCH) {
            Ok(duration) => {
                let secs = duration.as_secs();
                let datetime = chrono::DateTime::<chrono::Local>::from(
                    std::time::UNIX_EPOCH + std::time::Duration::from_secs(secs)
                );
                datetime.format("%Y-%m-%d %H:%M:%S").to_string()
            }
            Err(_) => "Unknown".to_string(),
        };

        println!("{:<50} {:<15} {:<20}", 
            &file.name[..file.name.len().min(50)],
            size_str,
            modified_str
        );
        total_size += file.size;
    }

    println!("{}", "-".repeat(85));
    println!("Total size: {} B ({:.2} MB)", total_size, total_size as f64 / 1024.0 / 1024.0);
}

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

    #[test]
    fn test_scan_downloads_empty_directory() {
        let temp_dir = TempDir::new().unwrap();
        let files = scan_downloads(temp_dir.path()).unwrap();
        assert_eq!(files.len(), 0);
    }

    #[test]
    fn test_scan_downloads_with_files() {
        let temp_dir = TempDir::new().unwrap();
        let test_file1 = temp_dir.path().join("test1.txt");
        let test_file2 = temp_dir.path().join("test2.txt");

        fs::write(&test_file1, "content1").unwrap();
        fs::write(&test_file2, "content2").unwrap();

        let files = scan_downloads(temp_dir.path()).unwrap();
        assert_eq!(files.len(), 2);
        assert_eq!(files[0].name, "test1.txt");
        assert_eq!(files[1].name, "test2.txt");
    }
}