embedded_sdmmc/filesystem/
files.rs

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