embedded_sdmmc_dev/filesystem/
directory.rs

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