source: obsdata/tags/0.3.3/ObsData/Repository.pm @ 339

Last change on this file since 339 was 339, checked in by nanardon, 10 years ago

Essai d'optimisation: ne pas dumper l'état à chaque fichier

  • Property cvs2svn:cvs-rev set to 1.79
  • Property svn:keywords set to Author Date Id Revision
File size: 20.0 KB
Line 
1# $Id$
2
3package ObsData::Repository;
4
5use strict;
6use warnings;
7use File::Path;
8use POSIX qw(getcwd);
9use ObsData;
10use ObsData::Archive;
11use ObsData::Event;
12use ObsData::Repository::Status;
13use POSIX qw(strftime);
14
15our $CVSID = q$Id$;
16our $CVSREV = ( q$Revision$ =~ /^Revision: (.*) $/ )[0];
17
18=head1 METHODS
19
20=head2 ObsData::Repository
21
22=head3 new($parent, %param)
23
24Create a new ObsData::Repository object
25
26=over 4
27
28=item $parent
29
30A ObsData::Repository to take information
31
32=item %param
33
34A list of paramter to set in new object:
35
36=over 4
37
38=item datatype
39
40If set, only this datatype will be checked
41
42=item obs
43
44The obsveratory where data come from, informationnal only
45
46=item dir
47
48The based directory where archive are located
49
50=item glob
51
52Apply this glob to search files in dir
53
54=item statusfile
55
56The file where status info should be written
57
58=item status
59
60The Config::IniFiles object
61
62=back
63
64=back
65
66=cut
67
68sub new {
69    my ( $class, $parent, %param ) = @_;
70
71    my $or = {%$parent};
72    foreach ( keys %param ) {
73        $or->{$_} = $param{$_};
74    }
75
76    $or->{obsdata} or die "Error #1de8d015, please insult programmer";
77    $or->{dir}     or die "No dir given";
78    -d $or->{dir}  or return undef;
79
80    $or->{status} ||= new ObsData::Repository::Status(
81        filename => $or->{statusfile},
82    );
83    $or->{statusfile}           ||= $or->{status}->GetFileName;
84    $or->{interactive_callback} ||= $or->{obsdata}->{interactive_callback};
85
86    bless( $or, $class );
87
88    $or;
89}
90
91sub DESTROY {
92    my ($self) = @_;
93    $self->logging( 0, "Destroy ObsData::Repository for %s", ref($self) );
94    $self->save_status;
95}
96
97=head3 mydir
98
99return directory handle by the object
100
101=cut
102
103sub mydir {
104    $_[0]->{dir};
105}
106
107=head3 update_global_status
108
109Set standard information into status ini file
110
111=cut
112
113sub update_global_status {
114    my ($self) = @_;
115    $self->{status}->AddSection('.');
116    $self->{status}
117      ->SetSectionComment( '.', $ObsData::CVSID, $ObsData::Repository::CVSID,
118        scalar(localtime), );
119
120    my $hostname = `hostname`;
121    chomp($hostname);
122    $self->{status}->newval( '.', 'hostname', $hostname );
123    $self->{status}
124      ->SetParameterComment( '.', 'hostname', 'The computer host name' );
125
126    $self->{status}->newval( '.', 'directory', $self->{dir} );
127    $self->{status}->SetParameterComment( '.', 'directory',
128        'Directory where archive where located' );
129
130    $self->{status}->newval( '.', 'runfrom', getcwd() );
131    $self->{status}
132      ->SetParameterComment( '.', 'runfrom', 'The current directory' );
133}
134
135=head3 save_status
136
137Save current information about parsed files into status file
138
139=cut
140
141sub save_status {
142    my ($self) = @_;
143    $self->{statusfile} or return;
144    if ( !$self->{dry_run} ) {
145        $self->logging( 0, "Writing status file: %s", $self->{statusfile}, );
146        $self->update_global_status;
147        $self->{status}->WriteConfig( $self->{statusfile} );
148    }
149}
150
151=head3 dump_status($output)
152
153Dump current status file into $output, see Config::IniFiles,
154function WriteConfig
155
156=cut
157
158sub dump_status {
159    my ( $self, $output ) = @_;
160    $self->update_global_status;
161    $self->{status}->WriteConfig($output);
162}
163
164=head3 logging($level, $msg, ...)
165
166Pass a message for logging to ObsData Object. See L<ObsData>
167
168=cut
169
170sub logging {
171    my $self = shift;
172    $self->{obsdata}->logging(@_);
173}
174
175=head2 ObsData::Repository::dir
176
177The child class to handle directory
178
179=cut
180
181package ObsData::Repository::dir;
182
183our @ISA = qw(ObsData::Repository);
184
185=head3 new
186
187=cut
188
189sub new {
190    my ( $class, $infos, %param ) = @_;
191
192    my $or = ObsData::Repository->new( $infos, %param ) or return;
193
194    bless( $or, $class );
195}
196
197=head3 findfile
198
199Return the list of files needing to be checked
200
201=cut
202
203sub findfile {
204    my ($self) = @_;
205    my @files;
206
207    if ( !-d $self->{dir} ) {
208        $self->logging( 3, "directory %s does not exists", $self->{dir}, );
209        return;
210    }
211
212    my $patern = $self->{patern} || '*';
213    foreach my $f ( glob("$self->{dir}/$patern") ) {
214
215        -f $f or next;    # skip no regular files
216        $f =~ m/obsdata\.ini$/ and next;    # avoid default status file
217
218        my $bf = $f;
219        $bf =~ s!^\Q$self->{dir}\E/*!!;
220        push( @files, $bf );
221    }
222    @files;
223}
224
225=head3 list_archive_from_status()
226
227Return the list of archive currently registered in status file.
228
229=cut
230
231sub list_archive_from_status {
232    my ($self) = @_;
233
234    grep { $_ ne '.' } $self->{status}->Sections;
235}
236
237=head3 process
238
239Process all files in directory
240
241=cut
242
243sub process {
244    my ($self) = @_;
245    $self->logging( 0, "%s() start for %s", ( caller(0) )[3], $self->{dir} );
246
247    foreach my $file ( $self->findfile ) {
248        my $orda =
249          ObsData::Repository::dir::archive->new( $self, archivefile => $file,
250          );
251
252        my $oe = ObsData::Event->new( 'do_archive', "Should I parse $file" );
253
254        $oe->add_test( 'archive_exists',
255            $self->{status}->SectionExists( $orda->{archivefile} )
256            ? ( "Archive file found in index", 1 )
257            : ( "Archive file not found in index", 0 ) );
258
259        if ( defined( my $oldsize = $orda->getvalue('size') ) ) {
260            my $size = ( stat("$orda->{dir}/$orda->{archivefile}") )[7];
261
262            $oe->add_test( 'archive_size',
263                  ( $size <=> $oldsize ) == 0
264                ? ( sprintf( "size does not differ (%d)", $size ), 1 )
265                : ( sprintf( "differ %d => %d", $oldsize, $size ), 0 ) );
266        }
267
268        if ( defined( my $oldmtime = $orda->getvalue('mtime') ) ) {
269            my $mtime = ( stat("$orda->{dir}/$orda->{archivefile}") )[9];
270            $oe->add_test( 'archive_mtime',
271                  ( $mtime <=> $oldmtime ) == 0
272                ? ( sprintf( "mtime does not differ (%d)", $mtime ), 1 )
273                : ( sprintf( "differ %d => %d", $oldmtime, $mtime ), 0 ) );
274        }
275
276        $orda->ask_user($oe) or next;
277
278        $orda->process;
279    }
280}
281
282sub get_archives_from_status {
283    my ($self) = @_;
284    map { ObsData::Repository::dir::archive->new( $self, archivefile => $_, ) }
285      $self->list_archive_from_status();
286}
287
288=head2 ObsData::Repository::dir::archive
289
290Object to handle archive and to dispatch data
291
292=cut
293
294package ObsData::Repository::dir::archive;
295
296use POSIX qw(strftime);
297
298our @ISA = qw(ObsData::Repository);
299
300=head3 new($info, %param)
301
302Return a new ObsData::Repository::dir::archive object.
303
304$info is an ObsData::Repository or ObsData::Repository::dir object
305
306%param are:
307   
308    archivefile => the archive file to read
309
310=cut
311
312sub new {
313    my ( $class, $infos, %param ) = @_;
314
315    my $or = ObsData::Repository->new( $infos, %param ) or return;
316
317    # dir directory to parse
318    $or->{archivefile} or die "No archivefile given";
319
320    # $or->{interactive_callback}
321    $or->{_cache_checks} = {};
322
323    $or->logging( 0, "Preparing to parse %s/%s",
324        $or->{dir}, $or->{archivefile} );
325
326    bless( $or, $class );
327}
328
329sub DESTROY {
330    my ($self) = @_;
331    $self->logging( 0, "Destroy ObsData::Repository for %s", ref($self) );
332#    if ( $self->{status_changed} ) {
333#        $self->save_status;
334#    }
335}
336
337=head3 archivefile
338
339Return the archive name handle by this object
340
341=cut
342
343sub archivefile {
344    $_[0]->{archivefile};
345}
346
347=head3 load_archive
348
349Try to read archive, return 1 on success
350
351=cut
352
353sub load_archive {
354    my ($self) = @_;
355    if ( $self->{Oarchive} ) {
356        return 1;
357    }
358    else {
359        $self->{Oarchive} =
360          ObsData::Archive->new("$self->{dir}/$self->{archivefile}");
361        if ( $self->{Oarchive} ) {
362            $self->logging( 0, "Archive %s/%s properly load",
363                $self->{dir}, $self->{archivefile} );
364            return 1;
365        }
366        else {
367            $self->logging( 4, "Can't handle archive %s/%s: %s, skipping",
368                $self->{dir}, $self->{archivefile}, ObsData::Archive->error );
369            return 0;
370        }
371    }
372}
373
374=head2 archive()
375
376Return the ObsData::Archive object handle by the object, load it if need.
377Return undef if failed.
378
379=cut
380
381sub archive {
382    my ($self) = @_;
383    if ( $self->load_archive() ) {
384        return $self->{Oarchive};
385    }
386    else {
387        return undef;
388    }
389}
390
391=head2 getvalue($var, $default)
392
393Return a value from status file related to current archive.
394
395=cut
396
397sub getvalue {
398    my ( $self, $var, $default ) = @_;
399    return $self->{status}->val( $self->{archivefile}, $var, $default );
400}
401
402=head2 setvalue
403
404Define a value related to current archive file.
405
406=cut
407
408sub setvalue {
409    my ( $self, $var, $val, $comment ) = @_;
410    if ( !$self->{status}->SectionExists( $self->{archivefile} ) ) {
411        $self->{status}->AddSection( $self->{archivefile} );
412    }
413    $self->{status}->newval( $self->{archivefile}, $var, $val )
414      if ( defined($val) );
415    $self->{status}->SetParameterComment( $self->{archivefile}, $var, $comment )
416      if ( defined($comment) );
417    $self->{status_changed} = 1;
418}
419
420=head2 get_data_value($datafile, $var, $default)
421
422Return a value from status file related to current archive about $datafile.
423
424=cut
425
426sub get_data_value {
427    my ( $self, $datafile, $var, $default ) = @_;
428    return $self->getvalue( sprintf( 'data_%s_%s', $var, $datafile ),
429        $default );
430}
431
432=head2 set_data_value($datafile, $var, $val, $comment)
433
434Set a value and optionnaly a comment for $datafile found in archive file
435
436=cut
437
438sub set_data_value {
439    my ( $self, $datafile, $var, $val, $comment ) = @_;
440    $self->setvalue( sprintf( 'data_%s_%s', $var, $datafile ), $val, $comment );
441}
442
443=head3 checks
444
445=cut
446
447=head3 default_choice($oevent)
448
449In batch mode, check ObsData::event content and return result about behaviour
450soft whould have.
451
452=cut
453
454sub default_choice {
455    my ( $self, $oevent ) = @_;
456
457    for ( $oevent->id ) {
458        /^do_archive$/ and do {
459            return ( $oevent->test_result('archive_exists') ? 0 : 1 );
460            last;
461        };
462        /^do_data$/ and do {
463            return 1;    # TODO
464            last;
465        };
466        /^overwrite$/ and do {
467            return ( $oevent->test_result('dest_exists') ? 0 : 1 );
468            last;
469        };
470    }
471}
472
473=head3 ask_user($oevent)
474
475The function take a filled ObsData::Event object, and perform return
476default_choice verification or result the callback set by the application.
477
478=cut
479
480sub ask_user {
481    my ( $self, $oevent ) = @_;
482
483    $self->logging( 0, "Event(%s): %s", $oevent->id, $oevent->message );
484    foreach ( $oevent->list_test ) {
485        $self->logging(
486            0, "  (%s) %s: %s",
487            $_,
488            $oevent->test_message($_),
489            $oevent->test_result($_),
490        );
491    }
492
493    my $res =
494        $self->{interactive_callback}
495      ? $self->{interactive_callback}->( $self, $oevent )
496      : $self->default_choice($oevent);
497
498    $self->logging( 0, "Event(%s): Result: %s", $oevent->id, $res );
499    return ($res);
500}
501
502=head3 list_archive
503
504Return archive content
505
506=cut
507
508sub list_archive {
509    my ($self) = @_;
510    if ( my $archive = $self->archive ) {
511        return $archive->ls();
512    }
513    else {
514        return undef;
515    }
516}
517
518=head3 update_status
519
520Update status information about current archive
521
522=cut
523
524sub update_status {
525    my ($self) = @_;
526    my @filestats = stat("$self->{dir}/$self->{archivefile}");
527
528    $self->setvalue( 'mtime',      $filestats[9] );
529    $self->setvalue( 'size',       $filestats[7] );
530    $self->setvalue( 'configtime', $self->{obsdata}->config_mtime );
531}
532
533=head3 find_match_data_type($label)
534
535Check if datafile match one of the regexp associate to datatype in the
536configuration, if yes, return the datatype and assiociate values, else
537return nothing
538
539=cut
540
541sub match_data_type {
542    my ( $self, $datafile ) = @_;
543    foreach
544      my $datatype ( ( $self->{datatype} ) || $self->{obsdata}->list_datatype )
545    {
546        my $regexp = $self->{obsdata}->getvalue( $datatype, 'match' ) or next;
547        my @data = $datafile =~ /$regexp/i;
548        my @dataar = $self->archive_match($datatype);
549        my ( $pret, %parg ) =
550          $self->get_info_from_plugin( $datatype, $datafile );
551
552        # if no regexp => return undef
553        # if regexp and match, @dataar contain something
554        # if regexp no match, @dataar is empty
555        if (   @data
556            && ( @dataar             || !defined( $dataar[0] ) )
557            && ( ( !defined($pret) ) || $pret ) )
558        {
559            return ( $datatype, \@data, \@dataar, \%parg );
560        }
561    }
562    return;
563}
564
565=head3 archive_match($datatype)
566
567Apply matching to archive filename
568
569=cut
570
571sub archive_match {
572    my ( $self, $datatype ) = @_;
573    my $regexp = $self->{obsdata}->getvalue( $datatype, 'match_archive' )
574      or return undef;
575    my @data = $self->{archivefile} =~ /$regexp/i;
576    return @data;
577}
578
579=head3 get_info_from_plugin($datatype, $datafile)
580
581First arg is:
582  undef if no plugin
583  0 no match
584  1 match, %list follow
585
586=cut
587
588sub get_info_from_plugin {
589    my ( $self, $datatype, $datafile ) = @_;
590
591    my %val;
592
593    my ( $sub, @args ) =
594      $self->{obsdata}->get_sub_plugin( $datatype, 'match_plugin' );
595    if ($sub) {
596        $self->logging( 0, "Found plugin %s for %s, running it",
597            'match_plugin', $datatype, );
598
599        return $self->run_plugin( $sub, $datatype, $datafile, @args );
600    }
601    else {
602        return undef;
603    }
604}
605
606=head3 run_plugin( $sub, $datatype, $datafile, @args )
607
608Run the plugin code for a datafile
609
610    $sub is the code ref
611    $datatype is type of data expect if any
612    $datafile is the filename in archive
613    @arg arbitrary information
614
615=cut
616
617sub run_plugin {
618    my ( $self, $sub, $datatype, $datafile, @args ) = @_;
619    my ( $exitstatus, @result ) =
620      $sub->( $self, 'match_plugin', $datafile, $datatype || "-", @args, );
621    if ($exitstatus) {
622        if ( @result % 2 == 0 ) {
623            $self->logging( 0, "Plugin %s for %s exit with %s (%d values)",
624                'match_plugin', $datatype, $exitstatus, scalar(@result), );
625            return ( 1, @result );
626        }
627        else {
628            $self->logging( 4, "Plugin %s for %s return impair values %d",
629                $datatype, $exitstatus, scalar(@result), );
630            return undef;
631        }
632    }
633    else {
634        $self->logging( 0, "Plugin %s of %s exit with null, skipping",
635            'match_plugin', $datatype, );
636        return 0;
637    }
638}
639
640=head3 build_dest_filename($datafile)
641
642Build the destination file from original filename and optionnaly.
643Return the datatype and the filename. If no datatype apply,
644return undef, if destination file cannot be build, return only the datatype.
645
646If object datatype is not defined, the function try to find the right datatype from
647configuration.
648
649=cut
650
651sub build_dest_filename {
652    my ( $self, $datafile ) = @_;
653    my ( $datatype, $data, $dataar, $parg ) = $self->match_data_type($datafile);
654
655    if ( !$datatype ) {
656        return;
657    }
658
659    my %val = %{ $parg || {} };
660
661    my $dest = $self->{obsdata}->getvalue( $datatype, 'dest' );
662    my @matcharg =
663      split( /\s+/, $self->{obsdata}->getvalue( $datatype, 'matcharg' ) || '' );
664    my @matcharg_ar =
665      split( /\s+/,
666        $self->{obsdata}->getvalue( $datatype, 'matcharg_archive' ) || '' );
667    my @destarg =
668      split( /\s+/, $self->{obsdata}->getvalue( $datatype, 'destarg' ) || '' );
669    my @gmtime = gmtime;
670
671    foreach (@matcharg) {
672        $val{$_} = shift(@$data);
673    }
674    foreach (@matcharg_ar) {
675        $val{$_} = shift(@$dataar);
676    }
677
678    # FIXME Is this need ?
679    # foreach (keys %{$magic_words || {}}) {
680    #    $val{'$' . $_} = $magic_words->{$_};
681    #}
682
683    $val{'$datafile'}    = $datafile;
684    $val{'$archivefile'} = $self->{archivefile};
685    ( $val{'$basedatafile'} ) = $datafile =~ m:([^/]*)$:;
686    my $destfile = sprintf( $dest,
687        map { m/^%/ ? strftime( $_, @gmtime ) : $val{$_} || '' }
688          grep { defined($_) } @destarg,
689    );
690    if ( !$destfile ) {
691        $self->logging(
692            4,
693            "Can evaluate destination file from %s, %s",
694            $dest,
695            join( ", ",
696                map { m/^%/ ? strftime( $_, @gmtime ) : $val{$_} } @destarg )
697        );
698        return ();
699    }
700    return ( $datatype, $destfile );
701}
702
703# Sub data function:
704
705=head3 get_data_dest($datafile)
706
707Return the datatype and destination for $datafile
708
709=cut
710
711sub get_data_dest {
712    my ( $self, $datafile ) = @_;
713
714    my ( $t, $d ) = $self->build_dest_filename( $datafile, $self->{datatype} );
715    $self->logging(
716        0, "%s/%s file, datatype %s, destination %s",
717        $self->{archivefile}, $datafile,
718        $t || '(none)',
719        $d || '(none)',
720    );
721    if ($t) {
722        $self->set_data_value( $datafile, 'type', $t );
723    }
724    return ( $t, $d );
725}
726
727=head3 extract_data($datafile, $datatype, $destfile)
728
729Extract data file into $destfile, $datafile is informationnal only.
730
731$datatype and $destfile are the result of L<get_data_dest>.
732
733A basic call is:
734    $o->extract_data($file, $self->get_data_dest($file));
735
736Return 1 on success
737
738=cut
739
740sub extract_data {
741
742    # $datatype, informationnal only here
743    my ( $self, $datafile, $datatype, $destfile ) = @_;
744    $self->load_archive or return 0;
745    my $ltime = time;
746
747    $self->set_data_value(
748        $datafile,
749        'processtime',
750        $ltime,
751        scalar( localtime($ltime) )
752          . ", $ObsData::VERSION: $ObsData::CVSREV, $ObsData::Repository::CVSREV",
753    );
754    $self->set_data_value( $datafile, 'processversion', $ObsData::VERSION, );
755
756    if ( !$destfile ) {
757        $self->logging( 3, "%s/%s has no destination",
758            $self->{archivefile}, $datafile, );
759        $self->{obsdata}
760          ->processed( $self->{obs}, $self->{archivefile}, $datafile, );
761        return 0;
762    }
763
764    my $oe = ObsData::Event->new( 'overwrite', "Should I overwrite $destfile" );
765
766    $oe->add_test( 'dest_exists',
767        -e $destfile
768        ? ( "$destfile exists", 1 )
769        : ( "$destfile don't exists", 0 ) );
770    $self->ask_user($oe) or return 0;
771
772    $self->logging( 0, "Trying to extract %s from %s/%s",
773        $destfile, $self->{archivefile}, $datafile );
774
775    my ($dirname) = $destfile =~ m!(?:(.*)/)?(.*)$!;    #! vim syntax sux
776
777    if ( $dirname && !-d $dirname ) {
778        $self->logging( 1, "Creating directory %s", $dirname );
779        if ( $self->{dry_run} ) {
780            $self->logging( 1, "Testing mode, assume %s was correctly created",
781                $dirname );
782        }
783        else {
784            eval { File::Path::mkpath($dirname) };
785            if ($@) {
786                $self->logging( 4, "Can't create directory %s: %s",
787                    $dirname, $@ );
788                return 0;
789            }
790        }
791    }
792
793    if ( $self->{dry_run} ) {
794        $self->logging( 2, "Testing mode, %s/%s would be extracted as %s",
795            $self->{archivefile}, $datafile, $destfile, );
796    }
797    else {
798        if ( $self->{Oarchive}->extract( $datafile, $destfile ) ) {
799            $self->set_data_value( $datafile, 'dest', $destfile );
800            $self->{obsdata}
801              ->processed( $self->{obs}, $self->{archivefile}, $datafile,
802                $datatype, $destfile, );
803        }
804        else {
805            $self->logging(
806                4, "Can't extract %s/%s to %s: %s",
807                $self->{archivefile}, $datafile, $destfile,
808                $self->{Oarchive}->error,
809            );
810            return 0;
811        }
812    }
813
814    return 1;
815}
816
817=head3 process
818
819Process all files in the archive
820
821=cut
822
823sub process {
824    my ($self) = @_;
825
826    foreach my $data ( $self->list_archive ) {
827        $data or next;
828        my $oe =
829          ObsData::Event->new( 'do_data',
830            "Should I extract $data from $self->{archivefile}" );
831        $oe->add_test( 'data_extracted',
832            $self->get_data_value( $data, 'dest' )
833            ? ( 'Data was already extract', 1 )
834            : ( 'Data was not already extract', 0 ) );
835
836        $self->ask_user($oe) or next;
837
838        $self->extract_data( $data, $self->get_data_dest($data) );
839    }
840
841    $self->update_status;
842
843    return 1;
844}
845
846sub list_data_from_status {
847    my ($self) = @_;
848    map { m/data_processtime_(.*)/; $1 }
849      grep { $_ && index( $_, 'data_processtime_' ) == 0 }
850      $self->{status}->Parameters( $self->{archivefile} );
851}
852
853sub data_extracted {
854    my ( $self, $data ) = @_;
855    return $self->get_data_value( $data, 'dest' );
856}
857
858=head1 LICENSE
859
860This software is under GPL version 2 or highter
861(c) 2005, 2006 CNRS Service d'Aeronomie
862
863=head1 AUTHOR
864
865Olivier Thauvin <olivier.thauvin@aerov.jussieu.fr>
866
867=cut
868
8691;
Note: See TracBrowser for help on using the repository browser.