ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/Cache/Cache.pm
Revision: 1.2
Committed: Fri Dec 10 13:41:39 2004 UTC (20 years, 4 months ago) by sashby
Content type: text/plain
Branch: MAIN
CVS Tags: V1_0_0
Changes since 1.1: +481 -0 lines
Log Message:
Merged V1_0 branch to HEAD

File Contents

# User Rev Content
1 sashby 1.2 #____________________________________________________________________
2     # File: Cache.pm
3     #____________________________________________________________________
4     #
5     # Author: Shaun Ashby <Shaun.Ashby@cern.ch>
6     # (with contribution from Lassi.Tuura@cern.ch)
7     # Update: 2003-11-27 16:45:18+0100
8     # Revision: $Id: Cache.pm,v 1.1.2.9 2004/11/16 18:47:37 sashby Exp $
9     #
10     # Copyright: 2003 (C) Shaun Ashby
11     #
12     #--------------------------------------------------------------------
13     package Cache::Cache;
14     require 5.004;
15    
16     use Exporter;
17     @ISA=qw(Exporter);
18     #
19     sub new()
20     ###############################################################
21     # new #
22     ###############################################################
23     # modified : Thu Nov 27 16:45:27 2003 / SFA #
24     # params : #
25     # : #
26     # function : #
27     # : #
28     ###############################################################
29     {
30     my $proto=shift;
31     my $class=ref($proto) || $proto;
32     my $self=
33     {
34     CACHENAME => "DirCache.db", # Name of global file/dir cache;
35     BFCACHE => {}, # BuildFile cache;
36     DIRCACHE => {}, # Source code cache;
37     STATUS => 0, # Status of cache: 1 => something changed. If so, force save;
38     VERBOSE => 0 # Verbose mode (0/1);
39     };
40    
41     bless $self,$class;
42     return $self;
43     }
44    
45     sub getdir()
46     {
47     my $self=shift;
48     my ($path) = @_;
49     opendir (DIR, $path) || die "$path: cannot read: $!\n";
50     # Skip .admin subdirectories too:
51     my @items = map { "$path/$_" } grep ($_ ne "." && $_ ne ".." && $_ ne ".admin", readdir(DIR));
52     closedir (DIR);
53     return @items;
54     }
55    
56     sub prune()
57     {
58     my $self=shift;
59     my ($path) = @_;
60     $self->cachestatus(1);
61     return if ! exists $self->{DIRCACHE}->{$path};
62     my (undef, undef, @subs) = @{$self->{DIRCACHE}->{$path}};
63     delete $self->{DIRCACHE}->{$path};
64     foreach my $sub (@subs)
65     {
66     $self->prune($sub);
67     }
68     }
69    
70     sub checktree()
71     {
72     my ($self, $path, $required, $dofiles) = @_;
73     # Check if this path needs to be checked. If it exists, has the same mode
74     # and the same time stamp, it's up to date and doesn't need to be checked.
75     # Otherwise if it is a directory whose time-stamp has changed, rescan it.
76     # If the path has be removed, prune it from the cache. Note that we skip
77     # non-directories unless $dofiles is set. Considering only directories is
78     # dramatically faster.
79     next if ($path =~ /\.admin/); # skip .admin dirs
80    
81     # NB: We stat each path only once ever. The special "_" file handle uses
82     # the results from the last stat we've made. See man perlfunc/stat.
83     if (! stat($path))
84     {
85     die "$path: $!\n" if $required;
86     $self->logmsg("SCRAM: $path: missing: removing from cache\n");
87     $self->prune($path);
88     # Something changed so force write of cache:
89     $self->cachestatus(1);
90     return;
91     }
92    
93     # If the entry in the cache is not the same mode or time, force an update.
94     # Otherwise use the cache as the list of items we need to change.
95     my $cached = $self->{DIRCACHE}->{$path};
96     my @items = ();
97    
98     if (! -d _)
99     {
100     if ($dofiles)
101     {
102     $self->logmsg("SCRAM: $path: updating cache\n");
103     $self->{DIRCACHE}->{$path} = [ (stat(_))[2, 9] ];
104     }
105     else
106     {
107     $self->logmsg("SCRAM: $path: not a directory: removing from parent's list\n");
108     my $parent = $path;
109     $parent =~ s|(.*)/[^/]+$|$1|;
110     if ($parent ne $path && exists $self->{DIRCACHE}->{$parent})
111     {
112     my ($mode, $time, @subs) = @{$self->{DIRCACHE}->{$parent}};
113     $self->{DIRCACHE}->{$parent} = [ $mode, $time, grep ($_ ne $path, @subs) ];
114     }
115     $self->cachestatus(1);
116     }
117     }
118     elsif (! $cached || $cached->[0] != (stat(_))[2])
119     {
120     # When a directory is added, this block is activated
121     $self->added_dirs($path); # Store the newly-added dir
122     $self->logmsg("SCRAM: $path: new or changed: pruning and recreating cache\n");
123     $self->prune($path);
124     $self->{DIRCACHE}->{$path} = [ (stat(_))[2, 9], @items = $self->getdir($path) ];
125     $required = 1;
126     $self->cachestatus(1);
127     }
128     elsif ($cached->[1] != (stat(_))[9])
129     {
130     # When a subdirectory is removed, this block is activated
131     #
132     # This is a parent directory. We store this as any
133     # update can be taken recursively from this dir:
134     $self->modified_parentdirs($path);
135    
136     $self->logmsg("SCRAM: $path: modified: updating cache\n");
137     # Current subdirs:
138     @items = $self->getdir($path);
139    
140     # Start checking from element number 2:
141     for (my $i = 2; $i <= $#$cached; $i++)
142     {
143     if (! grep($cached->[$i] eq $_, @items))
144     {
145     # Add the removed path to a store for later access
146     # from the project cache. This info is needed to update
147     # the cached data:
148     $self->schedremoval($cached->[$i]);
149     # Remove all child data:
150     $self->clean_cache_recursive($cached->[$i]);
151     }
152     }
153    
154     $self->{DIRCACHE}->{$path} = [ (stat(_))[2, 9], @items ];
155     $required = 1;
156     $self->cachestatus(1);
157     }
158     else
159     {
160     $self->logmsg("SCRAM: $path: valid: using cached directory list\n");
161     (undef, undef, @items) = @{$self->{DIRCACHE}->{$path}};
162     $required = 0;
163     }
164    
165     # Process sub-directories
166     foreach my $item (@items)
167     {
168     $self->checktree($item, $required, $dofiles);
169     }
170     }
171    
172     sub clean_cache_recursive()
173     {
174     my $self=shift;
175     my ($startdir) = @_;
176     my $children = $self->{DIRCACHE}->{$startdir};
177    
178     for (my $i = 2; $i <= $#$children; $i++)
179     {
180     # Remove all children:
181     $self->schedremoval($children->[$i]);
182     $self->clean_cache_recursive($children->[$i]);
183     }
184    
185     delete $self->{DIRCACHE}->{$startdir};
186     return $self;
187     }
188    
189     sub dirtree()
190     {
191     my $self=shift;
192     my ($dir,$dofiles) = @_;
193    
194     # Get the directory tree:
195     $self->checktree($dir, 1, $dofiles);
196     return $self;
197     }
198    
199     sub checkfiles()
200     {
201     my $self=shift;
202     # Scan config dir for top-level data, then start from src:
203     my @scandirs=($ENV{LOCALTOP}."/".$ENV{SCRAM_CONFIGDIR}, $ENV{SCRAM_SOURCEDIR});
204     my $dofiles=1;
205     # Loop over all directories that need scanning (normally just src and config):
206     foreach my $scand (@scandirs)
207     {
208     $self->logmsg("SCRAM: Scanning $scand [dofiles set to ".$dofiles."]\n");
209     # Check the directory tree:
210     $self->dirtree($scand, $dofiles);
211     $dofiles=0;
212     }
213    
214     # Mark everything in the cache old:
215     map { $_->[0] = 0 } values %{$self->{BFCACHE}};
216     map { $_->[0] = 0 } values %{$self->{CONFIGCACHE}};
217    
218     # Remember which directories have buildfiles in them:
219     my %files = map { -f $_ ? ($_ => [ (stat(_))[9] ]) : () }
220     map { "$_/BuildFile" }
221     keys %{$self->{DIRCACHE}};
222    
223     # Get list of files in config dir:
224     my $configcache = {};
225     my %configfiles = map { -f $_ &&
226     $_ =~ m|$ENV{LOCALTOP}/$ENV{SCRAM_CONFIGDIR}.*?$|
227     ? ($_ => [ (stat(_))[9] ]) : () } keys %{$self->{DIRCACHE}};
228    
229     # Also add ToolCache.db to the cache: FIXME: should probably use aglob here so
230     # that all SCRAM_ARCHs are taken into account.
231     $configfiles{$ENV{LOCALTOP}."/.SCRAM/".$ENV{SCRAM_ARCH}."/ToolCache.db"} =
232     [ (stat($ENV{LOCALTOP}."/.SCRAM/".$ENV{SCRAM_ARCH}."/ToolCache.db"))[9] ];
233    
234     # Compare or add to config file cache. We need this to be separate so we can tell if a
235     # file affecting our build has been changed:
236     while (my ($path, $vals) = each %configfiles)
237     {
238     if ($self->{CONFIGCACHE}->{$path} && $self->{CONFIGCACHE}->{$path}[1] == $vals->[0])
239     {
240     $configcache->{$path} = $self->{CONFIGCACHE}->{$path};
241     delete $self->{CONFIGCACHE}->{$path};
242     }
243     else
244     {
245     $self->{STATUSCONFIG}=1;
246     $self->logmsg("SCRAM: $path: changed\n");
247     $configcache->{$path} = [ 1, @$vals ];
248     delete $self->{CONFIGCACHE}->{$path};
249     }
250     }
251    
252     # Compare with existing cache: remove from cache what no longer
253     # exists, then check which build files are newer than the cache.
254     my $newcache = {};
255    
256     while (my ($path, $vals) = each %files)
257     {
258     if ($self->{BFCACHE}->{$path} && $self->{BFCACHE}->{$path}[1] == $vals->[0])
259     {
260     $newcache->{$path} = $self->{BFCACHE}->{$path};
261     delete $self->{BFCACHE}->{$path};
262     }
263     else
264     {
265     $self->{STATUSSRC}=1;
266     $self->logmsg("SCRAM: $path: changed\n");
267     $newcache->{$path} = [ 1, @$vals ];
268     delete $self->{BFCACHE}->{$path};
269     }
270     }
271    
272     # If there were BuildFiles that were removed, force update of cache
273     # and remove the BUILDFILEDATA entries:
274     foreach my $path (keys %{$self->{BFCACHE}})
275     {
276     $self->logmsg("SCRAM: $path: removed. Build data will be removed from build cache.\n");
277     $self->cachestatus(1);
278     # Store this so that later, we can tell the BuildDataStore to remove it:
279     $self->schedremoval($path);
280     }
281    
282     # Save the BuildFile cache:
283     delete $self->{BFCACHE};
284     $self->{BFCACHE} = $newcache;
285    
286     # Save the config cache:
287     delete $self->{CONFIGCACHE};
288     $self->{CONFIGCACHE} = $configcache;
289     return $self;
290     }
291    
292     sub dircache()
293     {
294     my $self=shift;
295     # Return the file cache:
296     return $self->{DIRCACHE};
297     }
298    
299     sub added_dirs()
300     {
301     my $self=shift;
302     my ($path) = @_;
303    
304     # If we have a path to add, add it.
305     if ($path)
306     {
307     if (exists($self->{ADDEDDIRS}))
308     {
309     push(@{$self->{ADDEDDIRS}}, $path);
310     }
311     else
312     {
313     $self->{ADDEDDIRS} = [ $path ];
314     }
315     }
316     else
317     {
318     # Otherwise, return the array of added dirs:
319     my @addeddirs = @{$self->{ADDEDDIRS}};
320     delete $self->{ADDEDDIRS};
321     return \@addeddirs;
322     }
323     }
324    
325     sub modified_parentdirs()
326     {
327     my $self=shift;
328     my ($path) = @_;
329    
330     # If we have a path to add, add it.
331     # Don't bother if it's the main source dir as we don't
332     # want to rescan everything from src (that would be silly):
333     if ($path && $path ne $ENV{SCRAM_SOURCEDIR})
334     {
335     if (exists($self->{MODPARENTDIRS}))
336     {
337     push(@{$self->{MODPARENTDIRS}}, $path);
338     }
339     else
340     {
341     $self->{MODPARENTDIRS} = [ $path ];
342     }
343     }
344     else
345     {
346     # Otherwise, return the array of added dirs:
347     my @moddeddirs = @{$self->{MODPARENTDIRS}};
348     delete $self->{MODPARENTDIRS};
349     return \@moddeddirs;
350     }
351     }
352    
353     sub schedremoval()
354     {
355     my $self=shift;
356     my ($d)=@_;
357    
358     if ($d)
359     {
360     if (exists($self->{REMOVEDATA}))
361     {
362     push(@{$self->{REMOVEDATA}},$d);
363     }
364     else
365     {
366     $self->{REMOVEDATA} = [ $d ];
367     }
368     }
369     else
370     {
371     my $remove = [ @{$self->{REMOVEDATA}} ];
372     $self->{REMOVEDATA} = [];
373     return $remove;
374     }
375     }
376    
377     sub filestatus()
378     {
379     my $self=shift;
380     # Here we want to return a true or false value depending on whether
381     # or not a buildfile was changed:
382     return $self->{STATUSSRC};
383     }
384    
385     sub configstatus()
386     {
387     my $self=shift;
388     # Here we want to return a true or false value depending on whether or not a file
389     # in config dir was changed:
390     return $self->{STATUSCONFIG};
391     }
392    
393     sub bf_for_scanning()
394     {
395     my $self=shift;
396     my $MODIFIED = [];
397    
398     $self->{STATUSSRC} = 0;
399    
400     # Return a list of buildfiles to be reread. Note that we only do this
401     # if the status was changed (i.e. don't have to read through the list of BFs to know
402     # whether something changed as the flags STATUSSRC is set as the src tree is checked).
403     # Also we check to see if STATUSCONFIG is true. If so all BuildFiles are marked to be read:
404     if ($self->{STATUSCONFIG})
405     {
406     $self->{STATUSCONFIG} = 0;
407     # Return all the buildfiles since they'll all to be read:
408     return [ keys %{$self->{BFCACHE}} ];
409     }
410     else
411     {
412     # Only return the files that changed:
413     map { ( $self->{BFCACHE}{$_}->[0] == 1 ) && push(@$MODIFIED, $_) } keys %{$self->{BFCACHE}};
414     # Reset the flag:
415     $self->{STATUSCONFIG} = 0;
416     }
417     return $MODIFIED;
418     }
419    
420     sub paths()
421     {
422     my $self=shift;
423     my $paths = {};
424    
425     $self->{ALLDIRS} = [];
426    
427     # Pass over each dir, skipping those that are not wanted and
428     # storing those that are relevant to an array:
429     foreach my $path (keys %{$self->{DIRCACHE}})
430     {
431     if ( ! -d $path && $path != m|$ENV{LOCALTOP}/$ENV{SCRAM_CONFIGDIR}.*?$|)
432     {
433     $self->logmsg("SCRAM: $path no longer exists. Clearing from cache.\n");
434     $self->cachestatus(1);
435     delete $self->{DIRCACHE}->{$path};
436     }
437     else
438     {
439     next if $path =~ m|/CVS$|; # Ignore CVS directories.
440     next if $path =~ m|/\.admin$|; # Ignore .admin directories.
441     next if $path =~ m|\Q$ENV{LOCALTOP}/$ENV{SCRAM_CONFIGDIR}\L|;
442     push(@{$self->{ALLDIRS}},$path);
443     }
444     }
445    
446     # Return the array:
447     return $self->{ALLDIRS};
448     }
449    
450     sub verbose()
451     {
452     my $self=shift;
453     # Turn on verbose mode:
454     @_ ? $self->{VERBOSE} = shift
455     : $self->{VERBOSE}
456     }
457    
458     sub cachestatus()
459     {
460     my $self=shift;
461     # Set/return the status of the cache:
462     @_ ? $self->{STATUS} = shift
463     : $self->{STATUS}
464     }
465    
466     sub logmsg()
467     {
468     my $self=shift;
469     # Print a message to STDOUT if VERBOSE is true:
470     print STDERR @_ if $self->verbose();
471     }
472    
473     sub name()
474     {
475     my $self=shift;
476     # Set/return the name of the cache to use:
477     @_ ? $self->{CACHENAME} = shift
478     : $self->{CACHENAME}
479     }
480    
481     1;