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}