embedded_sdmmc/filesystem/
directory.rs

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