rlvgl_core/plugins/
fatfs.rs

1//! Utilities for working with FAT filesystem images.
2use alloc::string::{String, ToString};
3use alloc::vec;
4use alloc::vec::Vec;
5use fatfs::{FileSystem, FsOptions};
6use fscommon::BufStream;
7use std::io::{Read, Seek, SeekFrom, Write};
8
9#[cfg(test)]
10use fatfs::FormatVolumeOptions;
11#[cfg(test)]
12use std::io::Cursor;
13
14/// List files in the directory `dir` of a FAT image.
15///
16/// The image must be formatted before calling this function.
17/// Passing `"/"` or an empty string will list the root directory.
18pub fn list_dir<T>(mut image: T, dir: &str) -> std::io::Result<Vec<String>>
19where
20    T: Read + Write + Seek,
21{
22    image.seek(SeekFrom::Start(0))?;
23    let buf_stream = BufStream::new(image);
24    let fs = FileSystem::new(buf_stream, FsOptions::new())?;
25    let root = fs.root_dir();
26    let mut names = Vec::new();
27    if dir.is_empty() || dir == "/" {
28        for r in root.iter() {
29            let entry = r?;
30            names.push(entry.file_name().to_string());
31        }
32    } else {
33        let subdir = root.open_dir(dir)?;
34        for r in subdir.iter() {
35            let entry = r?;
36            names.push(entry.file_name().to_string());
37        }
38    }
39    Ok(names)
40}
41
42/// Determine whether a file exists at `path` within the image.
43pub fn file_exists<T>(mut image: T, path: &str) -> std::io::Result<bool>
44where
45    T: Read + Write + Seek,
46{
47    image.seek(SeekFrom::Start(0))?;
48    let buf_stream = BufStream::new(image);
49    let fs = FileSystem::new(buf_stream, FsOptions::new())?;
50    Ok(fs.root_dir().open_file(path).is_ok())
51}
52
53/// Read the entire contents of the file at `path` within the image.
54pub fn read_file<T>(mut image: T, path: &str) -> std::io::Result<Vec<u8>>
55where
56    T: Read + Write + Seek,
57{
58    image.seek(SeekFrom::Start(0))?;
59    let buf_stream = BufStream::new(image);
60    let fs = FileSystem::new(buf_stream, FsOptions::new())?;
61    let mut file = fs.root_dir().open_file(path)?;
62    let mut data = Vec::new();
63    file.read_to_end(&mut data)?;
64    Ok(data)
65}
66
67/// Read a slice of the file at `path` starting at byte `offset`.
68///
69/// Returns up to `len` bytes. If the file is smaller than `offset + len`,
70/// the returned buffer will contain all available bytes from `offset` to end.
71pub fn read_file_range<T>(
72    mut image: T,
73    path: &str,
74    offset: u64,
75    len: usize,
76) -> std::io::Result<Vec<u8>>
77where
78    T: Read + Write + Seek,
79{
80    image.seek(SeekFrom::Start(0))?;
81    let buf_stream = BufStream::new(image);
82    let fs = FileSystem::new(buf_stream, FsOptions::new())?;
83    let mut file = fs.root_dir().open_file(path)?;
84    file.seek(SeekFrom::Start(offset))?;
85    let mut buf = vec![0u8; len];
86    let n = file.read(&mut buf)?;
87    buf.truncate(n);
88    Ok(buf)
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use std::io::{SeekFrom, Write};
95
96    #[test]
97    fn basic_file_ops() {
98        let mut img = Cursor::new(vec![0u8; 1024 * 512]);
99        fatfs::format_volume(&mut img, FormatVolumeOptions::new()).unwrap();
100        img.seek(SeekFrom::Start(0)).unwrap();
101        {
102            let buf_stream = BufStream::new(&mut img);
103            let fs = FileSystem::new(buf_stream, FsOptions::new()).unwrap();
104            fs.root_dir().create_dir("testdir").unwrap();
105            fs.root_dir()
106                .create_file("foo.txt")
107                .unwrap()
108                .write_all(b"hello")
109                .unwrap();
110        }
111        img.seek(SeekFrom::Start(0)).unwrap();
112        let names = list_dir(&mut img, "/").unwrap();
113        assert!(names.contains(&"testdir".to_string()));
114        assert!(names.contains(&"foo.txt".to_string()));
115
116        img.seek(SeekFrom::Start(0)).unwrap();
117        assert!(file_exists(&mut img, "foo.txt").unwrap());
118        img.seek(SeekFrom::Start(0)).unwrap();
119        assert!(!file_exists(&mut img, "missing.txt").unwrap());
120
121        img.seek(SeekFrom::Start(0)).unwrap();
122        let data = read_file(&mut img, "foo.txt").unwrap();
123        assert_eq!(data, b"hello");
124    }
125
126    #[test]
127    fn partial_read_seek() {
128        let mut img = Cursor::new(vec![0u8; 1024 * 512]);
129        fatfs::format_volume(&mut img, FormatVolumeOptions::new()).unwrap();
130        img.seek(SeekFrom::Start(0)).unwrap();
131        {
132            let buf_stream = BufStream::new(&mut img);
133            let fs = FileSystem::new(buf_stream, FsOptions::new()).unwrap();
134            let mut file = fs.root_dir().create_file("foo.bin").unwrap();
135            let data: Vec<u8> = (0u8..=255).collect();
136            file.write_all(&data).unwrap();
137        }
138
139        img.seek(SeekFrom::Start(0)).unwrap();
140        let slice = read_file_range(&mut img, "foo.bin", 10, 5).unwrap();
141        assert_eq!(slice, vec![10, 11, 12, 13, 14]);
142
143        img.seek(SeekFrom::Start(0)).unwrap();
144        let slice = read_file_range(&mut img, "foo.bin", 250, 10).unwrap();
145        assert_eq!(slice, vec![250, 251, 252, 253, 254, 255]);
146    }
147}