| use crate::error_code::internal_error; |
| use crate::error_code::invalid_request; |
| use crate::fs_watch::FsWatchManager; |
| use crate::outgoing_message::ConnectionId; |
| use base64::Engine; |
| use base64::engine::general_purpose::STANDARD; |
| use codex_app_server_protocol::FsCopyParams; |
| use codex_app_server_protocol::FsCopyResponse; |
| use codex_app_server_protocol::FsCreateDirectoryParams; |
| use codex_app_server_protocol::FsCreateDirectoryResponse; |
| use codex_app_server_protocol::FsGetMetadataParams; |
| use codex_app_server_protocol::FsGetMetadataResponse; |
| use codex_app_server_protocol::FsReadDirectoryEntry; |
| use codex_app_server_protocol::FsReadDirectoryParams; |
| use codex_app_server_protocol::FsReadDirectoryResponse; |
| use codex_app_server_protocol::FsReadFileParams; |
| use codex_app_server_protocol::FsReadFileResponse; |
| use codex_app_server_protocol::FsRemoveParams; |
| use codex_app_server_protocol::FsRemoveResponse; |
| use codex_app_server_protocol::FsUnwatchParams; |
| use codex_app_server_protocol::FsUnwatchResponse; |
| use codex_app_server_protocol::FsWatchParams; |
| use codex_app_server_protocol::FsWatchResponse; |
| use codex_app_server_protocol::FsWriteFileParams; |
| use codex_app_server_protocol::FsWriteFileResponse; |
| use codex_app_server_protocol::JSONRPCErrorError; |
| use codex_exec_server::CopyOptions; |
| use codex_exec_server::CreateDirectoryOptions; |
| use codex_exec_server::EnvironmentManager; |
| use codex_exec_server::ExecutorFileSystem; |
| use codex_exec_server::RemoveOptions; |
| use codex_utils_path_uri::PathUri; |
| use std::io; |
| use std::sync::Arc; |
|
|
| #[derive(Clone)] |
| pub(crate) struct FsRequestProcessor { |
| environment_manager: Arc<EnvironmentManager>, |
| fs_watch_manager: FsWatchManager, |
| } |
|
|
| impl FsRequestProcessor { |
| pub(crate) fn new( |
| environment_manager: Arc<EnvironmentManager>, |
| fs_watch_manager: FsWatchManager, |
| ) -> Self { |
| Self { |
| environment_manager, |
| fs_watch_manager, |
| } |
| } |
|
|
| fn file_system(&self) -> Result<Arc<dyn ExecutorFileSystem>, JSONRPCErrorError> { |
| self.environment_manager |
| .try_local_environment() |
| .map(|environment| environment.get_filesystem()) |
| .ok_or_else(|| internal_error("local filesystem is not configured")) |
| } |
|
|
| pub(crate) async fn connection_closed(&self, connection_id: ConnectionId) { |
| self.fs_watch_manager.connection_closed(connection_id).await; |
| } |
|
|
| pub(crate) async fn read_file( |
| &self, |
| params: FsReadFileParams, |
| ) -> Result<FsReadFileResponse, JSONRPCErrorError> { |
| let path = PathUri::from_abs_path(¶ms.path); |
| let bytes = self |
| .file_system()? |
| .read_file(&path, Default::default(), None) |
| .await |
| .map_err(map_fs_error)?; |
| Ok(FsReadFileResponse { |
| data_base64: STANDARD.encode(bytes), |
| }) |
| } |
|
|
| pub(crate) async fn write_file( |
| &self, |
| params: FsWriteFileParams, |
| ) -> Result<FsWriteFileResponse, JSONRPCErrorError> { |
| let bytes = STANDARD.decode(params.data_base64).map_err(|err| { |
| invalid_request(format!( |
| "fs/writeFile requires valid base64 dataBase64: {err}" |
| )) |
| })?; |
| let path = PathUri::from_abs_path(¶ms.path); |
| self.file_system()? |
| .write_file(&path, bytes, Default::default(), None) |
| .await |
| .map_err(map_fs_error)?; |
| Ok(FsWriteFileResponse {}) |
| } |
|
|
| pub(crate) async fn create_directory( |
| &self, |
| params: FsCreateDirectoryParams, |
| ) -> Result<FsCreateDirectoryResponse, JSONRPCErrorError> { |
| let path = PathUri::from_abs_path(¶ms.path); |
| self.file_system()? |
| .create_directory( |
| &path, |
| CreateDirectoryOptions { |
| recursive: params.recursive.unwrap_or(true), |
| follow_symlinks: true, |
| }, |
| None, |
| ) |
| .await |
| .map_err(map_fs_error)?; |
| Ok(FsCreateDirectoryResponse {}) |
| } |
|
|
| pub(crate) async fn get_metadata( |
| &self, |
| params: FsGetMetadataParams, |
| ) -> Result<FsGetMetadataResponse, JSONRPCErrorError> { |
| let path = PathUri::from_abs_path(¶ms.path); |
| let metadata = self |
| .file_system()? |
| .get_metadata(&path, Default::default(), None) |
| .await |
| .map_err(map_fs_error)?; |
| Ok(FsGetMetadataResponse { |
| is_directory: metadata.is_directory, |
| is_file: metadata.is_file, |
| is_symlink: metadata.is_symlink, |
| created_at_ms: metadata.created_at_ms, |
| modified_at_ms: metadata.modified_at_ms, |
| }) |
| } |
|
|
| pub(crate) async fn read_directory( |
| &self, |
| params: FsReadDirectoryParams, |
| ) -> Result<FsReadDirectoryResponse, JSONRPCErrorError> { |
| let path = PathUri::from_abs_path(¶ms.path); |
| let entries = self |
| .file_system()? |
| .read_directory(&path, None) |
| .await |
| .map_err(map_fs_error)?; |
| Ok(FsReadDirectoryResponse { |
| entries: entries |
| .into_iter() |
| .map(|entry| FsReadDirectoryEntry { |
| file_name: entry.file_name, |
| is_directory: entry.is_directory, |
| is_file: entry.is_file, |
| }) |
| .collect(), |
| }) |
| } |
|
|
| pub(crate) async fn remove( |
| &self, |
| params: FsRemoveParams, |
| ) -> Result<FsRemoveResponse, JSONRPCErrorError> { |
| let path = PathUri::from_abs_path(¶ms.path); |
| self.file_system()? |
| .remove( |
| &path, |
| RemoveOptions { |
| recursive: params.recursive.unwrap_or(true), |
| force: params.force.unwrap_or(true), |
| follow_symlinks: true, |
| }, |
| None, |
| ) |
| .await |
| .map_err(map_fs_error)?; |
| Ok(FsRemoveResponse {}) |
| } |
|
|
| pub(crate) async fn copy( |
| &self, |
| params: FsCopyParams, |
| ) -> Result<FsCopyResponse, JSONRPCErrorError> { |
| let source_path = PathUri::from_abs_path(¶ms.source_path); |
| let destination_path = PathUri::from_abs_path(¶ms.destination_path); |
| self.file_system()? |
| .copy( |
| &source_path, |
| &destination_path, |
| CopyOptions { |
| recursive: params.recursive, |
| }, |
| None, |
| ) |
| .await |
| .map_err(map_fs_error)?; |
| Ok(FsCopyResponse {}) |
| } |
|
|
| pub(crate) async fn watch( |
| &self, |
| connection_id: ConnectionId, |
| params: FsWatchParams, |
| ) -> Result<FsWatchResponse, JSONRPCErrorError> { |
| self.file_system()?; |
| self.fs_watch_manager.watch(connection_id, params).await |
| } |
|
|
| pub(crate) async fn unwatch( |
| &self, |
| connection_id: ConnectionId, |
| params: FsUnwatchParams, |
| ) -> Result<FsUnwatchResponse, JSONRPCErrorError> { |
| self.file_system()?; |
| self.fs_watch_manager.unwatch(connection_id, params).await |
| } |
| } |
|
|
| fn map_fs_error(err: io::Error) -> JSONRPCErrorError { |
| if err.kind() == io::ErrorKind::InvalidInput { |
| invalid_request(err.to_string()) |
| } else { |
| internal_error(err.to_string()) |
| } |
| } |
|
|