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,
|
width: isize,
|
||||||
height: isize,
|
height: isize,
|
||||||
thumbhash: Option<Vec<u8>>,
|
thumbhash: Option<Vec<u8>>,
|
||||||
#[serde(default)]
|
animated: Option<bool>,
|
||||||
animated: bool,
|
|
||||||
},
|
},
|
||||||
/// File is a video with specific dimensions
|
/// File is a video with specific dimensions
|
||||||
Video { width: isize, height: isize },
|
Video { width: isize, height: isize },
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ pub trait AbstractAttachmentHashes: Sync + Send {
|
|||||||
/// Update an attachment hash nonce value.
|
/// Update an attachment hash nonce value.
|
||||||
async fn set_attachment_hash_nonce(&self, hash: &str, nonce: &str) -> Result<()>;
|
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.
|
/// Delete attachment hash by id.
|
||||||
async fn delete_attachment_hash(&self, id: &str) -> Result<()>;
|
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))
|
.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.
|
/// Delete attachment hash by id.
|
||||||
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
|
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
|
||||||
query!(self, delete_one_by_id, COL, id).map(|_| ())
|
query!(self, delete_one_by_id, COL, id).map(|_| ())
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
|
|
||||||
use crate::FileHash;
|
use crate::{FileHash, Metadata, ReferenceDb};
|
||||||
use crate::ReferenceDb;
|
|
||||||
|
|
||||||
use super::AbstractAttachmentHashes;
|
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.
|
/// Delete attachment hash by id.
|
||||||
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
|
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
|
||||||
let mut file_hashes = self.file_hashes.lock().await;
|
let mut file_hashes = self.file_hashes.lock().await;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ auto_derived!(
|
|||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
thumbhash: Option<Vec<u8>>,
|
thumbhash: Option<Vec<u8>>,
|
||||||
animated: bool,
|
animated: Option<bool>,
|
||||||
},
|
},
|
||||||
/// File is a video with specific dimensions
|
/// File is a video with specific dimensions
|
||||||
Video { width: usize, height: usize },
|
Video { width: usize, height: usize },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
io::{Cursor, Read},
|
io::{Cursor, Read, Write},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,9 +15,10 @@ use lazy_static::lazy_static;
|
|||||||
use revolt_config::{config, report_internal_error};
|
use revolt_config::{config, report_internal_error};
|
||||||
use revolt_database::{iso8601_timestamp::Timestamp, Database, FileHash, Metadata, User};
|
use revolt_database::{iso8601_timestamp::Timestamp, Database, FileHash, Metadata, User};
|
||||||
use revolt_files::{
|
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 serde::{Deserialize, Serialize};
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
@@ -25,7 +26,9 @@ use tokio::time::Instant;
|
|||||||
use tower_http::cors::{AllowHeaders, Any, CorsLayer};
|
use tower_http::cors::{AllowHeaders, Any, CorsLayer};
|
||||||
use utoipa::ToSchema;
|
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
|
/// Build the API router
|
||||||
pub async fn router() -> Router<AppState> {
|
pub async fn router() -> Router<AppState> {
|
||||||
@@ -380,7 +383,30 @@ async fn fetch_preview(
|
|||||||
|
|
||||||
let hash = file.as_hash(&db).await?;
|
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
|
// Only process image files and don't process GIFs if not avatar or icon
|
||||||
if !matches!(hash.metadata, Metadata::Image { .. })
|
if !matches!(hash.metadata, Metadata::Image { .. })
|
||||||
@@ -392,7 +418,11 @@ async fn fetch_preview(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Original image data
|
// 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
|
// Read image and create thumbnail
|
||||||
let data = 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)
|
thumbhash::rgba_to_thumb_hash(width as usize, height as usize, &rgba)
|
||||||
})
|
})
|
||||||
.ok(),
|
.ok(),
|
||||||
animated: is_animated(f, mime_type).unwrap_or(false),
|
animated: is_animated(f, mime_type).or(Some(false)),
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
} else if mime_type.starts_with("video/") {
|
} else if mime_type.starts_with("video/") {
|
||||||
|
|||||||
Reference in New Issue
Block a user