A bug in lib/Onis/Parser/Persistent.pm has been fixed.
[onis.git] / lib / Onis / Parser / Persistent.pm
1 package Onis::Parser::Persistent;
2
3 =head1 Parser::Persistent
4
5 This module provides routines used for ``statefull parsing'' or however
6 you want to call what's going on. It is used to find an absolute time in
7 the logfile and rewind the file or seek further, whichever is neccessary.
8
9 =head1 Usage
10
11 use Parser::Persistent qw#set_absolute_time add_relative_time get_state
12 newfile %MONTHNAMES#;
13
14 set_absolute_time ($year, $month, $day, $hour, $min, $sec);
15 add_relative_time ($hour, $minute);
16 get_state ();
17 newfile ();
18
19 =cut
20
21 # This module was quite hard to write, so I guess it's hard to understand,
22 # too. I'll try to explain as much as possible, but it twisted my mind
23 # more than once since it actually worked. Good luck :)
24
25 use strict;
26 use warnings;
27
28 use vars qw#%MONTHNAMES @MONTHNUMS#;
29
30 use Exporter;
31 use Time::Local;
32 use Onis::Data::Persistent;
33
34 @Onis::Parser::Persistent::EXPORT_OK = qw/set_absolute_time get_absolute_time add_relative_time get_state newfile %MONTHNAMES @MONTHNUMS/;
35 @Onis::Parser::Persistent::ISA = ('Exporter');
36
37 %MONTHNAMES =
38 (
39         Jan     => 0,
40         Feb     => 1,
41         Mar     => 2,
42         Apr     => 3,
43         May     => 4,
44         Jun     => 5,
45         Jul     => 6,
46         Aug     => 7,
47         Sep     => 8,
48         Oct     => 9,
49         Nov     => 10,
50         Dec     => 11
51 );
52
53 @MONTHNUMS = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
54
55 our $TimeNewest = Onis::Data::Persistent->new ('TimeNewest', 'inode', 'time');
56 our $AbsoluteTime = 0;
57 our $TimeData =
58 {
59         Seeking         => 1,
60         NeedsRewind     => 1,
61         Duration        => 0
62 };
63 our $CurFile = 0;
64
65 my $VERSION = '$Id: Persistent.pm,v 1.7 2004/01/07 20:31:17 octo Exp $';
66 print STDERR $/, __FILE__, ": $VERSION" if ($::DEBUG);
67
68 return (1);
69
70 =head1 Exported routines
71
72 =head2 get_state ();
73
74 This routine decides between four states: ``don't have time'', ``line is
75 old'', ``parse this line'' and ``rewind file and begin again''. The last
76 three imply that the time has been set.
77
78 B<rewind file and begin again>: The parser should tell the main routine to
79 rewind the file and start reading at the beginning again. This is
80 neccessay if we went past the point where we left off during the last run.
81 In this case zero is returned.
82
83 B<parse this line>: If the parser should parse the line and call I<store>
84 with the results this routine returns one.
85
86 B<line is old>: If the parser should simply ignore this line and
87 continue with the next one this routine returns three.
88
89 B<don't have time>: No time is set. Ignore lines until a date is found.
90
91 The desire is to pass the return code back to the main routine, unless it
92 is equal to one. The parser should then return one upon success, two upon
93 failure.
94
95 =cut
96
97 # Return values:
98 # 0 == rewind file
99 # 1 == line parsed
100 # 2 == unable to parse
101 # 3 == line old
102 # 4 == don't have date
103 sub get_state
104 {
105         my ($newest) = $TimeNewest->get ($CurFile);
106         $newest ||= 0;
107
108         # We're seeking for an absolute date.
109         if ($TimeData->{'Seeking'})
110         {
111                 # We're still seeking for a date..
112                 if (!$AbsoluteTime)
113                 {
114                         return (4);
115                 }
116
117                 # we're seeking past this date
118                 elsif ($newest)
119                 {
120                         # We have a date and it's before the date we're seeking for.
121                         # So we continue seeking..
122                         if ($AbsoluteTime <= $newest)
123                         {
124                                 if ($::DEBUG & 0x40)
125                                 {
126                                         print STDERR $/, __FILE__, ": Absolute time found. Is earlier than the newest time. Disabling ``NeedsRewind''.";
127                                 }
128                                 $TimeData->{'NeedsRewind'} = 0;
129                                 
130                                 # line old. ignore it
131                                 return (3);
132                         }
133
134                         # We went too far, so we have to go back.
135                         # We substract the duration since the beginning
136                         # of the file and tell the main routine to rewind
137                         # the file.
138                         elsif ($TimeData->{'NeedsRewind'})
139                         {
140                                 my $found;
141                                 my $set;
142                                 my $diff = $TimeData->{'Duration'};
143                         
144                                 $found = localtime ($AbsoluteTime) if ($::DEBUG & 0x40);
145
146                                 $AbsoluteTime -= $diff;
147
148                                 if ($::DEBUG & 0x40)
149                                 {
150                                         $set = localtime ($AbsoluteTime);
151                                         print STDERR $/, __FILE__, ": Absolute time ``$found'' found. Setting back $diff seconds to ``$set''" if ($::DEBUG & 0x40);
152                                 }
153
154                                 $TimeData->{'NeedsRewind'} = 0;
155                                 delete ($TimeData->{'LastHourSeen'});
156                                 delete ($TimeData->{'LastMinuteSeen'});
157
158                                 # rewind file
159                                 return (0);
160                         }
161
162                         # This is the line we were looking for.
163                         # It's past $newest, but not the first absolute time found.
164                         else
165                         {
166                                 print STDERR $/, __FILE__, ": Seeking done." if ($::DEBUG & 0x40);
167                                 $TimeData->{'Seeking'} = 0;
168                         }
169                 }
170                                 
171                 # $newest is not set but we have an absolute date.
172                 else
173                 {
174                         print STDERR $/, __FILE__, ": \$newest not set. Setting it to \$AbsoluteTime." if ($::DEBUG & 0x40);
175                         
176                         $TimeData->{'Seeking'} = 0;
177
178                         # We had to read some lines to get an absolute date.
179                         # Lets go back.
180                         if ($TimeData->{'Duration'})
181                         {
182                                 my $diff = $TimeData->{'Duration'};
183                                 if ($::DEBUG & 0x40)
184                                 {
185                                         my $time = localtime ($AbsoluteTime);
186                                         print STDERR $/, __FILE__, ": AbsolutTime found is ``$time'', but we are $diff seconds into the file.";
187                                 }
188                                 
189                                 $AbsoluteTime -= $TimeData->{'Duration'};
190                                 
191                                 if ($::DEBUG & 0x40)
192                                 {
193                                         my $time = localtime ($AbsoluteTime);
194                                         print STDERR $/, __FILE__, ": Corrected AbsolutTime (set back $diff seconds) is ``$time''";
195                                 }
196
197                                 delete ($TimeData->{'LastHourSeen'});
198                                 delete ($TimeData->{'LastMinuteSeen'});
199
200                                 # tell parser to rewind file
201                                 return (0);
202                         }
203
204                         # We didn't miss anything, so we don't need to rewind the file.
205                         else
206                         {
207                                 $newest = $AbsoluteTime;
208                                 $TimeNewest->put ($CurFile, $newest);
209                                 return (1);
210                         }
211                 }
212         }
213
214         # Ok, we're in the past. Let's skip that line..
215         # This is NOT supposed to happen. If it does, it's a bug!
216         elsif ($AbsoluteTime < $newest)
217         {
218                 my $now =  localtime ($AbsoluteTime);
219                 my $then = localtime ($newest);
220                 print STDERR $/, __FILE__, ": Absolute time set, but we're in the past. Skipping. ($now < $then)" if ($::DEBUG & 0x40);
221                 return (3);
222         }
223
224         # We're up to date. $TimeNewest needs to be set accordingly..
225         elsif ($AbsoluteTime != $newest)
226         {
227                 if ($::DEBUG & 0x40)
228                 {
229                         my $time = localtime ($AbsoluteTime);
230                         print STDERR $/, __FILE__, ": Updating. Newest time is now ``$time''";
231                 }
232
233                 $newest = $AbsoluteTime;
234                 $TimeNewest->put ($CurFile, $newest);
235         }
236
237         return (1);
238 }
239
240 =head2 add_relative_time ($hour, $min);
241
242 This routine does two different things, depending on wether or not the
243 absolute time is known.
244
245 If the absolute time is not known, it will add up the seconds since the
246 start of the file. When we know the absolute time later we can subtract
247 that value to get the absolute time of the beginning of the file.
248
249 If the absolute time is known it simply adds to it to keep it up to date.
250
251 =cut
252
253 sub add_relative_time
254 {
255         my $this_hour = shift;
256         my $this_minute = shift;
257
258         my $this_seconds = ($this_hour * 3600) + ($this_minute * 60);
259         
260         if ((defined ($TimeData->{'LastHourSeen'}))
261                         and (defined ($TimeData->{'LastMinuteSeen'})))
262         {
263                 my $diff = 0;
264                 my $last_hour = $TimeData->{'LastHourSeen'};
265                 my $last_minute = $TimeData->{'LastMinuteSeen'};
266                 my $last_seconds = ($last_hour * 3600) + ($last_minute * 60);
267                 
268                 if ($last_seconds > $this_seconds)
269                 {
270                         $this_seconds += 86400; # one day
271                 }
272
273                 $diff = $this_seconds - $last_seconds;
274
275                 if ($::DEBUG & 0x40)
276                 {
277                         print STDERR $/, __FILE__, ': ';
278                         printf STDERR ("diff ('%02u:%02u', '%02u:%02u') = %u seconds",
279                                 $last_hour, $last_minute,
280                                 $this_hour, $this_minute,
281                                 $diff);
282                 }
283                 
284                 # FIXME needs testing!
285                 if (!$AbsoluteTime)
286                 {
287                         $TimeData->{'Duration'} += $diff;
288                 }
289                 else
290                 {
291                         $AbsoluteTime += $diff;
292                 }
293         }
294
295         $TimeData->{'LastHourSeen'} = $this_hour;
296         $TimeData->{'LastMinuteSeen'} = $this_minute;
297 }
298
299 =head2 set_absolute_time ($year, $month, $day, $hour, $min, $sec);
300
301 As the name suggests this routine sets the absolute time.
302
303 =cut
304
305 sub set_absolute_time
306 {
307         my $year  = shift;
308         my $month = shift;
309         my $day   = shift;
310         my $hour  = shift;
311         my $min   = shift;
312         my $sec   = shift;
313
314         $year -= 1900;
315
316         my $time = timelocal ($sec, $min, $hour, $day, $month, $year);
317         print STDERR $/, __FILE__, ": Set absolute time to ", scalar (localtime ($time)) if ($::DEBUG & 0x40);
318
319         # Add diff if this is the first 
320         if (!$AbsoluteTime)
321         {
322                 add_relative_time ($hour, $min);
323         }
324         else
325         {
326                 # FIXME neccessary?
327                 $TimeData->{'LastHourSeen'}   = $hour;
328                 $TimeData->{'LastMinuteSeen'} = $min;
329         }
330         
331         $AbsoluteTime = $time;
332 }
333
334 sub get_absolute_time
335 {
336         return ($AbsoluteTime);
337 }
338
339 =head2 newfile ();
340
341 Resets the internal counters to be ready for another file.
342
343 =cut
344
345 sub newfile
346 {
347         my $inode = shift;
348
349         my ($time) = $TimeNewest->get ($inode);
350         $time ||= 0;
351         $TimeNewest->put ($inode, $time);
352         
353         $AbsoluteTime = 0;
354         $TimeData->{'Duration'} = 0;
355         $TimeData->{'NeedsRewind'} = 1;
356         $TimeData->{'Seeking'} = 1;
357         delete ($TimeData->{'LastHourSeen'});
358         delete ($TimeData->{'LastMinuteSeen'});
359 }