mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-22 19:31:35 +00:00
Finish redoing config probably
This commit is contained in:
parent
ca28f68ef5
commit
3792a8923a
16 changed files with 856 additions and 253 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -2651,6 +2651,16 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-serde"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -2661,12 +2671,15 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"matchers",
|
"matchers",
|
||||||
"regex",
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
|
"tracing-serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -63,8 +63,11 @@ tracing-futures = "0.2.4"
|
||||||
tracing-log = "0.1.2"
|
tracing-log = "0.1.2"
|
||||||
tracing-opentelemetry = "0.17"
|
tracing-opentelemetry = "0.17"
|
||||||
tracing-subscriber = { version = "0.3.0", features = [
|
tracing-subscriber = { version = "0.3.0", features = [
|
||||||
|
"ansi",
|
||||||
"env-filter",
|
"env-filter",
|
||||||
"fmt",
|
"fmt",
|
||||||
|
"json",
|
||||||
|
"registry",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
] }
|
] }
|
||||||
url = { version = "2.2", features = ["serde"] }
|
url = { version = "2.2", features = ["serde"] }
|
||||||
|
|
39
defaults.toml
Normal file
39
defaults.toml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
[server]
|
||||||
|
address = '0.0.0.0:8080'
|
||||||
|
[tracing.logging]
|
||||||
|
format = 'normal'
|
||||||
|
targets = 'info'
|
||||||
|
|
||||||
|
[tracing.console]
|
||||||
|
buffer_capacity = 102400
|
||||||
|
|
||||||
|
[tracing.opentelemetry]
|
||||||
|
service_name = 'pict-rs'
|
||||||
|
targets = 'info'
|
||||||
|
|
||||||
|
[old_db]
|
||||||
|
path = '/mnt'
|
||||||
|
|
||||||
|
[media]
|
||||||
|
max_width = 10000
|
||||||
|
max_height = 10000
|
||||||
|
max_area = 40000000
|
||||||
|
max_file_size = 40
|
||||||
|
enable_silent_video = true
|
||||||
|
filters = [
|
||||||
|
'crop',
|
||||||
|
'blur',
|
||||||
|
'resize',
|
||||||
|
'identity',
|
||||||
|
'thumbnail',
|
||||||
|
]
|
||||||
|
skip_validate_imports = false
|
||||||
|
|
||||||
|
[repo]
|
||||||
|
type = 'sled'
|
||||||
|
path = '/mnt/sled-repo'
|
||||||
|
cache_capacity = 67108864
|
||||||
|
|
||||||
|
[store]
|
||||||
|
type = 'filesystem'
|
||||||
|
path = '/mnt/files'
|
39
dev.toml
Normal file
39
dev.toml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
[server]
|
||||||
|
address = '0.0.0.0:8080'
|
||||||
|
[tracing.logging]
|
||||||
|
format = 'normal'
|
||||||
|
targets = 'info'
|
||||||
|
|
||||||
|
[tracing.console]
|
||||||
|
buffer_capacity = 102400
|
||||||
|
|
||||||
|
[tracing.opentelemetry]
|
||||||
|
service_name = 'pict-rs'
|
||||||
|
targets = 'info'
|
||||||
|
|
||||||
|
[old_db]
|
||||||
|
path = 'data/'
|
||||||
|
|
||||||
|
[media]
|
||||||
|
max_width = 10000
|
||||||
|
max_height = 10000
|
||||||
|
max_area = 40000000
|
||||||
|
max_file_size = 40
|
||||||
|
enable_silent_video = true
|
||||||
|
filters = [
|
||||||
|
'identity',
|
||||||
|
'resize',
|
||||||
|
'crop',
|
||||||
|
'thumbnail',
|
||||||
|
'blur',
|
||||||
|
]
|
||||||
|
skip_validate_imports = false
|
||||||
|
|
||||||
|
[repo]
|
||||||
|
type = 'sled'
|
||||||
|
path = 'data/sled-repo'
|
||||||
|
cache_capacity = 67108864
|
||||||
|
|
||||||
|
[store]
|
||||||
|
type = 'filesystem'
|
||||||
|
path = 'data/files'
|
|
@ -1,12 +1,45 @@
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
mod commandline;
|
mod commandline;
|
||||||
mod defaults;
|
mod defaults;
|
||||||
mod file;
|
mod file;
|
||||||
mod primitives;
|
mod primitives;
|
||||||
|
|
||||||
use crate::magick::ValidInputType;
|
use commandline::{Args, Output};
|
||||||
|
use config::Config;
|
||||||
|
use defaults::Defaults;
|
||||||
|
|
||||||
pub(crate) use file::ConfigFile as Configuration;
|
pub(crate) use commandline::Operation;
|
||||||
|
pub(crate) use file::{ConfigFile as Configuration, OpenTelemetry, Repo, Sled, Tracing};
|
||||||
|
pub(crate) use primitives::{Filesystem, ImageFormat, LogFormat, ObjectStorage, Store};
|
||||||
|
|
||||||
pub(crate) fn configure() -> anyhow::Result<Configuration> {
|
pub(crate) fn configure() -> anyhow::Result<(Configuration, Operation)> {
|
||||||
unimplemented!()
|
let Output {
|
||||||
|
config_format,
|
||||||
|
operation,
|
||||||
|
save_to,
|
||||||
|
config_file,
|
||||||
|
} = Args::parse().into_output();
|
||||||
|
|
||||||
|
let config = Config::builder().add_source(config::Config::try_from(&Defaults::default())?);
|
||||||
|
|
||||||
|
let config = if let Some(config_file) = config_file {
|
||||||
|
config.add_source(config::File::from(config_file))
|
||||||
|
} else {
|
||||||
|
config
|
||||||
|
};
|
||||||
|
|
||||||
|
let built = config
|
||||||
|
.add_source(config::Environment::with_prefix("PICTRS").separator("__"))
|
||||||
|
.add_source(config::Config::try_from(&config_format)?)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let config: Configuration = built.try_deserialize()?;
|
||||||
|
|
||||||
|
if let Some(save_to) = save_to {
|
||||||
|
let output = toml::to_string_pretty(&config)?;
|
||||||
|
std::fs::write(save_to, output)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((config, operation))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,359 @@
|
||||||
use crate::config::primitives::{ImageFormat, LogFormat, Targets};
|
use crate::{
|
||||||
|
config::primitives::{ImageFormat, LogFormat, Targets},
|
||||||
|
serde_str::Serde,
|
||||||
|
};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use std::{net::SocketAddr, path::PathBuf};
|
use std::{net::SocketAddr, path::PathBuf};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
impl Args {
|
||||||
|
pub(super) fn into_output(self) -> Output {
|
||||||
|
let Args {
|
||||||
|
config_file,
|
||||||
|
old_db_path,
|
||||||
|
log_format,
|
||||||
|
log_targets,
|
||||||
|
console_address,
|
||||||
|
console_buffer_capacity,
|
||||||
|
opentelemetry_url,
|
||||||
|
opentelemetry_service_name,
|
||||||
|
opentelemetry_targets,
|
||||||
|
save_to,
|
||||||
|
command,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let old_db = OldDb { path: old_db_path };
|
||||||
|
|
||||||
|
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,
|
||||||
|
media_skip_validate_imports,
|
||||||
|
media_max_width,
|
||||||
|
media_max_height,
|
||||||
|
media_max_area,
|
||||||
|
media_max_file_size,
|
||||||
|
media_enable_silent_video,
|
||||||
|
media_filters,
|
||||||
|
media_format,
|
||||||
|
store,
|
||||||
|
}) => {
|
||||||
|
let server = Server { address, api_key };
|
||||||
|
let media = Media {
|
||||||
|
skip_validate_imports: media_skip_validate_imports,
|
||||||
|
max_width: media_max_width,
|
||||||
|
max_height: media_max_height,
|
||||||
|
max_area: media_max_area,
|
||||||
|
max_file_size: media_max_file_size,
|
||||||
|
enable_silent_video: media_enable_silent_video,
|
||||||
|
filters: media_filters,
|
||||||
|
format: media_format,
|
||||||
|
};
|
||||||
|
let operation = Operation::Run;
|
||||||
|
|
||||||
|
match store {
|
||||||
|
Some(RunStore::Filesystem(RunFilesystem { system, repo })) => {
|
||||||
|
let store = Some(Store::Filesystem(system));
|
||||||
|
Output {
|
||||||
|
config_format: ConfigFormat {
|
||||||
|
server,
|
||||||
|
old_db,
|
||||||
|
tracing,
|
||||||
|
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,
|
||||||
|
old_db,
|
||||||
|
tracing,
|
||||||
|
media,
|
||||||
|
store,
|
||||||
|
repo,
|
||||||
|
},
|
||||||
|
operation,
|
||||||
|
config_file,
|
||||||
|
save_to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Output {
|
||||||
|
config_format: ConfigFormat {
|
||||||
|
server,
|
||||||
|
old_db,
|
||||||
|
tracing,
|
||||||
|
media,
|
||||||
|
store: None,
|
||||||
|
repo: None,
|
||||||
|
},
|
||||||
|
operation,
|
||||||
|
config_file,
|
||||||
|
save_to,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::MigrateStore(migrate_store) => {
|
||||||
|
let server = Server::default();
|
||||||
|
let media = Media::default();
|
||||||
|
|
||||||
|
match migrate_store {
|
||||||
|
MigrateStore::Filesystem(MigrateFilesystem { from, to }) => match to {
|
||||||
|
MigrateStoreInner::Filesystem(MigrateFilesystemInner { to, repo }) => {
|
||||||
|
Output {
|
||||||
|
config_format: ConfigFormat {
|
||||||
|
server,
|
||||||
|
old_db,
|
||||||
|
tracing,
|
||||||
|
media,
|
||||||
|
store: None,
|
||||||
|
repo,
|
||||||
|
},
|
||||||
|
operation: Operation::MigrateStore {
|
||||||
|
from: from.into(),
|
||||||
|
to: to.into(),
|
||||||
|
},
|
||||||
|
config_file,
|
||||||
|
save_to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MigrateStoreInner::ObjectStorage(MigrateObjectStorageInner {
|
||||||
|
to,
|
||||||
|
repo,
|
||||||
|
}) => Output {
|
||||||
|
config_format: ConfigFormat {
|
||||||
|
server,
|
||||||
|
old_db,
|
||||||
|
tracing,
|
||||||
|
media,
|
||||||
|
store: None,
|
||||||
|
repo,
|
||||||
|
},
|
||||||
|
operation: Operation::MigrateStore {
|
||||||
|
from: from.into(),
|
||||||
|
to: to.into(),
|
||||||
|
},
|
||||||
|
config_file,
|
||||||
|
save_to,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MigrateStore::ObjectStorage(MigrateObjectStorage { from, to }) => match to {
|
||||||
|
MigrateStoreInner::Filesystem(MigrateFilesystemInner { to, repo }) => {
|
||||||
|
Output {
|
||||||
|
config_format: ConfigFormat {
|
||||||
|
server,
|
||||||
|
old_db,
|
||||||
|
tracing,
|
||||||
|
media,
|
||||||
|
store: None,
|
||||||
|
repo,
|
||||||
|
},
|
||||||
|
operation: Operation::MigrateStore {
|
||||||
|
from: from.into(),
|
||||||
|
to: to.into(),
|
||||||
|
},
|
||||||
|
config_file,
|
||||||
|
save_to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MigrateStoreInner::ObjectStorage(MigrateObjectStorageInner {
|
||||||
|
to,
|
||||||
|
repo,
|
||||||
|
}) => Output {
|
||||||
|
config_format: ConfigFormat {
|
||||||
|
server,
|
||||||
|
old_db,
|
||||||
|
tracing,
|
||||||
|
media,
|
||||||
|
store: None,
|
||||||
|
repo,
|
||||||
|
},
|
||||||
|
operation: Operation::MigrateStore {
|
||||||
|
from: from.into(),
|
||||||
|
to: to.into(),
|
||||||
|
},
|
||||||
|
config_file,
|
||||||
|
save_to,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct Output {
|
||||||
|
pub(super) config_format: ConfigFormat,
|
||||||
|
pub(super) operation: Operation,
|
||||||
|
pub(super) save_to: Option<PathBuf>,
|
||||||
|
pub(super) config_file: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) enum Operation {
|
||||||
|
Run,
|
||||||
|
MigrateStore {
|
||||||
|
from: crate::config::primitives::Store,
|
||||||
|
to: crate::config::primitives::Store,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub(super) struct ConfigFormat {
|
||||||
|
server: Server,
|
||||||
|
old_db: OldDb,
|
||||||
|
tracing: Tracing,
|
||||||
|
media: Media,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
repo: Option<Repo>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
store: Option<Store>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct Server {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
address: Option<SocketAddr>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
api_key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<LogFormat>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
targets: Option<Serde<Targets>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct Console {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
address: Option<SocketAddr>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
buffer_capacity: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct OpenTelemetry {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
url: Option<Url>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
service_name: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
targets: Option<Serde<Targets>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct OldDb {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct Media {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
max_width: Option<usize>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
max_height: Option<usize>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
max_area: Option<usize>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
max_file_size: Option<usize>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
enable_silent_video: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
filters: Option<Vec<String>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
format: Option<ImageFormat>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
skip_validate_imports: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Run the pict-rs application
|
/// Run the pict-rs application
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
pub(crate) struct Args {
|
pub(super) struct Args {
|
||||||
/// Path to the pict-rs configuration file
|
/// Path to the pict-rs configuration file
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub(crate) config_file: Option<PathBuf>,
|
config_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Path to the old pict-rs sled database
|
||||||
|
#[clap(long)]
|
||||||
|
old_db_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Format of logs printed to stdout
|
/// Format of logs printed to stdout
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) log_format: Option<LogFormat>,
|
log_format: Option<LogFormat>,
|
||||||
/// Log levels to print to stdout, respects RUST_LOG formatting
|
/// Log levels to print to stdout, respects RUST_LOG formatting
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) log_targets: Option<Targets>,
|
log_targets: Option<Targets>,
|
||||||
|
|
||||||
/// Address and port to expose tokio-console metrics
|
/// Address and port to expose tokio-console metrics
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) console_address: Option<SocketAddr>,
|
console_address: Option<SocketAddr>,
|
||||||
/// Capacity of the console-subscriber Event Buffer
|
/// Capacity of the console-subscriber Event Buffer
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) console_buffer_capacity: Option<usize>,
|
console_buffer_capacity: Option<usize>,
|
||||||
|
|
||||||
/// URL to send OpenTelemetry metrics
|
/// URL to send OpenTelemetry metrics
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) opentelemetry_url: Option<Url>,
|
opentelemetry_url: Option<Url>,
|
||||||
/// Service Name to use for OpenTelemetry
|
/// Service Name to use for OpenTelemetry
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) opentelemetry_service_name: Option<String>,
|
opentelemetry_service_name: Option<String>,
|
||||||
/// Log levels to use for OpenTelemetry, respects RUST_LOG formatting
|
/// Log levels to use for OpenTelemetry, respects RUST_LOG formatting
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) opentelemetry_targets: Option<Targets>,
|
opentelemetry_targets: Option<Targets>,
|
||||||
|
|
||||||
/// File to save the current configuration for reproducible runs
|
/// File to save the current configuration for reproducible runs
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) save_to: Option<PathBuf>,
|
save_to: Option<PathBuf>,
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub(crate) command: Command,
|
command: Command,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub(crate) enum Command {
|
enum Command {
|
||||||
/// Runs the pict-rs web server
|
/// Runs the pict-rs web server
|
||||||
Run(Run),
|
Run(Run),
|
||||||
|
|
||||||
|
@ -54,47 +363,49 @@ pub(crate) enum Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub(crate) struct Run {
|
struct Run {
|
||||||
/// The address and port to bind the pict-rs web server
|
/// The address and port to bind the pict-rs web server
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub(crate) address: SocketAddr,
|
address: Option<SocketAddr>,
|
||||||
|
|
||||||
/// The API KEY required to access restricted routes
|
/// The API KEY required to access restricted routes
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) api_key: Option<String>,
|
api_key: Option<String>,
|
||||||
|
|
||||||
/// Whether to validate media on the "import" endpoint
|
/// Whether to validate media on the "import" endpoint
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) media_skip_validate_imports: Option<bool>,
|
media_skip_validate_imports: Option<bool>,
|
||||||
/// The maximum width, in pixels, for uploaded media
|
/// The maximum width, in pixels, for uploaded media
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) media_max_width: Option<usize>,
|
media_max_width: Option<usize>,
|
||||||
/// The maximum height, in pixels, for uploaded media
|
/// The maximum height, in pixels, for uploaded media
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) media_max_height: Option<usize>,
|
media_max_height: Option<usize>,
|
||||||
/// The maximum area, in pixels, for uploaded media
|
/// The maximum area, in pixels, for uploaded media
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) media_max_area: Option<usize>,
|
media_max_area: Option<usize>,
|
||||||
/// The maximum size, in megabytes, for uploaded media
|
/// The maximum size, in megabytes, for uploaded media
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) media_max_file_size: Option<usize>,
|
media_max_file_size: Option<usize>,
|
||||||
/// Whether to enable GIF and silent MP4 uploads. Full videos are unsupported
|
/// Whether to enable GIF and silent MP4 uploads. Full videos are unsupported
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) media_enable_silent_video: Option<bool>,
|
media_enable_silent_video: Option<bool>,
|
||||||
/// Which media filters should be enabled on the `process` endpoint
|
/// Which media filters should be enabled on the `process` endpoint
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) media_filters: Option<Vec<String>>,
|
media_filters: Option<Vec<String>>,
|
||||||
/// Enforce uploaded media is transcoded to the provided format
|
/// Enforce uploaded media is transcoded to the provided format
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) media_format: Option<ImageFormat>,
|
media_format: Option<ImageFormat>,
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub(crate) store: Option<RunStore>,
|
store: Option<RunStore>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure the provided storage
|
/// Configure the provided storage
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Clone, Debug, Subcommand, serde::Serialize)]
|
||||||
pub(crate) enum Store {
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
enum Store {
|
||||||
/// configure filesystem storage
|
/// configure filesystem storage
|
||||||
Filesystem(Filesystem),
|
Filesystem(Filesystem),
|
||||||
|
|
||||||
|
@ -104,7 +415,7 @@ pub(crate) enum Store {
|
||||||
|
|
||||||
/// Run pict-rs with the provided storage
|
/// Run pict-rs with the provided storage
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub(crate) enum RunStore {
|
enum RunStore {
|
||||||
/// Run pict-rs with filesystem storage
|
/// Run pict-rs with filesystem storage
|
||||||
Filesystem(RunFilesystem),
|
Filesystem(RunFilesystem),
|
||||||
|
|
||||||
|
@ -114,7 +425,7 @@ pub(crate) enum RunStore {
|
||||||
|
|
||||||
/// Configure the pict-rs storage migration
|
/// Configure the pict-rs storage migration
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub(crate) enum MigrateStore {
|
enum MigrateStore {
|
||||||
/// Migrate from the provided filesystem storage
|
/// Migrate from the provided filesystem storage
|
||||||
Filesystem(MigrateFilesystem),
|
Filesystem(MigrateFilesystem),
|
||||||
|
|
||||||
|
@ -122,95 +433,134 @@ pub(crate) enum MigrateStore {
|
||||||
ObjectStorage(MigrateObjectStorage),
|
ObjectStorage(MigrateObjectStorage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure the destination storage for pict-rs storage migration
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
enum MigrateStoreInner {
|
||||||
|
/// Migrate to the provided filesystem storage
|
||||||
|
Filesystem(MigrateFilesystemInner),
|
||||||
|
|
||||||
|
/// Migrate to the provided object storage
|
||||||
|
ObjectStorage(MigrateObjectStorageInner),
|
||||||
|
}
|
||||||
|
|
||||||
/// Migrate pict-rs' storage from the provided filesystem storage
|
/// Migrate pict-rs' storage from the provided filesystem storage
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub(crate) struct MigrateFilesystem {
|
struct MigrateFilesystem {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) from: Filesystem,
|
from: crate::config::primitives::Filesystem,
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub(crate) to: RunStore,
|
to: MigrateStoreInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Migrate pict-rs' storage to the provided filesystem storage
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
struct MigrateFilesystemInner {
|
||||||
|
#[clap(flatten)]
|
||||||
|
to: crate::config::primitives::Filesystem,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
repo: Option<Repo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Migrate pict-rs' storage from the provided object storage
|
/// Migrate pict-rs' storage from the provided object storage
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub(crate) struct MigrateObjectStorage {
|
struct MigrateObjectStorage {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) from: ObjectStorage,
|
from: crate::config::primitives::ObjectStorage,
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub(crate) to: RunStore,
|
to: MigrateStoreInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Migrate pict-rs' storage to the provided object storage
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
struct MigrateObjectStorageInner {
|
||||||
|
#[clap(flatten)]
|
||||||
|
to: crate::config::primitives::ObjectStorage,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
repo: Option<Repo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run pict-rs with the provided filesystem storage
|
/// Run pict-rs with the provided filesystem storage
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub(crate) struct RunFilesystem {
|
struct RunFilesystem {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) system: Filesystem,
|
system: Filesystem,
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub(crate) repo: Repo,
|
repo: Option<Repo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run pict-rs with the provided object storage
|
/// Run pict-rs with the provided object storage
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub(crate) struct RunObjectStorage {
|
struct RunObjectStorage {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) storage: ObjectStorage,
|
storage: ObjectStorage,
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub(crate) repo: Repo,
|
repo: Option<Repo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for data repositories
|
/// Configuration for data repositories
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand, serde::Serialize)]
|
||||||
pub(crate) enum Repo {
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
enum Repo {
|
||||||
/// Run pict-rs with the provided sled-backed data repository
|
/// Run pict-rs with the provided sled-backed data repository
|
||||||
Sled(Sled),
|
Sled(Sled),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for filesystem media storage
|
/// Configuration for filesystem media storage
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Clone, Debug, Parser, serde::Serialize)]
|
||||||
pub(crate) struct Filesystem {
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct Filesystem {
|
||||||
/// The path to store uploaded media
|
/// The path to store uploaded media
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub(crate) path: Option<PathBuf>,
|
path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for Object Storage
|
/// Configuration for Object Storage
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Clone, Debug, Parser, serde::Serialize)]
|
||||||
pub(crate) struct ObjectStorage {
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct ObjectStorage {
|
||||||
/// The bucket in which to store media
|
/// The bucket in which to store media
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub(crate) bucket_name: Option<String>,
|
bucket_name: Option<String>,
|
||||||
|
|
||||||
/// The region the bucket is located in
|
/// The region the bucket is located in
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub(crate) region: Option<s3::Region>,
|
region: Option<Serde<s3::Region>>,
|
||||||
|
|
||||||
/// The Access Key for the user accessing the bucket
|
/// The Access Key for the user accessing the bucket
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub(crate) access_key: Option<String>,
|
access_key: Option<String>,
|
||||||
|
|
||||||
/// The secret key for the user accessing the bucket
|
/// The secret key for the user accessing the bucket
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub(crate) secret_key: Option<String>,
|
secret_key: Option<String>,
|
||||||
|
|
||||||
/// The security token for accessing the bucket
|
/// The security token for accessing the bucket
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) security_token: Option<String>,
|
security_token: Option<String>,
|
||||||
|
|
||||||
/// The session token for accessing the bucket
|
/// The session token for accessing the bucket
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub(crate) session_token: Option<String>,
|
session_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for the sled-backed data repository
|
/// Configuration for the sled-backed data repository
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser, serde::Serialize)]
|
||||||
pub(crate) struct Sled {
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct Sled {
|
||||||
/// The path to store the sled database
|
/// The path to store the sled database
|
||||||
pub(crate) path: Option<PathBuf>,
|
#[clap(short, long)]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
|
||||||
/// The cache capacity, in bytes, allowed to sled for in-memory operations
|
/// The cache capacity, in bytes, allowed to sled for in-memory operations
|
||||||
pub(crate) cache_capacity: Option<u64>,
|
#[clap(short, long)]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
cache_capacity: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
use std::{net::SocketAddr, path::PathBuf};
|
use std::{net::SocketAddr, path::PathBuf};
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) struct Defaults {
|
pub(crate) struct Defaults {
|
||||||
server: ServerDefaults,
|
server: ServerDefaults,
|
||||||
tracing: TracingDefaults,
|
tracing: TracingDefaults,
|
||||||
|
@ -14,35 +15,50 @@ pub(crate) struct Defaults {
|
||||||
store: StoreDefaults,
|
store: StoreDefaults,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
struct ServerDefaults {
|
struct ServerDefaults {
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, Default, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
struct TracingDefaults {
|
struct TracingDefaults {
|
||||||
logging: LoggingDefaults,
|
logging: LoggingDefaults,
|
||||||
|
|
||||||
console: ConsoleDefaults,
|
console: ConsoleDefaults,
|
||||||
|
|
||||||
|
opentelemetry: OpenTelemetryDefaults,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
struct LoggingDefaults {
|
struct LoggingDefaults {
|
||||||
format: LogFormat,
|
format: LogFormat,
|
||||||
targets: Serde<Targets>,
|
targets: Serde<Targets>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
struct ConsoleDefaults {
|
struct ConsoleDefaults {
|
||||||
buffer_capacity: usize,
|
buffer_capacity: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
struct OpenTelemetryDefaults {
|
||||||
|
service_name: String,
|
||||||
|
targets: Serde<Targets>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
struct OldDbDefaults {
|
struct OldDbDefaults {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
struct MediaDefaults {
|
struct MediaDefaults {
|
||||||
max_width: usize,
|
max_width: usize,
|
||||||
max_height: usize,
|
max_height: usize,
|
||||||
|
@ -53,42 +69,33 @@ struct MediaDefaults {
|
||||||
skip_validate_imports: bool,
|
skip_validate_imports: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
enum RepoDefaults {
|
enum RepoDefaults {
|
||||||
Sled(SledDefaults),
|
Sled(SledDefaults),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
struct SledDefaults {
|
struct SledDefaults {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
cache_capacity: u64,
|
cache_capacity: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
enum StoreDefaults {
|
enum StoreDefaults {
|
||||||
Filesystem(FilesystemDefaults),
|
Filesystem(FilesystemDefaults),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
struct FilesystemDefaults {
|
struct FilesystemDefaults {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Defaults {
|
|
||||||
fn default() -> Self {
|
|
||||||
Defaults {
|
|
||||||
server: ServerDefaults::default(),
|
|
||||||
tracing: TracingDefaults::default(),
|
|
||||||
old_db: OldDbDefaults::default(),
|
|
||||||
media: MediaDefaults::default(),
|
|
||||||
repo: RepoDefaults::default(),
|
|
||||||
store: StoreDefaults::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ServerDefaults {
|
impl Default for ServerDefaults {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ServerDefaults {
|
ServerDefaults {
|
||||||
|
@ -97,15 +104,6 @@ impl Default for ServerDefaults {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TracingDefaults {
|
|
||||||
fn default() -> TracingDefaults {
|
|
||||||
TracingDefaults {
|
|
||||||
logging: LoggingDefaults::default(),
|
|
||||||
console: ConsoleDefaults::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LoggingDefaults {
|
impl Default for LoggingDefaults {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
LoggingDefaults {
|
LoggingDefaults {
|
||||||
|
@ -123,6 +121,15 @@ impl Default for ConsoleDefaults {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for OpenTelemetryDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
OpenTelemetryDefaults {
|
||||||
|
service_name: String::from("pict-rs"),
|
||||||
|
targets: "info".parse().expect("Valid targets string"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for OldDbDefaults {
|
impl Default for OldDbDefaults {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
OldDbDefaults {
|
OldDbDefaults {
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config::primitives::{ImageFormat, LogFormat, Targets},
|
config::primitives::{ImageFormat, LogFormat, Store, Targets},
|
||||||
serde_str::Serde,
|
serde_str::Serde,
|
||||||
};
|
};
|
||||||
use std::{net::SocketAddr, path::PathBuf};
|
use std::{collections::HashSet, net::SocketAddr, path::PathBuf};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) struct ConfigFile {
|
pub(crate) struct ConfigFile {
|
||||||
pub(crate) server: Server,
|
pub(crate) server: Server,
|
||||||
|
|
||||||
pub(crate) tracing: Tracing,
|
pub(crate) tracing: Tracing,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub(crate) old_db: OldDb,
|
||||||
pub(crate) old_db: Option<OldDb>,
|
|
||||||
|
|
||||||
pub(crate) media: Media,
|
pub(crate) media: Media,
|
||||||
|
|
||||||
|
@ -22,20 +22,14 @@ pub(crate) struct ConfigFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub(crate) enum Repo {
|
pub(crate) enum Repo {
|
||||||
Sled(Sled),
|
Sled(Sled),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) enum Store {
|
|
||||||
Filesystem(Filesystem),
|
|
||||||
|
|
||||||
ObjectStorage(ObjectStorage),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
||||||
pub(crate) struct Server {
|
pub(crate) struct Server {
|
||||||
pub(crate) address: SocketAddr,
|
pub(crate) address: SocketAddr,
|
||||||
|
|
||||||
|
@ -44,17 +38,17 @@ pub(crate) struct Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) struct Tracing {
|
pub(crate) struct Tracing {
|
||||||
logging: Logging,
|
pub(crate) logging: Logging,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub(crate) console: Console,
|
||||||
console: Option<Console>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub(crate) opentelemetry: OpenTelemetry,
|
||||||
opentelemetry: Option<OpenTelemetry>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) struct Logging {
|
pub(crate) struct Logging {
|
||||||
pub(crate) format: LogFormat,
|
pub(crate) format: LogFormat,
|
||||||
|
|
||||||
|
@ -62,8 +56,10 @@ pub(crate) struct Logging {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) struct OpenTelemetry {
|
pub(crate) struct OpenTelemetry {
|
||||||
pub(crate) url: Url,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) url: Option<Url>,
|
||||||
|
|
||||||
pub(crate) service_name: String,
|
pub(crate) service_name: String,
|
||||||
|
|
||||||
|
@ -71,17 +67,22 @@ pub(crate) struct OpenTelemetry {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) struct Console {
|
pub(crate) struct Console {
|
||||||
pub(crate) address: SocketAddr,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) address: Option<SocketAddr>,
|
||||||
|
|
||||||
pub(crate) buffer_capacity: usize,
|
pub(crate) buffer_capacity: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) struct OldDb {
|
pub(crate) struct OldDb {
|
||||||
pub(crate) path: PathBuf,
|
pub(crate) path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) struct Media {
|
pub(crate) struct Media {
|
||||||
pub(crate) max_width: usize,
|
pub(crate) max_width: usize,
|
||||||
|
|
||||||
|
@ -93,7 +94,7 @@ pub(crate) struct Media {
|
||||||
|
|
||||||
pub(crate) enable_silent_video: bool,
|
pub(crate) enable_silent_video: bool,
|
||||||
|
|
||||||
pub(crate) filters: Vec<String>,
|
pub(crate) filters: HashSet<String>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub(crate) format: Option<ImageFormat>,
|
pub(crate) format: Option<ImageFormat>,
|
||||||
|
@ -102,28 +103,7 @@ pub(crate) struct Media {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub(crate) struct Filesystem {
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
||||||
pub(crate) struct ObjectStorage {
|
|
||||||
pub(crate) bucket_name: String,
|
|
||||||
|
|
||||||
pub(crate) region: Serde<s3::Region>,
|
|
||||||
|
|
||||||
pub(crate) access_key: String,
|
|
||||||
|
|
||||||
pub(crate) secret_key: String,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub(crate) security_token: Option<String>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub(crate) session_token: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
||||||
pub(crate) struct Sled {
|
pub(crate) struct Sled {
|
||||||
pub(crate) path: PathBuf,
|
pub(crate) path: PathBuf,
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
use crate::magick::ValidInputType;
|
||||||
|
use crate::serde_str::Serde;
|
||||||
use clap::ArgEnum;
|
use clap::ArgEnum;
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, path::PathBuf, str::FromStr};
|
||||||
|
use tracing::Level;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone,
|
Clone,
|
||||||
|
Copy,
|
||||||
Debug,
|
Debug,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
Eq,
|
Eq,
|
||||||
|
@ -13,6 +17,7 @@ use std::{fmt::Display, str::FromStr};
|
||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
ArgEnum,
|
ArgEnum,
|
||||||
)]
|
)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) enum LogFormat {
|
pub(crate) enum LogFormat {
|
||||||
Compact,
|
Compact,
|
||||||
Json,
|
Json,
|
||||||
|
@ -22,6 +27,7 @@ pub(crate) enum LogFormat {
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone,
|
Clone,
|
||||||
|
Copy,
|
||||||
Debug,
|
Debug,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
Eq,
|
Eq,
|
||||||
|
@ -32,6 +38,7 @@ pub(crate) enum LogFormat {
|
||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
ArgEnum,
|
ArgEnum,
|
||||||
)]
|
)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) enum ImageFormat {
|
pub(crate) enum ImageFormat {
|
||||||
Jpeg,
|
Jpeg,
|
||||||
Webp,
|
Webp,
|
||||||
|
@ -43,6 +50,81 @@ pub(crate) struct Targets {
|
||||||
pub(crate) targets: tracing_subscriber::filter::Targets,
|
pub(crate) targets: tracing_subscriber::filter::Targets,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for filesystem media storage
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, clap::Parser)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub(crate) struct Filesystem {
|
||||||
|
/// Path to store media
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for object media storage
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, clap::Parser)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub(crate) struct ObjectStorage {
|
||||||
|
/// The bucket in which to store media
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) bucket_name: String,
|
||||||
|
|
||||||
|
/// The region the bucket is located in
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) region: Serde<s3::Region>,
|
||||||
|
|
||||||
|
/// The Access Key for the user accessing the bucket
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) access_key: String,
|
||||||
|
|
||||||
|
/// The secret key for the user accessing the bucket
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) secret_key: String,
|
||||||
|
|
||||||
|
/// The security token for accessing the bucket
|
||||||
|
#[clap(long)]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) security_token: Option<String>,
|
||||||
|
|
||||||
|
/// The session token for accessing the bucket
|
||||||
|
#[clap(long)]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) session_token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub(crate) enum Store {
|
||||||
|
Filesystem(Filesystem),
|
||||||
|
|
||||||
|
ObjectStorage(ObjectStorage),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageFormat {
|
||||||
|
pub(crate) fn as_hint(self) -> Option<ValidInputType> {
|
||||||
|
Some(ValidInputType::from_format(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_magick_format(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Jpeg => "JPEG",
|
||||||
|
Self::Png => "PNG",
|
||||||
|
Self::Webp => "WEBP",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Filesystem> for Store {
|
||||||
|
fn from(f: Filesystem) -> Self {
|
||||||
|
Self::Filesystem(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ObjectStorage> for Store {
|
||||||
|
fn from(o: ObjectStorage) -> Self {
|
||||||
|
Self::ObjectStorage(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for Targets {
|
impl FromStr for Targets {
|
||||||
type Err = <tracing_subscriber::filter::Targets as FromStr>::Err;
|
type Err = <tracing_subscriber::filter::Targets as FromStr>::Err;
|
||||||
|
|
||||||
|
@ -62,7 +144,37 @@ impl Display for Targets {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(",");
|
.join(",");
|
||||||
|
|
||||||
write!(f, "{}", targets)
|
let max_level = [
|
||||||
|
Level::TRACE,
|
||||||
|
Level::DEBUG,
|
||||||
|
Level::INFO,
|
||||||
|
Level::WARN,
|
||||||
|
Level::ERROR,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.fold(None, |found, level| {
|
||||||
|
if found.is_none()
|
||||||
|
&& self
|
||||||
|
.targets
|
||||||
|
.would_enable("not_a_real_target_so_nothing_can_conflict", level)
|
||||||
|
{
|
||||||
|
Some(level.to_string().to_lowercase())
|
||||||
|
} else {
|
||||||
|
found
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(level) = max_level {
|
||||||
|
if !targets.is_empty() {
|
||||||
|
write!(f, "{},{}", level, targets)
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", level)
|
||||||
|
}
|
||||||
|
} else if !targets.is_empty() {
|
||||||
|
write!(f, "{}", targets)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,3 +221,31 @@ impl Display for LogFormat {
|
||||||
.fmt(f)
|
.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{Serde, Targets};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builds_info_targets() {
|
||||||
|
let t: Serde<Targets> = "info".parse().unwrap();
|
||||||
|
|
||||||
|
println!("{:?}", t);
|
||||||
|
|
||||||
|
assert_eq!(t.to_string(), "info");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builds_specific_targets() {
|
||||||
|
let t: Serde<Targets> = "pict_rs=info".parse().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(t.to_string(), "pict_rs=info");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builds_warn_and_specific_targets() {
|
||||||
|
let t: Serde<Targets> = "warn,pict_rs=info".parse().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(t.to_string(), "warn,pict_rs=info");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::config::{LogFormat, OpenTelemetry, Tracing};
|
||||||
use console_subscriber::ConsoleLayer;
|
use console_subscriber::ConsoleLayer;
|
||||||
use opentelemetry::{
|
use opentelemetry::{
|
||||||
sdk::{propagation::TraceContextPropagator, Resource},
|
sdk::{propagation::TraceContextPropagator, Resource},
|
||||||
|
@ -8,74 +9,73 @@ use tracing::subscriber::set_global_default;
|
||||||
use tracing_error::ErrorLayer;
|
use tracing_error::ErrorLayer;
|
||||||
use tracing_log::LogTracer;
|
use tracing_log::LogTracer;
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
filter::Targets, fmt::format::FmtSpan, layer::SubscriberExt, registry::LookupSpan, Layer,
|
fmt::format::FmtSpan, layer::SubscriberExt, registry::LookupSpan, Layer, Registry,
|
||||||
Registry,
|
|
||||||
};
|
};
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
pub(super) fn init_tracing(
|
pub(super) fn init_tracing(tracing: &Tracing) -> anyhow::Result<()> {
|
||||||
servic_name: &'static str,
|
|
||||||
opentelemetry_url: Option<&Url>,
|
|
||||||
buffer_capacity: Option<usize>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
LogTracer::init()?;
|
LogTracer::init()?;
|
||||||
|
|
||||||
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
|
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
|
||||||
|
|
||||||
let targets = std::env::var("RUST_LOG")
|
let format_layer =
|
||||||
.unwrap_or_else(|_| "info".into())
|
tracing_subscriber::fmt::layer().with_span_events(FmtSpan::NEW | FmtSpan::CLOSE);
|
||||||
.parse::<Targets>()?;
|
|
||||||
|
|
||||||
let format_layer = tracing_subscriber::fmt::layer()
|
match tracing.logging.format {
|
||||||
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
LogFormat::Compact => with_format(format_layer.compact(), tracing),
|
||||||
.with_filter(targets.clone());
|
LogFormat::Json => with_format(format_layer.json(), tracing),
|
||||||
|
LogFormat::Normal => with_format(format_layer, tracing),
|
||||||
|
LogFormat::Pretty => with_format(format_layer.pretty(), tracing),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_format<F>(format_layer: F, tracing: &Tracing) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
F: Layer<Registry> + Send + Sync,
|
||||||
|
{
|
||||||
|
let format_layer = format_layer.with_filter(tracing.logging.targets.targets.clone());
|
||||||
|
|
||||||
let subscriber = Registry::default()
|
let subscriber = Registry::default()
|
||||||
.with(format_layer)
|
.with(format_layer)
|
||||||
.with(ErrorLayer::default());
|
.with(ErrorLayer::default());
|
||||||
|
|
||||||
if let Some(buffer_capacity) = buffer_capacity {
|
if let Some(address) = tracing.console.address {
|
||||||
let console_layer = ConsoleLayer::builder()
|
let console_layer = ConsoleLayer::builder()
|
||||||
.with_default_env()
|
.with_default_env()
|
||||||
.event_buffer_capacity(buffer_capacity)
|
.event_buffer_capacity(tracing.console.buffer_capacity)
|
||||||
.server_addr(([0, 0, 0, 0], 6669))
|
.server_addr(address)
|
||||||
.spawn();
|
.spawn();
|
||||||
|
|
||||||
let subscriber = subscriber.with(console_layer);
|
let subscriber = subscriber.with(console_layer);
|
||||||
|
|
||||||
with_otel(subscriber, targets, servic_name, opentelemetry_url)
|
with_subscriber(subscriber, &tracing.opentelemetry)
|
||||||
} else {
|
} else {
|
||||||
with_otel(subscriber, targets, servic_name, opentelemetry_url)
|
with_subscriber(subscriber, &tracing.opentelemetry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_otel<S>(
|
fn with_subscriber<S>(subscriber: S, otel: &OpenTelemetry) -> anyhow::Result<()>
|
||||||
subscriber: S,
|
|
||||||
targets: Targets,
|
|
||||||
servic_name: &'static str,
|
|
||||||
opentelemetry_url: Option<&Url>,
|
|
||||||
) -> anyhow::Result<()>
|
|
||||||
where
|
where
|
||||||
S: SubscriberExt + Send + Sync,
|
S: SubscriberExt + Send + Sync,
|
||||||
for<'a> S: LookupSpan<'a>,
|
for<'a> S: LookupSpan<'a>,
|
||||||
{
|
{
|
||||||
if let Some(url) = opentelemetry_url {
|
if let Some(url) = otel.url.as_ref() {
|
||||||
let tracer =
|
let tracer = opentelemetry_otlp::new_pipeline()
|
||||||
opentelemetry_otlp::new_pipeline()
|
.tracing()
|
||||||
.tracing()
|
.with_trace_config(
|
||||||
.with_trace_config(opentelemetry::sdk::trace::config().with_resource(
|
opentelemetry::sdk::trace::config().with_resource(Resource::new(vec![
|
||||||
Resource::new(vec![KeyValue::new("service.name", servic_name)]),
|
KeyValue::new("service.name", otel.service_name.clone()),
|
||||||
))
|
])),
|
||||||
.with_exporter(
|
)
|
||||||
opentelemetry_otlp::new_exporter()
|
.with_exporter(
|
||||||
.tonic()
|
opentelemetry_otlp::new_exporter()
|
||||||
.with_endpoint(url.as_str()),
|
.tonic()
|
||||||
)
|
.with_endpoint(url.as_str()),
|
||||||
.install_batch(opentelemetry::runtime::Tokio)?;
|
)
|
||||||
|
.install_batch(opentelemetry::runtime::Tokio)?;
|
||||||
|
|
||||||
let otel_layer = tracing_opentelemetry::layer()
|
let otel_layer = tracing_opentelemetry::layer()
|
||||||
.with_tracer(tracer)
|
.with_tracer(tracer)
|
||||||
.with_filter(targets);
|
.with_filter(otel.targets.as_ref().targets.clone());
|
||||||
|
|
||||||
let subscriber = subscriber.with(otel_layer);
|
let subscriber = subscriber.with(otel_layer);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Format,
|
config::ImageFormat,
|
||||||
error::{Error, UploadError},
|
error::{Error, UploadError},
|
||||||
process::Process,
|
process::Process,
|
||||||
repo::Alias,
|
repo::Alias,
|
||||||
|
@ -63,11 +63,11 @@ impl ValidInputType {
|
||||||
matches!(self, Self::Mp4)
|
matches!(self, Self::Mp4)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_format(format: Format) -> Self {
|
pub(crate) fn from_format(format: ImageFormat) -> Self {
|
||||||
match format {
|
match format {
|
||||||
Format::Jpeg => ValidInputType::Jpeg,
|
ImageFormat::Jpeg => ValidInputType::Jpeg,
|
||||||
Format::Png => ValidInputType::Png,
|
ImageFormat::Png => ValidInputType::Png,
|
||||||
Format::Webp => ValidInputType::Webp,
|
ImageFormat::Webp => ValidInputType::Webp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ pub(crate) fn clear_metadata_bytes_read(input: Bytes) -> std::io::Result<impl As
|
||||||
|
|
||||||
pub(crate) fn convert_bytes_read(
|
pub(crate) fn convert_bytes_read(
|
||||||
input: Bytes,
|
input: Bytes,
|
||||||
format: Format,
|
format: ImageFormat,
|
||||||
) -> std::io::Result<impl AsyncRead + Unpin> {
|
) -> std::io::Result<impl AsyncRead + Unpin> {
|
||||||
let process = Process::run(
|
let process = Process::run(
|
||||||
"magick",
|
"magick",
|
||||||
|
@ -259,7 +259,7 @@ pub(crate) fn process_image_store_read<S: Store>(
|
||||||
store: S,
|
store: S,
|
||||||
identifier: S::Identifier,
|
identifier: S::Identifier,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
format: Format,
|
format: ImageFormat,
|
||||||
) -> std::io::Result<impl AsyncRead + Unpin> {
|
) -> std::io::Result<impl AsyncRead + Unpin> {
|
||||||
let command = "magick";
|
let command = "magick";
|
||||||
let convert_args = ["convert", "-"];
|
let convert_args = ["convert", "-"];
|
||||||
|
@ -278,9 +278,9 @@ pub(crate) fn process_image_store_read<S: Store>(
|
||||||
impl Details {
|
impl Details {
|
||||||
#[instrument(name = "Validating input type")]
|
#[instrument(name = "Validating input type")]
|
||||||
fn validate_input(&self) -> Result<ValidInputType, Error> {
|
fn validate_input(&self) -> Result<ValidInputType, Error> {
|
||||||
if self.width > crate::CONFIG.max_width()
|
if self.width > crate::CONFIG.media.max_width
|
||||||
|| self.height > crate::CONFIG.max_height()
|
|| self.height > crate::CONFIG.media.max_height
|
||||||
|| self.width * self.height > crate::CONFIG.max_area()
|
|| self.width * self.height > crate::CONFIG.media.max_area
|
||||||
{
|
{
|
||||||
return Err(UploadError::Dimensions.into());
|
return Err(UploadError::Dimensions.into());
|
||||||
}
|
}
|
||||||
|
|
90
src/main.rs
90
src/main.rs
|
@ -49,7 +49,7 @@ mod validate;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
concurrent_processor::CancelSafeProcessor,
|
concurrent_processor::CancelSafeProcessor,
|
||||||
config::{CommandConfig, Config, Format, RequiredFilesystemStorage, RequiredObjectStorage},
|
config::{Configuration, ImageFormat, Operation},
|
||||||
details::Details,
|
details::Details,
|
||||||
either::Either,
|
either::Either,
|
||||||
error::{Error, UploadError},
|
error::{Error, UploadError},
|
||||||
|
@ -58,6 +58,7 @@ use self::{
|
||||||
middleware::{Deadline, Internal},
|
middleware::{Deadline, Internal},
|
||||||
migrate::LatestDb,
|
migrate::LatestDb,
|
||||||
repo::{Alias, DeleteToken, Repo},
|
repo::{Alias, DeleteToken, Repo},
|
||||||
|
serde_str::Serde,
|
||||||
store::{file_store::FileStore, object_store::ObjectStore, Store},
|
store::{file_store::FileStore, object_store::ObjectStore, Store},
|
||||||
upload_manager::{UploadManager, UploadManagerSession},
|
upload_manager::{UploadManager, UploadManagerSession},
|
||||||
};
|
};
|
||||||
|
@ -67,7 +68,10 @@ const MINUTES: u32 = 60;
|
||||||
const HOURS: u32 = 60 * MINUTES;
|
const HOURS: u32 = 60 * MINUTES;
|
||||||
const DAYS: u32 = 24 * HOURS;
|
const DAYS: u32 = 24 * HOURS;
|
||||||
|
|
||||||
static CONFIG: Lazy<Config> = Lazy::new(|| Config::build().unwrap());
|
static DO_CONFIG: Lazy<(Configuration, Operation)> =
|
||||||
|
Lazy::new(|| config::configure().expect("Failed to configure"));
|
||||||
|
static CONFIG: Lazy<Configuration> = Lazy::new(|| DO_CONFIG.0.clone());
|
||||||
|
static OPERATION: Lazy<Operation> = Lazy::new(|| DO_CONFIG.1.clone());
|
||||||
static PROCESS_SEMAPHORE: Lazy<Semaphore> =
|
static PROCESS_SEMAPHORE: Lazy<Semaphore> =
|
||||||
Lazy::new(|| Semaphore::new(num_cpus::get().saturating_sub(1).max(1)));
|
Lazy::new(|| Semaphore::new(num_cpus::get().saturating_sub(1).max(1)));
|
||||||
|
|
||||||
|
@ -202,7 +206,7 @@ async fn download<S: Store>(
|
||||||
|
|
||||||
let stream = Limit::new(
|
let stream = Limit::new(
|
||||||
map_error::map_crate_error(res),
|
map_error::map_crate_error(res),
|
||||||
(CONFIG.max_file_size() * MEGABYTES) as u64,
|
(CONFIG.media.max_file_size * MEGABYTES) as u64,
|
||||||
);
|
);
|
||||||
|
|
||||||
futures_util::pin_mut!(stream);
|
futures_util::pin_mut!(stream);
|
||||||
|
@ -260,7 +264,7 @@ fn prepare_process(
|
||||||
query: web::Query<ProcessQuery>,
|
query: web::Query<ProcessQuery>,
|
||||||
ext: &str,
|
ext: &str,
|
||||||
filters: &Option<HashSet<String>>,
|
filters: &Option<HashSet<String>>,
|
||||||
) -> Result<(Format, Alias, PathBuf, Vec<String>), Error> {
|
) -> Result<(ImageFormat, Alias, PathBuf, Vec<String>), Error> {
|
||||||
let (alias, operations) =
|
let (alias, operations) =
|
||||||
query
|
query
|
||||||
.into_inner()
|
.into_inner()
|
||||||
|
@ -290,7 +294,7 @@ fn prepare_process(
|
||||||
};
|
};
|
||||||
|
|
||||||
let format = ext
|
let format = ext
|
||||||
.parse::<Format>()
|
.parse::<ImageFormat>()
|
||||||
.map_err(|_| UploadError::UnsupportedFormat)?;
|
.map_err(|_| UploadError::UnsupportedFormat)?;
|
||||||
|
|
||||||
let (thumbnail_path, thumbnail_args) = self::processor::build_chain(&operations)?;
|
let (thumbnail_path, thumbnail_args) = self::processor::build_chain(&operations)?;
|
||||||
|
@ -639,7 +643,7 @@ async fn launch<S: Store + Clone + 'static>(
|
||||||
let store2 = store.clone();
|
let store2 = store.clone();
|
||||||
let form = Form::new()
|
let form = Form::new()
|
||||||
.max_files(10)
|
.max_files(10)
|
||||||
.max_file_size(CONFIG.max_file_size() * MEGABYTES)
|
.max_file_size(CONFIG.media.max_file_size * MEGABYTES)
|
||||||
.transform_error(transform_error)
|
.transform_error(transform_error)
|
||||||
.field(
|
.field(
|
||||||
"images",
|
"images",
|
||||||
|
@ -667,12 +671,12 @@ async fn launch<S: Store + Clone + 'static>(
|
||||||
// Create a new Multipart Form validator for internal imports
|
// Create a new Multipart Form validator for internal imports
|
||||||
//
|
//
|
||||||
// This form is expecting a single array field, 'images' with at most 10 files in it
|
// This form is expecting a single array field, 'images' with at most 10 files in it
|
||||||
let validate_imports = CONFIG.validate_imports();
|
let validate_imports = !CONFIG.media.skip_validate_imports;
|
||||||
let manager2 = manager.clone();
|
let manager2 = manager.clone();
|
||||||
let store2 = store.clone();
|
let store2 = store.clone();
|
||||||
let import_form = Form::new()
|
let import_form = Form::new()
|
||||||
.max_files(10)
|
.max_files(10)
|
||||||
.max_file_size(CONFIG.max_file_size() * MEGABYTES)
|
.max_file_size(CONFIG.media.max_file_size * MEGABYTES)
|
||||||
.transform_error(transform_error)
|
.transform_error(transform_error)
|
||||||
.field(
|
.field(
|
||||||
"images",
|
"images",
|
||||||
|
@ -708,7 +712,7 @@ async fn launch<S: Store + Clone + 'static>(
|
||||||
.app_data(web::Data::new(store.clone()))
|
.app_data(web::Data::new(store.clone()))
|
||||||
.app_data(web::Data::new(manager.clone()))
|
.app_data(web::Data::new(manager.clone()))
|
||||||
.app_data(web::Data::new(build_client()))
|
.app_data(web::Data::new(build_client()))
|
||||||
.app_data(web::Data::new(CONFIG.allowed_filters()))
|
.app_data(web::Data::new(CONFIG.media.filters.clone()))
|
||||||
.service(
|
.service(
|
||||||
web::scope("/image")
|
web::scope("/image")
|
||||||
.service(
|
.service(
|
||||||
|
@ -739,7 +743,9 @@ async fn launch<S: Store + Clone + 'static>(
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::scope("/internal")
|
web::scope("/internal")
|
||||||
.wrap(Internal(CONFIG.api_key().map(|s| s.to_owned())))
|
.wrap(Internal(
|
||||||
|
CONFIG.server.api_key.as_ref().map(|s| s.to_owned()),
|
||||||
|
))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/import")
|
web::resource("/import")
|
||||||
.wrap(import_form.clone())
|
.wrap(import_form.clone())
|
||||||
|
@ -749,7 +755,7 @@ async fn launch<S: Store + Clone + 'static>(
|
||||||
.service(web::resource("/aliases").route(web::get().to(aliases::<S>))),
|
.service(web::resource("/aliases").route(web::get().to(aliases::<S>))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.bind(CONFIG.bind_address())?
|
.bind(CONFIG.server.address)?
|
||||||
.run()
|
.run()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -762,17 +768,17 @@ async fn migrate_inner<S1>(
|
||||||
manager: &UploadManager,
|
manager: &UploadManager,
|
||||||
repo: &Repo,
|
repo: &Repo,
|
||||||
from: S1,
|
from: S1,
|
||||||
to: &config::Storage,
|
to: &config::Store,
|
||||||
) -> anyhow::Result<()>
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
S1: Store,
|
S1: Store,
|
||||||
{
|
{
|
||||||
match to {
|
match to {
|
||||||
config::Storage::Filesystem(RequiredFilesystemStorage { path }) => {
|
config::Store::Filesystem(config::Filesystem { path }) => {
|
||||||
let to = FileStore::build(path.clone(), repo.clone()).await?;
|
let to = FileStore::build(path.clone(), repo.clone()).await?;
|
||||||
manager.migrate_store::<S1, FileStore>(from, to).await?;
|
manager.migrate_store::<S1, FileStore>(from, to).await?;
|
||||||
}
|
}
|
||||||
config::Storage::ObjectStorage(RequiredObjectStorage {
|
config::Store::ObjectStorage(config::ObjectStorage {
|
||||||
bucket_name,
|
bucket_name,
|
||||||
region,
|
region,
|
||||||
access_key,
|
access_key,
|
||||||
|
@ -782,9 +788,9 @@ where
|
||||||
}) => {
|
}) => {
|
||||||
let to = ObjectStore::build(
|
let to = ObjectStore::build(
|
||||||
bucket_name,
|
bucket_name,
|
||||||
region.clone(),
|
region.as_ref().clone(),
|
||||||
access_key.clone(),
|
Some(access_key.clone()),
|
||||||
secret_key.clone(),
|
Some(secret_key.clone()),
|
||||||
security_token.clone(),
|
security_token.clone(),
|
||||||
session_token.clone(),
|
session_token.clone(),
|
||||||
repo.clone(),
|
repo.clone(),
|
||||||
|
@ -801,38 +807,24 @@ where
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
init_tracing(
|
init_tracing(&CONFIG.tracing)?;
|
||||||
"pict-rs",
|
|
||||||
CONFIG.opentelemetry_url(),
|
|
||||||
CONFIG.console_buffer_capacity(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let repo = Repo::open(CONFIG.repo())?;
|
let repo = Repo::open(CONFIG.repo.clone())?;
|
||||||
|
|
||||||
let db = LatestDb::exists(CONFIG.data_dir()).migrate()?;
|
let db = LatestDb::exists(CONFIG.old_db.path.clone()).migrate()?;
|
||||||
repo.from_db(db).await?;
|
repo.from_db(db).await?;
|
||||||
|
|
||||||
let manager = UploadManager::new(repo.clone(), CONFIG.format()).await?;
|
let manager = UploadManager::new(repo.clone(), CONFIG.media.format).await?;
|
||||||
|
|
||||||
match CONFIG.command()? {
|
|
||||||
CommandConfig::Run => (),
|
|
||||||
CommandConfig::Dump { path } => {
|
|
||||||
let configuration = toml::to_string_pretty(&*CONFIG)?;
|
|
||||||
tokio::fs::write(path, configuration).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
CommandConfig::MigrateRepo { to: _ } => {
|
|
||||||
unimplemented!("Repo migrations are currently unsupported")
|
|
||||||
}
|
|
||||||
CommandConfig::MigrateStore { to } => {
|
|
||||||
let from = CONFIG.store()?;
|
|
||||||
|
|
||||||
|
match (*OPERATION).clone() {
|
||||||
|
Operation::Run => (),
|
||||||
|
Operation::MigrateStore { from, to } => {
|
||||||
match from {
|
match from {
|
||||||
config::Storage::Filesystem(RequiredFilesystemStorage { path }) => {
|
config::Store::Filesystem(config::Filesystem { path }) => {
|
||||||
let from = FileStore::build(path.clone(), repo.clone()).await?;
|
let from = FileStore::build(path.clone(), repo.clone()).await?;
|
||||||
migrate_inner(&manager, &repo, from, &to).await?;
|
migrate_inner(&manager, &repo, from, &to).await?;
|
||||||
}
|
}
|
||||||
config::Storage::ObjectStorage(RequiredObjectStorage {
|
config::Store::ObjectStorage(config::ObjectStorage {
|
||||||
bucket_name,
|
bucket_name,
|
||||||
region,
|
region,
|
||||||
access_key,
|
access_key,
|
||||||
|
@ -842,9 +834,9 @@ async fn main() -> anyhow::Result<()> {
|
||||||
}) => {
|
}) => {
|
||||||
let from = ObjectStore::build(
|
let from = ObjectStore::build(
|
||||||
&bucket_name,
|
&bucket_name,
|
||||||
region,
|
Serde::into_inner(region),
|
||||||
access_key,
|
Some(access_key),
|
||||||
secret_key,
|
Some(secret_key),
|
||||||
security_token,
|
security_token,
|
||||||
session_token,
|
session_token,
|
||||||
repo.clone(),
|
repo.clone(),
|
||||||
|
@ -860,12 +852,12 @@ async fn main() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match CONFIG.store()? {
|
match CONFIG.store.clone() {
|
||||||
config::Storage::Filesystem(RequiredFilesystemStorage { path }) => {
|
config::Store::Filesystem(config::Filesystem { path }) => {
|
||||||
let store = FileStore::build(path, repo).await?;
|
let store = FileStore::build(path, repo).await?;
|
||||||
launch(manager, store).await
|
launch(manager, store).await
|
||||||
}
|
}
|
||||||
config::Storage::ObjectStorage(RequiredObjectStorage {
|
config::Store::ObjectStorage(config::ObjectStorage {
|
||||||
bucket_name,
|
bucket_name,
|
||||||
region,
|
region,
|
||||||
access_key,
|
access_key,
|
||||||
|
@ -875,9 +867,9 @@ async fn main() -> anyhow::Result<()> {
|
||||||
}) => {
|
}) => {
|
||||||
let store = ObjectStore::build(
|
let store = ObjectStore::build(
|
||||||
&bucket_name,
|
&bucket_name,
|
||||||
region,
|
Serde::into_inner(region),
|
||||||
access_key,
|
Some(access_key),
|
||||||
secret_key,
|
Some(secret_key),
|
||||||
security_token,
|
security_token,
|
||||||
session_token,
|
session_token,
|
||||||
repo,
|
repo,
|
||||||
|
|
11
src/repo.rs
11
src/repo.rs
|
@ -1,9 +1,4 @@
|
||||||
use crate::{
|
use crate::{config, details::Details, error::Error, store::Identifier};
|
||||||
config::{Repository, RequiredSledRepo},
|
|
||||||
details::Details,
|
|
||||||
error::Error,
|
|
||||||
store::Identifier,
|
|
||||||
};
|
|
||||||
use futures_util::Stream;
|
use futures_util::Stream;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -125,9 +120,9 @@ pub(crate) trait AliasRepo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Repo {
|
impl Repo {
|
||||||
pub(crate) fn open(config: Repository) -> anyhow::Result<Self> {
|
pub(crate) fn open(config: config::Repo) -> anyhow::Result<Self> {
|
||||||
match config {
|
match config {
|
||||||
Repository::Sled(RequiredSledRepo {
|
config::Repo::Sled(config::Sled {
|
||||||
mut path,
|
mut path,
|
||||||
cache_capacity,
|
cache_capacity,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
|
@ -18,6 +18,18 @@ impl<T> Serde<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> AsRef<T> for Serde<T> {
|
||||||
|
fn as_ref(&self) -> &T {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsMut<T> for Serde<T> {
|
||||||
|
fn as_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Deref for Serde<T> {
|
impl<T> Deref for Serde<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Format,
|
config::ImageFormat,
|
||||||
details::Details,
|
details::Details,
|
||||||
error::{Error, UploadError},
|
error::{Error, UploadError},
|
||||||
ffmpeg::{InputFormat, ThumbnailFormat},
|
ffmpeg::{InputFormat, ThumbnailFormat},
|
||||||
|
@ -28,14 +28,14 @@ pub(crate) struct UploadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct UploadManagerInner {
|
pub(crate) struct UploadManagerInner {
|
||||||
format: Option<Format>,
|
format: Option<ImageFormat>,
|
||||||
hasher: sha2::Sha256,
|
hasher: sha2::Sha256,
|
||||||
repo: Repo,
|
repo: Repo,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UploadManager {
|
impl UploadManager {
|
||||||
/// Create a new UploadManager
|
/// Create a new UploadManager
|
||||||
pub(crate) async fn new(repo: Repo, format: Option<Format>) -> Result<Self, Error> {
|
pub(crate) async fn new(repo: Repo, format: Option<ImageFormat>) -> Result<Self, Error> {
|
||||||
let manager = UploadManager {
|
let manager = UploadManager {
|
||||||
inner: Arc::new(UploadManagerInner {
|
inner: Arc::new(UploadManagerInner {
|
||||||
format,
|
format,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Format, either::Either, error::Error, ffmpeg::InputFormat, magick::ValidInputType,
|
config::ImageFormat, either::Either, error::Error, ffmpeg::InputFormat, magick::ValidInputType,
|
||||||
};
|
};
|
||||||
use actix_web::web::Bytes;
|
use actix_web::web::Bytes;
|
||||||
use tokio::io::AsyncRead;
|
use tokio::io::AsyncRead;
|
||||||
|
@ -35,7 +35,7 @@ impl AsyncRead for UnvalidatedBytes {
|
||||||
#[instrument(name = "Validate image", skip(bytes))]
|
#[instrument(name = "Validate image", skip(bytes))]
|
||||||
pub(crate) async fn validate_image_bytes(
|
pub(crate) async fn validate_image_bytes(
|
||||||
bytes: Bytes,
|
bytes: Bytes,
|
||||||
prescribed_format: Option<Format>,
|
prescribed_format: Option<ImageFormat>,
|
||||||
validate: bool,
|
validate: bool,
|
||||||
) -> Result<(ValidInputType, impl AsyncRead + Unpin), Error> {
|
) -> Result<(ValidInputType, impl AsyncRead + Unpin), Error> {
|
||||||
let input_type = crate::magick::input_type_bytes(bytes.clone()).await?;
|
let input_type = crate::magick::input_type_bytes(bytes.clone()).await?;
|
||||||
|
@ -57,19 +57,19 @@ pub(crate) async fn validate_image_bytes(
|
||||||
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Mp4).await?,
|
crate::ffmpeg::to_mp4_bytes(bytes, InputFormat::Mp4).await?,
|
||||||
)),
|
)),
|
||||||
)),
|
)),
|
||||||
(Some(Format::Jpeg) | None, ValidInputType::Jpeg) => Ok((
|
(Some(ImageFormat::Jpeg) | None, ValidInputType::Jpeg) => Ok((
|
||||||
ValidInputType::Jpeg,
|
ValidInputType::Jpeg,
|
||||||
Either::right(Either::right(Either::left(
|
Either::right(Either::right(Either::left(
|
||||||
crate::exiftool::clear_metadata_bytes_read(bytes)?,
|
crate::exiftool::clear_metadata_bytes_read(bytes)?,
|
||||||
))),
|
))),
|
||||||
)),
|
)),
|
||||||
(Some(Format::Png) | None, ValidInputType::Png) => Ok((
|
(Some(ImageFormat::Png) | None, ValidInputType::Png) => Ok((
|
||||||
ValidInputType::Png,
|
ValidInputType::Png,
|
||||||
Either::right(Either::right(Either::left(
|
Either::right(Either::right(Either::left(
|
||||||
crate::exiftool::clear_metadata_bytes_read(bytes)?,
|
crate::exiftool::clear_metadata_bytes_read(bytes)?,
|
||||||
))),
|
))),
|
||||||
)),
|
)),
|
||||||
(Some(Format::Webp) | None, ValidInputType::Webp) => Ok((
|
(Some(ImageFormat::Webp) | None, ValidInputType::Webp) => Ok((
|
||||||
ValidInputType::Webp,
|
ValidInputType::Webp,
|
||||||
Either::right(Either::right(Either::right(Either::left(
|
Either::right(Either::right(Either::right(Either::left(
|
||||||
crate::magick::clear_metadata_bytes_read(bytes)?,
|
crate::magick::clear_metadata_bytes_read(bytes)?,
|
||||||
|
|
Loading…
Reference in a new issue