From d730e8d2350a014a410f390948ff5020f82dd5af Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Thu, 28 Aug 2025 17:36:18 -0300 Subject: [PATCH] feat: add stream parameter support for item content responses Co-authored-by: aider (openai/andrew/openrouter/deepseek/deepseek-chat-v3.1) --- src/modes/server/api/item.rs | 62 ++++++++++++++++++++++++++++-------- src/modes/server/common.rs | 4 +++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/modes/server/api/item.rs b/src/modes/server/api/item.rs index 14968ae..c5b0c2f 100644 --- a/src/modes/server/api/item.rs +++ b/src/modes/server/api/item.rs @@ -243,7 +243,7 @@ pub async fn handle_get_item_content( state.cmd.clone(), state.settings.clone() ); - stream_item_content_response(&item_service, item_id, params.allow_binary, params.offset, params.length).await + stream_item_content_response(&item_service, item_id, params.allow_binary, params.offset, params.length, params.stream).await } async fn stream_item_content_response( @@ -252,6 +252,7 @@ async fn stream_item_content_response( allow_binary: bool, offset: u64, length: u64, + stream: bool, ) -> Result { // Get the item with metadata once let item_with_meta = item_service.get_item(item_id).await.map_err(|e| { @@ -260,7 +261,7 @@ async fn stream_item_content_response( })?; let metadata = item_with_meta.meta_as_map(); - stream_item_content_response_with_metadata(item_service, item_id, &metadata, allow_binary, offset, length).await + stream_item_content_response_with_metadata(item_service, item_id, &metadata, allow_binary, offset, length, stream).await } async fn stream_item_content_response_with_metadata( @@ -270,6 +271,7 @@ async fn stream_item_content_response_with_metadata( allow_binary: bool, offset: u64, length: u64, + stream: bool, ) -> Result { let mime_type = metadata .get("mime_type") @@ -296,19 +298,51 @@ async fn stream_item_content_response_with_metadata( } } - // Now stream the content with the specified offset and length using pre-fetched metadata - match item_service.stream_item_content_by_id_with_metadata(item_id, metadata, true, offset, length).await { - Ok((stream, _)) => { - let body = axum::body::Body::from_stream(stream); - let response = Response::builder() - .header(header::CONTENT_TYPE, mime_type) - .body(body) - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - Ok(response) + if stream { + // Stream the content + match item_service.stream_item_content_by_id_with_metadata(item_id, metadata, true, offset, length).await { + Ok((stream, _)) => { + let body = axum::body::Body::from_stream(stream); + let response = Response::builder() + .header(header::CONTENT_TYPE, mime_type) + .body(body) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + Ok(response) + } + Err(e) => { + warn!("Failed to stream content for item {}: {}", item_id, e); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } } - Err(e) => { - warn!("Failed to stream content for item {}: {}", item_id, e); - Err(StatusCode::INTERNAL_SERVER_ERROR) + } else { + // Build the full response in memory + match item_service.get_item_content_info(item_id).await { + Ok((content, _, _)) => { + // Apply offset and length + let content_len = content.len() as u64; + let start = std::cmp::min(offset, content_len); + let end = if length > 0 { + std::cmp::min(start + length, content_len) + } else { + content_len + }; + + let response_content = if start < content_len { + &content[start as usize..end as usize] + } else { + &[] + }; + + let response = Response::builder() + .header(header::CONTENT_TYPE, mime_type) + .body(axum::body::Body::from(response_content.to_vec())) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + Ok(response) + } + Err(e) => { + warn!("Failed to get content for item {}: {}", item_id, e); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } } } } diff --git a/src/modes/server/common.rs b/src/modes/server/common.rs index f379b58..db3af4f 100644 --- a/src/modes/server/common.rs +++ b/src/modes/server/common.rs @@ -128,6 +128,8 @@ pub struct ItemQuery { pub offset: u64, #[serde(default)] pub length: u64, + #[serde(default = "default_stream")] + pub stream: bool, } #[derive(Debug, Deserialize, utoipa::ToSchema)] @@ -139,6 +141,8 @@ pub struct ItemContentQuery { pub offset: u64, #[serde(default)] pub length: u64, + #[serde(default = "default_stream")] + pub stream: bool, } fn check_bearer_auth(auth_str: &str, expected_password: &str, expected_hash: &Option) -> bool {