extendable_assets/filesystem/native.rs
1use std::path::{Path, PathBuf};
2
3use crate::filesystem::{Filesystem, FilesystemError};
4
5use async_trait::async_trait;
6
7/// A filesystem implementation that reads from the native OS filesystem.
8///
9/// This implementation provides async access to files on the local filesystem,
10/// with all asset paths resolved relative to a configured root directory.
11/// Currently uses blocking I/O operations wrapped in async functions.
12pub struct NativeFilesystem {
13 /// Root directory where all asset paths are resolved relative to
14 root_dir: PathBuf,
15}
16impl NativeFilesystem {
17 /// Creates a new native filesystem with the specified root directory.
18 ///
19 /// # Arguments
20 ///
21 /// * `root_dir` - The root directory for resolving asset paths
22 pub fn new<P: AsRef<Path>>(root_dir: P) -> Self {
23 Self {
24 root_dir: PathBuf::from(root_dir.as_ref()),
25 }
26 }
27
28 /// Returns a reference to the root directory path.
29 ///
30 /// This is the directory that all asset paths are resolved relative to.
31 pub fn root_dir(&self) -> &Path {
32 &self.root_dir
33 }
34}
35#[async_trait]
36impl Filesystem for NativeFilesystem {
37 async fn read_bytes(&self, asset_path: &str) -> Result<Vec<u8>, FilesystemError> {
38 // Resolve the asset path relative to our root directory
39 let path = self.root_dir.join(asset_path);
40
41 // Check if the file exists before attempting to read
42 if !path.is_file() {
43 return Err(FilesystemError::NotFound(asset_path.to_string()));
44 }
45
46 // Read the entire file into memory asynchronously
47 // Note: Currently using blocking I/O - could be improved with tokio::fs for true async I/O
48 let bytes = std::fs::read(path).map_err(FilesystemError::from)?;
49 Ok(bytes)
50 }
51}
52
53#[cfg(test)]
54mod test {
55 use super::*;
56 use std::sync::Arc;
57
58 /// Tests that the native filesystem can successfully read a test file asynchronously.
59 ///
60 /// Uses pollster to block on the async operation for testing purposes.
61 #[test]
62 fn read_bytes() {
63 let tests_dir = Path::new(&env!("CARGO_MANIFEST_DIR")).join("tests");
64
65 // Create a filesystem instance rooted at the "tests" directory
66 let fs: Arc<dyn Filesystem> = Arc::new(NativeFilesystem::new(tests_dir));
67
68 // Read a test file asynchronously and verify its contents
69 // Using pollster::block_on to wait for the async operation in a sync test
70 let greeting = pollster::block_on(fs.read_bytes("test_data_0/hello.txt")).unwrap();
71 assert_eq!(greeting, b"Hello world\n");
72 }
73}