use std::net::{SocketAddr, ToSocketAddrs};
use std::path::PathBuf;
use hex::FromHex;
use structopt::StructOpt;
use tracing_subscriber::filter::EnvFilter;
use tracing_subscriber::filter::LevelFilter;
use rudolfs::{Cache, LocalServerBuilder, S3ServerBuilder};
static AFTER_HELP: &str = include_str!("help.md");
#[derive(StructOpt)]
#[structopt(after_help = AFTER_HELP)]
struct Args {
#[structopt(flatten)]
global: GlobalArgs,
#[structopt(subcommand)]
backend: Backend,
}
#[derive(StructOpt)]
enum Backend {
#[structopt(name = "s3")]
S3(S3Args),
#[structopt(name = "local")]
Local(LocalArgs),
}
#[derive(StructOpt)]
struct GlobalArgs {
#[structopt(long = "host", env = "RUDOLFS_HOST")]
host: Option<String>,
#[structopt(long = "port", default_value = "8080", env = "PORT")]
port: u16,
#[structopt(
long = "key",
parse(try_from_str = FromHex::from_hex),
env = "RUDOLFS_KEY"
)]
key: Option<[u8; 32]>,
#[structopt(long = "cache-dir", env = "RUDOLFS_CACHE_DIR")]
cache_dir: Option<PathBuf>,
#[structopt(
long = "max-cache-size",
default_value = "50 GiB",
env = "RUDOLFS_MAX_CACHE_SIZE"
)]
max_cache_size: human_size::Size,
#[structopt(
long = "log-level",
default_value = "info",
env = "RUDOLFS_LOG"
)]
log_level: LevelFilter,
}
#[derive(StructOpt)]
struct S3Args {
#[structopt(long, env = "RUDOLFS_S3_BUCKET")]
bucket: String,
#[structopt(long, default_value = "lfs", env = "RUDOLFS_S3_PREFIX")]
prefix: String,
#[structopt(long = "cdn", env = "RUDOLFS_S3_CDN")]
cdn: Option<String>,
}
#[derive(StructOpt)]
struct LocalArgs {
#[structopt(long, env = "RUDOLFS_LOCAL_PATH")]
path: PathBuf,
}
impl Args {
async fn main(self) -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::from_default_env().add_directive(
format!(
"{}={}",
env!("CARGO_PKG_NAME"),
self.global.log_level
)
.parse()?,
),
)
.init();
let addr = match self.global.host {
Some(ref host) => host
.to_socket_addrs()?
.next()
.unwrap_or_else(|| SocketAddr::from(([0, 0, 0, 0], 8080))),
None => SocketAddr::from(([0, 0, 0, 0], self.global.port)),
};
tracing::info!("Initializing storage...");
match self.backend {
Backend::S3(s3) => s3.run(addr, self.global).await?,
Backend::Local(local) => local.run(addr, self.global).await?,
}
Ok(())
}
}
impl S3Args {
async fn run(
self,
addr: SocketAddr,
global_args: GlobalArgs,
) -> Result<(), Box<dyn std::error::Error>> {
let mut builder = S3ServerBuilder::new(self.bucket);
builder.prefix(self.prefix);
if let Some(key) = global_args.key {
builder.key(key);
}
if let Some(cdn) = self.cdn {
builder.cdn(cdn);
}
if let Some(cache_dir) = global_args.cache_dir {
let max_cache_size = global_args
.max_cache_size
.into::<human_size::Byte>()
.value() as u64;
builder.cache(Cache::new(cache_dir, max_cache_size));
}
builder.run(addr).await
}
}
impl LocalArgs {
async fn run(
self,
addr: SocketAddr,
global_args: GlobalArgs,
) -> Result<(), Box<dyn std::error::Error>> {
let mut builder = LocalServerBuilder::new(self.path);
if let Some(key) = global_args.key {
builder.key(key);
}
if let Some(cache_dir) = global_args.cache_dir {
let max_cache_size = global_args
.max_cache_size
.into::<human_size::Byte>()
.value() as u64;
builder.cache(Cache::new(cache_dir, max_cache_size));
}
builder.run(addr).await
}
}
#[tokio::main]
async fn main() {
let exit_code = if let Err(err) = Args::from_args().main().await {
tracing::error!("{err}");
1
} else {
0
};
std::process::exit(exit_code);
}