use clap::{Parser, Subcommand};
use indoc::indoc;
use log::{error, info, warn};
use simd_r_drive::{DataStore, EntryStream};
mod utils;
use std::io::{self, IsTerminal, Write};
use std::path::PathBuf;
use utils::format_bytes;
const HELP_TEMPLATE: &str = indoc! {r#"
Examples:
# Writing a value explicitly
%BINARY_NAME% data.bin write mykey "Hello, world!"
# Writing a value from stdin
echo "Hello, world!" | %BINARY_NAME% data.bin write mykey
cat file.txt | %BINARY_NAME% data.bin write mykey
# Reading a value
%BINARY_NAME% data.bin read mykey
# Deleting a key
%BINARY_NAME% data.bin delete mykey
# Copying a key to another storage file
%BINARY_NAME% data.bin copy mykey target_data.bin
# Moving a key to another storage file (deletes from source)
%BINARY_NAME% data.bin move mykey target_data.bin
# Compacting the storage file
%BINARY_NAME% data.bin compact
# Displaying storage file info
%BINARY_NAME% data.bin info
"#};
#[derive(Parser)]
#[command(
name = env!("CARGO_PKG_NAME"),
version = env!("CARGO_PKG_VERSION"),
about = env!("CARGO_PKG_DESCRIPTION"),
long_about = None
)]
#[command(
after_help = HELP_TEMPLATE.replace("%BINARY_NAME%", env!("CARGO_PKG_NAME"))
)]
struct Cli {
#[arg(
value_name = "storage",
help = "Path to the storage file. If the file does not exist, it will be created automatically."
)]
storage: PathBuf,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Read {
key: String,
},
Write {
key: String,
value: Option<String>,
},
Copy {
key: String,
#[arg(value_name = "target")]
target: PathBuf,
},
Move {
key: String,
#[arg(value_name = "target")]
target: PathBuf,
},
Rename {
old_key: String,
new_key: String
},
Delete {
key: String,
},
Compact,
Info,
Metadata {
key: String,
},
}
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let cli = Cli::parse();
match &cli.command {
Commands::Read { key } => {
let storage = DataStore::open(&cli.storage).expect("Failed to open storage");
match storage.read(key.as_bytes()) {
Some(entry_handle) => {
let stdout = io::stdout();
let mut stdout_handle = stdout.lock();
if stdout.is_terminal() {
writeln!(stdout_handle, "{}", String::from_utf8_lossy(entry_handle.as_slice()))
.expect("Failed to write output");
} else {
let mut output_stream = EntryStream::from(entry_handle);
io::copy(&mut output_stream, &mut stdout_handle).expect("Failed to write binary output");
stdout_handle.flush().expect("Failed to flush output");
}
}
None => {
error!("Error: Key '{}' not found", key);
std::process::exit(1);
}
}
}
Commands::Write { key, value } => {
let storage = DataStore::open(&cli.storage).expect("Failed to open storage");
let key_as_bytes = key.as_bytes();
if let Some(value) = value {
storage
.write(key_as_bytes, value.as_bytes())
.expect("Failed to write entry");
} else if !io::stdin().is_terminal() {
let mut stdin_reader = io::stdin().lock();
if let Err(err) = storage.write_stream(key_as_bytes, &mut stdin_reader) {
error!("Failed to write streamed stdin data: {}", err);
std::process::exit(1);
}
} else {
error!("Error: No value provided and stdin is empty.");
std::process::exit(1);
}
info!("Stored '{}'", key);
}
Commands::Copy { key, target } => {
let source_storage =
DataStore::open(&cli.storage).expect("Failed to open source storage");
let mut target_storage =
DataStore::open(target).expect("Failed to open target storage");
source_storage
.copy_entry(key.as_bytes(), &mut target_storage)
.map_err(|err| {
error!("Could not copy entry. Received error: {}", err.to_string());
std::process::exit(1);
})
.ok();
info!("Copied key '{}' to {:?}", key, target);
}
Commands::Move { key, target } => {
let source_storage =
DataStore::open(&cli.storage).expect("Failed to open source storage");
let mut target_storage =
DataStore::open(target).expect("Failed to open target storage");
source_storage
.move_entry(key.as_bytes(), &mut target_storage)
.map_err(|err| {
error!("Could not copy entry. Received error: {}", err.to_string());
std::process::exit(1);
})
.ok();
info!("Moved key '{}' to {:?}", key, target);
}
Commands::Rename { old_key, new_key } => {
let storage =
DataStore::open(&cli.storage).expect("Failed to open source storage");
storage
.rename_entry(old_key.as_bytes(), new_key.as_bytes())
.map_err(|err| {
error!("Could not rename entry. Received error: {}", err.to_string());
std::process::exit(1);
})
.ok();
info!("Renamed key '{}' to {}", old_key, new_key);
}
Commands::Delete { key } => {
let storage = DataStore::open(&cli.storage).expect("Failed to open storage");
storage
.delete_entry(key.as_bytes())
.expect("Failed to delete entry");
warn!("Deleted key '{}'", key);
}
Commands::Compact => {
let mut storage = DataStore::open(&cli.storage).expect("Failed to open storage");
info!("Starting compaction...");
if let Err(e) = storage.compact() {
error!("Compaction failed: {}", e);
std::process::exit(1);
}
info!("Compaction completed successfully.");
}
Commands::Metadata { key } => {
let storage = DataStore::open(&cli.storage).expect("Failed to open storage");
match storage.read(key.as_bytes()) {
Some(entry) => {
println!(
"\n{:=^50}\n\
{:<25} \"{}\"\n\
{:-<50}\n\
{:<25} {} bytes\n\
{:<25} {} bytes\n\
{:<25} {:?}\n\
{:<25} {:?}\n\
{:<25} {}\n\
{:<25} {}\n\
{:<25} {}\n\
{:-<50}\n\
{:<25} {:?}\n\
{:=<50}",
" METADATA SUMMARY ", "ENTRY FOR:",
key, "", "PAYLOAD SIZE:",
entry.size(),
"TOTAL SIZE (W/ METADATA):",
entry.size_with_metadata(),
"OFFSET RANGE:",
entry.offset_range(),
"MEMORY ADDRESS:",
entry.address_range(),
"KEY HASH:",
entry.key_hash(),
"CHECKSUM:",
entry.checksum(),
"CHECKSUM VALIDITY:",
if entry.is_valid_checksum() {
"VALID"
} else {
"INVALID"
},
"", "STORED METADATA:",
entry.metadata(),
"=" );
}
None => {
error!("Error: Key '{}' not found", key);
std::process::exit(1);
}
}
}
Commands::Info => {
let storage = DataStore::open(&cli.storage).expect("Failed to open storage");
let storage_size = storage.get_storage_size().unwrap_or(0);
let savings_estimate = storage.estimate_compaction_savings();
let entry_count = storage.count();
println!(
"\n{:=^50}\n\
{:<25} {:?}\n\
{:-<50}\n\
{:<25} {}\n\
{:<25} {}\n\
{:<25} {}\n\
{:=<50}",
" STORAGE INFO ", "STORAGE FILE:",
cli.storage, "", "TOTAL SIZE:",
format_bytes(storage_size),
"ACTIVE ENTRIES:",
entry_count,
"COMPACTION SAVINGS:",
format_bytes(savings_estimate),
"=" );
}
}
}