// // Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) // // This file is part of the Bichon Email Archiving Project // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use std::{ collections::HashMap, fs, path::{Path, PathBuf}, }; use console::style; use dialoguer::{theme::ColorfulTheme, Input}; use mail_parser::MessageParser; use reqwest::Client; use bichon_core::base64_encode_url_safe; use crate::{BichonCtlConfig, api::sender::send_batch_request}; pub async fn handle_eml_directory_import( config: &BichonCtlConfig, account_id: u64, theme: &ColorfulTheme, ) { let root_str: String = Input::with_theme(theme) .with_prompt("Enter the ROOT directory to scan for .eml files") .validate_with(|input: &String| { let p = std::path::Path::new(input); if p.exists() && p.is_dir() { Ok(()) } else { Err("Directory not found.") } }) .interact_text() .unwrap(); let root_path = std::path::PathBuf::from(root_str); let mut tasks: HashMap> = HashMap::new(); println!( "{}", style("šŸ” Scanning recursively using std::fs...").dim() ); if let Err(e) = scan_dir(&root_path, &root_path, &mut tasks) { eprintln!("Error scanning directory: {}", e); return; } if tasks.is_empty() { println!("{}", style("No .eml files found.").yellow()); } else { process_and_upload(config, account_id, tasks).await; } } fn scan_dir( root: &Path, current: &Path, tasks: &mut HashMap>, ) -> std::io::Result<()> { if current.is_dir() { for entry in fs::read_dir(current)? { let entry = entry?; let path = entry.path(); if path.is_dir() { scan_dir(root, &path, tasks)?; } else if path.is_file() { if path.extension().and_then(|s| s.to_str()) == Some("eml") { let rel_path = path.strip_prefix(root).unwrap_or(Path::new("")); let mailbox_name = rel_path .parent() .map(|p| p.to_string_lossy().replace('\\', "/")) .unwrap_or_default(); let folder = if mailbox_name.is_empty() { "Inbox".to_string() } else { mailbox_name }; tasks.entry(folder).or_insert_with(|| Vec::new()).push(path); } } } } Ok(()) } async fn process_and_upload( config: &BichonCtlConfig, account_id: u64, tasks: HashMap>, ) { let client = Client::new(); let batch_size = 50; for (mailbox, files) in tasks { println!("\nšŸš€ Processing mailbox: {}", style(&mailbox).cyan().bold()); let mut current_batch = Vec::new(); for file_path in files { let body = match fs::read(&file_path) { Ok(b) => b, Err(e) => { eprintln!( " {} Failed to read file {:?}: {}", style("✘").red(), file_path, e ); continue; } }; if MessageParser::new().parse(&body).is_some() { let b64_content = base64_encode_url_safe!(&body); current_batch.push(b64_content); if current_batch.len() >= batch_size { let to_send = current_batch; current_batch = Vec::with_capacity(batch_size); send_batch_request(&client, config, account_id, &mailbox, to_send).await; } } else { eprintln!( " {} Invalid format, skipping: {:?}", style("⚠").yellow(), file_path ); } } if !current_batch.is_empty() { send_batch_request(&client, config, account_id, &mailbox, current_batch).await; } } }