File Coverage

blib/lib/Palm/StdAppInfo.pm
Criterion Covered Total %
statement 53 133 39.8
branch 6 34 17.6
condition 0 12 0.0
subroutine 10 18 55.5
pod 10 10 100.0
total 79 207 38.1


line stmt bran cond sub pod time code
1             package Palm::StdAppInfo;
2             #
3             # ABSTRACT: Handle standard AppInfo blocks in Palm OS PDBs
4             #
5             # Copyright (C) 1999, 2000, Andrew Arensburger.
6             #
7             # This program is free software; you can redistribute it and/or modify
8             # it under the same terms as Perl itself.
9             #
10             # This program is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the
13             # GNU General Public License or the Artistic License for more details.
14              
15 6     6   22 use strict;
  6         10  
  6         186  
16 6     6   23 use Palm::Raw();
  6         406  
  6         111  
17              
18             # Don't harass me about these variables
19 6     6   23 use vars qw( $VERSION @ISA $error );
  6         7  
  6         561  
20             # $error acts like $! in that it reports the error that occurred
21              
22             # One liner, to allow MakeMaker to work.
23             $VERSION = '1.400';
24             # This file is part of Palm 1.400 (March 14, 2015)
25              
26             @ISA = qw( Palm::Raw );
27              
28             #'
29              
30 6     6   32 use constant APPINFO_PADDING => 1; # Whether to add the padding byte at
  6         9  
  6         428  
31             # the end of the AppInfo block.
32             # Note that this might be considered a hack:
33             # this relies on the fact that 'use constant'
34             # defines a function with no arguments; that
35             # therefore this can be called as an instance
36             # method, with full inheritance. That is, if
37             # the handler class doesn't define it, Perl
38             # will find the constant in the parent. If
39             # this ever changes, the code below that uses
40             # $self->APPINFO_PADDING will need to be
41             # changed.
42 6     6   36 use constant numCategories => 16; # Number of categories in AppInfo block
  6         11  
  6         269  
43 6     6   29 use constant categoryLength => 16; # Length of category names
  6         10  
  6         476  
44 6         7512 use constant stdAppInfoSize => # Length of a standard AppInfo block
45             2 +
46             (categoryLength * numCategories) +
47             numCategories +
48 6     6   30 1 + 1; # The padding byte at the end may
  6         8  
