File Coverage

blib/lib/D64/Disk/Layout/Dir.pm
Criterion Covered Total %
statement 500 515 97.0
branch 154 172 89.5
condition 85 106 80.1
subroutine 52 52 100.0
pod 16 18 88.8
total 807 863 93.5


line stmt bran cond sub pod time code
1             package D64::Disk::Layout::Dir;
2              
3             =head1 NAME
4              
5             D64::Disk::Layout::Dir - Handling entire Commodore (D64/D71/D81) disk image directories in pure Perl
6              
7             =head1 SYNOPSIS
8              
9             use D64::Disk::Layout::Dir;
10              
11             # Create an empty disk directory instance:
12             my $dir = D64::Disk::Layout::Dir->new();
13              
14             # Create a new disk directory instance providing 18 * 256 bytes of scalar data:
15             my $dir = D64::Disk::Layout::Dir->new(data => $data);
16              
17             # Fetch directory object data as a scalar of 18 * 256 bytes:
18             my $data = $dir->data();
19              
20             # Replace directory providing 18 * 256 bytes of scalar data:
21             $dir->data($data);
22              
23             # Fetch directory data as an array of up to 18 * 8 items:
24             my @items = $dir->items();
25              
26             # Replace directory providing an array of up to 18 * 8 items:
27             $dir->items(@items);
28              
29             # Get count of non-empty items stored in a disk directory:
30             my $num_items = $dir->num_items();
31              
32             # Fetch directory data as an array of 18 * sectors:
33             my @sectors = $dir->sectors();
34              
35             # Replace directory providing an array of 18 * sectors:
36             $dir->sectors(@sectors);
37              
38             # Fetch an item from a directory listing at any given position:
39             my $item = $dir->get(item => $index);
40              
41             # Fetch a list of items from a directory listing matching given PETSCII pattern:
42             my @items = $dir->get(pattern => $petscii_pattern);
43              
44             # Append an item to the end of directory listing, increasing number of files by one element:
45             $dir->push(item => $item);
46              
47             # Pop and return the last directory item, shortening a directory listing by one element:
48             my $item = $dir->pop();
49              
50             # Shift the first directory item, shortening a directory listing by one and moving everything down:
51             my $item = $dir->shift();
52              
53             # Prepend an item to the front of directory listing, and return the new number of elements:
54             my $num_items = $dir->unshift(item => $item);
55              
56             # Mark directory item designated by an offset as deleted:
57             my $num_deleted = $dir->delete(index => $index);
58              
59             # Wipe out directory item designated by an offset completely:
60             my $num_removed = $dir->remove(index => $index);
61              
62             # Add a new directory item to a directory listing:
63             my $is_success = $dir->add(item => $item);
64              
65             # Put an item to a directory listing at any given position:
66             my $is_success = $dir->put(item => $item, index => $index);
67              
68             # Print out formatted disk directory listing:
69             $dir->print();
70              
71             =head1 DESCRIPTION
72              
73             C provides a helper class for C module, enabling users to access and manipulate entire directories of D64/D71/D81 disk images in an object oriented way without the hassle of worrying about the meaning of individual bits and bytes describing each sector data on a disk directory track. The whole family of C modules has been implemented in pure Perl as an alternative to Per Olofsson's "diskimage.c" library originally written in an ANSI C.
74              
75             =head1 METHODS
76              
77             =cut
78              
79 15     15   1989261 use bytes;
  15         133  
  15         113  
80 15     15   538 use strict;
  15         36  
  15         318  
81 15     15   69 use utf8;
  15         30  
  15         150  
82 15     15   407 use warnings;
  15         37  
  15         987  
83              
84             our $VERSION = '0.04';
85              
86 15     15   123 use D64::Disk::Dir::Item qw(:types);
  15         38  
  15         2923  
87 15     15   108 use D64::Disk::Layout::Sector;
  15         30  
  15         430  
88 15     15   84 use Data::Dumper;
  15         26  
  15         986  
89 15     15   9626 use List::MoreUtils qw(uniq);
  15         207425  
  15         94  
90 15     15   17423 use Readonly;
  15         42  
  15         834  
91 15     15   111 use Text::Convert::PETSCII qw(:convert :validate);
  15         30  
  15         10697  
