use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use rusqlite::Connection; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TreeNode { pub id: String, pub name: String, pub node_type: String, pub size: Option, pub children: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct FileUploadResult { pub success: bool, pub message: String, pub file_id: Option, } #[tauri::command] pub async fn get_tree( user_id: String, tree_type: String, ) -> Result { let db_path = PathBuf::from("data/users") .join(format!("{}.sqlite", user_id)); if !db_path.exists() { return Err(format!("Database not found: {:?}", db_path)); } let conn = Connection::open(&db_path) .map_err(|e| format!("Failed to open database: {}", e))?; let mut stmt = conn.prepare( "SELECT node_id, label, node_type, parent_id, file_size FROM file_nodes WHERE tree_type = ?1 ORDER BY sort_order ASC" ).map_err(|e| format!("Failed to prepare statement: {}", e))?; let nodes = stmt.query_map([&tree_type], |row| { Ok(( row.get::<_, String>(0)?, // node_id row.get::<_, String>(1)?, // label row.get::<_, String>(2)?, // node_type row.get::<_, Option>(3)?, // parent_id row.get::<_, Option>(4)?, // file_size )) }).map_err(|e| format!("Failed to query nodes: {}", e))?; let mut all_nodes: Vec<(String, String, String, Option, Option)> = vec ![]; for node in nodes { let node_data = node.map_err(|e| format!("Failed to get node: {}", e))?; all_nodes.push(node_data); } let root_node = build_tree(&all_nodes, None)?; Ok(root_node) } #[tauri::command] pub async fn list_files( user_id: String, tree_type: String, parent_id: String, ) -> Result, String> { let db_path = PathBuf::from("data/users") .join(format!("{}.sqlite", user_id)); if !db_path.exists() { return Err(format!("Database not found: {:?}", db_path)); } let conn = Connection::open(&db_path) .map_err(|e| format!("Failed to open database: {}", e))?; let mut stmt = conn.prepare( "SELECT node_id, label, node_type, file_size FROM file_nodes WHERE tree_type = ?1 AND parent_id = ?2 ORDER BY sort_order ASC" ).map_err(|e| format!("Failed to prepare statement: {}", e))?; let files = stmt.query_map([&tree_type, &parent_id], |row| { Ok(TreeNode { id: row.get::<_, String>(0)?, name: row.get::<_, String>(1)?, node_type: row.get::<_, String>(2)?, size: row.get::<_, Option>(3)?.map(|s| s as u64), children: vec ![], }) }).map_err(|e| format!("Failed to query files: {}", e))?; let mut result: Vec = vec ![]; for file in files { let file_node = file.map_err(|e| format!("Failed to get file: {}", e))?; result.push(file_node); } Ok(result) } #[tauri::command] pub async fn upload_file( user_id: String, source_path: String, target_path: String, tree_type: String, ) -> Result { use std::fs; use uuid::Uuid; let demo_dir = "/Users/accusys/momentry/var/sftpgo/data/demo"; let target_file = PathBuf::from(demo_dir).join(&target_path); if !Path::new(&source_path).exists() { return Err(format!("Source file not found: {}", source_path)); } fs::copy(&source_path, &target_file) .map_err(|e| format!("Failed to copy file: {}", e))?; let db_path = PathBuf::from("data/users") .join(format!("{}.sqlite", user_id)); let conn = Connection::open(&db_path) .map_err(|e| format!("Failed to open database: {}", e))?; let file_uuid = Uuid::new_v4().to_string(); let file_name = Path::new(&target_path) .file_name() .and_then(|n| n.to_str()) .unwrap_or("unknown") .to_string(); conn.execute( "INSERT INTO file_registry (file_uuid, original_name, file_path, file_size, registered_at, last_seen_at, status) VALUES (?1, ?2, ?3, ?4, datetime('now'), datetime('now'), 'active')", rusqlite::params![&file_uuid, &file_name, target_file.to_string_lossy().to_string(), fs::metadata(&target_file).map(|m| m.len() as i64).unwrap_or(0)] ).map_err(|e| format!("Failed to register file: {}", e))?; Ok(FileUploadResult { success: true, message: format!("File uploaded to: {}", target_path), file_id: Some(file_uuid), }) } #[tauri::command] pub async fn search_files( user_id: String, tree_type: String, query: String, ) -> Result, String> { let db_path = PathBuf::from("data/users") .join(format!("{}.sqlite", user_id)); if !db_path.exists() { return Err(format!("Database not found: {:?}", db_path)); } let conn = Connection::open(&db_path) .map_err(|e| format!("Failed to open database: {}", e))?; let mut stmt = conn.prepare( "SELECT node_id, label, node_type, file_size FROM file_nodes WHERE tree_type = ?1 AND label LIKE ?2 ORDER BY label ASC" ).map_err(|e| format!("Failed to prepare statement: {}", e))?; let search_pattern = format!("%{}%", query); let files = stmt.query_map([&tree_type, &search_pattern], |row| { Ok(TreeNode { id: row.get::<_, String>(0)?, name: row.get::<_, String>(1)?, node_type: row.get::<_, String>(2)?, size: row.get::<_, Option>(3)?.map(|s| s as u64), children: vec ![], }) }).map_err(|e| format!("Failed to search files: {}", e))?; let mut result: Vec = vec ![]; for file in files { let file_node = file.map_err(|e| format!("Failed to get file: {}", e))?; result.push(file_node); } Ok(result) } #[tauri::command] pub async fn download_file( user_id: String, file_uuid: String, ) -> Result { let db_path = PathBuf::from("data/users") .join(format!("{}.sqlite", user_id)); if !db_path.exists() { return Err(format!("Database not found: {:?}", db_path)); } let conn = Connection::open(&db_path) .map_err(|e| format!("Failed to open database: {}", e))?; let file_path: String = conn.query_row( "SELECT fr.file_path FROM file_registry fr JOIN file_nodes fn ON fr.file_uuid = fn.file_uuid WHERE fn.node_id = ?1", [&file_uuid], |row| row.get(0) ).map_err(|e| format!("Failed to query file path: {}", e))?; Ok(file_path) } #[tauri::command] pub async fn open_file(file_path: String) -> Result<(), String> { use std::process::Command; #[cfg(target_os = "macos")] { Command::new("open") .arg(&file_path) .spawn() .map_err(|e| format!("Failed to open file: {}", e))?; } #[cfg(target_os = "windows")] { Command::new("cmd") .args(&["/C", "start", &file_path]) .spawn() .map_err(|e| format!("Failed to open file: {}", e))?; } #[cfg(target_os = "linux")] { Command::new("xdg-open") .arg(&file_path) .spawn() .map_err(|e| format!("Failed to open file: {}", e))?; } Ok(()) } fn build_tree( nodes: &[(String, String, String, Option, Option)], parent_id: Option<&str>, ) -> Result { for (node_id, label, node_type, node_parent_id, file_size) in nodes { let is_match = match (parent_id, node_parent_id) { (None, None) => true, (Some(p), Some(np)) => p == np, _ => false, }; if is_match { let children = nodes.iter() .filter(|(_, _, _, np, _)| np.as_deref() == Some(node_id.as_str())) .map(|(cid, clabel, ctype, _, csize)| { TreeNode { id: cid.clone(), name: clabel.clone(), node_type: ctype.clone(), size: csize.map(|s| s as u64), children: vec ![], } }) .collect(); return Ok(TreeNode { id: node_id.clone(), name: label.clone(), node_type: node_type.clone(), size: file_size.map(|s| s as u64), children, }); } } Err("Root node not found".to_string()) }