Fixed user config: User `ignore' is ignored again.
[onis.git] / onis
1 #!/usr/bin/perl
2 ##########################################################################
3 #    onis 0.8.0                                               2005-04-17 #
4 #---=============--------------------------------------------------------#
5 # Language: Perl                                                         #
6 # Purpose:  Generating statistics                                        #
7 # Input:    IRC-Logfiles                                                 #
8 # Output:   One HTML file                                                #
9 # Version:  0.8.0 (unstable)                                             #
10 # License:  GPL                                                          #
11 # Homepage: http://verplant.org/onis/                                    #
12 # Authors:  Florian octo Forster <octo@verplant.org>                     #
13 #           Contributions are listed in THANKS                           #
14 ##########################################################################
15
16 BEGIN
17 {
18         if ($0 =~ m#^(.*)[/\\]#) { chdir ($1); }
19
20         unshift (@INC, 'lib');
21
22         # 0x0010   Language (make not-translated lines red/yellow)
23         # 0x0020   Parser (dropped lines)
24         # 0x0040   Parser (time information)
25         # 0x0100   Data::Core (host unsharp)
26         # 0x0200   Data::Persistent
27         # 0x0400   Data::Core (dump incoming data to stderr)
28         # 0x0800   Data::Core (initializing)
29         # 0x1000   Onis::Users
30         $::DEBUG = 0x0000;
31 }
32
33 use strict;
34 use warnings;
35 use vars qw/$VERSION $REVISION/;
36
37 use Onis::Config qw/get_config parse_argv read_config/;
38 use File::Basename qw/dirname/;
39 use Fcntl qw/:flock/;
40
41 =head1 NAME
42
43 onis - onis not irs stats
44
45 =head1 SYNOPSIS
46
47 B<onis> [I<options>] I<logfile>...
48
49 =head1 DESCRIPTION
50
51 onis is a script that converts IRC logfiles into an HTML statistics page. It
52 provides information about daily channel usage, user activity, and channel
53 trivia. It provides a configurable customization and supports Dancer,
54 dircproxy, eggdrop, irssi, mIRC, and XChat logs. Persistent data (history
55 files) and automatic log purging make onis applicable for a large number of
56 logfiles. It also features a powerful translation infrastructure.
57
58 =cut
59
60 $VERSION = '';
61 $REVISION = '$LastChangedRevision$';
62
63 if (!$VERSION)
64 {
65         $VERSION = $REVISION;
66         $VERSION =~ s/^\D*(\d+).*/r$1/;
67 }
68
69 our $FileInfo;
70 our $PurgeLogs = 0;
71
72 print STDERR $/, __FILE__, ': $Id$' if ($::DEBUG);
73
74 parse_argv (@ARGV);
75 read_config (get_config ('config') ? get_config ('config') : 'onis.conf');
76 read_config (scalar get_config ('theme')) if (get_config ('theme'));
77
78 my $output = get_config ('output');
79 if (!$output)
80 {
81         $output = "reports/onis.html";
82 }
83
84 foreach ('Core', get_config ('plugin'))
85 {
86         my $module = ucfirst (lc ($_));
87         require "Onis/Plugins/$module.pm";
88 }
89
90 if (!get_config ('input'))
91 {
92         # TODO: Make a complete (!) lsit..
93         print STDERR <<EOF;
94
95 Usage: $0 [options] <logfile> [logfile logfile ..]
96
97 Options:
98         --config                Specify alternate config file
99         --output <file>         Defines the file to write the HTML to.
100         --overwrite <bool>      Overwrites files without prompting.
101         --channel <channel>     Defines the channel's name.
102         --logtype <type>        Defines the logfile's type.
103                                 See 'config' for a complete list.
104         --user <name>           Define's the generator's name.
105
106 For a full list of all options please read the ``config'' file.
107 EOF
108         exit (1);
109 }
110
111 if (-e $output)
112 {
113         my $overwrite = 0;
114         if (get_config ('overwrite'))
115         {
116                 my $tmp = lc (get_config ('overwrite'));
117                 if ($tmp eq 'true' or $tmp eq 'yes' or $tmp eq 'on')
118                 {
119                         $overwrite = 1;
120                 }
121         }
122         
123         if (!$overwrite)
124         {
125                 print STDERR <<MESSAGE;
126
127 WARNING: The output file ``$output'' already exists
128
129   You can set the ``overwrite'' option in the config
130   file to disable this dialog.
131
132 MESSAGE
133                 print STDERR 'Are you sure you want to overwrite it? [Y|n] ';
134                 my $answer = <STDIN>;
135                 exit (1) if ($answer =~ m/n/i);
136         }
137 }
138
139 my $logtype = 'Eggdrop';
140 if (get_config ('logtype'))
141 {
142         $logtype = ucfirst (lc (get_config ('logtype')));
143 }
144
145 require "Onis/Parser/$logtype.pm";
146 require Onis::Parser::Persistent;
147 require Onis::Data::Persistent;
148 import Onis::Parser (qw(parse last_date));
149 import Onis::Parser::Persistent (qw(newfile));
150 import Onis::Data::Persistent ();
151
152 $FileInfo = Onis::Data::Persistent->new ('FileInfo', 'inode', qw(mtime));
153
154 if (get_config ('purge_logs'))
155 {
156         my $temp = lc (get_config ('purge_logs'));
157         if (($temp eq 'truncate') or ($temp eq 'shorten'))
158         {
159                 $PurgeLogs = 1;
160         }
161         elsif (($temp eq 'delete') or ($temp eq 'remove')
162                         or ($temp eq 'del'))
163         {
164                 $PurgeLogs = 2;
165         }
166 }
167
168 for (get_config ('input'))
169 {
170         my $file = $_;
171         my $logfile;
172         my $status = 4;
173         my $position = 0;
174         my $mtime;
175         my $size;
176         my $inode;
177
178         ($inode, $size, $mtime) = (stat ($file))[1,7,9];
179
180         print STDERR $/, $/, __FILE__, " --- New File ``$file'' ---" if ($::DEBUG & 0x200);
181         
182         if (!defined ($mtime))
183         {
184                 print STDERR $/, __FILE__, ": Unable to stat file ``$file''";
185                 next;
186         }
187         else
188         {
189                 my ($old_mtime) = $FileInfo->get ($inode);
190
191                 print STDERR $/, __FILE__, ": ``$file'': " if ($::DEBUG & 0x200);
192
193                 if (defined ($old_mtime))
194                 {
195                         if ($old_mtime == $mtime)
196                         {
197                                 print STDERR "File did not change. Skipping." if ($::DEBUG & 0x200);
198                                 next;
199                         }
200                         elsif ($old_mtime < $mtime)
201                         {
202                                 print STDERR "File changed. Reading it again." if ($::DEBUG & 0x200);
203                         }
204                         else
205                         {
206                                 print STDERR "File ``$file'' is older than expected. There might be a problem!";
207                         }
208                 }
209                 else
210                 {
211                         print STDERR "File appears to be new. Reading it." if ($::DEBUG & 0x200);
212                 }
213                 $FileInfo->put ($inode, $mtime);
214         }
215         
216         # truncate
217         if ($PurgeLogs == 1)
218         {
219                 unless (open ($logfile, '+< ' . $file))
220                 {
221                         print STDERR $/, __FILE__, ": Unable to open file ``$file'': $!";
222                         next;
223                 }
224         }
225         else
226         {
227                 unless (open ($logfile, '< ' . $file))
228                 {
229                         print STDERR $/, __FILE__, ": Unable to open file ``$file'': $!";
230                         next;
231                 }
232         }
233         
234         if ($PurgeLogs)
235         {
236                 unless (flock ($logfile, LOCK_EX))
237                 {
238                         print STDERR $/, __FILE__, ": Unable to get an exclusive lock for file ``$file'': $!";
239                         close ($logfile);
240                         next;
241                 }
242         }
243         else
244         {
245                 unless (flock ($logfile, LOCK_SH))
246                 {
247                         print STDERR $/, __FILE__, ": Unable to get a shared lock for file ``$file'': $!";
248                         close ($logfile);
249                         next;
250                 }
251         }
252         
253         newfile ($FileInfo->{$inode});
254         while (<$logfile>)
255         {
256                 s/\n|\r//g;
257                 $status = parse ($_);
258
259                 # 0 == rewind file
260                 # 1 == line parsed
261                 # 2 == unable to parse
262                 # 3 == line old
263                 # 4 == don't have date
264
265                 if ($status == 0)
266                 {
267                         print STDERR $/, __FILE__, ": Rewinding file ``$file''" if ($::DEBUG & 0x200);
268                         seek ($logfile, 0, 0);
269                         $position = 0;
270                 }
271                 elsif (($status == 1) or ($status == 2)
272                                 or ($status == 3))
273                 {
274                         $position = tell ($logfile);
275                 }
276                 elsif ($status == 4)
277                 {
278                         # void
279                 }
280                 else
281                 {
282                         print STDERR $/, __FILE__, ": Parser returned unknown status code: ``$status''";
283                 }
284         }
285
286         if ($PurgeLogs and (($status == 1)
287                                 or ($status == 2)
288                                 or ($status == 3)))
289         {
290                 if (($PurgeLogs > 1)
291                         #and (($position + 1) >= $size)
292                         )
293                 {
294                         # delete file
295                         print STDERR $/, __FILE__, ": Deleting empty file ``$file''" if ($::DEBUG & 0x200);
296                         close ($logfile);
297
298                         if (-w $file)
299                         {
300                                 unless (unlink ($file))
301                                 {
302                                         print STDERR $/, __FILE__, ": Unable to delete empty file ``$file'': $!";
303                                 }
304                                 delete ($FileInfo->{$inode});
305                         }
306                         else
307                         {
308                                 print STDERR $/, __FILE__, ": Won't delete ``$file''. Set it to writeable first!";
309                         }
310                 }
311                 else
312                 {
313                         seek ($logfile, 0, 0);
314                         if (truncate ($logfile, 0))
315                         {
316                                 print $logfile &last_date ();
317                                 print STDERR $/, __FILE__, ": Truncated ``$file''" if ($::DEBUG & 0x200);
318                         }
319                         else
320                         {
321                                 print STDERR $/, __FILE__, ": Couldn't truncate file ``$file'': $!";
322                         }
323                         
324                         close ($logfile);
325                 }
326         }
327         else
328         {       
329                 close ($logfile);
330         }
331 }
332
333 require Onis::Data::Core;
334 require Onis::Html;
335 import Onis::Data::Core qw#print_output#;
336 import Onis::Html qw#open_file close_file#;
337
338 if (open_file ($output))
339 {
340         print_output ();
341         close_file ();
342 }
343 else
344 {
345         # Fail and make noise! ;)
346         print STDERR <<MESSAGE;
347
348 ERROR: Unable to open output file
349
350 The output file ``$output'' could not be opened. Please make sure to set
351 the permissions right and try again.
352
353 MESSAGE
354         exit (1);
355 }
356
357 exit (0);
358
359 END
360 {
361         print $/ if ($::DEBUG);
362 }
363
364 =head1 OPTIONS
365
366 =head2 Core options
367
368 =over 4
369
370 =item B<config>: I<file>;
371
372 Load the config from this file. B<(command line only)>
373
374 =item B<users_config>: I<file>;
375
376 Sets the file from which to read the user configuration.
377
378 =item B<language_file>: I<file>;
379
380 Sets the language file/translation to use.
381
382 =item B<plugin>: I<string>;
383
384 Sets the plugins to load. The plugin B<Core> will always be loaded.
385
386 =item B<input>: I<file>;
387
388 Read and parse this file(s). B<(config file only)>
389
390 =item B<logtype>: I<string>;
391
392 Sets the parser to use for parsing the input file.
393
394 =item B<output>: I<file>;
395
396 Write the generated output to this file.
397
398 =item B<overwrite>: I<bool>;
399
400 Sets wether or not to overwrite the output-file if it exists.
401
402 =item B<purge_logs>: "I<false>" | "I<truncate>" | "I<delete>";
403
404 Sets wether logs should be truncated or even removes after they have been
405 parsed.
406
407 =item B<user>: I<string>;
408
409 Sets the user generating the stats if it's not detected right.
410
411 =item B<channel>: I<string>;
412
413 Sets the name of the channel being parsed. Normally this is auto-detected.
414
415 =item B<unsharp>: "I<none>" | "I<light>" | "I<medium>" | "I<hard>";
416
417 Sets how to do unsharping. What each setting actually does is described in the
418 readme and in L<Onis::Data::Core>.
419
420 =back
421
422 =head2 Appearance
423
424 =over 4
425
426 =item B<theme>: I<file>;
427
428 Theme file to load.
429
430 =item B<stylesheet>: I<file>;
431
432 Sets the stylesheet (.css file) to use. This should be set in a theme file.
433
434 =item B<color_codes>: I<bool>;
435
436 Wether to print mIRC color-codes or filter them.
437
438 =item B<display_images>: I<bool>;
439
440 Sets if user-images should be displayed.
441
442 =item B<default_image>: I<file>;
443
444 Sets the default image to use if no user-defined image is available.
445
446 =item B<display_lines>: "I<none>" | "I<number>" | "I<bar>" | "I<both>";
447
448 =item B<display_words>: "I<none>" | "I<number>" | "I<bar>" | "I<both>";
449
450 =item B<display_chars>: "I<none>" | "I<number>" | "I<bar>" | "I<both>";
451
452 Sets if and how lines, words and/or characters should be displayed.
453
454 =item B<sort_by>: "I<lines>" | "I<words>" | "I<chars>";
455
456 Sets wether to sort by lines, words or characters written.
457
458 =item B<display_times>: I<bool>;
459
460 Wether or not to display a fixed width bar that shows when a user is most
461 active.
462
463 =item B<bar_height>: I<number>;
464
465 =item B<bar_width>: I<number>;
466
467 Sets the width of horizontal bars and the height of vertical bars.
468
469 =item B<horizontal_images>: I<file>, I<file>, I<file>, I<file>;
470
471 =item B<vertical_images>:   I<file>, I<file>, I<file>, I<file>;
472
473 Sets the images to use for horizontal and vertical bars. This should be used in
474 the theme-file.
475
476 =item B<encoding>: I<string>;
477
478 Sets the encoding for the output file. This is merely the string that will be
479 included in the generated HTML file.
480
481 =item B<public_page>: I<bool>;
482
483 Wether or not this is a public page. Public pages may be linked on the onis
484 homepage at some point in the fututre..
485
486 =back
487
488 =head2 Storage / Persistency
489
490 =over 4
491
492 =item B<storage_module>: I<string>;
493
494 Sets the storage-module to use.
495
496 =item B<storage_dir>: I<directory>;
497
498 Sets the directory to store persistency information under.
499
500 =item B<storage_file>: I<file>;
501
502 Sets the file to write persistency data to, if applicable by the
503 storage-module.
504
505 =back
506
507 =head2 Plugins
508
509 =over 4
510
511 =item B<min_word_length>: I<number>;
512
513 Substring containing only word-characters needs to be this long to be
514 considered a word.
515
516 =item B<plugin_max>: I<number>;
517
518 Sets the number of "most referenced nicks", "most used words" and the like to
519 be displayed. This option will be removed in the future.
520
521 =item B<longlines>: I<number>;
522
523 =item B<shortlines>: I<number>;
524
525 The number of lines in the big and the small table. While in the big table one
526 line is dedicated for one person the small table displays six persons per line.
527
528 =item B<quote_cache_size>: I<number>;
529
530 Sets how many quotes are to be cached for each nick. At the end of the run one
531 of the quotes in the cache will be chosen at random and displayed.
532
533 =item B<quote_min>: I<number>;
534
535 =item B<quote_max>: I<number>;
536
537 Sets the minimum and maximum length of a quote. Too short quotes may be not
538 very typical for a person, too long quotes may clutter the layout.
539
540 =item B<soliloquies_count>: I<number>;
541
542 Sets how many lines without interruption are considered a soliloquy.
543
544 =item B<longterm_days>: I<number>;
545
546 =item B<userdetails_longterm_days>: I<number>;
547
548 Sets the number of days shown in the longterm-plugin (or the longter-section of
549 the userdetails-plugin).
550
551 =item B<ignore_words>: I<number>;
552
553 The Words-Plugin will ignore words with less than this characters.
554
555 =back
556
557 =head1 AUTHOR
558
559 Florian Forster E<lt>octo at verplant.orgE<gt>
560
561 =cut