use arcbox_connect::sandbox_v1 as pb;
use arcbox_connect::sandbox_v1::{FileChunk, write_file_request};
use buffa_types::google::protobuf::Empty;
use connectrpc::{
ConnectError, InboundStream, RequestContext, Response, ServiceRequest, ServiceResult,
ServiceStream,
};
use tokio_stream::StreamExt as _;
use tokio_stream::wrappers::ReceiverStream;
use arcbox_core::WriteFileChunk;
use super::SharedRuntime;
use crate::ApiError;
use super::{ConnectRuntimeExt as _, ContextExt as _, with_keepalive};
pub struct SandboxFilesystemServiceImpl {
runtime: SharedRuntime,
}
impl SandboxFilesystemServiceImpl {
#[must_use]
pub fn new(runtime: SharedRuntime) -> Self {
Self { runtime }
}
}
#[allow(
refining_impl_trait,
reason = "the trait returns `impl Encodable<M>`; naming the concrete body \
type is strictly more informative and these impls are registered on a \
Router rather than named by callers"
)]
impl pb::SandboxFilesystemService for SandboxFilesystemServiceImpl {
async fn read_file(
&self,
ctx: RequestContext,
request: ServiceRequest<'_, pb::ReadFileRequest>,
) -> ServiceResult<ServiceStream<FileChunk>> {
let machine = ctx.sandbox_machine_id()?;
let agent = self
.runtime
.ready()?
.get_agent(&machine)
.map_err(ApiError::from)?;
let rx = agent
.sandbox_read_file(request.to_owned_message())
.await
.map_err(ApiError::from)?;
let stream =
ReceiverStream::new(rx).map(|r| r.map_err(|e| ConnectError::from(ApiError::from(e))));
let stream = with_keepalive(stream, FileChunk::default);
Response::ok(Box::pin(stream))
}
async fn write_file(
&self,
ctx: RequestContext,
mut requests: InboundStream<pb::WriteFileRequest>,
) -> ServiceResult<Empty> {
let machine = ctx.sandbox_machine_id()?;
let agent = self
.runtime
.ready()?
.get_agent(&machine)
.map_err(ApiError::from)?;
let first = requests.next().await.ok_or_else(|| {
ConnectError::invalid_argument("write_file: stream closed before Open message")
})??;
let open = match first.to_owned_message().payload {
Some(write_file_request::Payload::Open(open)) => *open,
_ => {
return Err(ConnectError::invalid_argument(
"write_file: first message must be Open",
));
}
};
let (tx, rx) = tokio::sync::mpsc::channel(16);
tokio::spawn(async move {
loop {
match requests.next().await {
Some(Ok(item)) => {
if let Some(write_file_request::Payload::Chunk(chunk)) =
item.to_owned_message().payload
{
let done = chunk.done;
if !chunk.data.is_empty()
&& tx.send(WriteFileChunk::Data(chunk.data)).await.is_err()
{
return;
}
if done {
return; }
}
}
Some(Err(_)) | None => {
let _ = tx.send(WriteFileChunk::Abort).await;
return;
}
}
}
});
agent
.sandbox_write_file(open, rx)
.await
.map_err(ApiError::from)?;
Response::ok(Empty::default())
}
async fn stat(
&self,
_ctx: RequestContext,
_request: ServiceRequest<'_, pb::StatFileRequest>,
) -> ServiceResult<pb::FileStat> {
Err(ConnectError::unimplemented(
"stat is not implemented yet (CORE-62)",
))
}
async fn list_dir(
&self,
_ctx: RequestContext,
_request: ServiceRequest<'_, pb::ListDirRequest>,
) -> ServiceResult<pb::ListDirResponse> {
Err(ConnectError::unimplemented(
"list_dir is not implemented yet (CORE-62)",
))
}
async fn make_dir(
&self,
_ctx: RequestContext,
_request: ServiceRequest<'_, pb::MakeDirRequest>,
) -> ServiceResult<Empty> {
Err(ConnectError::unimplemented(
"make_dir is not implemented yet (CORE-62)",
))
}
async fn remove(
&self,
_ctx: RequestContext,
_request: ServiceRequest<'_, pb::RemoveEntryRequest>,
) -> ServiceResult<Empty> {
Err(ConnectError::unimplemented(
"remove is not implemented yet (CORE-62)",
))
}
async fn r#move(
&self,
_ctx: RequestContext,
_request: ServiceRequest<'_, pb::MoveEntryRequest>,
) -> ServiceResult<Empty> {
Err(ConnectError::unimplemented(
"move is not implemented yet (CORE-62)",
))
}
async fn watch_dir(
&self,
_ctx: RequestContext,
_request: ServiceRequest<'_, pb::WatchDirRequest>,
) -> ServiceResult<ServiceStream<pb::WatchDirResponse>> {
Err(ConnectError::unimplemented(
"watch_dir is not implemented yet (CORE-62)",
))
}
}