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

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