diff --git a/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs index 6abee4d..58f9fc3 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/material_commands.rs @@ -80,7 +80,6 @@ async fn import_materials_with_tauri_events( config: MaterialProcessingConfig, app_handle: tauri::AppHandle, ) -> Result { - use anyhow::anyhow; use std::path::Path; use std::time::Instant; use tracing::{info, warn, error, debug}; @@ -113,6 +112,9 @@ async fn import_materials_with_tauri_events( "progress_percentage": 0.0 })); + // 添加小延迟确保用户能看到loading动画 + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + // 异步处理每个文件 for (index, file_path) in request.file_paths.iter().enumerate() { debug!(file_path = %file_path, "异步处理文件"); @@ -127,12 +129,13 @@ async fn import_materials_with_tauri_events( "progress_percentage": (index as f64 / result.total_files as f64) * 100.0 })); - // 处理单个文件 - match process_single_file_simple( + // 处理单个文件 - 使用完整的业务逻辑 + match process_single_file_with_full_logic( Arc::clone(&repository), &request.project_id, file_path, &config, + app_handle.clone(), ).await { Ok(Some(material)) => { info!( @@ -186,19 +189,23 @@ async fn import_materials_with_tauri_events( Ok(result) } -/// 简化的单文件处理函数 -async fn process_single_file_simple( +/// 使用完整业务逻辑的单文件处理函数 +async fn process_single_file_with_full_logic( repository: Arc, project_id: &str, file_path: &str, - _config: &MaterialProcessingConfig, + config: &MaterialProcessingConfig, + app_handle: tauri::AppHandle, ) -> Result, anyhow::Error> { use anyhow::anyhow; use std::path::Path; use std::fs; use tokio::task; - use crate::data::models::material::{Material, MaterialType}; + use crate::data::models::material::{Material, MaterialType, ProcessingStatus}; use crate::business::errors::error_utils; + use tracing::{info, warn, error, debug}; + + debug!(file_path = %file_path, "开始处理单个文件"); // 验证文件路径 error_utils::validate_file_path(file_path) @@ -218,6 +225,7 @@ async fn process_single_file_simple( // 检查是否已存在相同的文件 if repository.exists_by_md5(project_id, &md5_hash)? { + warn!(file_path = %file_path, "文件已存在,跳过"); return Ok(None); // 跳过重复文件 } @@ -235,17 +243,148 @@ async fn process_single_file_simple( let material_type = MaterialType::from_extension(extension); // 创建素材对象 - let material = Material::new( + let mut material = Material::new( project_id.to_string(), file_name.clone(), file_path.to_string(), file_size, md5_hash, - material_type, + material_type.clone(), ); // 保存到数据库 repository.create(&material)?; + info!(material_id = %material.id, file_name = %file_name, "素材创建成功"); + + // 发送处理进度事件 + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "开始处理", + "progress_percentage": 0.0 + })); + + // 如果启用自动处理,则进行完整的业务处理 + if config.auto_process.unwrap_or(true) { + // 更新状态为处理中 + MaterialService::update_material_status( + &repository, + &material.id, + ProcessingStatus::Processing, + None, + )?; + + // 1. 提取元数据 + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "提取元数据", + "progress_percentage": 25.0 + })); + + let original_path = material.original_path.clone(); + let material_type_clone = material.material_type.clone(); + + match task::spawn_blocking(move || { + MaterialService::extract_metadata(&original_path, &material_type_clone) + }).await? { + Ok(metadata) => { + material.set_metadata(metadata); + repository.update(&material)?; + info!(material_id = %material.id, "元数据提取成功"); + } + Err(e) => { + error!(material_id = %material.id, error = %e, "元数据提取失败"); + MaterialService::update_material_status( + &repository, + &material.id, + ProcessingStatus::Failed, + Some(format!("元数据提取失败: {}", e)), + )?; + return Err(e); + } + } + + // 2. 场景检测(如果是视频且启用了场景检测) + if matches!(material.material_type, MaterialType::Video) && config.enable_scene_detection { + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "场景检测", + "progress_percentage": 50.0 + })); + + let original_path = material.original_path.clone(); + let threshold = config.scene_detection_threshold; + + match task::spawn_blocking(move || { + MaterialService::detect_video_scenes(&original_path, threshold) + }).await? { + Ok(scene_detection) => { + info!(material_id = %material.id, scenes = scene_detection.scenes.len(), "场景检测成功"); + material.set_scene_detection(scene_detection); + repository.update(&material)?; + } + Err(e) => { + // 场景检测失败不应该导致整个处理失败 + warn!(material_id = %material.id, error = %e, "场景检测失败"); + } + } + } + + // 3. 检查是否需要切分视频 + let should_segment = material.needs_segmentation(config.max_segment_duration) || + (matches!(material.material_type, MaterialType::Video) && material.scene_detection.is_some()); + + if should_segment { + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "视频切分", + "progress_percentage": 75.0 + })); + + let repository_clone = Arc::clone(&repository); + let material_clone = material.clone(); + let config_clone = config.clone(); + + match task::spawn_blocking(move || { + MaterialService::segment_video(&repository_clone, &material_clone, &config_clone) + }).await? { + Ok(_) => { + info!(material_id = %material.id, "视频切分完成"); + } + Err(e) => { + error!(material_id = %material.id, error = %e, "视频切分失败"); + MaterialService::update_material_status( + &repository, + &material.id, + ProcessingStatus::Failed, + Some(format!("视频切分失败: {}", e)), + )?; + return Err(e); + } + } + } + + // 标记为完成 + MaterialService::update_material_status( + &repository, + &material.id, + ProcessingStatus::Completed, + None, + )?; + + // 发送处理完成事件 + let _ = app_handle.emit("material_processing_progress", serde_json::json!({ + "material_id": material.id, + "file_name": file_name, + "stage": "处理完成", + "progress_percentage": 100.0 + })); + + info!(material_id = %material.id, "素材处理完成"); + } Ok(Some(material)) } diff --git a/apps/desktop/src/components/MaterialImportDialog.tsx b/apps/desktop/src/components/MaterialImportDialog.tsx index dd0c2e2..2bd5b99 100644 --- a/apps/desktop/src/components/MaterialImportDialog.tsx +++ b/apps/desktop/src/components/MaterialImportDialog.tsx @@ -183,13 +183,11 @@ export const MaterialImportDialog: React.FC = ({ console.log('开始异步导入:', request); - // 使用异步导入,进度更新通过事件监听器处理 - const result = await importMaterialsAsync(request); + // 使用异步导入,进度更新和完成状态完全通过事件监听器处理 + await importMaterialsAsync(request); - // 如果没有通过事件监听器收到完成事件,手动处理完成状态 - console.log('异步导入完成:', result); - setStep('complete'); - onImportComplete(result); + // 注意:不在这里设置完成状态,完全依赖事件监听器 + console.log('异步导入命令执行完成,等待事件通知'); } catch (error) { console.error('导入失败:', error); setStep('configure'); // 返回配置步骤