Move some functions from utils::files to media module

This commit is contained in:
silverpill 2023-02-12 18:54:29 +00:00
parent 0988c0c91e
commit 2acf50fa01
13 changed files with 98 additions and 96 deletions

View file

@ -15,6 +15,7 @@ use mitra::ethereum::{
sync::save_current_block_number, sync::save_current_block_number,
utils::key_to_ethereum_address, utils::key_to_ethereum_address,
}; };
use mitra::media::remove_files;
use mitra::models::{ use mitra::models::{
attachments::queries::delete_unused_attachments, attachments::queries::delete_unused_attachments,
cleanup::find_orphaned_files, cleanup::find_orphaned_files,
@ -53,7 +54,6 @@ use mitra::utils::{
serialize_private_key, serialize_private_key,
}, },
datetime::{days_before_now, get_min_datetime}, datetime::{days_before_now, get_min_datetime},
files::remove_files,
passwords::hash_password, passwords::hash_password,
}; };

View file

@ -22,6 +22,7 @@ use crate::activitypub::{
}; };
use crate::config::Instance; use crate::config::Instance;
use crate::errors::ValidationError; use crate::errors::ValidationError;
use crate::media::get_file_url;
use crate::models::profiles::types::{ use crate::models::profiles::types::{
ExtraField, ExtraField,
IdentityProof, IdentityProof,
@ -29,7 +30,6 @@ use crate::models::profiles::types::{
}; };
use crate::models::users::types::User; use crate::models::users::types::User;
use crate::utils::crypto_rsa::{deserialize_private_key, get_public_key_pem}; use crate::utils::crypto_rsa::{deserialize_private_key, get_public_key_pem};
use crate::utils::files::get_file_url;
use crate::utils::urls::get_hostname; use crate::utils::urls::get_hostname;
use crate::webfinger::types::ActorAddress; use crate::webfinger::types::ActorAddress;
use super::attachments::{ use super::attachments::{

View file

@ -26,6 +26,7 @@ use crate::activitypub::{
}; };
use crate::config::Instance; use crate::config::Instance;
use crate::database::{DatabaseClient, DatabaseError}; use crate::database::{DatabaseClient, DatabaseError};
use crate::media::get_file_url;
use crate::models::{ use crate::models::{
emojis::types::DbEmoji, emojis::types::DbEmoji,
posts::queries::get_post_author, posts::queries::get_post_author,
@ -33,7 +34,6 @@ use crate::models::{
relationships::queries::{get_followers, get_subscribers}, relationships::queries::{get_followers, get_subscribers},
users::types::User, users::types::User,
}; };
use crate::utils::files::get_file_url;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Serialize)] #[derive(Serialize)]

View file

@ -15,11 +15,8 @@ use crate::http_signatures::create::{
create_http_signature, create_http_signature,
HttpSignatureError, HttpSignatureError,
}; };
use crate::utils::files::{ use crate::media::{save_file, SUPPORTED_MEDIA_TYPES};
save_file, use crate::utils::files::sniff_media_type;
sniff_media_type,
SUPPORTED_MEDIA_TYPES,
};
use crate::utils::urls::guess_protocol; use crate::utils::urls::guess_protocol;
use crate::webfinger::types::{ActorAddress, JsonResourceDescriptor}; use crate::webfinger::types::{ActorAddress, JsonResourceDescriptor};

View file

@ -12,6 +12,7 @@ pub mod job_queue;
mod json_signatures; mod json_signatures;
pub mod logger; pub mod logger;
pub mod mastodon_api; pub mod mastodon_api;
pub mod media;
pub mod models; pub mod models;
pub mod monero; pub mod monero;
pub mod nodeinfo; pub mod nodeinfo;

View file