49             # be omitted
50              
51             sub import
52             {
53 1     1   295 &Palm::PDB::RegisterPDBHandlers(__PACKAGE__,
54             [ "", "" ],
55             );
56             }
57              
58              
59             # seed_StdAppInfo
60             # *** THIS IS NOT A METHOD ***
61             # Given a reference to an appinfo hash, creates all of the fields for
62             # a new AppInfo block.
63             sub seed_StdAppInfo
64             {
65 5     5 1 10 my $appinfo = shift;
66 5         6 my $i;
67              
68 5         13 $appinfo->{categories} = []; # Create array of categories
69              
70             # Initialize the categories
71             # Note that all of the IDs are initialized to $i. There's no
72             # real good reason for doing it this way, except that that's
73             # what the Palm appears to do with new category lists.
74 5         30 for ($i = 0; $i < numCategories; $i++)
75             {
76 80         141 $appinfo->{categories}[$i] = {};
77              
78 80         84 $appinfo->{categories}[$i]{renamed} = 0;
79 80         80 $appinfo->{categories}[$i]{name} = undef;
80 80         126 $appinfo->{categories}[$i]{id} = $i;
81             }
82              
83             # The only fixed category is "Unfiled". Initialize it now
84 5         14 $appinfo->{categories}[0]{name} = "Unfiled";
85 5         11 $appinfo->{categories}[0]{id} = 0;
86              
87             # I'm not sure what this is, but let's initialize it.
88             # The Palm appears to initialize this to numCategories - 1.
89 5         10 $appinfo->{lastUniqueID} = numCategories - 1;
90             }
91              
92              
93             sub newStdAppInfo
94             {
95 0     0 1 0 my $class = shift;
96 0         0 my $retval = {};
97              
98 0         0 &seed_StdAppInfo($retval);
99 0         0 return $retval;
100             }
101              
102             #'
103              
104             sub new
105             {
106 0     0 1 0 my $classname = shift;
107 0         0 my $self = $classname->SUPER::new(@_);
108             # Create a generic PDB. No need to rebless it,
109             # though.
110              
111             # Initialize the AppInfo block
112 0         0 $self->{appinfo} = &newStdAppInfo();
113              
114 0         0 return $self;
115             }
116              
117             #'
118              
119             # parse_StdAppInfo
120             # *** THIS IS NOT A METHOD ***
121             #
122             # Reads the raw data from $data, parses it as a standard AppInfo
123             # block, and fills in the corresponding fields in %$appinfo. Returns
124             # the number of bytes parsed.
125             sub parse_StdAppInfo
126             {
127 5     5 1 6 my $appinfo = shift; # A reference to hash, to fill in
128 5         9 my $data = shift; # Raw data to read
129 5         9 my $nopadding = shift; # Optional: no padding byte at end
130 5         7 my $unpackstr; # First argument to unpack()
131             my $renamed; # Bitmap of renamed categories
132 0         0 my @labels; # Array of category labels
133 0         0 my @uniqueIDs; # Array of category IDs
134 0         0 my $lastUniqueID; # Not sure what this is
135              
136             return undef
137 5 50       22 if length $data < 4+(categoryLength*numCategories)+numCategories;
138              
139 5 50       16 if (!defined($nopadding))
140             {
141 5         10 $nopadding = 0;
142             }
143              
144             # Make sure $appinfo contains all of the requisite fields
145 5         34 &seed_StdAppInfo($appinfo);
146              
147             # The argument to unpack() isn't hard to understand, it's just
148             # hard to write in a readable fashion.
149 5         24 $unpackstr = # Argument to unpack(), since it's hairy
150             "n" . # Renamed categories
151             ("a" . categoryLength) x numCategories .
152             # Category labels
153             "C" x numCategories .
154             # Category IDs
155             "C" . # Last unique ID
156             "x";
157              
158             # Unpack the data
159 5         72 ($renamed,
160             @labels[0..(numCategories-1)],
161             @uniqueIDs[0..(numCategories-1)],
162             $lastUniqueID) =
163             unpack $unpackstr, $data;
164              
165             # Clean this stuff up a bit
166 5         19 for (@labels)
167             {
168 80         148 s/\0.*$//; # Trim at NUL
169             }
170              
171             # Now put the data into $appinfo
172 5         10 my $i;
173              
174 5         17 for ($i = 0; $i < numCategories; $i++)
175             {
176 80 100       120 $appinfo->{categories}[$i]{renamed} =
177             ($renamed & (1 << $i) ? 1 : 0);
178 80         84 $appinfo->{categories}[$i]{name} = $labels[$i];
179 80         141 $appinfo->{categories}[$i]{id} = $uniqueIDs[$i];
180             }
181 5         10 $appinfo->{lastUniqueID} = $lastUniqueID;
182              
183             # There might be other stuff in the AppInfo block other than
184             # the standard categories. Put everything else in
185             # $appinfo->{other}.
186 5 50       24 $appinfo->{other} = substr($data,
187             stdAppInfoSize - ($nopadding ? 1 : 0));
188              
189 5 50       57 return ($nopadding ? stdAppInfoSize - 1 : stdAppInfoSize);
190             }
191              
192             #'
193              
194             sub ParseAppInfoBlock
195             {
196 0     0 1   my $self = shift;
197 0           my $data = shift;
198              
199 0           my $appinfo = {};
200              
201 0           &parse_StdAppInfo($appinfo, $data, $self->APPINFO_PADDING);
202 0           return $appinfo;
203             }
204              
205             #'
206              
207             # pack_StdAppInfo
208             # *** THIS IS NOT A METHOD ***
209             #
210             # Given a reference to a hash containing an AppInfo block (such as
211             # that initialized by parse_StdAppInfo()), returns a packed string
212             # that can be written to the PDB file.
213             sub pack_StdAppInfo
214             {
215 0     0 1   my $appinfo = shift;
216 0           my $nopadding = shift;
217 0           my $retval;
218             my $i;
219              
220 0 0         $nopadding = 0 if !defined($nopadding);
221              
222             # Create the bitfield of renamed categories
223 0           my $renamed;
224              
225 0           $renamed = 0;
226 0           for ($i = 0; $i < numCategories; $i++)
227             {
228 0 0         if ($appinfo->{categories}[$i]{renamed})
229             {
230 0           $renamed |= (1 << $i);
231             }
232             }
233 0           $retval = pack("n", $renamed);
234              
235             # There have to be exactly 16 categories in the AppInfo block,
236             # even though $appinfo->{categories} may have been mangled
237             # by a naive (or clever) user or broken program.
238 0           for ($i = 0; $i < numCategories; $i++)
239             {
240 0           my $name; # Category name
241              
242             # This is mainly to stop Perl 5.6 from complaining if
243             # the category name is undefined.
244 0 0 0       if ((!defined($appinfo->{categories}[$i]{name})) ||
245             $appinfo->{categories}[$i]{name} eq "")
246             {
247 0           $name = "";
248             } else {
249 0           $name = $appinfo->{categories}[$i]{name};
250             }
251              
252 0           $retval .= pack("a" . categoryLength, $name);
253             }
254              
255             # Ditto for category IDs
256 0           for ($i = 0; $i < numCategories; $i++)
257             {
258 0           $retval .= pack("C", $appinfo->{categories}[$i]{id});
259             }
260              
261             # Last unique ID, and alignment padding
262 0           $retval .= pack("Cx", $appinfo->{lastUniqueID});
263              
264 0 0         $retval .= $appinfo->{other} if defined($appinfo->{other});
265              
266 0           return $retval;
267             }
268              
269             #'
270              
271             sub PackAppInfoBlock
272             {
273 0     0 1   my $self = shift;
274              
275 0           return &pack_StdAppInfo($self->{appinfo}, $self->{APPINFO_PADDING});
276             }
277              
278             #'
279             # XXX - When choosing a new category ID, should pick them from the
280             # range 128-255.
281             sub addCategory
282             {
283 0     0 1   my $self = shift; # PDB
284 0           my $name = shift; # Category name
285 0           my $id = shift; # Category ID (optional)
286 0 0         my $renamed = $#_ >= 0 ? $_[0] : 1;
287             # Flag: was the category renamed (optional)
288             # This initialization may look weird,
289             # but it's this way so that it'll
290             # default to true if omitted.
291 0           my $categories = $self->{appinfo}{categories};
292 0           my $i;
293             my %used; # Category IDs in use
294              
295             # Collect all the IDs in the current list
296 0           for (@{$categories})
  0            
297             {
298 0 0 0       next if !defined($_->{name}) || $_->{name} eq "";
299 0           $used{$_->{id}} = 1;
300             }
301              
302 0 0         if (defined($id))
303             {
304             # Sanity check: make sure this ID isn't already in use
305 0 0         if (defined($used{$id}))
306             {
307 0           $error = "Category ID already in use";
308 0           return undef;
309             }
310             } else {
311             # Find an unused category number, if none was specified
312 0           for ($id = 128; $id < 256; $id++)
313             {
314 0 0         last if !defined($used{$id});
315             }
316             }
317              
318             # Go through the list of categories, looking for an unused slot
319 0           for ($i = 0; $i < numCategories; $i++)
320             {
321             # Ignore named categories
322 0 0 0       next unless !defined($categories->[$i]{name}) or
323             $categories->[$i]{name} eq "";
324              
325             # Found an empty slot
326 0           $categories->[$i]{name} = $name;
327 0           $categories->[$i]{id} = $id;
328 0           $categories->[$i]{renamed} = $renamed;
329 0           return 1;
330             }
331              
332             # If we get this far, there are no empty category slots
333 0           $error = "No unused categories";
334 0           return undef;
335             }
336              
337             #'
338             sub deleteCategory
339             {
340 0     0 1   my $self = shift;
341 0           my $name = shift; # Category name
342              
343 0           for (@{$self->{appinfo}{categories}})
  0            
344             {
345             # Find the category named $name
346 0 0         next if $_->{name} ne $name;
347              
348             # Erase this category
349 0           $_->{name} = "";
350              
351             # You'd think it would make sense to set the "renamed"
352             # field here, but the Palm doesn't do that.
353             }
354             }
355              
356             #'
357             # XXX - This doesn't behave the same way as the Palm: the Palm also
358             # picks a new category ID.
359             sub renameCategory
360             {
361 0     0 1   my $self = shift;
362 0           my $oldname = shift;
363 0           my $newname = shift;
364              
365 0           for (@{$self->{appinfo}{categories}})
  0            
366             {
367             # Look for a category named $oldname
368 0 0 0       next if !defined($_->{name}) || $_->{name} ne $oldname;
369              
370             # Found it. Rename it and mark it as renamed.
371 0           $_->{name} = $newname;
372 0           $_->{renamed} = 1;
373 0           return 1;
374             }
375              
376 0           $error = "No such category";
377 0           return undef;
378             }
379              
380             1;
381              
382             __END__