dialtone_axum 0.1.0

Dialtone Axum Back-end
Documentation
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}"))
}