Skip to main content

dk_protocol/
file_list.rs

1use tonic::{Response, Status};
2use tracing::info;
3
4use crate::server::ProtocolServer;
5use crate::validation::validate_file_path;
6use crate::{FileEntry, FileListRequest, FileListResponse};
7
8/// Handle a FileList RPC.
9///
10/// Lists files visible in the session workspace, optionally filtered to
11/// only modified files or by a path prefix.
12pub async fn handle_file_list(
13    server: &ProtocolServer,
14    req: FileListRequest,
15) -> Result<Response<FileListResponse>, Status> {
16    // Validate prefix if provided
17    if let Some(ref prefix) = req.prefix {
18        if !prefix.is_empty() {
19            validate_file_path(prefix)?;
20        }
21    }
22
23    let session = server.validate_session(&req.session_id)?;
24
25    let sid = req
26        .session_id
27        .parse::<uuid::Uuid>()
28        .map_err(|_| Status::invalid_argument("Invalid session ID"))?;
29    server.session_mgr().touch_session(&sid);
30
31    let engine = server.engine();
32
33    // Get workspace for this session
34    let ws = engine
35        .workspace_manager()
36        .get_workspace(&sid)
37        .ok_or_else(|| Status::not_found("Workspace not found for session"))?;
38
39    // Get git repo for base-tree listing
40    let (_repo_id, git_repo) = engine
41        .get_repo(&session.codebase)
42        .await
43        .map_err(|e| Status::internal(format!("Repo error: {e}")))?;
44
45    // Push prefix filter into list_files so the base tree traversal
46    // only collects matching entries instead of the entire tree.
47    let prefix = req.prefix.as_deref().filter(|p| !p.is_empty());
48
49    let all_files = ws
50        .list_files(&git_repo, req.only_modified, prefix)
51        .map_err(|e| Status::internal(format!("List files failed: {e}")))?;
52
53    // Collect modified file paths for O(1) lookup (list_paths avoids cloning content)
54    let modified_paths: std::collections::HashSet<String> =
55        ws.overlay.list_paths().into_iter().collect();
56
57    // Look up the repo_id from the workspace so we can query cross-session info.
58    let repo_id = ws.repo_id;
59    let wm = engine.workspace_manager();
60
61    let files: Vec<FileEntry> = all_files
62        .into_iter()
63        .map(|path| {
64            let modified = modified_paths.contains(&path);
65            let modified_by_other = wm.describe_other_modifiers(&path, repo_id, sid);
66            FileEntry {
67                path,
68                modified_in_session: modified,
69                modified_by_other,
70            }
71        })
72        .collect();
73
74    info!(
75        session_id = %req.session_id,
76        file_count = files.len(),
77        only_modified = req.only_modified,
78        "FILE_LIST: served"
79    );
80
81    Ok(Response::new(FileListResponse { files }))
82}