simple_fatfs/fat/direntry/
public.rs

1use super::*;
2
3use core::ops;
4
5use crate::*;
6
7#[cfg(not(feature = "std"))]
8use alloc::{borrow::ToOwned, boxed::Box, string::String};
9
10use ::time;
11use bincode::{Decode, Encode};
12use embedded_io::*;
13use time::{Date, PrimitiveDateTime};
14
15/// A list of the various attributes specified for a file/directory
16#[derive(Debug, Clone, Copy)]
17pub struct Attributes {
18    /// This is a read-only file
19    pub read_only: bool,
20    /// This file is to be hidden unless a request is issued
21    /// explicitly requesting inclusion of “hidden files”
22    pub hidden: bool,
23    /// This is a system file and shouldn't be listed unless a request
24    /// is issued explicitly requesting inclusion of ”system files”
25    pub system: bool,
26    /// This file has been modified since last archival
27    /// or has never been archived.
28    ///
29    /// This field should only concern archival software
30    pub archive: bool,
31}
32
33impl From<RawAttributes> for Attributes {
34    fn from(value: RawAttributes) -> Self {
35        Attributes {
36            read_only: value.contains(RawAttributes::READ_ONLY),
37            hidden: value.contains(RawAttributes::HIDDEN),
38            system: value.contains(RawAttributes::SYSTEM),
39            archive: value.contains(RawAttributes::ARCHIVE),
40        }
41    }
42}
43
44// a directory entry occupies 32 bytes
45pub(crate) const DIRENTRY_SIZE: usize = 32;
46
47pub(crate) const SFN_NAME_LEN: usize = 8;
48pub(crate) const SFN_EXT_LEN: usize = 3;
49// don't forget the "." between the name and the file extension
50pub(crate) const SFN_LEN: usize = SFN_NAME_LEN + 1 + SFN_EXT_LEN;
51
52#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, Eq)]
53/// The short filename of an entry
54///
55/// In FAT, each file has 2 filenames: one long and one short filename.
56/// The short filename is retained for backwards-compatibility reasons
57/// by the FAT specification and shouldn't concern most users.
58pub(crate) struct Sfn {
59    pub(crate) name: [u8; SFN_NAME_LEN],
60    pub(crate) ext: [u8; SFN_EXT_LEN],
61}
62
63pub(crate) const CURRENT_DIR_SFN: Sfn = Sfn {
64    name: {
65        use typed_path::constants::windows::CURRENT_DIR;
66
67        // not pretty, but it works
68        let mut s = [b' '; SFN_NAME_LEN];
69        // apparently, subslicing a const slice is not const, nice!
70        s[0] = CURRENT_DIR[0];
71        s
72    },
73    ext: [b' '; SFN_EXT_LEN],
74};
75
76pub(crate) const PARENT_DIR_SFN: Sfn = Sfn {
77    name: {
78        use typed_path::constants::windows::PARENT_DIR;
79
80        // not pretty, but it works
81        let mut s = [b' '; SFN_NAME_LEN];
82        // apparently, subslicing a const slice is not const, nice!
83        s[0] = PARENT_DIR[0];
84        s[1] = PARENT_DIR[1];
85        s
86    },
87    ext: [b' '; SFN_EXT_LEN],
88};
89
90impl Sfn {
91    fn get_byte_slice(&self) -> [u8; SFN_NAME_LEN + SFN_EXT_LEN] {
92        let mut slice = [0; SFN_NAME_LEN + SFN_EXT_LEN];
93
94        slice[..SFN_NAME_LEN].copy_from_slice(&self.name);
95        slice[SFN_NAME_LEN..].copy_from_slice(&self.ext);
96
97        slice
98    }
99
100    pub(crate) fn gen_checksum(&self) -> u8 {
101        let mut sum = 0;
102
103        for c in self.get_byte_slice() {
104            sum = (if (sum & 1) != 0 { 0x80_u8 } else { 0_u8 })
105                .wrapping_add(sum >> 1)
106                .wrapping_add(c)
107        }
108
109        sum
110    }
111
112    pub(crate) fn decode(&self, codepage: &Codepage) -> String {
113        let mut string = String::with_capacity(SFN_LEN);
114        // we begin by writing the name (even if it is padded with spaces, they will be trimmed, so we don't care)
115        string.push_str(codepage.decode(&self.name).trim_end());
116
117        // then, if the extension isn't empty (padded with zeroes), we write it too
118        let ext = codepage.decode(&self.ext).trim_end().to_owned();
119        if !ext.is_empty() {
120            string.push_str(&ext);
121        };
122
123        string
124    }
125}
126
127/// A container for file/directory properties
128#[derive(Clone, Debug)]
129pub struct Properties {
130    pub(crate) path: Box<Path>,
131    pub(crate) sfn: (Sfn, Codepage),
132    pub(crate) is_dir: bool,
133    pub(crate) attributes: Attributes,
134    pub(crate) created: Option<PrimitiveDateTime>,
135    pub(crate) modified: PrimitiveDateTime,
136    pub(crate) accessed: Option<Date>,
137    pub(crate) file_size: u32,
138    pub(crate) data_cluster: u32,
139
140    // internal fields
141    pub(crate) chain: DirEntryChain,
142}
143
144/// Getter methods
145impl Properties {
146    #[inline]
147    /// Get the corresponding [`Path`] to this entry
148    pub fn path(&self) -> &Path {
149        &self.path
150    }
151
152    #[inline]
153    /// Get the corresponding short filename for this entry
154    pub fn sfn(&self) -> String {
155        self.sfn.0.decode(&self.sfn.1)
156    }
157
158    #[inline]
159    /// Check whether this entry belongs to a directory
160    pub fn is_dir(&self) -> bool {
161        self.is_dir
162    }
163
164    #[inline]
165    /// Check whether this entry belongs to a file
166    pub fn is_file(&self) -> bool {
167        !self.is_dir()
168    }
169
170    #[inline]
171    /// Get the corresponding [`Attributes`] to this entry
172    pub fn attributes(&self) -> &Attributes {
173        &self.attributes
174    }
175
176    #[inline]
177    /// Find out when this entry was created (max resolution: 1ms)
178    ///
179    /// Returns an [`Option`] containing a [`PrimitiveDateTime`] from the [`time`] crate,
180    /// since that field is specified as optional in the FAT32 specification
181    pub fn creation_time(&self) -> &Option<PrimitiveDateTime> {
182        &self.created
183    }
184
185    #[inline]
186    /// Find out when this entry was last modified (max resolution: 2 secs)
187    ///
188    /// Returns a [`PrimitiveDateTime`] from the [`time`] crate
189    pub fn modification_time(&self) -> &PrimitiveDateTime {
190        &self.modified
191    }
192
193    #[inline]
194    /// Find out when this entry was last accessed (max resolution: 1 day)
195    ///
196    /// Returns an [`Option`] containing a [`Date`] from the [`time`] crate,
197    /// since that field is specified as optional in the FAT32 specification
198    pub fn last_accessed_date(&self) -> &Option<Date> {
199        &self.accessed
200    }
201
202    #[inline]
203    /// Find out the size of this entry
204    ///
205    /// Always returns `0` for directories
206    pub fn file_size(&self) -> u32 {
207        self.file_size
208    }
209}
210
211impl Properties {
212    pub(crate) fn from_raw(raw_props: RawProperties, path: Box<Path>, codepage: Codepage) -> Self {
213        Self {
214            path,
215            sfn: (raw_props.sfn, codepage),
216            is_dir: raw_props.is_dir,
217            attributes: raw_props.attributes.into(),
218            created: raw_props.created,
219            modified: raw_props.modified,
220            accessed: raw_props.accessed,
221            file_size: raw_props.file_size,
222            data_cluster: raw_props.data_cluster,
223            chain: raw_props.chain,
224        }
225    }
226}
227
228/// A thin wrapper for [`Properties`] representing a directory entry
229#[derive(Debug)]
230pub struct DirEntry<'a, S>
231where
232    S: Read + Seek,
233{
234    pub(crate) entry: Properties,
235    pub(crate) fs: &'a FileSystem<S>,
236}
237
238impl<'a, S> DirEntry<'a, S>
239where
240    S: Read + Seek,
241{
242    /// Get the corresponding [`ROFile`] object for this [`DirEntry`]
243    ///
244    /// Will return [`None`] if the entry isn't a file
245    pub fn to_ro_file(&self) -> Option<ROFile<'a, S>> {
246        self.is_file().then(|| ROFile {
247            fs: self.fs,
248            props: FileProps {
249                entry: self.entry.clone(),
250                offset: 0,
251                current_cluster: self.data_cluster,
252            },
253        })
254    }
255
256    /// Get the corresponding [`ReadDir`] object for this [`DirEntry`]
257    ///
258    /// Will return [`None`] if the entry isn't a directory
259    pub fn to_dir(&self) -> Option<ReadDir<'a, S>> {
260        self.is_dir().then(|| {
261            ReadDir::new(
262                self.fs,
263                &EntryLocationUnit::DataCluster(self.data_cluster),
264                self.path(),
265            )
266        })
267    }
268}
269
270impl<'a, S> DirEntry<'a, S>
271where
272    S: Read + Write + Seek,
273{
274    /// Get the corresponding [`RWFile`] object of this [`DirEntry`]
275    ///
276    /// Will return `None` if the entry is a directory
277    pub fn to_rw_file(self) -> Option<RWFile<'a, S>> {
278        self.to_ro_file().map(|ro_file| ro_file.into())
279    }
280}
281
282impl<S> ops::Deref for DirEntry<'_, S>
283where
284    S: Read + Seek,
285{
286    type Target = Properties;
287
288    #[inline]
289    fn deref(&self) -> &Self::Target {
290        &self.entry
291    }
292}
293
294/// Iterator over the entries in a directory.
295///
296/// The order in which this iterator returns entries can vary
297/// and shouldn't be relied upon
298#[derive(Debug)]
299pub struct ReadDir<'a, S>
300where
301    S: Read + Seek,
302{
303    inner: ReadDirInt<'a, S>,
304    parent: Box<Path>,
305}
306
307impl<'a, S> ReadDir<'a, S>
308where
309    S: Read + Seek,
310{
311    pub(crate) fn new<P>(fs: &'a FileSystem<S>, chain_start: &EntryLocationUnit, parent: P) -> Self
312    where
313        P: AsRef<Path>,
314    {
315        Self {
316            inner: ReadDirInt::new(fs, chain_start),
317            parent: parent.as_ref().into(),
318        }
319    }
320}
321
322impl<'a, S> Iterator for ReadDir<'a, S>
323where
324    S: Read + Seek,
325{
326    type Item = Result<DirEntry<'a, S>, S::Error>;
327
328    fn next(&mut self) -> Option<Self::Item> {
329        loop {
330            match self.inner.next() {
331                Some(res) => match res {
332                    Ok(value) => {
333                        if self.inner.fs.filter.borrow().filter(&value)
334                            // we shouldn't expose the special entries to the user
335                            && ![path_consts::CURRENT_DIR_STR, path_consts::PARENT_DIR_STR]
336                                .contains(&value.name.as_str())
337                        {
338                            return Some(Ok(value.into_dir_entry(&self.parent, self.inner.fs)));
339                        } else {
340                            continue;
341                        }
342                    }
343                    Err(err) => return Some(Err(err)),
344                },
345                None => return None,
346            }
347        }
348    }
349}