File Coverage

blib/lib/D64/Disk/Dir/Entry.pm
Criterion Covered Total %
statement 118 138 85.5
branch 15 30 50.0
condition 4 9 44.4
subroutine 19 21 90.4
pod 13 13 100.0
total 169 211 80.0


line stmt bran cond sub pod time code
1             package D64::Disk::Dir::Entry;
2              
3             =head1 NAME
4              
5             D64::Disk::Dir::Entry - Handling individual Commodore (D64/D71/D81) disk image directory entries
6              
7             =head1 SYNOPSIS
8              
9             use D64::Disk::Dir::Entry;
10              
11             # Create a new directory entry and initialize it with 30 bytes of binary data retrieved from a D64 disk image:
12             my $entryObj = D64::Disk::Dir::Entry->new($bytes);
13              
14             # Get filename converted to ASCII string:
15             my $convert2ascii = 1;
16             my $name = $entryObj->get_name($convert2ascii);
17              
18             # Get various parameters describing detailed entry properties:
19             my $type = $entryObj->get_type();
20             my $track = $entryObj->get_track();
21             my $sector = $entryObj->get_sector();
22              
23             # Print out a single line out of entire disk directory with the contents of this particular entry to the standard output:
24             $entryObj->print_entry();
25              
26             =head1 DESCRIPTION
27              
28             This package provides a helper class for D64::Disk::Dir module, enabling user to handle individual directory entries in a higher-level object-oriented way.
29              
30             =head1 METHODS
31              
32             =cut
33              
34 4     4   22 use bytes;
  4         6  
  4         17  
35 4     4   95 use strict;
  4         5  
  4         65  
36 4     4   14 use warnings;
  4         6  
  4         167  
37              
38             our $VERSION = '0.04';
39              
40 4     4   33 use Carp qw/carp croak verbose/;
  4         8  
  4         323  
41              
42 4     4   1236 use D64::Disk::Image qw(:all);
  4         11515  
  4         4831  
