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

Last change on this file since 220 was 220, checked in by nanardon, 18 years ago
  • add ObsData::get_sub_plugin()
  • Property cvs2svn:cvs-rev set to 1.56
  • Property svn:keywords set to Author Date Id Revision
File size: 12.8 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 get_sub_plugin
153
154Return code ref of plugintype for datatype
155
156=cut
157
158sub get_sub_plugin {
159    my ($self, $datatype, $plugintype) = @_;
160    if (exists($self->{plugin}{$datatype}{$plugintype})) {
161        return $self->{plugin}{$datatype}{$plugintype};
162    } else {
163        return undef;
164    }
165}
166
167=head2 logging($level, $message, ...)
168
169Pass a message into the logging process.
170
171   $level is the level of message
172   $message and or other arguments are printf format string
173
174=cut
175
176sub logging {
177    my ($self, $level, $fmt, @val) = @_;
178    my $msg = sprintf($fmt, @val);
179    my $logh = $self->{loghandle};
180    if ($self->{logcallback}) {
181        $self->{logcallback}->($level, $msg);
182    }
183    if($level >= 0 && $level >= $self->{verbose}) {
184        if ($logh) {
185            printf $logh 
186                "%-9s %s %s\n", 
187                sprintf("[%s]", $self->loglevel($level)),
188                strftime("%b %d %H:%M:%S %Y", gmtime),
189                $msg;
190        }
191    }
192    $msg
193}
194
195=head2 loglevel($level)
196
197Return the text human readable log level associate to $level number
198
199=cut
200
201sub loglevel {
202    my $l = pop(@_);
203    defined($l) or $l = pop(@_);
204    return $loglevel[ $l ] || "?????";
205}
206
207=head2 checkconfig()
208
209Check the validity of information contains in configfile.
210
211Notice: This test does not include the syntaxe validity
212
213=cut
214
215sub checkconfig {
216    my ($self) = @_;
217    my $result = 1;
218    foreach my $g ($self->{config}->GroupMembers('Obs')) {
219        my ($obs) = $g =~ /\S+\s+(.*)/;
220        if (!$self->{config}->SectionExists($obs)) {
221            print STDERR "E: '$obs' is listed as Obs but it does not exists\n";
222            next;
223        }
224        foreach my $param ($self->{config}->Parameters($obs)) {
225        }
226        foreach my $datatype ($self->list_datatype) {
227            foreach my $var (qw(match match_archive)) {
228                my $regexp = $self->getvalue($datatype, $var) or next; # next ? are we sure ?
229                eval { qr/$regexp/ }; # Many thanks Rafael
230                if ($@) {
231                    $self->logging(4, "error in regexp for %s: '%s': %s",
232                        $datatype,
233                        $regexp,
234                        $@,
235                    );
236                    $result = 0;
237                    # TODO set this reg unavalable
238                }
239            }
240        }
241    }
242    return($result);
243}
244
245=head2 getvalue($section, $var, $default)
246
247Return a value from the configuration, $default is assumed if the value is not set
248
249=cut
250
251sub getvalue {
252    my ($self, $section, $var, $default) = @_;
253    $self->{config}->val($section, $var, $default);
254}
255
256=head2 config_mtime
257
258Return the modification time of config file currently used
259
260=cut
261
262sub config_mtime {
263    my ($self) = @_;
264    return $self->{configmtime} ||= (stat($self->{config}->GetFileName))[9];
265}
266
267=head2 list_obs
268
269Return the list of observatories defined in configuration
270
271=cut
272
273sub list_obs {
274    my ($self) = @_;
275    grep { $self->{config}->SectionExists($_) }
276        map { s/^\S+\s+//; $_ }
277        $self->{config}->GroupMembers('Obs');
278}
279
280=head2 is_obs($obsname)
281
282Return True if $obsname is an observatory
283
284=cut
285
286sub is_obs {
287    my ($self, $obs) = @_;
288    scalar(grep { $_ eq "Obs $obs" } $self->{config}->GroupMembers('Obs'));
289}
290
291=head2 list_obsdatadir($obsname)
292
293Return a hash of data directories per data type for $obsname observatories
294
295=cut
296
297sub list_obsdatadir {
298    my ($self, $obs) = @_;
299    $self->is_obs($obs) or return undef;
300    map { m,^datadir/(.*),; ( ($1 || "") => $self->{config}->val($obs, $_) ) }
301        grep { m,^datadir/, || $_ eq 'datadir' }
302        $self->{config}->Parameters($obs)
303}
304
305=head2 list_typedatadir($type)
306
307List all directories for $type data
308
309=cut
310
311sub list_typedatadir {
312    my ($self, $type) = @_;
313    my %dirs;
314    foreach my $obs ($self->list_obs) {
315        $dirs{$_} = 1 foreach(grep { $_ } $self->get_datadir($obs, $type));
316    }
317    keys %dirs; 
318       
319}
320
321=head2 get_datadir($obs, $type)
322
323Return a list of directories for $type data on $obs observatory
324
325=cut
326
327sub get_datadir {
328    my ($self, $obs, $type) = @_;
329    $self->is_obs($obs) or return undef;
330    grep { defined($_) } ($self->getvalue($obs, "datadir/$type"), $self->getvalue($obs, "datadir"));
331}
332
333=head2 list_datatype
334
335Return a list of datatype handle by config
336
337=cut
338
339sub list_datatype {
340    my ($self) = @_;
341    grep { $_ ne 'global' }
342    grep { $_ !~ /^Obs\s+/ }
343    grep { !$self->is_obs($_) } $self->{config}->Sections;
344}
345
346=head2 get_obs_data_handle($obs, $datatype)
347
348Return an ObsData::Repository object about directory for $obs station
349about $datatype data. $datatype can be undefined if you want to get default
350directory
351
352=cut
353
354sub get_obs_data_handle {
355    my ($self, $obs, $datatype) = @_;
356
357    my $dir = $self->getvalue($obs, ($datatype ? "datadir/$datatype" : "datadir"));
358    if (!$dir) {
359        $self->logging(4,
360            "Can't find data directory for %s, type: %s",
361            $obs, $datatype || '(none)'
362        );
363        return undef;
364    }
365    my $or = ObsData::Repository::dir->new(
366        {
367            obsdata => $self,
368            dir => $dir,
369            obs => $obs,
370            datatype => $datatype,
371            dry_run => $self->{dry_run}, # FIXME does this have a better place ?
372            patern => $self->getvalue(
373                $obs,
374                ($datatype ? "searchfiles/$datatype" : "searchfiles")
375            ),
376            statusfile => $self->getvalue(
377                $obs, 
378                ($datatype ? "index/$datatype" : "index"),
379                "$dir/obsdata.ini"
380            ),
381        }
382    );
383    if (!defined($or)) {
384        $self->logging(4, "Can't parse %s, check directory exists", $dir);
385        return undef;
386    }
387   
388    return($or);
389}
390
391=head2 process_obs($obs)
392
393Process all data handle by $obs station
394
395=cut
396
397sub process_obs {
398    my ($self, $obs) = @_;
399    my %datadir = $self->list_obsdatadir($obs);
400    $self->logging(0, "Starting %s() for %s", (caller(0))[3], $obs);
401   
402    foreach my $datatype (keys %datadir) {
403        my $or = $self->get_obs_data_handle($obs, $datatype);
404        $or or next;
405        $or->process;
406    }
407}
408
409sub processed {
410    my ($self, $obs, $archive, $datafile, $datatype, $dest) = @_;
411    push(@{$self->{processed_lists}},
412        {
413            obs => $obs,
414            archive => $archive,
415            datafile => $datafile,
416            datatype => $datatype,
417            destfile => $dest,
418        }
419    );
420    $self->logging(
421        2, "Extraction of %s/%s done as %s",
422        $archive,
423        $datafile,
424        $dest,
425    );
426}
427
428sub generated_reported {
429    my ($self) = @_;
430    my $result = {
431        all => [],
432        users => {},
433    };
434
435    foreach my $entry (@{$self->{processed_lists}}) {
436        my @people = split(/\s*,\s*/, $self->getvalue($entry->{datatype}, 'reportto') || "");
437        @people or next;
438        foreach my $p (@people) {
439            push(@{$result->{users}{$p}{$entry->{datatype}}}, $entry);
440        }
441    }
442
443    foreach my $p (keys(%{$result->{users} || {}})) {
444        my %msg = (
445            Subject => 'ObsData Report',
446            To => $p,
447            'X-ObsData-Version' => $VERSION,
448            'Content-Type' => "TEXT/PLAIN;\n  charset=ISO-8859-1",
449            'Content-Transfer-Encoding' => 'QUOTED-PRINTABLE',
450            From => 'ObsData <robot@aerov.jussieu.fr>',
451        );
452        my $message = "";
453        foreach my $d (keys(%{$result->{users}{$p} || {}})) {
454            $message .= "\nDataType: $d\n";
455            foreach (@{$result->{users}{$p}{$d} || []}) {
456                $message .= sprintf("\tfile %s from %s\n\t=> %s\n",
457                    $_->{datafile},
458                    $_->{archive},
459                    $_->{destfile},
460                );
461            }
462        }
463        sendmail(
464            %msg,
465            smtp => 'mailhost',
466            Message => encode_qp($message),
467        ) or $self->log(4, "Cannot send mail to %s: %s",
468            $msg{To}, $Mail::Sendmail::error,
469        );
470    }
471    return $result;
472}
473
474sub postprocessed {
475    my ($self) = @_;
476    my %datastype;
477    foreach my $entry (@{$self->{processed_lists}}) {
478        push(@{$datastype{$entry->{datatype}}}, $entry);
479    }
480
481    foreach my $datatype (keys %datastype) {
482        my $command = $self->getvalue($datatype, 'postexec');
483        $self->logging(0, 'postexec for %s is %s',
484            $datatype,
485            $command ? "`$command'" : 'not set, skipping',
486        );
487        if ($command) {
488            if (open(my $posthandle, "| $command")) {
489                foreach (@{$datastype{$datatype}}) {
490                    print $posthandle "$_->{destfile}\n" or do {
491                        $self->logging(4, "cannot write to postexec handle for `%s': %s",
492                            $datatype,
493                            $!,
494                        );
495                        last;
496                    };
497                }
498                my $exitstatus = close($posthandle);
499                $self->logging($exitstatus ? 0 : 4,
500                    "postexec for %s exit %s",
501                    $datatype,
502                    $exitstatus ? "correctly" : "with failure : " . ($! ? ($!) : "(??)"),
503                );
504            } else {
505                $self->logging(4, "Cannot exec post `%s' for `%s'",
506                    $command,
507                    $datatype,
508                );
509                next;
510            }
511        }
512    }
513}
514
5151;
516
517=head1 LICENSE
518
519This software is under GPL version 2 or highter
520(c) 2005, 2006 CNRS Service d'Aeronomie
521
522=head1 AUTHOR
523
524Olivier Thauvin <olivier.thauvin@aerov.jussieu.fr>
525
526=cut
Note: See TracBrowser for help on using the repository browser.