use std::{
env::{self, VarError},
error::Error,
fmt::Display,
future::Future,
path::{Path, PathBuf},
};
use dialtone_common::ap::ap_object::ApObjectMediaType;
use lazy_static::lazy_static;
use tokio::fs::{create_dir_all, metadata, read_dir, DirEntry};
pub const MEDIA_DIR_VAR: &str = "MEDIA";
lazy_static! {
pub static ref MEDIA_DIR: Result<String, VarError> = env::var(MEDIA_DIR_VAR);
}
#[derive(Debug)]
pub enum MediaError {
MediaEnvironmentVariableNotResolved,
MediaPathInvalid,
MediaIoError,
DatabaseError,
ErrorWhileVisiting,
}
impl Error for MediaError {}
impl Display for MediaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MediaError::MediaEnvironmentVariableNotResolved => {
f.write_str("Media Error: MEDIA environment variable not set")
}
MediaError::MediaPathInvalid => f.write_str("Media Error: Invalid Path to Media"),
MediaError::MediaIoError => f.write_str("Media Error: I/O Error"),
MediaError::DatabaseError => f.write_str("Media Error: Database Error"),
MediaError::ErrorWhileVisiting => f.write_str("Media Error: Closure Visitation Error"),
}
}
}
impl From<std::io::Error> for MediaError {
fn from(_: std::io::Error) -> Self {
MediaError::MediaIoError
}
}
impl From<sqlx::Error> for MediaError {
fn from(_: sqlx::Error) -> Self {
MediaError::DatabaseError
}
}
impl From<anyhow::Error> for MediaError {
fn from(_: anyhow::Error) -> Self {
MediaError::ErrorWhileVisiting
}
}
fn media_path(host_name: &str, pun: &str) -> Result<String, MediaError> {
let media_dir = MEDIA_DIR
.as_deref()
.map_err(|_| MediaError::MediaEnvironmentVariableNotResolved)?;
let pathbuf: PathBuf = [media_dir, host_name, pun].iter().collect();
let path = pathbuf.to_str().ok_or(MediaError::MediaPathInvalid)?;
Ok(path.to_string())
}
pub async fn create_media_dir(host_name: &str, pun: &str) -> Result<(), MediaError> {
let path = media_path(host_name, pun)?;
create_dir_all(path).await?;
Ok(())
}
pub async fn media_dir_exists(host_name: &str, pun: &str) -> Result<bool, MediaError> {
let path = media_path(host_name, pun)?;
let result = metadata(path).await;
if result.is_err() {
Ok(false)
} else {
Ok(true)
}
}
pub async fn visit_media_dir<'a, F, Fut>(
host_name: &str,
pun: &str,
cb: F,
) -> Result<(), MediaError>
where
F: Fn(DirEntry) -> Fut,
Fut: Future<Output = Result<(), MediaError>>,
{
let path = media_path(host_name, pun)?;
let mut read_dir = read_dir(path).await?;
let mut entry = read_dir.next_entry().await?;
loop {
if let Some(entry) = entry {
cb(entry).await?;
} else {
break;
}
entry = read_dir.next_entry().await?;
}
Ok(())
}
pub fn media_type_from_file_name(file_name: &str) -> Option<ApObjectMediaType> {
let extension = Path::new(file_name).extension();
if let Some(extension) = extension {
let extension = extension.to_string_lossy().to_lowercase();
match extension.as_str() {
"jpeg" | "jpg" => Some(ApObjectMediaType::ImageJpeg),
"gif" => Some(ApObjectMediaType::ImageGif),
"png" => Some(ApObjectMediaType::ImagePng),
_ => None,
}
} else {
None
}
}
pub fn owned_media_url(host_name: &str, pun: &str, file_name: &str) -> Result<String, MediaError> {
Ok(format!("https://{host_name}/media/{host_name}/{pun}/{file_name}"))
}