92              
93             require XSLoader;
94             XSLoader::load(__PACKAGE__, $VERSION);
95              
96             Readonly our $ITEMS_PER_SECTOR => 8;
97             Readonly our $TOTAL_SECTOR_COUNT => 18;
98              
99             Readonly our $ITEM_SIZE => $D64::Disk::Dir::Item::ITEM_SIZE;
100             Readonly our $SECTOR_DATA_SIZE => $D64::Disk::Layout::Sector::SECTOR_DATA_SIZE;
101              
102             # First directory track and sector:
103             Readonly our $DIRECTORY_FIRST_TRACK => 0x12;
104             Readonly our $DIRECTORY_FIRST_SECTOR => 0x01;
105              
106             Readonly our @TRACK_WRITE_ORDER => (
107             0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
108             0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
109             0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
110             );
111             Readonly our @SECTOR_WRITE_ORDER => (
112             0x01, 0x04, 0x07, 0x0a, 0x0d, 0x10,
113             0x02, 0x05, 0x08, 0x0b, 0x0e, 0x11,
114             0x03, 0x06, 0x09, 0x0c, 0x0f, 0x12,
115             );
116              
117             Readonly our $MAX_ENTRIES => $TOTAL_SECTOR_COUNT * $ITEMS_PER_SECTOR;
118              
119             =head2 new
120              
121             Create an empty disk directory instance:
122              
123             my $dir = D64::Disk::Layout::Dir->new();
124              
125             Create a new disk directory instance providing 18 * 256 bytes of scalar data:
126              
127             my $dir = D64::Disk::Layout::Dir->new(data => $data);
128              
129             Create a new disk directory instance given array with 18 * 256 bytes of data:
130              
131             my $dir = D64::Disk::Layout::Dir->new(data => \@data);
132              
133             Alternatively setup source data structure required to initialize new object using 18 * sector objects:
134              
135             my @sectors = (
136             # It needs to be a list of D64::Disk::Layout::Sector objects:
137             D64::Disk::Layout::Sector->new(data => $sector1, track => 18, sector => 1),
138             D64::Disk::Layout::Sector->new(data => $sector2, track => 18, sector => 4),
139             D64::Disk::Layout::Sector->new(data => $sector3, track => 18, sector => 7),
140             # It needs to contain as many sectors as large directory may be:
141             ...
142             );
143              
144             Create a new disk directory instance providing source sector data:
145              
146             my $dir = D64::Disk::Layout::Dir->new(sectors => \@sectors);
147              
148             Directory object may also be initialized using the list of directory item objects:
149              
150             my @items = (
151             # It needs to be a list of D64::Disk::Dir::Item objects:
152             D64::Disk::Dir::Item->new($item1),
153             D64::Disk::Dir::Item->new($item2),
154             D64::Disk::Dir::Item->new($item3),
155             # Up to the maximum number of directory entries (18 * 8 = 144):
156             ...
157             );
158              
159             Create a new disk directory instance providing list of dir items:
160              
161             my $dir = D64::Disk::Layout::Dir->new(items => \@items);
162              
163             Individual directory items are stored, accessed and manipulated as C objects.
164              
165             =cut
166              
167             sub new {
168 256     256 1 2694955 my ($this) = CORE::shift;
169 256   33     1453 my $class = ref ($this) || $this;
170 256         838 my $object = $class->_init();
171 256         704 my $self = bless $object, $class;
172 256         1042 $self->_setup(@_);
173 240         977 return $self;
174             }
175              
176             sub _init {
177 452     452   1059 my ($class) = @_;
178              
179 452         2030 my @items = map { D64::Disk::Dir::Item->new() } (1 .. $ITEMS_PER_SECTOR * $TOTAL_SECTOR_COUNT);
  65088         3628873  
180              
181 452         39512 my $object = {
182             items => \@items,
183             sector_order => [@SECTOR_WRITE_ORDER],
184             track_order => [@TRACK_WRITE_ORDER],
185             };
186              
187 452         66240 return $object;
188             }
189              
190             sub _setup {
191 256     256   837 my ($self, %args) = @_;
192              
193 256 100       1052 $self->data($args{data}) if exists $args{data};
194 245 100       1149 $self->items($args{items}) if exists $args{items};
195 244 100       773 $self->sectors($args{sectors}) if exists $args{sectors};
196              
197 240         611 return undef;
198             }
199              
200             sub _validate_data {
201 54     54   176 my ($self, $data) = @_;
202              
203 54         221 my $expected_data_size = $TOTAL_SECTOR_COUNT * $SECTOR_DATA_SIZE;
204              
205 54 100       588 unless (defined $data) {
206 1         188 die sprintf q{Unable to initialize disk directory: Undefined value of data (expected %d bytes)}, $expected_data_size;
207             }
208              
209             # Convert scalar data into an array:
210 53 100 66     266 unless (ref $data) {
211 15     15   126 no bytes;
  15         35  
  15         122  
212 13         12717 $data = [ split //, $data ];
213             }
214             elsif (ref $data ne 'ARRAY') {
215             die sprintf q{Unable to initialize disk directory: Invalid arguments given (expected %d bytes)}, $expected_data_size;
216             }
217              
218 52 100       2853 unless (scalar (@{$data}) == $expected_data_size) {
  52         242  
219 6         11 die sprintf q{Unable to initialize disk directory: Invalid amount of data (got %d bytes, but required %d)}, scalar (@{$data}), $expected_data_size;
  6         1153  
220             }
221              
222 46         160 for (my $i = 0; $i < @{$data}; $i++) {
  198214         354319  
223 198171         280816 my $byte_value = $data->[$i];
224 198171 100       316367 if (ref $byte_value) {
225 1         246 die sprintf q{Unable to initialize disk directory: Invalid data type at offset %d (%s)}, $i, ref $byte_value;
226             }
227 198170 100       308330 unless ($self->_is_valid_byte_value($byte_value)) {
228 2         10 die sprintf q{Unable to initialize disk directory: Invalid byte value at offset %d (%s)}, $i, $self->_dump($byte_value);
229             }
230             }
231              
232 43         162 return @{$data};
  43         37201  
233             }
234              
235             sub _validate_sectors {
236 59     59   186 my ($self, $sectors) = @_;
237              
238 59         245 my $expected_sectors_size = $TOTAL_SECTOR_COUNT;
239              
240 59 100       310 unless (scalar (@{$sectors}) == $expected_sectors_size) {
  59         301  
241 3         9 die sprintf q{Unable to initialize disk directory: Invalid number of sectors (got %d sectors, but required %d)}, scalar (@{$sectors}), $expected_sectors_size;
  3         703  
242             }
243              
244             # Remove duplicate sectors (objects sharing the same track/sector position):
245 56         259 my $count_removed = $self->_remove_duplicate_sectors($sectors);
246              
247 56 50       221 unless (defined $sectors) {
248 0         0 die sprintf q{Unable to initialize disk directory: Undefined value of sectors (expected %d sectors)}, $expected_sectors_size;
249             }
250              
251 56 50       225 unless (ref $sectors eq 'ARRAY') {
252 0         0 die sprintf q{Unable to initialize disk directory: Invalid arguments given (expected %d sectors)}, $expected_sectors_size;
253             }
254              
255 56 100       109 unless (scalar (@{$sectors}) == $expected_sectors_size) {
  56         195  
256 1         4 die sprintf q{Unable to initialize disk directory: Invalid number of sectors (got %d sectors, but required %d)}, scalar (@{$sectors}), $expected_sectors_size;
  1         240  
257             }
258              
259 55         157 for (my $i = 0; $i < @{$sectors}; $i++) {
  1045         2004  
260 990         1329 my $sector_value = $sectors->[$i];
261 990 50       2380 unless ($sector_value->isa('D64::Disk::Layout::Sector')) {
262 0         0 die sprintf q{Unable to initialize disk directory: Invalid sector type at offset %d (%s)}, $i, ref $sector_value;
263             }
264             }
265              
266 55         198 return $sectors;
267             }
268              
269             sub _remove_duplicate_sectors {
270 56     56   161 my ($self, $sectors) = @_;
271              
272 56         182 my $count_removed = 0;
273              
274 56         163 for (my $i = 0; $i < @{$sectors}; $i++) {
  1063         1998  
275 1007         1444 my $sector_object = $sectors->[$i];
276 1007         1915 my $track = $sector_object->track();
277 1007         11202 my $sector = $sector_object->sector();
278 1007         10831 for (my $j = $i + 1; $j < @{$sectors}; $j++) {
  9570         191641  
279 8563         11563 my $test_sector = $sectors->[$j];
280 8563 100 66     15289 if ($test_sector->track() == $track && $test_sector->sector() == $sector) {
281 1         23 splice @{$sectors}, $j, 1;
  1         4  
282 1         2 $j--;
283 1         18 $count_removed++;
284             }
285             }
286             }
287              
288 56         227 return $count_removed;
289             }
290              
291             sub _find_sector {
292 1061     1061   2348 my ($self, $sectors, $track, $sector) = @_;
293              
294 1061 50 33     3586 return unless defined $track && defined $sector;
295              
296 1061         1524 for my $sector_object (@{$sectors}) {
  1061         2374  
297 9492 100 66     194024 if ($sector_object->track() == $track && $sector_object->sector() == $sector) {
298 1061         25442 return $sector_object;
299             }
300             }
301              
302 0         0 return undef;
303             }
304              
305             sub _validate_items {
306 142     142   345 my ($self, $items) = @_;
307              
308 142         474 my $expected_items_size = $ITEMS_PER_SECTOR * $TOTAL_SECTOR_COUNT;
309              
310 142 50       2133 unless (defined $items) {
311 0         0 die sprintf q{Unable to initialize disk directory: Undefined value of items (expected up to %d items)}, $expected_items_size;
312             }
313              
314 142 50       482 unless (ref $items eq 'ARRAY') {
315 0         0 die sprintf q{Unable to initialize disk directory: Invalid arguments given (expected up to %d items)}, $expected_items_size;
316             }
317              
318 142 100       237 unless (scalar (@{$items}) <= $expected_items_size) {
  142         411  
319 1         2 die sprintf q{Unable to initialize disk directory: Invalid number of items (got %d items, but required up to %d)}, scalar (@{$items}), $expected_items_size;
  1         230  
320             }
321              
322 141         322 for (my $i = 0; $i < @{$items}; $i++) {
  1812         1086599  
323 1671         3039 my $item_value = $items->[$i];
324 1671 50       5626 unless ($item_value->isa('D64::Disk::Dir::Item')) {
325 0         0 die sprintf q{Unable to initialize disk directory: Invalid item type at offset %d (%s)}, $i, ref $item_value;
326             }
327 1671 50       3876 unless ($item_value->validate()) {
328 0         0 die sprintf q{Unable to initialize disk directory: Invalid item value at offset %d (%s)}, $i, $self->_dump($item_value);
329             }
330             }
331              
332 141         414 return undef;
333             }
334              
335             =head2 data
336              
337             Fetch directory object data as a scalar of 18 * 256 bytes:
338              
339             my $data = $dir->data();
340              
341             Fetch directory object data as an array of 18 * 256 bytes:
342              
343             my @data = $dir->data();
344              
345             Replace directory providing 18 * 256 bytes of scalar data:
346              
347             $dir->data($data);
348              
349             Replace directory given array with 18 * 256 bytes of data:
350              
351             $dir->data(@data);
352             $dir->data(\@data);
353              
354             =cut
355              
356             sub data {
357 153     153 1 571861 my ($self, @args) = @_;
358              
359 153 100       567 if (@args) {
360 54         210 my ($arg) = @args;
361 54 100       244 my $data = (scalar @args == 1) ? $arg : \@args;
362 54         277 my @data = $self->_validate_data($data);
363              
364 43         976 my $iter = $self->_get_order_from_data(\@data);
365 43         246 my ($track_order, $sector_order) = $self->_get_order($iter);
366              
367             ## TODO: Optimize code below by constructing directory "items" directly here!!!
368              
369             # Convert data into sectors and initialize object:
370 43         100 my @sectors;
371 43         184 while (my @sector_data = splice @data, 0, $SECTOR_DATA_SIZE) {
372 774         8137 my $track = CORE::shift @{$track_order};
  774         1453  
373 774         1162 my $sector = CORE::shift @{$sector_order};
  774         1332  
374 774         2809 my $sector_object = D64::Disk::Layout::Sector->new(data => \@sector_data, track => $track, sector => $sector);
375 774         2247589 CORE::push @sectors, $sector_object;
376             }
377 43         571 $self->sectors(@sectors);
378             }
379              
380 142         510 my $items = $self->{items};
381 142         454 my $num_items = $self->num_items();
382              
383             # Get directory object data as an array of bytes:
384 142         7003 my @data;
385 142         376 for (my $i = 0; $i < @{$items}; $i++) {
  20590         40116  
386 20448         42337 my @item_data = $items->[$i]->data();
387 20448 100 100     267445 if ($i % $ITEMS_PER_SECTOR == 0 && ($i + $ITEMS_PER_SECTOR) < $num_items) {
    100 66        
    100 100        
      66        
388             # Add information about the next directory track/sector data:
389 21         354 CORE::push @data, chr $self->{track_order}->[$i / $ITEMS_PER_SECTOR + 1];
390 21         208 CORE::push @data, chr $self->{sector_order}->[$i / $ITEMS_PER_SECTOR + 1];
391             }
392             elsif ($i % $ITEMS_PER_SECTOR == 0 && ($i + $ITEMS_PER_SECTOR) >= $num_items && $i < $num_items) {
393 118         3380 CORE::push @data, chr 0x00, chr 0xff;
394             }
395             elsif ($i == 0 && $num_items == 0) {
396 24         944 CORE::push @data, chr 0x00, chr 0xff;
397             }
398             else {
399 20285         219838 CORE::push @data, chr 0x00, chr 0x00;
400             }
401 20448         151246 CORE::push @data, @item_data;
402             }
403              
404 142 100       65639 return wantarray ? @data : join '', @data;
405             }
406              
407             sub _get_order_from_data {
408 43     43   162 my ($self, $data) = @_;
409              
410 43         106 my $i = 0;
411              
412             return sub {
413 55     55   211 my $index = $SECTOR_DATA_SIZE * $i++;
414              
415 55         373 my $track = ord $data->[$index + 0];
416 55         123 my $sector = ord $data->[$index + 1];
417              
418 55         164 return ($track, $sector);
419 43         556 };
420             }
421              
422             sub _get_order {
423 98     98   301 my ($self, $next) = @_;
424              
425 98         644 my @track_order = @TRACK_WRITE_ORDER;
426 98         7766 my @sector_order = @SECTOR_WRITE_ORDER;
427              
428 98         7821 $sector_order[0] = _magic_to_int($DIRECTORY_FIRST_SECTOR);
429              
430 98         1006 for (my $i = 0; $i < @sector_order; $i++) {
431 126         428 my ($track, $sector) = $next->();
432              
433 126 100       534 last if $track == 0x00;
434              
435 28         130 splice @track_order, $i + 1, 0, $track;
436 28         111 splice @sector_order, $i + 1, 0, $sector;
437             }
438              
439             # Remove duplicated track/sector order pairs:
440 98         415 for (my $i = 0; $i < @sector_order; $i++) {
441 1764         2510 my $track = $track_order[$i];
442 1764         2324 my $sector = $sector_order[$i];
443 1764         3358 for (my $j = $i + 1; $j < @sector_order; $j++) {
444 15050 100 66     46057 if ($track_order[$j] == $track && $sector_order[$j] == $sector) {
445 28         93 splice @track_order, $j, 1;
446 28         73 splice @sector_order, $j, 1;
447 28         71 $j--;
448             }
449             }
450             }
451              
452 98         481 return (\@track_order, \@sector_order);
453             }
454              
455             =head2 items
456              
457             Fetch directory object data as an array of up to 18 * 8 items:
458              
459             my @items = $dir->items();
460              
461             This method returns only non-empty directory items.
462              
463             Replace entire directory providing an array of up to 18 * 8 items:
464              
465             $dir->items(@items);
466             $dir->items(\@items);
467              
468             An entire directory object data will be replaced when calling this method. This will happen even when number of items provided as an input parameter is less than the number of non-empty items stored in an object before method was invoked.
469              
470             =cut
471              
472             sub items {
473 163     163 1 81849 my ($self, @args) = @_;
474              
475 163 100       495 if (@args) {
476 142         290 my ($arg) = @args;
477 142 50       567 my $items = (scalar @args == 1) ? (ref $arg ? $arg : [ $arg ]) : \@args;
    100          
478 142         530 $self->_validate_items($items);
479              
480 141         434 my $object = $self->_init();
481 141         29212 $self->{items} = $object->{items};
482 141         621 $self->{sector_order} = $object->{sector_order};
483 141         378 $self->{track_order} = $object->{track_order};
484              
485 141         283 my $i = 0;
486              
487 141         244 for my $item (@{$items}) {
  141         437  
488 1671         46915 $self->{items}->[$i] = $item->clone();
489 1671         3277 $i++;
490             }
491             }
492              
493 162         379 my $items = $self->{items};
494 162         476 my $num_items = $self->num_items();
495              
496 162         7641 my @items;
497              
498 162         478 for (my $i = 0; $i < $num_items; $i++) {
499 765         22993 CORE::push @items, $items->[$i]->clone();
500             }
501              
502 162         1384 return @items;
503             }
504              
505             =head2 num_items
506              
507             Get count of non-empty items stored in a disk directory:
508              
509             my $num_items = $dir->num_items();
510              
511             =cut
512              
513             sub num_items {
514 641     641 1 1790 my ($self, @args) = @_;
515              
516 641         1265 my $items = $self->{items};
517              
518 641         1246 for (my $i = 0; $i < @{$items}; $i++) {
  2850         107797  
519 2850         4331 my $item = $items->[$i];
520              
521 2850 100       6233 return $i if $item->empty();
522             }
523              
524 0         0 return scalar @{$items};
  0         0  
525             }
526              
527             sub _last_item_index {
528 49     49   87 my ($self) = @_;
529              
530 49         103 my $num_items = $self->num_items();
531              
532 49         2312 return $num_items - 1; # -1 .. ($ITEMS_PER_SECTOR * $TOTAL_SECTOR_COUNT - 1)
533             }
534              
535             =head2 sectors
536              
537             Fetch directory object data as an array of 18 * sector objects:
538              
539             my @sectors = $dir->sectors();
540              
541             Replace entire directory providing an array of 18 * sector objects:
542              
543             $dir->sectors(@sectors);
544             $dir->sectors(\@sectors);
545              
546             =cut
547              
548             sub sectors {
549 70     70 1 90197 my ($self, @args) = @_;
550              
551 70 100       268 if (@args) {
552 59         165 my ($arg) = @args;
553 59 50       275 my $sectors = (scalar @args == 1) ? (ref $arg ? $arg : [ $arg ]) : \@args;
    100          
554 59         337 $sectors = $self->_validate_sectors($sectors);
555              
556 55         311 my $object = $self->_init();
557 55         17890 $self->{items} = $object->{items};
558              
559 55         449 my $iter = $self->_get_order_from_sectors($sectors);
560 55         338 my ($track_order, $sector_order) = $self->_get_order($iter);
561              
562 55         351 $self->{sector_order} = $sector_order;
563 55         206 $self->{track_order} = $track_order;
564              
565 55         120 my $sector = $sector_order->[0];
566 55         126 my $track = $track_order->[0];
567              
568 55         113 my $index = 0;
569 55         185 while (my $sector_object = $self->_find_sector($sectors, $track, $sector)) {
570 990         2232 my @items = $self->_sector_to_items($sector_object);
571              
572 990         1717 splice @{$self->{items}}, $index * $ITEMS_PER_SECTOR, $ITEMS_PER_SECTOR, @items;
  990         2999  
573              
574 990         23325 $index++;
575              
576 990         2055 $sector = $sector_order->[$index];
577 990         1706 $track = $track_order->[$index];
578              
579 990 100 66     6280 last unless defined $track && defined $sector;
580             }
581             }
582              
583 66         239 my $items = $self->{items};
584 66         313 my $num_items = $self->num_items();
585              
586             # Get directory object data as an array of sectors:
587 66         3407 my @sectors;
588 66         347 for (my $i = 0; $i < $TOTAL_SECTOR_COUNT; $i++) {
589 1188         7781 my $track = $self->{track_order}->[$i];
590 1188         2283 my $sector = $self->{sector_order}->[$i];
591              
592 1188         1762 my @data;
593 1188         2996 for (my $j = 0; $j < $ITEMS_PER_SECTOR; $j++) {
594 9504         48836 my @item_data = $items->[$i * $ITEMS_PER_SECTOR + $j]->data();
595 9504 100 100     181832 if ($j == 0 && ($i + 1) * $ITEMS_PER_SECTOR < $num_items) {
    100 66        
      100        
596             # Add information about the next directory track/sector data:
597 38         353 CORE::push @data, chr $self->{track_order}->[$i + 1];
598 38         130 CORE::push @data, chr $self->{sector_order}->[$i + 1];
599             }
600             elsif ($j == 0 && ($i + 1) * $ITEMS_PER_SECTOR >= $num_items && $i * $ITEMS_PER_SECTOR < $num_items) {
601 57         1293 CORE::push @data, chr 0x00, chr 0xff;
602             }
603             else {
604 9409         31899 CORE::push @data, chr 0x00, chr 0x00;
605             }
606 9504         67136 CORE::push @data, @item_data;
607             }
608              
609 1188         8203 my $sector_object = D64::Disk::Layout::Sector->new(data => \@data, track => $track, sector => $sector);
610 1188         3461256 CORE::push @sectors, $sector_object;
611             }
612              
613 66         27397 return @sectors;
614             }
615              
616             =head2 num_sectors
617              
618             Get total number of allocated sectors that can be used to store disk directory data:
619              
620             my $num_sectors = $dir->num_sectors(count => 'all');
621              
622             In the case of a C disk image format, the value of C<18> is always returned, as this is a standard number of sectors designated to store disk directory data.
623              
624             Get number of currently used sectors that are used to store actual disk directory data:
625              
626             my $num_sectors = $dir->num_sectors(count => 'used');
627              
628             In this case method call returns an integer value between C<0> and C<18> (total count of sectors used to store actual data), i.a. for an empty disk directory C<0> is returned, and for a disk directory filled with more than 136 files the value of C<18> will be retrieved.
629              
630             C parameter defaults to C.
631              
632             =cut
633              
634             sub num_sectors {
635 5     5 1 50 my ($self, %args) = @_;
636              
637 5   50     22 my $mode = $args{'count'} || 'all';
638              
639 5 100       26 if ($mode eq 'all') {
    50          
640 2         9 return $TOTAL_SECTOR_COUNT;
641             }
642             elsif ($mode eq 'used') {
643 3         11 my $last_item_index = $self->_last_item_index();
644              
645 3         23 while (++$last_item_index % 8) {};
646              
647 3         16 return int ($last_item_index / 8);
648             }
649             else {
650 0         0 die sprintf q{Invalid value of "count" parameter: %s}, $mode;
651             }
652             }
653              
654             sub _get_order_from_sectors {
655 55     55   205 my ($self, $sectors) = @_;
656              
657 55         352 my $track = $DIRECTORY_FIRST_TRACK;
658 55         430 my $sector = $DIRECTORY_FIRST_SECTOR;
659              
660             return sub {
661 71     71   297 my $sector_object = $self->_find_sector($sectors, $track, $sector);
662 71 50       256 return unless $sector_object;
663              
664 71         224 my $sector_data = $sector_object->data();
665              
666 71         3923 $track = ord substr $sector_data, 0, 1;
667 71         183 $sector = ord substr $sector_data, 1, 1;
668              
669 71         217 return ($track, $sector);
670 55         1006 };
671             }
672              
673             sub _sector_to_items {
674 990     990   1673 my ($self, $sector_object) = @_;
675              
676 990         2192 my @data = $sector_object->data();
677              
678 990         69087 my @items;
679              
680 990         2699 for (my $i = 0; $i < $ITEMS_PER_SECTOR; $i++) {
681 7920         3084349 my $index = 2 + $i * ($ITEM_SIZE + 2);
682 7920         38842 my @item_data = @data[$index .. $index + $ITEM_SIZE - 1];
683 7920         80093 CORE::push @items, D64::Disk::Dir::Item->new(@item_data);
684             }
685              
686 990         451308 return @items;
687             }
688              
689             =head2 get
690              
691             Fetch an item from a directory listing at any given position:
692              
693             my $item = $dir->get(index => $index);
694              
695             C<$index> indicates an offset from the beginning of a directory listing, with count starting from C<0>. When C<$index> indicates an element beyond the number of non-empty items stored in a disk directory, an undefined value will be returned.
696              
697             Fetch a list of items from a directory listing matching given PETSCII pattern:
698              
699             use Text::Convert::PETSCII qw(:convert);
700              
701             my $pattern = ascii_to_petscii 'workstage*';
702              
703             my @items = $dir->get(pattern => $pattern);
704              
705             C is expected to be any valid PETSCII text string. Such call to this method always returns B items with filename matching given PETSCII pattern.
706              
707             =cut
708              
709             sub get {
710 17     17 1 817 my ($self, %args) = @_;
711              
712 17 50 66     70 if (exists $args{index} && exists $args{pattern}) {
713 0         0 die q{Unable to fetch an item from a directory listing: ambiguous file index/matching pattern specified (you cannot specify both parameters at the same time)};
714             }
715              
716 17 50 66     56 unless (exists $args{index} || exists $args{pattern}) {
717 0         0 die q{Unable to fetch an item from a directory listing: Missing index/pattern parameter (which element did you want to get?)};
718             }
719              
720 17         31 my $index = $args{index};
721 17         29 my $pattern = $args{pattern};
722              
723 17 100       37 if (exists $args{index}) {
724              
725 11         31 $self->_validate_index($index, 'get');
726              
727 6         13 my $num_items = $self->num_items();
728 6         274 my $items = $self->{items};
729              
730 6 100       16 if ($index < $num_items) {
731 4         39 return $items->[$index];
732             }
733             else {
734 2         13 return undef;
735             }
736             }
737             else {
738              
739 6         27 $self->_validate_pattern($pattern, 'get');
740              
741 6         14 my @items = $self->items();
742              
743 6         22 for my $item (@items) {
744 18         77 my $is_matched = $item->match_name($pattern);
745              
746 18 100       2163 $item = undef unless $is_matched;
747             }
748              
749 6         17 return grep { defined } @items;
  18         49  
750             }
751             }
752              
753             sub _validate_index {
754 63     63   156 my ($self, $index, $operation) = @_;
755              
756 63         122 my $items = $self->{items};
757 63         92 my $maximum_allowed_position = scalar (@{$items}) - 1;
  63         126  
758              
759 63 100 100     202 if (D64::Disk::Dir::Item->is_int($index) && $index >= 0x00 && $index <= $maximum_allowed_position) {
      100        
760 39         416 return undef;
761             }
762              
763 24 100       282 my $dumped_index = $self->_is_valid_number_value($index) ? $index : $self->_dump($index);
764              
765 24         132 my %description = (
766             'add' => 'Unable to add an item to a directory listing',
767             'delete' => 'Unable to mark disk directory item as deleted',
768             'get' => 'Unable to fetch an item from a directory listing',
769             'put' => 'Unable to put an item to a directory listing',
770             'remove' => 'Unable to entirely remove directory item',
771             );
772              
773 24         344 die sprintf q{%s: Invalid index parameter (got "%s", but expected an integer between 0 and %d)}, $description{$operation}, $dumped_index, $maximum_allowed_position;
774             }
775              
776             sub _validate_pattern {
777 56     56   137 my ($self, $pattern, $operation) = @_;
778              
779 56 100 100     353 if (defined ($pattern) && !ref ($pattern) && is_valid_petscii_string($pattern) && length ($pattern) > 0 && length ($pattern) <= 16) {
      100        
      100        
      100        
780 46         902 return undef;
781             }
782              
783 10 100       160 my $pattern_to_dump = ref ($pattern) ? $pattern :
    100          
784             is_printable_petscii_string($pattern) ? petscii_to_ascii($pattern) :
785             $pattern;
786              
787 10 50       773 my $dumped_pattern = !defined ($pattern) ? 'undef' :
    100          
788             $self->_is_valid_number_value($pattern) ? $pattern :
789             $self->_dump($pattern_to_dump);
790              
791 10         45 $dumped_pattern =~ s/^"(.*)"$/$1/;
792 10         42 $dumped_pattern =~ s/^'(.*)'$/$1/;
793              
794 10         42 my %description = (
795             'delete' => 'Unable to mark disk directory item as deleted',
796             'get' => 'Unable to fetch an item from a directory listing',
797             'remove' => 'Unable to entirely remove directory item',
798             );
799              
800 10         141 die sprintf q{%s: Invalid pattern parameter (got "%s", but expected a valid PETSCII text string)}, $description{$operation}, $dumped_pattern;
801             }
802              
803             sub _validate_item_object {
804 60     60   136 my ($self, $item, $operation) = @_;
805              
806 60         251 my %description = (
807             'add' => 'Unable to add an item to a directory listing',
808             'prepended' => 'Failed to validate prepended directory item',
809             'pushed' => 'Failed to validate pushed directory item',
810             'put' => 'Unable to put an item to a directory listing',
811             );
812              
813 60 100       154 unless (defined $item) {
814 2         33 die sprintf q{%s: Undefined item parameter (expected valid item object)}, $description{$operation};
815             }
816              
817 58 100 66     431 unless (ref $item && $item->isa('D64::Disk::Dir::Item')) {
818 4         61 die sprintf q{%s: Invalid item parameter (got "%s", but expected a valid item object)}, $description{$operation}, ref $item;
819             }
820              
821 50         163 return undef;
822             }
823              
824             =head2 push
825              
826             Append an item to the end of directory listing, increasing number of files by one element:
827              
828             $dir->push(item => $item);
829              
830             C<$item> is expected to be a valid C object. This method will not work when number of non-empty items stored in a disk directory has already reached its maximum.
831              
832             =cut
833              
834             sub push {
835 10     10 1 4434 my ($self, %args) = @_;
836              
837 10         34 my $num_items = $self->num_items();
838 10 100       512 if ($num_items >= $MAX_ENTRIES) {
839 1         11 warn sprintf q{Unable to push another item to a directory listing, maximum number of %d entries has been reached}, $MAX_ENTRIES;
840             }
841              
842 10         136 my $item = $args{item};
843 10         32 $self->_validate_item_object($item, 'pushed');
844              
845 7         20 my $last_item_index = $self->_last_item_index();
846              
847 7         265 $self->{items}->[$last_item_index + 1] = $item->clone();
848              
849 7         30 $num_items = $self->num_items();
850              
851 7         287 return $num_items;
852             }
853              
854             =head2 pop
855              
856             Pop and return the last non-empty directory item, shortening a directory listing by one element:
857              
858             my $item = $dir->pop();
859              
860             When there is at least one non-empty item stored in a disk directory, a C object will be returned. Otherwise return value is undefined.
861              
862             =cut
863              
864             sub pop {
865 18     18 1 102 my ($self, %args) = @_;
866              
867 18         49 my $last_item_index = $self->_last_item_index();
868              
869 18 100       53 return if $last_item_index < 0;
870              
871 14         25 my $item = $self->{items}->[$last_item_index];
872 14         53 $self->{items}->[$last_item_index] = D64::Disk::Dir::Item->new();
873              
874 14         1152 return $item->clone();
875             }
876              
877             =head2 shift
878              
879             Shift the first directory item, shortening a directory listing by one and moving everything down:
880              
881             my $item = $dir->shift();
882              
883             When there is at least one non-empty item stored in a disk directory, a C object will be returned. Otherwise return value is undefined.
884              
885             =cut
886              
887             sub shift {
888 21     21 1 125 my ($self, %args) = @_;
889              
890 21         42 my $last_item_index = $self->_last_item_index();
891              
892 21 100       53 return if $last_item_index < 0;
893              
894 17         24 my $items = $self->{items};
895              
896 17         29 my $item = CORE::shift @{$items};
  17         29  
897 17         27 CORE::push @{$items}, D64::Disk::Dir::Item->new();
  17         44  
898              
899 17         1768 return $item->clone();
900             }
901              
902             =head2 unshift
903              
904             Prepend an item to the front of directory listing, and return the new number of elements:
905              
906             my $num_items = $dir->unshift(item => $item);
907              
908             C<$item> is expected to be a valid C object. This method will not work when number of non-empty items stored in a disk directory has already reached its maximum.
909              
910             =cut
911              
912             sub unshift {
913 13     13 1 5674 my ($self, %args) = @_;
914              
915 13         38 my $num_items = $self->num_items();
916 13 100       709 if ($num_items >= $MAX_ENTRIES) {
917 1         11 warn sprintf q{Unable to prepend an item to the front of directory listing, maximum number of %d entries has been reached}, $MAX_ENTRIES;
918             }
919              
920 13         149 my $item = $args{item};
921 13         49 $self->_validate_item_object($item, 'prepended');
922              
923 10         22 my $items = $self->{items};
924 10         18 CORE::pop @{$items};
  10         17  
925 10         58 CORE::unshift @{$items}, $item->clone();
  10         496  
926              
927 10         37 $num_items = $self->num_items();
928              
929 10         534 return $num_items;
930             }
931              
932             =head2 delete
933              
934             Mark directory item designated by an offset as deleted:
935              
936             my $num_deleted = $dir->delete(index => $index);
937              
938             Mark directory item being the first one to match given PETSCII pattern as deleted:
939              
940             use Text::Convert::PETSCII qw(:convert);
941              
942             my $pattern = ascii_to_petscii 'workstage*';
943              
944             my $num_deleted = $dir->delete(pattern => $pattern, global => 0);
945              
946             Mark all directory items matching given PETSCII pattern as deleted:
947              
948             use Text::Convert::PETSCII qw(:convert);
949              
950             my $pattern = ascii_to_petscii 'workstage*';
951              
952             my $num_deleted = $dir->delete(pattern => $pattern, global => 1);
953              
954             C is expected to be any valid PETSCII text string. C parameter defaults to C<0>, hence deleting only a single file matching given criteria by default. When set to any C value, it will trigger deletion of B items with filename matching given PETSCII pattern.
955              
956             A call to this method always returns the number of successfully deleted items. When deleting an item designated by an offset of an already deleted directory item, such operation does not contribute to the count of successfully deleted items during such a particular method call. In other words, delete an item once, and you get it counted as a successfully deleted one, delete the same item again, and it will not be counted as a deleted one anymore. Of course an item remains delete in a directory listing, it just does not contribute to a value that is returned from this method's call.
957              
958             Note that this method does not remove an entry from directory layout, it only marks it as deleted. In order to wipe out an entry entirely, see description of L method.
959              
960             =cut
961              
962             sub delete {
963 35     35 1 2530 my ($self, %args) = @_;
964              
965 35 100 100     154 if (exists $args{index} && exists $args{pattern}) {
966 1         11 die q{Unable to mark directory item as deleted: ambiguous deletion index/pattern specified (you cannot specify both parameters at the same time)};
967             }
968              
969 34 100 100     146 unless (exists $args{index} || exists $args{pattern}) {
970 1         13 die q{Unable to mark directory item as deleted: Missing index/pattern parameter (which element did you want to delete?)};
971             }
972              
973 33         64 my $index = $args{index};
974 33         67 my $global = $args{global};
975 33         62 my $pattern = $args{pattern};
976              
977 33         69 my $num_items = $self->num_items();
978 33         1529 my $items = $self->{items};
979              
980 33 100       118 if (exists $args{index}) {
981              
982 10         45 $self->_validate_index($index, 'delete');
983              
984 5 100       14 if ($index < $num_items) {
985 4         13 my $item = $items->[$index];
986 4         13 my $count = $self->_delete_item($item);
987 4         13 return $count;
988             }
989             else {
990 1         5 return 0;
991             }
992             }
993             else {
994              
995 23         97 $self->_validate_pattern($pattern, 'delete');
996              
997 18         35 my $num_deleted = 0;
998              
999 18         50 for (my $i = 0; $i < $num_items; $i++) {
1000              
1001 35         796 my $item = $items->[$i];
1002              
1003 35 100       96 if ($item->match_name($pattern)) {
1004              
1005 25         3092 my $count = $self->_delete_item($item);
1006              
1007 25         41 $num_deleted += $count;
1008              
1009             # File got deleted and only one was requested to get deleted:
1010 25 100 100     112 last if $count and !$global;
1011             }
1012             }
1013              
1014 18         496 return $num_deleted;
1015             }
1016             }
1017              
1018             sub _delete_item {
1019 29     29   73 my ($self, $item) = @_;
1020              
1021 29         96 my $was_closed = $item->closed();
1022 29         338 my $was_deleted = $item->type($T_DEL);
1023              
1024 29         1331 my $is_closed = $item->closed(0);
1025 29         578 my $is_deleted = $item->type($T_DEL);
1026              
1027 29 100 66     1245 if ($was_closed == $is_closed && $was_deleted == $is_deleted) {
1028 5         15 return 0;
1029             }
1030              
1031 24         48 return 1;
1032             }
1033              
1034             =head2 remove
1035              
1036             Wipe out directory item designated by an offset entirely:
1037              
1038             my $num_removed = $dir->remove(index => $index);
1039              
1040             Wipe out directory item being the first one to match given PETSCII pattern entirely:
1041              
1042             use Text::Convert::PETSCII qw(:convert);
1043              
1044             my $pattern = ascii_to_petscii 'workstage*';
1045              
1046             my $num_removed = $dir->remove(pattern => $pattern, global => 0);
1047              
1048             Wipe out all directory items matching given PETSCII pattern entirely:
1049              
1050             use Text::Convert::PETSCII qw(:convert);
1051              
1052             my $pattern = ascii_to_petscii 'workstage*';
1053              
1054             my $num_removed = $dir->remove(pattern => $pattern, global => 1);
1055              
1056             C is expected to be any valid PETSCII text string. C parameter defaults to C<0>, hence removing only a single file matching given criteria by default. When set to any C value, it will trigger removal of B items with filename matching given PETSCII pattern.
1057              
1058             A call to this method always returns the number of successfully removed items.
1059              
1060             Note that this method removes an item from directory layout completely. It works a little bit like C, Perl's core method, removing a single element designated by an offset from an array of disk directory items, however it does not replace it with any new elements, it just shifts the remaining items, shortening a directory listing by one and moving everything from a given offset down. In order to safely mark given file as deleted without removing it from a directory listing, see description of L method.
1061              
1062             =cut
1063              
1064             sub remove {
1065 39     39 1 3190 my ($self, %args) = @_;
1066              
1067 39 100 100     145 if (exists $args{index} && exists $args{pattern}) {
1068 1         10 die q{Unable to entirely remove directory item: ambiguous removal index/pattern specified (you cannot specify both parameters at the same time)};
1069             }
1070              
1071 38 100 100     161 unless (exists $args{index} || exists $args{pattern}) {
1072 1         13 die q{Unable to entirely remove directory item: Missing index/pattern parameter (which element did you want to remove?)};
1073             }
1074              
1075 37         69 my $index = $args{index};
1076 37         52 my $global = $args{global};
1077 37         71 my $pattern = $args{pattern};
1078              
1079 37         90 my $num_items = $self->num_items();
1080 37         1690 my $items = $self->{items};
1081              
1082 37 100       86 if (exists $args{index}) {
1083              
1084 10         44 $self->_validate_index($index, 'remove');
1085              
1086 5 100       13 if ($index < $num_items) {
1087 4         15 $self->_remove_item($index);
1088 4         15 return 1;
1089             }
1090             else {
1091 1         4 return 0;
1092             }
1093             }
1094             else {
1095              
1096 27         97 $self->_validate_pattern($pattern, 'remove');
1097              
1098 22         37 my $num_deleted = 0;
1099              
1100 22         59 for (my $i = 0; $i < $num_items; $i++) {
1101              
1102 40         1212 my $item = $items->[$i];
1103              
1104 40 100       104 if ($item->match_name($pattern)) {
1105              
1106 24         3100 $self->_remove_item($i);
1107              
1108 24         41 $num_deleted += 1;
1109              
1110             # File got deleted and only one was requested to get deleted:
1111 24 100       93 last unless $global;
1112              
1113 11         18 $i--;
1114 11         51 $num_items--;
1115             }
1116             }
1117              
1118 22         781 return $num_deleted;
1119             }
1120             }
1121              
1122             sub _remove_item {
1123 28     28   57 my ($self, $index) = @_;
1124              
1125 28         50 my $items = $self->{items};
1126              
1127 28         35 splice @{$items}, $index, 1;
  28         59  
1128              
1129 28         123 CORE::push @{$items}, D64::Disk::Dir::Item->new();
  28         78  
1130              
1131 28         2476 return undef;
1132             }
1133              
1134             =head2 add
1135              
1136             Add a new directory item to a directory listing:
1137              
1138             my $is_success = $dir->add(item => $item);
1139              
1140             Add a new directory item designated by an offset:
1141              
1142             my $is_success = $dir->add(item => $item, index => $index);
1143              
1144             C<$item> is expected to be a valid C object.
1145              
1146             A call to this method returns true on a successful addition of a new entry, and false otherwise. Addition of a new item may not be possible, for instance when a maximum number of allowed disk directory elements has already been reached.
1147              
1148             C<$index> indicates an offset from the beginning of a directory listing where a new item should be added, with count starting from C<0>. Note that this method will not only insert a new item into a disk directory, it will also shift the remaining items, extending a directory listing by one and moving everything from a given offset up. When C<$index> indicates an element beyond the number of non-empty items currently stored in a disk directory, subroutine will fail and an undefined value will be returned, because such operation would not make much sense (such added entry would not be obtainable from a directory listing anyway). It will also not work when number of non-empty items stored in a disk directory has already reached its maximum. Please note that this operation will not replace a "*", or "splat" file it encounters at a given offset, rather it will always it altogether with the remaining items, unlike C method called without an C parameter specified at all, which is described in the next paragraph.
1149              
1150             When C<$index> parameter is unspecified, the method behaves as follows. It finds the first empty slot in a directory listing (that is a first directory item with a "closed" flag unset), and writes given item at that exact position. It will however not work when there is no writable slot in a directory listing available at all. Please note that this operation may or may not write given item at the end of a directory listing, since it will replace any "*", or "splat" file it encounters earlier on its way. In most cases this is a desired behaviour, that is why it is always performed as a default action.
1151              
1152             =cut
1153              
1154             sub add {
1155 24     24 1 11706 my ($self, %args) = @_;
1156              
1157 24 100       70 unless (exists $args{item}) {
1158 2         21 die q{Unable to add an item to a directory listing: Missing item parameter (what element did you want to add?)};
1159             }
1160              
1161 22         45 my $index = $args{index};
1162 22         40 my $item = $args{item};
1163              
1164 22         65 $self->_validate_item_object($item, 'add');
1165              
1166 20         48 my $num_items = $self->num_items();
1167 20         918 my $items = $self->{items};
1168              
1169 20 100       56 unless (defined $index) {
1170 8         28 my $first_empty_slot = $self->_find_first_empty_slot();
1171              
1172 8 50       21 if (defined $first_empty_slot) {
1173 8         11 splice @{$items}, $first_empty_slot, 0x01, $item->clone();
  8         269  
1174 8         45 return 1;
1175             }
1176             }
1177             else {
1178 12         52 $self->_validate_index($index, 'add');
1179              
1180 8 100       38 if ($num_items >= $MAX_ENTRIES) {
1181 1         21 warn sprintf q{Unable to add another item to a directory listing, maximum number of %d entries has been reached}, $MAX_ENTRIES;
1182             }
1183              
1184 8 100       135 if ($index <= $num_items) {
1185 7         26 splice @{$items}, $index, 0x00, $item->clone();
  7         457  
1186 7         33 CORE::pop @{$items};
  7         16  
1187 7         43 return 1;
1188             }
1189             }
1190              
1191 1         4 return 0;
1192             }
1193              
1194             sub _find_first_empty_slot {
1195 8     8   37 my ($self) = @_;
1196              
1197 8         16 my $items = $self->{items};
1198              
1199 8         10 my $index = 0;
1200              
1201 8         39 while ($index < $MAX_ENTRIES) {
1202 18         94 my $item = $items->[$index];
1203 18 100       37 if ($item->writable()) {
1204 8         218 return $index;
1205             }
1206 10         158 $index++;
1207             }
1208              
1209 0         0 return undef;
1210             }
1211              
1212             =head2 put
1213              
1214             Put an item to a directory listing at any given position:
1215              
1216             my $is_success = $dir->put(item => $item, index => $index);
1217              
1218             C<$item> is expected to be a valid C object. A call to this method returns true on a successful put of a new entry, and false otherwise.
1219              
1220             C<$index> is a required parameter that indicates an offset from the beginning of a directory listing where a new item should be put, with count starting from C<0>. Note that this method does not just insert a new item into a disk directory, it rather replaces an existing item previously stored at a given offset. When C<$index> indicates an element beyond the number of non-empty items currently stored in a disk directory, subroutine will fail and an undefined value will be returned, because such operation would not make much sense (such added entry would not be obtainable from a directory listing anyway).
1221              
1222             =cut
1223              
1224             sub put {
1225 22     22 1 10684 my ($self, %args) = @_;
1226              
1227 22 100       62 unless (exists $args{index}) {
1228 1         11 die q{Unable to put an item to a directory listing: Missing index parameter (where did you want to put it?)};
1229             }
1230 21 100       50 unless (exists $args{item}) {
1231 1         10 die q{Unable to put an item to a directory listing: Missing item parameter (what did you want to put there?)};
1232             }
1233              
1234 20         35 my $index = $args{index};
1235 20         33 my $item = $args{item};
1236              
1237 20         73 $self->_validate_index($index, 'put');
1238 15         56 $self->_validate_item_object($item, 'put');
1239              
1240 13         35 my $num_items = $self->num_items();
1241 13         576 my $items = $self->{items};
1242              
1243 13 100       32 if ($index <= $num_items) {
1244 12         404 $items->[$index] = $item->clone();
1245 12         53 return 1;
1246             }
1247              
1248 1         4 return 0;
1249             }
1250              
1251             =head2 print
1252              
1253             Print out formatted disk directory listing:
1254              
1255             $dir->print(fh => $fh, as_petscii => $as_petscii);
1256              
1257             C<$fh> defaults to the standard output. C defaults to false (meaning that ASCII characters will be printed out by default).
1258              
1259             A printout does not include header and number of blocks free lines, because information about disk title, disk ID and number of free sectors is stored in a Block Availability Map (see L for more details on how to access these bits of information).
1260              
1261             =cut
1262              
1263             sub print {
1264 5     5 1 379 my ($self, %args) = @_;
1265              
1266 5   33     29 my $fh = $args{fh} || *STDOUT;
1267 5   100     66 my $as_petscii = $args{as_petscii} || 0;
1268              
1269 5         30 $fh->binmode(':bytes');
1270 5         34 my $stdout = select $fh;
1271              
1272 5         14 my $items = $self->{items};
1273 5         17 my $num_items = $self->num_items();
1274              
1275 5         260 for (my $i = 0; $i < $num_items; $i++) {
1276 30         5881 my $item = $items->[$i];
1277 30         81 $item->print(fh => $fh, as_petscii => $as_petscii);
1278             }
1279              
1280 5         908 select $stdout;
1281              
1282 5         23 return undef;
1283             }
1284              
1285             sub is_numeric {
1286 29     29 0 63 my ($self, $var) = @_;
1287              
1288 29         96 my $is_numeric = _is_numeric($var);
1289              
1290 29         76 return $is_numeric;
1291             }
1292              
1293             sub set_iok {
1294 416     416 0 1691530 my ($self, $var) = @_;
1295              
1296 416         1237 my $var_iok = _set_iok($var);
1297              
1298 416         930 return $var_iok;
1299             }
1300              
1301             sub _is_valid_byte_value {
1302 198170     198170   303339 my ($self, $byte_value) = @_;
1303              
1304 198170 50 66     696213 if (length ($byte_value) == 1 && ord ($byte_value) >= 0x00 && ord ($byte_value) <= 0xff) {
      66        
1305 198168         420343 return 1;
1306             }
1307              
1308 2         5 return 0;
1309             }
1310              
1311             sub _is_valid_number_value {
1312 61     61   129 my ($self, $number_value) = @_;
1313              
1314 61 100 100     158 if (D64::Disk::Dir::Item->is_int($number_value) && $number_value >= 0x00 && $number_value <= 0xff) {
      100        
1315 5         82 return 1;
1316             }
1317              
1318 56         459 return 0;
1319             }
1320              
1321             sub _dump {
1322 29     29   67 my ($self, $value) = @_;
1323              
1324 29 50       63 if ($self->_is_valid_number_value($value)) {
1325 0         0 return sprintf q{$%02x}, $value;
1326             }
1327              
1328 29 100       112 if ($self->is_numeric($value)) {
1329 11         324 return sprintf q{%s}, $value;
1330             }
1331              
1332 18         146 my $dump = Data::Dumper->new([$value])->Indent(0)->Terse(1)->Deepcopy(1)->Sortkeys(1)->Dump();
1333              
1334 18         1833 return $dump;
1335             }
1336              
1337             =head1 BUGS
1338              
1339             There are no known bugs at the moment. Please report any bugs or feature requests.
1340              
1341             =head1 EXPORT
1342              
1343             None. No method is exported into the caller's namespace neither by default nor explicitly.
1344              
1345             =head1 SEE ALSO
1346              
1347             L, L, L, L, L, L.
1348              
1349             =head1 AUTHOR
1350              
1351             Pawel Krol, Epawelkrol@cpan.orgE.
1352              
1353             =head1 VERSION
1354              
1355             Version 0.04 (2021-01-16)
1356              
1357             =head1 COPYRIGHT AND LICENSE
1358              
1359             Copyright 2013-2021 by Pawel Krol Epawelkrol@cpan.orgE.
1360              
1361             This library is free open source software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available.
1362              
1363             PLEASE NOTE THAT IT COMES WITHOUT A WARRANTY OF ANY KIND!
1364              
1365             =cut
1366              
1367             1;
1368              
1369             __END__