use crate::{ config::primitives::{LogFormat, Targets}, formats::{AnimationFormat, AudioCodec, ImageFormat, VideoCodec}, serde_str::Serde, }; use clap::{Parser, Subcommand}; use std::{net::SocketAddr, path::PathBuf}; use url::Url; use super::primitives::RetentionValue; impl Args { pub(super) fn into_output(self) -> Output { let Args { config_file, old_repo_path, old_repo_cache_capacity, log_format, log_targets, console_address, console_buffer_capacity, opentelemetry_url, opentelemetry_service_name, opentelemetry_targets, save_to, command, } = self; let old_repo = OldSled { path: old_repo_path, cache_capacity: old_repo_cache_capacity, } .set(); let tracing = Tracing { logging: Logging { format: log_format, targets: log_targets.map(Serde::new), }, console: Console { address: console_address, buffer_capacity: console_buffer_capacity, }, opentelemetry: OpenTelemetry { url: opentelemetry_url, service_name: opentelemetry_service_name, targets: opentelemetry_targets.map(Serde::new), }, }; match command { Command::Run(Run { address, api_key, client_timeout, upgrade_concurrency, metrics_prometheus_address, media_preprocess_steps, media_external_validation, media_external_validation_timeout, media_max_file_size, media_process_timeout, media_retention_variants, media_retention_proxy, media_image_max_width, media_image_max_height, media_image_max_area, media_image_max_file_size, media_image_format, media_image_quality_avif, media_image_quality_jpeg, media_image_quality_jxl, media_image_quality_png, media_image_quality_webp, media_animation_max_width, media_animation_max_height, media_animation_max_area, media_animation_max_file_size, media_animation_max_frame_count, media_animation_format, media_animation_quality_apng, media_animation_quality_avif, media_animation_quality_webp, media_video_disable, media_video_allow_audio, media_video_max_width, media_video_max_height, media_video_max_area, media_video_max_file_size, media_video_max_frame_count, media_video_codec, media_video_audio_codec, media_video_quality_max, media_video_quality_240, media_video_quality_360, media_video_quality_480, media_video_quality_720, media_video_quality_1080, media_video_quality_1440, media_video_quality_2160, media_filters, read_only, danger_dummy_mode, max_file_count, store, }) => { let server = Server { address, api_key, read_only, danger_dummy_mode, max_file_count, }; let client = Client { timeout: client_timeout, }; let upgrade = Upgrade { concurrency: upgrade_concurrency, }; let metrics = Metrics { prometheus_address: metrics_prometheus_address, }; let retention = Retention { variants: media_retention_variants, proxy: media_retention_proxy, }; let image_quality = ImageQuality { avif: media_image_quality_avif, jpeg: media_image_quality_jpeg, jxl: media_image_quality_jxl, png: media_image_quality_png, webp: media_image_quality_webp, }; let image = Image { max_file_size: media_image_max_file_size, max_width: media_image_max_width, max_height: media_image_max_height, max_area: media_image_max_area, format: media_image_format, quality: image_quality.set(), }; let animation_quality = AnimationQuality { apng: media_animation_quality_apng, avif: media_animation_quality_avif, webp: media_animation_quality_webp, }; let animation = Animation { max_file_size: media_animation_max_file_size, max_width: media_animation_max_width, max_height: media_animation_max_height, max_area: media_animation_max_area, max_frame_count: media_animation_max_frame_count, format: media_animation_format, quality: animation_quality.set(), }; let video_quality = VideoQuality { crf_240: media_video_quality_240, crf_360: media_video_quality_360, crf_480: media_video_quality_480, crf_720: media_video_quality_720, crf_1080: media_video_quality_1080, crf_1440: media_video_quality_1440, crf_2160: media_video_quality_2160, crf_max: media_video_quality_max, }; let video = Video { enable: !media_video_disable, allow_audio: media_video_allow_audio, max_file_size: media_video_max_file_size, max_width: media_video_max_width, max_height: media_video_max_height, max_area: media_video_max_area, max_frame_count: media_video_max_frame_count, video_codec: media_video_codec, audio_codec: media_video_audio_codec, quality: video_quality.set(), }; let media = Media { max_file_size: media_max_file_size, process_timeout: media_process_timeout, preprocess_steps: media_preprocess_steps, external_validation: media_external_validation, external_validation_timeout: media_external_validation_timeout, filters: media_filters, retention: retention.set(), image: image.set(), animation: animation.set(), video: video.set(), }; let operation = Operation::Run; match store { Some(RunStore::Filesystem(RunFilesystem { system, repo })) => { let store = Some(Store::Filesystem(system)); Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, store, repo, }, operation, config_file, save_to, } } Some(RunStore::ObjectStorage(RunObjectStorage { storage, repo })) => { let store = Some(Store::ObjectStorage(storage)); Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, store, repo, }, operation, config_file, save_to, } } None => Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, store: None, repo: None, }, operation, config_file, save_to, }, } } Command::MigrateStore(MigrateStore { skip_missing_files, concurrency, store, }) => { let server = Server::default(); let client = Client::default(); let upgrade = Upgrade::default(); let media = Media::default(); let metrics = Metrics::default(); match store { MigrateStoreFrom::Filesystem(MigrateFilesystem { from, to }) => match to { MigrateStoreTo::Filesystem(MigrateFilesystemInner { to, repo }) => Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, store: None, repo, }, operation: Operation::MigrateStore { skip_missing_files, concurrency, from: from.into(), to: to.into(), }, config_file, save_to, }, MigrateStoreTo::ObjectStorage(MigrateObjectStorageInner { to, repo }) => { Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, store: None, repo, }, operation: Operation::MigrateStore { skip_missing_files, concurrency, from: from.into(), to: to.into(), }, config_file, save_to, } } }, MigrateStoreFrom::ObjectStorage(MigrateObjectStorage { from, to }) => { match to { MigrateStoreTo::Filesystem(MigrateFilesystemInner { to, repo }) => { Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, store: None, repo, }, operation: Operation::MigrateStore { skip_missing_files, concurrency, from: from.into(), to: to.into(), }, config_file, save_to, } } MigrateStoreTo::ObjectStorage(MigrateObjectStorageInner { to, repo, }) => Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, store: None, repo, }, operation: Operation::MigrateStore { skip_missing_files, concurrency, from: from.into(), to: to.into(), }, config_file, save_to, }, } } } } Command::MigrateRepo(MigrateRepo { repo }) => { let server = Server::default(); let client = Client::default(); let upgrade = Upgrade::default(); let media = Media::default(); let metrics = Metrics::default(); match repo { MigrateRepoFrom::Sled(MigrateSledRepo { from, to }) => match to { MigrateRepoTo::Sled(MigrateSledInner { to }) => Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, repo: None, store: None, }, operation: Operation::MigrateRepo { from: from.into(), to: to.into(), }, save_to, config_file, }, MigrateRepoTo::Postgres(MigratePostgresInner { to }) => Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, repo: None, store: None, }, operation: Operation::MigrateRepo { from: from.into(), to: to.into(), }, save_to, config_file, }, }, MigrateRepoFrom::Postgres(MigratePostgresRepo { from, to }) => match to { MigrateRepoTo::Sled(MigrateSledInner { to }) => Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, repo: None, store: None, }, operation: Operation::MigrateRepo { from: from.into(), to: to.into(), }, save_to, config_file, }, MigrateRepoTo::Postgres(MigratePostgresInner { to }) => Output { config_format: ConfigFormat { server, client, upgrade, old_repo, tracing, metrics, media, repo: None, store: None, }, operation: Operation::MigrateRepo { from: from.into(), to: to.into(), }, save_to, config_file, }, }, } } } } } pub(super) struct Output { pub(super) config_format: ConfigFormat, pub(super) operation: Operation, pub(super) save_to: Option, pub(super) config_file: Option, } #[allow(clippy::large_enum_variant)] #[derive(Clone)] pub(crate) enum Operation { Run, MigrateStore { skip_missing_files: bool, concurrency: usize, from: crate::config::primitives::Store, to: crate::config::primitives::Store, }, MigrateRepo { from: crate::config::file::Repo, to: crate::config::file::Repo, }, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] pub(super) struct ConfigFormat { server: Server, client: Client, upgrade: Upgrade, #[serde(skip_serializing_if = "Option::is_none")] old_repo: Option, tracing: Tracing, metrics: Metrics, media: Media, #[serde(skip_serializing_if = "Option::is_none")] repo: Option, #[serde(skip_serializing_if = "Option::is_none")] store: Option, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] struct Server { #[serde(skip_serializing_if = "Option::is_none")] address: Option, #[serde(skip_serializing_if = "Option::is_none")] api_key: Option, #[serde(skip_serializing_if = "std::ops::Not::not")] read_only: bool, #[serde(skip_serializing_if = "std::ops::Not::not")] danger_dummy_mode: bool, #[serde(skip_serializing_if = "Option::is_none")] max_file_count: Option, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] struct Client { #[serde(skip_serializing_if = "Option::is_none")] timeout: Option, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] struct Upgrade { #[serde(skip_serializing_if = "Option::is_none")] concurrency: Option, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] struct Tracing { logging: Logging, console: Console, opentelemetry: OpenTelemetry, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] struct Logging { #[serde(skip_serializing_if = "Option::is_none")] format: Option, #[serde(skip_serializing_if = "Option::is_none")] targets: Option>, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] struct Console { #[serde(skip_serializing_if = "Option::is_none")] address: Option, #[serde(skip_serializing_if = "Option::is_none")] buffer_capacity: Option, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] struct OpenTelemetry { #[serde(skip_serializing_if = "Option::is_none")] url: Option, #[serde(skip_serializing_if = "Option::is_none")] service_name: Option, #[serde(skip_serializing_if = "Option::is_none")] targets: Option>, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] struct Metrics { #[serde(skip_serializing_if = "Option::is_none")] prometheus_address: Option, } #[derive(Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] struct Media { #[serde(skip_serializing_if = "Option::is_none")] max_file_size: Option, #[serde(skip_serializing_if = "Option::is_none")] process_timeout: Option, #[serde(skip_serializing_if = "Option::is_none")] preprocess_steps: Option, #[serde(skip_serializing_if = "Option::is_none")] external_validation: Option, #[serde(skip_serializing_if = "Option::is_none")] external_validation_timeout: Option, #[serde(skip_serializing_if = "Option::is_none")] filters: Option>, #[serde(skip_serializing_if = "Option::is_none")] retention: Option, #[serde(skip_serializing_if = "Option::is_none")] image: Option, #[serde(skip_serializing_if = "Option::is_none")] animation: Option, #[serde(skip_serializing_if = "Option::is_none")] video: Option