cloud_terrastodon_pathing 0.25.0

Well-known paths for the Cloud Terrastodon project
Documentation
use clap::ValueEnum;
use directories_next::ProjectDirs;
use eyre::Context;
use eyre::bail;
use std::path::Path;
use std::path::PathBuf;
use std::sync::LazyLock;
use tokio::fs::create_dir_all;
use tokio::fs::try_exists;
use tracing::debug;
use tracing::field::debug;

static PROJECT_DIRS: LazyLock<ProjectDirs> = LazyLock::new(|| {
    let Some(project_dirs) = ProjectDirs::from_path(PathBuf::from("cloud_terrastodon")) else {
        panic!("Failed to acquire disk locations for project");
    };
    project_dirs
});
static CACHE_DIR: LazyLock<PathBuf> = LazyLock::new(|| PROJECT_DIRS.cache_dir().to_path_buf());
static DATA_DIR: LazyLock<PathBuf> = LazyLock::new(|| PROJECT_DIRS.data_dir().to_path_buf());
static CONFIG_DIR: LazyLock<PathBuf> = LazyLock::new(|| PROJECT_DIRS.config_dir().to_path_buf());

#[derive(Debug, Clone, ValueEnum)]
pub enum AppDir {
    Commands,
    Imports,
    Processed,
    Temp,
    Config,
    WorkItems,
}
impl std::fmt::Display for AppDir {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            AppDir::Commands => "Commands",
            AppDir::Imports => "Imports",
            AppDir::Processed => "Processed",
            AppDir::Temp => "Temp",
            AppDir::Config => "Config",
            AppDir::WorkItems => "Work Items",
        })
    }
}
impl AppDir {
    pub fn as_path_buf(&self) -> PathBuf {
        match self {
            AppDir::Commands => CACHE_DIR.join("commands"),
            AppDir::WorkItems => CACHE_DIR.join("work_items"),
            AppDir::Imports => DATA_DIR.join("imports"),
            AppDir::Processed => DATA_DIR.join("processed"),
            AppDir::Temp => DATA_DIR.join("temp"),
            AppDir::Config => CONFIG_DIR.clone(),
        }
    }
    pub fn join(self, path: impl AsRef<Path>) -> PathBuf {
        let buf: PathBuf = self.into();
        buf.join(path)
    }
    pub fn ok_to_clean() -> Vec<AppDir> {
        Self::variants()
            .iter()
            .filter(|x| !matches!(x, Self::Config))
            .cloned()
            .collect()
    }
    pub fn variants() -> &'static [AppDir] {
        AppDir::value_variants()
    }
}

#[async_trait::async_trait]
pub trait Existy {
    async fn ensure_dir_exists(&self) -> eyre::Result<()>;
    async fn ensure_parent_dir_exists(&self) -> eyre::Result<()>;
    async fn exists_async(&self) -> eyre::Result<bool>;
}
#[async_trait::async_trait]
impl<T: AsRef<Path>> Existy for T
where
    T: Sync,
{
    async fn ensure_dir_exists(&self) -> eyre::Result<()> {
        let path = self.as_ref();
        debug("Ensuring path exist: {path:?}");
        match try_exists(&path).await {
            Ok(true) => {
                if !path.is_dir() {
                    bail!("Path {} exists but isn't a dir!", path.display());
                }
                Ok(())
            }
            Ok(false) => {
                debug!("Creating {}", path.display());
                create_dir_all(&path)
                    .await
                    .wrap_err(format!("Ensuring dir exists: {}", path.display()))?;
                Ok(())
            }
            Err(e) => {
                bail!(
                    "Error encountered checking if {} exists: {:?}",
                    path.display(),
                    e
                )
            }
        }
    }
    async fn ensure_parent_dir_exists(&self) -> eyre::Result<()> {
        if let Some(parent) = self.as_ref().parent() {
            parent.ensure_dir_exists().await?;
            Ok(())
        } else {
            bail!("Could not acquire parent for {}", self.as_ref().display());
        }
    }
    async fn exists_async(&self) -> eyre::Result<bool> {
        tokio::fs::try_exists(&self).await.wrap_err(format!(
            "Checking if path exists: {:?}",
            self.as_ref() as &Path
        ))
    }
}
#[async_trait::async_trait]
impl Existy for AppDir {
    async fn ensure_dir_exists(&self) -> eyre::Result<()> {
        self.as_path_buf().ensure_dir_exists().await
    }
    async fn ensure_parent_dir_exists(&self) -> eyre::Result<()> {
        self.as_path_buf().ensure_parent_dir_exists().await
    }
    async fn exists_async(&self) -> eyre::Result<bool> {
        self.as_path_buf().exists_async().await
    }
}

impl From<AppDir> for PathBuf {
    fn from(dir: AppDir) -> Self {
        dir.as_path_buf()
    }
}