Skip to main content

Mountain/IPC/DevLog/
AppDataPrefix.rs

1#![allow(non_snake_case)]
2
3//! Resolve the Tauri app-data prefix for THIS profile so logs
4//! and aliasing pick the right `~/Library/Application Support/
5//! land.editor.*.mountain` directory. The detection walks the
6//! Application Support tree, prefers a strict suffix match
7//! against the binary signature, falls back to the first
8//! `*.mountain` candidate so a mismatch still produces a
9//! usable path.
10
11use std::sync::OnceLock;
12
13static APP_DATA_PREFIX:OnceLock<Option<String>> = OnceLock::new();
14
15pub fn Fn() -> &'static Option<String> { APP_DATA_PREFIX.get_or_init(DetectAppDataPrefix) }
16
17fn BinarySignature() -> String {
18	let PackageName = env!("CARGO_PKG_NAME");
19	let Segments:Vec<&str> = PackageName.split('_').collect();
20	let Take = Segments.len().min(4);
21	let Start = Segments.len().saturating_sub(Take);
22	Segments[Start..]
23		.iter()
24		.flat_map(|Segment| SplitPascalCaseIntoWords(Segment))
25		.collect::<Vec<String>>()
26		.join(".")
27		.to_ascii_lowercase()
28}
29
30fn SplitPascalCaseIntoWords(Segment:&str) -> Vec<String> {
31	let mut Words:Vec<String> = Vec::new();
32	let mut Current = String::new();
33	let mut PrevWasUpper = false;
34	let mut PrevWasDigit = false;
35	for Ch in Segment.chars() {
36		let IsUpper = Ch.is_ascii_uppercase();
37		let IsDigit = Ch.is_ascii_digit();
38		let NeedBreak =
39			!Current.is_empty() && ((IsUpper && !PrevWasUpper) || (IsDigit != PrevWasDigit && !Current.is_empty()));
40		if NeedBreak {
41			Words.push(std::mem::take(&mut Current));
42		}
43		Current.push(Ch);
44		PrevWasUpper = IsUpper;
45		PrevWasDigit = IsDigit;
46	}
47	if !Current.is_empty() {
48		Words.push(Current);
49	}
50	Words.into_iter().filter(|Word| !Word.is_empty()).collect()
51}
52
53fn DetectAppDataPrefix() -> Option<String> {
54	let Home = std::env::var("HOME").ok()?;
55	let Base = format!("{}/Library/Application Support", Home);
56	let Signature = BinarySignature();
57
58	let mut FirstMatchingMountain:Option<String> = None;
59	if let Ok(Entries) = std::fs::read_dir(&Base) {
60		for Entry in Entries.flatten() {
61			let Name = Entry.file_name();
62			let Name = Name.to_string_lossy().into_owned();
63			if !Name.starts_with("land.editor.") || !Name.contains("mountain") {
64				continue;
65			}
66			if Name.ends_with(&Signature) {
67				return Some(format!("{}/{}", Base, Name));
68			}
69			if FirstMatchingMountain.is_none() {
70				FirstMatchingMountain = Some(format!("{}/{}", Base, Name));
71			}
72		}
73	}
74	FirstMatchingMountain
75}