Files
claudia/src-tauri/src/commands/filesystem.rs
2025-10-17 17:20:46 +08:00

307 lines
8.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::file_watcher::FileWatcherState;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use tauri::State;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FileNode {
pub name: String,
pub path: String,
pub file_type: String, // "file" or "directory"
pub children: Option<Vec<FileNode>>,
pub size: Option<u64>,
pub modified: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FileSystemChange {
pub path: String,
pub change_type: String, // "created", "modified", "deleted"
}
/// 读取文件内容
#[tauri::command]
pub async fn read_file(path: String) -> Result<String, String> {
fs::read_to_string(&path).map_err(|e| format!("Failed to read file: {}", e))
}
/// 写入文件内容
#[tauri::command]
pub async fn write_file(path: String, content: String) -> Result<(), String> {
fs::write(&path, content).map_err(|e| format!("Failed to write file: {}", e))
}
/// 读取目录树结构
#[tauri::command]
pub async fn read_directory_tree(
path: String,
max_depth: Option<u32>,
ignore_patterns: Option<Vec<String>>,
) -> Result<FileNode, String> {
let path = Path::new(&path);
if !path.exists() {
return Err(format!("Path does not exist: {}", path.display()));
}
let max_depth = max_depth.unwrap_or(5);
let ignore_patterns = ignore_patterns.unwrap_or_else(|| {
vec![
String::from("node_modules"),
String::from(".git"),
String::from("target"),
String::from("dist"),
String::from("build"),
String::from(".idea"),
String::from(".vscode"),
String::from("__pycache__"),
String::from(".DS_Store"),
]
});
read_directory_recursive(path, 0, max_depth, &ignore_patterns).map_err(|e| e.to_string())
}
fn read_directory_recursive(
path: &Path,
current_depth: u32,
max_depth: u32,
ignore_patterns: &[String],
) -> std::io::Result<FileNode> {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
let metadata = fs::metadata(path)?;
let node = if metadata.is_dir() {
let mut children = Vec::new();
if current_depth < max_depth {
// Check if directory should be ignored
let should_ignore = ignore_patterns
.iter()
.any(|pattern| &name == pattern || name.starts_with('.'));
if !should_ignore {
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
let child_path = entry.path();
// Skip symlinks to avoid infinite loops
if let Ok(meta) = entry.metadata() {
if !meta.file_type().is_symlink() {
if let Ok(child_node) = read_directory_recursive(
&child_path,
current_depth + 1,
max_depth,
ignore_patterns,
) {
children.push(child_node);
}
}
}
}
// Sort children: directories first, then files, alphabetically
children.sort_by(|a, b| match (a.file_type.as_str(), b.file_type.as_str()) {
("directory", "file") => std::cmp::Ordering::Less,
("file", "directory") => std::cmp::Ordering::Greater,
_ => a.name.to_lowercase().cmp(&b.name.to_lowercase()),
});
}
}
FileNode {
name,
path: path.to_string_lossy().to_string(),
file_type: String::from("directory"),
children: Some(children),
size: None,
modified: metadata
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs()),
}
} else {
FileNode {
name,
path: path.to_string_lossy().to_string(),
file_type: String::from("file"),
children: None,
size: Some(metadata.len()),
modified: metadata
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs()),
}
};
Ok(node)
}
/// 搜索文件
#[tauri::command]
pub async fn search_files_by_name(
base_path: String,
query: String,
max_results: Option<usize>,
) -> Result<Vec<String>, String> {
let base_path = Path::new(&base_path);
if !base_path.exists() {
return Err(format!("Path does not exist: {}", base_path.display()));
}
let query_lower = query.to_lowercase();
let max_results = max_results.unwrap_or(100);
let mut results = Vec::new();
search_recursive(base_path, &query_lower, &mut results, max_results)?;
Ok(results)
}
fn search_recursive(
dir: &Path,
query: &str,
results: &mut Vec<String>,
max_results: usize,
) -> Result<(), String> {
if results.len() >= max_results {
return Ok(());
}
let entries = fs::read_dir(dir).map_err(|e| format!("Failed to read directory: {}", e))?;
for entry in entries {
if results.len() >= max_results {
break;
}
let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?;
let path = entry.path();
let file_name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_lowercase();
if file_name.contains(query) {
results.push(path.to_string_lossy().to_string());
}
if path.is_dir() {
// Skip hidden directories and common ignore patterns
if !file_name.starts_with('.')
&& file_name != "node_modules"
&& file_name != "target"
&& file_name != "dist"
{
let _ = search_recursive(&path, query, results, max_results);
}
}
}
Ok(())
}
/// 获取文件信息
#[tauri::command]
pub async fn get_file_info(path: String) -> Result<FileNode, String> {
let path = Path::new(&path);
if !path.exists() {
return Err(format!("Path does not exist: {}", path.display()));
}
let metadata = fs::metadata(path).map_err(|e| format!("Failed to get metadata: {}", e))?;
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
Ok(FileNode {
name,
path: path.to_string_lossy().to_string(),
file_type: if metadata.is_dir() {
String::from("directory")
} else {
String::from("file")
},
children: None,
size: if metadata.is_file() {
Some(metadata.len())
} else {
None
},
modified: metadata
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs()),
})
}
/// 监听文件系统变化
#[tauri::command]
pub async fn watch_directory(
watcher_state: State<'_, FileWatcherState>,
path: String,
recursive: Option<bool>,
) -> Result<(), String> {
let recursive = recursive.unwrap_or(false);
watcher_state.with_manager(|manager| manager.watch_path(&path, recursive))
}
/// 停止监听指定路径
#[tauri::command]
pub async fn unwatch_directory(
watcher_state: State<'_, FileWatcherState>,
path: String,
) -> Result<(), String> {
watcher_state.with_manager(|manager| manager.unwatch_path(&path))
}
/// 获取当前监听的路径列表
#[tauri::command]
pub async fn get_watched_paths(
watcher_state: State<'_, FileWatcherState>,
) -> Result<Vec<String>, String> {
watcher_state.with_manager(|manager| Ok(manager.get_watched_paths()))
}
/// 获取文件树(简化版,供文件浏览器使用)
#[tauri::command]
pub async fn get_file_tree(project_path: String) -> Result<Vec<FileNode>, String> {
let path = Path::new(&project_path);
if !path.exists() {
return Err(format!("Path does not exist: {}", path.display()));
}
let ignore_patterns = vec![
String::from("node_modules"),
String::from(".git"),
String::from("target"),
String::from("dist"),
String::from("build"),
String::from(".idea"),
String::from(".vscode"),
String::from("__pycache__"),
String::from(".DS_Store"),
];
// 增加最大深度为 10以支持更深的文件夹结构
let root_node =
read_directory_recursive(path, 0, 10, &ignore_patterns).map_err(|e| e.to_string())?;
// Return children of root node if it has any
Ok(root_node.children.unwrap_or_default())
}