File Coverage

blib/lib/EBook/Ishmael/PDB.pm
Criterion Covered Total %
statement 76 91 83.5
branch 11 18 61.1
condition 3 9 33.3
subroutine 21 26 80.7
pod 18 18 100.0
total 129 162 79.6


line stmt bran cond sub pod time code
1             package EBook::Ishmael::PDB;
2 17     17   328 use 5.016;
  17         63  
3             our $VERSION = '2.03';
4 17     17   112 use strict;
  17         79  
  17         535  
5 17     17   94 use warnings;
  17         52  
  17         25538  
6              
7             my $HEADER_COMMON = 78;
8             my $RECORD_INFO = 8;
9              
10             # Offset of Palm's Epoch (Jan 1, 1904) from Unix's Epoch (Jan 1, 1970)
11             my $EPOCH_OFFSET = -2082844800;
12              
13             package EBook::Ishmael::PDB::Record {
14              
15             sub new {
16              
17 585     585   983 my $class = shift;
18 585         878 my $data = shift;
19 585         884 my $params = shift;
20              
21             my $self = {
22             Data => $data,
23             Off => $params->{Offset},
24             Attr => $params->{Attributes},
25             UID => $params->{UID},
26 585         2078 };
27              
28 585         1871 return bless $self, $class;
29              
30             }
31              
32             sub data {
33              
34 386     386   602 my $self = shift;
35              
36 386         1132 return $self->{Data};
37              
38             }
39              
40             sub offset {
41              
42 0     0   0 my $self = shift;
43              
44 0         0 return $self->{Off};
45              
46             }
47              
48             sub attributes {
49              
50 0     0   0 my $self = shift;
51              
52 0         0 return $self->{Attr};
53              
54             }
55              
56             sub uid {
57              
58 0     0   0 my $self = shift;
59              
60 0         0 return $self->{UID};
61              
62             }
63              
64             }
65              
66             sub new {
67              
68 42     42 1 121 my $class = shift;
69 42         89 my $pdb = shift;
70              
71 42         531 my $self = {
72             Name => undef,
73             Attr => undef,
74             Version => undef,
75             CDate => undef,
76             MDate => undef,
77             BDate => undef,
78             ModNum => undef,
79             AppInfo => undef,
80             SortInfo => undef,
81             Type => undef,
82             Creator => undef,
83             UIDSeed => undef,
84             NextRecList => undef,
85             RecNum => undef,
86             Recs => [],
87             Size => undef,
88             };
89              
90 42 50       2104 open my $fh, '<', $pdb
91             or die "Failed to open $pdb for reading: $!\n";
92 42         183 binmode $fh;
93              
94 42         362 seek $fh, 0, 2;
95 42         170 $self->{Size} = tell $fh;
96 42         188 seek $fh, 0, 0;
97              
98 42         547 read $fh, my ($hdr), $HEADER_COMMON;
99              
100             (
101             $self->{Name},
102             $self->{Attr},
103             $self->{Version},
104             $self->{CDate},
105             $self->{MDate},
106             $self->{BDate},
107             $self->{ModNum},
108             $self->{AppInfo},
109             $self->{SortInfo},
110             $self->{Type},
111             $self->{Creator},
112             $self->{UIDSeed},
113             $self->{NextRecList},
114             $self->{RecNum},
115 42         556 ) = unpack "a32 n n N N N N N N N N N N n", $hdr;
116              
117 42 50       347 unless ($self->{Name} =~ /\0$/) {
118 0         0 die "$self->{Source} is not a PDB file, name is not null-terminated\n";
119             }
120              
121 42 50       150 unless ($self->{NextRecList} == 0) {
122 0         0 die "$pdb is not a PDB file\n";
123             }
124              
125 42 50       142 if ($self->{RecNum} == 0) {
126 0         0 die "PDB $pdb has no records\n";
127             }
128              
129             # If the epoch offset knocks the time below zero, then that probably means
130             # that the time was stored as a Unix time.
131             $self->{CDate} &&= $self->{CDate} + $EPOCH_OFFSET > 0
132             ? $self->{CDate} + $EPOCH_OFFSET
133 42 100 33     393 : $self->{CDate};
134             $self->{MDate} &&= $self->{MDate} + $EPOCH_OFFSET > 0
135             ? $self->{MDate} + $EPOCH_OFFSET
136 42 100 33     346 : $self->{MDate};
137             $self->{BDate} &&= $self->{BDate} + $EPOCH_OFFSET > 0
138             ? $self->{BDate} + $EPOCH_OFFSET
139 42 0 33     123 : $self->{BDate};
140              
141 42         116 my @recs;
142              
143 42         195 for my $i (0 .. $self->{RecNum} - 1) {
144              
145 585         1070 read $fh, my ($buf), $RECORD_INFO;
146              
147 585         941 my $rec = {};
148              
149             (
150             $rec->{Offset},
151             $rec->{Attributes},
152             $rec->{UID},
153 585         1954 ) = unpack "N C C3", $buf;
154              
155 585 50       1399 if ($rec->{Offset} > $self->{Size}) {
156 0         0 die "Malformed PDB file: $pdb\n";
157             }
158              
159 585         1189 push @recs, $rec;
160              
161             }
162              
163 42         151 for my $i (0 .. $self->{RecNum} - 1) {
164              
165             my $size = $i == $self->{RecNum} - 1
166             ? $self->{Size} - $recs[$i]->{Offset}
167 585 100       1752 : $recs[$i + 1]->{Offset} - $recs[$i]->{Offset};
168              
169 585         4679 seek $fh, $recs[$i]->{Offset}, 0;
170              
171 585         5311 read $fh, my ($buf), $size;
172              
173 585         967 push @{ $self->{Recs} }, EBook::Ishmael::PDB::Record->new(
  585         1798  
174             $buf,
175             $recs[$i]
176             );
177              
178             }
179              
180 42         1336 return bless $self, $class;
181              
182             }
183              
184             sub name {
185              
186 26     26 1 13323 my $self = shift;
187              
188 26         326 return $self->{Name} =~ s/\0+$//r;
189              
190             }
191              
192             sub attributes {
193              
194 4     4 1 9 my $self = shift;
195              
196 4         16 return $self->{Attr};
197              
198             }
199              
200             sub version {
201              
202 15     15 1 33 my $self = shift;
203              
204 15         53 return $self->{Version};
205              
206             }
207              
208             sub cdate {
209              
210 72     72 1 125 my $self = shift;
211              
212 72         286 return $self->{CDate};
213              
214             }
215              
216             sub mdate {
217              
218 92     92 1 349 my $self = shift;
219              
220 92         397 return $self->{MDate};
221              
222             }
223              
224             sub bdate {
225              
226 4     4 1 10 my $self = shift;
227              
228 4         43 return $self->{BDate};
229              
230             }
231              
232             sub modnum {
233              
234 4     4 1 12 my $self = shift;
235              
236 4         18 return $self->{ModNum};
237              
238             }
239              
240             sub app_info {
241              
242 4     4 1 10 my $self = shift;
243              
244 4         19 return $self->{AppInfo};
245              
246             }
247              
248             sub sort_info {
249              
250 4     4 1 11 my $self = shift;
251              
252 4         24 return $self->{SortInfo};
253              
254             }
255              
256             sub type {
257              
258 4     4 1 11 my $self = shift;
259              
260 4         20 return $self->{Type};
261              
262             }
263              
264             sub creator {
265              
266 4     4 1 17 my $self = shift;
267              
268 4         28 return $self->{Creator};
269              
270             }
271              
272             sub uid_seed {
273              
274 4     4 1 12 my $self = shift;
275              
276 4         21 return $self->{UIDSeed};
277              
278             }
279              
280             sub next_rec_list {
281              
282 4     4 1 10 my $self = shift;
283              
284 4         20 return $self->{NextRecList};
285              
286             }
287              
288             sub recnum {
289              
290 24     24 1 65 my $self = shift;
291              
292 24         136 return $self->{RecNum};
293              
294             }
295              
296             sub record {
297              
298 386     386 1 903 my $self = shift;
299 386         549 my $rec = shift;
300              
301 386         1195 return $self->{Recs}->[$rec];
302              
303             }
304              
305             sub records {
306              
307 0     0 1   my $self = shift;
308              
309 0           return @{ $self->{Recs} };
  0            
310              
311             }
312              
313             sub size {
314              
315 0     0 1   my $self = shift;
316              
317 0           return $self->{Size};
318              
319             }
320              
321             1;
322              
323             =head1 NAME
324              
325             EBook::Ishmael::PDB - ishmael PDB interface
326              
327             =head1 SYNOPSIS
328              
329             use EBook::Ishmael::PDB;
330              
331             my $pdb = EBook::Ishmael::PDB->new($file);
332              
333             =head1 DESCRIPTION
334              
335             B is a simple interface for reading Palm PDB files.
336             For L user documentation, you should consult its manual (this is
337             developer documentation).
338              
339             =head1 METHODS
340              
341             =head2 $p = EBook::Ishmael::PDB->new($pdb)
342              
343             Returns a blessed C object representing the given
344             PDB file C<$pdb>.
345              
346             =head2 $n = $p->name()
347              
348             Returns the PDB's name (with the null characters stripped out).
349              
350             =head2 $a = $p->attributes()
351              
352             Returns the PDB's attribute bitfield.
353              
354             =head2 $v = $p->version()
355              
356             Returns the PDB's version.
357              
358             =head2 $c = $p->cdate()
359              
360             Returns the PDB's creation date.
361              
362             =head2 $m = $p->mdate()
363              
364             Returns the PDB's modification date.
365              
366             =head2 $b = $p->bdate()
367              
368             Returns the PDB's backup date.
369              
370             =head2 $m = $p->modnum()
371              
372             Returns the PDB's modification number.
373              
374             =head2 $a = $p->app_info()
375              
376             Returns the PDB's app info area offset.
377              
378             =head2 $s = $p->sort_info()
379              
380             Returns the PDB's sort info area offset.
381              
382             =head2 $t = $p->type()
383              
384             Returns the PDB's type.
385              
386             =head2 $c = $p->creator()
387              
388             Returns the PDB's creator.
389              
390             =head2 $u = $p->uid_seed()
391              
392             Returns the PDB's UID seed.
393              
394             =head2 $n = $p->next_rec_list()
395              
396             Returns the PDB's next record list (should always be C<0>).
397              
398             =head2 $r = $p->recnum()
399              
400             Returns the PDB's record count.
401              
402             =head2 $r = $p->record($rec)
403              
404             Returns the C<$r>th record object in the PDB object.
405              
406             =head2 @r = $p->records()
407              
408             Returns array of record objects in the PDB object.
409              
410             =head2 $s = $p->size()
411              
412             Returns the PDB's size.
413              
414             =head1 EBook::Ishmael::PDB::Record
415              
416             Class representing a PDB record, returned by the PDB's C method.
417              
418             =head1 METHODS
419              
420             =head2 $rec = EBook::Ishmael::PDB::Record->new($data, %params)
421              
422             Creates a new record.
423              
424             The following are valid parameters, and all are required.
425              
426             =over 4
427              
428             =item Offset
429              
430             Offset of data in PDB database.
431              
432             =item Attributes
433              
434             Bitmask of record attributes.
435              
436             =item UID
437              
438             UID of record.
439              
440             =back
441              
442             =head2 $data = $rec->data()
443              
444             Returns data held in record.
445              
446             =head2 $off = $rec->offset()
447              
448             Returns offset of record.
449              
450             =head2 $attr = $rec->attributes()
451              
452             Returns record's attribute bitmask.
453              
454             =head2 $uid = $rec->uid()
455              
456             Returns record's UID.
457              
458             =head1 AUTHOR
459              
460             Written by Samuel Young, Esamyoung12788@gmail.comE.
461              
462             This project's source can be found on its
463             L. Comments and pull
464             requests are welcome!
465              
466             =head1 COPYRIGHT
467              
468             Copyright (C) 2025-2026 Samuel Young
469              
470             This program is free software: you can redistribute it and/or modify
471             it under the terms of the GNU General Public License as published by
472             the Free Software Foundation, either version 3 of the License, or
473             (at your option) any later version.
474              
475             =head1 SEE ALSO
476              
477             L
478              
479             =cut