nexus_common/media/
mod.rs1use 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 if variant == FileVariant::Main {
75 return true;
76 }
77
78 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}