use std::path::{Path, PathBuf};
use rand::random;
use cli_state::error::Result;
use ockam::SqlxDatabase;
use ockam_core::compat::sync::Arc;
use ockam_core::env::get_env_with_default;
use ockam_node::Executor;
use crate::cli_state;
use crate::cli_state::CliStateError;
#[derive(Debug, Clone)]
pub struct CliState {
dir: PathBuf,
database: Arc<SqlxDatabase>,
}
impl CliState {
pub fn new(dir: &Path) -> Result<Self> {
Executor::execute_future(Self::create(dir.into()))?
}
pub fn dir(&self) -> PathBuf {
self.dir.clone()
}
pub fn database_path(&self) -> PathBuf {
Self::make_database_path(&self.dir)
}
}
impl CliState {
pub fn with_default_dir() -> Result<Self> {
Self::new(Self::default_dir()?.as_path())
}
pub async fn reset(&self) -> Result<()> {
self.delete_all_nodes(true).await?;
self.delete_all_named_vaults().await?;
self.delete()
}
pub fn delete(&self) -> Result<()> {
Self::delete_at(&self.dir)
}
pub async fn recreate(&self) -> Result<CliState> {
self.reset().await?;
Self::create(self.dir.clone()).await
}
pub fn backup_and_reset() -> Result<CliState> {
let dir = Self::default_dir()?;
let backup_dir = Self::backup_default_dir()?;
if backup_dir.exists() {
let _ = std::fs::remove_dir_all(&backup_dir);
}
std::fs::create_dir_all(&backup_dir)?;
for entry in std::fs::read_dir(&dir)? {
let entry = entry?;
let from = entry.path();
let to = backup_dir.join(entry.file_name());
std::fs::rename(from, to)?;
}
Self::delete_at(&dir)?;
let state = Self::new(&dir)?;
let dir = &state.dir;
let backup_dir = CliState::backup_default_dir().unwrap();
eprintln!("The {dir:?} directory has been reset and has been backed up to {backup_dir:?}");
Ok(state)
}
pub fn backup_default_dir() -> Result<PathBuf> {
let dir = Self::default_dir()?;
let dir_name =
dir.file_name()
.and_then(|n| n.to_str())
.ok_or(CliStateError::InvalidOperation(
"The $OCKAM_HOME directory does not have a valid name".to_string(),
))?;
let parent = dir.parent().ok_or(CliStateError::InvalidOperation(
"The $OCKAM_HOME directory does not a valid parent directory".to_string(),
))?;
Ok(parent.join(format!("{dir_name}.bak")))
}
}
impl CliState {
pub(super) async fn create(dir: PathBuf) -> Result<Self> {
std::fs::create_dir_all(&dir)?;
let database = Arc::new(SqlxDatabase::create(Self::make_database_path(&dir)).await?);
debug!("Opened the database with options {:?}", database);
let state = Self { dir, database };
Ok(state)
}
pub(super) fn database(&self) -> Arc<SqlxDatabase> {
self.database.clone()
}
pub(super) fn make_database_path(root_path: &Path) -> PathBuf {
root_path.join("database.sqlite3")
}
pub(super) fn make_node_dir_path(root_path: &Path, node_name: &str) -> PathBuf {
Self::make_nodes_dir_path(root_path).join(node_name)
}
pub(super) fn make_nodes_dir_path(root_path: &Path) -> PathBuf {
root_path.join("nodes")
}
fn delete_at(root_path: &Path) -> Result<()> {
let _ = std::fs::remove_dir_all(Self::make_nodes_dir_path(root_path));
let _ = std::fs::remove_file(Self::make_database_path(root_path));
let _ = std::fs::remove_dir(root_path);
Ok(())
}
fn default_dir() -> Result<PathBuf> {
Ok(get_env_with_default::<PathBuf>(
"OCKAM_HOME",
home::home_dir()
.ok_or(CliStateError::InvalidPath("$HOME".to_string()))?
.join(".ockam"),
)?)
}
}
pub fn random_name() -> String {
petname::petname(2, "-").unwrap_or(hex::encode(random::<[u8; 4]>()))
}