use std::path::Path;
use std::sync::OnceLock;
use uuid::Uuid;
static DEVICE_ID: OnceLock<Uuid> = OnceLock::new();
pub fn stable_device_id() -> Uuid {
*DEVICE_ID.get_or_init(|| {
let Some(home) = dirs::home_dir() else {
return Uuid::now_v7();
};
load_or_create_at(&home.join(".mati").join("device_id"))
})
}
fn load_or_create_at(path: &Path) -> Uuid {
if let Ok(existing) = std::fs::read_to_string(path) {
if let Ok(id) = Uuid::parse_str(existing.trim()) {
return id;
}
let id = Uuid::now_v7();
let _ = std::fs::write(path, id.to_string());
return id;
}
let id = Uuid::now_v7();
if let Some(dir) = path.parent() {
let _ = std::fs::create_dir_all(dir);
}
match std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
{
Ok(mut f) => {
use std::io::Write as _;
let _ = f.write_all(id.to_string().as_bytes());
id
}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => std::fs::read_to_string(path)
.ok()
.and_then(|s| Uuid::parse_str(s.trim()).ok())
.unwrap_or(id),
Err(_) => id,
}
}
#[cfg(test)]
pub(crate) fn load_or_create_for_test(path: &Path) -> Uuid {
load_or_create_at(path)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn creates_then_reloads_same_id() {
let dir = TempDir::new().expect("tempdir");
let path = dir.path().join("device_id");
let first = load_or_create_for_test(&path);
let second = load_or_create_for_test(&path);
assert_eq!(first, second, "id must be stable across loads");
let on_disk = std::fs::read_to_string(&path).expect("file must exist");
assert_eq!(
Uuid::parse_str(on_disk.trim()).expect("valid uuid"),
first,
"persisted id must match the returned id"
);
}
#[test]
fn repairs_corrupt_file() {
let dir = TempDir::new().expect("tempdir");
let path = dir.path().join("device_id");
std::fs::write(&path, "not-a-uuid\n").expect("write");
let repaired = load_or_create_for_test(&path);
let reloaded = load_or_create_for_test(&path);
assert_eq!(repaired, reloaded, "repair must persist a stable id");
}
#[test]
fn creates_parent_dir_when_missing() {
let dir = TempDir::new().expect("tempdir");
let path = dir.path().join("nested").join("device_id");
let id = load_or_create_for_test(&path);
assert_eq!(load_or_create_for_test(&path), id);
}
#[test]
fn stable_device_id_is_memoized() {
assert_eq!(stable_device_id(), stable_device_id());
}
}