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
101pub struct DirIter<'a, D: BlockDevice> {
105 device: &'a D,
106 hash_table: [u32; HASH_TABLE_SIZE],
107 hash_index: usize,
108 current_chain: u32,
109 intl: bool,
110 buf: [u8; BLOCK_SIZE],
111}
112
113impl<'a, D: BlockDevice> DirIter<'a, D> {
114 pub(crate) fn new(device: &'a D, hash_table: [u32; HASH_TABLE_SIZE], intl: bool) -> Self {
116 Self {
117 device,
118 hash_table,
119 hash_index: 0,
120 current_chain: 0,
121 intl,
122 buf: [0u8; BLOCK_SIZE],
123 }
124 }
125
126 pub fn find(mut self, name: &[u8]) -> Result<DirEntry> {
128 if name.len() > MAX_NAME_LEN {
129 return Err(AffsError::NameTooLong);
130 }
131
132 let hash = hash_name(name, self.intl);
133 let mut block = self.hash_table[hash];
134
135 while block != 0 {
136 self.device
137 .read_block(block, &mut self.buf)
138 .map_err(|()| AffsError::BlockReadError)?;
139
140 let entry = EntryBlock::parse(&self.buf)?;
141
142 if names_equal(entry.name(), name, self.intl) {
143 return DirEntry::from_entry_block(block, &entry).ok_or(AffsError::InvalidSecType);
144 }
145
146 block = entry.next_same_hash;
147 }
148
149 Err(AffsError::EntryNotFound)
150 }
151}
152
153impl<D: BlockDevice> Iterator for DirIter<'_, D> {
154 type Item = Result<DirEntry>;
155
156 fn next(&mut self) -> Option<Self::Item> {
157 loop {
158 if self.current_chain != 0 {
160 let result = self.device.read_block(self.current_chain, &mut self.buf);
161 if result.is_err() {
162 return Some(Err(AffsError::BlockReadError));
163 }
164
165 match EntryBlock::parse(&self.buf) {
166 Ok(entry) => {
167 let block = self.current_chain;
168 self.current_chain = entry.next_same_hash;
169
170 match DirEntry::from_entry_block(block, &entry) {
171 Some(dir_entry) => return Some(Ok(dir_entry)),
172 None => continue, }
174 }
175 Err(e) => return Some(Err(e)),
176 }
177 }
178
179 while self.hash_index < HASH_TABLE_SIZE {
181 let block = self.hash_table[self.hash_index];
182 self.hash_index += 1;
183
184 if block != 0 {
185 self.current_chain = block;
186 break;
187 }
188 }
189
190 if self.current_chain == 0 {
192 return None;
193 }
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_dir_entry_name() {
204 let mut entry = DirEntry {
205 name: [0u8; MAX_NAME_LEN],
206 name_len: 4,
207 entry_type: EntryType::File,
208 block: 100,
209 parent: 880,
210 size: 1024,
211 access: Access::new(0),
212 date: AmigaDate::default(),
213 real_entry: 0,
214 comment: [0u8; MAX_COMMENT_LEN],
215 comment_len: 0,
216 };
217 entry.name[..4].copy_from_slice(b"test");
218
219 assert_eq!(entry.name(), b"test");
220 assert_eq!(entry.name_str(), Some("test"));
221 }
222}