use std::io;
use std::path::PathBuf;
use clap::Subcommand;
use crate::config::StorageConfiguration;
use crate::{Error, Storage};
pub mod admin;
pub mod schema;
#[derive(Subcommand, Debug)]
pub enum StorageCommand {
#[clap(subcommand)]
Backup(Location),
#[clap(subcommand)]
Restore(Location),
#[clap(subcommand)]
Admin(admin::Command),
Schema(schema::Command),
}
#[derive(Subcommand, Debug)]
pub enum Location {
Path {
path: PathBuf,
},
}
impl StorageCommand {
pub fn execute(self, config: StorageConfiguration) -> Result<(), Error> {
let storage = Storage::open(config)?;
self.execute_on(&storage)
}
pub fn execute_on(self, storage: &Storage) -> Result<(), Error> {
match self {
StorageCommand::Backup(location) => location.backup(storage),
StorageCommand::Restore(location) => location.restore(storage),
StorageCommand::Admin(admin) => admin.execute(storage),
StorageCommand::Schema(schema) => schema.execute(storage),
}
}
#[cfg(feature = "async")]
pub async fn execute_on_async(self, storage: &crate::AsyncStorage) -> Result<(), Error> {
match self {
StorageCommand::Backup(location) => location.backup_async(storage).await,
StorageCommand::Restore(location) => location.restore_async(storage).await,
StorageCommand::Admin(admin) => admin.execute_async(storage).await,
StorageCommand::Schema(schema) => schema.execute_async(storage).await,
}
}
}
impl Location {
pub fn backup(&self, storage: &Storage) -> Result<(), Error> {
match self {
Location::Path { path } => storage.backup(path),
}
}
pub fn restore(&self, storage: &Storage) -> Result<(), Error> {
match self {
Location::Path { path } => storage.restore(path),
}
}
#[cfg(feature = "async")]
pub async fn backup_async(&self, storage: &crate::AsyncStorage) -> Result<(), Error> {
match self {
Location::Path { path } => storage.backup(path.clone()).await,
}
}
#[cfg(feature = "async")]
pub async fn restore_async(&self, storage: &crate::AsyncStorage) -> Result<(), Error> {
match self {
Location::Path { path } => storage.restore(path.clone()).await,
}
}
}
#[cfg(feature = "password-hashing")]
pub fn read_password_from_stdin(
confirm: bool,
) -> Result<bonsaidb_core::connection::SensitiveString, ReadPasswordError> {
let password = read_sensitive_input_from_stdin("Enter Password:")?;
if confirm {
let confirmed = read_sensitive_input_from_stdin("Re-enter the same password:")?;
if password != confirmed {
return Err(ReadPasswordError::PasswordConfirmationFailed);
}
}
Ok(password)
}
#[cfg(feature = "password-hashing")]
#[derive(thiserror::Error, Debug)]
pub enum ReadPasswordError {
#[error("password input cancelled")]
Cancelled,
#[error("password confirmation did not match")]
PasswordConfirmationFailed,
#[error("io error: {0}")]
Io(#[from] io::Error),
}
#[cfg(feature = "password-hashing")]
fn read_sensitive_input_from_stdin(
prompt: &str,
) -> Result<bonsaidb_core::connection::SensitiveString, ReadPasswordError> {
use std::io::stdout;
use crossterm::cursor::MoveToColumn;
use crossterm::terminal::{Clear, ClearType};
use crossterm::ExecutableCommand;
println!("{prompt} (input Enter or Return when done, or Escape to cancel)");
crossterm::terminal::enable_raw_mode()?;
let password = read_password_loop();
drop(
stdout()
.execute(MoveToColumn(1))?
.execute(Clear(ClearType::CurrentLine)),
);
crossterm::terminal::disable_raw_mode()?;
if let Some(password) = password? {
println!("********");
Ok(password)
} else {
Err(ReadPasswordError::Cancelled)
}
}
#[cfg(feature = "password-hashing")]
fn read_password_loop() -> io::Result<Option<bonsaidb_core::connection::SensitiveString>> {
const ESCAPE: u8 = 27;
const BACKSPACE: u8 = 127;
const CANCEL: u8 = 3;
const EOF: u8 = 4;
const CLEAR_BEFORE_CURSOR: u8 = 21;
use std::io::Read;
use crossterm::cursor::{MoveLeft, MoveToColumn};
use crossterm::style::Print;
use crossterm::terminal::{Clear, ClearType};
use crossterm::ExecutableCommand;
let mut stdin = std::io::stdin();
let mut stdout = std::io::stdout();
let mut buffer = [0; 1];
let mut password = bonsaidb_core::connection::SensitiveString::default();
loop {
if stdin.read(&mut buffer)? == 0 {
return Ok(Some(password));
}
match buffer[0] {
ESCAPE | CANCEL => return Ok(None),
BACKSPACE => {
password.pop();
stdout
.execute(MoveLeft(1))?
.execute(Clear(ClearType::UntilNewLine))?;
}
CLEAR_BEFORE_CURSOR => {
password.clear();
stdout
.execute(MoveToColumn(1))?
.execute(Clear(ClearType::CurrentLine))?;
}
b'\n' | b'\r' | EOF => {
return Ok(Some(password));
}
other if other.is_ascii_control() => {}
other => {
password.push(other as char);
stdout.execute(Print('*'))?;
}
}
}
}