File Coverage

blib/lib/D64/Disk/Layout/Dir.pm
Criterion Covered Total %
statement 501 516 97.0
branch 156 174 89.6
condition 90 112 80.3
subroutine 52 52 100.0
pod 16 18 88.8
total 815 872 93.4


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   1989223 use bytes;
  15         151  
  15         121  
80 15     15   489 use strict;
  15         47  
  15         317  
81 15     15   83 use utf8;
  15         26  
  15         103  
82 15     15   354 use warnings;
  15         32  
  15         1010  
83              
84             our $VERSION = '0.05';
85              
86 15     15   133 use D64::Disk::Dir::Item qw(:types);
  15         39  
  15         3058  
87 15     15   120 use D64::Disk::Layout::Sector;
  15         30  
  15         412  
88 15     15   90 use Data::Dumper;
  15         31  
  15         978  
89 15     15   10271 use List::MoreUtils qw(uniq);
  15         205914  
  15         98  
90 15     15   16626 use Readonly;
  15         46  
  15         925  
91 15     15   102 use Text::Convert::PETSCII qw(:convert :validate);
  15         30  
  15         10643  
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 257     257 1 2784862 my ($this) = CORE::shift;
169 257   33     1441 my $class = ref ($this) || $this;
170 257         838 my $object = $class->_init();
171 257         748 my $self = bless $object, $class;
172 257         1078 $self->_setup(@_);
173 241         916 return $self;
174             }
175              
176             sub _init {
177 453     453   1039 my ($class) = @_;
178              
179 453         2023 my @items = map { D64::Disk::Dir::Item->new() } (1 .. $ITEMS_PER_SECTOR * $TOTAL_SECTOR_COUNT);
  65232         3645603  
180              
181 453         40830 my $object = {
182             items => \@items,
183             sector_order => [@SECTOR_WRITE_ORDER],
184             track_order => [@TRACK_WRITE_ORDER],
185             };
186              
187 453         66109 return $object;
188             }
189              
190             sub _setup {
191 257     257   860 my ($self, %args) = @_;
192              
193 257 100       1019 $self->data($args{data}) if exists $args{data};
194 246 100       1193 $self->items($args{items}) if exists $args{items};
195 245 100       769 $self->sectors($args{sectors}) if exists $args{sectors};
196              
197 241         608 return undef;
198             }
199              
200             sub _validate_data {
201 54     54   162 my ($self, $data) = @_;
202              
203 54         230 my $expected_data_size = $TOTAL_SECTOR_COUNT * $SECTOR_DATA_SIZE;
204              
205 54 100       602 unless (defined $data) {
206 1         192 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     317 unless (ref $data) {
211 15     15   125 no bytes;
  15         33  
  15         115  
212 13         12678 $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       2574 unless (scalar (@{$data}) == $expected_data_size) {
  52         244  
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         1220  
220             }
221              
222 46         157 for (my $i = 0; $i < @{$data}; $i++) {
  198214         358691  
223 198171         284275 my $byte_value = $data->[$i];
224 198171 100       318570 if (ref $byte_value) {
225 1         271 die sprintf q{Unable to initialize disk directory: Invalid data type at offset %d (%s)}, $i, ref $byte_value;
226             }
227 198170 100       311457 unless ($self->_is_valid_byte_value($byte_value)) {
228 2         8 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         130 return @{$data};
  43         37216  
233             }
234              
235             sub _validate_sectors {
236 59     59   168 my ($self, $sectors) = @_;
237              
238 59         229 my $expected_sectors_size = $TOTAL_SECTOR_COUNT;
239              
240 59 100       303 unless (scalar (@{$sectors}) == $expected_sectors_size) {
  59         291  
241 3         8 die sprintf q{Unable to initialize disk directory: Invalid number of sectors (got %d sectors, but required %d)}, scalar (@{$sectors}), $expected_sectors_size;
  3         701  
242             }
243              
244             # Remove duplicate sectors (objects sharing the same track/sector position):
245 56         253 my $count_removed = $self->_remove_duplicate_sectors($sectors);
246              
247 56 50       182 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       216 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       127 unless (scalar (@{$sectors}) == $expected_sectors_size) {
  56         226  
256 1         3 die sprintf q{Unable to initialize disk directory: Invalid number of sectors (got %d sectors, but required %d)}, scalar (@{$sectors}), $expected_sectors_size;
  1         222  
257             }
258              
259 55         172 for (my $i = 0; $i < @{$sectors}; $i++) {
  1045         1897  
260 990         1358 my $sector_value = $sectors->[$i];
261 990 50       2438 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         247 return $sectors;
267             }
268              
269             sub _remove_duplicate_sectors {
270 56     56   226 my ($self, $sectors) = @_;
271              
272 56         167 my $count_removed = 0;
273              
274 56         153 for (my $i = 0; $i < @{$sectors}; $i++) {
  1063         2025  
275 1007         1504 my $sector_object = $sectors->[$i];
276 1007         1874 my $track = $sector_object->track();
277 1007         11356 my $sector = $sector_object->sector();
278 1007         10830 for (my $j = $i + 1; $j < @{$sectors}; $j++) {
  9570         194153  
279 8563         11711 my $test_sector = $sectors->[$j];
280 8563 100 66     15309 if ($test_sector->track() == $track && $test_sector->sector() == $sector) {
281 1         24 splice @{$sectors}, $j, 1;
  1         7  
282 1         3 $j--;
283 1         18 $count_removed++;
284             }
285             }
286             }
287              
288 56         242 return $count_removed;
289             }
290              
291             sub _find_sector {
292 1061     1061   2401 my ($self, $sectors, $track, $sector) = @_;
293              
294 1061 50 33     3556 return unless defined $track && defined $sector;
295              
296 1061         1612 for my $sector_object (@{$sectors}) {
  1061         2331  
297 9492 100 66     196164 if ($sector_object->track() == $track && $sector_object->sector() == $sector) {
298 1061         26071 return $sector_object;
299             }
300             }
301              
302 0         0 return undef;
303             }
304              
305             sub _validate_items {
306 142     142   338 my ($self, $items) = @_;
307              
308 142         484 my $expected_items_size = $ITEMS_PER_SECTOR * $TOTAL_SECTOR_COUNT;
309              
310 142 50       1311 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       487 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         424  
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         259  
320             }
321              
322 141         328 for (my $i = 0; $i < @{$items}; $i++) {
  1812         1091218  
323 1671         3009 my $item_value = $items->[$i];
324 1671 50       5809 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       3948 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         382 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 573585 my ($self, @args) = @_;
358              
359 153 100       588 if (@args) {
360 54         182 my ($arg) = @args;
361 54 100       226 my $data = (scalar @args == 1) ? $arg : \@args;
362 54         248 my @data = $self->_validate_data($data);
363              
364 43         963 my $iter = $self->_get_order_from_data(\@data);
365 43         243 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         135 my @sectors;
371 43         191 while (my @sector_data = splice @data, 0, $SECTOR_DATA_SIZE) {
372 774         8191 my $track = CORE::shift @{$track_order};
  774         1523  
373 774         1217 my $sector = CORE::shift @{$sector_order};
  774         1233  
374 774         2745 my $sector_object = D64::Disk::Layout::Sector->new(data => \@sector_data, track => $track, sector => $sector);
375 774         2264216 CORE::push @sectors, $sector_object;
376             }
377 43         621 $self->sectors(@sectors);
378             }
379              
380 142         481 my $items = $self->{items};
381 142         512 my $num_items = $self->num_items();
382              
383             # Get directory object data as an array of bytes:
384 142         7063 my @data;
385 142         417 for (my $i = 0; $i < @{$items}; $i++) {
  20590         40086  
386 20448         43225 my @item_data = $items->[$i]->data();
387 20448 100 100     270138 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         377 CORE::push @data, chr $self->{track_order}->[$i / $ITEMS_PER_SECTOR + 1];
390 21         195 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         3168 CORE::push @data, chr (0x00), chr (0xff);
394             }
395             elsif ($i == 0 && $num_items == 0) {
396 24         821 CORE::push @data, chr (0x00), chr (0xff);
397             }
398             else {
399 20285         219331 CORE::push @data, chr (0x00), chr (0x00);
400             }
401 20448         154367 CORE::push @data, @item_data;
402             }
403              
404 142 100       65915 return wantarray ? @data : join '', @data;
405             }
406              
407             sub _get_order_from_data {
408 43     43   154 my ($self, $data) = @_;
409              
410 43         96 my $i = 0;
411              
412             return sub {
413 55     55   198 my $index = $SECTOR_DATA_SIZE * $i++;
414              
415 55         387 my $track = ord $data->[$index + 0];
416 55         129 my $sector = ord $data->[$index + 1];
417              
418 55         161 return ($track, $sector);
419 43         657 };
420             }
421              
422             sub _get_order {
423 98     98   305 my ($self, $next) = @_;
424              
425 98         593 my @track_order = @TRACK_WRITE_ORDER;
426 98         7794 my @sector_order = @SECTOR_WRITE_ORDER;
427              
428 98         7597 $sector_order[0] = _magic_to_int($DIRECTORY_FIRST_SECTOR);
429              
430 98         1052 for (my $i = 0; $i < @sector_order; $i++) {
431 126         371 my ($track, $sector) = $next->();
432              
433 126 100       541 last if $track == 0x00;
434              
435 28         144 splice @track_order, $i + 1, 0, $track;
436 28         125 splice @sector_order, $i + 1, 0, $sector;
437             }
438              
439             # Remove duplicated track/sector order pairs:
440 98         469 for (my $i = 0; $i < @sector_order; $i++) {
441 1764         2524 my $track = $track_order[$i];
442 1764         2397 my $sector = $sector_order[$i];
443 1764         3425 for (my $j = $i + 1; $j < @sector_order; $j++) {
444 15050 100 66     46363 if ($track_order[$j] == $track && $sector_order[$j] == $sector) {
445 28         125 splice @track_order, $j, 1;
446 28         78 splice @sector_order, $j, 1;
447 28         80 $j--;
448             }
449             }
450             }
451              
452 98         449 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 82876 my ($self, @args) = @_;
474              
475 163 100       484 if (@args) {
476 142         302 my ($arg) = @args;
477 142 50       624 my $items = (scalar @args == 1) ? (ref $arg ? $arg : [ $arg ]) : \@args;
    100          
478 142         526 $self->_validate_items($items);
479              
480 141         455 my $object = $self->_init();
481 141         28075 $self->{items} = $object->{items};
482 141         619 $self->{sector_order} = $object->{sector_order};
483 141         373 $self->{track_order} = $object->{track_order};
484              
485 141         286 my $i = 0;
486              
487 141         286 for my $item (@{$items}) {
  141         469  
488 1671         46430 $self->{items}->[$i] = $item->clone();
489 1671         3369 $i++;
490             }
491             }
492              
493 162         397 my $items = $self->{items};
494 162         526 my $num_items = $self->num_items();
495              
496 162         7757 my @items;
497              
498 162         493 for (my $i = 0; $i < $num_items; $i++) {
499 765         23127 CORE::push @items, $items->[$i]->clone();
500             }
501              
502 162         1377 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 642     642 1 1699 my ($self, @args) = @_;
515              
516 642         1230 my $items = $self->{items};
517              
518 642         1264 for (my $i = 0; $i < @{$items}; $i++) {
  2851         107657  
519 2851         4358 my $item = $items->[$i];
520              
521 2851 100       6427 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   96 my ($self) = @_;
529              
530 49         116 my $num_items = $self->num_items();
531              
532 49         2310 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 71     71 1 94139 my ($self, @args) = @_;
550              
551 71 100       268 if (@args) {
552 59         168 my ($arg) = @args;
553 59 50       270 my $sectors = (scalar @args == 1) ? (ref $arg ? $arg : [ $arg ]) : \@args;
    100          
554 59         287 $sectors = $self->_validate_sectors($sectors);
555              
556 55         304 my $object = $self->_init();
557 55         17287 $self->{items} = $object->{items};
558              
559 55         439 my $iter = $self->_get_order_from_sectors($sectors);
560 55         323 my ($track_order, $sector_order) = $self->_get_order($iter);
561              
562 55         322 $self->{sector_order} = $sector_order;
563 55         214 $self->{track_order} = $track_order;
564              
565 55         123 my $sector = $sector_order->[0];
566 55         117 my $track = $track_order->[0];
567              
568 55         123 my $index = 0;
569 55         182 while (my $sector_object = $self->_find_sector($sectors, $track, $sector)) {
570 990         2409 my @items = $self->_sector_to_items($sector_object);
571              
572 990         1729 splice @{$self->{items}}, $index * $ITEMS_PER_SECTOR, $ITEMS_PER_SECTOR, @items;
  990         2943  
573              
574 990         23345 $index++;
575              
576 990         1999 $sector = $sector_order->[$index];
577 990         1651 $track = $track_order->[$index];
578              
579 990 100 66     6427 last unless defined $track && defined $sector;
580             }
581             }
582              
583 67         229 my $items = $self->{items};
584 67         323 my $num_items = $self->num_items();
585              
586             # Get directory object data as an array of sectors:
587 67         3415 my @sectors;
588 67         348 for (my $i = 0; $i < $TOTAL_SECTOR_COUNT; $i++) {
589 1206         7879 my $track = $self->{track_order}->[$i];
590 1206         2274 my $sector = $self->{sector_order}->[$i];
591              
592 1206         1743 my @data;
593 1206         3014 for (my $j = 0; $j < $ITEMS_PER_SECTOR; $j++) {
594 9648         50297 my @item_data = $items->[$i * $ITEMS_PER_SECTOR + $j]->data();
595 9648 100 100     190835 if ($j == 0 && ($i + 1) * $ITEMS_PER_SECTOR < $num_items) {
    100 66        
    100 100        
      100        
      66        
596             # Add information about the next directory track/sector data:
597 38         397 CORE::push @data, chr $self->{track_order}->[$i + 1];
598 38         140 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         1281 CORE::push @data, chr (0x00), chr (0xff);
602             }
603             elsif ($i == 0 && $j == 0 && $num_items == 0) {
604 10         285 CORE::push @data, chr (0x00), chr (0xff);
605             }
606             else {
607 9543         34692 CORE::push @data, chr (0x00), chr (0x00);
608             }
609 9648         68462 CORE::push @data, @item_data;
610             }
611              
612 1206         8404 my $sector_object = D64::Disk::Layout::Sector->new(data => \@data, track => $track, sector => $sector);
613 1206         3547012 CORE::push @sectors, $sector_object;
614             }
615              
616 67         26601 return @sectors;
617             }
618              
619             =head2 num_sectors
620              
621             Get total number of allocated sectors that can be used to store disk directory data:
622              
623             my $num_sectors = $dir->num_sectors(count => 'all');
624              
625             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.
626              
627             Get number of currently used sectors that are used to store actual disk directory data:
628              
629             my $num_sectors = $dir->num_sectors(count => 'used');
630              
631             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.
632              
633             C parameter defaults to C.
634              
635             =cut
636              
637             sub num_sectors {
638 5     5 1 52 my ($self, %args) = @_;
639              
640 5   50     23 my $mode = $args{'count'} || 'all';
641              
642 5 100       26 if ($mode eq 'all') {
    50          
643 2         7 return $TOTAL_SECTOR_COUNT;
644             }
645             elsif ($mode eq 'used') {
646 3         14 my $last_item_index = $self->_last_item_index();
647              
648 3         23 while (++$last_item_index % 8) {};
649              
650 3         21 return int ($last_item_index / 8);
651             }
652             else {
653 0         0 die sprintf q{Invalid value of "count" parameter: %s}, $mode;
654             }
655             }
656              
657             sub _get_order_from_sectors {
658 55     55   221 my ($self, $sectors) = @_;
659              
660 55         362 my $track = $DIRECTORY_FIRST_TRACK;
661 55         405 my $sector = $DIRECTORY_FIRST_SECTOR;
662              
663             return sub {
664 71     71   315 my $sector_object = $self->_find_sector($sectors, $track, $sector);
665 71 50       238 return unless $sector_object;
666              
667 71         225 my $sector_data = $sector_object->data();
668              
669 71         3847 $track = ord substr $sector_data, 0, 1;
670 71         172 $sector = ord substr $sector_data, 1, 1;
671              
672 71         216 return ($track, $sector);
673 55         1010 };
674             }
675              
676             sub _sector_to_items {
677 990     990   1748 my ($self, $sector_object) = @_;
678              
679 990         2332 my @data = $sector_object->data();
680              
681 990         69389 my @items;
682              
683 990         3230 for (my $i = 0; $i < $ITEMS_PER_SECTOR; $i++) {
684 7920         3095991 my $index = 2 + $i * ($ITEM_SIZE + 2);
685 7920         39296 my @item_data = @data[$index .. $index + $ITEM_SIZE - 1];
686 7920         80213 CORE::push @items, D64::Disk::Dir::Item->new(@item_data);
687             }
688              
689 990         453600 return @items;
690             }
691              
692             =head2 get
693              
694             Fetch an item from a directory listing at any given position:
695              
696             my $item = $dir->get(index => $index);
697              
698             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.
699              
700             Fetch a list of items from a directory listing matching given PETSCII pattern:
701              
702             use Text::Convert::PETSCII qw(:convert);
703              
704             my $pattern = ascii_to_petscii 'workstage*';
705              
706             my @items = $dir->get(pattern => $pattern);
707              
708             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.
709              
710             =cut
711              
712             sub get {
713 17     17 1 785 my ($self, %args) = @_;
714              
715 17 50 66     70 if (exists $args{index} && exists $args{pattern}) {
716 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)};
717             }
718              
719 17 50 66     61 unless (exists $args{index} || exists $args{pattern}) {
720 0         0 die q{Unable to fetch an item from a directory listing: Missing index/pattern parameter (which element did you want to get?)};
721             }
722              
723 17         33 my $index = $args{index};
724 17         28 my $pattern = $args{pattern};
725              
726 17 100       38 if (exists $args{index}) {
727              
728 11         34 $self->_validate_index($index, 'get');
729              
730 6         17 my $num_items = $self->num_items();
731 6         280 my $items = $self->{items};
732              
733 6 100       14 if ($index < $num_items) {
734 4         31 return $items->[$index];
735             }
736             else {
737 2         16 return undef;
738             }
739             }
740             else {
741              
742 6         22 $self->_validate_pattern($pattern, 'get');
743              
744 6         14 my @items = $self->items();
745              
746 6         17 for my $item (@items) {
747 18         50 my $is_matched = $item->match_name($pattern);
748              
749 18 100       2267 $item = undef unless $is_matched;
750             }
751              
752 6         16 return grep { defined } @items;
  18         50  
753             }
754             }
755              
756             sub _validate_index {
757 63     63   166 my ($self, $index, $operation) = @_;
758              
759 63         133 my $items = $self->{items};
760 63         103 my $maximum_allowed_position = scalar (@{$items}) - 1;
  63         148  
761              
762 63 100 100     208 if (D64::Disk::Dir::Item->is_int($index) && $index >= 0x00 && $index <= $maximum_allowed_position) {
      100        
763 39         417 return undef;
764             }
765              
766 24 100       278 my $dumped_index = $self->_is_valid_number_value($index) ? $index : $self->_dump($index);
767              
768 24         145 my %description = (
769             'add' => 'Unable to add an item to a directory listing',
770             'delete' => 'Unable to mark disk directory item as deleted',
771             'get' => 'Unable to fetch an item from a directory listing',
772             'put' => 'Unable to put an item to a directory listing',
773             'remove' => 'Unable to entirely remove directory item',
774             );
775              
776 24         399 die sprintf q{%s: Invalid index parameter (got "%s", but expected an integer between 0 and %d)}, $description{$operation}, $dumped_index, $maximum_allowed_position;
777             }
778              
779             sub _validate_pattern {
780 56     56   147 my ($self, $pattern, $operation) = @_;
781              
782 56 100 100     376 if (defined ($pattern) && !ref ($pattern) && is_valid_petscii_string($pattern) && length ($pattern) > 0 && length ($pattern) <= 16) {
      100        
      100        
      100        
783 46         916 return undef;
784             }
785              
786 10 100       178 my $pattern_to_dump = ref ($pattern) ? $pattern :
    100          
787             is_printable_petscii_string($pattern) ? petscii_to_ascii($pattern) :
788             $pattern;
789              
790 10 50       766 my $dumped_pattern = !defined ($pattern) ? 'undef' :
    100          
791             $self->_is_valid_number_value($pattern) ? $pattern :
792             $self->_dump($pattern_to_dump);
793              
794 10         52 $dumped_pattern =~ s/^"(.*)"$/$1/;
795 10         46 $dumped_pattern =~ s/^'(.*)'$/$1/;
796              
797 10         68 my %description = (
798             'delete' => 'Unable to mark disk directory item as deleted',
799             'get' => 'Unable to fetch an item from a directory listing',
800             'remove' => 'Unable to entirely remove directory item',
801             );
802              
803 10         159 die sprintf q{%s: Invalid pattern parameter (got "%s", but expected a valid PETSCII text string)}, $description{$operation}, $dumped_pattern;
804             }
805              
806             sub _validate_item_object {
807 60     60   137 my ($self, $item, $operation) = @_;
808              
809 60         285 my %description = (
810             'add' => 'Unable to add an item to a directory listing',
811             'prepended' => 'Failed to validate prepended directory item',
812             'pushed' => 'Failed to validate pushed directory item',
813             'put' => 'Unable to put an item to a directory listing',
814             );
815              
816 60 100       147 unless (defined $item) {
817 2         32 die sprintf q{%s: Undefined item parameter (expected valid item object)}, $description{$operation};
818             }
819              
820 58 100 66     437 unless (ref $item && $item->isa('D64::Disk::Dir::Item')) {
821 4         61 die sprintf q{%s: Invalid item parameter (got "%s", but expected a valid item object)}, $description{$operation}, ref $item;
822             }
823              
824 50         146 return undef;
825             }
826              
827             =head2 push
828              
829             Append an item to the end of directory listing, increasing number of files by one element:
830              
831             $dir->push(item => $item);
832              
833             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.
834              
835             =cut
836              
837             sub push {
838 10     10 1 4232 my ($self, %args) = @_;
839              
840 10         27 my $num_items = $self->num_items();
841 10 100       461 if ($num_items >= $MAX_ENTRIES) {
842 1         11 warn sprintf q{Unable to push another item to a directory listing, maximum number of %d entries has been reached}, $MAX_ENTRIES;
843             }
844              
845 10         115 my $item = $args{item};
846 10         36 $self->_validate_item_object($item, 'pushed');
847              
848 7         18 my $last_item_index = $self->_last_item_index();
849              
850 7         263 $self->{items}->[$last_item_index + 1] = $item->clone();
851              
852 7         32 $num_items = $self->num_items();
853              
854 7         292 return $num_items;
855             }
856              
857             =head2 pop
858              
859             Pop and return the last non-empty directory item, shortening a directory listing by one element:
860              
861             my $item = $dir->pop();
862              
863             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.
864              
865             =cut
866              
867             sub pop {
868 18     18 1 100 my ($self, %args) = @_;
869              
870 18         50 my $last_item_index = $self->_last_item_index();
871              
872 18 100       57 return if $last_item_index < 0;
873              
874 14         28 my $item = $self->{items}->[$last_item_index];
875 14         35 $self->{items}->[$last_item_index] = D64::Disk::Dir::Item->new();
876              
877 14         1194 return $item->clone();
878             }
879              
880             =head2 shift
881              
882             Shift the first directory item, shortening a directory listing by one and moving everything down:
883              
884             my $item = $dir->shift();
885              
886             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.
887              
888             =cut
889              
890             sub shift {
891 21     21 1 107 my ($self, %args) = @_;
892              
893 21         52 my $last_item_index = $self->_last_item_index();
894              
895 21 100       66 return if $last_item_index < 0;
896              
897 17         27 my $items = $self->{items};
898              
899 17         21 my $item = CORE::shift @{$items};
  17         29  
900 17         25 CORE::push @{$items}, D64::Disk::Dir::Item->new();
  17         49  
901              
902 17         1872 return $item->clone();
903             }
904              
905             =head2 unshift
906              
907             Prepend an item to the front of directory listing, and return the new number of elements:
908              
909             my $num_items = $dir->unshift(item => $item);
910              
911             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.
912              
913             =cut
914              
915             sub unshift {
916 13     13 1 5522 my ($self, %args) = @_;
917              
918 13         34 my $num_items = $self->num_items();
919 13 100       612 if ($num_items >= $MAX_ENTRIES) {
920 1         61 warn sprintf q{Unable to prepend an item to the front of directory listing, maximum number of %d entries has been reached}, $MAX_ENTRIES;
921             }
922              
923 13         151 my $item = $args{item};
924 13         44 $self->_validate_item_object($item, 'prepended');
925              
926 10         24 my $items = $self->{items};
927 10         16 CORE::pop @{$items};
  10         18  
928 10         43 CORE::unshift @{$items}, $item->clone();
  10         508  
929              
930 10         31 $num_items = $self->num_items();
931              
932 10         436 return $num_items;
933             }
934              
935             =head2 delete
936              
937             Mark directory item designated by an offset as deleted:
938              
939             my $num_deleted = $dir->delete(index => $index);
940              
941             Mark directory item being the first one to match given PETSCII pattern as deleted:
942              
943             use Text::Convert::PETSCII qw(:convert);
944              
945             my $pattern = ascii_to_petscii 'workstage*';
946              
947             my $num_deleted = $dir->delete(pattern => $pattern, global => 0);
948              
949             Mark all directory items matching given PETSCII pattern as deleted:
950              
951             use Text::Convert::PETSCII qw(:convert);
952              
953             my $pattern = ascii_to_petscii 'workstage*';
954              
955             my $num_deleted = $dir->delete(pattern => $pattern, global => 1);
956              
957             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.
958              
959             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.
960              
961             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.
962              
963             =cut
964              
965             sub delete {
966 35     35 1 2517 my ($self, %args) = @_;
967              
968 35 100 100     140 if (exists $args{index} && exists $args{pattern}) {
969 1         13 die q{Unable to mark directory item as deleted: ambiguous deletion index/pattern specified (you cannot specify both parameters at the same time)};
970             }
971              
972 34 100 100     153 unless (exists $args{index} || exists $args{pattern}) {
973 1         13 die q{Unable to mark directory item as deleted: Missing index/pattern parameter (which element did you want to delete?)};
974             }
975              
976 33         60 my $index = $args{index};
977 33         71 my $global = $args{global};
978 33         57 my $pattern = $args{pattern};
979              
980 33         72 my $num_items = $self->num_items();
981 33         1546 my $items = $self->{items};
982              
983 33 100       94 if (exists $args{index}) {
984              
985 10         48 $self->_validate_index($index, 'delete');
986              
987 5 100       17 if ($index < $num_items) {
988 4         9 my $item = $items->[$index];
989 4         11 my $count = $self->_delete_item($item);
990 4         15 return $count;
991             }
992             else {
993 1         3 return 0;
994             }
995             }
996             else {
997              
998 23         115 $self->_validate_pattern($pattern, 'delete');
999              
1000 18         34 my $num_deleted = 0;
1001              
1002 18         55 for (my $i = 0; $i < $num_items; $i++) {
1003              
1004 35         698 my $item = $items->[$i];
1005              
1006 35 100       91 if ($item->match_name($pattern)) {
1007              
1008 25         3022 my $count = $self->_delete_item($item);
1009              
1010 25         51 $num_deleted += $count;
1011              
1012             # File got deleted and only one was requested to get deleted:
1013 25 100 100     109 last if $count and !$global;
1014             }
1015             }
1016              
1017 18         717 return $num_deleted;
1018             }
1019             }
1020              
1021             sub _delete_item {
1022 29     29   52 my ($self, $item) = @_;
1023              
1024 29         76 my $was_closed = $item->closed();
1025 29         340 my $was_deleted = $item->type($T_DEL);
1026              
1027 29         1245 my $is_closed = $item->closed(0);
1028 29         571 my $is_deleted = $item->type($T_DEL);
1029              
1030 29 100 66     1296 if ($was_closed == $is_closed && $was_deleted == $is_deleted) {
1031 5         15 return 0;
1032             }
1033              
1034 24         62 return 1;
1035             }
1036              
1037             =head2 remove
1038              
1039             Wipe out directory item designated by an offset entirely:
1040              
1041             my $num_removed = $dir->remove(index => $index);
1042              
1043             Wipe out directory item being the first one to match given PETSCII pattern entirely:
1044              
1045             use Text::Convert::PETSCII qw(:convert);
1046              
1047             my $pattern = ascii_to_petscii 'workstage*';
1048              
1049             my $num_removed = $dir->remove(pattern => $pattern, global => 0);
1050              
1051             Wipe out all directory items matching given PETSCII pattern entirely:
1052              
1053             use Text::Convert::PETSCII qw(:convert);
1054              
1055             my $pattern = ascii_to_petscii 'workstage*';
1056              
1057             my $num_removed = $dir->remove(pattern => $pattern, global => 1);
1058              
1059             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.
1060              
1061             A call to this method always returns the number of successfully removed items.
1062              
1063             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.
1064              
1065             =cut
1066              
1067             sub remove {
1068 39     39 1 2736 my ($self, %args) = @_;
1069              
1070 39 100 100     137 if (exists $args{index} && exists $args{pattern}) {
1071 1         14 die q{Unable to entirely remove directory item: ambiguous removal index/pattern specified (you cannot specify both parameters at the same time)};
1072             }
1073              
1074 38 100 100     151 unless (exists $args{index} || exists $args{pattern}) {
1075 1         24 die q{Unable to entirely remove directory item: Missing index/pattern parameter (which element did you want to remove?)};
1076             }
1077              
1078 37         76 my $index = $args{index};
1079 37         61 my $global = $args{global};
1080 37         61 my $pattern = $args{pattern};
1081              
1082 37         93 my $num_items = $self->num_items();
1083 37         1732 my $items = $self->{items};
1084              
1085 37 100       88 if (exists $args{index}) {
1086              
1087 10         41 $self->_validate_index($index, 'remove');
1088              
1089 5 100       14 if ($index < $num_items) {
1090 4         18 $self->_remove_item($index);
1091 4         15 return 1;
1092             }
1093             else {
1094 1         4 return 0;
1095             }
1096             }
1097             else {
1098              
1099 27         123 $self->_validate_pattern($pattern, 'remove');
1100              
1101 22         41 my $num_deleted = 0;
1102              
1103 22         59 for (my $i = 0; $i < $num_items; $i++) {
1104              
1105 40         1229 my $item = $items->[$i];
1106              
1107 40 100       111 if ($item->match_name($pattern)) {
1108              
1109 24         2968 $self->_remove_item($i);
1110              
1111 24         37 $num_deleted += 1;
1112              
1113             # File got deleted and only one was requested to get deleted:
1114 24 100       95 last unless $global;
1115              
1116 11         18 $i--;
1117 11         47 $num_items--;
1118             }
1119             }
1120              
1121 22         746 return $num_deleted;
1122             }
1123             }
1124              
1125             sub _remove_item {
1126 28     28   55 my ($self, $index) = @_;
1127              
1128 28         47 my $items = $self->{items};
1129              
1130 28         46 splice @{$items}, $index, 1;
  28         60  
1131              
1132 28         125 CORE::push @{$items}, D64::Disk::Dir::Item->new();
  28         86  
1133              
1134 28         2352 return undef;
1135             }
1136              
1137             =head2 add
1138              
1139             Add a new directory item to a directory listing:
1140              
1141             my $is_success = $dir->add(item => $item);
1142              
1143             Add a new directory item designated by an offset:
1144              
1145             my $is_success = $dir->add(item => $item, index => $index);
1146              
1147             C<$item> is expected to be a valid C object.
1148              
1149             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.
1150              
1151             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.
1152              
1153             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.
1154              
1155             =cut
1156              
1157             sub add {
1158 24     24 1 11725 my ($self, %args) = @_;
1159              
1160 24 100       68 unless (exists $args{item}) {
1161 2         22 die q{Unable to add an item to a directory listing: Missing item parameter (what element did you want to add?)};
1162             }
1163              
1164 22         74 my $index = $args{index};
1165 22         36 my $item = $args{item};
1166              
1167 22         73 $self->_validate_item_object($item, 'add');
1168              
1169 20         50 my $num_items = $self->num_items();
1170 20         946 my $items = $self->{items};
1171              
1172 20 100       53 unless (defined $index) {
1173 8         23 my $first_empty_slot = $self->_find_first_empty_slot();
1174              
1175 8 50       21 if (defined $first_empty_slot) {
1176 8         15 splice @{$items}, $first_empty_slot, 0x01, $item->clone();
  8         251  
1177 8         44 return 1;
1178             }
1179             }
1180             else {
1181 12         40 $self->_validate_index($index, 'add');
1182              
1183 8 100       31 if ($num_items >= $MAX_ENTRIES) {
1184 1         13 warn sprintf q{Unable to add another item to a directory listing, maximum number of %d entries has been reached}, $MAX_ENTRIES;
1185             }
1186              
1187 8 100       122 if ($index <= $num_items) {
1188 7         13 splice @{$items}, $index, 0x00, $item->clone();
  7         425  
1189 7         15 CORE::pop @{$items};
  7         15  
1190 7         43 return 1;
1191             }
1192             }
1193              
1194 1         4 return 0;
1195             }
1196              
1197             sub _find_first_empty_slot {
1198 8     8   37 my ($self) = @_;
1199              
1200 8         17 my $items = $self->{items};
1201              
1202 8         14 my $index = 0;
1203              
1204 8         39 while ($index < $MAX_ENTRIES) {
1205 18         97 my $item = $items->[$index];
1206 18 100       39 if ($item->writable()) {
1207 8         221 return $index;
1208             }
1209 10         161 $index++;
1210             }
1211              
1212 0         0 return undef;
1213             }
1214              
1215             =head2 put
1216              
1217             Put an item to a directory listing at any given position:
1218              
1219             my $is_success = $dir->put(item => $item, index => $index);
1220              
1221             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.
1222              
1223             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).
1224              
1225             =cut
1226              
1227             sub put {
1228 22     22 1 10354 my ($self, %args) = @_;
1229              
1230 22 100       81 unless (exists $args{index}) {
1231 1         14 die q{Unable to put an item to a directory listing: Missing index parameter (where did you want to put it?)};
1232             }
1233 21 100       61 unless (exists $args{item}) {
1234 1         13 die q{Unable to put an item to a directory listing: Missing item parameter (what did you want to put there?)};
1235             }
1236              
1237 20         43 my $index = $args{index};
1238 20         38 my $item = $args{item};
1239              
1240 20         78 $self->_validate_index($index, 'put');
1241 15         60 $self->_validate_item_object($item, 'put');
1242              
1243 13         42 my $num_items = $self->num_items();
1244 13         573 my $items = $self->{items};
1245              
1246 13 100       35 if ($index <= $num_items) {
1247 12         401 $items->[$index] = $item->clone();
1248 12         56 return 1;
1249             }
1250              
1251 1         4 return 0;
1252             }
1253              
1254             =head2 print
1255              
1256             Print out formatted disk directory listing:
1257              
1258             $dir->print(fh => $fh, as_petscii => $as_petscii);
1259              
1260             C<$fh> defaults to the standard output. C defaults to false (meaning that ASCII characters will be printed out by default).
1261              
1262             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).
1263              
1264             =cut
1265              
1266             sub print {
1267 5     5 1 347 my ($self, %args) = @_;
1268              
1269 5   33     36 my $fh = $args{fh} || *STDOUT;
1270 5   100     57 my $as_petscii = $args{as_petscii} || 0;
1271              
1272 5         16 $fh->binmode(':bytes');
1273 5         29 my $stdout = select $fh;
1274              
1275 5         15 my $items = $self->{items};
1276 5         13 my $num_items = $self->num_items();
1277              
1278 5         253 for (my $i = 0; $i < $num_items; $i++) {
1279 30         5821 my $item = $items->[$i];
1280 30         76 $item->print(fh => $fh, as_petscii => $as_petscii);
1281             }
1282              
1283 5         847 select $stdout;
1284              
1285 5         17 return undef;
1286             }
1287              
1288             sub is_numeric {
1289 29     29 0 65 my ($self, $var) = @_;
1290              
1291 29         112 my $is_numeric = _is_numeric($var);
1292              
1293 29         73 return $is_numeric;
1294             }
1295              
1296             sub set_iok {
1297 416     416 0 1709363 my ($self, $var) = @_;
1298              
1299 416         1273 my $var_iok = _set_iok($var);
1300              
1301 416         1013 return $var_iok;
1302             }
1303              
1304             sub _is_valid_byte_value {
1305 198170     198170   310636 my ($self, $byte_value) = @_;
1306              
1307 198170 50 66     703063 if (length ($byte_value) == 1 && ord ($byte_value) >= 0x00 && ord ($byte_value) <= 0xff) {
      66        
1308 198168         417181 return 1;
1309             }
1310              
1311 2         7 return 0;
1312             }
1313              
1314             sub _is_valid_number_value {
1315 61     61   118 my ($self, $number_value) = @_;
1316              
1317 61 100 100     143 if (D64::Disk::Dir::Item->is_int($number_value) && $number_value >= 0x00 && $number_value <= 0xff) {
      100        
1318 5         62 return 1;
1319             }
1320              
1321 56         461 return 0;
1322             }
1323              
1324             sub _dump {
1325 29     29   70 my ($self, $value) = @_;
1326              
1327 29 50       69 if ($self->_is_valid_number_value($value)) {
1328 0         0 return sprintf q{$%02x}, $value;
1329             }
1330              
1331 29 100       104 if ($self->is_numeric($value)) {
1332 11         315 return sprintf q{%s}, $value;
1333             }
1334              
1335 18         160 my $dump = Data::Dumper->new([$value])->Indent(0)->Terse(1)->Deepcopy(1)->Sortkeys(1)->Dump();
1336              
1337 18         1956 return $dump;
1338             }
1339              
1340             =head1 BUGS
1341              
1342             There are no known bugs at the moment. Please report any bugs or feature requests.
1343              
1344             =head1 EXPORT
1345              
1346             None. No method is exported into the caller's namespace neither by default nor explicitly.
1347              
1348             =head1 SEE ALSO
1349              
1350             L, L, L, L, L, L.
1351              
1352             =head1 AUTHOR
1353              
1354             Pawel Krol, Epawelkrol@cpan.orgE.
1355              
1356             =head1 VERSION
1357              
1358             Version 0.05 (2021-01-16)
1359              
1360             =head1 COPYRIGHT AND LICENSE
1361              
1362             Copyright 2013-2021 by Pawel Krol Epawelkrol@cpan.orgE.
1363              
1364             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.
1365              
1366             PLEASE NOTE THAT IT COMES WITHOUT A WARRANTY OF ANY KIND!
1367              
1368             =cut
1369              
1370             1;
1371              
1372             __END__