ceadeef329
功能: - Tauri dialog API集成(文件选择对话框) - upload_file命令完整实现(文件复制 + 数据库注册) - 上传按钮UI(带loading状态) - 上传完成后自动刷新文件树 技术: - 添加uuid依赖(UUID v4生成) - Rust: std::fs文件复制 + rusqlite数据库注册 - Vue: @tauri-apps/api/dialog集成 - Vite: 修复dialog API外部化配置 状态:Phase 2完成100%
296 lines
8.7 KiB
Rust
296 lines
8.7 KiB
Rust
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<u64>,
|
|
pub children: Vec<TreeNode>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct FileUploadResult {
|
|
pub success: bool,
|
|
pub message: String,
|
|
pub file_id: Option<String>,
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn get_tree(
|
|
user_id: String,
|
|
tree_type: String,
|
|
) -> Result<TreeNode, 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, 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<String>>(3)?, // parent_id
|
|
row.get::<_, Option<i64>>(4)?, // file_size
|
|
))
|
|
}).map_err(|e| format!("Failed to query nodes: {}", e))?;
|
|
|
|
let mut all_nodes: Vec<(String, String, String, Option<String>, Option<i64>)> = 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<Vec<TreeNode>, 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<i64>>(3)?.map(|s| s as u64),
|
|
children: vec
|
|
![],
|
|
})
|
|
}).map_err(|e| format!("Failed to query files: {}", e))?;
|
|
|
|
let mut result: Vec<TreeNode> = 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<FileUploadResult, String> {
|
|
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<Vec<TreeNode>, 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<i64>>(3)?.map(|s| s as u64),
|
|
children: vec
|
|
![],
|
|
})
|
|
}).map_err(|e| format!("Failed to search files: {}", e))?;
|
|
|
|
let mut result: Vec<TreeNode> = 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<String, 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 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<String>, Option<i64>)],
|
|
parent_id: Option<&str>,
|
|
) -> Result<TreeNode, String> {
|
|
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())
|
|
} |