1package Onis::Parser::Persistent;
2
3=head1 Parser::Persistent
4
5This module provides routines used for ``statefull parsing'' or however
6you want to call what's going on. It is used to find an absolute time in
7the logfile and rewind the file or seek further, whichever is neccessary.
8
9=head1 Usage
10
11use Parser::Persistent qw#set_absolute_time add_relative_time get_state
12newfile %MONTHNAMES#;
13
14set_absolute_time ($year, $month, $day, $hour, $min, $sec);
15add_relative_time ($hour, $minute);
16get_state ();
17newfile ();
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
25use strict;
26use warnings;
27
28use vars qw#%MONTHNAMES @MONTHNUMS#;
29
30use Exporter;
31use Time::Local;
32use 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
55our $TimeNewest = Onis::Data::Persistent->new ('TimeNewest', 'inode', 'time');
56our $AbsoluteTime = 0;
57our $TimeData =
58{
59	Seeking		=> 1,
60	NeedsRewind	=> 1,
61	Duration	=> 0
62};
63our $CurFile = 0;
64
65my $VERSION = '$Id: Persistent.pm,v 1.7 2004/01/07 20:31:17 octo Exp $';
66print STDERR $/, __FILE__, ": $VERSION" if ($::DEBUG);
67
68return (1);
69
70=head1 Exported routines
71
72=head2 get_state ();
73
74This routine decides between four states: ``don't have time'', ``line is
75old'', ``parse this line'' and ``rewind file and begin again''. The last
76three imply that the time has been set.
77
78B<rewind file and begin again>: The parser should tell the main routine to
79rewind the file and start reading at the beginning again. This is
80neccessay if we went past the point where we left off during the last run.
81In this case zero is returned.
82
83B<parse this line>: If the parser should parse the line and call I<store>
84with the results this routine returns one.
85
86B<line is old>: If the parser should simply ignore this line and
87continue with the next one this routine returns three.
88
89B<don't have time>: No time is set. Ignore lines until a date is found.
90
91The desire is to pass the return code back to the main routine, unless it
92is equal to one. The parser should then return one upon success, two upon
93failure.
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
103sub 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
242This routine does two different things, depending on wether or not the
243absolute time is known.
244
245If the absolute time is not known, it will add up the seconds since the
246start of the file. When we know the absolute time later we can subtract
247that value to get the absolute time of the beginning of the file.
248
249If the absolute time is known it simply adds to it to keep it up to date.
250
251=cut
252
253sub 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
301As the name suggests this routine sets the absolute time.
302
303=cut
304
305sub 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
334sub get_absolute_time
335{
336	return ($AbsoluteTime);
337}
338
339=head2 newfile ();
340
341Resets the internal counters to be ready for another file.
342
343=cut
344
345sub 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}
360