Add default-enabled feature-gates for url_preview and media_thumbnail

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk
2024-12-18 21:29:30 +00:00
parent 0238f27605
commit cc1889d135
8 changed files with 182 additions and 123 deletions

View File

@@ -1,7 +1,13 @@
use std::{cmp, io::Cursor, num::Saturating as Sat};
//! Media Thumbnails
//!
//! This functionality is gated by 'media_thumbnail', but not at the unit level
//! for historical and simplicity reasons. Instead the feature gates the
//! inclusion of dependencies and nulls out results using the existing interface
//! when not featured.
use conduwuit::{checked, err, Result};
use image::{imageops::FilterType, DynamicImage};
use std::{cmp, num::Saturating as Sat};
use conduwuit::{checked, err, implement, Result};
use ruma::{http_headers::ContentDisposition, media::Method, Mxc, UInt, UserId};
use tokio::{
fs,
@@ -67,65 +73,89 @@ impl super::Service {
Ok(None)
}
}
/// Using saved thumbnail
#[tracing::instrument(skip(self), name = "saved", level = "debug")]
async fn get_thumbnail_saved(&self, data: Metadata) -> Result<Option<FileMeta>> {
let mut content = Vec::new();
let path = self.get_media_file(&data.key);
fs::File::open(path)
.await?
.read_to_end(&mut content)
.await?;
Ok(Some(into_filemeta(data, content)))
}
/// Generate a thumbnail
#[tracing::instrument(skip(self), name = "generate", level = "debug")]
async fn get_thumbnail_generate(
&self,
mxc: &Mxc<'_>,
dim: &Dim,
data: Metadata,
) -> Result<Option<FileMeta>> {
let mut content = Vec::new();
let path = self.get_media_file(&data.key);
fs::File::open(path)
.await?
.read_to_end(&mut content)
.await?;
let Ok(image) = image::load_from_memory(&content) else {
// Couldn't parse file to generate thumbnail, send original
return Ok(Some(into_filemeta(data, content)));
};
if dim.width > image.width() || dim.height > image.height() {
return Ok(Some(into_filemeta(data, content)));
}
let mut thumbnail_bytes = Vec::new();
let thumbnail = thumbnail_generate(&image, dim)?;
thumbnail.write_to(&mut Cursor::new(&mut thumbnail_bytes), image::ImageFormat::Png)?;
// Save thumbnail in database so we don't have to generate it again next time
let thumbnail_key = self.db.create_file_metadata(
mxc,
None,
dim,
data.content_disposition.as_ref(),
data.content_type.as_deref(),
)?;
let mut f = self.create_media_file(&thumbnail_key).await?;
f.write_all(&thumbnail_bytes).await?;
Ok(Some(into_filemeta(data, thumbnail_bytes)))
}
}
fn thumbnail_generate(image: &DynamicImage, requested: &Dim) -> Result<DynamicImage> {
/// Using saved thumbnail
#[implement(super::Service)]
#[tracing::instrument(name = "saved", level = "debug", skip(self, data))]
async fn get_thumbnail_saved(&self, data: Metadata) -> Result<Option<FileMeta>> {
let mut content = Vec::new();
let path = self.get_media_file(&data.key);
fs::File::open(path)
.await?
.read_to_end(&mut content)
.await?;
Ok(Some(into_filemeta(data, content)))
}
/// Generate a thumbnail
#[cfg(feature = "media_thumbnail")]
#[implement(super::Service)]
#[tracing::instrument(name = "generate", level = "debug", skip(self, data))]
async fn get_thumbnail_generate(
&self,
mxc: &Mxc<'_>,
dim: &Dim,
data: Metadata,
) -> Result<Option<FileMeta>> {
let mut content = Vec::new();
let path = self.get_media_file(&data.key);
fs::File::open(path)
.await?
.read_to_end(&mut content)
.await?;
let Ok(image) = image::load_from_memory(&content) else {
// Couldn't parse file to generate thumbnail, send original
return Ok(Some(into_filemeta(data, content)));
};
if dim.width > image.width() || dim.height > image.height() {
return Ok(Some(into_filemeta(data, content)));
}
let mut thumbnail_bytes = Vec::new();
let thumbnail = thumbnail_generate(&image, dim)?;
let mut cursor = std::io::Cursor::new(&mut thumbnail_bytes);
thumbnail
.write_to(&mut cursor, image::ImageFormat::Png)
.map_err(|error| err!(error!(?error, "Error writing PNG thumbnail.")))?;
// Save thumbnail in database so we don't have to generate it again next time
let thumbnail_key = self.db.create_file_metadata(
mxc,
None,
dim,
data.content_disposition.as_ref(),
data.content_type.as_deref(),
)?;
let mut f = self.create_media_file(&thumbnail_key).await?;
f.write_all(&thumbnail_bytes).await?;
Ok(Some(into_filemeta(data, thumbnail_bytes)))
}
#[cfg(not(feature = "media_thumbnail"))]
#[implement(super::Service)]
#[tracing::instrument(name = "fallback", level = "debug", skip_all)]
async fn get_thumbnail_generate(
&self,
_mxc: &Mxc<'_>,
_dim: &Dim,
data: Metadata,
) -> Result<Option<FileMeta>> {
self.get_thumbnail_saved(data).await
}
#[cfg(feature = "media_thumbnail")]
fn thumbnail_generate(
image: &image::DynamicImage,
requested: &Dim,
) -> Result<image::DynamicImage> {
use image::imageops::FilterType;
let thumbnail = if !requested.crop() {
let Dim { width, height, .. } = requested.scaled(&Dim {
width: image.width(),