Skip to main content

embedded_sdmmc/filesystem/
directory.rs

1//! Directory related code
2
3use core::ops::ControlFlow;
4
5use crate::blockdevice::BlockIdx;
6use crate::fat::{FatType, OnDiskDirEntry};
7use crate::filesystem::{Attributes, ClusterId, Handle, LfnBuffer, ShortFileName, Timestamp};
8use crate::{Error, RawVolume, VolumeManager};
9
10use super::ToShortFileName;
11
12/// A directory entry, which tells you about other files and directories.
13#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
14#[derive(Debug, PartialEq, Eq, Clone)]
15pub struct DirEntry {
16    /// The name of the file
17    pub name: ShortFileName,
18    /// When the file was last modified
19    pub mtime: Timestamp,
20    /// When the file was first created
21    pub ctime: Timestamp,
22    /// The file attributes (Read Only, Archive, etc)
23    pub attributes: Attributes,
24    /// The starting cluster of the file. The FAT tells us the following Clusters.
25    pub cluster: ClusterId,
26    /// The size of the file in bytes.
27    pub size: u32,
28    /// The disk block of this entry
29    pub entry_block: BlockIdx,
30    /// The offset on its block (in bytes)
31    pub entry_offset: u32,
32}
33
34/// A handle for an open directory on disk.
35///
36/// Do NOT drop this object! It doesn't hold a reference to the Volume Manager
37/// it was created from and if you drop it, the VolumeManager will think you
38/// still have the directory open, and it won't let you open the directory
39/// again.
40///
41/// Instead you must pass it to [`crate::VolumeManager::close_dir`] to close it
42/// cleanly.
43///
44/// If you want your directories to close themselves on drop, create your own
45/// `Directory` type that wraps this one and also holds a `VolumeManager`
46/// reference. You'll then also need to put your `VolumeManager` in some kind of
47/// Mutex or RefCell, and deal with the fact you can't put them both in the same
48/// struct any more because one refers to the other. Basically, it's complicated
49/// and there's a reason we did it this way.
50#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
51#[derive(Debug, Copy, Clone, PartialEq, Eq)]
52pub struct RawDirectory(pub(crate) Handle);
53
54impl RawDirectory {
55    /// Convert a raw directory into a droppable [`Directory`]
56    pub fn to_directory<
57        D,
58        T,
59        const MAX_DIRS: usize,
60        const MAX_FILES: usize,
61        const MAX_VOLUMES: usize,
62    >(
63        self,
64        volume_mgr: &VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
65    ) -> Directory<'_, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
66    where
67        D: crate::BlockDevice,
68        T: crate::TimeSource,
69    {
70        Directory::new(self, volume_mgr)
71    }
72}
73
74/// A handle for an open directory on disk, which closes on drop.
75///
76/// In contrast to a `RawDirectory`, a `Directory` holds a mutable reference to
77/// its parent `VolumeManager`, which restricts which operations you can perform.
78///
79/// If you drop a value of this type, it closes the directory automatically, but
80/// any error that may occur will be ignored. To handle potential errors, use
81/// the [`Directory::close`] method.
82pub struct Directory<
83    'a,
84    D,
85    T,
86    const MAX_DIRS: usize,
87    const MAX_FILES: usize,
88    const MAX_VOLUMES: usize,
89> where
90    D: crate::BlockDevice,
91    T: crate::TimeSource,
92{
93    raw_directory: RawDirectory,
94    volume_mgr: &'a VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
95}
96
97impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
98    Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
99where
100    D: crate::BlockDevice,
101    T: crate::TimeSource,
102{
103    /// Create a new `Directory` from a `RawDirectory`
104    pub fn new(
105        raw_directory: RawDirectory,
106        volume_mgr: &'a VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
107    ) -> Self {
108        Directory {
109            raw_directory,
110            volume_mgr,
111        }
112    }
113
114    /// Open a directory.
115    ///
116    /// You can then read the directory entries with `iterate_dir` and `open_file_in_dir`.
117    ///
118    /// See [`VolumeManager::open_dir`] for details, except the directory
119    /// given is this directory.
120    pub fn open_dir<N>(&self, name: N) -> Result<Self, Error<D::Error>>
121    where
122        N: ToShortFileName,
123    {
124        let d = self.volume_mgr.open_dir(self.raw_directory, name)?;
125        Ok(d.to_directory(self.volume_mgr))
126    }
127
128    /// Change to a directory, mutating this object.
129    ///
130    /// You can then read the directory entries with `iterate_dir` and `open_file_in_dir`.
131    pub fn change_dir<N>(&mut self, name: N) -> Result<(), Error<D::Error>>
132    where
133        N: ToShortFileName,
134    {
135        let d = self.volume_mgr.open_dir(self.raw_directory, name)?;
136        self.volume_mgr.close_dir(self.raw_directory).unwrap();
137        self.raw_directory = d;
138        Ok(())
139    }
140
141    /// Read the directory entry with the given filename from this directory, if it exists.
142    ///
143    /// See [`VolumeManager::find_directory_entry`] for details, except the
144    /// directory given is this directory.
145    pub fn find_directory_entry<N>(&self, name: N) -> Result<DirEntry, Error<D::Error>>
146    where
147        N: ToShortFileName,
148    {
149        self.volume_mgr
150            .find_directory_entry(self.raw_directory, name)
151    }
152
153    /// Check whether a directory entry exists.
154    pub fn directory_entry_exists<N: ToShortFileName>(&self, name: N) -> bool {
155        self.find_directory_entry(name).is_ok()
156    }
157
158    /// Call a callback function for each directory entry in a directory.
159    ///
160    /// Long File Names will be ignored.
161    ///
162    /// See [`VolumeManager::iterate_dir`] for details, except the directory
163    /// given is this directory.
164    pub fn iterate_dir<F>(&self, func: F) -> Result<(), Error<D::Error>>
165    where
166        F: FnMut(&DirEntry) -> ControlFlow<()>,
167    {
168        self.volume_mgr.iterate_dir(self.raw_directory, func)
169    }
170
171    /// Call a callback function for each directory entry in a directory, and
172    /// process Long File Names.
173    ///
174    /// See [`VolumeManager::iterate_dir_lfn`] for details, except the
175    /// directory given is this directory.
176    pub fn iterate_dir_lfn<F>(
177        &self,
178        lfn_buffer: &mut LfnBuffer<'_>,
179        func: F,
180    ) -> Result<(), Error<D::Error>>
181    where
182        F: FnMut(&DirEntry, Option<&str>) -> ControlFlow<()>,
183    {
184        self.volume_mgr
185            .iterate_dir_lfn(self.raw_directory, lfn_buffer, func)
186    }
187
188    /// Open a file.
189    ///
190    /// See [`VolumeManager::open_file_in_dir`] for details, except the
191    /// directory given is this directory.
192    pub fn open_file_in_dir<N>(
193        &self,
194        name: N,
195        mode: crate::Mode,
196    ) -> Result<crate::File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>, crate::Error<D::Error>>
197    where
198        N: super::ToShortFileName,
199    {
200        let f = self
201            .volume_mgr
202            .open_file_in_dir(self.raw_directory, name, mode)?;
203        Ok(f.to_file(self.volume_mgr))
204    }
205
206    /// Open a file.
207    ///
208    /// See [`VolumeManager::open_long_name_file_in_dir`] for details, except the
209    /// directory given is this directory.
210    pub fn open_long_name_file_in_dir(
211        &self,
212        name: &str,
213        mode: crate::Mode,
214    ) -> Result<crate::File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>, crate::Error<D::Error>>
215    {
216        let f = self
217            .volume_mgr
218            .open_long_name_file_in_dir(self.raw_directory, name, mode)?;
219        Ok(f.to_file(self.volume_mgr))
220    }
221
222    /// Delete a file/directory.
223    ///
224    /// See [`VolumeManager::delete_entry_in_dir`] for details, except the
225    /// directory given is this directory.
226    pub fn delete_entry_in_dir<N>(&self, name: N) -> Result<(), Error<D::Error>>
227    where
228        N: ToShortFileName,
229    {
230        self.volume_mgr
231            .delete_entry_in_dir(self.raw_directory, name)
232    }
233
234    /// Create a new empty directory.
235    ///
236    /// See [`VolumeManager::make_dir_in_dir`] for details, except the
237    /// directory given is this directory.
238    pub fn make_dir_in_dir<N>(&self, name: N) -> Result<(), Error<D::Error>>
239    where
240        N: ToShortFileName,
241    {
242        self.volume_mgr.make_dir_in_dir(self.raw_directory, name)
243    }
244
245    /// Convert back to a raw directory
246    pub fn to_raw_directory(self) -> RawDirectory {
247        let d = self.raw_directory;
248        core::mem::forget(self);
249        d
250    }
251
252    /// Consume the `Directory` handle and close it. The behavior of this is similar
253    /// to using [`core::mem::drop`] or letting the `Directory` go out of scope,
254    /// except this lets the user handle any errors that may occur in the process,
255    /// whereas when using drop, any errors will be discarded silently.
256    pub fn close(self) -> Result<(), Error<D::Error>> {
257        let result = self.volume_mgr.close_dir(self.raw_directory);
258        core::mem::forget(self);
259        result
260    }
261}
262
263impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> Drop
264    for Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
265where
266    D: crate::BlockDevice,
267    T: crate::TimeSource,
268{
269    fn drop(&mut self) {
270        _ = self.volume_mgr.close_dir(self.raw_directory)
271    }
272}
273
274impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
275    core::fmt::Debug for Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
276where
277    D: crate::BlockDevice,
278    T: crate::TimeSource,
279{
280    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
281        write!(f, "Directory({})", self.raw_directory.0.0)
282    }
283}
284
285#[cfg(feature = "defmt-log")]
286impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
287    defmt::Format for Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
288where
289    D: crate::BlockDevice,
290    T: crate::TimeSource,
291{
292    fn format(&self, fmt: defmt::Formatter) {
293        defmt::write!(fmt, "Directory({})", self.raw_directory.0.0)
294    }
295}
296
297/// Holds information about an open file on disk
298#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
299#[derive(Debug, Clone)]
300pub(crate) struct DirectoryInfo {
301    /// The handle for this directory.
302    pub(crate) raw_directory: RawDirectory,
303    /// The handle for the volume this directory is on
304    pub(crate) raw_volume: RawVolume,
305    /// The starting point of the directory listing.
306    pub(crate) cluster: ClusterId,
307}
308
309impl DirEntry {
310    pub(crate) fn serialize(&self, fat_type: FatType) -> [u8; OnDiskDirEntry::LEN] {
311        let mut data = [0u8; OnDiskDirEntry::LEN];
312        data[0..11].copy_from_slice(&self.name.contents);
313        data[11] = self.attributes.0;
314        // 12: Reserved. Must be set to zero
315        // 13: CrtTimeTenth, not supported, set to zero
316        data[14..18].copy_from_slice(&self.ctime.serialize_to_fat()[..]);
317        // 0 + 18: LastAccDate, not supported, set to zero
318        let cluster_number = self.cluster.0;
319        let cluster_hi = if fat_type == FatType::Fat16 {
320            [0u8; 2]
321        } else {
322            // Safe due to the AND operation
323            (((cluster_number >> 16) & 0x0000_FFFF) as u16).to_le_bytes()
324        };
325        data[20..22].copy_from_slice(&cluster_hi[..]);
326        data[22..26].copy_from_slice(&self.mtime.serialize_to_fat()[..]);
327        // Safe due to the AND operation
328        let cluster_lo = ((cluster_number & 0x0000_FFFF) as u16).to_le_bytes();
329        data[26..28].copy_from_slice(&cluster_lo[..]);
330        data[28..32].copy_from_slice(&self.size.to_le_bytes()[..]);
331        data
332    }
333
334    pub(crate) fn new(
335        name: ShortFileName,
336        attributes: Attributes,
337        cluster: ClusterId,
338        ctime: Timestamp,
339        entry_block: BlockIdx,
340        entry_offset: u32,
341    ) -> Self {
342        Self {
343            name,
344            mtime: ctime,
345            ctime,
346            attributes,
347            cluster,
348            size: 0,
349            entry_block,
350            entry_offset,
351        }
352    }
353}
354
355// ****************************************************************************
356//
357// End Of File
358//
359// ****************************************************************************