embedded_sdmmc/filesystem/
files.rs

1use crate::{
2    filesystem::{ClusterId, DirEntry, SearchId},
3    Error, RawVolume, VolumeManager,
4};
5
6/// A handle for an open file on disk.
7///
8/// Do NOT drop this object! It doesn't hold a reference to the Volume Manager
9/// it was created from and cannot update the directory entry if you drop it.
10/// Additionally, the VolumeManager will think you still have the file open if
11/// you just drop it, and it won't let you open the file again.
12///
13/// Instead you must pass it to [`crate::VolumeManager::close_file`] to close it
14/// cleanly.
15///
16/// If you want your files to close themselves on drop, create your own File
17/// type that wraps this one and also holds a `VolumeManager` reference. You'll
18/// then also need to put your `VolumeManager` in some kind of Mutex or RefCell,
19/// and deal with the fact you can't put them both in the same struct any more
20/// because one refers to the other. Basically, it's complicated and there's a
21/// reason we did it this way.
22#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
23#[derive(Debug, Copy, Clone, PartialEq, Eq)]
24pub struct RawFile(pub(crate) SearchId);
25
26impl RawFile {
27    /// Convert a raw file into a droppable [`File`]
28    pub fn to_file<D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>(
29        self,
30        volume_mgr: &mut VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
31    ) -> File<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
32    where
33        D: crate::BlockDevice,
34        T: crate::TimeSource,
35    {
36        File::new(self, volume_mgr)
37    }
38}
39
40/// A handle for an open file on disk, which closes on drop.
41///
42/// In contrast to a `RawFile`, a `File`  holds a mutable reference to its
43/// parent `VolumeManager`, which restricts which operations you can perform.
44///
45/// If you drop a value of this type, it closes the file automatically, and but
46/// error that may occur will be ignored. To handle potential errors, use
47/// the [`File::close`] method.
48pub struct File<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
49where
50    D: crate::BlockDevice,
51    T: crate::TimeSource,
52{
53    raw_file: RawFile,
54    volume_mgr: &'a mut VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
55}
56
57impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
58    File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
59where
60    D: crate::BlockDevice,
61    T: crate::TimeSource,
62{
63    /// Create a new `File` from a `RawFile`
64    pub fn new(
65        raw_file: RawFile,
66        volume_mgr: &'a mut VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
67    ) -> File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> {
68        File {
69            raw_file,
70            volume_mgr,
71        }
72    }
73
74    /// Read from the file
75    ///
76    /// Returns how many bytes were read, or an error.
77    pub fn read(&mut self, buffer: &mut [u8]) -> Result<usize, crate::Error<D::Error>> {
78        self.volume_mgr.read(self.raw_file, buffer)
79    }
80
81    /// Write to the file
82    pub fn write(&mut self, buffer: &[u8]) -> Result<(), crate::Error<D::Error>> {
83        self.volume_mgr.write(self.raw_file, buffer)
84    }
85
86    /// Check if a file is at End Of File.
87    pub fn is_eof(&self) -> bool {
88        self.volume_mgr
89            .file_eof(self.raw_file)
90            .expect("Corrupt file ID")
91    }
92
93    /// Seek a file with an offset from the current position.
94    pub fn seek_from_current(&mut self, offset: i32) -> Result<(), crate::Error<D::Error>> {
95        self.volume_mgr
96            .file_seek_from_current(self.raw_file, offset)
97    }
98
99    /// Seek a file with an offset from the start of the file.
100    pub fn seek_from_start(&mut self, offset: u32) -> Result<(), crate::Error<D::Error>> {
101        self.volume_mgr.file_seek_from_start(self.raw_file, offset)
102    }
103
104    /// Seek a file with an offset back from the end of the file.
105    pub fn seek_from_end(&mut self, offset: u32) -> Result<(), crate::Error<D::Error>> {
106        self.volume_mgr.file_seek_from_end(self.raw_file, offset)
107    }
108
109    /// Get the length of a file
110    pub fn length(&self) -> u32 {
111        self.volume_mgr
112            .file_length(self.raw_file)
113            .expect("Corrupt file ID")
114    }
115
116    /// Get the current offset of a file
117    pub fn offset(&self) -> u32 {
118        self.volume_mgr
119            .file_offset(self.raw_file)
120            .expect("Corrupt file ID")
121    }
122
123    /// Convert back to a raw file
124    pub fn to_raw_file(self) -> RawFile {
125        let f = self.raw_file;
126        core::mem::forget(self);
127        f
128    }
129
130    /// Flush any written data by updating the directory entry.
131    pub fn flush(&mut self) -> Result<(), Error<D::Error>> {
132        self.volume_mgr.flush_file(self.raw_file)
133    }
134
135    /// Consume the `File` handle and close it. The behavior of this is similar
136    /// to using [`core::mem::drop`] or letting the `File` go out of scope,
137    /// except this lets the user handle any errors that may occur in the process,
138    /// whereas when using drop, any errors will be discarded silently.
139    pub fn close(self) -> Result<(), Error<D::Error>> {
140        let result = self.volume_mgr.close_file(self.raw_file);
141        core::mem::forget(self);
142        result
143    }
144}
145
146impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> Drop
147    for File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
148where
149    D: crate::BlockDevice,
150    T: crate::TimeSource,
151{
152    fn drop(&mut self) {
153        _ = self.volume_mgr.close_file(self.raw_file);
154    }
155}
156
157impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
158    core::fmt::Debug for File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
159where
160    D: crate::BlockDevice,
161    T: crate::TimeSource,
162{
163    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164        write!(f, "File({})", self.raw_file.0 .0)
165    }
166}
167
168#[cfg(feature = "defmt-log")]
169impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
170    defmt::Format for File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
171where
172    D: crate::BlockDevice,
173    T: crate::TimeSource,
174{
175    fn format(&self, fmt: defmt::Formatter) {
176        defmt::write!(fmt, "File({})", self.raw_file.0 .0)
177    }
178}
179
180/// Errors related to file operations
181#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum FileError {
184    /// Tried to use an invalid offset.
185    InvalidOffset,
186}
187
188/// The different ways we can open a file.
189#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
190#[derive(Debug, PartialEq, Eq, Copy, Clone)]
191pub enum Mode {
192    /// Open a file for reading, if it exists.
193    ReadOnly,
194    /// Open a file for appending (writing to the end of the existing file), if it exists.
195    ReadWriteAppend,
196    /// Open a file and remove all contents, before writing to the start of the existing file, if it exists.
197    ReadWriteTruncate,
198    /// Create a new empty file. Fail if it exists.
199    ReadWriteCreate,
200    /// Create a new empty file, or truncate an existing file.
201    ReadWriteCreateOrTruncate,
202    /// Create a new empty file, or append to an existing file.
203    ReadWriteCreateOrAppend,
204}
205
206/// Internal metadata about an open file
207#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
208#[derive(Debug, Clone)]
209pub(crate) struct FileInfo {
210    /// Unique ID for this file
211    pub(crate) file_id: RawFile,
212    /// The unique ID for the volume this directory is on
213    pub(crate) volume_id: RawVolume,
214    /// The last cluster we accessed, and how many bytes that short-cuts us.
215    ///
216    /// This saves us walking from the very start of the FAT chain when we move
217    /// forward through a file.
218    pub(crate) current_cluster: (u32, ClusterId),
219    /// How far through the file we've read (in bytes).
220    pub(crate) current_offset: u32,
221    /// What mode the file was opened in
222    pub(crate) mode: Mode,
223    /// DirEntry of this file
224    pub(crate) entry: DirEntry,
225    /// Did we write to this file?
226    pub(crate) dirty: bool,
227}
228
229impl FileInfo {
230    /// Are we at the end of the file?
231    pub fn eof(&self) -> bool {
232        self.current_offset == self.entry.size
233    }
234
235    /// How long is the file?
236    pub fn length(&self) -> u32 {
237        self.entry.size
238    }
239
240    /// Seek to a new position in the file, relative to the start of the file.
241    pub fn seek_from_start(&mut self, offset: u32) -> Result<(), FileError> {
242        if offset > self.entry.size {
243            return Err(FileError::InvalidOffset);
244        }
245        self.current_offset = offset;
246        Ok(())
247    }
248
249    /// Seek to a new position in the file, relative to the end of the file.
250    pub fn seek_from_end(&mut self, offset: u32) -> Result<(), FileError> {
251        if offset > self.entry.size {
252            return Err(FileError::InvalidOffset);
253        }
254        self.current_offset = self.entry.size - offset;
255        Ok(())
256    }
257
258    /// Seek to a new position in the file, relative to the current position.
259    pub fn seek_from_current(&mut self, offset: i32) -> Result<(), FileError> {
260        let new_offset = i64::from(self.current_offset) + i64::from(offset);
261        if new_offset < 0 || new_offset > i64::from(self.entry.size) {
262            return Err(FileError::InvalidOffset);
263        }
264        self.current_offset = new_offset as u32;
265        Ok(())
266    }
267
268    /// Amount of file left to read.
269    pub fn left(&self) -> u32 {
270        self.entry.size - self.current_offset
271    }
272
273    /// Update the file's length.
274    pub(crate) fn update_length(&mut self, new: u32) {
275        self.entry.size = new;
276    }
277}
278
279// ****************************************************************************
280//
281// End Of File
282//
283// ****************************************************************************