ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/SCRAM/src/Cache/Cache.pm
Revision: 1.4
Committed: Tue Jun 28 19:08:55 2005 UTC (19 years, 10 months ago) by sashby
Content type: text/plain
Branch: MAIN
Changes since 1.3: +4 -3 lines
Log Message:
*** empty log message ***

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