apply new rustfmt.toml changes, fix some clippy lints
Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
parent
0317cc8cc5
commit
77e0b76408
296 changed files with 7147 additions and 4300 deletions
src/service/media
|
@ -34,7 +34,11 @@ impl Data {
|
|||
}
|
||||
|
||||
pub(super) fn create_file_metadata(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, dim: &Dim, content_disposition: Option<&ContentDisposition>,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
dim: &Dim,
|
||||
content_disposition: Option<&ContentDisposition>,
|
||||
content_type: Option<&str>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let dim: &[u32] = &[dim.width, dim.height];
|
||||
|
@ -63,7 +67,10 @@ impl Data {
|
|||
.stream_prefix_raw(&prefix)
|
||||
.ignore_err()
|
||||
.ready_for_each(|(key, val)| {
|
||||
debug_assert!(key.starts_with(mxc.to_string().as_bytes()), "key should start with the mxc");
|
||||
debug_assert!(
|
||||
key.starts_with(mxc.to_string().as_bytes()),
|
||||
"key should start with the mxc"
|
||||
);
|
||||
|
||||
let user = str_from_bytes(val).unwrap_or_default();
|
||||
debug_info!("Deleting key {key:?} which was uploaded by user {user}");
|
||||
|
@ -95,7 +102,11 @@ impl Data {
|
|||
Ok(keys)
|
||||
}
|
||||
|
||||
pub(super) async fn search_file_metadata(&self, mxc: &Mxc<'_>, dim: &Dim) -> Result<Metadata> {
|
||||
pub(super) async fn search_file_metadata(
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
dim: &Dim,
|
||||
) -> Result<Metadata> {
|
||||
let dim: &[u32] = &[dim.width, dim.height];
|
||||
let prefix = (mxc, dim, Interfix);
|
||||
|
||||
|
@ -113,8 +124,9 @@ impl Data {
|
|||
let content_type = parts
|
||||
.next()
|
||||
.map(|bytes| {
|
||||
string_from_bytes(bytes)
|
||||
.map_err(|_| Error::bad_database("Content type in mediaid_file is invalid unicode."))
|
||||
string_from_bytes(bytes).map_err(|_| {
|
||||
Error::bad_database("Content type in mediaid_file is invalid unicode.")
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
|
@ -127,16 +139,16 @@ impl Data {
|
|||
} else {
|
||||
Some(
|
||||
string_from_bytes(content_disposition_bytes)
|
||||
.map_err(|_| Error::bad_database("Content Disposition in mediaid_file is invalid unicode."))?
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Content Disposition in mediaid_file is invalid unicode.",
|
||||
)
|
||||
})?
|
||||
.parse()?,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Metadata {
|
||||
content_disposition,
|
||||
content_type,
|
||||
key,
|
||||
})
|
||||
Ok(Metadata { content_disposition, content_type, key })
|
||||
}
|
||||
|
||||
/// Gets all the MXCs associated with a user
|
||||
|
@ -144,7 +156,9 @@ impl Data {
|
|||
self.mediaid_user
|
||||
.stream()
|
||||
.ignore_err()
|
||||
.ready_filter_map(|(key, user): (&str, &UserId)| (user == user_id).then(|| key.into()))
|
||||
.ready_filter_map(|(key, user): (&str, &UserId)| {
|
||||
(user == user_id).then(|| key.into())
|
||||
})
|
||||
.collect()
|
||||
.await
|
||||
}
|
||||
|
@ -166,7 +180,12 @@ impl Data {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn set_url_preview(&self, url: &str, data: &UrlPreviewData, timestamp: Duration) -> Result<()> {
|
||||
pub(super) fn set_url_preview(
|
||||
&self,
|
||||
url: &str,
|
||||
data: &UrlPreviewData,
|
||||
timestamp: Duration,
|
||||
) -> Result<()> {
|
||||
let mut value = Vec::<u8>::new();
|
||||
value.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
||||
value.push(0xFF);
|
||||
|
@ -218,43 +237,43 @@ impl Data {
|
|||
.next()
|
||||
.and_then(|b| String::from_utf8(b.to_vec()).ok())
|
||||
{
|
||||
Some(s) if s.is_empty() => None,
|
||||
x => x,
|
||||
| Some(s) if s.is_empty() => None,
|
||||
| x => x,
|
||||
};
|
||||
let description = match values
|
||||
.next()
|
||||
.and_then(|b| String::from_utf8(b.to_vec()).ok())
|
||||
{
|
||||
Some(s) if s.is_empty() => None,
|
||||
x => x,
|
||||
| Some(s) if s.is_empty() => None,
|
||||
| x => x,
|
||||
};
|
||||
let image = match values
|
||||
.next()
|
||||
.and_then(|b| String::from_utf8(b.to_vec()).ok())
|
||||
{
|
||||
Some(s) if s.is_empty() => None,
|
||||
x => x,
|
||||
| Some(s) if s.is_empty() => None,
|
||||
| x => x,
|
||||
};
|
||||
let image_size = match values
|
||||
.next()
|
||||
.map(|b| usize::from_be_bytes(b.try_into().unwrap_or_default()))
|
||||
{
|
||||
Some(0) => None,
|
||||
x => x,
|
||||
| Some(0) => None,
|
||||
| x => x,
|
||||
};
|
||||
let image_width = match values
|
||||
.next()
|
||||
.map(|b| u32::from_be_bytes(b.try_into().unwrap_or_default()))
|
||||
{
|
||||
Some(0) => None,
|
||||
x => x,
|
||||
| Some(0) => None,
|
||||
| x => x,
|
||||
};
|
||||
let image_height = match values
|
||||
.next()
|
||||
.map(|b| u32::from_be_bytes(b.try_into().unwrap_or_default()))
|
||||
{
|
||||
Some(0) => None,
|
||||
x => x,
|
||||
| Some(0) => None,
|
||||
| x => x,
|
||||
};
|
||||
|
||||
Ok(UrlPreviewData {
|
||||
|
|
|
@ -83,7 +83,8 @@ pub(crate) async fn checkup_sha256_media(services: &Services) -> Result<()> {
|
|||
for key in media.db.get_all_media_keys().await {
|
||||
let new_path = media.get_media_file_sha256(&key).into_os_string();
|
||||
let old_path = media.get_media_file_b64(&key).into_os_string();
|
||||
if let Err(e) = handle_media_check(&dbs, config, &files, &key, &new_path, &old_path).await {
|
||||
if let Err(e) = handle_media_check(&dbs, config, &files, &key, &new_path, &old_path).await
|
||||
{
|
||||
error!(
|
||||
media_id = ?encode_key(&key), ?new_path, ?old_path,
|
||||
"Failed to resolve media check failure: {e}"
|
||||
|
@ -100,8 +101,12 @@ pub(crate) async fn checkup_sha256_media(services: &Services) -> Result<()> {
|
|||
}
|
||||
|
||||
async fn handle_media_check(
|
||||
dbs: &(&Arc<database::Map>, &Arc<database::Map>), config: &Config, files: &HashSet<OsString>, key: &[u8],
|
||||
new_path: &OsStr, old_path: &OsStr,
|
||||
dbs: &(&Arc<database::Map>, &Arc<database::Map>),
|
||||
config: &Config,
|
||||
files: &HashSet<OsString>,
|
||||
key: &[u8],
|
||||
new_path: &OsStr,
|
||||
old_path: &OsStr,
|
||||
) -> Result<()> {
|
||||
use crate::media::encode_key;
|
||||
|
||||
|
|
|
@ -80,13 +80,21 @@ impl crate::Service for Service {
|
|||
impl Service {
|
||||
/// Uploads a file.
|
||||
pub async fn create(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, content_disposition: Option<&ContentDisposition>,
|
||||
content_type: Option<&str>, file: &[u8],
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
content_disposition: Option<&ContentDisposition>,
|
||||
content_type: Option<&str>,
|
||||
file: &[u8],
|
||||
) -> Result<()> {
|
||||
// Width, Height = 0 if it's not a thumbnail
|
||||
let key = self
|
||||
.db
|
||||
.create_file_metadata(mxc, user, &Dim::default(), content_disposition, content_type)?;
|
||||
let key = self.db.create_file_metadata(
|
||||
mxc,
|
||||
user,
|
||||
&Dim::default(),
|
||||
content_disposition,
|
||||
content_type,
|
||||
)?;
|
||||
|
||||
//TODO: Dangling metadata in database if creation fails
|
||||
let mut f = self.create_media_file(&key).await?;
|
||||
|
@ -132,10 +140,10 @@ impl Service {
|
|||
|
||||
debug_info!(%deletion_count, "Deleting MXC {mxc} by user {user} from database and filesystem");
|
||||
match self.delete(&mxc).await {
|
||||
Ok(()) => {
|
||||
| Ok(()) => {
|
||||
deletion_count = deletion_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
debug_error!(%deletion_count, "Failed to delete {mxc} from user {user}, ignoring error: {e}");
|
||||
},
|
||||
}
|
||||
|
@ -146,11 +154,8 @@ impl Service {
|
|||
|
||||
/// Downloads a file.
|
||||
pub async fn get(&self, mxc: &Mxc<'_>) -> Result<Option<FileMeta>> {
|
||||
if let Ok(Metadata {
|
||||
content_disposition,
|
||||
content_type,
|
||||
key,
|
||||
}) = self.db.search_file_metadata(mxc, &Dim::default()).await
|
||||
if let Ok(Metadata { content_disposition, content_type, key }) =
|
||||
self.db.search_file_metadata(mxc, &Dim::default()).await
|
||||
{
|
||||
let mut content = Vec::new();
|
||||
let path = self.get_media_file(&key);
|
||||
|
@ -181,13 +186,19 @@ impl Service {
|
|||
let mxc = parts
|
||||
.next()
|
||||
.map(|bytes| {
|
||||
utils::string_from_bytes(bytes)
|
||||
.map_err(|e| err!(Database(error!("Failed to parse MXC unicode bytes from our database: {e}"))))
|
||||
utils::string_from_bytes(bytes).map_err(|e| {
|
||||
err!(Database(error!(
|
||||
"Failed to parse MXC unicode bytes from our database: {e}"
|
||||
)))
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let Some(mxc_s) = mxc else {
|
||||
debug_warn!(?mxc, "Parsed MXC URL unicode bytes from database but is still invalid");
|
||||
debug_warn!(
|
||||
?mxc,
|
||||
"Parsed MXC URL unicode bytes from database but is still invalid"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -207,7 +218,11 @@ impl Service {
|
|||
/// Deletes all remote only media files in the given at or after
|
||||
/// time/duration. Returns a usize with the amount of media files deleted.
|
||||
pub async fn delete_all_remote_media_at_after_time(
|
||||
&self, time: SystemTime, before: bool, after: bool, yes_i_want_to_delete_local_media: bool,
|
||||
&self,
|
||||
time: SystemTime,
|
||||
before: bool,
|
||||
after: bool,
|
||||
yes_i_want_to_delete_local_media: bool,
|
||||
) -> Result<usize> {
|
||||
let all_keys = self.db.get_all_media_keys().await;
|
||||
let mut remote_mxcs = Vec::with_capacity(all_keys.len());
|
||||
|
@ -218,19 +233,26 @@ impl Service {
|
|||
let mxc = parts
|
||||
.next()
|
||||
.map(|bytes| {
|
||||
utils::string_from_bytes(bytes)
|
||||
.map_err(|e| err!(Database(error!("Failed to parse MXC unicode bytes from our database: {e}"))))
|
||||
utils::string_from_bytes(bytes).map_err(|e| {
|
||||
err!(Database(error!(
|
||||
"Failed to parse MXC unicode bytes from our database: {e}"
|
||||
)))
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let Some(mxc_s) = mxc else {
|
||||
debug_warn!(?mxc, "Parsed MXC URL unicode bytes from database but is still invalid");
|
||||
debug_warn!(
|
||||
?mxc,
|
||||
"Parsed MXC URL unicode bytes from database but is still invalid"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
trace!("Parsed MXC key to URL: {mxc_s}");
|
||||
let mxc = OwnedMxcUri::from(mxc_s);
|
||||
if (mxc.server_name() == Ok(self.services.globals.server_name()) && !yes_i_want_to_delete_local_media)
|
||||
if (mxc.server_name() == Ok(self.services.globals.server_name())
|
||||
&& !yes_i_want_to_delete_local_media)
|
||||
|| !mxc.is_valid()
|
||||
{
|
||||
debug!("Ignoring local or broken media MXC: {mxc}");
|
||||
|
@ -240,9 +262,12 @@ impl Service {
|
|||
let path = self.get_media_file(&key);
|
||||
|
||||
let file_metadata = match fs::metadata(path.clone()).await {
|
||||
Ok(file_metadata) => file_metadata,
|
||||
Err(e) => {
|
||||
error!("Failed to obtain file metadata for MXC {mxc} at file path \"{path:?}\", skipping: {e}");
|
||||
| Ok(file_metadata) => file_metadata,
|
||||
| Err(e) => {
|
||||
error!(
|
||||
"Failed to obtain file metadata for MXC {mxc} at file path \
|
||||
\"{path:?}\", skipping: {e}"
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
@ -250,12 +275,12 @@ impl Service {
|
|||
trace!(%mxc, ?path, "File metadata: {file_metadata:?}");
|
||||
|
||||
let file_created_at = match file_metadata.created() {
|
||||
Ok(value) => value,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::Unsupported => {
|
||||
| Ok(value) => value,
|
||||
| Err(err) if err.kind() == std::io::ErrorKind::Unsupported => {
|
||||
debug!("btime is unsupported, using mtime instead");
|
||||
file_metadata.modified()?
|
||||
},
|
||||
Err(err) => {
|
||||
| Err(err) => {
|
||||
error!("Could not delete MXC {mxc} at path {path:?}: {err:?}. Skipping...");
|
||||
continue;
|
||||
},
|
||||
|
@ -264,10 +289,16 @@ impl Service {
|
|||
debug!("File created at: {file_created_at:?}");
|
||||
|
||||
if file_created_at >= time && before {
|
||||
debug!("File is within (before) user duration, pushing to list of file paths and keys to delete.");
|
||||
debug!(
|
||||
"File is within (before) user duration, pushing to list of file paths and \
|
||||
keys to delete."
|
||||
);
|
||||
remote_mxcs.push(mxc.to_string());
|
||||
} else if file_created_at <= time && after {
|
||||
debug!("File is not within (after) user duration, pushing to list of file paths and keys to delete.");
|
||||
debug!(
|
||||
"File is not within (after) user duration, pushing to list of file paths \
|
||||
and keys to delete."
|
||||
);
|
||||
remote_mxcs.push(mxc.to_string());
|
||||
}
|
||||
}
|
||||
|
@ -289,10 +320,10 @@ impl Service {
|
|||
debug_info!("Deleting MXC {mxc} from database and filesystem");
|
||||
|
||||
match self.delete(&mxc).await {
|
||||
Ok(()) => {
|
||||
| Ok(()) => {
|
||||
deletion_count = deletion_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
},
|
||||
|
|
|
@ -53,10 +53,10 @@ pub async fn download_image(&self, url: &str) -> Result<UrlPreviewData> {
|
|||
self.create(&mxc, None, None, None, &image).await?;
|
||||
|
||||
let (width, height) = match ImgReader::new(Cursor::new(&image)).with_guessed_format() {
|
||||
Err(_) => (None, None),
|
||||
Ok(reader) => match reader.into_dimensions() {
|
||||
Err(_) => (None, None),
|
||||
Ok((width, height)) => (Some(width), Some(height)),
|
||||
| Err(_) => (None, None),
|
||||
| Ok(reader) => match reader.into_dimensions() {
|
||||
| Err(_) => (None, None),
|
||||
| Ok((width, height)) => (Some(width), Some(height)),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -79,8 +79,8 @@ pub async fn get_url_preview(&self, url: &Url) -> Result<UrlPreviewData> {
|
|||
let _request_lock = self.url_preview_mutex.lock(url.as_str()).await;
|
||||
|
||||
match self.db.get_url_preview(url.as_str()).await {
|
||||
Ok(preview) => Ok(preview),
|
||||
Err(_) => self.request_url_preview(url).await,
|
||||
| Ok(preview) => Ok(preview),
|
||||
| Err(_) => self.request_url_preview(url).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,9 +111,9 @@ async fn request_url_preview(&self, url: &Url) -> Result<UrlPreviewData> {
|
|||
return Err!(Request(Unknown("Unknown Content-Type")));
|
||||
};
|
||||
let data = match content_type {
|
||||
html if html.starts_with("text/html") => self.download_html(url.as_str()).await?,
|
||||
img if img.starts_with("image/") => self.download_image(url.as_str()).await?,
|
||||
_ => return Err!(Request(Unknown("Unsupported Content-Type"))),
|
||||
| html if html.starts_with("text/html") => self.download_html(url.as_str()).await?,
|
||||
| img if img.starts_with("image/") => self.download_image(url.as_str()).await?,
|
||||
| _ => return Err!(Request(Unknown("Unsupported Content-Type"))),
|
||||
};
|
||||
|
||||
self.set_url_preview(url.as_str(), &data).await?;
|
||||
|
@ -131,8 +131,9 @@ async fn download_html(&self, url: &str) -> Result<UrlPreviewData> {
|
|||
bytes.extend_from_slice(&chunk);
|
||||
if bytes.len() > self.services.globals.url_preview_max_spider_size() {
|
||||
debug!(
|
||||
"Response body from URL {} exceeds url_preview_max_spider_size ({}), not processing the rest of the \
|
||||
response body and assuming our necessary data is in this range.",
|
||||
"Response body from URL {} exceeds url_preview_max_spider_size ({}), not \
|
||||
processing the rest of the response body and assuming our necessary data is in \
|
||||
this range.",
|
||||
url,
|
||||
self.services.globals.url_preview_max_spider_size()
|
||||
);
|
||||
|
@ -145,8 +146,8 @@ async fn download_html(&self, url: &str) -> Result<UrlPreviewData> {
|
|||
};
|
||||
|
||||
let mut data = match html.opengraph.images.first() {
|
||||
None => UrlPreviewData::default(),
|
||||
Some(obj) => self.download_image(&obj.url).await?,
|
||||
| None => UrlPreviewData::default(),
|
||||
| Some(obj) => self.download_image(&obj.url).await?,
|
||||
};
|
||||
|
||||
let props = html.opengraph.properties;
|
||||
|
@ -169,11 +170,11 @@ pub fn url_preview_allowed(&self, url: &Url) -> bool {
|
|||
}
|
||||
|
||||
let host = match url.host_str() {
|
||||
None => {
|
||||
| None => {
|
||||
debug!("Ignoring URL preview for a URL that does not have a host (?): {}", url);
|
||||
return false;
|
||||
},
|
||||
Some(h) => h.to_owned(),
|
||||
| Some(h) => h.to_owned(),
|
||||
};
|
||||
|
||||
let allowlist_domain_contains = self
|
||||
|
@ -205,7 +206,10 @@ pub fn url_preview_allowed(&self, url: &Url) -> bool {
|
|||
}
|
||||
|
||||
if allowlist_domain_explicit.contains(&host) {
|
||||
debug!("Host {} is allowed by url_preview_domain_explicit_allowlist (check 2/4)", &host);
|
||||
debug!(
|
||||
"Host {} is allowed by url_preview_domain_explicit_allowlist (check 2/4)",
|
||||
&host
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -213,7 +217,10 @@ pub fn url_preview_allowed(&self, url: &Url) -> bool {
|
|||
.iter()
|
||||
.any(|domain_s| domain_s.contains(&host.clone()))
|
||||
{
|
||||
debug!("Host {} is allowed by url_preview_domain_contains_allowlist (check 3/4)", &host);
|
||||
debug!(
|
||||
"Host {} is allowed by url_preview_domain_contains_allowlist (check 3/4)",
|
||||
&host
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -229,11 +236,12 @@ pub fn url_preview_allowed(&self, url: &Url) -> bool {
|
|||
if self.services.globals.url_preview_check_root_domain() {
|
||||
debug!("Checking root domain");
|
||||
match host.split_once('.') {
|
||||
None => return false,
|
||||
Some((_, root_domain)) => {
|
||||
| None => return false,
|
||||
| Some((_, root_domain)) => {
|
||||
if denylist_domain_explicit.contains(&root_domain.to_owned()) {
|
||||
debug!(
|
||||
"Root domain {} is not allowed by url_preview_domain_explicit_denylist (check 1/3)",
|
||||
"Root domain {} is not allowed by \
|
||||
url_preview_domain_explicit_denylist (check 1/3)",
|
||||
&root_domain
|
||||
);
|
||||
return true;
|
||||
|
@ -241,7 +249,8 @@ pub fn url_preview_allowed(&self, url: &Url) -> bool {
|
|||
|
||||
if allowlist_domain_explicit.contains(&root_domain.to_owned()) {
|
||||
debug!(
|
||||
"Root domain {} is allowed by url_preview_domain_explicit_allowlist (check 2/3)",
|
||||
"Root domain {} is allowed by url_preview_domain_explicit_allowlist \
|
||||
(check 2/3)",
|
||||
&root_domain
|
||||
);
|
||||
return true;
|
||||
|
@ -252,7 +261,8 @@ pub fn url_preview_allowed(&self, url: &Url) -> bool {
|
|||
.any(|domain_s| domain_s.contains(&root_domain.to_owned()))
|
||||
{
|
||||
debug!(
|
||||
"Root domain {} is allowed by url_preview_domain_contains_allowlist (check 3/3)",
|
||||
"Root domain {} is allowed by url_preview_domain_contains_allowlist \
|
||||
(check 3/3)",
|
||||
&root_domain
|
||||
);
|
||||
return true;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::{fmt::Debug, time::Duration};
|
||||
|
||||
use conduwuit::{debug_warn, err, implement, utils::content_disposition::make_content_disposition, Err, Error, Result};
|
||||
use conduwuit::{
|
||||
debug_warn, err, implement, utils::content_disposition::make_content_disposition, Err, Error,
|
||||
Result,
|
||||
};
|
||||
use http::header::{HeaderValue, CONTENT_DISPOSITION, CONTENT_TYPE};
|
||||
use ruma::{
|
||||
api::{
|
||||
|
@ -19,7 +22,12 @@ use super::{Dim, FileMeta};
|
|||
|
||||
#[implement(super::Service)]
|
||||
pub async fn fetch_remote_thumbnail(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, server: Option<&ServerName>, timeout_ms: Duration, dim: &Dim,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
server: Option<&ServerName>,
|
||||
timeout_ms: Duration,
|
||||
dim: &Dim,
|
||||
) -> Result<FileMeta> {
|
||||
self.check_fetch_authorized(mxc)?;
|
||||
|
||||
|
@ -38,7 +46,11 @@ pub async fn fetch_remote_thumbnail(
|
|||
|
||||
#[implement(super::Service)]
|
||||
pub async fn fetch_remote_content(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, server: Option<&ServerName>, timeout_ms: Duration,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
server: Option<&ServerName>,
|
||||
timeout_ms: Duration,
|
||||
) -> Result<FileMeta> {
|
||||
self.check_fetch_authorized(mxc)?;
|
||||
|
||||
|
@ -57,7 +69,12 @@ pub async fn fetch_remote_content(
|
|||
|
||||
#[implement(super::Service)]
|
||||
async fn fetch_thumbnail_authenticated(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, server: Option<&ServerName>, timeout_ms: Duration, dim: &Dim,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
server: Option<&ServerName>,
|
||||
timeout_ms: Duration,
|
||||
dim: &Dim,
|
||||
) -> Result<FileMeta> {
|
||||
use federation::authenticated_media::get_content_thumbnail::v1::{Request, Response};
|
||||
|
||||
|
@ -70,20 +87,22 @@ async fn fetch_thumbnail_authenticated(
|
|||
timeout_ms,
|
||||
};
|
||||
|
||||
let Response {
|
||||
content,
|
||||
..
|
||||
} = self.federation_request(mxc, user, server, request).await?;
|
||||
let Response { content, .. } = self.federation_request(mxc, user, server, request).await?;
|
||||
|
||||
match content {
|
||||
FileOrLocation::File(content) => self.handle_thumbnail_file(mxc, user, dim, content).await,
|
||||
FileOrLocation::Location(location) => self.handle_location(mxc, user, &location).await,
|
||||
| FileOrLocation::File(content) =>
|
||||
self.handle_thumbnail_file(mxc, user, dim, content).await,
|
||||
| FileOrLocation::Location(location) => self.handle_location(mxc, user, &location).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
async fn fetch_content_authenticated(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, server: Option<&ServerName>, timeout_ms: Duration,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
server: Option<&ServerName>,
|
||||
timeout_ms: Duration,
|
||||
) -> Result<FileMeta> {
|
||||
use federation::authenticated_media::get_content::v1::{Request, Response};
|
||||
|
||||
|
@ -92,21 +111,23 @@ async fn fetch_content_authenticated(
|
|||
timeout_ms,
|
||||
};
|
||||
|
||||
let Response {
|
||||
content,
|
||||
..
|
||||
} = self.federation_request(mxc, user, server, request).await?;
|
||||
let Response { content, .. } = self.federation_request(mxc, user, server, request).await?;
|
||||
|
||||
match content {
|
||||
FileOrLocation::File(content) => self.handle_content_file(mxc, user, content).await,
|
||||
FileOrLocation::Location(location) => self.handle_location(mxc, user, &location).await,
|
||||
| FileOrLocation::File(content) => self.handle_content_file(mxc, user, content).await,
|
||||
| FileOrLocation::Location(location) => self.handle_location(mxc, user, &location).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[implement(super::Service)]
|
||||
async fn fetch_thumbnail_unauthenticated(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, server: Option<&ServerName>, timeout_ms: Duration, dim: &Dim,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
server: Option<&ServerName>,
|
||||
timeout_ms: Duration,
|
||||
dim: &Dim,
|
||||
) -> Result<FileMeta> {
|
||||
use media::get_content_thumbnail::v3::{Request, Response};
|
||||
|
||||
|
@ -123,17 +144,10 @@ async fn fetch_thumbnail_unauthenticated(
|
|||
};
|
||||
|
||||
let Response {
|
||||
file,
|
||||
content_type,
|
||||
content_disposition,
|
||||
..
|
||||
file, content_type, content_disposition, ..
|
||||
} = self.federation_request(mxc, user, server, request).await?;
|
||||
|
||||
let content = Content {
|
||||
file,
|
||||
content_type,
|
||||
content_disposition,
|
||||
};
|
||||
let content = Content { file, content_type, content_disposition };
|
||||
|
||||
self.handle_thumbnail_file(mxc, user, dim, content).await
|
||||
}
|
||||
|
@ -141,7 +155,11 @@ async fn fetch_thumbnail_unauthenticated(
|
|||
#[allow(deprecated)]
|
||||
#[implement(super::Service)]
|
||||
async fn fetch_content_unauthenticated(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, server: Option<&ServerName>, timeout_ms: Duration,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
server: Option<&ServerName>,
|
||||
timeout_ms: Duration,
|
||||
) -> Result<FileMeta> {
|
||||
use media::get_content::v3::{Request, Response};
|
||||
|
||||
|
@ -154,27 +172,27 @@ async fn fetch_content_unauthenticated(
|
|||
};
|
||||
|
||||
let Response {
|
||||
file,
|
||||
content_type,
|
||||
content_disposition,
|
||||
..
|
||||
file, content_type, content_disposition, ..
|
||||
} = self.federation_request(mxc, user, server, request).await?;
|
||||
|
||||
let content = Content {
|
||||
file,
|
||||
content_type,
|
||||
content_disposition,
|
||||
};
|
||||
let content = Content { file, content_type, content_disposition };
|
||||
|
||||
self.handle_content_file(mxc, user, content).await
|
||||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
async fn handle_thumbnail_file(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, dim: &Dim, content: Content,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
dim: &Dim,
|
||||
content: Content,
|
||||
) -> Result<FileMeta> {
|
||||
let content_disposition =
|
||||
make_content_disposition(content.content_disposition.as_ref(), content.content_type.as_deref(), None);
|
||||
let content_disposition = make_content_disposition(
|
||||
content.content_disposition.as_ref(),
|
||||
content.content_type.as_deref(),
|
||||
None,
|
||||
);
|
||||
|
||||
self.upload_thumbnail(
|
||||
mxc,
|
||||
|
@ -193,9 +211,17 @@ async fn handle_thumbnail_file(
|
|||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
async fn handle_content_file(&self, mxc: &Mxc<'_>, user: Option<&UserId>, content: Content) -> Result<FileMeta> {
|
||||
let content_disposition =
|
||||
make_content_disposition(content.content_disposition.as_ref(), content.content_type.as_deref(), None);
|
||||
async fn handle_content_file(
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
content: Content,
|
||||
) -> Result<FileMeta> {
|
||||
let content_disposition = make_content_disposition(
|
||||
content.content_disposition.as_ref(),
|
||||
content.content_type.as_deref(),
|
||||
None,
|
||||
);
|
||||
|
||||
self.create(
|
||||
mxc,
|
||||
|
@ -213,7 +239,12 @@ async fn handle_content_file(&self, mxc: &Mxc<'_>, user: Option<&UserId>, conten
|
|||
}
|
||||
|
||||
#[implement(super::Service)]
|
||||
async fn handle_location(&self, mxc: &Mxc<'_>, user: Option<&UserId>, location: &str) -> Result<FileMeta> {
|
||||
async fn handle_location(
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
location: &str,
|
||||
) -> Result<FileMeta> {
|
||||
self.location_request(location).await.map_err(|error| {
|
||||
err!(Request(NotFound(
|
||||
debug_warn!(%mxc, ?user, ?location, ?error, "Fetching media from location failed")
|
||||
|
@ -263,7 +294,11 @@ async fn location_request(&self, location: &str) -> Result<FileMeta> {
|
|||
|
||||
#[implement(super::Service)]
|
||||
async fn federation_request<Request>(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, server: Option<&ServerName>, request: Request,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
server: Option<&ServerName>,
|
||||
request: Request,
|
||||
) -> Result<Request::IncomingResponse>
|
||||
where
|
||||
Request: OutgoingRequest + Send + Debug,
|
||||
|
@ -277,7 +312,12 @@ where
|
|||
|
||||
// Handles and adjusts the error for the caller to determine if they should
|
||||
// request the fallback endpoint or give up.
|
||||
fn handle_federation_error(mxc: &Mxc<'_>, user: Option<&UserId>, server: Option<&ServerName>, error: Error) -> Error {
|
||||
fn handle_federation_error(
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
server: Option<&ServerName>,
|
||||
error: Error,
|
||||
) -> Error {
|
||||
let fallback = || {
|
||||
err!(Request(NotFound(
|
||||
debug_error!(%mxc, ?user, ?server, ?error, "Remote media not found")
|
||||
|
@ -303,7 +343,8 @@ fn handle_federation_error(mxc: &Mxc<'_>, user: Option<&UserId>, server: Option<
|
|||
#[implement(super::Service)]
|
||||
#[allow(deprecated)]
|
||||
pub async fn fetch_remote_thumbnail_legacy(
|
||||
&self, body: &media::get_content_thumbnail::v3::Request,
|
||||
&self,
|
||||
body: &media::get_content_thumbnail::v3::Request,
|
||||
) -> Result<media::get_content_thumbnail::v3::Response> {
|
||||
let mxc = Mxc {
|
||||
server_name: &body.server_name,
|
||||
|
@ -315,20 +356,17 @@ pub async fn fetch_remote_thumbnail_legacy(
|
|||
let reponse = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
mxc.server_name,
|
||||
media::get_content_thumbnail::v3::Request {
|
||||
allow_remote: body.allow_remote,
|
||||
height: body.height,
|
||||
width: body.width,
|
||||
method: body.method.clone(),
|
||||
server_name: body.server_name.clone(),
|
||||
media_id: body.media_id.clone(),
|
||||
timeout_ms: body.timeout_ms,
|
||||
allow_redirect: body.allow_redirect,
|
||||
animated: body.animated,
|
||||
},
|
||||
)
|
||||
.send_federation_request(mxc.server_name, media::get_content_thumbnail::v3::Request {
|
||||
allow_remote: body.allow_remote,
|
||||
height: body.height,
|
||||
width: body.width,
|
||||
method: body.method.clone(),
|
||||
server_name: body.server_name.clone(),
|
||||
media_id: body.media_id.clone(),
|
||||
timeout_ms: body.timeout_ms,
|
||||
allow_redirect: body.allow_redirect,
|
||||
animated: body.animated,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
|
||||
|
@ -341,27 +379,30 @@ pub async fn fetch_remote_thumbnail_legacy(
|
|||
#[implement(super::Service)]
|
||||
#[allow(deprecated)]
|
||||
pub async fn fetch_remote_content_legacy(
|
||||
&self, mxc: &Mxc<'_>, allow_redirect: bool, timeout_ms: Duration,
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
allow_redirect: bool,
|
||||
timeout_ms: Duration,
|
||||
) -> Result<media::get_content::v3::Response, Error> {
|
||||
self.check_legacy_freeze()?;
|
||||
self.check_fetch_authorized(mxc)?;
|
||||
let response = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
mxc.server_name,
|
||||
media::get_content::v3::Request {
|
||||
allow_remote: true,
|
||||
server_name: mxc.server_name.into(),
|
||||
media_id: mxc.media_id.into(),
|
||||
timeout_ms,
|
||||
allow_redirect,
|
||||
},
|
||||
)
|
||||
.send_federation_request(mxc.server_name, media::get_content::v3::Request {
|
||||
allow_remote: true,
|
||||
server_name: mxc.server_name.into(),
|
||||
media_id: mxc.media_id.into(),
|
||||
timeout_ms,
|
||||
allow_redirect,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let content_disposition =
|
||||
make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None);
|
||||
let content_disposition = make_content_disposition(
|
||||
response.content_disposition.as_ref(),
|
||||
response.content_type.as_deref(),
|
||||
None,
|
||||
);
|
||||
|
||||
self.create(
|
||||
mxc,
|
||||
|
|
|
@ -13,7 +13,12 @@ async fn long_file_names_works() {
|
|||
|
||||
impl Data for MockedKVDatabase {
|
||||
fn create_file_metadata(
|
||||
&self, _sender_user: Option<&str>, mxc: String, width: u32, height: u32, content_disposition: Option<&str>,
|
||||
&self,
|
||||
_sender_user: Option<&str>,
|
||||
mxc: String,
|
||||
width: u32,
|
||||
height: u32,
|
||||
content_disposition: Option<&str>,
|
||||
content_type: Option<&str>,
|
||||
) -> Result<Vec<u8>> {
|
||||
// copied from src/database/key_value/media.rs
|
||||
|
@ -46,14 +51,22 @@ async fn long_file_names_works() {
|
|||
fn get_all_media_keys(&self) -> Vec<Vec<u8>> { todo!() }
|
||||
|
||||
fn search_file_metadata(
|
||||
&self, _mxc: String, _width: u32, _height: u32,
|
||||
&self,
|
||||
_mxc: String,
|
||||
_width: u32,
|
||||
_height: u32,
|
||||
) -> Result<(Option<String>, Option<String>, Vec<u8>)> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn remove_url_preview(&self, _url: &str) -> Result<()> { todo!() }
|
||||
|
||||
fn set_url_preview(&self, _url: &str, _data: &UrlPreviewData, _timestamp: std::time::Duration) -> Result<()> {
|
||||
fn set_url_preview(
|
||||
&self,
|
||||
_url: &str,
|
||||
_data: &UrlPreviewData,
|
||||
_timestamp: std::time::Duration,
|
||||
) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
@ -64,11 +77,18 @@ async fn long_file_names_works() {
|
|||
let mxc = "mxc://example.com/ascERGshawAWawugaAcauga".to_owned();
|
||||
let width = 100;
|
||||
let height = 100;
|
||||
let content_disposition = "attachment; filename=\"this is a very long file name with spaces and special \
|
||||
characters like äöüß and even emoji like 🦀.png\"";
|
||||
let content_disposition = "attachment; filename=\"this is a very long file name with spaces \
|
||||
and special characters like äöüß and even emoji like 🦀.png\"";
|
||||
let content_type = "image/png";
|
||||
let key = db
|
||||
.create_file_metadata(None, mxc, width, height, Some(content_disposition), Some(content_type))
|
||||
.create_file_metadata(
|
||||
None,
|
||||
mxc,
|
||||
width,
|
||||
height,
|
||||
Some(content_disposition),
|
||||
Some(content_type),
|
||||
)
|
||||
.unwrap();
|
||||
let mut r = PathBuf::from("/tmp/media");
|
||||
// r.push(base64::encode_config(key, base64::URL_SAFE_NO_PAD));
|
||||
|
|
|
@ -22,12 +22,17 @@ impl super::Service {
|
|||
/// Uploads or replaces a file thumbnail.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn upload_thumbnail(
|
||||
&self, mxc: &Mxc<'_>, user: Option<&UserId>, content_disposition: Option<&ContentDisposition>,
|
||||
content_type: Option<&str>, dim: &Dim, file: &[u8],
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
user: Option<&UserId>,
|
||||
content_disposition: Option<&ContentDisposition>,
|
||||
content_type: Option<&str>,
|
||||
dim: &Dim,
|
||||
file: &[u8],
|
||||
) -> Result<()> {
|
||||
let key = self
|
||||
.db
|
||||
.create_file_metadata(mxc, user, dim, content_disposition, content_type)?;
|
||||
let key =
|
||||
self.db
|
||||
.create_file_metadata(mxc, user, dim, content_disposition, content_type)?;
|
||||
|
||||
//TODO: Dangling metadata in database if creation fails
|
||||
let mut f = self.create_media_file(&key).await?;
|
||||
|
@ -78,7 +83,12 @@ impl super::Service {
|
|||
|
||||
/// 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>> {
|
||||
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)
|
||||
|
@ -117,11 +127,7 @@ impl super::Service {
|
|||
|
||||
fn thumbnail_generate(image: &DynamicImage, requested: &Dim) -> Result<DynamicImage> {
|
||||
let thumbnail = if !requested.crop() {
|
||||
let Dim {
|
||||
width,
|
||||
height,
|
||||
..
|
||||
} = requested.scaled(&Dim {
|
||||
let Dim { width, height, .. } = requested.scaled(&Dim {
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
..Dim::default()
|
||||
|
@ -202,12 +208,12 @@ impl Dim {
|
|||
#[must_use]
|
||||
pub fn normalized(&self) -> Self {
|
||||
match (self.width, self.height) {
|
||||
(0..=32, 0..=32) => Self::new(32, 32, Some(Method::Crop)),
|
||||
(0..=96, 0..=96) => Self::new(96, 96, Some(Method::Crop)),
|
||||
(0..=320, 0..=240) => Self::new(320, 240, Some(Method::Scale)),
|
||||
(0..=640, 0..=480) => Self::new(640, 480, Some(Method::Scale)),
|
||||
(0..=800, 0..=600) => Self::new(800, 600, Some(Method::Scale)),
|
||||
_ => Self::default(),
|
||||
| (0..=32, 0..=32) => Self::new(32, 32, Some(Method::Crop)),
|
||||
| (0..=96, 0..=96) => Self::new(96, 96, Some(Method::Crop)),
|
||||
| (0..=320, 0..=240) => Self::new(320, 240, Some(Method::Scale)),
|
||||
| (0..=640, 0..=480) => Self::new(640, 480, Some(Method::Scale)),
|
||||
| (0..=800, 0..=600) => Self::new(800, 600, Some(Method::Scale)),
|
||||
| _ => Self::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue