use std::path::PathBuf;
use std::sync::Arc;
use serde::Deserialize;
use serde_json::Value;
use sqry_core::graph::unified::GraphMemorySize;
use sqry_core::project::ProjectRootMode;
use sqry_daemon_protocol::{LoadResult, LogicalWorkspaceWire};
use crate::config::{DaemonConfig, WORKING_SET_MULTIPLIER};
use crate::error::DaemonError;
use crate::workspace::{WorkspaceKey, WorkspaceState};
use super::super::path_policy::resolve_index_root;
use super::super::protocol::{ResponseEnvelope, ResponseMeta};
use super::{ConnectionState, HandlerContext, MethodError, format_panic_payload};
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LoadParams {
pub index_root: PathBuf,
#[serde(default)]
pub root_mode: Option<ProjectRootMode>,
#[serde(default)]
pub config_fingerprint: Option<u64>,
#[serde(default)]
pub logical_workspace: Option<LogicalWorkspaceWire>,
}
const INITIAL_WORKING_SET_BYTES: u64 = 2 * 1024 * 1024;
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn working_set_estimate_for_initial(_cfg: &DaemonConfig) -> u64 {
#[allow(clippy::cast_precision_loss)]
let scaled = (INITIAL_WORKING_SET_BYTES as f64) * WORKING_SET_MULTIPLIER;
scaled as u64
}
pub(crate) async fn handle(
ctx: &HandlerContext,
conn: &ConnectionState,
params: Value,
) -> Result<Value, MethodError> {
let params: LoadParams = serde_json::from_value(params).map_err(MethodError::InvalidParams)?;
let canonical_root = resolve_index_root(¶ms.index_root)?;
let root_mode = params.root_mode.unwrap_or_default();
let config_fingerprint = params.config_fingerprint.unwrap_or(0);
let effective_logical_workspace: Option<&LogicalWorkspaceWire> = params
.logical_workspace
.as_ref()
.or(conn.connection_logical_workspace.as_ref());
let effective_fingerprint_for = |path: &PathBuf| -> u64 {
if let Some(lw) = effective_logical_workspace {
for binding in &lw.source_root_bindings {
if binding.path == *path && binding.config_fingerprint != 0 {
return binding.config_fingerprint;
}
}
if lw.workspace_config_fingerprint != 0 {
return lw.workspace_config_fingerprint;
}
}
config_fingerprint
};
let primary_fp = effective_fingerprint_for(&canonical_root);
let primary_key = match effective_logical_workspace {
Some(lw) => WorkspaceKey::with_workspace_id(
lw.workspace_id,
canonical_root.clone(),
root_mode,
primary_fp,
),
None => WorkspaceKey::new(canonical_root.clone(), root_mode, primary_fp),
};
let mut keys: Vec<WorkspaceKey> = vec![primary_key.clone()];
if let Some(lw) = effective_logical_workspace {
for sr in &lw.source_roots {
let canon = resolve_index_root(sr)?;
if canon == canonical_root {
continue; }
let fp = effective_fingerprint_for(&canon);
keys.push(WorkspaceKey::with_workspace_id(
lw.workspace_id,
canon,
root_mode,
fp,
));
}
}
let mut primary_graph_arc: Option<Arc<sqry_core::graph::CodeGraph>> = None;
for key in &keys {
let manager = Arc::clone(&ctx.manager);
let builder = Arc::clone(&ctx.workspace_builder);
let estimate = working_set_estimate_for_initial(&ctx.config);
let key_for_task = key.clone();
let key_for_diag = key.clone();
let graph = match tokio::task::spawn_blocking(move || {
manager.get_or_load(&key_for_task, &*builder, estimate)
})
.await
{
Ok(Ok(graph)) => graph,
Ok(Err(daemon_err)) => return Err(MethodError::Daemon(daemon_err)),
Err(join_err) if join_err.is_panic() => {
let reason = format_panic_payload(join_err);
return Err(MethodError::Daemon(DaemonError::WorkspaceBuildFailed {
root: key_for_diag.source_root.clone(),
reason: format!("workspace builder panicked: {reason}"),
}));
}
Err(join_err) => return Err(MethodError::JoinError(join_err)),
};
if *key == primary_key {
primary_graph_arc = Some(graph);
}
}
let primary_graph = primary_graph_arc
.expect("primary_key is always present in `keys` so get_or_load runs at least once for it");
let current_bytes = primary_graph.heap_bytes() as u64;
let envelope = ResponseEnvelope {
result: LoadResult {
root: primary_key.source_root.clone(),
current_bytes,
state: WorkspaceState::Loaded,
},
meta: ResponseMeta::loaded(ctx.daemon_version),
};
serde_json::to_value(&envelope)
.map_err(|e| MethodError::Internal(anyhow::anyhow!("serialise daemon/load: {e}")))
}