File Coverage

blib/lib/Mac/Finder/DSStore.pm
Criterion Covered Total %
statement 274 307 89.2
branch 81 118 68.6
condition 23 42 54.7
subroutine 32 36 88.8
pod 4 10 40.0
total 414 513 80.7


line stmt bran cond sub pod time code
1             package Mac::Finder::DSStore;
2              
3             =head1 NAME
4              
5             Mac::Finder::DSStore - Read and write Macintosh Finder DS_Store files
6              
7             =head1 DESCRIPTION
8              
9             C provides a handful of functions for reading and
10             writing the desktop database files created by the Macintosh Finder.
11              
12             =head1 FUNCTIONS
13              
14             Many functions take a C<$store> argument which is the opened file as
15             an instance of L, or a C<$block>
16             argument which is a specific block of the file as an instance of
17             L.
18              
19             =cut
20              
21 3     3   134631 use strict;
  3         10  
  3         1565  
22 3     3   24 use warnings;
  3         8  
  3         119  
23 3     3   3158 use POSIX qw(ceil);
  3         35283  
  3         24  
24 3     3   3695 use Carp qw(croak);
  3         7  
  3         113  
25 3     3   23 use Fcntl;
  3         7  
  3         7237  
26             require Exporter;
27              
28             our($VERSION) = '1.00';
29             our(@ISA) = qw(Exporter);
30             our(@EXPORT_OK) = qw( getDSDBEntries putDSDBEntries writeDSDBEntries makeEntries );
31              
32             our($testpoint);
33              
34             =head2 @records = &Mac::Finder::DSStore::getDSDBEntries($store[, $callback])
35              
36             Retrieves the "superblock" pointed to by the C entry in the store's table
37             of contents, and traverses the B-tree it points to, returning a list of
38             the records in the tree. Alternately, you can supply a callback which will
39             be invoked for each record, and C will return an empty list.
40              
41             =cut
42              
43             sub getBTreeRootblock {
44 9     9 0 19 my($store) = @_;
45 9         150 return $store->blockByNumber($store->{toc}->{DSDB})->read(20, 'N*');
46             }
47              
48             sub getDSDBEntries {
49 6     6 1 4946 my($file, $callback) = @_;
50              
51 6         13 my(@retval);
52              
53 6 50   633   45 $callback = sub { push(@retval, $_[0]); } unless defined $callback;
  633         1222  
54              
55 6         25 my($rootnode, $height, $nrec, $nnodes, $blksize) = &getBTreeRootblock($file);
56            
57 6         35 my($n) = &traverse_btree($file, $rootnode, $callback);
58              
59 6 50       20 warn "Header node count ($nrec) not equal to actual node count ($n)"
60             if $n != $nrec;
61              
62 6         182 @retval;
63             }
64              
65             =head2 &Mac::Finder::DSStore::putDSDBEntries($store, $arrayref)
66              
67             C<$arrayref> must contain a correctly ordered list of
68             C objects. They will be evenly
69             organized into a B-tree structure and written to the C<$store>. If there is
70             an existing tree of records in the file already, it will be deallocated.
71              
72             This function does not flush the allocator's information back to the file.
73              
74             =cut
75              
76             sub putDSDBEntries {
77 5     5 1 31 my($file, $recs) = @_;
78            
79 5         9 my($tocblock, $pagesize);
80 0         0 my($pagecount, $reccount, $height);
81              
82             # Delete the old btree (but keep its superblock), or allocate a superblock.
83 5 100       29 if(defined($file->{toc}->{DSDB})) {
84 3         13 $tocblock = $file->{toc}->{DSDB};
85 3         6 my($old_rootblock);
86 3         17 ($old_rootblock, $pagesize) = (&getBTreeRootblock($file))[0, 4];
87 3         19 &freeBTreeNode($file, $old_rootblock);
88             } else {
89 2         12 $tocblock = $file->allocate( 20 );
90 2         8 $file->{toc}->{DSDB} = $tocblock;
91 2         3 $pagesize = 0x1000;
92             }
93              
94 5         15 $reccount = @$recs;
95 5         9 $pagecount = 0;
96 5         8 $height = 0;
97              
98 5         11 my(@children);
99            
100             # Partition the records into btree nodes, from the bottom of
101             # the tree working towards the root.
102 5         13 do {
103 8         12 my(@sizes);
104              
105 8 100       25 if (@children) {
106             # Interior node: child pointers interleaved with records
107 3         10 @sizes = map { 4 + $_->byteSize } @$recs;
  52         87  
108             } else {
109             # Leaf node: just a bunch of records
110 5         22 @sizes = map { $_->byteSize } @$recs;
  624         1324  
111             }
112              
113             # In addition to @sizes, each page contains a record
114             # count and a flag/childnode field (4 bytes each)
115 8         132 my(@interleaf) = &partition_sizes($pagesize - 8, @sizes);
116 8         16 my(@nchildren);
117              
118 8         15 my($next) = 0;
119 8         27 foreach my $non (@interleaf, 1+$#$recs) {
120 60         379 my($blknr) = $file->allocate($pagesize);
121 60         141 push(@nchildren, $blknr);
122 60         403 my($blk) = $file->blockByNumber($blknr, 1);
123 60 100       171 if (@children) {
124 6         256 &writeBTreeNode($blk,
125             [ @$recs[ $next .. $non-1 ] ],
126             [ @children[ $next .. $non ] ] );
127             } else {
128 54         426 &writeBTreeNode($blk,
129             [ @$recs[ $next .. $non-1 ] ]);
130             }
131 60         263839 $blk->close(1);
132 60         101 $next = $non + 1;
133 60         366 $pagecount ++;
134             }
135            
136 8         17 $height ++;
137 8         21 $recs = [ map { $recs->[$_] } @interleaf ];
  52         86  
138 8         46 @children = @nchildren;
139 8 50       83 die unless @children == 1+@$recs;
140             } while(@children > 1);
141 5 50       16 die unless 0 == @$recs;
142              
143 5         24 my($masterblock) = $file->blockByNumber($tocblock, 1);
144 5         24 $masterblock->write('NNNNN',
145             $children[0],
146             $height - 1,
147             $reccount,
148             $pagecount,
149             $pagesize);
150 5         18 $masterblock->close;
151              
152 5         17 1;
153             }
154              
155             # Given a list of sizes, break them into groups so that
156             # each group sums to no more than $max, not including the items
157             # that separate them (returned in @ejecta).
158             sub partition_sizes {
159 8     8 0 74 my($max, @sizes) = @_;
160 8         15 my($sum) = 0;
161 8         214 $sum += $_ foreach @sizes;
162              
163 8 100       41 return () if $sum <= $max;
164              
165 3         7 my(@ejecta);
166 3         32 my($bcount) = ceil($sum / $max);
167 3         7 my($target) = $sum / $bcount;
168              
169 3         7 my($n) = 0;
170 3         5 for(;;) {
171 55         63 my($bsum) = 0;
172 55   66     332 while( $n < @sizes && $bsum < $target && ($bsum + $sizes[$n]) < $max ) {
      100        
173 589         656 $bsum += $sizes[$n];
174 589         3342 $n ++;
175             }
176              
177 55 100       113 last if $n >= @sizes;
178              
179 52         76 push(@ejecta, $n);
180 52         88 $n++;
181             }
182              
183 3         74 @ejecta;
184             }
185              
186             sub traverse_btree {
187 61     61 0 117 my($store, $nodenr, $callback) = @_;
188 61         134 my($count);
189 61         219 my($values, $pointers) = &readBTreeNode( $store->blockByNumber( $nodenr ) );
190              
191 61 50       258 if ($testpoint) {
192 61         734 my($o) = Mac::Finder::DSStore::BuddyAllocator::StringBlock->new();
193             {
194             # Temporarily disable the test point so writeBTreeNode doesn't
195             # recursively invoke it
196 61         91 local($testpoint) = undef;
  61         94  
197 61         162 &writeBTreeNode($o, $values, $pointers);
198             }
199 61         294 my($actual) = $store->blockByNumber( $nodenr )->copyback;
200 61         267 my($roundtrip) = $o->copyback;
201 61         258 $actual = substr($actual, 0, length($roundtrip));
202 61         227 $testpoint->( $actual, $roundtrip );
203             }
204              
205 61         45805 $count = @$values;
206            
207 61 100       165 if (defined $pointers) {
208 6 50       28 die "Value count should be one less than pointer count"
209             unless ( @$values + 1 ) == ( @$pointers ) ;
210 6         34 $count += &traverse_btree($store, shift(@$pointers), $callback);
211 6         25 while(@$values) {
212 49         88 &{$callback}(shift @$values);
  49         100  
213 49         146 $count += &traverse_btree($store, shift(@$pointers), $callback);
214             }
215             } else {
216 55         131 &{$callback}($_) foreach @$values;
  584         976  
217             }
218              
219 61         299 $count;
220             }
221              
222             sub freeBTreeNode {
223 58     58 0 89 my($allocator, $nodeid) = @_;
224 58         179 my($block) = $allocator->blockByNumber( $nodeid );
225            
226 58 100       180 if($block->read(4, 'N') != 0) {
227 6         25 $block->seek(0);
228 6         16 my(undef, $pointers) = &readBTreeNode($block);
229 6         75 &freeBTreeNode($allocator, $_) foreach @$pointers;
230             }
231              
232 58         180 $allocator->free($nodeid);
233             }
234              
235             sub readBTreeNode {
236 127     127 0 226 my($node) = @_;
237              
238 127         495 my($pointer) = $node->read(4, 'N');
239              
240 127         427 my($count) = $node->read(4, 'N');
241 127 100       302 if ($pointer > 0) {
242 18         27 my(@pointers, @values);
243 18         54 while($count) {
244 147         475 push(@pointers, $node->read(4, 'N'));
245 147         411 push(@values, Mac::Finder::DSStore::Entry->readEntry($node));
246 147         376 $count --;
247             }
248 18         37 push(@pointers, $pointer);
249 18         86 return \@values, \@pointers;
250             } else {
251 109         131 my(@values);
252 109         264 while($count) {
253 1159         3118 push(@values, Mac::Finder::DSStore::Entry->readEntry($node));
254 1159         3124 $count --;
255             }
256 109         656 return \@values, undef;
257             }
258             }
259              
260             sub writeBTreeNode {
261 121     121 0 187 my($into, $values, $pointers) = @_;
262              
263 121 100       318 if (!$pointers) {
264             # A leaf node: no pointers, just database entries.
265 109         421 $into->write('NN', 0, scalar(@$values));
266 109         639 $_->write($into) foreach @$values;
267             } else {
268             # An internal node: interleaved pointers and values,
269             # with the final pointer moved to the front.
270 12         45 my(@vals) = @$values;
271 12         44 my(@ps) = @$pointers;
272 12 50       45 die "number of pointers must be one more than number of entries"
273             unless 1+@vals == @ps;
274 12         53 $into->write('NN', pop(@ps), scalar(@vals));
275 12         41 while(@vals) {
276 98         301 $into->write('N', shift(@ps));
277 98         261 ( shift(@vals) )->write($into);
278             }
279             }
280              
281 121 100       731 if($testpoint) {
282 60         291 my($x) = [ &readBTreeNode($into->copyback) ];
283 60         491 $testpoint->( [ $values, $pointers], $x );
284             }
285             }
286              
287             =head2 &Mac::Finder::DSStore::writeDSDBEntries($file, @entries)
288              
289             A convenience function which sorts a list of entries and writes them
290             to the specified file using C, then flushes the allocator's
291             data structures to disk.
292             C<$file> may be a filename or an open file handle.
293             The store object is returned, but you don't need to do anything else with it.
294              
295             =cut
296              
297             sub writeDSDBEntries {
298 1     1 1 1226 my($store, $recs);
299             {
300 1         3 my($file, @entries) = @_;
  1         5  
301              
302 1         13 require IO::File;
303 1         7 require Mac::Finder::DSStore::BuddyAllocator;
304            
305 1 50       7 unless(ref $file) {
306 1         2 my($filename) = $file;
307 1         11 $file = IO::File->new( $filename, Fcntl::O_RDWR | Fcntl::O_CREAT );
308 1 50       104 croak "$filename: $!, died" unless $file;
309             }
310              
311 1 50       17 if((stat($file))[7] > 32) {
312 0         0 $store = Mac::Finder::DSStore::BuddyAllocator->open($file);
313             } else {
314 1         14 $store = Mac::Finder::DSStore::BuddyAllocator->new($file);
315             }
316              
317 1         8 $recs = [ sort { $a->cmp($b) } @entries ];
  18         33  
318             }
319              
320 1         15 putDSDBEntries($store, $recs);
321 1         6 $store->writeMetaData;
322              
323 1         7 $store;
324             }
325              
326             =head2 &Mac::Finder::DSStore::makeEntries($filename, [ what => value ... ])
327              
328             C encapsulates some information about the format of individual
329             records in the DS_Store file. It returns a list of records constructed with the
330             given filename and with the information specified in the rest of its args.
331             Most args come in pairs, a name and a value, so C kind of looks
332             like it takes a hash. Some names take no value and some could take several.
333             Some produce more than one record as a result.
334              
335             See the output of the F script for an example of how
336             to use this, and check the source code for a list of the formats it accepts.
337              
338             This function might change in the future.
339              
340             =cut
341              
342             sub makeEntries {
343 11     11 1 6283 my($filename, @info) = @_;
344 11         12 my(@results);
345            
346 11         23 while(@info) {
347 16         21 my($recordType) = shift @info;
348            
349 16 100       54 if ($recordType =~ /^....$/) {
    100          
350 5         13 my($record) = Mac::Finder::DSStore::Entry->new($filename, $recordType);
351 5         34 $record->value( shift @info );
352 4         11 push(@results, $record);
353             } elsif ($recordType =~ /^(....)_hex$/) {
354 1         5 my($record) = Mac::Finder::DSStore::Entry->new($filename, $1);
355 1         7 $record->value( pack('H*', shift @info) );
356 1         4 push(@results, $record);
357             } else {
358 10         30 my($mkr) = $Mac::Finder::DSStore::Entry::{'make_'.$recordType};
359 10 100       118 croak "Don't know how to handle '$recordType'" unless $mkr;
360 9         12 push(@results, &{$mkr}($filename, $recordType, \@info));
  9         24  
361             }
362             }
363            
364 6         27 @results;
365             }
366              
367             package Mac::Finder::DSStore::Entry;
368              
369             =head1 Mac::Finder::DSStore::Entry
370              
371             This class holds the individual records from the database. Each record
372             contains a filename (in some cases, "." to refer to the containing
373             directory), a 4-character record type, and a value. The value is
374             one of a few concrete types, according to the record type.
375              
376             =cut
377              
378 3     3   37 use strict;
  3         5  
  3         134  
