feat: Remove support for server-side blurhashing

This commit is contained in:
Ginger
2026-05-12 10:53:00 -04:00
committed by Ellis Git
parent 384ddc89d1
commit ba2c123e82
20 changed files with 9 additions and 725 deletions
-6
View File
@@ -16,10 +16,6 @@ crate-type = [
]
[features]
blurhashing = [
"dep:image",
"dep:blurhash",
]
brotli_compression = [
"conduwuit-core/brotli_compression",
"reqwest/brotli",
@@ -119,8 +115,6 @@ tracing.workspace = true
url.workspace = true
webpage.workspace = true
webpage.optional = true
blurhash.workspace = true
blurhash.optional = true
recaptcha-verify = { version = "0.2.0", default-features = false }
reqwest_recaptcha = { package = "reqwest", version = "0.12.28", default-features = false, features = ["rustls-tls-native-roots-no-provider"] } # As long as recaptcha-verify's reqwest is outdated
yansi.workspace = true
-179
View File
@@ -1,179 +0,0 @@
#[cfg(feature = "blurhashing")]
use conduwuit::config::BlurhashConfig as CoreBlurhashConfig;
use conduwuit::{Result, implement};
use super::Service;
#[implement(Service)]
#[cfg(not(feature = "blurhashing"))]
pub fn create_blurhash(
&self,
_file: &[u8],
_content_type: Option<&str>,
_file_name: Option<&str>,
) -> Result<Option<String>> {
conduwuit::debug_warn!("blurhashing on upload support was not compiled");
Ok(None)
}
#[implement(Service)]
#[cfg(feature = "blurhashing")]
pub fn create_blurhash(
&self,
file: &[u8],
content_type: Option<&str>,
file_name: Option<&str>,
) -> Result<Option<String>> {
let config = BlurhashConfig::from(self.services.server.config.blurhashing);
// since 0 means disabled blurhashing, skipped blurhashing
if config.size_limit == 0 {
return Ok(None);
}
get_blurhash_from_request(file, content_type, file_name, config)
.map_err(|e| conduwuit::err!(debug_error!("blurhashing error: {e}")))
.map(Some)
}
/// Returns the blurhash or a blurhash error which implements Display.
#[tracing::instrument(
name = "blurhash",
level = "debug",
skip(data),
fields(
bytes = data.len(),
),
)]
#[cfg(feature = "blurhashing")]
fn get_blurhash_from_request(
data: &[u8],
mime: Option<&str>,
filename: Option<&str>,
config: BlurhashConfig,
) -> Result<String, BlurhashingError> {
// Get format image is supposed to be in
let format = get_format_from_data_mime_and_filename(data, mime, filename)?;
// Get the image reader for said image format
let decoder = get_image_decoder_with_format_and_data(format, data)?;
// Check image size makes sense before unpacking whole image
if is_image_above_size_limit(&decoder, config) {
return Err(BlurhashingError::ImageTooLarge);
}
let image = image::DynamicImage::from_decoder(decoder)?;
blurhash_an_image(&image, config)
}
/// Gets the Image Format value from the data,mime, and filename
/// It first checks if the mime is a valid image format
/// Then it checks if the filename has a format, otherwise just guess based on
/// the binary data Assumes that mime and filename extension won't be for a
/// different file format than file.
#[cfg(feature = "blurhashing")]
fn get_format_from_data_mime_and_filename(
data: &[u8],
mime: Option<&str>,
filename: Option<&str>,
) -> Result<image::ImageFormat, BlurhashingError> {
let extension = filename
.map(std::path::Path::new)
.and_then(std::path::Path::extension)
.map(std::ffi::OsStr::to_string_lossy);
mime.or(extension.as_deref())
.and_then(image::ImageFormat::from_mime_type)
.map_or_else(|| image::guess_format(data).map_err(Into::into), Ok)
}
#[cfg(feature = "blurhashing")]
fn get_image_decoder_with_format_and_data(
image_format: image::ImageFormat,
data: &[u8],
) -> Result<Box<dyn image::ImageDecoder + '_>, BlurhashingError> {
let mut image_reader = image::ImageReader::new(std::io::Cursor::new(data));
image_reader.set_format(image_format);
Ok(Box::new(image_reader.into_decoder()?))
}
#[cfg(feature = "blurhashing")]
fn is_image_above_size_limit<T: image::ImageDecoder>(
decoder: &T,
blurhash_config: BlurhashConfig,
) -> bool {
decoder.total_bytes() >= blurhash_config.size_limit
}
#[cfg(feature = "blurhashing")]
#[tracing::instrument(name = "encode", level = "debug", skip_all)]
#[inline]
fn blurhash_an_image(
image: &image::DynamicImage,
blurhash_config: BlurhashConfig,
) -> Result<String, BlurhashingError> {
Ok(blurhash::encode_image(
blurhash_config.components_x,
blurhash_config.components_y,
&image.to_rgba8(),
)?)
}
#[derive(Clone, Copy, Debug)]
pub struct BlurhashConfig {
pub components_x: u32,
pub components_y: u32,
/// size limit in bytes
pub size_limit: u64,
}
#[cfg(feature = "blurhashing")]
impl From<CoreBlurhashConfig> for BlurhashConfig {
fn from(value: CoreBlurhashConfig) -> Self {
Self {
components_x: value.components_x,
components_y: value.components_y,
size_limit: value.blurhash_max_raw_size,
}
}
}
#[derive(Debug)]
#[cfg(feature = "blurhashing")]
pub enum BlurhashingError {
HashingLibError(Box<dyn std::error::Error + Send>),
#[cfg(feature = "blurhashing")]
ImageError(Box<image::ImageError>),
ImageTooLarge,
}
#[cfg(feature = "blurhashing")]
impl From<image::ImageError> for BlurhashingError {
fn from(value: image::ImageError) -> Self { Self::ImageError(Box::new(value)) }
}
#[cfg(feature = "blurhashing")]
impl From<blurhash::Error> for BlurhashingError {
fn from(value: blurhash::Error) -> Self { Self::HashingLibError(Box::new(value)) }
}
#[cfg(feature = "blurhashing")]
impl std::fmt::Display for BlurhashingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Blurhash Error:")?;
match &self {
| Self::ImageTooLarge => write!(f, "Image was too large to blurhash")?,
| Self::HashingLibError(e) =>
write!(f, "There was an error with the blurhashing library => {e}")?,
#[cfg(feature = "blurhashing")]
| Self::ImageError(e) =>
write!(f, "There was an error with the image loading library => {e}")?,
}
Ok(())
}
}
-1
View File
@@ -1,4 +1,3 @@
pub mod blurhash;
mod data;
pub(super) mod migrations;
pub mod mxc;
-2
View File
@@ -240,7 +240,6 @@ impl Service {
let mut content = RoomMemberEventContent::new(MembershipState::Join);
content.displayname = self.services.users.displayname(sender_user).await.ok();
content.avatar_url = self.services.users.avatar_url(sender_user).await.ok();
content.blurhash = self.services.users.blurhash(sender_user).await.ok();
content.reason.clone_from(&reason);
content.join_authorized_via_users_server = auth_user;
@@ -351,7 +350,6 @@ impl Service {
let mut join_content = RoomMemberEventContent::new(MembershipState::Join);
join_content.displayname = self.services.users.displayname(sender_user).await.ok();
join_content.avatar_url = self.services.users.avatar_url(sender_user).await.ok();
join_content.blurhash = self.services.users.blurhash(sender_user).await.ok();
join_content.reason = reason;
join_content
.join_authorized_via_users_server
-17
View File
@@ -79,7 +79,6 @@ struct Data {
userdeviceid_token: Arc<Map>,
userfilterid_filter: Arc<Map>,
userid_avatarurl: Arc<Map>,
userid_blurhash: Arc<Map>,
userid_dehydrateddevice: Arc<Map>,
userid_devicelistversion: Arc<Map>,
userid_displayname: Arc<Map>,
@@ -120,7 +119,6 @@ impl crate::Service for Service {
userdeviceid_token: args.db["userdeviceid_token"].clone(),
userfilterid_filter: args.db["userfilterid_filter"].clone(),
userid_avatarurl: args.db["userid_avatarurl"].clone(),
userid_blurhash: args.db["userid_blurhash"].clone(),
userid_dehydrateddevice: args.db["userid_dehydrateddevice"].clone(),
userid_devicelistversion: args.db["userid_devicelistversion"].clone(),
userid_displayname: args.db["userid_displayname"].clone(),
@@ -430,20 +428,6 @@ impl Service {
}
}
/// Get the blurhash of a user.
pub async fn blurhash(&self, user_id: &UserId) -> Result<String> {
self.db.userid_blurhash.get(user_id).await.deserialized()
}
/// Sets a new avatar_url or removes it if avatar_url is None.
pub fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) {
if let Some(blurhash) = blurhash {
self.db.userid_blurhash.insert(user_id, blurhash);
} else {
self.db.userid_blurhash.remove(user_id);
}
}
/// Adds a new device to a user.
pub async fn create_device(
&self,
@@ -1379,7 +1363,6 @@ impl Service {
pub async fn clear_profile(&self, user_id: &UserId) {
self.set_displayname(user_id, None);
self.set_avatar_url(user_id, None);
self.set_blurhash(user_id, None);
self.all_profile_keys(user_id)
.ready_for_each(|(key, _)| self.set_profile_key(user_id, &key, None))
.await;