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}