fix: remove unused imports and fix stream handling

Co-authored-by: aider (openai/andrew/openrouter/qwen/qwen3-coder) <aider@aider.chat>
This commit is contained in:
Andrew Phillips
2025-08-25 18:19:11 -03:00
parent 97c4e26dbf
commit ff91e7051f

View File

@@ -1,19 +1,17 @@
use axum::{ use axum::{
extract::{Path, Query, State}, extract::{Path, Query, State},
http::{StatusCode}, http::{StatusCode},
response::{Json, Response, IntoResponse}, response::{Json, Response},
http::header, http::header,
}; };
use tokio::io::{AsyncReadExt, AsyncSeekExt}; use tokio::io::{AsyncReadExt, AsyncSeekExt};
use tokio_util::io::ReaderStream;
use log::warn; use log::warn;
use std::collections::HashMap; use std::collections::HashMap;
use anyhow; use anyhow;
use crate::services::async_item_service::AsyncItemService; use crate::services::async_item_service::AsyncItemService;
use crate::services::error::CoreError; 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::modes::server::common::{AppState, ApiResponse, ItemInfo, TagsQuery, ListItemsQuery, ItemInfoListResponse, ItemInfoResponse, MetadataResponse};
use crate::common::is_binary::is_binary;
#[utoipa::path( #[utoipa::path(
get, get,
@@ -95,54 +93,6 @@ pub async fn handle_list_items(
Ok(Json(response)) Ok(Json(response))
} }
async fn stream_item_content(service: &AsyncItemService, item_id: i64, allow_binary: bool, offset: u64, length: u64) -> anyhow::Result<(ReaderStream<tokio::io::Take<tokio::io::BufReader<tokio::fs::File>>>, 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( #[utoipa::path(
post, 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") (status = 500, description = "Internal server error - Failed to retrieve item content due to decompression or filesystem error")
), ),
params( params(
("tags" = Option<String>, 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."), ("tags" = Option<String>, 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<bool>, Query, description = "Whether to allow binary content to be returned (default: true). When false, returns 400 for binary files."),
("offset" = Option<u64>, Query, description = "Byte offset from the start of the file to begin reading (default: 0)"),
("length" = Option<u64>, Query, description = "Maximum number of bytes to return, starting at offset (default: 0 for unlimited)")
), ),
security( security(
("bearerAuth" = []) ("bearerAuth" = [])
@@ -209,7 +156,7 @@ pub async fn handle_post_item(
)] )]
pub async fn handle_get_item_latest_content( pub async fn handle_get_item_latest_content(
State(state): State<AppState>, State(state): State<AppState>,
Query(params): Query<ItemContentQuery>, Query(params): Query<TagsQuery>,
) -> Result<Response, StatusCode> { ) -> Result<Response, StatusCode> {
let tags: Vec<String> = params let tags: Vec<String> = params
.tags .tags
@@ -226,17 +173,24 @@ pub async fn handle_get_item_latest_content(
match item_with_meta { match item_with_meta {
Ok(item) => { Ok(item) => {
let item_id = item.item.id.unwrap(); 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 { match item_service.get_item_content(item_id).await {
Ok((stream, mime_type)) => { Ok(item_with_content) => {
let body = axum::body::boxed(axum::body::StreamBody::new(stream)); 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() let response = Response::builder()
.header(header::CONTENT_TYPE, mime_type) .header(header::CONTENT_TYPE, mime_type)
.body(body) .body(axum::body::Body::from(content))
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(response) Ok(response)
} }
Err(e) => { 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) 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.", 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( responses(
(status = 200, description = "Successfully retrieved item raw content with appropriate Content-Type header set based on detected MIME type"), (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 = 401, description = "Unauthorized - Invalid or missing authentication credentials"),
(status = 404, description = "Item not found - No item exists with the specified ID"), (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") (status = 500, description = "Internal server error - Failed to retrieve item content due to decompression or filesystem error")
), ),
params( params(
("item_id" = i64, Path, description = "Unique identifier of the item to retrieve content for (must be a positive integer)"), ("item_id" = i64, Path, description = "Unique identifier of the item to retrieve content for (must be a positive integer)")
("allow_binary" = Option<bool>, Query, description = "Whether to allow binary content to be returned (default: true). When false, returns 400 for binary files."),
("offset" = Option<u64>, Query, description = "Byte offset from the start of the file to begin reading (default: 0)"),
("length" = Option<u64>, Query, description = "Maximum number of bytes to return, starting at offset (default: 0 for unlimited)")
), ),
security( security(
("bearerAuth" = []) ("bearerAuth" = [])
@@ -276,7 +227,6 @@ pub async fn handle_get_item_latest_content(
pub async fn handle_get_item_content( pub async fn handle_get_item_content(
State(state): State<AppState>, State(state): State<AppState>,
Path(item_id): Path<i64>, Path(item_id): Path<i64>,
Query(params): Query<ItemContentParams>,
) -> Result<Response, StatusCode> { ) -> Result<Response, StatusCode> {
// Validate that item ID is positive to prevent path traversal issues // Validate that item ID is positive to prevent path traversal issues
if item_id <= 0 { 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()); 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 { match item_service.get_item_content(item_id).await {
Ok((stream, mime_type)) => { Ok(item_with_content) => {
let body = axum::body::boxed(axum::body::StreamBody::new(stream)); 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() let response = Response::builder()
.header(header::CONTENT_TYPE, mime_type) .header(header::CONTENT_TYPE, mime_type)
.body(body) .body(axum::body::Body::from(content))
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(response) Ok(response)
} }
@@ -300,7 +257,7 @@ pub async fn handle_get_item_content(
return Err(StatusCode::NOT_FOUND); 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) Err(StatusCode::INTERNAL_SERVER_ERROR)
} }
} }