source: trunk/soft/ObsData/ObsData.pm @ 219

Last change on this file since 219 was 219, checked in by nanardon, 18 years ago
  • add few doc
  • Property cvs2svn:cvs-rev set to 1.56
  • Property svn:keywords set to Author Date Id Revision
File size: 12.5 KB
Line 
1# $Id$
2
3package ObsData;
4
5use strict;
6use warnings;
7use Config::IniFiles;
8use POSIX qw(strftime);
9use ObsData::Repository;
10use Mail::Sendmail;
11use MIME::QuotedPrint;
12use File::Basename;
13
14my @loglevel = (
15    'DEBUG',
16    'INFO',
17    'RESULT',
18    'WARNING',
19    'ERROR',
20    'FATAL',
21);
22
23our $VERSION = "0.3.0";
24our $CVSID = q$Id$;
25our $CVSREV = (q$Revision$ =~ /^Revision: (.*) $/)[0];
26
27=head1 NAME
28
29ObsData - Main object to manage data files
30
31=head1 SYNOPSIS
32
33    use ObsData;
34    my $conf = "configfile";
35    my $obsdata = ObsData->new($conf);
36
37=head1 METHODS
38
39=head2 new($configfile)
40
41Create a new Obsdata object from $configfile
42
43=cut
44
45sub new {
46    my ($class, $configfile, %options) = @_;
47    my $obsdata = {
48        config => new Config::IniFiles(
49            -file => $configfile,
50            -default => 'global',
51            -allowcontinue => 1
52        ),
53        verbose => defined($options{verbose}) ? $options{verbose} : 1,
54        logcallback => $options{logcallback},
55        logfile => $options{logfile},
56        dry_run => $options{dry_run},
57        interactive_callback => $options{interactive_callback},
58        processed_lists => [],
59    };
60
61    if (!($configfile && -f $configfile && -r _)) {
62        return undef;
63    }
64
65    $obsdata->{config} or return undef;
66
67    # directory where to search plugins
68    @{$obsdata->{plugindir}} = grep { $_ && -d $_ } (
69        $options{plugindir},
70        dirname($0),
71        $obsdata->{config}->val('.', 'plugindir'),
72    );
73       
74    $obsdata->{logfile} ||= $obsdata->{config}->val('.', 'logfile') || 'obsdata.log';
75
76    bless($obsdata, $class);
77}
78
79sub DESTROY {
80    my ($self) = @_;
81
82    if ($self->{loghandle}) {
83        close($self->{loghandle});
84        $self->{loghandle} = undef;
85    }
86}
87
88=head2 load
89
90Prepare the object for usage
91
92=cut
93
94sub load {
95    my ($self) = @_;
96
97    if (!open($self->{loghandle}, ">> $self->{logfile}")) {
98        $self->{loghandle} = undef;
99        $self->logging(5, "Can't open log file %s, exiting", "$self->{logfile}");
100        return 0;
101    }
102   
103    $self->load_plugins();
104   
105    return 1;
106}
107
108=head2 load_plugins
109
110Load plugins files and get code reference.
111
112=cut
113
114sub load_plugins {
115    my ($self) = @_;
116    foreach my $datatype ($self->list_datatype()) {
117        foreach my $plugin (qw/match_plugin/) {
118            my $plugfile = $self->getvalue($datatype, $plugin)
119                or next; # if no plugin, skipping
120            my ($plugfilename) = grep { -f $_ }
121                map { "$_/$plugfile" } @{$self->{plugindir}};
122            if (!$plugfilename) {
123                $self->logging(5,
124                    "Cannot find plugin %s (%s) for %s datatype",
125                    $plugfile,
126                    $plugin,
127                    $datatype,
128                );
129                next; # FIXME die ??
130            }
131            my $sub = do $plugfilename;
132            if ($sub && ref $sub eq 'CODE') {
133                $self->{plugin}{$datatype}{$plugin} = $sub;
134                $self->logging(0,
135                    "loading plugin %s for %s (%s)",
136                    $plugin,
137                    $datatype,
138                    $sub,
139                );
140            } else {
141                $self->logging(4,
142                    "Cannot load plugin %s for %s (is %s)",
143                    $plugin,
144                    $datatype,
145                    $sub ? ref $sub : 'undef or empty',
146                );
147            }
148        }
149    }
150}
151
152=head2 logging($level, $message, ...)
153
154Pass a message into the logging process.
155
156   $level is the level of message
157   $message and or other arguments are printf format string
158
159=cut
160
161sub logging {
162    my ($self, $level, $fmt, @val) = @_;
163    my $msg = sprintf($fmt, @val);
164    my $logh = $self->{loghandle};
165    if ($self->{logcallback}) {
166        $self->{logcallback}->($level, $msg);
167    }
168    if($level >= 0 && $level >= $self->{verbose}) {
169        if ($logh) {
170            printf $logh 
171                "%-9s %s %s\n", 
172                sprintf("[%s]", $self->loglevel($level)),
173                strftime("%b %d %H:%M:%S %Y", gmtime),
174                $msg;
175        }
176    }
177    $msg
178}
179
180=head2 loglevel($level)
181
182Return the text human readable log level associate to $level number
183
184=cut
185
186sub loglevel {
187    my $l = pop(@_);
188    defined($l) or $l = pop(@_);
189    return $loglevel[ $l ] || "?????";
190}
191
192=head2 checkconfig()
193
194Check the validity of information contains in configfile.
195
196Notice: This test does not include the syntaxe validity
197
198=cut
199
200sub checkconfig {
201    my ($self) = @_;
202    my $result = 1;
203    foreach my $g ($self->{config}->GroupMembers('Obs')) {
204        my ($obs) = $g =~ /\S+\s+(.*)/;
205        if (!$self->{config}->SectionExists($obs)) {
206            print STDERR "E: '$obs' is listed as Obs but it does not exists\n";
207            next;
208        }
209        foreach my $param ($self->{config}->Parameters($obs)) {
210        }
211        foreach my $datatype ($self->list_datatype) {
212            foreach my $var (qw(match match_archive)) {
213                my $regexp = $self->getvalue($datatype, $var) or next; # next ? are we sure ?
214                eval { qr/$regexp/ }; # Many thanks Rafael
215                if ($@) {
216                    $self->logging(4, "error in regexp for %s: '%s': %s",
217                        $datatype,
218                        $regexp,
219                        $@,
220                    );
221                    $result = 0;
222                    # TODO set this reg unavalable
223                }
224            }
225        }
226    }
227    return($result);
228}
229
230=head2 getvalue($section, $var, $default)
231
232Return a value from the configuration, $default is assumed if the value is not set
233
234=cut
235
236sub getvalue {
237    my ($self, $section, $var, $default) = @_;
238    $self->{config}->val($section, $var, $default);
239}
240
241=head2 config_mtime
242
243Return the modification time of config file currently used
244
245=cut
246
247sub config_mtime {
248    my ($self) = @_;
249    return $self->{configmtime} ||= (stat($self->{config}->GetFileName))[9];
250}
251
252=head2 list_obs
253
254Return the list of observatories defined in configuration
255
256=cut
257
258sub list_obs {
259    my ($self) = @_;
260    grep { $self->{config}->SectionExists($_) }
261        map { s/^\S+\s+//; $_ }
262        $self->{config}->GroupMembers('Obs');
263}
264
265=head2 is_obs($obsname)
266
267Return True if $obsname is an observatory
268
269=cut
270
271sub is_obs {
272    my ($self, $obs) = @_;
273    scalar(grep { $_ eq "Obs $obs" } $self->{config}->GroupMembers('Obs'));
274}
275
276=head2 list_obsdatadir($obsname)
277
278Return a hash of data directories per data type for $obsname observatories
279
280=cut
281
282sub list_obsdatadir {
283    my ($self, $obs) = @_;
284    $self->is_obs($obs) or return undef;
285    map { m,^datadir/(.*),; ( ($1 || "") => $self->{config}->val($obs, $_) ) }
286        grep { m,^datadir/, || $_ eq 'datadir' }
287        $self->{config}->Parameters($obs)
288}
289
290=head2 list_typedatadir($type)
291
292List all directories for $type data
293
294=cut
295
296sub list_typedatadir {
297    my ($self, $type) = @_;
298    my %dirs;
299    foreach my $obs ($self->list_obs) {
300        $dirs{$_} = 1 foreach(grep { $_ } $self->get_datadir($obs, $type));
301    }
302    keys %dirs; 
303       
304}
305
306=head2 get_datadir($obs, $type)
307
308Return a list of directories for $type data on $obs observatory
309
310=cut
311
312sub get_datadir {
313    my ($self, $obs, $type) = @_;
314    $self->is_obs($obs) or return undef;
315    grep { defined($_) } ($self->getvalue($obs, "datadir/$type"), $self->getvalue($obs, "datadir"));
316}
317
318=head2 list_datatype
319
320Return a list of datatype handle by config
321
322=cut
323
324sub list_datatype {
325    my ($self) = @_;
326    grep { $_ ne 'global' }
327    grep { $_ !~ /^Obs\s+/ }
328    grep { !$self->is_obs($_) } $self->{config}->Sections;
329}
330
331=head2 get_obs_data_handle($obs, $datatype)
332
333Return an ObsData::Repository object about directory for $obs station
334about $datatype data. $datatype can be undefined if you want to get default
335directory
336
337=cut
338
339sub get_obs_data_handle {
340    my ($self, $obs, $datatype) = @_;
341
342    my $dir = $self->getvalue($obs, ($datatype ? "datadir/$datatype" : "datadir"));
343    if (!$dir) {
344        $self->logging(4,
345            "Can't find data directory for %s, type: %s",
346            $obs, $datatype || '(none)'
347        );
348        return undef;
349    }
350    my $or = ObsData::Repository::dir->new(
351        {
352            obsdata => $self,
353            dir => $dir,
354            obs => $obs,
355            datatype => $datatype,
356            dry_run => $self->{dry_run}, # FIXME does this have a better place ?
357            patern => $self->getvalue(
358                $obs,
359                ($datatype ? "searchfiles/$datatype" : "searchfiles")
360            ),
361            statusfile => $self->getvalue(
362                $obs, 
363                ($datatype ? "index/$datatype" : "index"),
364                "$dir/obsdata.ini"
365            ),
366        }
367    );
368    if (!defined($or)) {
369        $self->logging(4, "Can't parse %s, check directory exists", $dir);
370        return undef;
371    }
372   
373    return($or);
374}
375
376=head2 process_obs($obs)
377
378Process all data handle by $obs station
379
380=cut
381
382sub process_obs {
383    my ($self, $obs) = @_;
384    my %datadir = $self->list_obsdatadir($obs);
385    $self->logging(0, "Starting %s() for %s", (caller(0))[3], $obs);
386   
387    foreach my $datatype (keys %datadir) {
388        my $or = $self->get_obs_data_handle($obs, $datatype);
389        $or or next;
390        $or->process;
391    }
392}
393
394sub processed {
395    my ($self, $obs, $archive, $datafile, $datatype, $dest) = @_;
396    push(@{$self->{processed_lists}},
397        {
398            obs => $obs,
399            archive => $archive,
400            datafile => $datafile,
401            datatype => $datatype,
402            destfile => $dest,
403        }
404    );
405    $self->logging(
406        2, "Extraction of %s/%s done as %s",
407        $archive,
408        $datafile,
409        $dest,
410    );
411}
412
413sub generated_reported {
414    my ($self) = @_;
415    my $result = {
416        all => [],
417        users => {},
418    };
419
420    foreach my $entry (@{$self->{processed_lists}}) {
421        my @people = split(/\s*,\s*/, $self->getvalue($entry->{datatype}, 'reportto') || "");
422        @people or next;
423        foreach my $p (@people) {
424            push(@{$result->{users}{$p}{$entry->{datatype}}}, $entry);
425        }
426    }
427
428    foreach my $p (keys(%{$result->{users} || {}})) {
429        my %msg = (
430            Subject => 'ObsData Report',
431            To => $p,
432            'X-ObsData-Version' => $VERSION,
433            'Content-Type' => "TEXT/PLAIN;\n  charset=ISO-8859-1",
434            'Content-Transfer-Encoding' => 'QUOTED-PRINTABLE',
435            From => 'ObsData <robot@aerov.jussieu.fr>',
436        );
437        my $message = "";
438        foreach my $d (keys(%{$result->{users}{$p} || {}})) {
439            $message .= "\nDataType: $d\n";
440            foreach (@{$result->{users}{$p}{$d} || []}) {
441                $message .= sprintf("\tfile %s from %s\n\t=> %s\n",
442                    $_->{datafile},
443                    $_->{archive},
444                    $_->{destfile},
445                );
446            }
447        }
448        sendmail(
449            %msg,
450            smtp => 'mailhost',
451            Message => encode_qp($message),
452        ) or $self->log(4, "Cannot send mail to %s: %s",
453            $msg{To}, $Mail::Sendmail::error,
454        );
455    }
456    return $result;
457}
458
459sub postprocessed {
460    my ($self) = @_;
461    my %datastype;
462    foreach my $entry (@{$self->{processed_lists}}) {
463        push(@{$datastype{$entry->{datatype}}}, $entry);
464    }
465
466    foreach my $datatype (keys %datastype) {
467        my $command = $self->getvalue($datatype, 'postexec');
468        $self->logging(0, 'postexec for %s is %s',
469            $datatype,
470            $command ? "`$command'" : 'not set, skipping',
471        );
472        if ($command) {
473            if (open(my $posthandle, "| $command")) {
474                foreach (@{$datastype{$datatype}}) {
475                    print $posthandle "$_->{destfile}\n" or do {
476                        $self->logging(4, "cannot write to postexec handle for `%s': %s",
477                            $datatype,
478                            $!,
479                        );
480                        last;
481                    };
482                }
483                my $exitstatus = close($posthandle);
484                $self->logging($exitstatus ? 0 : 4,
485                    "postexec for %s exit %s",
486                    $datatype,
487                    $exitstatus ? "correctly" : "with failure : " . ($! ? ($!) : "(??)"),
488                );
489            } else {
490                $self->logging(4, "Cannot exec post `%s' for `%s'",
491                    $command,
492                    $datatype,
493                );
494                next;
495            }
496        }
497    }
498}
499
5001;
501
502=head1 LICENSE
503
504This software is under GPL version 2 or highter
505(c) 2005, 2006 CNRS Service d'Aeronomie
506
507=head1 AUTHOR
508
509Olivier Thauvin <olivier.thauvin@aerov.jussieu.fr>
510
511=cut
Note: See TracBrowser for help on using the repository browser.