Skip to main content

nexus_common/media/
mod.rs

1use crate::{
2    models::file::{FileDetails, FileUrls},
3    types::DynError,
4};
5use processors::{ImageProcessor, VariantProcessor, VideoProcessor};
6use serde::{Deserialize, Serialize};
7use std::{
8    fmt::Display,
9    path::{Path, PathBuf},
10    str::FromStr,
11};
12use tokio::fs;
13use utoipa::ToSchema;
14
15pub mod processors;
16
17#[derive(Debug, PartialEq, Serialize, Deserialize, ToSchema, Clone)]
18#[serde(rename_all = "lowercase")]
19pub enum FileVariant {
20    Main,
21    Feed,
22    Small,
23}
24
25impl FromStr for FileVariant {
26    type Err = DynError;
27
28    fn from_str(s: &str) -> Result<Self, Self::Err> {
29        match s {
30            "main" => Ok(FileVariant::Main),
31            "feed" => Ok(FileVariant::Feed),
32            "small" => Ok(FileVariant::Small),
33            _ => Err("Invalid file version".into()),
34        }
35    }
36}
37
38impl Display for FileVariant {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        let version_string = match self {
41            FileVariant::Main => "main",
42            FileVariant::Feed => "feed",
43            FileVariant::Small => "small",
44        };
45        write!(f, "{version_string}")
46    }
47}
48
49pub struct VariantController;
50
51impl VariantController {
52    pub async fn create_file_variant(
53        file: &FileDetails,
54        variant: &FileVariant,
55        file_path: PathBuf,
56    ) -> Result<String, DynError> {
57        match &file.content_type {
58            content_type if content_type.starts_with("image/") => {
59                ImageProcessor::create_variant(file, variant, file_path).await
60            }
61            content_type if content_type.starts_with("video/") => {
62                VideoProcessor::create_variant(file, variant, file_path).await
63            }
64            _ => Err(format!("Unsupported content type: {}", file.content_type).into()),
65        }
66    }
67
68    pub async fn check_variant_exists(
69        file: &FileDetails,
70        variant: FileVariant,
71        file_path: PathBuf,
72    ) -> bool {
73        // main variant always exists
74        if variant == FileVariant::Main {
75            return true;
76        }
77
78        // if file exists, variant has already been created
79        let path = file_path
80            .join(file.owner_id.as_str())
81            .join(file.id.as_str())
82            .join(variant.to_string());
83
84        fs::metadata(path).await.is_ok()
85    }
86
87    pub fn get_content_type_for_variant(file: &FileDetails, variant: &FileVariant) -> String {
88        match &file.content_type {
89            content_type if content_type.starts_with("image/") => {
90                ImageProcessor::get_content_type_for_variant(file, variant)
91            }
92            content_type if content_type.starts_with("video/") => {
93                VideoProcessor::get_content_type_for_variant(file, variant)
94            }
95            _ => file.content_type.clone(),
96        }
97    }
98
99    pub fn get_file_urls_by_content_type(content_type: &str, path: &Path) -> FileUrls {
100        let variants = Self::get_valid_variants_for_content_type(content_type);
101
102        FileUrls::new(path, &variants)
103    }
104
105    pub fn validate_variant_for_content_type(content_type: &str, variant: &FileVariant) -> bool {
106        if variant == &FileVariant::Main {
107            return true;
108        }
109        let valid_variants = Self::get_valid_variants_for_content_type(content_type);
110        valid_variants.contains(variant)
111    }
112
113    fn get_valid_variants_for_content_type(content_type: &str) -> Vec<FileVariant> {
114        match content_type {
115            value if value.starts_with("image") => {
116                ImageProcessor::get_valid_variants_for_content_type(content_type)
117            }
118            value if value.starts_with("video") => {
119                VideoProcessor::get_valid_variants_for_content_type(content_type)
120            }
121            _ => vec![],
122        }
123    }
124}