379 3     3   22 use warnings;
  3         7  
  3         108  
380 3     3   3438 use Encode ();
  3         42900  
  3         86  
381 3     3   28 use Carp qw(croak);
  3         6  
  3         7234  
382              
383             #
384             # Concrete types of known ids
385             #
386             our(%types) = (
387             'BKGD' => 'blob',
388             'bwsp' => 'blob',
389             'cmmt' => 'ustr',
390             'dilc' => 'blob',
391             'dscl' => 'bool',
392             'extn' => 'ustr',
393             'fwi0' => 'blob',
394             'fwsw' => 'long',
395             'fwvh' => 'shor',
396             'GRP0' => 'ustr',
397             'icgo' => 'blob',
398             'icsp' => 'blob',
399             'icvo' => 'blob',
400             'ICVO' => 'bool',
401             'icvp' => 'blob',
402             'icvt' => 'shor',
403             'Iloc' => 'blob',
404             'info' => 'blob',
405             'lg1S' => 'comp',
406             'logS' => 'comp',
407             'lssp' => 'blob',
408             'lsvo' => 'blob',
409             'LSVO' => 'bool',
410             'lsvP' => 'blob',
411             'lsvp' => 'blob',
412             'lsvt' => 'shor',
413             'moDD' => 'dutc',
414             'modD' => 'dutc',
415             'ph1S' => 'comp',
416             'phyS' => 'comp',
417             'pict' => 'blob',
418             'vSrn' => 'long',
419             'vstl' => 'type',
420             );
421              
422             =head2 $entry = ...::Entry->new($filename, $typecode)
423              
424             Creates a new entry with no value. The concrete type is inferred from the
425             record type code.
426              
427             =head2 $entry->filename
428              
429             Gets the filename of an entry.
430              
431             =head2 $entry->strucId
432              
433             Gets the record type of this entry, as a four-character string, indicating
434             what aspect of the file the entry describes.
435              
436             =head2 $entry->value([$value])
437              
438             Gets or sets the value of an entry.
439              
440             If the concrete type is C or C, the value is interpreted as a byte string;
441             if it is C, as a character string.
442             If the concrete type is C, C, C, C, or C,
443             then the value should be an integer.
444              
445             =cut
446              
447             sub new {
448 627     627   157669 my($class, $filename, $strucId, @opts) = @_;
449            
450 627 50       1603 croak "no opts supported yet, died" if @opts;
451              
452 627   33     5206 bless([ $filename, $strucId, $types{$strucId}, undef ],
453             ref $class || $class);
454             }
455              
456             sub filename {
457 0     0   0 $_[0]->[0];
458             }
459              
460             sub strucId {
461 0     0   0 $_[0]->[1];
462             }
463              
464             sub value {
465 627     627   2777 my($self, $value) = @_;
466            
467 627 50       1353 return $self->[3] unless defined $value;
468            
469 627 100       1482 croak "Can't set a value on an entry with no concrete type"
470             unless defined($self->[2]);
471            
472 626         849 my($t) = $self->[2];
473 626 100 100     2617 if($t eq 'blob' or $t eq 'ustr') {
    50 100        
    0 66        
474 623         1710 $self->[3] = '' . $value;
475             } elsif ($t eq 'bool' or $t eq 'shor' or $t eq 'long') {
476 3         5 $self->[3] = 0 + $value;
477             } elsif ($t eq 'type') {
478 0         0 $value = '' . $value;
479 0 0       0 croak "'type' values must be exactly four bytes long"
480             unless length($value) == 4;
481 0         0 $self->[3] = $value;
482             } else {
483 0         0 die "Unknown concrete type $t, died";
484             }
485              
486 626         1438 $self->[3];
487             }
488              
489             sub readEntry {
490 1306     1306   1962 my($class, $block) = @_;
491              
492 1306         1894 my($filename, $strucId, $strucType, $value);
493              
494 1306         2667 $filename = &readFilename($block);
495 1306         36810 $strucId = $block->read(4);
496 1306         3763 $strucType = $block->read(4);
497            
498 1306 100 66     10140 if ($strucType eq 'bool') {
    100 0        
    100          
    50          
    0          
    0          
499 3         10 $value = $block->read(1, 'C');
500             } elsif ($strucType eq 'long' or $strucType eq 'shor') {
501 3         11 $value = $block->read(4, 'N');
502             } elsif ($strucType eq 'blob') {
503 18         56 my($bloblen) = $block->read(4, 'N');
504 18         48 $value = $block->read($bloblen);
505             } elsif ($strucType eq 'ustr') {
506 1282         3854 my($strlen) = $block->read(4, 'N');
507 1282         4147 $value = Encode::decode('UTF-16BE', $block->read(2 * $strlen));
508             } elsif ($strucType eq 'type') {
509 0         0 $value = $block->read(4);
510             } elsif ($strucType eq 'comp' || $strucType eq 'dutc') {
511 0         0 $value = $block->read(8, 'Q>');
512             } else {
513 0         0 die "Unknown struc type '$strucType', died";
514             }
515              
516 1306   33     43634 return bless([ $filename, $strucId, $strucType, $value ],
517             ref($class) || $class);
518             }
519              
520             sub readFilename {
521 1306     1306   1683 my($block) = @_;
522              
523 1306         3767 my($flen) = $block->read(4, 'N');
524 1306         4255 my($utf16be) = $block->read(2 * $flen);
525            
526 1306         5480 return Encode::decode('UTF-16BE', $utf16be, Encode::FB_CROAK);
527             }
528              
529             sub byteSize {
530 676     676   703 my($filename, $strucId, $strucType, $value) = @{$_[0]};
  676         1504  
531 676         797 my($size);
532              
533             # TODO: We're assuming that the filename is completely normal
534             # basic-multilingual-plane characters, and doesn't need to be de/re-
535             # composed or anything.
536 676         877 $size = length($filename) * 2 + 12;
537             # 12 bytes: 4 each for filename length, struct id, and struct type
538              
539 676 100 66     5040 if ($strucType eq 'long' or $strucType eq 'shor' or $strucType eq 'type') {
    100 66        
    100 0        
    50          
    0          
540 1         2 $size += 4;
541             } elsif ($strucType eq 'bool') {
542 1         3 $size += 1;
543             } elsif ($strucType eq 'blob') {
544 6         12 $size += 4 + length($value);
545             } elsif ($strucType eq 'ustr') {
546 668         1276 $size += 4 + 2 * length($value);
547             } elsif ($strucType eq 'comp' or $strucType eq 'dutc') {
548 0         0 $size += 8;
549             } else {
550 0         0 die "Unknown struc type '$strucType', died";
551             }
552              
553 676         1706 $size;
554             }
555              
556             sub write {
557 1257     1257   1795 my($self, $into) = @_;
558            
559 1257         3451 my($fname) = Encode::encode('UTF-16BE', $self->[0]);
560              
561 1257         33597 my($strucType) = $self->[2];
562              
563 1257         5338 $into->write('N a* a4 a4', length($fname)/2, $fname,
564             $self->[1], $strucType);
565              
566 1257 100 66     8713 if ($strucType eq 'long' or $strucType eq 'shor') {
    100 0        
    100          
    50          
    0          
    0          
567 3         9 $into->write('N', $self->[3]);
568             } elsif ($strucType eq 'bool') {
569 3         11 $into->write('C', $self->[3]);
570             } elsif ($strucType eq 'blob') {
571 18         57 $into->write('N', length($self->[3]));
572 18         91 $into->write($self->[3]);
573             } elsif ($strucType eq 'ustr') {
574 1233         5840 $into->write('N', length($self->[3]));
575 1233         3907 $into->write(Encode::encode('UTF-16BE', $self->[3]));
576             } elsif ($strucType eq 'type') {
577 0         0 $into->write('a4', $self->[3]);
578             } elsif ($strucType eq 'comp' or $strucType eq 'dutc') {
579 0         0 $into->write('Q>', $self->[3]);
580             } else {
581 0         0 die "Unknown struc type '$strucType', died";
582             }
583             }
584              
585             =head2 $entry->cmp($other)
586              
587             Returns -1, 0, or 1 depending on the relative ordering of the two entries,
588             according to (a guess at) the record ordering used by the store's B-tree.
589              
590             =cut
591              
592             sub cmp {
593 1380     1380   9673 my($self, $other) = @_;
594              
595             #
596             # There's probably some wacky Mac-specific Unicode collation
597             # rule for these, but case-insensitive comparison is a good
598             # approximation
599             #
600              
601             # Ordering in the btree is Finder-filename-ordering on the files,
602             # and simple bytewise ordering on the structure IDs.
603              
604 1380 100       5025 ( lc($self->[0]) cmp lc($other->[0]) )
605             ||
606             ( $self->[1] cmp $other->[1] );
607             }
608              
609             #
610             # The make_foo subs are used by Mac::Finder::DSStore::makeEntries.
611             #
612              
613             sub make_BKGD_default {
614 1     1   2 my($filename, undef, undef) = @_;
615              
616 1         9 my($rec) = Mac::Finder::DSStore::Entry->new($filename, 'BKGD');
617 1         4 $rec->value( pack('A4 x8', 'DefB') );
618 1         5 $rec;
619             }
620              
621             sub make_BKGD_color {
622 4     4   5 my($filename, $strucId, $argv) = @_;
623 4         6 my($color) = shift @$argv;
624 4         4 my($rgb);
625              
626 4 100       14 if ($color =~ /^\#([0-9a-f]+)$/i) {
627 3 100       15 if(length($1) == 3) {
    100          
    50          
628 1         8 ( $rgb = $1 ) =~ s/(.)(.)(.)/$1$1$1$1$2$2$2$2$3$3$3$3/;
629             } elsif (length($1) == 6) {
630 1         12 ( $rgb = $1 ) =~ s/(..)(..)(..)/$1$1$2$2$3$3/;
631             } elsif (length($1) == 12) {
632 1         2 $rgb = $1;
633             }
634             }
635              
636 4 100       119 croak "Can't parse color string '$color'"
637             unless $rgb;
638              
639 3         8 my($rec) = Mac::Finder::DSStore::Entry->new($filename, 'BKGD');
640 3         15 $rec->value( pack('A4 H12 x2', 'ClrB', $rgb) );
641              
642 3         12 $rec;
643             }
644              
645             sub make_BKGD_alias {
646 0     0   0 my($filename, $strucId, $argv) = @_;
647              
648 0         0 my($image) = shift @$argv;
649              
650 0 0       0 if(!ref $image) {
651 0         0 require Mac::Memory;
652 0         0 require Mac::Files;
653 0         0 $image = Mac::Files::NewAlias($image);
654             }
655              
656 0         0 my($isize) = $image->size;
657 0         0 my($bkgd, $pict);
658              
659 0         0 $bkgd = Mac::Finder::DSStore::Entry->new($filename, 'BKGD');
660 0         0 $bkgd->value( pack('A4 N nn', 'PctB', $isize, 0, 0) );
661              
662 0         0 $pict = Mac::Finder::DSStore::Entry->new($filename, 'pict');
663 0         0 $pict->value( $image->get );
664              
665 0         0 ( $bkgd, $pict );
666             }
667              
668             sub _make_packed {
669 2     2   5 my($filename, $strucId, $fmt, @values) = @_;
670 2         5 my($record) = Mac::Finder::DSStore::Entry->new($filename, $strucId);
671 2         7 $record->value( pack($fmt, @values) );
672 2         11 $record;
673             }
674              
675             sub _make_packed_arrayref {
676 4     4   7 my($filename, $strucId, $argv, $format, $reqcount, $dflt) = @_;
677 4         6 my($values) = shift @$argv;
678              
679 4 50       9 croak "$strucId argument must be an array ref"
680             unless ref $values;
681              
682 4 100       119 croak "$strucId argument must have at least $reqcount items"
683             unless $reqcount <= @$values;
684              
685 3         4 my($max) = $reqcount + @$dflt;
686              
687 3 100       110 croak "$strucId argument can't have more than $max items"
688             if $max < @$values;
689              
690 2         3 my(@fields) = @$values;
691 2 50       4 if ($max > @fields) {
692 2         5 push(@fields, @{$dflt}[ ( @fields - $max ) .. -1 ]);
  2         4  
693             }
694              
695 2         8 return &_make_packed($filename, substr($strucId, 0, 4),
696             $format, @fields);
697             }
698              
699             sub make_Iloc_xy {
700 4     4   6 my($filename, $strucId, $argv) = @_;
701 4         13 return &_make_packed_arrayref($filename, $strucId, $argv,
702             'NN nnnn', 2, [65535, 65535, 65535, 0]);
703             }
704              
705             sub make_fwi0_flds {
706 0     0     my($filename, $strucId, $argv) = @_;
707 0           my($flds) = shift @$argv;
708            
709 0 0         croak "$strucId argument must have 7 values"
710             unless 7 == @$flds;
711              
712 0           return &_make_packed($filename, 'fwi0', 'n4 A4 n*', @$flds);
713             }
714            
715              
716             =head1 SEE ALSO
717              
718             See L for more detailed information on
719             the record types found in a DS_Store file.
720              
721             See L for the low-level organization
722             of the DS_Store file.
723              
724             =head1 AUTHOR
725              
726             Copyright 2008 by Wim Lewis Ewiml@hhhh.orgE.
727              
728             Some information is from Mark Mentovai via the Mozilla project.
729             Thanks also to Martin Baker for bug reports.
730              
731             =cut
732              
733             1;