File Coverage

blib/lib/Palm/ThinkDB.pm
Criterion Covered Total %
statement 173 245 70.6
branch 61 90 67.7
condition 7 9 77.7
subroutine 15 19 78.9
pod 4 13 30.7
total 260 376 69.1


line stmt bran cond sub pod time code
1             # Palm::ThinkDB by Erik Arneson
2             #
3             # Perl class for dealing with ThinkDB databases.
4             #
5             # Copyright (C) 2001 Erik Arneson
6             # You may distribute this file under the terms of the Artistic
7             # License, as specified in the README file.
8             #
9             # $Id: ThinkDB.pm,v 1.8 2001/06/12 20:11:10 erik Exp $
10              
11             package Palm::ThinkDB;
12              
13 1     1   55229 use strict;
  1         4  
  1         52  
14 1     1   1320 use Palm::Raw ();
  1         725  
  1         22  
15 1     1   1215 use Palm::StdAppInfo ();
  1         5410  
  1         4926  
16              
17             our $VERSION = '0.02';
18             our $DEBUG = 0;
19             our (@ISA);
20              
21             @ISA = qw(Palm::PDB Palm::Raw Palm::StdAppInfo);
22              
23             sub import {
24 1     1   21 &Palm::PDB::RegisterPDBHandlers(__PACKAGE__,
25             [qw(THNK data)]);
26             }
27              
28             # Can't really create a new DB yet.
29             sub new {
30 0     0 1 0 return {};
31             }
32              
33             sub new_Record {
34 1     1 1 24 my $class = shift;
35 1         10 my $record = $class->SUPER::new_Record(@_);
36              
37             # What exactly do we need to initialize?
38 1         14 $record->{category} = 0;
39 1         3 $record->{data} = '';
40             # This has to be a database record type, as we can't really handle
41             # anything else.
42 1         2 $record->{type} = 87;
43              
44 1         3 return $record;
45             }
46              
47             sub ParseRecord {
48 197     197 1 19050 my $self = shift;
49 197         871 my %record = @_;
50 197         15827 my $data = $record{data};
51              
52 197         323 delete $record{offset}; # apparently this is useless!
53             #delete $record{data};
54              
55 197         206 my ($record_type, $rec);
56            
57 197         370 $record_type = unpack "C", $data;
58            
59             # Column names! Yowch.
60 197 100 100     1113 if ($record_type == 1) {
    100          
    100          
61 2         6 my ($numcols, @trash, $tcnum, $tctype, $tcname, $tidx);
62 2         8 _debug_print("Columns:\n");
63 2         176 $data = substr $data, 1;
64 2         12 ($numcols, @trash) = unpack("C13", $data);
65 2         12 $data = substr($data, index($data, "\000", 14));
66 2         6 $numcols--;
67 2         19 for (my $i = 1; $i <= $numcols; $i++) {
68 18         198 (@trash[0..1], $tcnum, $tctype, @trash[0..9]) = unpack("C13", $data);
69 18         39 $tidx = index($data, "\000", 14);
70 18         31 $tcname = substr($data, 14, $tidx - 14);
71 18         55 _debug_printf(" i: $i colnum: %03d coltype: %02d colname: '%s'\n", $tcnum, $tctype, $tcname);
72 18         58 $self->{cols}[$tcnum]{type} = $tctype;
73 18         38 $self->{cols}[$tcnum]{name} = $tcname;
74              
75 18         58 $data = substr $data, $tidx;
76             }
77 2         6 _debug_print("\n");
78             }
79             # List items
80             elsif ($record_type > 2 &&
81             $record_type < 82) {
82 158         162 my (@list, $colid, $num, @order);
83 158         255 $data = substr $data, 1;
84 158         989 $colid = $record_type - 2;
85 158         594 ($num) = unpack("C", $data);
86 158 100       453 if ($num > 0) {
87 4         14 (@order) = unpack("C$num", $data);
88 4         28 (@list) = split("\000", substr($data, $num + 1), $num + 1);
89             # get rid of trailing garbage!
90 4         9 pop @list;
91             # Sort according to order? Not needed -- only for aesthetics
92             #(@list) = @list[sort { $order[$a] <=> $order[$b] } 0 .. $#list];
93              
94 4         16 $self->{list}{$colid} = \@list;
95            
96 4         30 _debug_print("Record ID: ", $record{id}, "\n",
97             " List Record for Column $colid\n",
98             " Ordering: ", join(", ", @order), "\n",
99             " Items: ", join(", ", @list), "\n",
100             " Data: ", safestr($data), "\n");
101             }
102             }
103             # The big one: a database record.
104             elsif ($record_type == 87) {
105 23         57 _debug_print( "Record ID: ", $record{id}, "\n");
106 23         44 _debug_print( " Record Cat: ", $record{category}, "\n");
107              
108             # Unpack a record
109 23         24 my $foo;
110 23         54 my ($type, $id) = unpack "CxN", $data;
111 23         52 _debug_printf(" type: %d id: %d\n", $type, $id);
112 23         41 $data = substr $data, 6;
113              
114 23         41 $record{idnum} = $id;
115 23 50       57 if ($id > $self->{high_id}) {
116 23         187 $self->{high_id} = $id;
117             }
118            
119 23         51 while (length($data) > 0) {
120 81         142 my ($ctype, $cid) = unpack "C2", $data;
121 81         112 $data = substr $data, 2;
122             # First are normal string types.
123 81 100       333 if ($ctype == 1) {
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    50          
    100          
    50          
    50          
    50          
124             #my ($slen) = unpack "C", $data;
125 22         54 my ($sdat) = unpack "C/a", $data;
126 22         34 my $slen = length($sdat);
127 22         37 _debug_printf(" (text)col: %02d strlen: %02d data: '%s'\n", $cid, $slen, $sdat);
128 22         50 $record{col}{$cid} = $sdat;
129 22         64 $data = substr $data, $slen+2;
130             }
131             # Integer types.
132             elsif ($ctype == 2) {
133             # Integer
134 22         60 my ($val) = unpack("n", $data);
135 22         45 _debug_printf(" col: %02d data: %d\n", $cid, $val);
136 22         89 $record{col}{$cid} = $val;
137 22         72 $data = substr $data, 2;
138             }
139             # Long
140             elsif ($ctype == 3) {
141 2         5 my ($val) = unpack("N", $data);
142 2         7 _debug_printf(" col: %02d data: %d\n", $cid, $val);
143 2         5 $record{col}{$cid} = $val;
144 2         7 $data = substr $data, 4;
145             }
146             # Float
147             elsif ($ctype == 4) {
148 2         6 my (@val) = unpack("s2", $data);
149 2         11 _debug_printf(" col: %02d data: %s\n", $cid, join(',', @val));
150 2         8 $record{col}{$cid} = $val[0];
151 2         9 $record{raw}{$cid} = substr $data, 0, 4;
152 2         7 $data = substr $data, 4;
153             }
154             # List!
155             elsif ($ctype == 5) {
156 4         10 my ($val) = unpack("C", $data);
157 4         18 $record{col}{$cid} = $self->{list}{$cid}[$val - 1];
158 4         11 _debug_printf(" col: %02d idx: %d val: '%s'\n",
159             $cid, $val, $record{col}{$cid});
160 4         12 $data = substr $data, 1;
161             }
162             # Checkbox
163             elsif ($ctype == 6) {
164 0         0 my ($val) = unpack("C", $data);
165 0 0       0 _debug_printf(" col: %02d checked: %s\n", $cid, ($val) ? 'yes' : 'no');
166 0         0 $record{col}{$cid} = $val;
167 0         0 $data = substr $data, 1;
168             }
169             # Date
170             elsif ($ctype == 7) {
171 2         6 my ($year, $month, $day) = unpack "nCC", $data;
172 2         12 _debug_print(" col: $cid date: $day/$month/$year\n");
173 2         18 $record{col}{$cid} = sprintf("%02d/%02d/%04d",
174             $day, $month, $year);
175 2         6 $data = substr $data, 4;
176             }
177             # Time
178             elsif ($ctype == 8) {
179             # Meridian doesn't seem to get used. Just a null byte?
180 2         7 my ($meridian, $hour, $minute, $second) = unpack("C4", $data);
181 2         10 _debug_printf(" col: %02d time: %02d:%02d:%02d %d\n", $cid,
182             $hour, $minute, $second, $meridian);
183 2         10 $record{col}{$cid} = sprintf("%02d:%02d:%02d", $hour, $minute, $second);
184 2         6 $data = substr $data, 4;
185             }
186             # Equation type
187             elsif ($ctype == 9) {
188             # We aren't going to do anything with these.
189 0         0 _debug_printf(" * equation type found\n");
190 0         0 $record{raw}{$cid} = substr $data, 0, 4;
191 0         0 $data = substr $data, 4;
192             }
193             # Memo field types.
194             elsif ($ctype == 10) {
195 2         2 my ($sdat, $slen);
196 2         7 ($sdat) = unpack "n/a", $data;
197 2         151 $slen = length($sdat);
198 2         6 _debug_printf(" col: %02d strlen: %02d data: '%s'\n",
199             $cid, $slen, $sdat);
200 2         43 $record{col}{$cid} = $sdat;
201 2         10 $data = substr $data, $slen+3;
202             }
203             # Foreign link types.
204             elsif ($ctype == 12) {
205 0         0 my ($ltype, $slen, $sdat);
206 0         0 ($ltype) = unpack "C", $data;
207 0 0       0 if ($ltype == 1) {
    0          
208             # Link is stored as text!
209 0         0 ($ltype, $sdat) = unpack "CC/a", $data;
210 0         0 $slen = length($sdat);
211 0         0 _debug_printf(" col: %02d strlen: %02d foo: %02d data: '%s'\n",
212             $cid, $slen, $ltype, $sdat);
213 0         0 $record{col}{$cid} = $sdat;
214 0         0 $record{raw}{$cid} = substr $data, 0, $slen + 3;
215 0         0 $data = substr $data, $slen+3;
216             } elsif ($ltype == 11) {
217             # What does this signify? Addressbook link?
218 0         0 ($ltype, $sdat) = unpack "C N", $data;
219 0         0 _debug_printf(" col: %02d ltype: %02d data: '%s'\n", $cid, $ltype, $sdat);
220 0         0 $record{col}{$cid} = $sdat;
221 0         0 $record{raw}{$cid} = substr $data, 0, 5;
222 0         0 $data = substr $data, 5;
223             } else {
224 0         0 _debug_print(" Column type: $ctype Column ID: $cid\n",
225             " Link Type: $ltype\n",
226             " Record data: ", safestr($data), "\n");
227 0         0 $data = '';
228             }
229             }
230             # Addressbook link
231             elsif ($ctype == 15) {
232 0         0 my (@foo, $slen, $sdat);
233 0         0 (@foo[0 .. 3], $sdat) = unpack "C4C/a", $data;
234 0         0 $slen = length($sdat);
235 0         0 _debug_printf(" col: %02d foo: [%s] data: '%s'\n", $cid, join(',',@foo), $sdat);
236 0         0 $record{col}{$cid} = $sdat;
237 0         0 $record{raw}{$cid} = substr $data, 0, $slen + 6;
238 0         0 $data = substr $data, $slen+6;
239             }
240             # Another equation sort of thing.
241             elsif ($ctype == 19) {
242             # We can't do anything with these, either.
243 0         0 _debug_printf(" * type 19 thingie found\n");
244 0         0 $record{raw}{$cid} = substr $data, 0, 5;
245 0         0 $data = substr($data, 5);
246             } else {
247 23         66 _debug_print(" Column type: $ctype\n",
248             " Column ID: $cid\n",
249             " Record data: ", safestr($data), "\n");
250 23         77 $data = '';
251             }
252             }
253 23         28 push @{$self->{db_records}}, \%record;
  23         69  
254             } else {
255             #_debug_print " Column type: $ctype\n";
256             #_debug_print " Column ID: $cid\n";
257 14         50 _debug_print(" Record data: ", safestr($record{data}), "\n");
258 14         39 $data = '';
259             }
260              
261 197         381 $record{type} = $record_type;
262              
263 197         697 return \%record;
264             }
265              
266             # This one is going to be tricky!
267             sub PackRecord {
268 99     99 1 3714 my $self = shift @_;
269 99         113 my $record = shift @_;
270 99         103 my ($retval, $ctype);
271              
272             # Create/pack our list record.
273 99 100 100     588 if ($record->{type} > 2 && $record->{type} < 82) {
    100          
274 79         1053 my $cid = $record->{type} - 2;
275 79 50 33     235 if (defined $self->{list_mod}{$cid} &&
276             $self->{list_mod}{$cid} == 1) {
277 0         0 _debug_print("modified\n");
278 0         0 my $num = scalar(@{$self->{list}{$cid}});
  0         0  
279 0         0 $retval = pack("C*", $record->{type}, $num, 1 .. $num);
280 0         0 $retval .= join("\000", @{$self->{list}{$cid}});
  0         0  
281 0         0 $retval .= "\000\000";
282 0         0 _debug_print("RETVAL: ", safestr($retval), "\n");
283 0         0 _debug_print("DATA: ", safestr($record->{data}), "\n");
284             } else {
285 79         145 $retval = $record->{data};
286             }
287             }
288             # Initialize data type.
289             elsif ($record->{type} == 87) {
290 12 100       37 if (!defined $record->{idnum}) {
291 1         5 $record->{idnum} = ++$self->{high_id};
292             }
293            
294 12         173 $retval = pack("CxN", 87, $record->{idnum});
295 12         2162 foreach my $field (sort { $a <=> $b } keys %{$record->{col}}) {
  29         461  
  12         88  
296 29         57 $ctype = $self->{cols}[$field]{type};
297              
298             # Pack type for this column.
299 29         54 $retval .= pack("C2", $ctype, $field);
300              
301             # Pack column data.
302             # Normal text.
303 29 100       90 if ($ctype == 1) {
    100          
    100          
    100          
    50          
    100          
    100          
    100          
304 11         54 $retval .= pack("C/a*x", $record->{col}{$field});
305             }
306             # Integer
307             elsif ($ctype == 2) {
308 11         144 $retval .= pack("n", $record->{col}{$field});
309             }
310             # Long
311             elsif ($ctype == 3) {
312 1         4 $retval .= pack("N", $record->{col}{$field});
313             }
314             # List
315             elsif ($ctype == 5) {
316 2         8 $retval .= pack("C", $self->list_lookup($field, $record->{col}{$field}));
317             }
318             # Checkbox
319             elsif ($ctype == 6) {
320 0 0       0 $retval .= pack("C", ($record->{col}{$field}) ? 1 : 0);
321             }
322             # Date
323             elsif ($ctype == 7) {
324 1         11 my (@date) = split('/',$record->{col}{$field});
325 1         5 $retval .= pack("nCC", int($date[2]), int($date[1]), int($date[0]));
326             }
327             # Time
328             elsif ($ctype == 8) {
329             # Why the null byte here?
330 1         5 my (@time) = split(':', $record->{col}{$field});
331 1         4 $retval .= pack("xC3", @time);
332             }
333             # Memo
334             elsif ($ctype == 10) {
335 1         4 $retval .= pack("n/a*x", $record->{col}{$field});
336             }
337             # Something we don't know about yet.
338             else {
339             # What do we do with 9, 12, 15, and 19? Especially 12 and 15.
340             # We can't handle it, so we just pass the data through.
341 1         5 _debug_print("Found record I don't know, $field, $ctype\n");
342 1         3 $retval .= $record->{raw}{$field};
343             }
344             }
345              
346 12 50       39 if ($retval ne $record->{data}) {
347 12         17 $retval .= "\000\000\000"; # Signals end of record, or something?
348             }
349            
350 12         27 _debug_print("RETVAL: ", safestr($retval), "\n",
351             "DATA: ", safestr($record->{data}), "\n");
352             } else {
353 8         21 _debug_print("*RETVAL: ", safestr($record->{data}), "\n");
354 8         16 $retval = $record->{data};
355             }
356              
357 99         562 return $retval;
358              
359             }
360              
361             # Special stuff.
362             sub db_records {
363 1     1 0 112 my $self = shift;
364              
365 1         4 return @{$self->{db_records}};
  1         7  
366             }
367              
368             sub get_colnum {
369 2     2 0 4 my $self = shift;
370 2         4 my $name = shift;
371              
372 2         3 for (my $i = 1; $i <= $#{$self->{cols}}; $i++) {
  20         50  
373 18 50       89 if ($self->{cols}[$i]{name} eq $name) {
374 0         0 return $i;
375             }
376             }
377              
378 2         6 return -1;
379             }
380              
381             sub get_colarray {
382 0     0 0 0 my $self = shift;
383 0         0 my $name = shift;
384 0         0 my $cid = $self->get_colnum($name);
385 0         0 my @ret;
386              
387 0         0 foreach my $rec (@{$self->{records}}) {
  0         0  
388 0 0       0 if ($rec->{type} == 87) {
389 0 0       0 push @ret, $rec->{col}{$cid}
390             unless $rec->{attributes}{deleted};
391             }
392             }
393              
394 0         0 return @ret;
395             }
396              
397             sub columns {
398 2     2 0 236 my $self = shift;
399 2         3 my @ret;
400            
401 2         5 for (my $i = 0; $i <= $#{$self->{cols}}; $i++) {
  22         7085  
402 20 100       59 if (defined $self->{cols}[$i]) {
403 18         56 push @ret, $self->{cols}[$i]{name};
404             }
405             }
406              
407 2         23 return @ret;
408             }
409              
410             # Modify lists
411             sub list_lookup {
412 2     2 0 5 my $self = shift;
413 2         3 my $cid = shift;
414 2         3 my $txt = shift;
415              
416 2         3 for (my $i = 0; $i <= $#{$self->{list}{$cid}}; $i++) {
  4         14  
417 4 100       12 if ($self->{list}{$cid}[$i] eq $txt) {
418 2         10 return $i + 1;
419             }
420             }
421 0         0 return 0;
422             }
423              
424             sub add_to_list {
425 0     0 0 0 my $self = shift;
426 0         0 my $cid = shift;
427 0         0 my $item = shift;
428              
429 0         0 _debug_print("Adding $item to $cid\n");
430 0         0 push @{$self->{list}{$cid}}, $item;
  0         0  
431 0         0 $self->{list_mod}{$cid} = 1;
432             }
433              
434             # Messy. Called as $self->set($record, $column_name, $value);
435             sub set {
436 2     2 0 795 my $self = shift;
437 2         4 my $record = shift;
438 2         3 my $column = shift;
439 2         4 my $data = shift;
440              
441 2         8 my $cnum = $self->get_colnum($column);
442              
443 2 50       12 if ($cnum > 0) {
444 0         0 $record->{col}{$cnum} = $data;
445             }
446             }
447              
448             sub get {
449 0     0 0 0 my $self = shift;
450 0         0 my $record = shift;
451 0         0 my $column = shift;
452              
453 0         0 my $cnum = $self->get_colnum($column);
454              
455 0 0       0 if ($cnum > 0) {
456 0 0       0 if (defined $record->{col}{$cnum}) {
457 0         0 return $record->{col}{$cnum};
458             } else {
459 0         0 return '';
460             }
461             } else {
462 0         0 return undef;
463             }
464             }
465              
466              
467             sub _debug_printf {
468 97 50   97   401 printf STDERR @_ if $DEBUG;
469             }
470              
471             sub _debug_print {
472 114 50   114   446 print STDERR @_ if $DEBUG;
473             }
474              
475             sub safestr ($) {
476 73     73 0 122 my $tmp = shift;
477            
478 73         364 $tmp =~ s/([^a-zA-Z0-9\!\?\+\'\" ])/unpack("C", $1) . '.'/eg;
  2781         21992  
479              
480 73         1744 return $tmp;
481             }
482              
483             1;
484             __END__