use anyhow::{anyhow, Result}; use flutter_rust_bridge::frb; use rayon::prelude::*; use std::collections::HashMap; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use unp4k::dataforge::DataForge; use unp4k::{CryXmlReader, P4kEntry, P4kFile}; /// P4K 文件项信息 #[frb(dart_metadata=("freezed"))] pub struct P4kFileItem { /// 文件名/路径 pub name: String, /// 是否为目录 pub is_directory: bool, /// 文件大小(字节) pub size: u64, /// 压缩后大小(字节) pub compressed_size: u64, /// 文件修改时间(毫秒时间戳) pub date_modified: i64, } /// 将 DOS 日期时间转换为毫秒时间戳 fn dos_datetime_to_millis(date: u16, time: u16) -> i64 { let year = ((date >> 9) & 0x7F) as i32 + 1980; let month = ((date >> 5) & 0x0F) as u32; let day = (date & 0x1F) as u32; let hour = ((time >> 11) & 0x1F) as u32; let minute = ((time >> 5) & 0x3F) as u32; let second = ((time & 0x1F) * 2) as u32; let days_since_epoch = { let mut days = 0i64; for y in 1970..year { days += if (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0) { 366 } else { 365 }; } let days_in_months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; if month >= 1 && month <= 12 { days += days_in_months[(month - 1) as usize] as i64; if month > 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) { days += 1; } } days += (day as i64) - 1; days }; (days_since_epoch * 86400 + (hour as i64) * 3600 + (minute as i64) * 60 + (second as i64)) * 1000 } // 全局 P4K 读取器实例(用于保持状态) static GLOBAL_P4K_READER: once_cell::sync::Lazy>>> = once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(None))); static GLOBAL_P4K_FILES: once_cell::sync::Lazy>>> = once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(HashMap::new()))); // 全局 DataForge 实例(用于 DCB 文件解析) static GLOBAL_DCB_READER: once_cell::sync::Lazy>>> = once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(None))); /// 打开 P4K 文件(仅打开,不读取文件列表) pub async fn p4k_open(p4k_path: String) -> Result<()> { let path = PathBuf::from(&p4k_path); if !path.exists() { return Err(anyhow!("P4K file not found: {}", p4k_path)); } // 在后台线程执行阻塞操作 let reader = tokio::task::spawn_blocking(move || { let reader = P4kFile::open(&path)?; Ok::<_, anyhow::Error>(reader) }) .await??; *GLOBAL_P4K_READER.lock().unwrap() = Some(reader); // 清空之前的文件列表缓存 GLOBAL_P4K_FILES.lock().unwrap().clear(); Ok(()) } /// 确保文件列表已加载(内部使用) fn ensure_files_loaded() -> Result { let mut files = GLOBAL_P4K_FILES.lock().unwrap(); if !files.is_empty() { return Ok(files.len()); } let reader = GLOBAL_P4K_READER.lock().unwrap(); if reader.is_none() { return Err(anyhow!("P4K reader not initialized")); } let entries = reader.as_ref().unwrap().entries(); for entry in entries { let name = if entry.name.starts_with("\\") { entry.name.clone() } else { format!("\\{}", entry.name.replace("/", "\\")) }; files.insert(name, entry.clone()); } Ok(files.len()) } /// 获取文件数量(会触发文件列表加载) pub async fn p4k_get_file_count() -> Result { tokio::task::spawn_blocking(|| ensure_files_loaded()).await? } /// 获取所有文件列表 pub async fn p4k_get_all_files() -> Result> { tokio::task::spawn_blocking(|| { ensure_files_loaded()?; let files = GLOBAL_P4K_FILES.lock().unwrap(); let mut result = Vec::with_capacity(files.len()); for (name, entry) in files.iter() { result.push(P4kFileItem { name: name.clone(), is_directory: false, size: entry.uncompressed_size, compressed_size: entry.compressed_size, date_modified: dos_datetime_to_millis(entry.mod_date, entry.mod_time), }); } Ok(result) }) .await? } /// 提取文件到内存 pub async fn p4k_extract_to_memory(file_path: String) -> Result> { // 确保文件列表已加载 tokio::task::spawn_blocking(|| ensure_files_loaded()).await??; // 获取文件 entry 的克隆 let entry = p4k_get_entry(file_path).await?; // 在后台线程执行阻塞的提取操作 let data = tokio::task::spawn_blocking(move || { let mut reader = GLOBAL_P4K_READER.lock().unwrap(); if reader.is_none() { return Err(anyhow!("P4K reader not initialized")); } let data = reader.as_mut().unwrap().extract_entry(&entry)?; if (entry.name.ends_with(".xml") || entry.name.ends_with(".mtl")) && CryXmlReader::is_cryxml(&data) { let cry_xml_string = CryXmlReader::parse(&data)?; return Ok(cry_xml_string.into_bytes()); } Ok::<_, anyhow::Error>(data) }) .await??; Ok(data) } async fn p4k_get_entry(file_path: String) -> Result { // 确保文件列表已加载 tokio::task::spawn_blocking(|| ensure_files_loaded()).await??; // 规范化路径 let normalized_path = if file_path.starts_with("\\") { file_path.clone() } else { format!("\\{}", file_path) }; // 获取文件 entry 的克隆 let entry = { let files = GLOBAL_P4K_FILES.lock().unwrap(); files .get(&normalized_path) .ok_or_else(|| anyhow!("File not found: {}", file_path))? .clone() }; Ok(entry) } /// 提取文件到磁盘 pub async fn p4k_extract_to_disk(file_path: String, output_path: String) -> Result<()> { let entry = p4k_get_entry(file_path).await?; // 在后台线程执行阻塞的文件写入操作 tokio::task::spawn_blocking(move || { let output = PathBuf::from(&output_path); // 创建父目录 if let Some(parent) = output.parent() { std::fs::create_dir_all(parent)?; } let mut reader_guard = GLOBAL_P4K_READER.lock().unwrap(); let reader = reader_guard .as_mut() .ok_or_else(|| anyhow!("P4K reader not initialized"))?; unp4k::p4k_utils::extract_single_file(reader, &entry, &output, true)?; Ok::<_, anyhow::Error>(()) }) .await??; Ok(()) } /// 关闭 P4K 读取器 pub async fn p4k_close() -> Result<()> { *GLOBAL_P4K_READER.lock().unwrap() = None; GLOBAL_P4K_FILES.lock().unwrap().clear(); Ok(()) } // ==================== DataForge/DCB API ==================== /// DCB 记录项信息 #[frb(dart_metadata=("freezed"))] pub struct DcbRecordItem { /// 记录路径 pub path: String, /// 记录索引 pub index: usize, } /// 检查数据是否为 DataForge/DCB 格式 pub fn dcb_is_dataforge(data: Vec) -> bool { DataForge::is_dataforge(&data) } /// 从内存数据打开 DCB 文件 pub async fn dcb_open(data: Vec) -> Result<()> { let df = tokio::task::spawn_blocking(move || { DataForge::parse(&data).map_err(|e| anyhow!("Failed to parse DataForge: {}", e)) }) .await??; *GLOBAL_DCB_READER.lock().unwrap() = Some(df); Ok(()) } /// 获取 DCB 记录数量 pub fn dcb_get_record_count() -> Result { let reader = GLOBAL_DCB_READER.lock().unwrap(); let df = reader .as_ref() .ok_or_else(|| anyhow!("DCB reader not initialized"))?; Ok(df.record_count()) } /// 获取所有 DCB 记录路径列表 pub async fn dcb_get_record_list() -> Result> { tokio::task::spawn_blocking(|| { let reader = GLOBAL_DCB_READER.lock().unwrap(); let df = reader .as_ref() .ok_or_else(|| anyhow!("DCB reader not initialized"))?; let path_to_record = df.path_to_record(); let mut result: Vec = path_to_record .iter() .map(|(path, &index)| DcbRecordItem { path: path.clone(), index, }) .collect(); // 按路径排序 result.sort_by(|a, b| a.path.cmp(&b.path)); Ok(result) }) .await? } /// 根据路径获取单条记录的 XML pub async fn dcb_record_to_xml(path: String) -> Result { tokio::task::spawn_blocking(move || { let reader = GLOBAL_DCB_READER.lock().unwrap(); let df = reader .as_ref() .ok_or_else(|| anyhow!("DCB reader not initialized"))?; df.record_to_xml(&path, true) .map_err(|e| anyhow!("Failed to convert record to XML: {}", e)) }) .await? } /// 根据索引获取单条记录的 XML pub async fn dcb_record_to_xml_by_index(index: usize) -> Result { tokio::task::spawn_blocking(move || { let reader = GLOBAL_DCB_READER.lock().unwrap(); let df = reader .as_ref() .ok_or_else(|| anyhow!("DCB reader not initialized"))?; df.record_to_xml_by_index(index, true) .map_err(|e| anyhow!("Failed to convert record to XML: {}", e)) }) .await? } /// 全文搜索 DCB 记录 /// 返回匹配的记录路径和预览摘要 #[frb(dart_metadata=("freezed"))] pub struct DcbSearchResult { /// 记录路径 pub path: String, /// 记录索引 pub index: usize, /// 匹配的行内容和行号列表 pub matches: Vec, } #[frb(dart_metadata=("freezed"))] pub struct DcbSearchMatch { /// 行号(从1开始) pub line_number: usize, /// 匹配行的内容(带上下文) pub line_content: String, } /// 全文搜索 DCB 记录 pub async fn dcb_search_all(query: String) -> Result> { tokio::task::spawn_blocking(move || { let reader = GLOBAL_DCB_READER.lock().unwrap(); let df = reader .as_ref() .ok_or_else(|| anyhow!("DCB reader not initialized"))?; let query_lower = query.to_lowercase(); // 收集所有记录路径和索引 let records: Vec<(String, usize)> = df .path_to_record() .iter() .map(|(path, &index)| (path.clone(), index)) .collect(); // 使用 rayon 并发搜索 let mut results: Vec = records .par_iter() .filter_map(|(path, index)| { // 先检查路径是否匹配 let path_matches = path.to_lowercase().contains(&query_lower); // 尝试获取 XML 并搜索内容 if let Ok(xml) = df.record_to_xml_by_index(*index, true) { let mut matches = Vec::new(); for (line_num, line) in xml.lines().enumerate() { if line.to_lowercase().contains(&query_lower) { let line_content = if line.len() > 200 { format!("{}...", &line[..200]) } else { line.to_string() }; matches.push(DcbSearchMatch { line_number: line_num + 1, line_content, }); // 每条记录最多保留 5 个匹配 if matches.len() >= 5 { break; } } } if path_matches || !matches.is_empty() { return Some(DcbSearchResult { path: path.clone(), index: *index, matches, }); } } None }) .collect(); // 按路径排序以保持结果稳定性 results.sort_by(|a, b| a.path.cmp(&b.path)); Ok(results) }) .await? } /// 导出 DCB 到磁盘 /// merge: true = 合并为单个 XML,false = 分离为多个 XML 文件 pub async fn dcb_export_to_disk(output_path: String, dcb_path: String, merge: bool) -> Result<()> { let output = PathBuf::from(&output_path); let dcb = PathBuf::from(&dcb_path); tokio::task::spawn_blocking(move || { let reader = GLOBAL_DCB_READER.lock().unwrap(); let df = reader .as_ref() .ok_or_else(|| anyhow!("DCB reader not initialized"))?; if merge { unp4k::dataforge::export_merged(&df, &dcb, Some(&output))?; } else { unp4k::dataforge::export_separate(&df, &dcb, Some(&output))?; } Ok(()) }) .await? } /// 关闭 DCB 读取器 pub async fn dcb_close() -> Result<()> { *GLOBAL_DCB_READER.lock().unwrap() = None; Ok(()) }