use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde_json::{Value, json};
use super::BridgeState;
use crate::bridge_protocol::{
CreateDirectoryBookmarkRequest, DirectoryEntry, DirectoryListing, ReadDirectoryRequest,
RemoveDirectoryBookmarkRequest,
};
use crate::config::expand_path;
use crate::directory::{canonicalize_directory, parent_directory};
use crate::storage::Storage;
const DIRECTORY_HISTORY_LIMIT: usize = 20;
pub(super) fn seed_directory_bookmarks(
storage: &Storage,
configured_bookmarks: &[PathBuf],
) -> Result<()> {
for path in configured_bookmarks {
storage.upsert_directory_bookmark(path, None)?;
}
Ok(())
}
impl BridgeState {
pub(super) fn directory_state_snapshot(
&self,
) -> Result<(
Vec<crate::bridge_protocol::DirectoryBookmarkRecord>,
Vec<crate::bridge_protocol::DirectoryHistoryRecord>,
)> {
Ok((
self.storage.list_directory_bookmarks()?,
self.storage
.list_directory_history(DIRECTORY_HISTORY_LIMIT)?,
))
}
pub(super) fn emit_directory_state(&self) -> Result<()> {
let (directory_bookmarks, directory_history) = self.directory_state_snapshot()?;
self.emit_event(
"directory_state",
None,
None,
json!({
"directoryBookmarks": directory_bookmarks,
"directoryHistory": directory_history,
}),
)
}
pub(super) async fn create_directory_bookmark(
&self,
request: CreateDirectoryBookmarkRequest,
) -> Result<Value> {
let path = expand_path(Path::new(&request.path))?;
let bookmark = self
.storage
.upsert_directory_bookmark(&path, request.display_name.as_deref())?;
let (directory_bookmarks, directory_history) = self.directory_state_snapshot()?;
self.emit_directory_state()?;
Ok(json!({
"bookmark": bookmark,
"directoryBookmarks": directory_bookmarks,
"directoryHistory": directory_history,
}))
}
pub(super) async fn remove_directory_bookmark(
&self,
request: RemoveDirectoryBookmarkRequest,
) -> Result<Value> {
let path = expand_path(Path::new(&request.path))?;
self.storage.remove_directory_bookmark(&path)?;
let (directory_bookmarks, directory_history) = self.directory_state_snapshot()?;
self.emit_directory_state()?;
Ok(json!({
"removed": true,
"directoryBookmarks": directory_bookmarks,
"directoryHistory": directory_history,
}))
}
pub(super) async fn read_directory(&self, request: ReadDirectoryRequest) -> Result<Value> {
let runtime = self.require_runtime(request.runtime_id.as_deref()).await?;
let runtime_id = runtime.record.runtime_id.clone();
let path = canonicalize_directory(&expand_path(Path::new(&request.path))?)?;
let response = runtime
.app_server
.request(
"fs/readDirectory",
json!({
"path": path.to_string_lossy().to_string(),
}),
)
.await?;
let raw_entries = response
.get("entries")
.and_then(Value::as_array)
.context("fs/readDirectory 返回格式不正确")?;
let mut entries = raw_entries
.iter()
.filter(|entry| {
entry
.get("isDirectory")
.and_then(Value::as_bool)
.unwrap_or(false)
})
.filter_map(|entry| {
let name = entry.get("fileName").and_then(Value::as_str)?.trim();
if name.is_empty() {
return None;
}
Some(DirectoryEntry {
name: name.to_string(),
path: path.join(name).to_string_lossy().to_string(),
is_directory: true,
})
})
.collect::<Vec<_>>();
entries.sort_by(|left, right| {
left.name
.to_ascii_lowercase()
.cmp(&right.name.to_ascii_lowercase())
.then_with(|| left.name.cmp(&right.name))
});
let listing = DirectoryListing {
path: path.to_string_lossy().to_string(),
parent_path: parent_directory(&path).map(|parent| parent.to_string_lossy().to_string()),
entries,
};
Ok(json!({
"runtimeId": runtime_id,
"listing": listing,
}))
}
}