File Coverage

blib/lib/EBook/Ishmael/PDB.pm
Criterion Covered Total %
statement 72 81 88.8
branch 11 18 61.1
condition 3 9 33.3
subroutine 20 22 90.9
pod 18 18 100.0
total 124 148 83.7


line stmt bran cond sub pod time code
1             package EBook::Ishmael::PDB;
2 17     17   365 use 5.016;
  17         71  
3             our $VERSION = '2.01';
4 17     17   111 use strict;
  17         34  
  17         491  
5 17     17   82 use warnings;
  17         33  
  17         995  
6              
7 17     17   9601 use EBook::Ishmael::PDB::Record;
  17         59  
  17         24081  
8              
9             my $HEADER_COMMON = 78;
10             my $RECORD_INFO = 8;
11              
12             # Offset of Palm's Epoch (Jan 1, 1904) from Unix's Epoch (Jan 1, 1970)
13             my $EPOCH_OFFSET = -2082844800;
14              
15             sub new {
16              
17 42     42 1 111 my $class = shift;
18 42         83 my $pdb = shift;
19              
20 42         584 my $self = {
21             Name => undef,
22             Attr => undef,
23             Version => undef,
24             CDate => undef,
25             MDate => undef,
26             BDate => undef,
27             ModNum => undef,
28             AppInfo => undef,
29             SortInfo => undef,
30             Type => undef,
31             Creator => undef,
32             UIDSeed => undef,
33             NextRecList => undef,
34             RecNum => undef,
35             Recs => [],
36             Size => undef,
37             };
38              
39 42 50       2124 open my $fh, '<', $pdb
40             or die "Failed to open $pdb for reading: $!\n";
41 42         141 binmode $fh;
42              
43 42         275 seek $fh, 0, 2;
44 42         163 $self->{Size} = tell $fh;
45 42         172 seek $fh, 0, 0;
46              
47 42         514 read $fh, my ($hdr), $HEADER_COMMON;
48              
49             (
50             $self->{Name},
51             $self->{Attr},
52             $self->{Version},
53             $self->{CDate},
54             $self->{MDate},
55             $self->{BDate},
56             $self->{ModNum},
57             $self->{AppInfo},
58             $self->{SortInfo},
59             $self->{Type},
60             $self->{Creator},
61             $self->{UIDSeed},
62             $self->{NextRecList},
63             $self->{RecNum},
64 42         475 ) = unpack "a32 n n N N N N N N N N N N n", $hdr;
65              
66 42 50       355 unless ($self->{Name} =~ /\0$/) {
67 0         0 die "$self->{Source} is not a PDB file, name is not null-terminated\n";
68             }
69              
70 42 50       172 unless ($self->{NextRecList} == 0) {
71 0         0 die "$pdb is not a PDB file\n";
72             }
73              
74 42 50       143 if ($self->{RecNum} == 0) {
75 0         0 die "PDB $pdb has no records\n";
76             }
77              
78             # If the epoch offset knocks the time below zero, then that probably means
79             # that the time was stored as a Unix time.
80             $self->{CDate} &&= $self->{CDate} + $EPOCH_OFFSET > 0
81             ? $self->{CDate} + $EPOCH_OFFSET
82 42 100 33     362 : $self->{CDate};
83             $self->{MDate} &&= $self->{MDate} + $EPOCH_OFFSET > 0
84             ? $self->{MDate} + $EPOCH_OFFSET
85 42 100 33     360 : $self->{MDate};
86             $self->{BDate} &&= $self->{BDate} + $EPOCH_OFFSET > 0
87             ? $self->{BDate} + $EPOCH_OFFSET
88 42 0 33     123 : $self->{BDate};
89              
90 42         121 my @recs;
91              
92 42         201 for my $i (0 .. $self->{RecNum} - 1) {
93              
94 585         1074 read $fh, my ($buf), $RECORD_INFO;
95              
96 585         941 my $rec = {};
97              
98             (
99             $rec->{Offset},
100             $rec->{Attributes},
101             $rec->{UID},
102 585         1928 ) = unpack "N C C3", $buf;
103              
104 585 50       1502 if ($rec->{Offset} > $self->{Size}) {
105 0         0 die "Malformed PDB file: $pdb\n";
106             }
107              
108 585         1144 push @recs, $rec;
109              
110             }
111              
112 42         154 for my $i (0 .. $self->{RecNum} - 1) {
113              
114             my $size = $i == $self->{RecNum} - 1
115             ? $self->{Size} - $recs[$i]->{Offset}
116 585 100       1833 : $recs[$i + 1]->{Offset} - $recs[$i]->{Offset};
117              
118 585         4411 seek $fh, $recs[$i]->{Offset}, 0;
119              
120 585         5720 read $fh, my ($buf), $size;
121              
122 585         1051 push @{ $self->{Recs} }, EBook::Ishmael::PDB::Record->new(
  585         2143  
123             $buf,
124             $recs[$i]
125             );
126              
127             }
128              
129 42         1228 return bless $self, $class;
130              
131             }
132              
133             sub name {
134              
135 26     26 1 16867 my $self = shift;
136              
137 26         329 return $self->{Name} =~ s/\0+$//r;
138              
139             }
140              
141             sub attributes {
142              
143 4     4 1 12 my $self = shift;
144              
145 4         22 return $self->{Attr};
146              
147             }
148              
149             sub version {
150              
151 15     15 1 37 my $self = shift;
152              
153 15         57 return $self->{Version};
154              
155             }
156              
157             sub cdate {
158              
159 72     72 1 128 my $self = shift;
160              
161 72         280 return $self->{CDate};
162              
163             }
164              
165             sub mdate {
166              
167 92     92 1 190 my $self = shift;
168              
169 92         339 return $self->{MDate};
170              
171             }
172              
173             sub bdate {
174              
175 4     4 1 30 my $self = shift;
176              
177 4         22 return $self->{BDate};
178              
179             }
180              
181             sub modnum {
182              
183 4     4 1 16 my $self = shift;
184              
185 4         22 return $self->{ModNum};
186              
187             }
188              
189             sub app_info {
190              
191 4     4 1 10 my $self = shift;
192              
193 4         21 return $self->{AppInfo};
194              
195             }
196              
197             sub sort_info {
198              
199 4     4 1 11 my $self = shift;
200              
201 4         20 return $self->{SortInfo};
202              
203             }
204              
205             sub type {
206              
207 4     4 1 12 my $self = shift;
208              
209 4         19 return $self->{Type};
210              
211             }
212              
213             sub creator {
214              
215 4     4 1 14 my $self = shift;
216              
217 4         24 return $self->{Creator};
218              
219             }
220              
221             sub uid_seed {
222              
223 4     4 1 14 my $self = shift;
224              
225 4         24 return $self->{UIDSeed};
226              
227             }
228              
229             sub next_rec_list {
230              
231 4     4 1 14 my $self = shift;
232              
233 4         93 return $self->{NextRecList};
234              
235             }
236              
237             sub recnum {
238              
239 24     24 1 58 my $self = shift;
240              
241 24         131 return $self->{RecNum};
242              
243             }
244              
245             sub record {
246              
247 388     388 1 647 my $self = shift;
248 388         573 my $rec = shift;
249              
250 388         1465 return $self->{Recs}->[$rec];
251              
252             }
253              
254             sub records {
255              
256 0     0 1   my $self = shift;
257              
258 0           return @{ $self->{Recs} };
  0            
259              
260             }
261              
262             sub size {
263              
264 0     0 1   my $self = shift;
265              
266 0           return $self->{Size};
267              
268             }
269              
270             1;
271              
272             =head1 NAME
273              
274             EBook::Ishmael::PDB - ishmael PDB interface
275              
276             =head1 SYNOPSIS
277              
278             use EBook::Ishmael::PDB;
279              
280             my $pdb = EBook::Ishmael::PDB->new($file);
281              
282             =head1 DESCRIPTION
283              
284             B is a simple interface for reading Palm PDB files.
285             For L user documentation, you should consult its manual (this is
286             developer documentation).
287              
288             =head1 METHODS
289              
290             =head2 $p = EBook::Ishmael::PDB->new($pdb)
291              
292             Returns a blessed C object representing the given
293             PDB file C<$pdb>.
294              
295             =head2 $n = $p->name()
296              
297             Returns the PDB's name (with the null characters stripped out).
298              
299             =head2 $a = $p->attributes()
300              
301             Returns the PDB's attribute bitfield.
302              
303             =head2 $v = $p->version()
304              
305             Returns the PDB's version.
306              
307             =head2 $c = $p->cdate()
308              
309             Returns the PDB's creation date.
310              
311             =head2 $m = $p->mdate()
312              
313             Returns the PDB's modification date.
314              
315             =head2 $b = $p->bdate()
316              
317             Returns the PDB's backup date.
318              
319             =head2 $m = $p->modnum()
320              
321             Returns the PDB's modification number.
322              
323             =head2 $a = $p->app_info()
324              
325             Returns the PDB's app info area offset.
326              
327             =head2 $s = $p->sort_info()
328              
329             Returns the PDB's sort info area offset.
330              
331             =head2 $t = $p->type()
332              
333             Returns the PDB's type.
334              
335             =head2 $c = $p->creator()
336              
337             Returns the PDB's creator.
338              
339             =head2 $u = $p->uid_seed()
340              
341             Returns the PDB's UID seed.
342              
343             =head2 $n = $p->next_rec_list()
344              
345             Returns the PDB's next record list (should always be C<0>).
346              
347             =head2 $r = $p->recnum()
348              
349             Returns the PDB's record count.
350              
351             =head2 $r = $p->record($rec)
352              
353             Returns the C<$r>th record object in the PDB object.
354              
355             =head2 @r = $p->records()
356              
357             Returns array of record objects in the PDB object.
358              
359             =head2 $s = $p->size()
360              
361             Returns the PDB's size.
362              
363             =head1 AUTHOR
364              
365             Written by Samuel Young, Esamyoung12788@gmail.comE.
366              
367             This project's source can be found on its
368             L. Comments and pull
369             requests are welcome!
370              
371             =head1 COPYRIGHT
372              
373             Copyright (C) 2025-2026 Samuel Young
374              
375             This program is free software: you can redistribute it and/or modify
376             it under the terms of the GNU General Public License as published by
377             the Free Software Foundation, either version 3 of the License, or
378             (at your option) any later version.
379              
380             =head1 SEE ALSO
381              
382             L
383              
384             =cut