1use crate::block::{EntryBlock, hash_name, names_equal};
4use crate::constants::*;
5use crate::date::AmigaDate;
6use crate::error::{AffsError, Result};
7use crate::types::{Access, BlockDevice, EntryType};
8
9#[derive(Debug, Clone)]
11pub struct DirEntry {
12 pub(crate) name: [u8; MAX_NAME_LEN],
14 pub(crate) name_len: u8,
16 pub entry_type: EntryType,
18 pub block: u32,
20 pub parent: u32,
22 pub size: u32,
24 pub access: Access,
26 pub date: AmigaDate,
28 pub real_entry: u32,
30 pub(crate) comment: [u8; MAX_COMMENT_LEN],
32 pub(crate) comment_len: u8,
34}
35
36impl DirEntry {
37 pub(crate) fn from_entry_block(block_num: u32, entry: &EntryBlock) -> Option<Self> {
39 let entry_type = entry.entry_type()?;
40
41 let mut name = [0u8; MAX_NAME_LEN];
42 let name_len = entry.name_len.min(MAX_NAME_LEN as u8);
43 name[..name_len as usize].copy_from_slice(&entry.name[..name_len as usize]);
44
45 let mut comment = [0u8; MAX_COMMENT_LEN];
46 let comment_len = entry.comment_len.min(MAX_COMMENT_LEN as u8);
47 comment[..comment_len as usize].copy_from_slice(&entry.comment[..comment_len as usize]);
48
49 Some(Self {
50 name,
51 name_len,
52 entry_type,
53 block: block_num,
54 parent: entry.parent,
55 size: entry.byte_size,
56 access: Access::new(entry.access),
57 date: entry.date,
58 real_entry: entry.real_entry,
59 comment,
60 comment_len,
61 })
62 }
63
64 #[inline]
66 pub fn name(&self) -> &[u8] {
67 &self.name[..self.name_len as usize]
68 }
69
70 #[inline]
72 pub fn name_str(&self) -> Option<&str> {
73 core::str::from_utf8(self.name()).ok()
74 }
75
76 #[inline]
78 pub fn comment(&self) -> &[u8] {
79 &self.comment[..self.comment_len as usize]
80 }
81
82 #[inline]
84 pub fn comment_str(&self) -> Option<&str> {
85 core::str::from_utf8(self.comment()).ok()
86 }
87
88 #[inline]
90 pub const fn is_dir(&self) -> bool {
91 self.entry_type.is_dir()
92 }
93
94 #[inline]
96 pub const fn is_file(&self) -> bool {
97 self.entry_type.is_file()
98 }
99
100 #[inline]
102 pub const fn is_symlink(&self) -> bool {
103 matches!(self.entry_type, EntryType::SoftLink)
104 }
105}
106
107pub struct DirIter<'a, D: BlockDevice> {
111 device: &'a D,
112 hash_table: [u32; HASH_TABLE_SIZE],
113 hash_index: usize,
114 current_chain: u32,
115 intl: bool,
116 buf: [u8; BLOCK_SIZE],
117}
118
119impl<'a, D: BlockDevice> DirIter<'a, D> {
120 pub(crate) fn new(device: &'a D, hash_table: [u32; HASH_TABLE_SIZE], intl: bool) -> Self {
122 Self {
123 device,
124 hash_table,
125 hash_index: 0,
126 current_chain: 0,
127 intl,
128 buf: [0u8; BLOCK_SIZE],
129 }
130 }
131
132 pub fn find(mut self, name: &[u8]) -> Result<DirEntry> {
134 if name.len() > MAX_NAME_LEN {
135 return Err(AffsError::NameTooLong);
136 }
137
138 let hash = hash_name(name, self.intl);
139 let mut block = self.hash_table[hash];
140
141 while block != 0 {
142 self.device
143 .read_block(block, &mut self.buf)
144 .map_err(|()| AffsError::BlockReadError)?;
145
146 let entry = EntryBlock::parse(&self.buf)?;
147
148 if names_equal(entry.name(), name, self.intl) {
149 return DirEntry::from_entry_block(block, &entry).ok_or(AffsError::InvalidSecType);
150 }
151
152 block = entry.next_same_hash;
153 }
154
155 Err(AffsError::EntryNotFound)
156 }
157}
158
159impl<D: BlockDevice> Iterator for DirIter<'_, D> {
160 type Item = Result<DirEntry>;
161
162 fn next(&mut self) -> Option<Self::Item> {
163 loop {
164 if self.current_chain != 0 {
166 let result = self.device.read_block(self.current_chain, &mut self.buf);
167 if result.is_err() {
168 return Some(Err(AffsError::BlockReadError));
169 }
170
171 match EntryBlock::parse(&self.buf) {
172 Ok(entry) => {
173 let block = self.current_chain;
174 self.current_chain = entry.next_same_hash;
175
176 match DirEntry::from_entry_block(block, &entry) {
177 Some(dir_entry) => return Some(Ok(dir_entry)),
178 None => continue, }
180 }
181 Err(e) => return Some(Err(e)),
182 }
183 }
184
185 while self.hash_index < HASH_TABLE_SIZE {
187 let block = self.hash_table[self.hash_index];
188 self.hash_index += 1;
189
190 if block != 0 {
191 self.current_chain = block;
192 break;
193 }
194 }
195
196 if self.current_chain == 0 {
198 return None;
199 }
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_dir_entry_name() {
210 let mut entry = DirEntry {
211 name: [0u8; MAX_NAME_LEN],
212 name_len: 4,
213 entry_type: EntryType::File,
214 block: 100,
215 parent: 880,
216 size: 1024,
217 access: Access::new(0),
218 date: AmigaDate::default(),
219 real_entry: 0,
220 comment: [0u8; MAX_COMMENT_LEN],
221 comment_len: 0,
222 };
223 entry.name[..4].copy_from_slice(b"test");
224
225 assert_eq!(entry.name(), b"test");
226 assert_eq!(entry.name_str(), Some("test"));
227 }
228}