source: obsdata/trunk/ObsData.pm @ 292

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