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

Last change on this file since 171 was 171, checked in by thauvin, 18 years ago
  • get interative_callback from ObsData? parent if not set
  • Property cvs2svn:cvs-rev set to 1.58
  • Property svn:keywords set to Author Date Id Revision
File size: 15.6 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;
13
14our $CVSID = q$Id$;
15our $CVSREV = (q$Revision$ =~ /^Revision: (.*) $/)[0];
16
17=head1 METHODS
18
19=head2 ObsData::Repository
20
21=head3 new($parent, %param)
22
23Create a new ObsData::Repository object
24
25=over 4
26
27=item $parent
28
29A ObsData::Repository to take information
30
31=item %param
32
33A list of paramter to set in new object:
34
35=over 4
36
37=item datatype
38
39If set, only this datatype will be checked
40
41=item obs
42
43The obsveratory where data come from, informationnal only
44
45=item dir
46
47The based directory where archive are located
48
49=item glob
50
51Apply this glob to search files in dir
52
53=item statusfile
54
55The file where status info should be written
56
57=item status
58
59The Config::IniFiles object
60
61=back
62
63=back
64
65=cut
66
67sub new {
68    my ($class, $parent, %param) = @_;
69
70    my $or = { %$parent };
71    foreach (keys %param) {
72        $or->{$_} = $param{$_};
73    }
74   
75    $or->{obsdata} or die "Error #1de8d015, please insult programmer";
76    $or->{dir} or die "No dir given";
77    -d $or->{dir} or return undef;
78   
79    $or->{status} ||= new Config::IniFiles(
80        -file => (($or->{statusfile} && -f $or->{statusfile}) ? $or->{statusfile} : undef),
81        # -default => '.', # Is this a good idea
82    );
83    $or->{statusfile} ||= $or->{status}->GetFileName;
84    $or->{interative_callback} ||= $or->{obsdata}->{interative_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 update_global_status
98
99Set standard information into status ini file
100
101=cut
102
103sub update_global_status {
104    my ($self) = @_;
105    $self->{status}->AddSection('.');
106    $self->{status}->SetSectionComment('.',
107        $ObsData::CVSID,
108        $ObsData::Repository::CVSID,
109        scalar(localtime),
110    );
111
112    my $hostname = `hostname`;
113    chomp($hostname);
114    $self->{status}->newval('.', 'hostname', $hostname);
115    $self->{status}->SetParameterComment(
116        '.',
117        'hostname',
118        'The computer host name'
119    );
120
121    $self->{status}->newval('.', 'directory', $self->{dir});
122    $self->{status}->SetParameterComment(
123        '.',
124        'directory', 
125        'Directory where archive where located'
126    );
127   
128    $self->{status}->newval('.', 'runfrom', getcwd());
129    $self->{status}->SetParameterComment(
130        '.',
131        'runfrom',
132        'The current directory'
133    );
134}
135
136=head3 save_status
137
138Save current information about parsed files into status file
139
140=cut
141
142sub save_status {
143    my ($self) = @_;
144    $self->{statusfile} or return;
145    if (!$self->{dry_run}) {
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,
209            "directory %s does not exists",
210            $self->{dir},
211        );
212        return;
213    }
214
215    my $patern = $self->{patern} || '*';
216    foreach my $f (glob("$self->{dir}/$patern")) {
217
218        -f $f or next; # skip no regular files
219        $f =~ m/obsdata\.ini$/ and next; # avoid default status file
220       
221        my $bf = $f;
222        $bf =~ s!^\Q$self->{dir}\E/*!!;
223        push(@files, $bf);
224    }
225    @files
226}
227
228=head3 process
229
230Process all files in directory
231
232=cut
233
234sub process {
235    my ($self) = @_;
236    $self->logging(0, "%s() start for %s", (caller(0))[3], $self->{dir});
237
238    foreach my $file ($self->findfile) {
239        my $orda = ObsData::Repository::dir::archive->new(
240            $self,
241            archivefile => $file,
242        );
243        if(!$orda->do_continue('do_archive')) {
244            $self->logging(0,
245                "Archive %s/%s allready parsed, skipping",
246                $self->{dir},
247                $file
248            );
249            next;
250        }
251        $orda->process;
252    }
253}
254
255=head2 ObsData::Repository::dir::archive
256
257Object to handle archive and to dispatch data
258
259=cut
260
261package ObsData::Repository::dir::archive;
262
263our @ISA = qw(ObsData::Repository);
264
265=head3 new
266
267=cut
268
269sub new {
270    my ($class, $infos, %param) = @_;
271
272    my $or = ObsData::Repository->new($infos, %param) or return;
273    # dir directory to parse
274    $or->{archivefile} or die "No archivefile given";
275
276    # $or->{interative_callback}
277    $or->{_cache_checks} = {};
278   
279    $or->logging(0,
280        "Preparing to parse %s/%s",
281         $or->{dir},
282         $or->{archivefile}
283    );
284
285    bless($or, $class);
286}
287
288=head3 load_archive
289
290Try to read archive, return 1 on success
291
292=cut
293
294sub load_archive {
295    my ($self) = @_;
296    if ($self->{Oarchive}) {
297        return 1;
298    } else {
299        $self->{Oarchive} = ObsData::Archive->new("$self->{dir}/$self->{archivefile}");
300        if ($self->{Oarchive}) {
301            $self->logging(0,
302                "Archive %s/%s properly load",
303                $self->{dir}, $self->{archivefile}
304            );
305            return 1;
306        } else {
307            $self->logging(4, 
308                "Can't handle archive %s/%s: %s, skipping",
309                $self->{dir}, $self->{archivefile},
310                ObsData::Archive->error
311            );
312            return 0;
313        }
314    }
315}
316
317=head2 getvalue
318
319=cut
320
321sub getvalue {
322    my ($self, $var, $default) = @_;
323    return $self->{status}->val($self->{archivefile}, $var, $default);
324}
325
326=head2 setvalue
327
328=cut
329
330sub setvalue {
331    my ($self, $var, $val, $comment) = @_;
332    if (!$self->{status}->SectionExists($self->{archivefile})) {
333        $self->{status}->AddSection($self->{archivefile});
334    }
335    $self->{status}->newval($self->{archivefile}, $var, $val) if(defined($val));
336    $self->{status}->SetParameterComment(
337        $self->{archivefile}, $var, $comment
338    ) if(defined($comment));
339}
340
341=head2 get_data_value
342
343=cut
344
345sub get_data_value {
346    my ($self, $datafile, $var, $default) = @_;
347    return $self->getvalue(
348        sprintf('data_%s_%s', $var, $datafile),
349        $var, 
350        $default
351    );
352}
353
354=head2 set_data_value
355
356=cut
357
358sub set_data_value {
359    my ($self, $datafile, $var, $val, $comment) = @_;
360    $self->setvalue(
361        sprintf('data_%s_%s', $var, $datafile),
362        $val,
363        $comment
364    );
365}
366
367=head3 checks
368
369=cut
370
371my $checks = {
372    archive_exists => sub {
373        my ($self) = @_;
374        my $res;
375        if($self->{status}->SectionExists($self->{archivefile})) {
376            $res = 1;
377        } else {
378            $res = 0;
379        }
380        return(
381            $res, 
382            $self->logging(0,
383                "%s/%s is%s new archive",
384                $self->{dir},
385                $self->{archivefile},
386                $res ? " not" : "",
387            )
388        );     
389    },
390
391    archive_size => sub {
392        my ($self) = @_;
393        my $res;
394        if (defined(my $oldsize = $self->getvalue('size')) &&
395            defined(my $size = (stat("$self->{dir}/$self->{archivefile}"))[7])) {
396            my $res = $size <=> $oldsize;
397            return $res, $self->logging(0,
398                "Size for %s/%s %s",
399                $self->{dir},
400                $self->{archivefile},
401                $res == 0 ?
402                    sprintf("does not differ (%d)", $size) :
403                    sprintf("differ %d => %d", $oldsize, $size)
404            );
405        }
406    },
407
408    archive_mtime => sub {
409        my ($self) = @_;
410        my $res;
411        if (defined(my $oldmtime = $self->getvalue('mtime')) &&
412            defined(my $mtime = (stat("$self->{dir}/$self->{archivefile}"))[9])) {
413            my $res = $mtime <=> $oldmtime;
414            return $res, $self->logging(0,
415                "Mtime for %s/%s %s",
416                $self->{dir},
417                $self->{archivefile},
418                $res == 0 ?
419                    sprintf("does not differ (%d)", $mtime) :
420                    sprintf("differ %d => %d", $oldmtime, $mtime)
421            );
422        }
423    },
424
425    dest_exists => sub {
426        my ($self, $check, $datafile, $dest) = @_;
427        if (-e $dest) {
428            return 1, "$dest exists";
429        } else {
430            return 0, "$dest don't exists";
431        }
432    },
433
434    data_extracted => sub {
435        my ($self, $check, $datafile) = @_;
436        my $last_process = $self->get_data_value($datafile, 'processtime');
437        if ($last_process) {
438            $self->logging(0, "%s/%s has been already processed at %s",
439                $self->{archivefile},
440                $datafile,
441                scalar(localtime($last_process)),
442            );
443        }
444        $last_process
445    },
446};
447
448#sub DESTROY {
449#    my ($self) = @_;
450#    $self->logging(0, "Destroy ObsData::Repository::archive for %s", ref($self));
451#    $self->SUPER::DESTROY();
452#}
453
454=head3 do_check($check, @args)
455
456Perform $check with @args if exists and return result of check
457
458=cut
459
460sub do_check {
461    my ($self, $check, @args) = @_;
462   
463    if (!$checks->{$check}) {
464        die $self->logging(5,
465            "check %s does not exists, exiting now",
466            $check
467        );
468    }
469   
470    return $checks->{$check}->($self, $check, @args);
471}
472
473sub populate_event {
474    my ($self, $oevent, @args) = @_;
475    my ($code, $text);
476   
477    for ($oevent->id) {
478        /^do_archive$/ and do {
479            foreach my $t (qw(archive_size archive_mtime archive_exists)) {
480                ($code, $text) = $self->do_check($t);
481                $oevent->add_test($t, $text, $code);
482            }
483            last;
484        };
485        /^do_data$/ and do {
486            last;
487        };
488        /^overwrite$/ and do {
489            ($code, $text) = $self->do_check('dest_exists', @args);
490            $oevent->add_test('dest_exists', $text, $code);
491            last;
492        };
493    }
494}
495
496=head3 default_choice($oevent)
497
498=cut
499
500sub default_choice {
501    my ($self, $oevent) = @_;
502
503    for ($oevent->id) {
504        /^do_archive$/ and do {
505            return($oevent->test_result('archive_exists') ? 0 : 1);
506            last;
507        };
508        /^do_data$/ and do {
509            return 1; # TODO
510            last;
511        };
512        /^overwrite$/ and do {
513            return($oevent->test_result('dest_exists') ? 0 : 1);
514            last;
515        };
516    }
517}
518
519=head3 do_continue($why, @args)
520
521Perform default or call the interactive callback.
522
523    $why is the reason of the call
524    @args are argument to passed to callback
525
526=cut
527
528sub do_continue {
529    my ($self, $why, @args) = @_;
530
531    my $oevent = ObsData::Event->new($why, $why);
532    $self->populate_event(
533        $oevent, @args
534    );
535
536    if ($self->{interative_callback}) {
537        return $self->{interative_callback}->($self, $oevent);
538    } else {
539        return $self->default_choice($oevent);
540    }
541}
542
543=head3 list_archive
544
545Return archive content
546
547=cut
548
549sub list_archive {
550    my ($self) = @_;
551    $self->load_archive or return;
552   
553    return $self->{Oarchive}->ls;
554}
555
556=head3 update_status
557
558Update status information about current archive
559
560=cut
561
562sub update_status {
563    my ($self) = @_;
564    my @filestats = stat("$self->{dir}/$self->{archivefile}");
565   
566    $self->setvalue('mtime', $filestats[9]);
567    $self->setvalue('size', $filestats[7]);
568    $self->setvalue('configtime', $self->{obsdata}->config_mtime);
569}
570
571# Sub data function:
572
573
574
575=head3 get_data_dest($datafile)
576
577Return the datatype and destination for $datafile
578
579=cut
580
581sub get_data_dest {
582    my ($self, $datafile) = @_;
583
584    my ($t, $d) = $self->{obsdata}->build_dest_filename(
585        $datafile,
586        $self->{datatype}
587    );
588    $self->logging(0, "%s/%s file, datatype %s, destination %s",
589        $self->{archivefile},
590        $datafile,
591        $t || '(none)',
592        $d || '(none)',
593    );
594    if ($t) {
595        $self->set_data_value($datafile, 'type', $t);
596    }
597    return($t, $d);
598}
599
600=head3 extract_data($datafile, $datatype, $destfile)
601
602Extract data file into $destfile, $datafile is informationnal only.
603
604$datatype and $destfile are the result of L<get_data_dest>.
605
606A basic call is:
607    $o->extract_data($file, $self->get_data_dest($file));
608
609Return 1 on success
610
611=cut
612
613sub extract_data {
614    # $datatype, informationnal only here
615    my ($self, $datafile, $datatype, $destfile) = @_;
616    $self->load_archive or return 0;
617    my $ltime = time;
618
619    $self->set_data_value(
620        $datafile,
621        'processtime',
622        $ltime,
623        scalar(localtime($ltime)) .
624        ", $ObsData::VERSION: $ObsData::CVSREV, $ObsData::Repository::CVSREV",
625    );
626    $self->set_data_value(
627        $datafile,
628        'processversion',
629        $ObsData::VERSION,
630    );
631   
632    if (!$destfile) {
633        $self->logging(3,
634            "%s/%s has no destination",
635            $self->{archivefile}, $datafile,
636        );
637        return 0;
638    }
639   
640    if(!$self->do_continue('overwrite', $datafile, $destfile)) {
641        $self->logging(3,
642            "%s extracted from %s/%s already exists, I won't overwrite it",
643            $destfile,
644            $self->{archivefile}, $datafile,
645        );
646        return 0;
647    }
648   
649    $self->logging(0,
650        "Trying to extract %s from %s/%s",
651        $destfile,
652        $self->{archivefile}, $datafile
653    );
654
655    my ($dirname) = $destfile =~ m!(?:(.*)/)?(.*)$!; #! vim syntax sux
656   
657    if ($dirname && ! -d $dirname) {
658        $self->logging(1, "Creating directory %s", $dirname);
659        if ($self->{dry_run}) {
660            $self->logging(1,
661                "Testing mode, assume %s was correctly created",
662                $dirname
663            );
664        } else {
665            if( ! File::Path::mkpath($dirname)) { 
666                $self->logging(
667                    4, "Can't create directory %s", 
668                    $dirname
669                );
670                return 0;
671            }
672        }
673    }
674
675    if ($self->{dry_run}) {
676        $self->logging(2,
677            "Testing mode, %s/%s would be extracted as %s",
678            $self->{archivefile},
679            $datafile,
680            $destfile,
681        );
682    } else {
683        if ($self->{Oarchive}->extract($datafile, $destfile)) {
684            $self->set_data_value($datafile, 'dest', $destfile);
685            $self->logging(
686                2, "Extraction of %s/%s done as %s",
687                $self->{archivefile},
688                $datafile,
689                $destfile,
690            );
691        } else {
692            $self->logging(
693                4, "Can't extract %s/%s to %s: %s",
694                $self->{archivefile}, $datafile, $destfile,
695                $self->{Oarchive}->error,
696            );
697            return 0;
698        }
699    }
700   
701    return 1;
702}
703
704=head3 process
705
706Process all files in the archive
707
708=cut
709
710sub process {
711    my ($self) = @_;
712
713    foreach my $data ($self->list_archive) {
714        if (!$self->do_continue('do_data', $data)) {
715            $self->logging(0,
716                "%s/%s has allready processed, skipping",
717                $self->{archivefile},
718                $data
719            );
720            next;
721        }
722        $self->extract_data($data, $self->get_data_dest($data));
723    }
724   
725    $self->update_status;
726
727    return 1;
728}
729
730=head1 AUTHOR
731
732Olivier Thauvin <olivier.thauvin@aerov.jussieu.fr>
733
734=cut
735
7361;
Note: See TracBrowser for help on using the repository browser.