1use std::fmt;
4use std::fmt::Write;
5use std::io;
6use std::iter;
7
8use disk::block::{Location, Position, PositionedData, BLOCK_SIZE};
9use disk::chain::{ChainIterator, ChainSector};
10use disk::file::Scheme;
11use disk::geos::{GEOSFileStructure, GEOSFileType};
12use disk::{Disk, DiskError, PADDING_BYTE};
13use petscii::Petscii;
14
15const FILE_TYPE_DEL: u8 = 0x00;
16const FILE_TYPE_SEQ: u8 = 0x01;
17const FILE_TYPE_PRG: u8 = 0x02;
18const FILE_TYPE_USR: u8 = 0x03;
19const FILE_TYPE_REL: u8 = 0x04;
20const FILE_ATTRIB_FILE_TYPE_MASK: u8 = 0x0F;
21const FILE_ATTRIB_UNUSED_MASK: u8 = 0x10;
22const FILE_ATTRIB_SAVE_WITH_REPLACE_MASK: u8 = 0x20;
23const FILE_ATTRIB_LOCKED_MASK: u8 = 0x40;
24const FILE_ATTRIB_CLOSED_MASK: u8 = 0x80;
25
26#[derive(PartialEq, Debug, Clone, Copy)]
29pub enum FileType {
30 DEL,
31 SEQ,
32 PRG,
33 USR,
34 REL,
35 Unknown(u8),
36}
37
38impl FileType {
39 pub fn from_string(string: &str) -> Option<FileType> {
41 match string.to_uppercase().as_str() {
42 "DEL" => Some(FileType::DEL),
43 "SEQ" => Some(FileType::SEQ),
44 "PRG" => Some(FileType::PRG),
45 "USR" => Some(FileType::USR),
46 "REL" => Some(FileType::REL),
47 _ => None,
48 }
49 }
50}
51
52impl fmt::Display for FileType {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 f.write_str(match self {
55 &FileType::DEL => "del",
56 &FileType::SEQ => "seq",
57 &FileType::PRG => "prg",
58 &FileType::USR => "usr",
59 &FileType::REL => "rel",
60 &FileType::Unknown(_) => "unk",
61 })
62 }
63}
64
65#[derive(Clone)]
68pub struct FileAttributes {
69 pub file_type: FileType,
71 pub unused_bit: bool,
74 pub save_with_replace_flag: bool,
76 pub locked_flag: bool,
78 pub closed_flag: bool,
82}
83
84impl FileAttributes {
85 pub fn from_byte(byte: u8) -> FileAttributes {
87 let file_type = match byte & FILE_ATTRIB_FILE_TYPE_MASK {
88 FILE_TYPE_DEL => FileType::DEL,
89 FILE_TYPE_SEQ => FileType::SEQ,
90 FILE_TYPE_PRG => FileType::PRG,
91 FILE_TYPE_USR => FileType::USR,
92 FILE_TYPE_REL => FileType::REL,
93 b => FileType::Unknown(b),
94 };
95 FileAttributes {
96 file_type,
97 unused_bit: byte & FILE_ATTRIB_UNUSED_MASK != 0,
98 save_with_replace_flag: byte & FILE_ATTRIB_SAVE_WITH_REPLACE_MASK != 0,
99 locked_flag: byte & FILE_ATTRIB_LOCKED_MASK != 0,
100 closed_flag: byte & FILE_ATTRIB_CLOSED_MASK != 0,
101 }
102 }
103
104 pub fn to_byte(&self) -> u8 {
106 let mut byte = match &self.file_type {
107 &FileType::DEL => FILE_TYPE_DEL,
108 &FileType::SEQ => FILE_TYPE_SEQ,
109 &FileType::PRG => FILE_TYPE_PRG,
110 &FileType::USR => FILE_TYPE_USR,
111 &FileType::REL => FILE_TYPE_REL,
112 &FileType::Unknown(b) => b,
113 };
114 if self.unused_bit {
115 byte = byte | FILE_ATTRIB_UNUSED_MASK
116 };
117 if self.save_with_replace_flag {
118 byte = byte | FILE_ATTRIB_SAVE_WITH_REPLACE_MASK
119 };
120 if self.locked_flag {
121 byte = byte | FILE_ATTRIB_LOCKED_MASK
122 };
123 if self.closed_flag {
124 byte = byte | FILE_ATTRIB_CLOSED_MASK
125 };
126 byte
127 }
128
129 pub fn is_scratched(&self) -> bool {
133 self.file_type == FileType::DEL && !self.closed_flag
134 }
135}
136
137impl fmt::Display for FileAttributes {
138 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139 write!(
140 f,
141 "{}{}{}",
142 if self.closed_flag { ' ' } else { '*' },
143 self.file_type,
144 match (self.locked_flag, self.save_with_replace_flag) {
145 (true, false) => "<",
146 (false, true) => "@",
147 (true, true) => "<@",
148 (false, false) => " ",
149 },
150 )
151 }
152}
153
154impl fmt::Debug for FileAttributes {
155 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156 if !self.closed_flag {
158 f.write_char('*')?;
159 }
160 <FileType as fmt::Debug>::fmt(&self.file_type, f)?;
161 f.write_str(match (self.locked_flag, self.save_with_replace_flag) {
162 (true, false) => "<",
163 (false, true) => "@",
164 (true, true) => "<@",
165 (false, false) => "",
166 })
167 }
168}
169
170pub(super) const ENTRY_SIZE: usize = 32;
171const ENTRY_FILE_ATTRIBUTE_OFFSET: usize = 0x02;
172const ENTRY_FIRST_SECTOR_OFFSET: usize = 0x03;
173const ENTRY_FILENAME_OFFSET: usize = 0x05;
174const ENTRY_FILENAME_LENGTH: usize = 16;
175const ENTRY_EXTRA_OFFSET: usize = 0x15;
176const EXTRA_SIZE: usize = 9;
177const ENTRY_FILE_SIZE_OFFSET: usize = 0x1E;
178
179#[derive(Clone, PartialEq)]
189pub enum Extra {
190 Linear(LinearExtra),
191 Relative(RelativeExtra),
192 GEOS(GEOSExtra),
193}
194
195impl Extra {
196 pub fn default() -> Extra {
197 Extra::Linear(LinearExtra::from_bytes(&[0u8; EXTRA_SIZE]))
198 }
199
200 pub fn from_bytes(scheme: Scheme, bytes: &[u8]) -> Extra {
201 assert_eq!(bytes.len(), EXTRA_SIZE);
202 match scheme {
203 Scheme::Linear => Extra::Linear(LinearExtra::from_bytes(bytes)),
204 Scheme::Relative => Extra::Relative(RelativeExtra::from_bytes(bytes)),
205 Scheme::GEOSSequential | Scheme::GEOSVLIR => Extra::GEOS(GEOSExtra::from_bytes(bytes)),
206 }
207 }
208
209 pub fn to_bytes(&self, bytes: &mut [u8]) {
210 assert_eq!(bytes.len(), EXTRA_SIZE);
211 match self {
212 Extra::Linear(e) => e.to_bytes(bytes),
213 Extra::Relative(e) => e.to_bytes(bytes),
214 Extra::GEOS(e) => e.to_bytes(bytes),
215 }
216 }
217}
218
219impl fmt::Debug for Extra {
220 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
221 match self {
222 Extra::Linear(e) => e.fmt(f),
223 Extra::Relative(e) => e.fmt(f),
224 Extra::GEOS(e) => e.fmt(f),
225 }
226 }
227}
228
229#[derive(Clone, PartialEq)]
232pub struct LinearExtra {
233 pub unused: Vec<u8>, }
235
236impl LinearExtra {
237 pub fn from_bytes(bytes: &[u8]) -> LinearExtra {
238 assert_eq!(bytes.len(), EXTRA_SIZE);
239 LinearExtra {
240 unused: bytes.to_vec(),
241 }
242 }
243
244 pub fn to_bytes(&self, bytes: &mut [u8]) {
245 assert_eq!(bytes.len(), EXTRA_SIZE);
246 (&mut bytes[..]).copy_from_slice(&self.unused);
247 }
248}
249
250impl fmt::Debug for LinearExtra {
251 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252 write!(f, "REGULAR")
253 }
254}
255
256#[derive(Clone, PartialEq)]
259pub struct RelativeExtra {
260 pub first_side_sector: Location,
261 pub record_length: u8,
262 pub unused: Vec<u8>, }
264
265impl RelativeExtra {
266 const FIRST_SIDE_SECTOR_OFFSET: usize = 0x00;
267 const RECORD_LENGTH_OFFSET: usize = 0x02;
268 const UNUSED_OFFSET: usize = 0x03;
269
270 pub fn from_bytes(bytes: &[u8]) -> RelativeExtra {
271 assert_eq!(bytes.len(), EXTRA_SIZE);
272 RelativeExtra {
273 first_side_sector: Location::from_bytes(
274 &bytes[Self::FIRST_SIDE_SECTOR_OFFSET..Self::FIRST_SIDE_SECTOR_OFFSET + 2],
275 ),
276 record_length: bytes[Self::RECORD_LENGTH_OFFSET],
277 unused: bytes[Self::UNUSED_OFFSET..EXTRA_SIZE].to_vec(),
278 }
279 }
280
281 pub fn to_bytes(&self, bytes: &mut [u8]) {
282 assert_eq!(bytes.len(), EXTRA_SIZE);
283 bytes[Self::FIRST_SIDE_SECTOR_OFFSET] = self.first_side_sector.0;
284 bytes[Self::FIRST_SIDE_SECTOR_OFFSET + 1] = self.first_side_sector.1;
285 (&mut bytes[Self::UNUSED_OFFSET..EXTRA_SIZE]).copy_from_slice(&self.unused);
286 }
287}
288
289impl fmt::Debug for RelativeExtra {
290 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
291 write!(
292 f,
293 "REL(side={} rec_len={})",
294 self.first_side_sector, self.record_length
295 )
296 }
297}
298
299#[derive(Clone, PartialEq)]
301pub struct GEOSExtra {
302 pub info_block: Location,
303 pub structure: GEOSFileStructure,
304 pub geos_file_type: GEOSFileType,
305 pub year: u8,
306 pub month: u8,
307 pub day: u8,
308 pub hour: u8,
309 pub minute: u8,
310}
311
312impl GEOSExtra {
313 const INFO_BLOCK_OFFSET: usize = 0x00;
314 const STRUCTURE_OFFSET: usize = 0x02;
315 const GEOS_FILE_TYPE_OFFSET: usize = 0x03;
316 const YEAR_OFFSET: usize = 0x04;
317 const MONTH_OFFSET: usize = 0x05;
318 const DAY_OFFSET: usize = 0x06;
319 const HOUR_OFFSET: usize = 0x07;
320 const MINUTE_OFFSET: usize = 0x08;
321
322 pub fn from_bytes(bytes: &[u8]) -> GEOSExtra {
323 assert_eq!(bytes.len(), EXTRA_SIZE);
324 GEOSExtra {
325 info_block: Location::from_bytes(
326 &bytes[Self::INFO_BLOCK_OFFSET..Self::INFO_BLOCK_OFFSET + 2],
327 ),
328 structure: GEOSFileStructure::from_byte(bytes[Self::STRUCTURE_OFFSET]),
329 geos_file_type: GEOSFileType::from_byte(bytes[Self::GEOS_FILE_TYPE_OFFSET]),
330 year: bytes[Self::YEAR_OFFSET],
331 month: bytes[Self::MONTH_OFFSET],
332 day: bytes[Self::DAY_OFFSET],
333 hour: bytes[Self::HOUR_OFFSET],
334 minute: bytes[Self::MINUTE_OFFSET],
335 }
336 }
337
338 pub fn to_bytes(&self, bytes: &mut [u8]) {
339 assert_eq!(bytes.len(), EXTRA_SIZE);
340 self.info_block
341 .to_bytes(&mut bytes[Self::INFO_BLOCK_OFFSET..]);
342 bytes[Self::STRUCTURE_OFFSET] = self.structure.to_byte();
343 bytes[Self::GEOS_FILE_TYPE_OFFSET] = self.geos_file_type.to_byte();
344 bytes[Self::YEAR_OFFSET] = self.year;
345 bytes[Self::MONTH_OFFSET] = self.month;
346 bytes[Self::DAY_OFFSET] = self.day;
347 bytes[Self::HOUR_OFFSET] = self.hour;
348 bytes[Self::MINUTE_OFFSET] = self.minute;
349 }
350
351 #[inline]
352 fn is_entry_geos(bytes: &[u8]) -> bool {
353 (bytes[ENTRY_FILE_ATTRIBUTE_OFFSET] & 0x07) < 4
361 && (bytes[ENTRY_EXTRA_OFFSET + Self::STRUCTURE_OFFSET] != 0
362 || bytes[ENTRY_EXTRA_OFFSET + Self::GEOS_FILE_TYPE_OFFSET] != 0)
363 && bytes[ENTRY_EXTRA_OFFSET + Self::STRUCTURE_OFFSET] <= 1
364 }
365
366 #[inline]
367 fn is_entry_vlir(bytes: &[u8]) -> bool {
368 bytes[ENTRY_EXTRA_OFFSET + Self::STRUCTURE_OFFSET] == 1
369 }
370}
371
372impl fmt::Debug for GEOSExtra {
373 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
374 write!(
375 f,
376 "GEOS({}, {}, {:04}-{:02}-{:02}-{:02}:{:02})",
377 self.structure,
378 self.geos_file_type,
379 1900 + (self.year as usize),
380 self.month,
381 self.day,
382 self.hour,
383 self.minute
384 )
385 }
386}
387
388#[derive(Clone)]
390pub struct DirectoryEntry {
391 pub file_attributes: FileAttributes,
392 pub first_sector: Location,
393 pub filename: Petscii,
394 pub extra: Extra,
395 pub file_size: u16,
396 pub position: Option<Position>,
398 pub scheme: Scheme,
399 geos_border: bool,
401}
402
403impl DirectoryEntry {
404 #[allow(unused)]
405 fn from_bytes(bytes: &[u8]) -> DirectoryEntry {
406 Self::parse(bytes, None)
407 }
408
409 fn from_positioned_bytes(bytes: &[u8], position: Position) -> DirectoryEntry {
410 Self::parse(bytes, Some(position))
411 }
412
413 fn parse(bytes: &[u8], position: Option<Position>) -> DirectoryEntry {
414 assert_eq!(bytes.len(), ENTRY_SIZE);
415
416 let file_attributes = FileAttributes::from_byte(bytes[ENTRY_FILE_ATTRIBUTE_OFFSET]);
417
418 let scheme = if file_attributes.file_type == FileType::REL {
420 Scheme::Relative
421 } else if GEOSExtra::is_entry_geos(&bytes) {
422 if GEOSExtra::is_entry_vlir(&bytes) {
423 Scheme::GEOSVLIR
424 } else {
425 Scheme::GEOSSequential
426 }
427 } else {
428 Scheme::Linear
429 };
430
431 let extra = Extra::from_bytes(
433 scheme,
434 &bytes[ENTRY_EXTRA_OFFSET..ENTRY_EXTRA_OFFSET + EXTRA_SIZE],
435 );
436
437 DirectoryEntry {
438 file_attributes,
439 first_sector: Location::from_bytes(&bytes[ENTRY_FIRST_SECTOR_OFFSET..]),
440 filename: Petscii::from_padded_bytes(
441 &bytes[ENTRY_FILENAME_OFFSET..ENTRY_FILENAME_OFFSET + ENTRY_FILENAME_LENGTH],
442 PADDING_BYTE,
443 ),
444 extra,
445 file_size: ((bytes[ENTRY_FILE_SIZE_OFFSET + 1] as u16) << 8)
446 | (bytes[ENTRY_FILE_SIZE_OFFSET] as u16),
447 position,
448 scheme,
449 geos_border: false,
450 }
451 }
452
453 pub(super) fn reset(&mut self) {
455 self.file_attributes = FileAttributes::from_byte(0);
456 self.first_sector = Location::new(0, 0);
457 self.filename = Petscii::from_bytes(&[0u8; ENTRY_FILENAME_LENGTH]);
458 self.extra = Extra::default();
459 self.file_size = 0;
460 }
462
463 fn reread_from_bytes(&mut self, bytes: &[u8]) {
467 let mut entry = DirectoryEntry::parse(bytes, self.position);
468 ::std::mem::swap(self, &mut entry);
469 }
470
471 pub fn to_bytes(&self, bytes: &mut [u8]) {
475 assert_eq!(bytes.len(), ENTRY_SIZE);
476 bytes[ENTRY_FILE_ATTRIBUTE_OFFSET] = self.file_attributes.to_byte();
477 bytes[ENTRY_FIRST_SECTOR_OFFSET] = self.first_sector.0;
478 bytes[ENTRY_FIRST_SECTOR_OFFSET + 1] = self.first_sector.1;
479 self.filename
480 .write_bytes_with_padding(
481 &mut bytes[ENTRY_FILENAME_OFFSET..ENTRY_FILENAME_OFFSET + ENTRY_FILENAME_LENGTH],
482 PADDING_BYTE,
483 )
484 .unwrap();
485 self.extra
486 .to_bytes(&mut bytes[ENTRY_EXTRA_OFFSET..ENTRY_EXTRA_OFFSET + EXTRA_SIZE]);
487 bytes[ENTRY_FILE_SIZE_OFFSET] = (self.file_size & 0xFF) as u8;
488 bytes[ENTRY_FILE_SIZE_OFFSET + 1] = (self.file_size >> 8) as u8;
489 }
490
491 pub fn is_geos_border(&self) -> bool {
493 self.geos_border
494 }
495}
496
497impl PositionedData for DirectoryEntry {
498 fn position(&self) -> io::Result<Position> {
499 match self.position {
500 Some(p) => Ok(p),
501 None => Err(DiskError::Unpositioned.into()),
502 }
503 }
504
505 fn positioned_read(&mut self, buffer: &[u8]) -> io::Result<()> {
506 let position = self.position()?;
507 if buffer.len() < position.size as usize {
508 return Err(DiskError::ReadUnderrun.into());
509 }
510 self.reread_from_bytes(buffer);
511 Ok(())
512 }
513
514 fn positioned_write(&self, buffer: &mut [u8]) -> io::Result<()> {
515 let position = self.position()?;
516 if buffer.len() < position.size as usize {
517 return Err(DiskError::WriteUnderrun.into());
518 }
519 self.to_bytes(buffer);
520 Ok(())
521 }
522}
523
524impl fmt::Display for DirectoryEntry {
525 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
526 write!(
527 f,
528 "{:<4} {:18}{}",
529 self.file_size,
530 format!("\"{}\"", self.filename),
531 self.file_attributes
532 )?;
533 if f.alternate() {
534 write!(f, " {:?}", self.extra)?;
536 }
537 if self.geos_border {
538 write!(f, " (GEOS border)")?;
539 }
540 Ok(())
541 }
542}
543
544impl fmt::Debug for DirectoryEntry {
545 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
546 write!(
547 f,
548 "{},{},{:?} @ {:?}",
549 format!("\"{}\"", self.filename),
550 self.file_size,
551 self.file_attributes,
552 self.position
553 )
554 }
555}
556
557type DirectoryBlockIterator = Box<Iterator<Item = io::Result<ChainSector>>>;
560
561pub struct DirectoryIterator {
564 block_iter: DirectoryBlockIterator,
565 chunks: ::std::vec::IntoIter<Vec<u8>>,
566 position: Position,
567 error: Option<io::Error>,
568 geos_border: Option<Location>,
569 processing_geos_border: bool,
570}
571
572impl DirectoryIterator {
573 pub fn new<T: Disk>(disk: &T) -> DirectoryIterator
575 where
576 T: ?Sized,
577 {
578 let format = match disk.disk_format() {
579 Ok(f) => f,
580 Err(e) => return Self::new_error(disk, e),
581 };
582 let header = match disk.header() {
583 Ok(h) => h,
584 Err(e) => return Self::new_error(disk, e),
585 };
586 let location = format.first_directory_location();
587
588 let chain = ChainIterator::new(disk.blocks(), location);
590
591 let (block_iter, geos_border): (DirectoryBlockIterator, Option<Location>) =
593 match header.geos {
594 Some(ref geos_header) => {
595 let border_block_result = disk.blocks_ref().sector_owned(geos_header.border);
596 let tail = iter::once(border_block_result.map(|block| ChainSector {
597 data: block,
598 location: geos_header.border,
599 }));
600 (Box::new(chain.chain(tail)), Some(geos_header.border))
601 }
602 None => (Box::new(chain), None),
603 };
604
605 DirectoryIterator {
606 block_iter,
607 chunks: vec![].into_iter(), position: Position {
609 location: location,
610 offset: 0,
611 size: ENTRY_SIZE as u8,
612 },
613 error: None,
614 geos_border,
615 processing_geos_border: false,
616 }
617 }
618
619 fn new_error<T: Disk>(_disk: &T, error: io::Error) -> DirectoryIterator
624 where
625 T: ?Sized,
626 {
627 DirectoryIterator {
628 block_iter: Box::new(iter::empty()),
629 chunks: vec![].into_iter(),
630 position: Position {
631 location: Location(0, 0),
632 offset: 0,
633 size: ENTRY_SIZE as u8,
634 },
635 error: Some(error),
636 geos_border: None,
637 processing_geos_border: false,
638 }
639 }
640}
641
642impl Iterator for DirectoryIterator {
643 type Item = io::Result<DirectoryEntry>;
644
645 fn next(&mut self) -> Option<io::Result<DirectoryEntry>> {
646 let error = self.error.take();
648 if let Some(e) = error {
649 return Some(Err(e));
650 }
651
652 loop {
653 match self.chunks.next() {
654 Some(chunk) => {
655 if chunk.len() != ENTRY_SIZE {
657 continue;
658 }
659
660 let entry_position = self.position;
662 self.position.offset = self.position.offset.wrapping_add(ENTRY_SIZE as u8);
665
666 let mut entry = DirectoryEntry::from_positioned_bytes(&chunk, entry_position);
668 entry.geos_border = self.processing_geos_border;
669 if entry.file_attributes.is_scratched() {
671 continue;
672 }
673 return Some(Ok(entry));
674 }
675 None => {
676 match self.block_iter.next() {
677 Some(Ok(block)) => {
678 if let Some(border) = self.geos_border {
679 self.processing_geos_border = border == block.location;
680 }
681 let mut chunks: Vec<Vec<u8>> = vec![];
683 for chunk in block.data.chunks(ENTRY_SIZE) {
684 chunks.push(chunk.to_vec());
685 }
686 self.chunks = chunks.into_iter();
687
688 self.position.location = block.location;
689 self.position.offset = 0;
690 }
692 Some(Err(e)) => {
693 return Some(Err(e));
694 }
695 None => {
696 return None;
697 }
698 }
699 }
700 }
701 }
702 }
703}
704
705pub(super) fn next_free_directory_entry<T: Disk>(disk: &mut T) -> io::Result<DirectoryEntry>
709where
710 T: ?Sized,
711{
712 let first_sector = disk.disk_format()?.first_directory_location();
713 let mut last_sector: Location = first_sector;
714
715 {
717 let chain = ChainIterator::new(disk.blocks(), first_sector);
718 for chain_block in chain {
719 let ChainSector { data, location } = chain_block?;
720 last_sector = location;
721 let mut offset: u8 = 0;
722 for chunk in data.chunks(ENTRY_SIZE) {
723 let entry = DirectoryEntry::from_positioned_bytes(
724 chunk,
725 Position {
726 location,
727 offset,
728 size: ENTRY_SIZE as u8,
729 },
730 );
731 if entry.file_attributes.is_scratched() {
732 return Ok(entry);
733 }
734 offset = offset.wrapping_add(ENTRY_SIZE as u8);
737 }
738 }
739 }
740
741 let bam = disk.bam()?;
744 let new_sector;
745 {
746 let bam = bam.borrow();
747 new_sector = bam.next_free_block(Some(last_sector))?;
748 }
749
750 let new_entry;
752 {
753 let mut blocks = disk.blocks_ref_mut();
754 let block = blocks.sector_mut(new_sector)?;
755 block[0] = 0x00;
758 block[1] = 0xFF;
759 for offset in 2..BLOCK_SIZE {
761 block[offset] = 0;
762 }
763 new_entry = DirectoryEntry::from_positioned_bytes(
764 &block[0..ENTRY_SIZE],
765 Position {
766 location: new_sector,
767 offset: 0,
768 size: ENTRY_SIZE as u8,
769 },
770 );
771 }
772
773 {
775 let mut bam = bam.borrow_mut();
776 bam.allocate(new_sector)?;
777 }
778
779 {
781 let mut blocks = disk.blocks_ref_mut();
782 let block = blocks.sector_mut(last_sector)?;
783 block[0] = new_sector.0;
784 block[1] = new_sector.1;
785 }
786
787 Ok(new_entry)
789}
790
791pub(super) fn check_filename_validity(filename: &Petscii) -> io::Result<()> {
794 if filename.len() > ENTRY_FILENAME_LENGTH {
795 return Err(DiskError::FilenameTooLong.into());
796 }
797 Ok(())
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803 use disk::d64::D64;
804 use disk::DiskFormat;
805
806 fn get_fresh_d64() -> (DiskFormat, D64) {
807 let mut d64 = D64::open_memory(D64::geometry(false)).unwrap();
808 d64.write_format(&"test".into(), &"t1".into()).unwrap();
809 let format = d64.disk_format().unwrap().clone();
810 (format, d64)
811 }
812
813 #[test]
814 fn test_next_free_directory_entry() {
815 const MAX_NEW_ENTRIES: usize = 1000;
816 const MAX_DIRECTORY_ENTRIES: usize = 144;
817 let (_format, mut disk) = get_fresh_d64();
818 let mut disk_full: bool = false;
819 let mut entries_written: usize = 0;
820 for _ in 0..MAX_NEW_ENTRIES {
821 let mut entry = match next_free_directory_entry(&mut disk) {
822 Ok(entry) => entry,
823 Err(ref e) => match DiskError::from_io_error(&e) {
824 Some(ref e) if *e == DiskError::DiskFull => {
825 disk_full = true;
826 break;
827 }
828 Some(ref e) => panic!("error: {}", e),
829 None => break,
830 },
831 };
832
833 entry.file_attributes.file_type = FileType::PRG;
834 entry.file_attributes.closed_flag = true;
835 entry.filename = "filename".into();
836 disk.write_directory_entry(&entry).unwrap();
837 entries_written += 1;
838 }
839 assert!(disk_full);
840 assert_eq!(entries_written, MAX_DIRECTORY_ENTRIES);
841 }
842
843 #[test]
844 fn test_directory_entry() {
845 static BUFFER1: [u8; ENTRY_SIZE] = [0u8; ENTRY_SIZE];
847 let entry = DirectoryEntry::from_bytes(&BUFFER1);
848 let mut output = [0u8; ENTRY_SIZE];
849 entry.to_bytes(&mut output);
850 assert_eq!(output, BUFFER1);
851 assert_eq!(entry.file_attributes.file_type, FileType::DEL);
852 assert_eq!(entry.file_attributes.unused_bit, false);
853 assert_eq!(entry.file_attributes.save_with_replace_flag, false);
854 assert_eq!(entry.file_attributes.locked_flag, false);
855 assert_eq!(entry.file_attributes.closed_flag, false);
856 assert_eq!(entry.first_sector, Location(0, 0));
857 assert_eq!(
858 entry.filename,
859 Petscii::from_bytes(&[0; ENTRY_FILENAME_LENGTH])
860 );
861 assert_eq!(entry.extra, Extra::default());
862 assert_eq!(entry.file_size, 0);
863
864 static BUFFER2: [u8; ENTRY_SIZE] = [PADDING_BYTE; ENTRY_SIZE];
866 let entry = DirectoryEntry::from_bytes(&BUFFER2);
867 let mut output = [0u8; ENTRY_SIZE];
868 output[0] = 0xa0; output[1] = 0xa0;
870 entry.to_bytes(&mut output);
871 assert_eq!(output, BUFFER2);
872 assert_eq!(entry.file_attributes.file_type, FileType::DEL);
873 assert_eq!(entry.file_attributes.unused_bit, false);
874 assert_eq!(entry.file_attributes.save_with_replace_flag, true);
875 assert_eq!(entry.file_attributes.locked_flag, false);
876 assert_eq!(entry.file_attributes.closed_flag, true);
877 assert_eq!(entry.first_sector, Location(PADDING_BYTE, PADDING_BYTE));
878 assert_eq!(entry.filename, Petscii::from_bytes(&[0; 0]));
879 assert_eq!(
880 entry.extra,
881 Extra::Linear(LinearExtra::from_bytes(&[PADDING_BYTE; EXTRA_SIZE]))
882 );
883 assert_eq!(
884 entry.file_size,
885 ((PADDING_BYTE as u16) << 8) | (PADDING_BYTE as u16)
886 );
887
888 static BUFFER3: [u8; ENTRY_SIZE] = [0xFFu8; ENTRY_SIZE];
890 let entry = DirectoryEntry::from_bytes(&BUFFER3);
891 let mut output = [0u8; ENTRY_SIZE];
892 output[0] = 0xff; output[1] = 0xff;
894 entry.to_bytes(&mut output);
895 assert_eq!(output, BUFFER3);
896 assert_eq!(entry.file_attributes.file_type, FileType::Unknown(0x0F));
897 assert_eq!(entry.file_attributes.unused_bit, true);
898 assert_eq!(entry.file_attributes.save_with_replace_flag, true);
899 assert_eq!(entry.file_attributes.locked_flag, true);
900 assert_eq!(entry.file_attributes.closed_flag, true);
901 assert_eq!(entry.first_sector, Location(0xFF, 0xFF));
902 assert_eq!(
903 entry.filename,
904 Petscii::from_bytes(&[0xFF; ENTRY_FILENAME_LENGTH])
905 );
906 assert_eq!(
907 entry.extra,
908 Extra::Linear(LinearExtra::from_bytes(&[0xFFu8; EXTRA_SIZE]))
909 );
910 assert_eq!(entry.file_size, 0xFFFF);
911
912 static BUFFER4: [u8; ENTRY_SIZE] = [
918 0x53, 0x47, 0x82, 0x11, 0x05, 0x41, 0x53, 0x43, 0x49, 0x49, 0x20, 0x43, 0x4f, 0x44,
919 0x45, 0x53, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
920 0x00, 0x00, 0x06, 0x00,
921 ];
922 let entry = DirectoryEntry::from_bytes(&BUFFER4);
923 let mut output = [0u8; ENTRY_SIZE];
924 output[0] = BUFFER4[0]; output[1] = BUFFER4[1];
926 entry.to_bytes(&mut output);
927 assert_eq!(output, BUFFER4);
928 assert_eq!(entry.file_attributes.file_type, FileType::PRG);
929 assert_eq!(entry.file_attributes.unused_bit, false);
930 assert_eq!(entry.file_attributes.save_with_replace_flag, false);
931 assert_eq!(entry.file_attributes.locked_flag, false);
932 assert_eq!(entry.file_attributes.closed_flag, true);
933 assert_eq!(entry.first_sector, Location(0x11, 0x05));
934 assert_eq!(entry.filename, Petscii::from_str("ascii codes"));
935 assert_eq!(entry.extra, Extra::default());
936 assert_eq!(entry.file_size, 0x0006);
937 }
938}