modde-cli 0.2.1

CLI interface for modde
use std::{
    fs, io,
    path::{Path, PathBuf},
};

use anyhow::Context;
use uuid::Uuid;

const INSTALL_ID_FILE: &str = "install-id";

pub fn persistent_install_id() -> anyhow::Result<Uuid> {
    persistent_install_id_in(data_dir()?)
}

fn data_dir() -> anyhow::Result<PathBuf> {
    dirs::data_local_dir()
        .map(|dir| dir.join("rs-modde"))
        .context("could not determine local data directory for telemetry install id")
}

fn persistent_install_id_in(dir: PathBuf) -> anyhow::Result<Uuid> {
    let path = dir.join(INSTALL_ID_FILE);
    if let Some(id) = read_install_id(&path)? {
        Ok(id)
    } else {
        fs::create_dir_all(&dir)
            .with_context(|| format!("failed to create telemetry data dir {}", dir.display()))?;
        let id = Uuid::new_v4();
        fs::write(&path, format!("{id}\n"))
            .with_context(|| format!("failed to write telemetry install id {}", path.display()))?;
        Ok(id)
    }
}

fn read_install_id(path: &Path) -> anyhow::Result<Option<Uuid>> {
    match fs::read_to_string(path) {
        Ok(value) => Uuid::parse_str(value.trim())
            .map(Some)
            .with_context(|| format!("invalid telemetry install id in {}", path.display())),
        Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
        Err(error) => Err(error)
            .with_context(|| format!("failed to read telemetry install id {}", path.display())),
    }
}

pub fn telemetry_dir() -> anyhow::Result<PathBuf> {
    data_dir().map(|dir| dir.join("telemetry"))
}

#[cfg(test)]
mod tests {
    use super::persistent_install_id_in;

    #[test]
    fn persistent_install_id_round_trips() {
        let dir = tempfile::tempdir().expect("temp dir");

        let first = persistent_install_id_in(dir.path().to_path_buf()).expect("first id");
        let second = persistent_install_id_in(dir.path().to_path_buf()).expect("second id");

        assert_eq!(first, second);
    }
}