source: trunk/soft/ObsData/ObsData/Repository.pm @ 197

Last change on this file since 197 was 197, checked in by thauvin, 18 years ago
  • fix typo
  • Property cvs2svn:cvs-rev set to 1.74
  • Property svn:keywords set to Author Date Id Revision
File size: 16.7 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 Config::IniFiles;
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 Config::IniFiles(
81        -file => (($or->{statusfile} && -f $or->{statusfile}) ? $or->{statusfile} : undef),
82        # -default => '.', # Is this a good idea
83    );
84    $or->{statusfile} ||= $or->{status}->GetFileName;
85    $or->{interactive_callback} ||= $or->{obsdata}->{interactive_callback};
86
87    bless($or, $class);
88
89    $or;
90}
91
92sub DESTROY {
93    my ($self) = @_;
94    $self->logging(0, "Destroy ObsData::Repository for %s", ref($self));
95    $self->save_status;
96}
97
98=head3 update_global_status
99
100Set standard information into status ini file
101
102=cut
103
104sub update_global_status {
105    my ($self) = @_;
106    $self->{status}->AddSection('.');
107    $self->{status}->SetSectionComment('.',
108        $ObsData::CVSID,
109        $ObsData::Repository::CVSID,
110        scalar(localtime),
111    );
112
113    my $hostname = `hostname`;
114    chomp($hostname);
115    $self->{status}->newval('.', 'hostname', $hostname);
116    $self->{status}->SetParameterComment(
117        '.',
118        'hostname',
119        'The computer host name'
120    );
121
122    $self->{status}->newval('.', 'directory', $self->{dir});
123    $self->{status}->SetParameterComment(
124        '.',
125        'directory', 
126        'Directory where archive where located'
127    );
128   
129    $self->{status}->newval('.', 'runfrom', getcwd());
130    $self->{status}->SetParameterComment(
131        '.',
132        'runfrom',
133        'The current directory'
134    );
135}
136
137=head3 save_status
138
139Save current information about parsed files into status file
140
141=cut
142
143sub save_status {
144    my ($self) = @_;
145    $self->{statusfile} or return;
146    if (!$self->{dry_run}) {
147        $self->update_global_status;
148        $self->{status}->WriteConfig($self->{statusfile});
149    }
150}
151
152=head3 dump_status($output)
153
154Dump current status file into $output, see Config::IniFiles,
155function WriteConfig
156
157=cut
158
159sub dump_status {
160    my ($self, $output) = @_;
161    $self->update_global_status;
162    $self->{status}->WriteConfig($output);
163}
164
165=head3 logging($level, $msg, ...)
166
167Pass a message for logging to ObsData Object. See L<ObsData>
168
169=cut
170
171sub logging {
172    my $self = shift;
173    $self->{obsdata}->logging(@_);
174}
175
176=head2 ObsData::Repository::dir
177
178The child class to handle directory
179
180=cut
181
182package ObsData::Repository::dir;
183
184our @ISA = qw(ObsData::Repository);
185
186=head3 new
187
188=cut
189
190sub new {
191    my ($class, $infos, %param) = @_;
192
193    my $or = ObsData::Repository->new($infos, %param) or return;
194
195    bless($or, $class);
196}
197
198=head3 findfile
199
200Return the list of files needing to be checked
201
202=cut
203
204sub findfile {
205    my ($self) = @_;
206    my @files;
207
208    if (!-d $self->{dir}) {
209        $self->logging(3,
210            "directory %s does not exists",
211            $self->{dir},
212        );
213        return;
214    }
215
216    my $patern = $self->{patern} || '*';
217    foreach my $f (glob("$self->{dir}/$patern")) {
218
219        -f $f or next; # skip no regular files
220        $f =~ m/obsdata\.ini$/ and next; # avoid default status file
221       
222        my $bf = $f;
223        $bf =~ s!^\Q$self->{dir}\E/*!!;
224        push(@files, $bf);
225    }
226    @files
227}
228
229=head3 list_archive_from_status()
230
231Return the list of archive currently registered in status file.
232
233=cut
234
235sub list_archive_from_status {
236    my ($self) = @_;
237
238    grep { $_ ne '.' } $self->{status}->Sections
239}
240
241=head3 process
242
243Process all files in directory
244
245=cut
246
247sub process {
248    my ($self) = @_;
249    $self->logging(0, "%s() start for %s", (caller(0))[3], $self->{dir});
250
251    foreach my $file ($self->findfile) {
252        my $orda = ObsData::Repository::dir::archive->new(
253            $self,
254            archivefile => $file,
255        );
256
257        my $oe = ObsData::Event->new('do_archive', "Should I parse $file");
258       
259        $oe->add_test('archive_exists',
260            $self->{status}->SectionExists($orda->{archivefile}) ?
261            ("Archive file found in index", 1) :
262            ("Archive file not found in index", 0)
263        );
264       
265        if (defined(my $oldsize = $orda->getvalue('size'))) {
266            my $size = (stat("$orda->{dir}/$orda->{archivefile}"))[7];
267           
268            $oe->add_test('archive_size',
269                ($size <=> $oldsize) == 0 ?
270                (sprintf("size does not differ (%d)", $size), 1) :
271                (sprintf("differ %d => %d", $oldsize, $size), 0)
272            );
273        }
274
275        if (defined(my $oldmtime = $orda->getvalue('mtime'))) {
276            my $mtime = (stat("$orda->{dir}/$orda->{archivefile}"))[9];
277            $oe->add_test('archive_mtime',
278                ($mtime <=> $oldmtime) == 0 ?
279                (sprintf("mtime does not differ (%d)", $mtime), 1) :
280                (sprintf("differ %d => %d", $oldmtime, $mtime), 0)
281            );
282        }
283       
284        $orda->ask_user($oe) or next;
285       
286        $orda->process;
287    }
288}
289
290=head2 ObsData::Repository::dir::archive
291
292Object to handle archive and to dispatch data
293
294=cut
295
296package ObsData::Repository::dir::archive;
297
298use POSIX qw(strftime);
299
300our @ISA = qw(ObsData::Repository);
301
302=head3 new($info, %param)
303
304Return a new ObsData::Repository::dir::archive object.
305
306$info is an ObsData::Repository or ObsData::Repository::dir object
307
308%param are:
309   
310    archivefile => the archive file to read
311
312=cut
313
314sub new {
315    my ($class, $infos, %param) = @_;
316
317    my $or = ObsData::Repository->new($infos, %param) or return;
318    # dir directory to parse
319    $or->{archivefile} or die "No archivefile given";
320
321    # $or->{interactive_callback}
322    $or->{_cache_checks} = {};
323   
324    $or->logging(0,
325        "Preparing to parse %s/%s",
326         $or->{dir},
327         $or->{archivefile}
328    );
329
330    bless($or, $class);
331}
332
333=head3 load_archive
334
335Try to read archive, return 1 on success
336
337=cut
338
339sub load_archive {
340    my ($self) = @_;
341    if ($self->{Oarchive}) {
342        return 1;
343    } else {
344        $self->{Oarchive} = ObsData::Archive->new("$self->{dir}/$self->{archivefile}");
345        if ($self->{Oarchive}) {
346            $self->logging(0,
347                "Archive %s/%s properly load",
348                $self->{dir}, $self->{archivefile}
349            );
350            return 1;
351        } else {
352            $self->logging(4, 
353                "Can't handle archive %s/%s: %s, skipping",
354                $self->{dir}, $self->{archivefile},
355                ObsData::Archive->error
356            );
357            return 0;
358        }
359    }
360}
361
362=head2 getvalue($var, $default)
363
364Return a value from status file related to current archive.
365
366=cut
367
368sub getvalue {
369    my ($self, $var, $default) = @_;
370    return $self->{status}->val($self->{archivefile}, $var, $default);
371}
372
373=head2 setvalue
374
375Define a value related to current archive file.
376
377=cut
378
379sub setvalue {
380    my ($self, $var, $val, $comment) = @_;
381    if (!$self->{status}->SectionExists($self->{archivefile})) {
382        $self->{status}->AddSection($self->{archivefile});
383    }
384    $self->{status}->newval($self->{archivefile}, $var, $val) if(defined($val));
385    $self->{status}->SetParameterComment(
386        $self->{archivefile}, $var, $comment
387    ) if(defined($comment));
388}
389
390=head2 get_data_value($datafile, $var, $default)
391
392Return a value from status file related to current archive about $datafile.
393
394=cut
395
396sub get_data_value {
397    my ($self, $datafile, $var, $default) = @_;
398    return $self->getvalue(
399        sprintf('data_%s_%s', $var, $datafile),
400        $var, 
401        $default
402    );
403}
404
405=head2 set_data_value($datafile, $var, $val, $comment)
406
407Set a value and optionnaly a comment for $datafile found in archive file
408
409=cut
410
411sub set_data_value {
412    my ($self, $datafile, $var, $val, $comment) = @_;
413    $self->setvalue(
414        sprintf('data_%s_%s', $var, $datafile),
415        $val,
416        $comment
417    );
418}
419
420=head3 checks
421
422=cut
423
424=head3 default_choice($oevent)
425
426In batch mode, check ObsData::event content and return result about behaviour
427soft whould have.
428
429=cut
430
431sub default_choice {
432    my ($self, $oevent) = @_;
433
434    for ($oevent->id) {
435        /^do_archive$/ and do {
436            return($oevent->test_result('archive_exists') ? 0 : 1);
437            last;
438        };
439        /^do_data$/ and do {
440            return 1; # TODO
441            last;
442        };
443        /^overwrite$/ and do {
444            return($oevent->test_result('dest_exists') ? 0 : 1);
445            last;
446        };
447    }
448}
449
450=head3 ask_user($oevent)
451
452The function take a filled ObsData::Event object, and perform return
453default_choice verification or result the callback set by the application.
454
455=cut
456
457sub ask_user {
458    my ($self, $oevent) = @_;
459
460    $self->logging(0, "Event(%s): %s",
461        $oevent->id,
462        $oevent->message);
463    foreach ($oevent->list_test) {
464        $self->logging(0, "  (%s) %s: %s",
465            $_,
466            $oevent->test_message($_),
467            $oevent->test_result($_),
468        );
469    }
470
471    my $res =
472        $self->{interactive_callback} ?
473        $self->{interactive_callback}->($self, $oevent) :
474        $self->default_choice($oevent);
475
476    $self->logging(0, "Event(%s): Result: %s", $oevent->id, $res);
477    return($res);
478}
479
480=head3 list_archive
481
482Return archive content
483
484=cut
485
486sub list_archive {
487    my ($self) = @_;
488    $self->load_archive or return;
489   
490    return $self->{Oarchive}->ls;
491}
492
493=head3 update_status
494
495Update status information about current archive
496
497=cut
498
499sub update_status {
500    my ($self) = @_;
501    my @filestats = stat("$self->{dir}/$self->{archivefile}");
502   
503    $self->setvalue('mtime', $filestats[9]);
504    $self->setvalue('size', $filestats[7]);
505    $self->setvalue('configtime', $self->{obsdata}->config_mtime);
506}
507
508=head3 find_match_data_type($label)
509
510Check if datafile match one of the regexp associate to datatype in the
511configuration, if yes, return the datatype and assiociate values, else
512return nothing
513
514=cut
515
516sub match_data_type {
517    my ($self, $datafile) =  @_;
518    foreach my $datatype (($self->{datatype}) || $self->{obsdata}->list_datatype) {
519        my $regexp = $self->{obsdata}->getvalue($datatype, 'match') or next;
520        my @data = $datafile =~ /$regexp/;
521        if (@data) {
522            return ($datatype, @data);
523        }
524    }
525}
526
527=head3 archive_match($datatype)
528
529Apply matching to archive filename
530
531=cut
532
533sub archive_match {
534    my ($self, $datatype) = @_;
535    my $regexp = $self->{obsdata}->getvalue($datatype, 'match_archive') or next;
536    my @data = $self->{archivefile} =~ /$regexp/;
537    return @data;
538}
539
540=head3 build_dest_filename($datafile)
541
542Build the destination file from original filename and optionnaly.
543Return the datatype and the filename. If no datatype apply,
544return undef, if destination file cannot be build, return only the datatype.
545
546If object datatype is not defined, the function try to find the right datatype from
547configuration.
548
549=cut
550
551sub build_dest_filename {
552    my ($self, $datafile) = @_;
553    my ($datatype, @data) = $self->match_data_type($datafile);
554   
555    if (! $datatype) {
556        return;
557    }
558
559    my @ardata = ();
560    if (my $arregexp = $self->{obsdata}->getvalue($datatype, 'match_archive')) {
561        @ardata = $self->{archivefile} =~ /$arregexp/;
562        if (!@ardata) {
563            return; # FIXME is this what we want ?
564        }
565    }
566
567    my $dest = $self->{obsdata}->getvalue($datatype, 'dest');
568    my @matcharg = split(/\s+/, $self->{obsdata}->getvalue($datatype, 'matcharg'));
569    my @matcharg_ar = split(/\s+/, $self->{obsdata}->getvalue($datatype, 'matcharg_archive')||"");
570    my @destarg = split(/\s+/, $self->{obsdata}->getvalue($datatype, 'destarg'));
571    my @gmtime = gmtime;
572    my %val;
573    foreach (@matcharg) {
574        $val{$_} = shift(@data);
575    }
576    foreach (@matcharg_ar) {
577        $val{$_} = shift(@ardata);
578    }
579    # FIXME Is this need ?
580    # foreach (keys %{$magic_words || {}}) {
581    #    $val{'$' . $_} = $magic_words->{$_};
582    #}
583   
584    $val{'$datafile'} = $datafile;
585    $val{'$archivefile'} = $self->{archivefile};
586    my $destfile = sprintf(
587        $dest,
588        map { m/^%/ ? strftime($_, @gmtime) : $val{$_} } @destarg,
589    );
590    if (!$destfile) {
591        $self->logging(4, "Can evaluate destination file from %s, %s",
592            $dest,
593            join(", ", map { m/^%/ ? strftime($_, @gmtime) : $val{$_} } @destarg)
594        );
595        return();
596    }
597    return($datatype, $destfile);
598}
599
600# Sub data function:
601
602=head3 get_data_dest($datafile)
603
604Return the datatype and destination for $datafile
605
606=cut
607
608sub get_data_dest {
609    my ($self, $datafile) = @_;
610
611    my ($t, $d) = $self->build_dest_filename(
612        $datafile,
613        $self->{datatype}
614    );
615    $self->logging(0, "%s/%s file, datatype %s, destination %s",
616        $self->{archivefile},
617        $datafile,
618        $t || '(none)',
619        $d || '(none)',
620    );
621    if ($t) {
622        $self->set_data_value($datafile, 'type', $t);
623    }
624    return($t, $d);
625}
626
627=head3 extract_data($datafile, $datatype, $destfile)
628
629Extract data file into $destfile, $datafile is informationnal only.
630
631$datatype and $destfile are the result of L<get_data_dest>.
632
633A basic call is:
634    $o->extract_data($file, $self->get_data_dest($file));
635
636Return 1 on success
637
638=cut
639
640sub extract_data {
641    # $datatype, informationnal only here
642    my ($self, $datafile, $datatype, $destfile) = @_;
643    $self->load_archive or return 0;
644    my $ltime = time;
645
646    $self->set_data_value(
647        $datafile,
648        'processtime',
649        $ltime,
650        scalar(localtime($ltime)) .
651        ", $ObsData::VERSION: $ObsData::CVSREV, $ObsData::Repository::CVSREV",
652    );
653    $self->set_data_value(
654        $datafile,
655        'processversion',
656        $ObsData::VERSION,
657    );
658   
659    if (!$destfile) {
660        $self->logging(3,
661            "%s/%s has no destination",
662            $self->{archivefile}, $datafile,
663        );
664        return 0;
665    }
666   
667    my $oe = ObsData::Event->new('overwrite',
668        "Should I overwrite $destfile"
669    );
670
671    $oe->add_test(
672        'dest_exists',
673        -e $destfile ? ("$destfile exists", 1) : ("$destfile don't exists", 0)
674    );
675    $self->ask_user($oe) or return 0;
676   
677    $self->logging(0,
678        "Trying to extract %s from %s/%s",
679        $destfile,
680        $self->{archivefile}, $datafile
681    );
682
683    my ($dirname) = $destfile =~ m!(?:(.*)/)?(.*)$!; #! vim syntax sux
684   
685    if ($dirname && ! -d $dirname) {
686        $self->logging(1, "Creating directory %s", $dirname);
687        if ($self->{dry_run}) {
688            $self->logging(1,
689                "Testing mode, assume %s was correctly created",
690                $dirname
691            );
692        } else {
693            if( ! File::Path::mkpath($dirname)) { 
694                $self->logging(
695                    4, "Can't create directory %s", 
696                    $dirname
697                );
698                return 0;
699            }
700        }
701    }
702
703    if ($self->{dry_run}) {
704        $self->logging(2,
705            "Testing mode, %s/%s would be extracted as %s",
706            $self->{archivefile},
707            $datafile,
708            $destfile,
709        );
710    } else {
711        if ($self->{Oarchive}->extract($datafile, $destfile)) {
712            $self->set_data_value($datafile, 'dest', $destfile);
713            $self->logging(
714                2, "Extraction of %s/%s done as %s",
715                $self->{archivefile},
716                $datafile,
717                $destfile,
718            );
719        } else {
720            $self->logging(
721                4, "Can't extract %s/%s to %s: %s",
722                $self->{archivefile}, $datafile, $destfile,
723                $self->{Oarchive}->error,
724            );
725            return 0;
726        }
727    }
728   
729    return 1;
730}
731
732=head3 process
733
734Process all files in the archive
735
736=cut
737
738sub process {
739    my ($self) = @_;
740
741    foreach my $data ($self->list_archive) {
742        my $oe = ObsData::Event->new('do_data',
743            "Should I extract $data from $self->{archivefile}"
744        );
745       
746        $self->ask_user($oe) or next;
747
748        $self->extract_data($data, $self->get_data_dest($data));
749    }
750   
751    $self->update_status;
752
753    return 1;
754}
755
756=head1 AUTHOR
757
758Olivier Thauvin <olivier.thauvin@aerov.jussieu.fr>
759
760=cut
761
7621;
Note: See TracBrowser for help on using the repository browser.