Skip to main content

ax_fs/api/
dir.rs

1// Copyright 2025 The Axvisor Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use alloc::string::String;
16use core::fmt;
17
18use ax_io::Result;
19
20use super::FileType;
21use crate::fops;
22
23/// Iterator over the entries in a directory.
24pub struct ReadDir<'a> {
25    path: &'a str,
26    inner: fops::Directory,
27    buf_pos: usize,
28    buf_end: usize,
29    end_of_stream: bool,
30    dirent_buf: [fops::DirEntry; 31],
31}
32
33/// Entries returned by the [`ReadDir`] iterator.
34pub struct DirEntry<'a> {
35    dir_path: &'a str,
36    entry_name: String,
37    entry_type: FileType,
38}
39
40/// A builder used to create directories in various manners.
41#[derive(Default, Debug)]
42pub struct DirBuilder {
43    recursive: bool,
44}
45
46impl<'a> ReadDir<'a> {
47    pub(super) fn new(path: &'a str) -> Result<Self> {
48        let mut opts = fops::OpenOptions::new();
49        opts.read(true);
50        let inner = fops::Directory::open_dir(path, &opts)?;
51        const EMPTY: fops::DirEntry = fops::DirEntry::default();
52        let dirent_buf = [EMPTY; 31];
53        Ok(ReadDir {
54            path,
55            inner,
56            end_of_stream: false,
57            buf_pos: 0,
58            buf_end: 0,
59            dirent_buf,
60        })
61    }
62}
63
64impl<'a> Iterator for ReadDir<'a> {
65    type Item = Result<DirEntry<'a>>;
66
67    fn next(&mut self) -> Option<Result<DirEntry<'a>>> {
68        if self.end_of_stream {
69            return None;
70        }
71
72        loop {
73            if self.buf_pos >= self.buf_end {
74                match self.inner.read_dir(&mut self.dirent_buf) {
75                    Ok(n) => {
76                        if n == 0 {
77                            self.end_of_stream = true;
78                            return None;
79                        }
80                        self.buf_pos = 0;
81                        self.buf_end = n;
82                    }
83                    Err(e) => {
84                        self.end_of_stream = true;
85                        return Some(Err(e));
86                    }
87                }
88            }
89            let entry = &self.dirent_buf[self.buf_pos];
90            self.buf_pos += 1;
91            let name_bytes = entry.name_as_bytes();
92            if name_bytes == b"." || name_bytes == b".." {
93                continue;
94            }
95            let entry_name = unsafe { core::str::from_utf8_unchecked(name_bytes).into() };
96            let entry_type = entry.entry_type();
97
98            return Some(Ok(DirEntry {
99                dir_path: self.path,
100                entry_name,
101                entry_type,
102            }));
103        }
104    }
105}
106
107impl DirEntry<'_> {
108    /// Returns the full path to the file that this entry represents.
109    ///
110    /// The full path is created by joining the original path to `read_dir`
111    /// with the filename of this entry.
112    pub fn path(&self) -> String {
113        String::from(self.dir_path.trim_end_matches('/')) + "/" + &self.entry_name
114    }
115
116    /// Returns the bare file name of this directory entry without any other
117    /// leading path component.
118    pub fn file_name(&self) -> String {
119        self.entry_name.clone()
120    }
121
122    /// Returns the file type for the file that this entry points at.
123    pub fn file_type(&self) -> FileType {
124        self.entry_type
125    }
126}
127
128impl fmt::Debug for DirEntry<'_> {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        f.debug_tuple("DirEntry").field(&self.path()).finish()
131    }
132}
133
134impl DirBuilder {
135    /// Creates a new set of options with default mode/security settings for all
136    /// platforms and also non-recursive.
137    pub fn new() -> Self {
138        Self { recursive: false }
139    }
140
141    /// Indicates that directories should be created recursively, creating all
142    /// parent directories. Parents that do not exist are created with the same
143    /// security and permissions settings.
144    pub fn recursive(&mut self, recursive: bool) -> &mut Self {
145        self.recursive = recursive;
146        self
147    }
148
149    /// Creates the specified directory with the options configured in this
150    /// builder.
151    pub fn create(&self, path: &str) -> Result<()> {
152        if self.recursive {
153            self.create_dir_all(path)
154        } else {
155            crate::root::create_dir(None, path)
156        }
157    }
158
159    fn create_dir_all(&self, _path: &str) -> Result<()> {
160        Err(ax_errno::AxError::Unsupported)
161    }
162}