Improve local filetree view state and sidebar performance
This commit is contained in:
@@ -19,6 +19,10 @@ use core_protocol::{KernelGraphDirection, KernelProjectionKind};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
#[cfg(test)]
|
||||
static LOCAL_FOLDER_PROJECTION_TEST_BLOCK_MS: std::sync::atomic::AtomicU64 =
|
||||
std::sync::atomic::AtomicU64::new(0);
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KernelProjectionQuery {
|
||||
@@ -89,31 +93,39 @@ async fn project_projection(
|
||||
})?;
|
||||
ensure_local_workspace_read_access_with_state(&state, &context, root_uri)
|
||||
.map_err(|error| error.with_context(&context))?;
|
||||
let snapshot = if projection == KernelProjectionKind::FileTree {
|
||||
if let Some(parent_relative_path) = query
|
||||
.parent_relative_path
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
load_local_folder_file_tree_children_snapshot(root_uri, parent_relative_path)?
|
||||
} else if query
|
||||
.root_node_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some()
|
||||
{
|
||||
load_local_folder_file_tree_snapshot_with_reveal(
|
||||
root_uri,
|
||||
query.root_node_id.as_deref(),
|
||||
)?
|
||||
let root_uri = root_uri.to_string();
|
||||
let parent_relative_path = query
|
||||
.parent_relative_path
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned);
|
||||
let root_node_id = query
|
||||
.root_node_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned);
|
||||
let snapshot = tokio::task::spawn_blocking(move || {
|
||||
#[cfg(test)]
|
||||
block_local_folder_projection_for_test();
|
||||
if projection == KernelProjectionKind::FileTree {
|
||||
if let Some(parent_relative_path) = parent_relative_path.as_deref() {
|
||||
load_local_folder_file_tree_children_snapshot(&root_uri, parent_relative_path)
|
||||
} else if root_node_id.is_some() {
|
||||
load_local_folder_file_tree_snapshot_with_reveal(
|
||||
&root_uri,
|
||||
root_node_id.as_deref(),
|
||||
)
|
||||
} else {
|
||||
load_local_folder_file_tree_snapshot(&root_uri)
|
||||
}
|
||||
} else {
|
||||
load_local_folder_file_tree_snapshot(root_uri)?
|
||||
load_local_folder_page_tree_snapshot(&root_uri)
|
||||
}
|
||||
} else {
|
||||
load_local_folder_page_tree_snapshot(root_uri)?
|
||||
};
|
||||
})
|
||||
.await
|
||||
.map_err(|error| WebError::internal(format!("本地树 projection 构建任务失败: {error}")))??;
|
||||
return Ok(ok_response(&context, snapshot.projection));
|
||||
}
|
||||
|
||||
@@ -137,6 +149,14 @@ async fn project_projection(
|
||||
Ok(ok_response(&context, snapshot.projection))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn block_local_folder_projection_for_test() {
|
||||
let delay_ms = LOCAL_FOLDER_PROJECTION_TEST_BLOCK_MS.load(std::sync::atomic::Ordering::SeqCst);
|
||||
if delay_ms > 0 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(delay_ms));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn project_tree_sidebar(
|
||||
state: State<AppState>,
|
||||
context: Extension<RequestContext>,
|
||||
@@ -245,6 +265,7 @@ mod tests {
|
||||
use axum::body::{to_bytes, Body};
|
||||
use axum::http::{Request, StatusCode};
|
||||
use serde_json::Value;
|
||||
use std::time::{Duration, Instant};
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
fn app() -> axum::Router {
|
||||
@@ -443,6 +464,70 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn local_folder_file_projection_does_not_block_leptos_runtime_asset_request() {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"mnote-local-kernel-projection-runtime-asset-{}",
|
||||
std::process::id()
|
||||
));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
std::fs::create_dir_all(root.join("docs")).expect("create docs");
|
||||
std::fs::write(root.join("docs").join("README.md"), "# README\n").expect("write readme");
|
||||
let root_uri = format!("file://{}", root.display());
|
||||
initialize_local_workspace_for_actor("dev-user", &root_uri).expect("init local workspace");
|
||||
let app = app();
|
||||
let projection_app = app.clone();
|
||||
let projection_uri = format!(
|
||||
"/api/tree/projections/file/children?workspaceId=local-ws:dev-user:my-space&sourceKind=local_folder&rootUri={root_uri}&parentRelativePath=docs"
|
||||
);
|
||||
super::LOCAL_FOLDER_PROJECTION_TEST_BLOCK_MS
|
||||
.store(400, std::sync::atomic::Ordering::SeqCst);
|
||||
struct ResetLocalProjectionBlock;
|
||||
impl Drop for ResetLocalProjectionBlock {
|
||||
fn drop(&mut self) {
|
||||
super::LOCAL_FOLDER_PROJECTION_TEST_BLOCK_MS
|
||||
.store(0, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
let _reset_block = ResetLocalProjectionBlock;
|
||||
let projection_task = tokio::spawn(async move {
|
||||
projection_app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri(projection_uri)
|
||||
.header("x-mnote-actor-id", "dev-user")
|
||||
.header("x-mnote-actor-type", "user")
|
||||
.body(Body::empty())
|
||||
.expect("projection request"),
|
||||
)
|
||||
.await
|
||||
.expect("projection response")
|
||||
});
|
||||
|
||||
let started = Instant::now();
|
||||
tokio::task::yield_now().await;
|
||||
let asset_response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/leptos-tiptap-runtime/mnote-leptos-tiptap-spike-island.js")
|
||||
.body(Body::empty())
|
||||
.expect("asset request"),
|
||||
)
|
||||
.await
|
||||
.expect("asset response");
|
||||
let asset_elapsed = started.elapsed();
|
||||
let projection_response = projection_task.await.expect("projection task");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
|
||||
assert_eq!(asset_response.status(), StatusCode::OK);
|
||||
assert_eq!(projection_response.status(), StatusCode::OK);
|
||||
assert!(
|
||||
asset_elapsed < Duration::from_millis(150),
|
||||
"leptos-tiptap runtime asset 不应被本地 FileTree projection 扫描阻塞,实际等待 {asset_elapsed:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tree_projection_routes_keep_ok_response_shape() {
|
||||
let response = app()
|
||||
|
||||
Reference in New Issue
Block a user