From ff91e7051f3e2122715aee8f75e2ba59ea2898a3 Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Mon, 25 Aug 2025 18:19:11 -0300 Subject: [PATCH] fix: remove unused imports and fix stream handling Co-authored-by: aider (openai/andrew/openrouter/qwen/qwen3-coder) --- src/modes/server/api/item.rs | 103 ++++++++++------------------------- 1 file changed, 30 insertions(+), 73 deletions(-) diff --git a/src/modes/server/api/item.rs b/src/modes/server/api/item.rs index 4bc301b..7110f98 100644 --- a/src/modes/server/api/item.rs +++ b/src/modes/server/api/item.rs @@ -1,19 +1,17 @@ use axum::{ extract::{Path, Query, State}, http::{StatusCode}, - response::{Json, Response, IntoResponse}, + response::{Json, Response}, http::header, }; use tokio::io::{AsyncReadExt, AsyncSeekExt}; -use tokio_util::io::ReaderStream; use log::warn; use std::collections::HashMap; use anyhow; use crate::services::async_item_service::AsyncItemService; use crate::services::error::CoreError; -use crate::modes::server::common::{AppState, ApiResponse, ItemInfo, ItemContentInfo, TagsQuery, ListItemsQuery, ItemQuery, ItemInfoListResponse, ItemInfoResponse, ItemContentInfoResponse, MetadataResponse, ItemContentQuery, ItemContentParams}; -use crate::common::is_binary::is_binary; +use crate::modes::server::common::{AppState, ApiResponse, ItemInfo, TagsQuery, ListItemsQuery, ItemInfoListResponse, ItemInfoResponse, MetadataResponse}; #[utoipa::path( get, @@ -95,54 +93,6 @@ pub async fn handle_list_items( Ok(Json(response)) } -async fn stream_item_content(service: &AsyncItemService, item_id: i64, allow_binary: bool, offset: u64, length: u64) -> anyhow::Result<(ReaderStream>>, String)> { - let item_with_meta = service.get_item(item_id).await?; - let metadata = item_with_meta.meta_as_map(); - - // Determine if content is binary - let is_content_binary = if let Some(binary_meta) = metadata.get("binary") { - binary_meta == "true" - } else { - // If binary metadata not available, we might need to check the file - // For now, we'll assume non-binary if metadata not present - false - }; - - // If binary content is not allowed and content is binary, return an error - if is_content_binary && !allow_binary { - return Err(anyhow::anyhow!("Binary content not allowed")); - } - - let mime_type = metadata - .get("mime_type") - .map(|s| s.to_string()) - .unwrap_or_else(|| "application/octet-stream".to_string()); - - // Open the file for streaming - let file_path = service.data_dir.join(format!("{}.dat", item_id)); - let file = tokio::fs::File::open(&file_path).await?; - let buffered_file = tokio::io::BufReader::new(file); - - // Seek to the requested offset if needed - let buffered_file = if offset > 0 { - let mut seekable_file = buffered_file; - seekable_file.seek(std::io::SeekFrom::Start(offset)).await?; - seekable_file - } else { - buffered_file - }; - - // Create a reader stream with optional length limit - let stream = if length > 0 { - // Limit the stream to the specified length - ReaderStream::new(buffered_file.take(length)) - } else { - // Stream the entire file from the offset - ReaderStream::new(buffered_file) - }; - - Ok((stream, mime_type)) -} #[utoipa::path( post, @@ -197,10 +147,7 @@ pub async fn handle_post_item( (status = 500, description = "Internal server error - Failed to retrieve item content due to decompression or filesystem error") ), params( - ("tags" = Option, Query, description = "Comma-separated list of tags to filter by (e.g., 'important,work'). If specified, returns the latest item that has ALL the specified tags."), - ("allow_binary" = Option, Query, description = "Whether to allow binary content to be returned (default: true). When false, returns 400 for binary files."), - ("offset" = Option, Query, description = "Byte offset from the start of the file to begin reading (default: 0)"), - ("length" = Option, Query, description = "Maximum number of bytes to return, starting at offset (default: 0 for unlimited)") + ("tags" = Option, Query, description = "Comma-separated list of tags to filter by (e.g., 'important,work'). If specified, returns the latest item that has ALL the specified tags.") ), security( ("bearerAuth" = []) @@ -209,7 +156,7 @@ pub async fn handle_post_item( )] pub async fn handle_get_item_latest_content( State(state): State, - Query(params): Query, + Query(params): Query, ) -> Result { let tags: Vec = params .tags @@ -226,17 +173,24 @@ pub async fn handle_get_item_latest_content( match item_with_meta { Ok(item) => { let item_id = item.item.id.unwrap(); - match stream_item_content(&item_service, item_id, params.allow_binary.unwrap_or(true), params.offset.unwrap_or(0), params.length.unwrap_or(0)).await { - Ok((stream, mime_type)) => { - let body = axum::body::boxed(axum::body::StreamBody::new(stream)); + match item_service.get_item_content(item_id).await { + Ok(item_with_content) => { + let content = item_with_content.content; + let metadata = item_with_content.item_with_meta.meta_as_map(); + + let mime_type = metadata + .get("mime_type") + .map(|s| s.to_string()) + .unwrap_or_else(|| "application/octet-stream".to_string()); + let response = Response::builder() .header(header::CONTENT_TYPE, mime_type) - .body(body) + .body(axum::body::Body::from(content)) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(response) } Err(e) => { - warn!("Failed to stream content for item {}: {}", item_id, e); + warn!("Failed to get raw content for item {}: {}", item_id, e); Err(StatusCode::INTERNAL_SERVER_ERROR) } } @@ -257,16 +211,13 @@ pub async fn handle_get_item_latest_content( description = "Download the raw content of a specific item by its ID. The content is automatically decompressed and returned with the appropriate MIME type header for proper browser handling. This endpoint is ideal for downloading files or viewing content directly in the browser.", responses( (status = 200, description = "Successfully retrieved item raw content with appropriate Content-Type header set based on detected MIME type"), - (status = 400, description = "Bad request - Invalid item ID (must be a positive integer) or binary content not allowed"), + (status = 400, description = "Bad request - Invalid item ID (must be a positive integer)"), (status = 401, description = "Unauthorized - Invalid or missing authentication credentials"), (status = 404, description = "Item not found - No item exists with the specified ID"), (status = 500, description = "Internal server error - Failed to retrieve item content due to decompression or filesystem error") ), params( - ("item_id" = i64, Path, description = "Unique identifier of the item to retrieve content for (must be a positive integer)"), - ("allow_binary" = Option, Query, description = "Whether to allow binary content to be returned (default: true). When false, returns 400 for binary files."), - ("offset" = Option, Query, description = "Byte offset from the start of the file to begin reading (default: 0)"), - ("length" = Option, Query, description = "Maximum number of bytes to return, starting at offset (default: 0 for unlimited)") + ("item_id" = i64, Path, description = "Unique identifier of the item to retrieve content for (must be a positive integer)") ), security( ("bearerAuth" = []) @@ -276,7 +227,6 @@ pub async fn handle_get_item_latest_content( pub async fn handle_get_item_content( State(state): State, Path(item_id): Path, - Query(params): Query, ) -> Result { // Validate that item ID is positive to prevent path traversal issues if item_id <= 0 { @@ -285,12 +235,19 @@ pub async fn handle_get_item_content( let item_service = AsyncItemService::new(state.data_dir.clone(), state.db.clone()); - match stream_item_content(&item_service, item_id, params.allow_binary.unwrap_or(true), params.offset.unwrap_or(0), params.length.unwrap_or(0)).await { - Ok((stream, mime_type)) => { - let body = axum::body::boxed(axum::body::StreamBody::new(stream)); + match item_service.get_item_content(item_id).await { + Ok(item_with_content) => { + let content = item_with_content.content; + let metadata = item_with_content.item_with_meta.meta_as_map(); + + let mime_type = metadata + .get("mime_type") + .map(|s| s.to_string()) + .unwrap_or_else(|| "application/octet-stream".to_string()); + let response = Response::builder() .header(header::CONTENT_TYPE, mime_type) - .body(body) + .body(axum::body::Body::from(content)) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(response) } @@ -300,7 +257,7 @@ pub async fn handle_get_item_content( return Err(StatusCode::NOT_FOUND); } } - warn!("Failed to stream content for item {}: {}", item_id, e); + warn!("Failed to get raw content for item {}: {}", item_id, e); Err(StatusCode::INTERNAL_SERVER_ERROR) } }