use crate::services::attachments::{attachment_stream_content_type, is_inline_image}; use cache::AppRedis; use db::AppDatabase; use imaging::layout::{ImageVariant, PreviewState}; use imaging::preset::PreviewPreset; use models::chat::attachments; use sea_orm::sea_query::Expr; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; use std::sync::Arc; use storage::S3Client; #[derive(Clone)] pub struct Media { pub(crate) client: Arc, previews: AppRedis, database: Arc, } impl Media { pub fn new(client: Arc, previews: AppRedis, database: Arc) -> Self { Self { client, previews, database, } } pub(crate) async fn preview( &self, row: &attachments::Model, recorded: Option<&PreviewState>, ) -> Option { let key_base = ::media::preview_key_base_of(&row.s3_key); ::media::ensure_preview( self.client.as_ref(), &self.previews, &key_base, &row.s3_key, &row.content_type, PreviewPreset::Content, recorded, ) .await } pub(crate) async fn attachment_variant_locator( &self, row: &attachments::Model, variant: ImageVariant, ) -> (String, String) { if variant == ImageVariant::Original || !is_inline_image(&row.content_type) { return (row.s3_key.clone(), attachment_stream_content_type(row)); } let recorded = imaging::layout::preview_from_variants(&row.variants); let state = self.preview(row, recorded.as_ref()).await; match state { Some(state @ PreviewState::Ready(_)) => { if recorded.as_ref() != Some(&state) && let Err(error) = self .record_attachment_preview(row.id, row.variants.clone(), &state) .await { tracing::warn!(%error, attachment_id = %row.id, "failed to record attachment preview"); } ( { let state = &state; match state { PreviewState::Ready(ready) => ready.key.clone(), PreviewState::Skipped(_) => String::new(), } }, { let state = &state; match state { PreviewState::Ready(ready) => ready.content_type.clone(), PreviewState::Skipped(_) => "application/octet-stream".to_string(), } }, ) } Some(PreviewState::Skipped(_)) | None => { (row.s3_key.clone(), attachment_stream_content_type(row)) } } } async fn record_attachment_preview( &self, attachment_id: uuid::Uuid, mut variants: serde_json::Value, state: &PreviewState, ) -> Result<(), sea_orm::DbErr> { imaging::layout::preview_into_variants(&mut variants, state); attachments::Entity::update_many() .filter(attachments::Column::Id.eq(attachment_id)) .col_expr(attachments::Column::Variants, Expr::value(variants)) .exec(self.database.write()) .await?; Ok(()) } }