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    crate::require_live_session::require_live_session(server, &req.session_id).await?;
25
26    let sid = req
27        .session_id
28        .parse::<uuid::Uuid>()
29        .map_err(|_| Status::invalid_argument("Invalid session ID"))?;
30    server.session_mgr().touch_session(&sid);
31
32    let engine = server.engine();
33
34    // Get workspace for this session
35    let ws = engine
36        .workspace_manager()
37        .get_workspace(&sid)
38        .ok_or_else(|| Status::not_found("Workspace not found for session"))?;
39
40    // Get git repo for base-tree listing
41    let (_repo_id, git_repo) = engine
42        .get_repo(&session.codebase)
43        .await
44        .map_err(|e| Status::internal(format!("Repo error: {e}")))?;
45
46    // Push prefix filter into list_files so the base tree traversal
47    // only collects matching entries instead of the entire tree.
48    let prefix = req.prefix.as_deref().filter(|p| !p.is_empty());
49
50    let all_files = ws
51        .list_files(&git_repo, req.only_modified, prefix)
52        .map_err(|e| Status::internal(format!("List files failed: {e}")))?;
53
54    // Collect modified file paths for O(1) lookup (list_paths avoids cloning content)
55    let modified_paths: std::collections::HashSet<String> =
56        ws.overlay.list_paths().into_iter().collect();
57
58    // Look up the repo_id from the workspace so we can query cross-session info.
59    let repo_id = ws.repo_id;
60    let wm = engine.workspace_manager();
61
62    let files: Vec<FileEntry> = all_files
63        .into_iter()
64        .map(|path| {
65            let modified = modified_paths.contains(&path);
66            let modified_by_other = wm.describe_other_modifiers(&path, repo_id, sid);
67            FileEntry {
68                path,
69                modified_in_session: modified,
70                modified_by_other,
71            }
72        })
73        .collect();
74
75    info!(
76        session_id = %req.session_id,
77        file_count = files.len(),
78        only_modified = req.only_modified,
79        "FILE_LIST: served"
80    );
81
82    Ok(Response::new(FileListResponse { files }))
83}