ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/Cache/Cache.pm
Revision: 1.14
Committed: Tue Oct 18 14:59:28 2011 UTC (13 years, 6 months ago) by muzaffar
Content type: text/plain
Branch: MAIN
CVS Tags: V2_2_5_pre2, V2_2_5_pre1, V2_2_4, V2_2_4_pre9, V2_2_4_pre8, V2_2_4_pre7, V2_2_4_pre6, V2_2_4_pre5, V2_2_4_pre4, V2_2_4_pre3, V2_2_4_pre2, V2_2_4_pre1, HEAD
Changes since 1.13: +0 -3 lines
Log Message:
removed cvs $id statement

File Contents

# Content
1 #____________________________________________________________________
2 # File: Cache.pm
3 #____________________________________________________________________
4 #
5 # Author: Shaun Ashby <Shaun.Ashby@cern.ch>
6 # (with contribution from Lassi.Tuura@cern.ch)
7 # Copyright: 2003 (C) Shaun Ashby
8 #
9 #--------------------------------------------------------------------
10
11 =head1 NAME
12
13 Cache::Cache - A generic directory cache object.
14
15 =head1 SYNOPSIS
16
17 my $cacheobject=Cache::Cache->new();
18
19 =head1 DESCRIPTION
20
21 A package to provide caching of directory information. Directory timestamps
22 are tracked on further reading of an existing cache and lists of modified
23 directories and BuildFiles can be obtained.
24
25 =head1 METHODS
26
27 =over
28
29 =cut
30
31 package Cache::Cache;
32 require 5.004;
33
34 use Exporter;
35 use Utilities::AddDir;
36 @ISA=qw(Exporter);
37 #
38
39 =item C<new()>
40
41 Create a new Cache::Cache object. The name of the cache is B<DirCache.db.gz> by default.
42
43 =cut
44
45 sub new()
46 ###############################################################
47 # new #
48 ###############################################################
49 # modified : Thu Nov 27 16:45:27 2003 / SFA #
50 # params : #
51 # : #
52 # function : #
53 # : #
54 ###############################################################
55 {
56 my $proto=shift;
57 my $class=ref($proto) || $proto;
58 my $self=
59 {
60 CACHENAME => "DirCache.db.gz", # Name of global file/dir cache;
61 BFCACHE => {}, # BuildFile cache;
62 DIRCACHE => {}, # Source code cache;
63 EXTRASUFFIX => {}, # path with extra suffix;
64 STATUS => 0, # Status of cache: 1 => something changed. If so, force save;
65 VERBOSE => 0 # Verbose mode (0/1);
66 };
67
68 bless $self,$class;
69 return $self;
70 }
71
72 =item C<getdir($path)>
73
74 Return a list of directories starting from $path.
75
76 =cut
77
78 sub getdir()
79 {
80 my $self=shift;
81 my $path=shift;
82 my $ignore=shift || 'CVS|\\..*';
83 my $match=shift || ".+";
84
85 opendir (DIR, $path) || die "$path: cannot read: $!\n";
86 # Skip .admin and CVS subdirectories too.
87 # Also skip files that look like backup files or files being modified with emacs:
88 my @items = map { "$path/$_" } grep ((-d "$path/$_") && ($_=~/^($match)$/) && ($_!~/^($ignore)$/),readdir(DIR));
89 closedir (DIR);
90 return @items;
91 }
92
93 =item C<prune($path)>
94
95 Recursively remove directories from the cache starting at $path.
96
97 =cut
98
99 sub prune()
100 {
101 my $self=shift;
102 my $path = shift;
103 my $skipparent = shift || 0;
104 my $suffix = shift || "";
105 $self->extra_suffix($path,$suffix) if ($suffix);
106 if (!$skipparent)
107 {
108 my $parent = $path;
109 $parent =~ s|(.*)/[^/]+$|$1|;
110 if ($parent ne $path && exists $self->{DIRCACHE}{$parent})
111 {
112 my ($time, @subs) = @{$self->{DIRCACHE}{$parent}};
113 $self->{DIRCACHE}{$parent} = [ $time, grep ($_ ne $path, @subs) ];
114 $self->{ADDEDDIR}{$parent}=1;
115 $self->cachestatus(1);
116 }
117 }
118 if (exists $self->{ADDEDDIR}{$path}){delete $self->{ADDEDDIR}{$path};}
119 return if ! exists $self->{DIRCACHE}{$path};
120 $self->cachestatus(1);
121 foreach my $bf ("$ENV{SCRAM_BUILDFILE}.xml","$ENV{SCRAM_BUILDFILE}")
122 {
123 if (exists $self->{BFCACHE}{"${path}/${bf}"})
124 {
125 if (!-f "${path}/${bf}") {$self->{REMOVEDBF}{"${path}/${bf}"}=1;}
126 delete $self->{BFCACHE}{"${path}/${bf}"};
127 if (exists $self->{ADDEDBF}{"${path}/${bf}"}){delete $self->{ADDEDBF}{"${path}/${bf}"};}
128 last;
129 }
130 }
131 if (!-d $path) {$self->{REMOVEDDIR}{$path}=1;}
132 my (undef, @subs) = @{$self->{DIRCACHE}{$path}};
133 delete $self->{DIRCACHE}{$path};
134 foreach my $sub (@subs)
135 {
136 $self->prune($sub,1);
137 }
138 }
139
140 =item C<checktree($path, $required, $dofiles)>
141
142 A timestamp checking routine. Starting from $path, check all timestamps of
143 directories and their files. Skip all files unless $dofiles is 1.
144
145 =cut
146
147 sub checktree()
148 {
149 my ($self, $path, $required) = @_;
150 # Check if this path needs to be checked. If it exists, has the same mode
151 # and the same time stamp, it's up to date and doesn't need to be checked.
152 # Otherwise if it is a directory whose time-stamp has changed, rescan it.
153 # If the path has be removed, prune it from the cache. Note that we skip
154 # non-directories unless $dofiles is set. Considering only directories is
155 # dramatically faster.
156
157 # NB: We stat each path only once ever. The special "_" file handle uses
158 # the results from the last stat we've made. See man perlfunc/stat.
159 if (! stat($path))
160 {
161 die "$path: $!\n" if $required;
162 $self->prune($path);
163 return;
164 }
165
166 # If the entry in the cache is not the same mode or time, force an update.
167 # Otherwise use the cache as the list of items we need to change.
168 my $cached = $self->{DIRCACHE}{$path};
169 my @items = ();
170 my $matchdir='[a-zA-Z0-9][a-zA-Z0-9-_]*';
171
172 if (! -d _)
173 {
174 $self->prune($path);
175 return;
176 }
177 elsif (! $cached)
178 {
179 # When a directory is added, this block is activated
180 $self->{ADDEDDIR}{$path}=1;
181 $self->{DIRCACHE}{$path} = [ (stat(_))[9], @items = $self->getdir($path,'',$matchdir) ];
182 $required = 1;
183 $self->cachestatus(1);
184 }
185 elsif ($cached->[0] != (stat(_))[9])
186 {
187 my $ntime = (stat(_))[9];
188 # When a subdirectory is removed, this block is activated
189 #
190 # This is a parent directory. We store this as any
191 # update can be taken recursively from this dir:
192 #$self->modified_parentdirs($path);
193 # Current subdirs:
194 my %curdirs = map { $_ => 1 } $self->getdir($path,'',$matchdir);
195 my %olddirs = ();
196 for (my $i = 1; $i <= $#$cached; $i++)
197 {
198 my $d = $cached->[$i];
199 $olddirs{$d}=1;
200 if (!exists $curdirs{$d})
201 {
202 $self->prune($d,1);
203 }
204 }
205
206 foreach my $d (keys %curdirs)
207 {
208 if (!exists $olddirs{$d})
209 {
210 if ($self->extra_suffix($d))
211 {
212 delete $curdirs{$d};
213 }
214 }
215 }
216
217 $self->{ADDEDDIR}{$path}=1;
218 $self->cachestatus(1);
219 @items = keys %curdirs;
220 $required = 0;
221 $self->{DIRCACHE}{$path} = [ $ntime, @items ];
222 }
223 else
224 {
225 (undef, @items) = @{$self->{DIRCACHE}{$path}};
226 $required = 0;
227 }
228 if (($self->{cachereset}) && (!exists $self->{ADDEDDIR}{$path}))
229 {
230 $self->{ADDEDDIR}{$path}=1;
231 $self->cachestatus(1);
232 }
233
234 my $bfcachedir=$ENV{LOCALTOP}."/".$ENV{SCRAM_TMP}."/".$ENV{SCRAM_ARCH}."/cache/bf/${path}";
235 my $cbf="${bfcachedir}/$ENV{SCRAM_BUILDFILE}";
236 my $bftime=0;
237 my $bf="${path}/$ENV{SCRAM_BUILDFILE}";
238 foreach my $ext (".xml","")
239 {
240 my $bfn="$bf$ext";
241 if (! stat ($bfn))
242 {
243 if (exists $self->{BFCACHE}{$bfn})
244 {
245 $self->{REMOVEDBF}{$bfn}=1;
246 delete $self->{BFCACHE}{$bfn};
247 Utilities::AddDir::adddir($bfcachedir);
248 open(BF,">${cbf}");close(BF);
249 $self->cachestatus(1);
250 }
251 }
252 else
253 {
254 $bftime = (stat(_))[9];
255 if ((! exists $self->{BFCACHE}{$bfn}) ||
256 ($bftime != $self->{BFCACHE}{$bfn}))
257 {
258 if ((!-f "${cbf}") || (exists $self->{BFCACHE}{$bfn}))
259 {
260 Utilities::AddDir::adddir($bfcachedir);
261 open(BF,">${cbf}");close(BF);
262 }
263 $self->{ADDEDBF}{$bfn}=1;
264 delete $self->{BFCACHE}{$bf};
265 $self->{BFCACHE}{$bfn}=$bftime;
266 if ($ext eq ""){$self->{nonxml}+=1;}
267 $self->cachestatus(1);
268 }
269 elsif($self->{cachereset})
270 {
271 $self->{ADDEDBF}{$bfn}=1;
272 if ($ext eq ""){$self->{nonxml}+=1;}
273 if (!-f "${cbf}")
274 {
275 Utilities::AddDir::adddir($bfcachedir);
276 open(BF,">${cbf}");close(BF);
277 }
278 $self->cachestatus(1);
279 }
280 last;
281 }
282 }
283 if (exists $self->{ExtraDirCache})
284 {
285 eval {$self->{ExtraDirCache}->DirCache($self,$path);};
286 }
287 # Process sub-directories
288 foreach my $item (@items)
289 {
290 $self->checktree($item, $required);
291 }
292 }
293
294 =item C<clean_cache_recursive($startdir)>
295
296 Recursive remove cached data for directories under $startdir.
297
298 =cut
299
300 sub clean_cache_recursive()
301 {
302 my $self=shift;
303 my ($startdir) = @_;
304 my $children = $self->{DIRCACHE}->{$startdir};
305
306 for (my $i = 2; $i <= $#$children; $i++)
307 {
308 # Remove all children:
309 $self->schedremoval($children->[$i]);
310 $self->clean_cache_recursive($children->[$i]);
311 }
312
313 delete $self->{DIRCACHE}->{$startdir};
314 return $self;
315 }
316
317 =item C<dirtree($dir,$dofiles)>
318
319 Starting from $dir, scan the directory tree. Ignore files unless $dofiles is set. This
320 function just calls checktree().
321
322 =cut
323
324 sub dirtree()
325 {
326 my $self=shift;
327 my ($dir) = @_;
328
329 # Get the directory tree:
330 $self->checktree($dir, 1);
331 return $self;
332 }
333
334 =item C<checkfiles()>
335
336 Function to actually run the timestamp checks. This is only run from
337 SCRAM::CMD::build().
338
339 =cut
340
341 sub checkfiles()
342 {
343 my $self=shift;
344 $self->{cachereset}=shift || 0;
345 # Scan config dir for top-level data, then start from src:
346 my @scandirs=($ENV{SCRAM_CONFIGDIR}, $ENV{SCRAM_SOURCEDIR});
347 # Loop over all directories that need scanning (normally just src and config):
348 $self->{nonxml}=0;
349 eval ("use SCRAM::Plugins::DirCache;");
350 if(!$@) {$self->{ExtraDirCache} = SCRAM::Plugins::DirCache->new();}
351 foreach my $scand (@scandirs)
352 {
353 $self->logmsg("SCRAM: Scanning $scand [dofiles set to ".$dofiles."]\n");
354 # Check the directory tree:
355 $self->dirtree($scand);
356 }
357 if ($self->cachestatus())
358 {
359 foreach my $bf ("$ENV{SCRAM_BUILDFILE}.xml","$ENV{SCRAM_BUILDFILE}")
360 {
361 if (exists $self->{BFCACHE}{"$ENV{SCRAM_CONFIGDIR}/${bf}"})
362 {
363 $self->{ADDEDBF}{"$ENV{SCRAM_CONFIGDIR}/${bf}"}=1;
364 last;
365 }
366 }
367 }
368 delete $self->{ExtraDirCache};
369 if ($self->{nonxml} > 0)
370 {
371 #print STDERR "**** WARNING: ",$self->{nonxml}," non-xml based $ENV{SCRAM_BUILDFILE} were read.\n";
372 }
373 return $self;
374 }
375
376 =item C<dircache()>
377
378 Return a reference to the directory cache hash.
379
380 =cut
381
382 sub dircache()
383 {
384 my $self=shift;
385 # Return the file cache:
386 return $self->{DIRCACHE};
387 }
388
389 =item C<added_dirs($path)>
390
391 Add $path to the list of directories added since last scan, or return
392 the list of added directories if no argument given.
393
394 =cut
395
396 sub added_dirs()
397 {
398 my $self=shift;
399 my ($path) = @_;
400
401 # If we have a path to add, add it.
402 if ($path)
403 {
404 if (exists($self->{ADDEDDIRS}))
405 {
406 push(@{$self->{ADDEDDIRS}}, $path);
407 }
408 else
409 {
410 $self->{ADDEDDIRS} = [ $path ];
411 }
412 }
413 else
414 {
415 # Otherwise, return the array of added dirs:
416 my @addeddirs = @{$self->{ADDEDDIRS}};
417 delete $self->{ADDEDDIRS};
418 return \@addeddirs;
419 }
420 }
421
422 =item C<modified_parentdirs($path)>
423
424 Add a directory $path to the list of parent directories (directories
425 having subdirectories), or return a reference to the list.
426 Storing this parent allows any update to be taken recursively from this
427 location.
428
429 =cut
430
431 sub modified_parentdirs()
432 {
433 my $self=shift;
434 my ($path) = @_;
435
436 # If we have a path to add, add it.
437 # Don't bother if it's the main source dir as we don't
438 # want to rescan everything from src (that would be silly):
439 if ($path && $path ne $ENV{SCRAM_SOURCEDIR})
440 {
441 if (exists($self->{MODPARENTDIRS}))
442 {
443 push(@{$self->{MODPARENTDIRS}}, $path);
444 }
445 else
446 {
447 $self->{MODPARENTDIRS} = [ $path ];
448 }
449 }
450 else
451 {
452 # Otherwise, return the array of added dirs:
453 my @moddeddirs = @{$self->{MODPARENTDIRS}};
454 delete $self->{MODPARENTDIRS};
455 return \@moddeddirs;
456 }
457 }
458
459 =item C<schedremoval($d)>
460
461 Add directory $d to list of directories that should be removed
462 recursively from the cache.
463 If no arguments given, return a reference to a list of
464 directories to be removed.
465
466 =cut
467
468 sub schedremoval()
469 {
470 my $self=shift;
471 my ($d)=@_;
472
473 if ($d)
474 {
475 if (exists($self->{REMOVEDATA}))
476 {
477 push(@{$self->{REMOVEDATA}},$d);
478 }
479 else
480 {
481 $self->{REMOVEDATA} = [ $d ];
482 }
483 }
484 else
485 {
486 my $remove = [ @{$self->{REMOVEDATA}} ];
487 $self->{REMOVEDATA} = [];
488 return $remove;
489 }
490 }
491
492 =item C<filestatus()>
493
494 Return a true or false value depending on whether
495 a BuildFile was changed or not.
496
497 =cut
498
499 sub filestatus()
500 {
501 my $self=shift;
502 # Here we want to return a true or false value depending on whether
503 # or not a buildfile was changed:
504 return $self->{STATUSSRC};
505 }
506
507 =item C<configstatus()>
508
509 Return a true or false value depending on whether
510 a file in the config directory was changed or not.
511
512 =cut
513
514 sub configstatus()
515 {
516 my $self=shift;
517 # Here we want to return a true or false value depending on whether or not a file
518 # in config dir was changed:
519 return $self->{STATUSCONFIG};
520 }
521
522 =item C<bf_for_scanning()>
523
524 Return a list of BuildFiles to re-read. Note that this is only done
525 if the status was changed (i.e. not necessary to read through the list
526 of BuildFiles to know whether something changed as the flag B<STATUSSRC>
527 is set as the source tree is checked).
528 If B<STATUSCONFIG> is true, all BuildFiles are marked to be read.
529
530 =cut
531
532 sub bf_for_scanning()
533 {
534 my $self=shift;
535 my $MODIFIED = [];
536 map { push(@$MODIFIED, $_) } @{$self->{ADDEDBF}};
537 return $MODIFIED;
538 }
539
540 =item C<paths()>
541
542 Return a reference to an array of directories for the current source tree.
543
544 =cut
545
546 sub paths()
547 {
548 my $self=shift;
549 my $paths = {};
550
551 my $ALLDIRS = [];
552 map { push(@$ALLDIRS, $_) } keys %{$self->{DIRCACHE}};
553 return $ALLDIRS;
554 }
555
556 =item C<verbose()>
557
558 Turn verbosity for the cache on or off.
559
560 =cut
561
562 sub verbose()
563 {
564 my $self=shift;
565 # Turn on verbose mode:
566 @_ ? $self->{VERBOSE} = shift
567 : $self->{VERBOSE}
568 }
569
570 =item C<cachestatus()>
571
572 Set or return the cache status to indicate whether or not a file
573 timestamp has changed since the last pass.
574
575 =cut
576
577 sub cachestatus()
578 {
579 my $self=shift;
580 # Set/return the status of the cache:
581 @_ ? $self->{STATUS} = shift
582 : $self->{STATUS}
583 }
584
585 =item C<logmsg(@message)>
586
587 Print a message to B<STDERR>. This is only used in
588 checktree(), checkfiles() and paths().
589
590 =cut
591
592 sub logmsg()
593 {
594 my $self=shift;
595 # Print a message to STDOUT if VERBOSE is true:
596 print STDERR @_ if $self->verbose();
597 }
598
599 =item C<name()>
600
601 Set or return the name of the cache. Normally set
602 to B<DirCache.db.gz> (and not architecture dependent).
603
604 =cut
605
606 sub name()
607 {
608 my $self=shift;
609 # Set/return the name of the cache to use:
610 @_ ? $self->{CACHENAME} = shift
611 : $self->{CACHENAME}
612 }
613
614 sub get_data()
615 {
616 my $self=shift;
617 my $type=shift;
618 @_ ? $self->{$type} = shift
619 : $self->{$type};
620 }
621
622 sub extra_suffix()
623 {
624 my $self=shift;
625 my $path=shift;
626 @_ ? $self->{EXTRASUFFIX}{$path}=shift
627 : exists $self->{EXTRASUFFIX}{$path};
628 }
629
630 sub get_nonxml()
631 {
632 my $self=shift;
633 return $self->{nonxml};
634 }
635
636 1;
637
638 =back
639
640 =head1 AUTHOR
641
642 Shaun Ashby (with contribution from Lassi Tuura)
643
644 =head1 MAINTAINER
645
646 Shaun Ashby
647
648 =cut
649