43              
44             # File type names:
45             our @file_types = qw/del seq prg usr rel cbm dir ???/;
46              
47             =head2 new
48              
49             Create new C object and initialize it with 30 bytes of binary data describing each directory entry on a D64 disk image (or a physical disk):
50              
51             my $entryObj = D64::Disk::Dir::Entry->new($bytes);
52              
53             The reason for initializing object not with 32 bytes of physical data but with 30 bytes instead is that two first bytes of each entry in a directory sector always should be $00 $00 as they are unused (except for the very first entry, in which case those bytes are still directory-wide, not entry-specific).
54              
55             A valid C object is returned upon success, an undefined value otherwise.
56              
57             =cut
58              
59             sub new {
60 76     76 1 131 my $this = shift;
61 76   33     187 my $class = ref($this) || $this;
62 76         97 my $self = {};
63 76         98 bless $self, $class;
64 76         116 my $initOK = $self->_initialize(@_);
65 76 50       121 if ($initOK) {
66 76         128 return $self;
67             }
68             else {
69 0         0 return undef;
70             }
71             }
72              
73             sub _initialize {
74 76     76   81 my $self = shift;
75 76         83 my $bytes = shift;
76             # Verify valid bytes sequence:
77 76 50       121 unless (length $bytes == 30) {
78 0         0 carp 'Initializing D64::Disk::Dir::Entry object with invalid bytes sequence (exactly 30 bytes of binary data retrieved from a physical device are required to initialize it)';
79 0         0 return 0;
80             }
81 76         109 my $convertOK = $self->_bytes_to_data($bytes);
82 76 50       115 return 0 unless $convertOK;
83 76         96 return 1;
84             }
85              
86             sub _bytes_to_data {
87 76     76   83 my $self = shift;
88 76         86 my $bytes = shift;
89             # Get file type:
90 76         100 my $file_type = ord (substr $bytes, 0x00, 0x01);
91             # Get the actual filetype:
92 76         88 my $type = $file_type & 7;
93             # Get closed flag (not set produces "*", or "splat" files):
94 76         79 my $closed = $file_type & 0x80;
95             # Get locked flag (set produces ">" locked files):
96 76         78 my $locked = $file_type & 0x40;
97             # Get track/sector location of first sector of file:
98 76         84 my $track = ord (substr $bytes, 0x01, 0x01);
99 76         89 my $sector = ord (substr $bytes, 0x02, 0x01);
100             # Get 16 character filename (in PETASCII, padded with $A0):
101 76         102 my $rawname = substr $bytes, 0x03, 0x10;
102 76         135 my $name = D64::Disk::Image->name_from_rawname($rawname);
103 76         630 my ($side_track, $side_sector, $record_length) = ();
104 76 50       133 if ($file_types[$type] eq 'rel') {
105             # Get track/sector location of first side-sector block (REL file only):
106 0         0 $side_track = ord (substr $bytes, 0x13, 0x01);
107 0         0 $side_sector = ord (substr $bytes, 0x14, 0x01);
108             # Get REL file record length (REL file only, maximum value 254):
109 0         0 $record_length = ord (substr $bytes, 0x15, 0x01);
110             }
111             # Get file size in sectors, low/high byte order ($1C+$1D*256):
112 76         134 my $size = ord (substr $bytes, 0x1D, 0x01) << 8 | ord (substr $bytes, 0x1C, 0x01);
113             # Store directory entry details in a hash:
114 76         312 my $dirEntry = {
115             'TYPE' => $type,
116             'CLOSED' => $closed,
117             'LOCKED' => $locked,
118             'TRACK' => $track,
119             'SECTOR' => $sector,
120             'NAME' => $name,
121             'SIDE_TRACK' => $side_track,
122             'SIDE_SECTOR' => $side_sector,
123             'RECORD_LENGTH' => $record_length,
124             'SIZE' => $size,
125             };
126 76         121 $self->{'DETAILS'} = $dirEntry;
127 76         128 return 1;
128             }
129              
130             sub _data_to_bytes {
131 2     2   2 my $self = shift;
132 2         3 my @bytes = ();
133             # Get detailed file information stored within this object instance:
134 2         3 my $dirEntry = $self->{'DETAILS'};
135 2         3 my $type = $dirEntry->{'TYPE'};
136 2         3 my $closed = $dirEntry->{'CLOSED'};
137 2         3 my $locked = $dirEntry->{'LOCKED'};
138 2         2 my $track = $dirEntry->{'TRACK'};
139 2         3 my $sector = $dirEntry->{'SECTOR'};
140 2         2 my $name = $dirEntry->{'NAME'};
141 2   50     6 my $side_track = $dirEntry->{'SIDE_TRACK'} || 0x00;
142 2   50     6 my $side_sector = $dirEntry->{'SIDE_SECTOR'} || 0x00;
143 2   50     5 my $record_length = $dirEntry->{'RECORD_LENGTH'} || 0x00;
144 2         3 my $size = $dirEntry->{'SIZE'};
145             # Byte $00 - File type:
146 2 50       7 $bytes[0x00] = chr ($type | ($locked ? 0x40 : 0x00) | ($closed ? 0x80 : 0x00));
    50          
147             # Byte $01 - Track location of first sector of file:
148 2         3 $bytes[0x01] = chr ($track);
149             # Byte $02 - Sector location of first sector of file:
150 2         2 $bytes[0x02] = chr ($sector);
151             # Bytes $03..$12 - 16 character filename (in PETASCII, padded with $A0):
152 2         5 my $rawname = D64::Disk::Image->rawname_from_name($name);
153 2         17 my $i = 0x03;
154 2         9 foreach my $byte (split //, $rawname) {
155 32         42 $bytes[$i++] = $byte;
156             }
157             # Bytes $13..$14 - Track/Sector location of first side-sector block:
158 2         5 $bytes[0x13] = chr ($side_track);
159 2         3 $bytes[0x14] = chr ($side_sector);
160             # Byte $15 - REL file record length:
161 2         2 $bytes[0x15] = chr ($record_length);
162             # Bytes $16..$1B - Unused
163 2         4 $bytes[0x16] = chr 0x00;
164 2         3 $bytes[0x17] = chr 0x00;
165 2         2 $bytes[0x18] = chr 0x00;
166 2         3 $bytes[0x19] = chr 0x00;
167 2         2 $bytes[0x1A] = chr 0x00;
168 2         3 $bytes[0x1B] = chr 0x00;
169             # Bytes $1C..$1D - File size in sectors, low/high byte order ($1C+$1D*256):
170 2         3 $bytes[0x1C] = chr ($size & 0xFF);
171 2         4 $bytes[0x1D] = chr (($size >> 8) & 0xFF);
172 2         5 my $bytes = join '', @bytes;
173 2         5 return $bytes;
174             }
175              
176             =head2 get_type
177              
178             Get the actual filetype:
179              
180             my $type = $entryObj->get_type();
181              
182             Returns the actual filetype as a three-letter string, the possibilities here are: "del", "seq", "prg", "usr", "rel", "cbm", "dir", and "???".
183              
184             =cut
185              
186             sub get_type {
187 14     14 1 141 my $self = shift;
188 14         21 my $type = $self->{'DETAILS'}->{'TYPE'};
189 14         22 my $file_type = $file_types[$type];
190 14         31 return $file_type;
191             }
192              
193             =head2 get_closed
194              
195             Get "Closed" flag (when not set produces "*", or "splat" files):
196              
197             my $closed = $entryObj->get_closed();
198              
199             Returns true when "Closed" flag is set, and false otherwise.
200              
201             =cut
202              
203             sub get_closed {
204 9     9 1 23 my $self = shift;
205 9         14 my $closed = $self->{'DETAILS'}->{'CLOSED'};
206 9 50       21 return $closed ? 1 : 0;
207             }
208              
209             =head2 get_locked
210              
211             Get "Locked" flag (when set produces ">" locked files):
212              
213             my $locked = $entryObj->get_locked();
214              
215             Returns true when "Locked" flag is set, and false otherwise.
216              
217             =cut
218              
219             sub get_locked {
220 9     9 1 23 my $self = shift;
221 9         13 my $locked = $self->{'DETAILS'}->{'LOCKED'};
222 9 50       18 return $locked ? 1 : 0;
223             }
224              
225             =head2 get_track
226              
227             Get track location of first sector of file:
228              
229             my $track = $entryObj->get_track();
230              
231             =cut
232              
233             sub get_track {
234 1     1 1 14 my $self = shift;
235 1         2 my $track = $self->{'DETAILS'}->{'TRACK'};
236 1         3 return $track;
237             }
238              
239             =head2 get_sector
240              
241             Get sector location of first sector of file:
242              
243             my $sector = $entryObj->get_sector();
244              
245             =cut
246              
247             sub get_sector {
248 1     1 1 14 my $self = shift;
249 1         2 my $sector = $self->{'DETAILS'}->{'SECTOR'};
250 1         2 return $sector;
251             }
252              
253             =head2 get_name
254              
255             Get 16 character filename (in PETASCII, padded with $A0):
256              
257             my $convert2ascii = 0;
258             my $name = $entryObj->get_name($convert2ascii);
259              
260             Get filename converted to ASCII string:
261              
262             my $convert2ascii = 1;
263             my $name = $entryObj->get_name($convert2ascii);
264              
265             =cut
266              
267             sub get_name {
268 10     10 1 24 my $self = shift;
269 10         12 my $convert2ascii = shift;
270 10         14 my $name = $self->{'DETAILS'}->{'NAME'};
271 10 100       36 $name = D64::Disk::Image->petscii_to_ascii($name) if $convert2ascii;
272 10         156 return $name;
273             }
274              
275             =head2 get_side_track
276              
277             Get track location of first side-sector block (relative file only):
278              
279             my $side_track = $entryObj->get_side_track();
280              
281             A track location of first side-sector block is returned upon success, an undefined value otherwise.
282              
283             =cut
284              
285             sub get_side_track {
286 1     1 1 701 my $self = shift;
287 1 50       3 if ($self->get_type() ne 'rel') {
288 1         178 carp "Unable to get track location of first side-sector block (not a REL file!)";
289 1         149 return undef;
290             }
291 0         0 my $side_track = $self->{'DETAILS'}->{'SIDE_TRACK'};
292 0         0 return $side_track;
293             }
294              
295             =head2 get_side_sector
296              
297             Get sector location of first side-sector block (relative file only):
298              
299             my $side_sector = $entryObj->get_side_sector();
300              
301             A sector location of first side-sector block is returned upon success, an undefined value otherwise.
302              
303             =cut
304              
305             sub get_side_sector {
306 0     0 1 0 my $self = shift;
307 0 0       0 if ($self->get_type() ne 'rel') {
308 0         0 carp "Unable to get sector location of first side-sector block (not a REL file!)";
309 0         0 return undef;
310             }
311 0         0 my $side_sector = $self->{'DETAILS'}->{'SIDE_SECTOR'};
312 0         0 return $side_sector;
313             }
314              
315             =head2 get_record_length
316              
317             Get relative file record length (relative file only, maximum value 254):
318              
319             my $record_length = $entryObj->get_record_length();
320              
321             A relative file record length is returned upon success, an undefined value otherwise.
322              
323             =cut
324              
325             sub get_record_length {
326 0     0 1 0 my $self = shift;
327 0 0       0 if ($self->get_type() ne 'rel') {
328 0         0 carp "Unable to get relative file record length (not a REL file!)";
329 0         0 return undef;
330             }
331 0         0 my $record_length = $self->{'DETAILS'}->{'RECORD_LENGTH'};
332 0         0 return $record_length;
333             }
334              
335             =head2 get_size
336              
337             Get file size in sectors:
338              
339             my $size = $entryObj->get_size();
340              
341             The approximate filesize in bytes is <= #sectors * 254.
342              
343             =cut
344              
345             sub get_size {
346 12     12 1 139 my $self = shift;
347 12         16 my $size = $self->{'DETAILS'}->{'SIZE'};
348 12         20 return $size;
349             }
350              
351             =head2 get_bytes
352              
353             Get 30 bytes of binary data that would describe this particular directory entry on a D64 disk image (or a physical disk):
354              
355             my $bytes = $entryObj->get_bytes();
356              
357             =cut
358              
359             sub get_bytes {
360 2     2 1 39 my $self = shift;
361 2         10 my $bytes = $self->_data_to_bytes();
362 2         10 return $bytes;
363             }
364              
365             =head2 print_entry
366              
367             Print entry details to any opened file handle (the standard output by default):
368              
369             $entryObj->print_entry($fh);
370              
371             This method is subsequently invoked for each single entry while printing an entire directory with D64::Disk::Dir module.
372              
373             =cut
374              
375             sub print_entry {
376 8     8 1 641 my $self = shift;
377 8         14 my $fh = shift;
378 8 100       17 $fh = *STDOUT unless defined $fh;
379             # Get detailed file information stored within this object instance:
380 8         42 my $type = $self->get_type();
381 8 50       13 my $closed = $self->get_closed() ? ord ' ' : ord '*';
382 8 50       16 my $locked = $self->get_locked() ? ord '<' : ord ' ';
383 8         16 my $size = $self->get_size();
384             # Get filename convert to ASCII and add quotes:
385 8         16 my $name = $self->get_name(1);
386 8         23 my $quotename = sprintf "\"%s\"", $name;
387             # Print directory entry:
388 8         95 printf $fh "%-4d %-18s%c%s%c\n", $size, $quotename, $closed, $type, $locked;
389             }
390              
391             =head1 BUGS
392              
393             There are no known bugs at the moment. Please report any bugs or feature requests.
394              
395             =head1 EXPORT
396              
397             None. No method is exported into the caller's namespace either by default or explicitly.
398              
399             =head1 SEE ALSO
400              
401             L, L
402              
403             =head1 AUTHOR
404              
405             Pawel Krol, Epawelkrol@cpan.orgE.
406              
407             =head1 VERSION
408              
409             Version 0.04 (2018-11-25)
410              
411             =head1 COPYRIGHT AND LICENSE
412              
413             This module is licensed under a slightly modified BSD license, the same terms as Per Olofsson's "diskimage.c" library and L Perl package it is based on, license contents are repeated below.
414              
415             Copyright (c) 2003-2006, Per Olofsson
416             All rights reserved.
417              
418             Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
419              
420             =over
421              
422             =item *
423              
424             Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
425              
426             =item *
427              
428             Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
429              
430             =back
431              
432             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
433              
434             diskimage.c website: L
435              
436             =cut
437              
438             1;