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
20	let Segments:Vec<&str> = PackageName.split('_').collect();
21
22	let Take = Segments.len().min(4);
23
24	let Start = Segments.len().saturating_sub(Take);
25
26	Segments[Start..]
27		.iter()
28		.flat_map(|Segment| SplitPascalCaseIntoWords(Segment))
29		.collect::<Vec<String>>()
30		.join(".")
31		.to_ascii_lowercase()
32}
33
34fn SplitPascalCaseIntoWords(Segment:&str) -> Vec<String> {
35	let mut Words:Vec<String> = Vec::new();
36
37	let mut Current = String::new();
38
39	let mut PrevWasUpper = false;
40
41	let mut PrevWasDigit = false;
42
43	for Ch in Segment.chars() {
44		let IsUpper = Ch.is_ascii_uppercase();
45
46		let IsDigit = Ch.is_ascii_digit();
47
48		let NeedBreak =
49			!Current.is_empty() && ((IsUpper && !PrevWasUpper) || (IsDigit != PrevWasDigit && !Current.is_empty()));
50
51		if NeedBreak {
52			Words.push(std::mem::take(&mut Current));
53		}
54
55		Current.push(Ch);
56
57		PrevWasUpper = IsUpper;
58
59		PrevWasDigit = IsDigit;
60	}
61
62	if !Current.is_empty() {
63		Words.push(Current);
64	}
65
66	Words.into_iter().filter(|Word| !Word.is_empty()).collect()
67}
68
69fn DetectAppDataPrefix() -> Option<String> {
70	let Home = std::env::var("HOME").ok()?;
71
72	let Base = format!("{}/Library/Application Support", Home);
73
74	let Signature = BinarySignature();
75
76	let mut FirstMatchingMountain:Option<String> = None;
77
78	if let Ok(Entries) = std::fs::read_dir(&Base) {
79		for Entry in Entries.flatten() {
80			let Name = Entry.file_name();
81
82			let Name = Name.to_string_lossy().into_owned();
83
84			if !Name.starts_with("land.editor.") || !Name.contains("mountain") {
85				continue;
86			}
87
88			if Name.ends_with(&Signature) {
89				return Some(format!("{}/{}", Base, Name));
90			}
91
92			if FirstMatchingMountain.is_none() {
93				FirstMatchingMountain = Some(format!("{}/{}", Base, Name));
94			}
95		}
96	}
97
98	FirstMatchingMountain
99}