use clap::{Parser, Subcommand};
use std::ffi::OsString;
use std::io::{self, Read, Write};
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;
use std::process::ExitCode;
use syntheca::{GetError, Hash, Pool, PutError, StatError};
#[derive(Parser)]
#[command(name = "syn", version, about = "syntheca content-addressable store")]
struct Cli {
#[arg(long, global = true)]
pool: Option<PathBuf>,
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
Put {
path: OsString,
},
Get { hash: String },
Stat { hash: String },
}
fn main() -> ExitCode {
let cli = Cli::parse();
let pool_root = cli.pool.unwrap_or_else(default_pool_root);
let pool = match Pool::open(&pool_root) {
Ok(p) => p,
Err(e) => {
let _ = writeln!(io::stderr(), "syn: open pool {}: {e}", pool_root.display());
return ExitCode::from(1);
}
};
match cli.cmd {
Cmd::Put { path } => cmd_put(&pool, path),
Cmd::Get { hash } => cmd_get(&pool, hash),
Cmd::Stat { hash } => cmd_stat(&pool, hash),
}
}
fn cmd_put(pool: &Pool, path: OsString) -> ExitCode {
let bytes = if path.as_bytes() == b"-" {
let mut v = Vec::new();
if let Err(e) = io::stdin().lock().read_to_end(&mut v) {
let _ = writeln!(io::stderr(), "syn: read stdin: {e}");
return ExitCode::from(1);
}
v
} else {
match std::fs::read(&path) {
Ok(v) => v,
Err(e) => {
let _ = writeln!(io::stderr(), "syn: read {:?}: {e}", path);
return ExitCode::from(1);
}
}
};
match pool.put(&bytes) {
Ok(hash) => {
let mut out = io::stdout().lock();
if writeln!(out, "{hash}").is_err() {
return ExitCode::from(1);
}
ExitCode::from(0)
}
Err(PutError::HashCollision) => {
let _ = writeln!(
io::stderr(),
"syn: hash collision: distinct bytes hashed to an existing entry's name"
);
ExitCode::from(1)
}
Err(e) => {
let _ = writeln!(io::stderr(), "syn: put: {e}");
ExitCode::from(1)
}
}
}
fn cmd_get(pool: &Pool, hash_str: String) -> ExitCode {
let hash = match Hash::from_hex(&hash_str) {
Ok(h) => h,
Err(e) => {
let _ = writeln!(io::stderr(), "syn: invalid hash: {e}");
return ExitCode::from(1);
}
};
match pool.get(&hash) {
Ok(bytes) => {
let mut out = io::stdout().lock();
if let Err(e) = out.write_all(&bytes) {
let _ = writeln!(io::stderr(), "syn: write stdout: {e}");
return ExitCode::from(1);
}
ExitCode::from(0)
}
Err(GetError::NotFound) => {
let _ = writeln!(io::stderr(), "syn: not found");
ExitCode::from(1)
}
Err(GetError::IntegrityError) => {
let _ = writeln!(io::stderr(), "syn: integrity error: stored bytes do not match expected hash");
ExitCode::from(1)
}
Err(e) => {
let _ = writeln!(io::stderr(), "syn: get: {e}");
ExitCode::from(1)
}
}
}
fn cmd_stat(pool: &Pool, hash_str: String) -> ExitCode {
let hash = match Hash::from_hex(&hash_str) {
Ok(h) => h,
Err(e) => {
let _ = writeln!(io::stderr(), "syn: invalid hash: {e}");
return ExitCode::from(1);
}
};
match pool.stat(&hash) {
Ok(stat) => {
let mut out = io::stdout().lock();
if writeln!(out, "size {}", stat.size).is_err()
|| writeln!(out, "sha256 {}", hex::encode(stat.sha256)).is_err()
{
return ExitCode::from(1);
}
ExitCode::from(0)
}
Err(StatError::NotFound) => {
let _ = writeln!(io::stderr(), "syn: not found");
ExitCode::from(1)
}
Err(e) => {
let _ = writeln!(io::stderr(), "syn: stat: {e}");
ExitCode::from(1)
}
}
}
fn default_pool_root() -> PathBuf {
let home = std::env::var_os("HOME").unwrap_or_else(|| OsString::from("."));
let mut p = PathBuf::from(home);
p.push(".syntheca");
p
}