Mountain/Environment/FileSystemProvider/
read_operations.rs

1//! # FileSystemProvider - Read Operations
2//!
3//! Implementation of
4//! [`FileSystemReader`](CommonLibrary::FileSystem::FileSystemReader) for
5//! [`MountainEnvironment`]
6//!
7//! Provides secure, validated filesystem read access with workspace trust
8//! enforcement.
9
10use std::path::PathBuf;
11
12use CommonLibrary::{
13	Error::CommonError::CommonError,
14	FileSystem::DTO::{FileSystemStatDTO::FileSystemStatDTO, FileTypeDTO::FileTypeDTO},
15};
16use tokio::fs;
17
18use super::super::{MountainEnvironment::MountainEnvironment, Utility};
19
20/// Read operations implementation for MountainEnvironment
21pub(super) async fn read_file_impl(env:&MountainEnvironment, path:&PathBuf) -> Result<Vec<u8>, CommonError> {
22	Utility::IsPathAllowedForAccess(&env.ApplicationState, path)?;
23
24	// Validate that the path exists and is a file, not a directory
25	let metadata = fs::metadata(path)
26		.await
27		.map_err(|error| CommonError::FromStandardIOError(error, path.clone(), "ReadFile.Stat"))?;
28
29	if metadata.is_dir() {
30		return Err(CommonError::InvalidArgument {
31			ArgumentName:"Path".to_string(),
32			Reason:format!("Cannot read directory as file: {}", path.display()),
33		});
34	}
35
36	fs::read(path)
37		.await
38		.map_err(|error| CommonError::FromStandardIOError(error, path.clone(), "ReadFile"))
39}
40
41/// Stat operations implementation for MountainEnvironment
42pub(super) async fn stat_file_impl(env:&MountainEnvironment, path:&PathBuf) -> Result<FileSystemStatDTO, CommonError> {
43	Utility::IsPathAllowedForAccess(&env.ApplicationState, path)?;
44
45	let metadata = fs::metadata(path)
46		.await
47		.map_err(|error| CommonError::FromStandardIOError(error, path.clone(), "StatFile"))?;
48
49	let mut file_type = 0_u8;
50
51	if metadata.is_file() {
52		file_type |= FileTypeDTO::File as u8;
53	}
54
55	if metadata.is_dir() {
56		file_type |= FileTypeDTO::Directory as u8;
57	}
58
59	// Check for symbolic link separately using symlink_metadata()
60	let file_type_raw = fs::symlink_metadata(path)
61		.await
62		.map_err(|error| CommonError::FromStandardIOError(error, path.clone(), "StatFile.FileType"))?;
63
64	if file_type_raw.is_symlink() {
65		file_type |= FileTypeDTO::SymbolicLink as u8;
66	}
67
68	// Note: Windows typically doesn't support creation_time, handle gracefully
69	let get_milli_timestamp = |system_time_result:Result<std::time::SystemTime, _>| -> u64 {
70		system_time_result
71			.ok()
72			.and_then(|time| time.duration_since(std::time::SystemTime::UNIX_EPOCH).ok())
73			.map_or(0, |duration| duration.as_millis() as u64)
74	};
75
76	Ok(FileSystemStatDTO {
77		FileType:file_type,
78
79		CreationTime:get_milli_timestamp(metadata.created()),
80
81		ModificationTime:get_milli_timestamp(metadata.modified()),
82
83		Size:metadata.len(),
84
85		// Capture file permissions by extracting Unix file mode (st_mode) and Windows
86		// file attributes. On Unix, extract permission bits (rwx for owner/group/others)
87		// and store in FileSystemPermissionsDTO. On Windows, capture attributes
88		// (readonly, hidden, system, archive). This enables preserving permissions
89		// during file operations and respecting the user's filesystem ACLs. Currently
90		// returns None, which defaults to inherited permissions.
91		Permissions:None,
92	})
93}
94
95/// ReadDirectory operations implementation for MountainEnvironment
96pub(super) async fn read_directory_impl(
97	env:&MountainEnvironment,
98	path:&PathBuf,
99) -> Result<Vec<(String, FileTypeDTO)>, CommonError> {
100	Utility::IsPathAllowedForAccess(&env.ApplicationState, path)?;
101
102	// Validate that the path exists and is a directory
103	let metadata = fs::metadata(path)
104		.await
105		.map_err(|error| CommonError::FromStandardIOError(error, path.clone(), "ReadDirectory.Stat"))?;
106
107	if !metadata.is_dir() {
108		return Err(CommonError::InvalidArgument {
109			ArgumentName:"Path".to_string(),
110			Reason:format!("Cannot read directory: path is not a directory: {}", path.display()),
111		});
112	}
113
114	let mut entries = Vec::new();
115
116	let mut read_dir = fs::read_dir(path)
117		.await
118		.map_err(|error| CommonError::FromStandardIOError(error, path.clone(), "ReadDirectory"))?;
119
120	while let Some(entry_result) = read_dir
121		.next_entry()
122		.await
123		.map_err(|error| CommonError::FromStandardIOError(error, path.clone(), "ReadDirectory.NextEntry"))?
124	{
125		let file_name = entry_result.file_name().to_string_lossy().into_owned();
126
127		// Determine file type including symbolic link detection
128		let file_type = match entry_result.file_type().await {
129			Ok(ft) => {
130				if ft.is_symlink() {
131					FileTypeDTO::SymbolicLink
132				} else if ft.is_dir() {
133					FileTypeDTO::Directory
134				} else if ft.is_file() {
135					FileTypeDTO::File
136				} else {
137					FileTypeDTO::Unknown
138				}
139			},
140
141			Err(_) => FileTypeDTO::Unknown,
142		};
143
144		entries.push((file_name, file_type));
145	}
146
147	Ok(entries)
148}