mesh_llm_analytics/
install_id.rs1use anyhow::{Context, Result};
11use std::fs;
12use std::path::{Path, PathBuf};
13use uuid::Uuid;
14
15pub const INSTALL_ID_FILE: &str = "analytics-id";
17
18#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct InstallId {
21 id: String,
22 first_run: bool,
23}
24
25impl InstallId {
26 #[must_use]
27 pub fn as_str(&self) -> &str {
28 &self.id
29 }
30
31 #[must_use]
33 pub const fn is_first_run(&self) -> bool {
34 self.first_run
35 }
36}
37
38pub fn state_dir() -> Result<PathBuf> {
40 let home = dirs::home_dir().context("cannot determine home directory")?;
41 Ok(home.join(".mesh-llm"))
42}
43
44pub fn load(dir: &Path) -> Option<InstallId> {
50 let raw = fs::read_to_string(dir.join(INSTALL_ID_FILE)).ok()?;
51 let trimmed = raw.trim();
52 Uuid::parse_str(trimmed).ok()?;
53 Some(InstallId {
54 id: trimmed.to_owned(),
55 first_run: false,
56 })
57}
58
59pub fn load_or_create(dir: &Path) -> Result<InstallId> {
64 let path = dir.join(INSTALL_ID_FILE);
65
66 if let Ok(existing) = fs::read_to_string(&path) {
67 let trimmed = existing.trim();
68 if Uuid::parse_str(trimmed).is_ok() {
69 return Ok(InstallId {
70 id: trimmed.to_owned(),
71 first_run: false,
72 });
73 }
74 }
75
76 fs::create_dir_all(dir)
77 .with_context(|| format!("failed to create state directory {}", dir.display()))?;
78 let id = Uuid::new_v4().to_string();
79 fs::write(&path, format!("{id}\n"))
80 .with_context(|| format!("failed to write install identifier {}", path.display()))?;
81 restrict_permissions(&path);
82
83 Ok(InstallId {
84 id,
85 first_run: true,
86 })
87}
88
89#[cfg(unix)]
91fn restrict_permissions(path: &Path) {
92 use std::os::unix::fs::PermissionsExt;
93 let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o600));
94}
95
96#[cfg(not(unix))]
97fn restrict_permissions(_path: &Path) {}
98
99#[cfg(test)]
100#[path = "install_id/tests.rs"]
101mod tests;