Mountain/ApplicationState/
Internal.rs

1// File: Mountain/Source/ApplicationState/Internal.rs
2// Role: Contains internal helper functions for the `ApplicationState` module.
3// Responsibilities:
4//   - Handle tasks like file I/O, path resolution, and serialization.
5//   - Provide helper logic for populating state, like scanning for extensions.
6//   - These are not part of the public API of the state struct itself.
7
8//! # Internal (ApplicationState)
9//!
10//! Contains internal helper functions for the `ApplicationState` module,
11//! handling tasks like file I/O, path resolution, and serialization that are
12//! not part of the public API of the state itself.
13
14#![allow(non_snake_case, non_camel_case_types)]
15
16use std::{collections::HashMap, fs, path::Path};
17
18use Common::Error::CommonError::CommonError;
19use log::{error, info, trace};
20use serde::{self, Deserializer, Serializer};
21use serde_json::Value;
22use url::Url;
23
24use crate::{
25	ApplicationState::{
26		ApplicationState::MapLockError,
27		DTO::ExtensionDescriptionStateDTO::ExtensionDescriptionStateDTO,
28	},
29	ExtensionManagement,
30};
31
32/// Analyzes text content to determine its line endings and splits it into a
33/// vector of lines.
34pub fn AnalyzeTextLinesAndEOL(TextContent:&str) -> (Vec<String>, String) {
35	let DetectedEOL = if TextContent.contains("\r\n") { "\r\n" } else { "\n" };
36
37	(
38		TextContent.split(DetectedEOL).map(String::from).collect(),
39		DetectedEOL.to_string(),
40	)
41}
42
43/// Synchronously loads Memento storage data from a JSON file.
44/// Used during the initial `default()` setup of `ApplicationState`.
45pub fn LoadInitialMementoFromDisk(StorageFilePath:&Path) -> HashMap<String, Value> {
46	if !StorageFilePath.exists() {
47		return HashMap::new();
48	}
49
50	match fs::read_to_string(StorageFilePath) {
51		Ok(Content) => {
52			serde_json::from_str(&Content).unwrap_or_else(|Error| {
53				error!(
54					"[AppStateInternal] Failed to parse JSON from '{}': {}. Returning empty map.",
55					StorageFilePath.display(),
56					Error
57				);
58
59				HashMap::new()
60			})
61		},
62
63		Err(Error) => {
64			error!(
65				"[AppStateInternal] Failed to read '{}': {}. Returning empty map.",
66				StorageFilePath.display(),
67				Error
68			);
69
70			HashMap::new()
71		},
72	}
73}
74
75/// Resolves the absolute path for a Memento storage file based on scope.
76pub fn ResolveMementoStorageFilePath(
77	ApplicationDataDirectory:&Path,
78
79	IsGlobalScope:bool,
80
81	WorkSpaceIdentifier:&str,
82) -> std::path::PathBuf {
83	let UserStorageBasePath = ApplicationDataDirectory.join("User");
84
85	if IsGlobalScope {
86		UserStorageBasePath.join("globalStorage.json")
87	} else {
88		// Sanitize the workspace identifier to be a safe directory name.
89		let Segment = WorkSpaceIdentifier.replace(|c:char| !c.is_alphanumeric() && c != '-' && c != '_', "_");
90
91		UserStorageBasePath.join("workspaceStorage").join(Segment).join("storage.json")
92	}
93}
94
95/// Scans all registered extension paths for valid extensions and populates the
96/// state.
97pub async fn ScanAndPopulateExtensions(
98	ApplicationHandle:tauri::AppHandle,
99
100	State:&crate::ApplicationState::ApplicationState::ApplicationState,
101) -> Result<(), CommonError> {
102	info!("[AppStateInternal] Starting extension scan...");
103
104	let mut AllFoundExtensions:HashMap<String, ExtensionDescriptionStateDTO> = HashMap::new();
105
106	let ScanPaths = State.ExtensionScanPaths.lock().map_err(MapLockError)?.clone();
107
108	trace!("[AppStateInternal] Scanning paths: {:?}", ScanPaths);
109
110	for Path in ScanPaths {
111		let FoundInPath =
112			ExtensionManagement::Scanner::ScanDirectoryForExtensions(ApplicationHandle.clone(), Path).await?;
113
114		for Extension in FoundInPath {
115			let Identifier = Extension
116				.Identifier
117				.get("value")
118				.and_then(Value::as_str)
119				.unwrap_or_default()
120				.to_string();
121
122			if !Identifier.is_empty() {
123				AllFoundExtensions.insert(Identifier, Extension);
124			}
125		}
126	}
127
128	let mut ScannedExtensionsGuard = State.ScannedExtensions.lock().map_err(MapLockError)?;
129
130	*ScannedExtensionsGuard = AllFoundExtensions;
131
132	info!(
133		"[AppStateInternal] Extension scan complete. Found {} extensions.",
134		ScannedExtensionsGuard.len()
135	);
136
137	Ok(())
138}
139
140/// A helper module for serializing and deserializing `url::Url` with `serde`.
141/// This is used in DTOs where a `Url` field needs to be serialized to a string.
142pub mod URLSerializationHelper {
143
144	use serde::Deserialize;
145
146	use super::*;
147
148	pub fn serialize<S>(URLInstance:&Url, SerializerInstance:S) -> Result<S::Ok, S::Error>
149	where
150		S: Serializer, {
151		SerializerInstance.serialize_str(URLInstance.as_str())
152	}
153
154	pub fn deserialize<'de, D>(DeserializerInstance:D) -> Result<Url, D::Error>
155	where
156		D: Deserializer<'de>, {
157		let StringValue = String::deserialize(DeserializerInstance)?;
158
159		Url::parse(&StringValue).map_err(serde::de::Error::custom)
160	}
161}