forked from jmug/stoatchat
fix: add migration to update existing files to be animated (#705)
* fix: add migration to update existing files to be animated
Signed-off-by: Zomatree <me@zomatree.live>
* Revert "fix: add migration to update existing files to be animated"
This reverts commit 4e1f1c116c.
Signed-off-by: Zomatree <me@zomatree.live>
* fix: calculate animated for existing files when fetched
Signed-off-by: Zomatree <me@zomatree.live>
---------
Signed-off-by: Zomatree <me@zomatree.live>
This commit is contained in:
@@ -46,8 +46,7 @@ auto_derived!(
|
||||
width: isize,
|
||||
height: isize,
|
||||
thumbhash: Option<Vec<u8>>,
|
||||
#[serde(default)]
|
||||
animated: bool,
|
||||
animated: Option<bool>,
|
||||
},
|
||||
/// File is a video with specific dimensions
|
||||
Video { width: isize, height: isize },
|
||||
|
||||
@@ -17,6 +17,12 @@ pub trait AbstractAttachmentHashes: Sync + Send {
|
||||
/// Update an attachment hash nonce value.
|
||||
async fn set_attachment_hash_nonce(&self, hash: &str, nonce: &str) -> Result<()>;
|
||||
|
||||
/// Updates the attachments animated metadata value.
|
||||
///
|
||||
/// The primary use for this is to update the metadata for existing uploaded files, this
|
||||
/// can only be used for images.
|
||||
async fn set_attachment_hash_animated(&self, hash: &str, animated: bool) -> Result<()>;
|
||||
|
||||
/// Delete attachment hash by id.
|
||||
async fn delete_attachment_hash(&self, id: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,29 @@ impl AbstractAttachmentHashes for MongoDb {
|
||||
.map_err(|_| create_database_error!("update_one", COL))
|
||||
}
|
||||
|
||||
/// Updates the attachments animated metadata value.
|
||||
///
|
||||
/// The primary use for this is to update the metadata for existing uploaded files, this
|
||||
/// can only be used for images.
|
||||
async fn set_attachment_hash_animated(&self, hash: &str, animated: bool) -> Result<()> {
|
||||
self.col::<FileHash>(COL)
|
||||
.update_one(
|
||||
doc! {
|
||||
"_id": hash,
|
||||
"metadata.type": "Image",
|
||||
"metadata.animated": { "$exists": false },
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"metadata.animated": animated
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_one", COL))
|
||||
}
|
||||
|
||||
/// Delete attachment hash by id.
|
||||
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
|
||||
query!(self, delete_one_by_id, COL, id).map(|_| ())
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use revolt_result::Result;
|
||||
|
||||
use crate::FileHash;
|
||||
use crate::ReferenceDb;
|
||||
use crate::{FileHash, Metadata, ReferenceDb};
|
||||
|
||||
use super::AbstractAttachmentHashes;
|
||||
|
||||
@@ -39,6 +38,29 @@ impl AbstractAttachmentHashes for ReferenceDb {
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the attachments animated metadata value.
|
||||
///
|
||||
/// The primary use for this is to update the metadata for existing uploaded files, this
|
||||
/// can only be used for images.
|
||||
async fn set_attachment_hash_animated(&self, hash: &str, animated: bool) -> Result<()> {
|
||||
let mut hashes = self.file_hashes.lock().await;
|
||||
if let Some(FileHash {
|
||||
metadata:
|
||||
Metadata::Image {
|
||||
animated: Some(animated_metadata),
|
||||
..
|
||||
},
|
||||
..
|
||||
}) = hashes.get_mut(hash)
|
||||
{
|
||||
*animated_metadata = animated;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete attachment hash by id.
|
||||
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
|
||||
let mut file_hashes = self.file_hashes.lock().await;
|
||||
|
||||
@@ -51,7 +51,7 @@ auto_derived!(
|
||||
width: usize,
|
||||
height: usize,
|
||||
thumbhash: Option<Vec<u8>>,
|
||||
animated: bool,
|
||||
animated: Option<bool>,
|
||||
},
|
||||
/// File is a video with specific dimensions
|
||||
Video { width: usize, height: usize },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
io::{Cursor, Read},
|
||||
io::{Cursor, Read, Write},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -15,9 +15,10 @@ use lazy_static::lazy_static;
|
||||
use revolt_config::{config, report_internal_error};
|
||||
use revolt_database::{iso8601_timestamp::Timestamp, Database, FileHash, Metadata, User};
|
||||
use revolt_files::{
|
||||
create_thumbnail, decode_image, fetch_from_s3, upload_to_s3, AUTHENTICATION_TAG_SIZE_BYTES,
|
||||
create_thumbnail, decode_image, fetch_from_s3, is_animated, upload_to_s3,
|
||||
AUTHENTICATION_TAG_SIZE_BYTES,
|
||||
};
|
||||
use revolt_result::{create_error, Error, Result};
|
||||
use revolt_result::{create_error, Error, Result, ToRevoltError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Digest;
|
||||
use tempfile::NamedTempFile;
|
||||
@@ -25,7 +26,9 @@ use tokio::time::Instant;
|
||||
use tower_http::cors::{AllowHeaders, Any, CorsLayer};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::{exif::strip_metadata, metadata::generate_metadata, mime_type::determine_mime_type, AppState};
|
||||
use crate::{
|
||||
exif::strip_metadata, metadata::generate_metadata, mime_type::determine_mime_type, AppState,
|
||||
};
|
||||
|
||||
/// Build the API router
|
||||
pub async fn router() -> Router<AppState> {
|
||||
@@ -380,7 +383,30 @@ async fn fetch_preview(
|
||||
|
||||
let hash = file.as_hash(&db).await?;
|
||||
|
||||
let is_animated = matches!(hash.metadata, Metadata::Image { animated: true, .. });
|
||||
let mut data = None;
|
||||
|
||||
// If animated is unset, check the file contents to see if it is animated and update the filehash
|
||||
let is_animated = match &hash.metadata {
|
||||
Metadata::Image {
|
||||
animated: Some(value),
|
||||
..
|
||||
} => *value,
|
||||
Metadata::Image { animated: None, .. } => {
|
||||
let file_data = retrieve_file_by_hash(&hash).await?;
|
||||
|
||||
let mut named_file = NamedTempFile::new().to_internal_error()?;
|
||||
named_file.write(&file_data).to_internal_error()?;
|
||||
|
||||
data = Some(file_data);
|
||||
|
||||
// If it fails for some reason, set it to not be animated
|
||||
let animated = is_animated(&named_file, &hash.content_type).unwrap_or(false);
|
||||
db.set_attachment_hash_animated(&hash.id, animated).await?;
|
||||
|
||||
animated
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Only process image files and don't process GIFs if not avatar or icon
|
||||
if !matches!(hash.metadata, Metadata::Image { .. })
|
||||
@@ -392,7 +418,11 @@ async fn fetch_preview(
|
||||
}
|
||||
|
||||
// Original image data
|
||||
let data = retrieve_file_by_hash(&hash).await?;
|
||||
let data = if let Some(data) = data {
|
||||
data
|
||||
} else {
|
||||
retrieve_file_by_hash(&hash).await?
|
||||
};
|
||||
|
||||
// Read image and create thumbnail
|
||||
let data = create_thumbnail(
|
||||
|
||||
@@ -37,7 +37,7 @@ pub fn generate_metadata(f: &NamedTempFile, mime_type: &str) -> Metadata {
|
||||
thumbhash::rgba_to_thumb_hash(width as usize, height as usize, &rgba)
|
||||
})
|
||||
.ok(),
|
||||
animated: is_animated(f, mime_type).unwrap_or(false),
|
||||
animated: is_animated(f, mime_type).or(Some(false)),
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else if mime_type.starts_with("video/") {
|
||||
|
||||
Reference in New Issue
Block a user