@ -10,6 +10,7 @@ use crate::mastodon_api::{
pagination::PageSize, pagination::PageSize,
uploads::{save_b64_file, UploadError}, uploads::{save_b64_file, UploadError},
}; };
use crate::media::get_file_url;
use crate::models::{ use crate::models::{
profiles::types::{ profiles::types::{
DbActorProfile, DbActorProfile,
@ -27,10 +28,7 @@ use crate::models::{
User, User,
}, },
}; };
use crate::utils::{ use crate::utils::markdown::markdown_basic_to_html;
files::get_file_url,
markdown::markdown_basic_to_html,
};
/// https://docs.joinmastodon.org/entities/field/ /// https://docs.joinmastodon.org/entities/field/
#[derive(Serialize)] #[derive(Serialize)]

View file

@ -1,7 +1,7 @@
use serde::Serialize; use serde::Serialize;
use crate::media::get_file_url;
use crate::models::emojis::types::DbEmoji; use crate::models::emojis::types::DbEmoji;
use crate::utils::files::get_file_url;
/// https://docs.joinmastodon.org/entities/CustomEmoji/ /// https://docs.joinmastodon.org/entities/CustomEmoji/
#[derive(Serialize)] #[derive(Serialize)]

View file

@ -12,11 +12,9 @@ use crate::mastodon_api::{
MASTODON_API_VERSION, MASTODON_API_VERSION,
uploads::UPLOAD_MAX_SIZE, uploads::UPLOAD_MAX_SIZE,
}; };
use crate::media::SUPPORTED_MEDIA_TYPES;
use crate::models::posts::validators::ATTACHMENTS_MAX_NUM; use crate::models::posts::validators::ATTACHMENTS_MAX_NUM;
use crate::utils::{ use crate::utils::markdown::markdown_to_html;
files::SUPPORTED_MEDIA_TYPES,
markdown::markdown_to_html,
};
#[derive(Serialize)] #[derive(Serialize)]
struct InstanceStats { struct InstanceStats {

View file

@ -1,11 +1,11 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use crate::media::get_file_url;
use crate::models::attachments::types::{ use crate::models::attachments::types::{
AttachmentType, AttachmentType,
DbMediaAttachment, DbMediaAttachment,
}; };
use crate::utils::files::get_file_url;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct AttachmentCreateData { pub struct AttachmentCreateData {

View file

@ -1,11 +1,8 @@
use std::path::Path; use std::path::Path;
use crate::errors::HttpError; use crate::errors::HttpError;
use crate::utils::files::{ use crate::media::{save_file, SUPPORTED_MEDIA_TYPES};
save_file, use crate::utils::files::sniff_media_type;
sniff_media_type,
SUPPORTED_MEDIA_TYPES,
};
pub const UPLOAD_MAX_SIZE: usize = 1024 * 1024 * 5; pub const UPLOAD_MAX_SIZE: usize = 1024 * 1024 * 5;

79
src/media.rs Normal file
View file

@ -0,0 +1,79 @@
use std::fs::remove_file;
use std::io::Error;
use std::path::Path;
use sha2::{Digest, Sha256};
use crate::utils::files::{get_media_type_extension, write_file};
pub const SUPPORTED_MEDIA_TYPES: [&str; 8] = [
"audio/mpeg",
"image/apng",
"image/gif",
"image/jpeg",
"image/png",
"image/webp",
"video/mp4",
"video/webm",
];
/// Generates unique file name based on file contents
fn get_file_name(data: &[u8], media_type: Option<&str>) -> String {
let digest = Sha256::digest(data);
let mut file_name = hex::encode(digest);
let maybe_extension = media_type
.and_then(get_media_type_extension);
if let Some(extension) = maybe_extension {
// Append extension for known media types
file_name = format!("{}.{}", file_name, extension);
};
file_name
}
/// Save validated file to specified directory
pub fn save_file(
data: Vec<u8>,
output_dir: &Path,
media_type: Option<&str>,
) -> Result<String, Error> {
let file_name = get_file_name(&data, media_type);
let file_path = output_dir.join(&file_name);
write_file(&data, &file_path)?;
Ok(file_name)
}
pub fn get_file_url(instance_url: &str, file_name: &str) -> String {
format!("{}/media/{}", instance_url, file_name)
}
pub fn remove_files(files: Vec<String>, from_dir: &Path) -> () {
for file_name in files {
let file_path = from_dir.join(file_name);
let file_path_str = file_path.to_string_lossy();
match remove_file(&file_path) {
Ok(_) => log::info!("removed file {}", file_path_str),
Err(err) => {
log::warn!("failed to remove file {} ({})", file_path_str, err);
},
};
};
}
#[cfg(test)]
mod tests {
use crate::utils::files::sniff_media_type;
use super::*;
#[test]
fn test_get_file_name() {
let mut data = vec![];
data.extend_from_slice(b"\x89PNG\x0D\x0A\x1A\x0A");
let media_type = sniff_media_type(&data);
let file_name = get_file_name(&data, media_type.as_deref());
assert_eq!(
file_name,
"4c4b6a3be1314ab86138bef4314dde022e600960d8689a2c8f8631802d20dab6.png",
);
}
}

View file

@ -1,7 +1,7 @@
use crate::config::Config; use crate::config::Config;
use crate::database::{DatabaseClient, DatabaseError}; use crate::database::{DatabaseClient, DatabaseError};
use crate::ipfs::store as ipfs_store; use crate::ipfs::store as ipfs_store;
use crate::utils::files::remove_files; use crate::media::remove_files;
pub struct DeletionQueue { pub struct DeletionQueue {
pub files: Vec<String>, pub files: Vec<String>,

View file

@ -1,5 +1,4 @@
use std::fs::{ use std::fs::{
remove_file,
set_permissions, set_permissions,
File, File,
Permissions, Permissions,
@ -11,35 +10,15 @@ use std::path::Path;
use mime_guess::get_mime_extensions_str; use mime_guess::get_mime_extensions_str;
use mime_sniffer::MimeTypeSniffer; use mime_sniffer::MimeTypeSniffer;
use sha2::{Digest, Sha256};
pub const SUPPORTED_MEDIA_TYPES: [&str; 8] = [
"audio/mpeg",
"image/apng",
"image/gif",
"image/jpeg",
"image/png",
"image/webp",
"video/mp4",
"video/webm",
];
pub fn sniff_media_type(data: &[u8]) -> Option<String> { pub fn sniff_media_type(data: &[u8]) -> Option<String> {
data.sniff_mime_type().map(|val| val.to_string()) data.sniff_mime_type().map(|val| val.to_string())
} }
/// Generates unique file name based on file contents pub fn get_media_type_extension(media_type: &str) -> Option<String> {
fn get_file_name(data: &[u8], media_type: Option<&str>) -> String { get_mime_extensions_str(media_type)
let digest = Sha256::digest(data); .and_then(|extensions| extensions.first())
let mut file_name = hex::encode(digest); .map(|extension| extension.to_string())
let maybe_extension = media_type
.and_then(get_mime_extensions_str)
.and_then(|extensions| extensions.first());
if let Some(extension) = maybe_extension {
// Append extension for known media types
file_name = format!("{}.{}", file_name, extension);
};
file_name
} }
pub fn write_file(data: &[u8], file_path: &Path) -> Result<(), Error> { pub fn write_file(data: &[u8], file_path: &Path) -> Result<(), Error> {
@ -53,50 +32,3 @@ pub fn set_file_permissions(file_path: &Path, mode: u32) -> Result<(), Error> {
set_permissions(file_path, permissions)?; set_permissions(file_path, permissions)?;
Ok(()) Ok(())
} }
/// Save validated file to specified directory
pub fn save_file(
data: Vec<u8>,
output_dir: &Path,
media_type: Option<&str>,
) -> Result<String, Error> {
let file_name = get_file_name(&data, media_type);
let file_path = output_dir.join(&file_name);
write_file(&data, &file_path)?;
Ok(file_name)
}
pub fn get_file_url(instance_url: &str, file_name: &str) -> String {
format!("{}/media/{}", instance_url, file_name)
}
pub fn remove_files(files: Vec<String>, from_dir: &Path) -> () {
for file_name in files {
let file_path = from_dir.join(file_name);
let file_path_str = file_path.to_string_lossy();
match remove_file(&file_path) {
Ok(_) => log::info!("removed file {}", file_path_str),
Err(err) => {
log::warn!("failed to remove file {} ({})", file_path_str, err);
},
};
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_file_name() {
let mut data = vec![];
data.extend_from_slice(b"\x89PNG\x0D\x0A\x1A\x0A");
let media_type = data.sniff_mime_type();
let file_name = get_file_name(&data, media_type);
assert_eq!(
file_name,
"4c4b6a3be1314ab86138bef4314dde022e600960d8689a2c8f8631802d20dab6.png",
);
}
}