File Coverage

blib/lib/Palm/MaTirelire/AccountsV1.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             #
2             # Author : Maxime Soulé
3             # Created On : Mon Aug 30 00:36:38 2004
4             # Last Modified By: Maxime Soule
5             # Last Modified On: Mon May 3 15:00:42 2010
6             # Update Count : 2
7             #
8             # Copyright (C) 2005, Maxime Soulé
9             # You may distribute this file under the terms of the Artistic
10             # License, as specified in the README file.
11             #
12              
13             package Palm::MaTirelire::AccountsV1;
14              
15 1     1   1426 use strict;
  1         2  
  1         39  
16              
17 1     1   5 use Palm::BlockPack;
  1         3  
  1         31  
18              
19 1     1   5 use Palm::MaTirelire;
  1         2  
  1         14  
20 1     1   543 use Palm::StdAppInfo();
  0            
  0            
21              
22             use base qw(Palm::MaTirelire Palm::StdAppInfo);
23              
24             our $VERSION = '1.0';
25              
26              
27             use constant UNKNOWN_MODE => ((1 << 5) - 1);
28             use constant UNKNOWN_TYPE => ((1 << 6) - 1);
29              
30             my $TRANS_BLOCK = Palm::BlockPack->new
31             ('DateType' => [ 'date_' => 'now' ],
32             'TimeType' => [ 'time_' => 'now' ],
33            
34             '-N' => [ 'amount' => 0 ],
35            
36             UInt32 => [
37             [ 'checked:1' => 0 ],
38             'repeat:1',
39             [ 'mode:5' => UNKNOWN_MODE ],
40             [ 'type:6' => UNKNOWN_TYPE ],
41             'check_num:1',
42             'xfer:1',
43             [ 'marked:1' => 0 ],
44             [ 'alarm:1' => 0 ],
45             'xfer_cat:1',
46             'value_date:1',
47             [ 'reserved:*' => 0 ],
48             ],
49             );
50              
51             my $TRANS_CHECKNUM_BLOCK = Palm::BlockPack->new(N => 'check_num');
52              
53             my $TRANS_VALUEDATE_BLOCK = Palm::BlockPack->new(DateType => '');
54              
55             my $TRANS_REPEAT_BLOCK = Palm::BlockPack->new
56             (UInt16 => [
57             'repeat_type:2',
58             'repeat_freq:6',
59             'reserved:*',
60             ],
61             skip => [ 2 => "\xff" ], # End date is not used in M1 and must be -1
62             );
63              
64             my $TRANS_XFER_BLOCK = Palm::BlockPack->new(N => 'xfer');
65              
66             my $TRANS_DESCRIPTION_BLOCK = Palm::BlockPack->new
67             ('Z*' => [ 'description' => '' ]);
68              
69              
70             sub import
71             {
72             &Palm::PDB::RegisterPDBHandlers(__PACKAGE__, [ "MaTi", 'Data' ]);
73             }
74              
75              
76             sub new
77             {
78             my $classname = shift;
79             my $self = $classname->SUPER::new(@_);
80             # Create a generic PDB. No need to rebless it,
81             # though.
82              
83             # Creator for V1 is not the same
84             $self->{creator} = "MaTi";
85              
86             $self->{name} = "MaTirelire Data"; # Default
87             $self->{type} = "Data";
88              
89             # Add the standard AppInfo block stuff
90             &Palm::StdAppInfo::seed_StdAppInfo($self->{appinfo});
91              
92             return $self;
93             }
94              
95              
96             sub ParseAppInfoBlock
97             {
98             my $self = shift;
99             my $data = shift;
100             my $appinfo = {};
101             my $std_len;
102              
103             # Get the standard parts of the AppInfo block
104             $std_len = &Palm::StdAppInfo::parse_StdAppInfo($appinfo, $data);
105              
106             return $appinfo;
107             }
108              
109              
110             sub PackAppInfoBlock
111             {
112             my $self = shift;
113             my $retval;
114              
115             # Pack the AppInfo block
116             $retval = &Palm::StdAppInfo::pack_StdAppInfo($self->{appinfo});
117              
118             return $retval;
119             }
120              
121              
122             sub new_Record
123             {
124             my $classname = shift;
125             my $retval = $classname->SUPER::new_Record(@_);
126              
127             $TRANS_BLOCK->init_block($retval);
128             $TRANS_DESCRIPTION_BLOCK->init_block($retval);
129            
130             return $retval;
131             }
132              
133              
134             sub ParseRecord
135             {
136             my $self = shift;
137             my %record = @_;
138              
139             delete $record{offset}; # This is useless
140             my $data = delete $record{data};
141              
142             $record{size} = length $data; # Used in validRecords method
143              
144             $TRANS_BLOCK->unpack_block(\$data, \%record, 1);
145              
146             # Cheque number
147             if (delete $record{check_num})
148             {
149             $TRANS_CHECKNUM_BLOCK->unpack_block(\$data, \%record, 1);
150             }
151              
152             # Value date
153             if (delete $record{value_date})
154             {
155             $record{value_date} = {};
156             $TRANS_VALUEDATE_BLOCK->unpack_block(\$data, $record{value_date}, 1);
157             }
158              
159             # Repetition
160             if (delete $record{repeat})
161             {
162             $record{repeat} = {};
163             $TRANS_REPEAT_BLOCK->unpack_block(\$data, $record{repeat}, 1);
164             }
165              
166             # Transfer
167             if (delete $record{xfer})
168             {
169             $TRANS_XFER_BLOCK->unpack_block(\$data, \%record, 1);
170             }
171             else
172             {
173             delete $record{xfer_cat};
174             }
175              
176             $TRANS_DESCRIPTION_BLOCK->unpack_block(\$data, \%record);
177              
178             #if (length($data) > 0)
179             #{
180             # use Data::Dumper;
181             #
182             # print Dumper(\%record);
183             # print Dumper($data);
184             #}
185              
186             return \%record;
187             }
188              
189              
190             sub PackRecord
191             {
192             my $self = shift;
193             my $record = shift;
194             my $pack;
195              
196             # Small check...
197             if ($record->{xfer_cat})
198             {
199             if (not defined $record->{xfer} or $record->{xfer} >= 16)
200             { delete $record->{xfer_cat} }
201             }
202              
203             $pack = $TRANS_BLOCK->pack_block($record);
204              
205             # Cheque number
206             if ($record->{check_num})
207             {
208             $pack .= $TRANS_CHECKNUM_BLOCK->pack_block($record);
209             }
210              
211             # Value date
212             if ($record->{value_date})
213             {
214             $pack .= $TRANS_VALUEDATE_BLOCK->pack_block($record->{value_date});
215             }
216              
217             # Repetition
218             if ($record->{repeat})
219             {
220             $pack .= $TRANS_REPEAT_BLOCK->pack_block($record->{repeat});
221             }
222              
223             # Transfer
224             if ($record->{xfer})
225             {
226             $pack .= $TRANS_XFER_BLOCK->pack_block($record);
227             }
228              
229             $pack .= $TRANS_DESCRIPTION_BLOCK->pack_block($record);
230              
231             return $pack;
232             }
233              
234              
235             sub sortRecords
236             {
237             my $self = shift;
238              
239             @{$self->{records}} = sort
240             {
241             # Pack date and time on an 31 bits width integer...
242              
243             # 11 bits: 30 .. 20
244             (($a->{date_year} << 20)
245             # 4 bits: 19 .. 16
246             | ($a->{date_month} << 16)
247             # 5 bits: 15 .. 11
248             | ($a->{date_day} << 11)
249             # 5 bits: 10 .. 6
250             | ($a->{time_hour} << 6)
251             # 6 bits: 5 .. 0 11 bits: 30 .. 20
252             | $a->{time_min}) <=> (($b->{date_year} << 20)
253             # 4 bits: 19 .. 16
254             | ($b->{date_month} << 16)
255             # 5 bits: 15 .. 11
256             | ($b->{date_day} << 11)
257             # 5 bits: 10 .. 6
258             | ($b->{time_hour} << 6)
259             # 6 bits: 5 .. 0
260             | $b->{time_min})
261             }
262             @{$self->{records}};
263             }
264              
265              
266             #
267             # Returns a list (number of deleted records, number of errors corrected)
268             sub validRecords ($;$)
269             {
270             my($self, $verbose) = @_;
271              
272             # $verbose can be a reference on a filehandle
273             $verbose = \*STDOUT if $verbose && not ref $verbose;
274              
275             my $deleted_records = 0;
276             my $errors_found = 0;
277              
278             my @to_del;
279             my %ids;
280             my $index = 0;
281              
282             foreach my $rec (@{$self->{records}})
283             {
284             if ($rec->{size} == 0)
285             {
286             print $verbose ("Record #$index (cat=$rec->{category}) "
287             . "UniqueID $rec->{id}\n"
288             . "**** empty => deleted\n")
289             if $verbose;
290              
291             push(@to_del, $index);
292             }
293             else
294             {
295             $ids{$rec->{id}} = 1;
296             }
297             $index++;
298             }
299              
300             if (@to_del)
301             {
302             $deleted_records = @to_del;
303              
304             foreach my $idx (reverse @to_del)
305             {
306             splice @{$self->{records}}, $idx, 1;
307             }
308             }
309              
310             my %links;
311             $index = 0;
312              
313             foreach my $rec (@{$self->{records}})
314             {
315             my @err_msg;
316              
317             # Repeat
318             if ($rec->{repeat})
319             {
320             if ($rec->{repeat}{repeat_freq} == 0
321             or $rec->{repeat}{repeat_type} > 2
322             or $rec->{repeat}{reserved} != 0)
323             {
324             push(@err_msg, "deleted repeat option");
325             delete $rec->{repeat};
326             }
327             }
328              
329             # Xfer
330             if (exists $rec->{xfer})
331             {
332             my $error = 0;
333              
334             if ($rec->{xfer_cat})
335             {
336             if ($rec->{xfer} >= 16)
337             {
338             push(@err_msg, "invalid account (xfer) link");
339              
340             $error = 1;
341             }
342             }
343             else
344             {
345             if (exists $ids{$rec->{xfer}})
346             {
347             $links{$rec->{id}} = $rec->{xfer};
348             }
349             else
350             {
351             push(@err_msg, "invalid transaction (xfer) link");
352              
353             $error = 1;
354             }
355             }
356              
357             if ($error)
358             {
359             delete @$rec{qw(xfer xfer_cat)};
360             push(@err_msg, "deleted transfer option");
361             }
362             }
363              
364             # No account (not possible ?)
365             if ($rec->{category} eq '')
366             {
367             $rec->{attributes} = { dirty => 1 };
368             push(@err_msg, "not associated to an account");
369             }
370              
371             if (@err_msg)
372             {
373             if ($verbose)
374             {
375             print $verbose
376             ("Record #$index (account=$rec->{category}) "
377             . "UniqueID $rec->{id}\n"
378             . "$rec->{date_year}/$rec->{date_month}/$rec->{date_day} "
379             . "$rec->{time_min}:$rec->{time_hour} "
380             . "amount = ", $rec->{amount} / 100, "\n",
381             " ");
382              
383             print $verbose join("\n ", @err_msg), "\n";
384             }
385              
386             $rec->{attributes}{Dirty} = 1;
387              
388             $errors_found++;
389             }
390             }
391              
392             while (my($id, $link) = each %links)
393             {
394             if (not exists $links{$link})
395             {
396             print $verbose ("**** Xfer: $id => $link but $link is not linked,",
397             " corrected.\n")
398             if $verbose;
399              
400             my $rec = $self->findRecordByID($link);
401             $rec->{xfer} = $id;
402             delete $rec->{xfer_cat};
403              
404             $rec->{attributes}{Dirty} = 1;
405             }
406             elsif ($links{$link} != $id)
407             {
408             print $verbose
409             "**** Xfer: $id => $link but $link => $links{$link}\n"
410             if $verbose;
411             }
412             }
413              
414             return ($deleted_records, $errors_found);
415             }
416              
417             1;
418             __END__