Skip to main content

Mountain/Track/Effect/CreateEffectForRequest/
FileSystem.rs

1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3use std::{future::Future, pin::Pin, sync::Arc};
4
5use base64::{Engine as _, engine::general_purpose::STANDARD};
6use CommonLibrary::{
7	Environment::Requires::Requires,
8	FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
9};
10use serde_json::{Value, json};
11use tauri::Runtime;
12
13use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, Track::Effect::MappedEffectType::MappedEffect};
14
15/// Strip a leading `file://` (or `file:///`) scheme from the incoming path.
16/// Cocoon sends full URIs like `file:///<home>/.land/extensions/...`
17/// through `FileSystem.ReadFile`/`WriteFile`/`ReadDirectory`; `PathBuf` from
18/// such a string treats the scheme literally and every read 404s. Without
19/// this the redhat.java activation (and any other extension that uses the
20/// gRPC fs.readFile path for its own package.json) fails with "Resource not
21/// found: file:///...".
22fn StripFileUriScheme(Input:&str) -> &str {
23	if let Some(Rest) = Input.strip_prefix("file://") {
24		// `file:///Users/...` - the third slash is part of the path, keep it.
25		if Rest.starts_with('/') {
26			return Rest;
27		}
28		// `file://localhost/Users/...` - rarely used, but normalise by
29		// stripping host-up-to-first-slash. Fall through on failure.
30		if let Some(Idx) = Rest.find('/') {
31			return &Rest[Idx..];
32		}
33	}
34	Input
35}
36
37pub fn CreateEffect<R:Runtime>(MethodName:&str, Parameters:Value) -> Option<Result<MappedEffect, String>> {
38	match MethodName {
39		"FileSystem.ReadFile" => {
40			let effect =
41				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
42					Box::pin(async move {
43						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
44						// Empty-path guard: extensions occasionally
45						// pass `""` to `vscode.workspace.fs.readFile`
46						// when probing optional config files. Stock VS
47						// Code's FileSystemProvider would return
48						// `FileNotFound`; replicating that contract
49						// here avoids a panic in `PathBuf::from("")`-
50						// rooted FS calls (which can confuse Mountain's
51						// path-security guard into emitting a "path
52						// outside workspace" rejection that trips the
53						// breaker cascade).
54						if path_str.is_empty() {
55							return Err("FileSystem.ReadFile: empty path (resource not found)".to_string());
56						}
57						if path_str.starts_with("vscode://schemas-associations/") {
58							let payload = serde_json::to_vec(&json!({ "schemas": [] }))
59								.unwrap_or_else(|_| b"{\"schemas\":[]}".to_vec());
60							return Ok(json!(payload));
61						}
62						let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
63						let path = std::path::PathBuf::from(StripFileUriScheme(path_str));
64						fs_reader
65							.ReadFile(&path)
66							.await
67							.map(|bytes| json!(bytes))
68							.map_err(|e| e.to_string())
69					})
70				};
71			Some(Ok(Box::new(effect)))
72		},
73
74		"FileSystem.WriteFile" => {
75			let effect =
76				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
77					Box::pin(async move {
78						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
79						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
80						let path = std::path::PathBuf::from(StripFileUriScheme(path_str));
81						let content = Parameters.get(1).cloned();
82						let content_bytes = match content {
83							Some(Value::Array(arr)) => {
84								arr.into_iter().filter_map(|v| v.as_u64().map(|n| n as u8)).collect()
85							},
86							Some(Value::String(s)) => STANDARD.decode(&s).unwrap_or_default(),
87							_ => vec![],
88						};
89						fs_writer
90							.WriteFile(&path, content_bytes, true, true)
91							.await
92							.map(|_| json!(null))
93							.map_err(|e| e.to_string())
94					})
95				};
96			Some(Ok(Box::new(effect)))
97		},
98
99		"FileSystem.ReadDirectory" => {
100			let effect =
101				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
102					Box::pin(async move {
103						let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
104						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
105						let path = std::path::PathBuf::from(StripFileUriScheme(path_str));
106						fs_reader
107							.ReadDirectory(&path)
108							.await
109							.map(|entries| json!(entries))
110							.map_err(|e| e.to_string())
111					})
112				};
113			Some(Ok(Box::new(effect)))
114		},
115
116		"FileSystem.Stat" => {
117			let effect =
118				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
119					Box::pin(async move {
120						let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
121						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
122						// Empty-path guard: same rationale as
123						// `FileSystem.ReadFile` above. Returning
124						// `not found` matches VS Code's
125						// `FileSystemProvider.stat()` contract for
126						// probes of paths the extension hasn't
127						// validated upstream.
128						if path_str.is_empty() {
129							return Err("FileSystem.Stat: empty path (resource not found)".to_string());
130						}
131						let path = std::path::PathBuf::from(StripFileUriScheme(path_str));
132						fs_reader
133							.StatFile(&path)
134							.await
135							.map(|stat| json!(stat))
136							.map_err(|e| e.to_string())
137					})
138				};
139			Some(Ok(Box::new(effect)))
140		},
141
142		"FileSystem.CreateDirectory" => {
143			let effect =
144				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
145					Box::pin(async move {
146						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
147						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
148						let path = std::path::PathBuf::from(StripFileUriScheme(path_str));
149						fs_writer
150							.CreateDirectory(&path, true)
151							.await
152							.map(|_| json!(null))
153							.map_err(|e| e.to_string())
154					})
155				};
156			Some(Ok(Box::new(effect)))
157		},
158
159		"FileSystem.Delete" => {
160			let effect =
161				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
162					Box::pin(async move {
163						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
164						let path_str = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
165						let path = std::path::PathBuf::from(StripFileUriScheme(path_str));
166						let recursive = Parameters.get(1).and_then(Value::as_bool).unwrap_or(false);
167						fs_writer
168							.Delete(&path, recursive, false)
169							.await
170							.map(|_| json!(null))
171							.map_err(|e| e.to_string())
172					})
173				};
174			Some(Ok(Box::new(effect)))
175		},
176
177		"FileSystem.Rename" => {
178			let effect =
179				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
180					Box::pin(async move {
181						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
182						let source = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
183						let target = Parameters.get(1).and_then(Value::as_str).unwrap_or("");
184						fs_writer
185							.Rename(
186								&std::path::PathBuf::from(StripFileUriScheme(source)),
187								&std::path::PathBuf::from(StripFileUriScheme(target)),
188								true,
189							)
190							.await
191							.map(|_| json!(null))
192							.map_err(|e| e.to_string())
193					})
194				};
195			Some(Ok(Box::new(effect)))
196		},
197
198		"FileSystem.Copy" => {
199			let effect =
200				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
201					Box::pin(async move {
202						let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
203						let source = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
204						let target = Parameters.get(1).and_then(Value::as_str).unwrap_or("");
205						fs_writer
206							.Copy(
207								&std::path::PathBuf::from(StripFileUriScheme(source)),
208								&std::path::PathBuf::from(StripFileUriScheme(target)),
209								true,
210							)
211							.await
212							.map(|_| json!(null))
213							.map_err(|e| e.to_string())
214					})
215				};
216			Some(Ok(Box::new(effect)))
217		},
218
219		_ => None,
220	}
221}