| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | ## | 
| 2 |  |  |  |  |  |  | # | 
| 3 |  |  |  |  |  |  | #    Copyright 2005-2006, Brian Szymanski | 
| 4 |  |  |  |  |  |  | # | 
| 5 |  |  |  |  |  |  | #    This file is part of Cache::Static | 
| 6 |  |  |  |  |  |  | # | 
| 7 |  |  |  |  |  |  | #    Cache::Static is free software; you can redistribute it and/or modify | 
| 8 |  |  |  |  |  |  | #    it under the terms of the GNU General Public License as published by | 
| 9 |  |  |  |  |  |  | #    the Free Software Foundation; either version 2 of the License, or | 
| 10 |  |  |  |  |  |  | #    any later version. | 
| 11 |  |  |  |  |  |  | # | 
| 12 |  |  |  |  |  |  | #    This program is distributed in the hope that it will be useful, | 
| 13 |  |  |  |  |  |  | #    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| 14 |  |  |  |  |  |  | #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| 15 |  |  |  |  |  |  | #    GNU General Public License for more details. | 
| 16 |  |  |  |  |  |  | # | 
| 17 |  |  |  |  |  |  | #    For more information about Cache::Static, point a web browser at | 
| 18 |  |  |  |  |  |  | #    http://chronicle.allafrica.com/scache/ or read the | 
| 19 |  |  |  |  |  |  | #    documentation included with the Cache::Static distribution in the | 
| 20 |  |  |  |  |  |  | #    doc/ directory | 
| 21 |  |  |  |  |  |  | # | 
| 22 |  |  |  |  |  |  | ## | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | package Cache::Static; | 
| 25 |  |  |  |  |  |  | our $VERSION = '0.9905'; | 
| 26 |  |  |  |  |  |  |  | 
| 27 | 6 |  |  | 6 |  | 20650 | use strict; | 
|  | 6 |  |  |  |  | 14 |  | 
|  | 6 |  |  |  |  | 201 |  | 
| 28 | 6 |  |  | 6 |  | 32 | use warnings; | 
|  | 6 |  |  |  |  | 9 |  | 
|  | 6 |  |  |  |  | 198 |  | 
| 29 |  |  |  |  |  |  |  | 
| 30 | 6 |  |  | 6 |  | 372431 | use Storable; | 
|  | 6 |  |  |  |  | 35502 |  | 
|  | 6 |  |  |  |  | 610 |  | 
| 31 | 6 |  |  | 6 |  | 63 | use Digest::MD5 qw(md5_base64); | 
|  | 6 |  |  |  |  | 39 |  | 
|  | 6 |  |  |  |  | 4096 |  | 
| 32 |  |  |  |  |  |  | #allow serialization of code refs | 
| 33 |  |  |  |  |  |  | $Storable::Deparse = 1; | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | our $ROOT = '/usr/local/Cache-Static'; | 
| 36 |  |  |  |  |  |  | our $LOGFILE = "$ROOT/log"; | 
| 37 |  |  |  |  |  |  | our $namespace = 'DEFAULT'; | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | ### LOG LEVELS: | 
| 40 |  |  |  |  |  |  | #0 - no output | 
| 41 |  |  |  |  |  |  | #1 - just hit/miss stats | 
| 42 |  |  |  |  |  |  | #2 - hit/miss stats and critical errors (production) | 
| 43 |  |  |  |  |  |  | #3 - his or miss and most error messages (development) | 
| 44 |  |  |  |  |  |  | #4 - hit or miss and verbose error messages (debugging) | 
| 45 |  |  |  |  |  |  | my @LOG_LEVEL_NAMES = qw ( NONE STAT CRIT WARN DEBUG ); | 
| 46 |  |  |  |  |  |  | ### /LOG LEVELS | 
| 47 |  |  |  |  |  |  | my @ILLEGAL_NAMESPACES = qw ( config log timestamps log_level ); | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | #used to use a different root directory (used in TEST.pm) | 
| 50 |  |  |  |  |  |  | sub _rebase { | 
| 51 | 6 |  |  | 6 |  | 1985 | my $base = shift; | 
| 52 | 6 |  |  |  |  | 18 | $ROOT = $base; | 
| 53 | 6 |  |  |  |  | 23 | $LOGFILE = "$ROOT/log"; | 
| 54 | 6 |  |  |  |  | 31 | _mkdir_p("$ROOT/DEFAULT/tmp"); | 
| 55 | 6 | 50 |  |  |  | 86 | die "couldn't create DEFAULT namespace tmp directory: $@" if($@); | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | #fill %conf with some sane defaults | 
| 59 |  |  |  |  |  |  | my %CONF = ( | 
| 60 |  |  |  |  |  |  | DEFAULT => { | 
| 61 |  |  |  |  |  |  | dep_file_not_found_returns => 0, | 
| 62 |  |  |  |  |  |  | unrecognized_dependency_returns => 0, | 
| 63 |  |  |  |  |  |  | recursive_unlink => 0, | 
| 64 |  |  |  |  |  |  | }, | 
| 65 |  |  |  |  |  |  | log_level => 3 | 
| 66 |  |  |  |  |  |  | ); | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | #create the tmp directory for the default namespace | 
| 69 |  |  |  |  |  |  | _mkdir_p("$ROOT/DEFAULT/tmp"); | 
| 70 |  |  |  |  |  |  | die "couldn't create DEFAULT namespace tmp directory: $@" if($@); | 
| 71 |  |  |  |  |  |  | #create the timestamp directory if it doesn't exist | 
| 72 |  |  |  |  |  |  | _mkdir_p("$ROOT/timestamps"); | 
| 73 |  |  |  |  |  |  | die "couldn't create timestamp directory: $@" if($@); | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | #read the global config | 
| 76 |  |  |  |  |  |  | _readconf(); | 
| 77 |  |  |  |  |  |  | _log(3, "conf -- global config --"); | 
| 78 |  |  |  |  |  |  | _print_config(); | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | sub _print_config { | 
| 81 | 6 |  |  | 6 |  | 175 | foreach my $c (keys %CONF) { | 
| 82 | 12 | 100 |  |  |  | 253 | if(ref($CONF{$c})) { | 
| 83 | 6 |  |  |  |  | 75 | foreach my $cc (keys %{$CONF{$c}}) { | 
|  | 6 |  |  |  |  | 130 |  | 
| 84 | 18 |  |  |  |  | 616 | _log(3, "conf($c): $cc = ".$CONF{$c}->{$cc}); | 
| 85 |  |  |  |  |  |  | } | 
| 86 |  |  |  |  |  |  | } else { | 
| 87 | 6 |  |  |  |  | 171 | _log(3, "conf: $c = ".$CONF{$c}); | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | #make sure the DEFAULT namespace's directories are there - we don't | 
| 93 |  |  |  |  |  |  | #call init for these... | 
| 94 |  |  |  |  |  |  | _mkdir_p("$ROOT/DEFAULT/tmp"); | 
| 95 |  |  |  |  |  |  | die "couldn't create DEFAULT namespace tmp directory: $@" if($@); | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | #just set the default namespace | 
| 98 |  |  |  |  |  |  | sub init { | 
| 99 | 6 |  |  | 6 | 0 | 48 | _die_if_invalid_namespace($_[0]); | 
| 100 | 6 |  |  |  |  | 27 | $namespace = shift; | 
| 101 |  |  |  |  |  |  | # | 
| 102 |  |  |  |  |  |  | #	_mkdir_p("$ROOT/$namespace/tmp"); | 
| 103 |  |  |  |  |  |  | #	die "couldn't make/walk tmp directory: $ROOT/$namespace/tmp: $@" if($@); | 
| 104 |  |  |  |  |  |  | # | 
| 105 |  |  |  |  |  |  | #	#override conf with namespace-specific values | 
| 106 |  |  |  |  |  |  | #	_readconf("$namespace") unless(defined($CONF{$namespace})); | 
| 107 |  |  |  |  |  |  | # | 
| 108 |  |  |  |  |  |  | #	_log(3, "conf --init--"); | 
| 109 |  |  |  |  |  |  | #	_print_config(); | 
| 110 |  |  |  |  |  |  | } | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | #determine whether we have fcntl and can use locking for native perl | 
| 113 |  |  |  |  |  |  | #log writes (if not we fall back to invoking echo, which is slower and | 
| 114 |  |  |  |  |  |  | #more error prone) | 
| 115 |  |  |  |  |  |  | my $have_fcntl; | 
| 116 |  |  |  |  |  |  | eval { | 
| 117 | 6 |  |  | 6 |  | 46 | use Fcntl ':flock'; | 
|  | 6 |  |  |  |  | 12 |  | 
|  | 6 |  |  |  |  | 1573 |  | 
| 118 |  |  |  |  |  |  | $have_fcntl = 1; | 
| 119 |  |  |  |  |  |  | }; if($@) { | 
| 120 |  |  |  |  |  |  | $have_fcntl = 0; | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | ########################### | 
| 124 |  |  |  |  |  |  | ### glue for extensions ### | 
| 125 |  |  |  |  |  |  | ########################### | 
| 126 | 6 |  |  | 6 |  | 4347 | use Cache::Static::Configuration; | 
|  | 6 |  |  |  |  | 24 |  | 
|  | 6 |  |  |  |  | 325 |  | 
| 127 |  |  |  |  |  |  | sub get_configuration_data { | 
| 128 | 6 |  |  | 6 |  | 37 | no strict 'refs'; | 
|  | 6 |  |  |  |  | 16 |  | 
|  | 6 |  |  |  |  | 48145 |  | 
| 129 | 6 |  |  | 6 | 0 | 26 | my $fh = *{ "Cache::Static::Configuration::DATA" }; | 
|  | 6 |  |  |  |  | 144 |  | 
| 130 | 6 |  |  |  |  | 187 | my $block = join ( '', <$fh> ); | 
| 131 | 6 |  |  |  |  | 1657 | my $conf = eval "{ $block }"; | 
| 132 | 6 |  |  |  |  | 60 | return $conf->{$_[0]}; | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | sub find_intersection { | 
| 136 | 12 |  |  | 12 | 0 | 31 | my ($ref1, $ref2) = @_; | 
| 137 | 12 |  |  |  |  | 21 | my (%h, @ret); | 
| 138 | 12 |  |  |  |  | 42 | foreach my $i (@$ref1, @$ref2) { $h{$i}++; }; | 
|  | 42 |  |  |  |  | 361 |  | 
| 139 | 12 |  |  |  |  | 43 | foreach my $e (keys %h) { | 
| 140 | 30 | 100 |  |  |  | 102 | push @ret, $e if($h{$e} == 2); | 
| 141 |  |  |  |  |  |  | } | 
| 142 | 12 |  |  |  |  | 61 | return @ret; | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | my @enabled_extensions = @{get_configuration_data("extensions")}; | 
| 146 |  |  |  |  |  |  | sub is_enabled { | 
| 147 | 2 |  |  | 2 | 0 | 15 | my $module = shift; | 
| 148 | 2 |  |  |  |  | 50 | return grep(/^$module$/i, @enabled_extensions); | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | my @POSSIBLE_HELPER_EXTENSIONS = find_intersection(\@enabled_extensions, | 
| 152 |  |  |  |  |  |  | [ qw ( HTML::Mason ) ] ); | 
| 153 |  |  |  |  |  |  | my @POSSIBLE_TIMESTAMP_EXTENSIONS = find_intersection(\@enabled_extensions, | 
| 154 |  |  |  |  |  |  | [ qw ( XML::Comma DBI ) ] ); | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | my @helper_extensions; | 
| 157 |  |  |  |  |  |  | foreach my $ext (@POSSIBLE_HELPER_EXTENSIONS) { | 
| 158 |  |  |  |  |  |  | eval "require $ext;"; | 
| 159 |  |  |  |  |  |  | next if($@); | 
| 160 |  |  |  |  |  |  | my $util = $ext; | 
| 161 |  |  |  |  |  |  | $util =~ s/\:\:/_/g; | 
| 162 |  |  |  |  |  |  | eval "require Cache::Static::${util}_Util"; | 
| 163 |  |  |  |  |  |  | if($@) { | 
| 164 |  |  |  |  |  |  | _log(2, "$ext exists but Cache::Static::${util}_Util does not\n"); | 
| 165 |  |  |  |  |  |  | } else { | 
| 166 |  |  |  |  |  |  | push @helper_extensions, $ext; | 
| 167 |  |  |  |  |  |  | } | 
| 168 |  |  |  |  |  |  | } | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | my @timestamp_extensions; | 
| 171 |  |  |  |  |  |  | foreach my $ext (@POSSIBLE_TIMESTAMP_EXTENSIONS) { | 
| 172 |  |  |  |  |  |  | eval "require $ext;"; | 
| 173 |  |  |  |  |  |  | next if($@); | 
| 174 |  |  |  |  |  |  | my $util = $ext; | 
| 175 |  |  |  |  |  |  | $util =~ s/\:\:/_/g; | 
| 176 |  |  |  |  |  |  | eval "require Cache::Static::${util}_Util"; | 
| 177 |  |  |  |  |  |  | if($@) { | 
| 178 |  |  |  |  |  |  | _log(2, "$ext exists but Cache::Static::${util}_Util does not, disabling extension\n"); | 
| 179 |  |  |  |  |  |  | } else { | 
| 180 |  |  |  |  |  |  | push @timestamp_extensions, $ext; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  | } | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | sub _readconf { | 
| 185 | 10 |  |  | 10 |  | 22 | my $ns = shift; | 
| 186 | 10 | 100 |  |  |  | 36 | $ns = '' unless(defined($ns)); | 
| 187 | 10 | 100 |  |  |  | 36 | _die_if_invalid_namespace($ns) if($ns); | 
| 188 |  |  |  |  |  |  |  | 
| 189 | 10 |  |  |  |  | 34 | my $dir = "$ROOT/$ns"; | 
| 190 | 10 |  |  |  |  | 15 | my @conf; | 
| 191 | 2 |  |  |  |  | 3 | open(CONF, "$dir/config") && | 
| 192 | 10 | 100 |  |  |  | 444 | (@conf = map { my $t = $_; $t = lc($t); $t =~ s/^\s+//; $t =~ s/\s+$//; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 5 |  | 
| 193 | 2 |  |  |  |  | 3 | my $ar = []; @$ar = split(/\s+/, $t, 2); $ar } | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 7 |  | 
| 194 |  |  |  |  |  |  | grep(/^[^#]/, grep(/./, ))); | 
| 195 | 10 |  |  |  |  | 42 | close(CONF); | 
| 196 | 10 |  |  |  |  | 36 | foreach my $cr (@conf) { | 
| 197 | 2 | 50 |  |  |  | 5 | if($cr->[0] eq 'log_level') { | 
| 198 | 0 | 0 | 0 |  |  | 0 | if(!$ns || $ns eq 'DEFAULT') { | 
| 199 | 0 |  |  |  |  | 0 | $CONF{log_level} = $cr->[1]; | 
| 200 |  |  |  |  |  |  | } else { | 
| 201 | 0 |  |  |  |  | 0 | _log(3, "log_level directive in CONF($ns) ignored"); | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  | } else { | 
| 204 | 2 | 50 |  |  |  | 12 | $CONF{$ns ? $ns : 'DEFAULT'}->{$cr->[0]} = $cr->[1]; | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | #### useful when adding new modules | 
| 210 |  |  |  |  |  |  | #warn "time: @timestamp_extensions\n"; | 
| 211 |  |  |  |  |  |  | #warn "help: @helper_extensions\n"; | 
| 212 |  |  |  |  |  |  | #die; | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | sub _has_timestamp { | 
| 215 | 0 |  |  | 0 |  | 0 | my $mod = shift; | 
| 216 | 0 |  |  |  |  | 0 | return grep(/^$mod$/, @timestamp_extensions); | 
| 217 |  |  |  |  |  |  | } | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | sub _has_helper { | 
| 220 | 0 |  |  | 0 |  | 0 | my $mod = shift; | 
| 221 | 0 |  |  |  |  | 0 | return grep(/^$mod$/, @helper_extensions); | 
| 222 |  |  |  |  |  |  | } | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | ############################ | 
| 225 |  |  |  |  |  |  | ### /glue for extensions ### | 
| 226 |  |  |  |  |  |  | ############################ | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | #try to set up the logfile with lenient permissions | 
| 229 |  |  |  |  |  |  | eval { | 
| 230 |  |  |  |  |  |  | open(FH, ">>$LOGFILE"); | 
| 231 |  |  |  |  |  |  | close(FH); | 
| 232 |  |  |  |  |  |  | chmod 0666, $LOGFILE; | 
| 233 |  |  |  |  |  |  | }; | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | #number of levels of directory in cache | 
| 236 |  |  |  |  |  |  | #TODO: move this to config file | 
| 237 |  |  |  |  |  |  | my $CACHE_LEVELS = 3; | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | sub get_if_same { | 
| 240 |  |  |  |  |  |  | ### uncomment the below line to disable Cache::Static | 
| 241 |  |  |  |  |  |  | #	return undef; | 
| 242 | 18 |  |  | 18 | 0 | 917 | my ($key, $depsref, %args) = @_; | 
| 243 | 18 |  |  |  |  | 112 | my ($ret, $dep) = _is_same($key, $depsref, %args); | 
| 244 | 18 | 100 |  |  |  | 51 | if($ret) { | 
| 245 | 8 |  |  |  |  | 31 | _log(1, "cache hit for key: $key"); | 
| 246 | 8 |  |  |  |  | 37 | return _get($key, %args); | 
| 247 |  |  |  |  |  |  | } else { | 
| 248 | 10 |  |  |  |  | 49 | _log(1, "cache miss for key: $key on dep: $dep"); | 
| 249 | 10 |  |  |  |  | 81 | return undef; | 
| 250 |  |  |  |  |  |  | } | 
| 251 |  |  |  |  |  |  | } | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | sub _die_if_invalid_namespace { | 
| 254 | 45 |  |  | 45 |  | 82 | my $ns = shift; | 
| 255 | 45 | 50 | 33 |  |  | 957 | die "illegal namespace: $namespace" if($namespace =~ /\// || | 
| 256 |  |  |  |  |  |  | grep (/^$namespace$/, @ILLEGAL_NAMESPACES)); | 
| 257 |  |  |  |  |  |  | } | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | sub set { | 
| 260 | 9 |  |  | 9 | 0 | 51 | my ($key, $content, $deps, %args) = @_; | 
| 261 | 9 |  | 66 |  |  | 54 | my $ns = $args{namespace} || $namespace; | 
| 262 | 9 |  |  |  |  | 666 | _die_if_invalid_namespace($ns); | 
| 263 | 9 |  |  |  |  | 18 | eval { | 
| 264 |  |  |  |  |  |  | #create any necessary directories | 
| 265 | 9 |  |  |  |  | 21 | my $dir = $key; | 
| 266 | 9 |  |  |  |  | 681 | $dir =~ s/\/[^\/]*$//; | 
| 267 | 9 |  |  |  |  | 57 | _mkdir_p("$ROOT/$ns/cache/$dir"); | 
| 268 | 9 | 50 |  |  |  | 35 | die "couldn't make/walk directories: $@" if($@); | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | #if we overrode the namespace, or if the dir got rm -rf'd out | 
| 271 |  |  |  |  |  |  | #from under us, this comes in handy... | 
| 272 | 9 |  |  |  |  | 83 | _mkdir_p("$ROOT/$ns/tmp"); | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | #write out the content | 
| 275 | 9 |  |  |  |  | 22 | my $tmpf = $key; | 
| 276 | 9 |  |  |  |  | 43 | $tmpf =~ s/\///g; | 
| 277 | 9 | 50 |  |  |  | 1001 | open(FH, ">$ROOT/$ns/tmp/$tmpf") || die "couldn't open $ROOT/$ns/tmp/$tmpf: $!"; | 
| 278 | 9 | 50 |  |  |  | 85 | (print FH $content) || die "couldn't print: $!"; | 
| 279 | 9 | 50 |  |  |  | 600 | close(FH) || die "couldn't close: $!"; | 
| 280 | 9 |  |  |  |  | 408 | chmod 0666, "$ROOT/$ns/tmp/$tmpf"; | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | #move the new cache file in place | 
| 283 | 9 | 50 |  |  |  | 1206 | (rename "$ROOT/$ns/tmp/$tmpf", "$ROOT/$ns/cache/$key") || | 
| 284 |  |  |  |  |  |  | die "couldn't rename content to $ROOT/$ns/cache/$key"; | 
| 285 |  |  |  |  |  |  |  | 
| 286 | 9 | 50 |  |  |  | 32 | if($deps) { | 
| 287 |  |  |  |  |  |  | #write out the deps | 
| 288 | 9 |  |  |  |  | 24 | my $frozen_deps = join('', map { $a=$_; $a.="\n"; $a } @$deps); | 
|  | 9 |  |  |  |  | 20 |  | 
|  | 9 |  |  |  |  | 16 |  | 
|  | 9 |  |  |  |  | 36 |  | 
| 289 | 9 | 50 |  |  |  | 730 | open(FH, ">$ROOT/$ns/tmp/$tmpf.dep") || die "couldn't open: $!"; | 
| 290 | 9 | 50 |  |  |  | 59 | (print FH $frozen_deps) || die "couldn't print: $!"; | 
| 291 | 9 | 50 |  |  |  | 378 | close(FH) || die "couldn't close: $!"; | 
| 292 | 9 |  |  |  |  | 310 | chmod 0666, "$ROOT/$ns/tmp/$tmpf.dep"; | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | #move the new .dep file in place | 
| 295 | 9 | 50 |  |  |  | 1092 | (rename "$ROOT/$ns/tmp/$tmpf.dep", "$ROOT/$ns/cache/$key.dep") || | 
| 296 |  |  |  |  |  |  | die "couldn't rename deps to $ROOT/$ns/cache/$key.dep: $!"; | 
| 297 |  |  |  |  |  |  | } | 
| 298 |  |  |  |  |  |  |  | 
| 299 | 9 | 50 |  |  |  | 29 | }; if($@) { | 
| 300 | 0 |  |  |  |  | 0 | _log(2, "Cache::Static::set couldn't save new value (in namespace: $ns) : $@"); | 
| 301 |  |  |  |  |  |  | } else { | 
| 302 | 9 |  |  |  |  | 56 | _log(3, "Cache::Static::set refreshed $key in namespace: $ns"); | 
| 303 |  |  |  |  |  |  | } | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | sub make_friendly_key { | 
| 307 | 3 |  |  | 3 | 0 | 9 | my ($url, $argsref) = @_; | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | #key for Cache is url + args in deterministic order | 
| 310 | 3 |  |  |  |  | 11 | my $key = "$url?"; | 
| 311 | 3 |  |  |  |  | 21 | foreach my $arg (sort keys %$argsref) { | 
| 312 | 0 |  |  |  |  | 0 | my $val = $argsref->{$arg}; | 
| 313 | 0 | 0 |  |  |  | 0 | if(ref($val)) { | 
| 314 | 0 | 0 | 0 |  |  | 0 | if(ref($val) eq 'ARRAY') { | 
|  |  | 0 |  |  |  |  |  | 
| 315 | 0 |  |  |  |  | 0 | $val = join("&$arg=", @$val); | 
| 316 |  |  |  |  |  |  | } elsif($val->isa('XML::Comma::Doc') | 
| 317 |  |  |  |  |  |  | && _has_timestamp('XML::Comma')) { | 
| 318 | 0 |  |  |  |  | 0 | $val = "XML::Comma::Doc:".$val->doc_key; | 
| 319 |  |  |  |  |  |  | } else { | 
| 320 | 0 |  |  |  |  | 0 | _log(3, "got a ".ref($val)." and we're just freezing it..."); | 
| 321 | 0 |  |  |  |  | 0 | $val = Storable::freeze($val); | 
| 322 |  |  |  |  |  |  | } | 
| 323 |  |  |  |  |  |  | } | 
| 324 | 0 |  |  |  |  | 0 | $key .= "$arg=$val&"; | 
| 325 |  |  |  |  |  |  | } | 
| 326 | 3 |  |  |  |  | 12 | $key =~ s/&$//; | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | #fix problem with friendly keys that have a multiple consecutive dashes, | 
| 329 |  |  |  |  |  |  | #as when they are printed in HTML debugging mode, they can cause SGML | 
| 330 |  |  |  |  |  |  | #comments to eat what is supposed to be code up to the next literal -- | 
| 331 |  |  |  |  |  |  | #for one-to-one-ness, also map '-' (single dash) to '-1-' | 
| 332 |  |  |  |  |  |  | #this is really something browsers should work around, but don't. see: | 
| 333 |  |  |  |  |  |  | #  https://bugzilla.mozilla.org/show_bug.cgi?id=214476 | 
| 334 | 3 | 50 |  |  |  | 15 | $key = join("", map { (/-+/) ? "-".length($_)."-" : $_ } | 
|  | 3 |  |  |  |  | 25 |  | 
| 335 |  |  |  |  |  |  | split(/(-+)/, $key)); | 
| 336 |  |  |  |  |  |  |  | 
| 337 | 3 |  |  |  |  | 20 | return $key; | 
| 338 |  |  |  |  |  |  | } | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | sub make_key { | 
| 341 | 3 |  |  | 3 | 0 | 1438 | return md5_path(make_friendly_key(@_)); | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | sub make_key_from_friendly { | 
| 345 | 0 |  |  | 0 | 0 | 0 | my $key = shift; | 
| 346 | 0 |  |  |  |  | 0 | return md5_path($key); | 
| 347 |  |  |  |  |  |  | } | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | sub md5_path { | 
| 350 | 3 |  |  | 3 | 0 | 7 | my $key = shift; | 
| 351 |  |  |  |  |  |  |  | 
| 352 | 3 |  |  |  |  | 26 | $key = md5_base64($key); | 
| 353 |  |  |  |  |  |  | # base64 is all alphanumeric except + and / | 
| 354 |  |  |  |  |  |  | # / must be translated | 
| 355 |  |  |  |  |  |  | #	# + is translated for cosmetic reasons | 
| 356 | 3 |  |  |  |  | 16 | $key =~ s/\//_/g; | 
| 357 |  |  |  |  |  |  | #	$key =~ s/\+/-/g; | 
| 358 |  |  |  |  |  |  |  | 
| 359 | 3 |  |  |  |  | 53 | $key = join('/', grep(/./, split(/(.)/, $key, $CACHE_LEVELS+1))); | 
| 360 |  |  |  |  |  |  |  | 
| 361 | 3 |  |  |  |  | 16 | return $key; | 
| 362 |  |  |  |  |  |  | } | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | sub get_seconds_from_timespec { | 
| 365 | 10 |  |  | 10 | 0 | 17 | my $arg = shift; | 
| 366 | 10 |  |  |  |  | 179 | my @args = split(/([a-zA-Z])/, $arg); | 
| 367 | 10 | 50 |  |  |  | 41 | push @args, 's' if(($#args%2) == 0); | 
| 368 | 10 |  |  |  |  | 19 | my ($i, $period) = (0, 0); | 
| 369 | 10 |  |  |  |  | 35 | while($i < $#args) { | 
| 370 | 28 |  |  |  |  | 44 | my $n = $args[$i]; | 
| 371 | 28 |  |  |  |  | 40 | my $c = $args[$i+1]; | 
| 372 | 28 |  |  |  |  | 34 | my $mult; | 
| 373 | 28 | 100 |  |  |  | 186 | if(lc($c) eq 'w') { $mult = 7 * 24 * 60 * 60; } | 
|  | 3 | 100 |  |  |  | 7 |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 374 | 5 |  |  |  |  | 10 | elsif(lc($c) eq 'd') { $mult = 24 * 60 * 60; } | 
| 375 | 5 |  |  |  |  | 9 | elsif(lc($c) eq 'h') { $mult = 60 * 60; } | 
| 376 | 5 |  |  |  |  | 9 | elsif(lc($c) eq 'm') { $mult = 60; } | 
| 377 | 10 |  |  |  |  | 18 | elsif(lc($c) eq 's') { $mult = 1; } | 
| 378 |  |  |  |  |  |  | else { | 
| 379 | 0 |  |  |  |  | 0 | _log(2, "Cache::Static::get_seconds_from_timespec: unknown multiplier in $arg: $c"); | 
| 380 | 0 |  |  |  |  | 0 | return undef; | 
| 381 |  |  |  |  |  |  | } | 
| 382 | 28 |  |  |  |  | 50 | $period += $n * $mult; | 
| 383 | 28 |  |  |  |  | 69 | $i += 2; | 
| 384 |  |  |  |  |  |  | } | 
| 385 | 10 |  |  |  |  | 36 | return $period; | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | sub _find_bound_before_time { | 
| 389 | 6 |  |  | 6 |  | 14 | my ($time, $offset, $bound) = @_; | 
| 390 |  |  |  |  |  |  | #valid bounds: [HMDW] | 
| 391 | 6 |  |  |  |  | 191 | my @lt = localtime($time); | 
| 392 |  |  |  |  |  |  |  | 
| 393 | 6 |  |  |  |  | 11 | my ($roffset, $interval); | 
| 394 |  |  |  |  |  |  | #this would be much nicer with switch/case, grumble. | 
| 395 | 6 | 100 |  |  |  | 29 | if($bound eq 'M') { | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 396 | 4 |  |  |  |  | 9 | $roffset = $lt[0]; | 
| 397 | 4 |  |  |  |  | 5 | $interval = 60; | 
| 398 |  |  |  |  |  |  | } elsif($bound eq 'H') { | 
| 399 | 0 |  |  |  |  | 0 | $roffset = $lt[0] + $lt[1] * 60; | 
| 400 | 0 |  |  |  |  | 0 | $interval = 60 * 60; | 
| 401 |  |  |  |  |  |  | } elsif($bound eq 'D') { | 
| 402 | 0 |  |  |  |  | 0 | $roffset = $lt[0] + $lt[1] * 60 + $lt[2] * 60 * 60; | 
| 403 | 0 |  |  |  |  | 0 | $interval = 24 * 60 * 60; | 
| 404 |  |  |  |  |  |  | } elsif($bound eq 'W') { | 
| 405 | 2 |  |  |  |  | 8 | $roffset = $lt[0] + $lt[1] * 60 + $lt[2] * 60 * 60 + | 
| 406 |  |  |  |  |  |  | $lt[6] * 24 * 60 * 60; | 
| 407 | 2 |  |  |  |  | 3 | $interval = 7 * 24 * 60 * 60; | 
| 408 |  |  |  |  |  |  | } else { | 
| 409 | 0 |  |  |  |  | 0 | _log(2, "Cache::Static::_find_bound_before_time: unknown time boundary: $bound"); | 
| 410 | 0 |  |  |  |  | 0 | return undef; | 
| 411 |  |  |  |  |  |  | } | 
| 412 | 6 | 50 |  |  |  | 20 | if($offset > $interval) { | 
| 413 | 0 |  |  |  |  | 0 | _log(2, "Cache::Static::_find_bound_before_time: offset ($offset) > interval ($interval)"); | 
| 414 | 0 |  |  |  |  | 0 | return undef; | 
| 415 |  |  |  |  |  |  | } | 
| 416 | 6 | 100 |  |  |  | 27 | return $offset + $time - $roffset - ($roffset > $offset ? 0 : $interval); | 
| 417 |  |  |  |  |  |  | } | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  | sub _is_same { | 
| 420 | 18 |  |  | 18 |  | 110 | my ($key, $depsref, %args) = @_; | 
| 421 | 18 |  | 66 |  |  | 107 | my $ns = $args{namespace} || $namespace; | 
| 422 | 18 |  |  |  |  | 862 | _die_if_invalid_namespace($ns); | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | #if no deps argument, find what we've got saved on disk for deps | 
| 425 | 18 | 50 |  |  |  | 60 | unless($depsref) { | 
| 426 | 0 |  |  |  |  | 0 | open(F, "$ROOT/$ns/cache/$key.dep"); | 
| 427 | 0 |  |  |  |  | 0 | my $deps_str = ; | 
| 428 | 0 |  |  |  |  | 0 | close(F); | 
| 429 | 0 |  |  |  |  | 0 | my @deps = split(/\0/, $deps_str); | 
| 430 | 0 |  |  |  |  | 0 | $depsref = \@deps; | 
| 431 | 0 |  |  |  |  | 0 | _log(4, "Cache::Static::_is_same: got ".($#deps+1)." deps for $key"); | 
| 432 |  |  |  |  |  |  | } | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | #get last modified time of the cached version, or 0 if it doesn't exist | 
| 435 | 18 |  |  |  |  | 797 | my @t = stat("$ROOT/$ns/cache/$key"); | 
| 436 | 18 | 50 |  |  |  | 66 | my $request_modtime = @t ? $t[9] : 0; | 
| 437 | 18 | 50 |  |  |  | 46 | return (0, "(not yet cached)") unless($request_modtime); | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | # give a chance to add any module specific extra deps | 
| 440 | 18 |  |  |  |  | 27 | my %extra_deps; | 
| 441 |  |  |  |  |  |  | ### TODO: this is too slow, at least for XML::Comma (0.02 sec on p4@3GHz) | 
| 442 |  |  |  |  |  |  | #	foreach my $dep (@$depsref) { | 
| 443 |  |  |  |  |  |  | #		my ($type, $spec) = split(/\|/, $dep, 2); | 
| 444 |  |  |  |  |  |  | #		my $dep_modtime; | 
| 445 |  |  |  |  |  |  | #		if($type =~ /^_/) { | 
| 446 |  |  |  |  |  |  | #			#not a builtin - call an extension | 
| 447 |  |  |  |  |  |  | #			my ($module, $type, $spec) = split(/\|/, $dep, 3); | 
| 448 |  |  |  |  |  |  | #			$module =~ s/^_//; | 
| 449 |  |  |  |  |  |  | #			$module =~ s/\:\:/_/g; | 
| 450 |  |  |  |  |  |  | #			my @deps = eval | 
| 451 |  |  |  |  |  |  | #				"Cache::Static::${module}_Util::get_extra_deps(\"$type\", \"$spec\")"; | 
| 452 |  |  |  |  |  |  | #			foreach my $d (@deps) { | 
| 453 |  |  |  |  |  |  | #				$extra_deps{$d} = 1 unless($extra_deps{$d}); | 
| 454 |  |  |  |  |  |  | #			} | 
| 455 |  |  |  |  |  |  | #		} | 
| 456 |  |  |  |  |  |  | #	} | 
| 457 | 18 |  |  |  |  | 45 | my @deps = (@$depsref, keys %extra_deps); | 
| 458 |  |  |  |  |  |  |  | 
| 459 | 18 |  |  |  |  | 44 | my @TRUE = ($key,1); | 
| 460 | 18 |  |  |  |  | 54 | foreach my $dep (@deps) { | 
| 461 | 18 |  |  |  |  | 35 | my @FALSE = (0,$dep); | 
| 462 | 18 |  |  |  |  | 77 | my ($full_type, $spec) = split(/\|/, $dep, 2); | 
| 463 | 18 |  |  |  |  | 91 | _log(4, "full_type: $full_type, spec: $spec"); | 
| 464 | 18 |  |  |  |  | 51 | my ($type, $modifier) = split(/-/, $full_type, 2); | 
| 465 | 18 | 50 |  |  |  | 78 | if(defined($modifier)) { | 
| 466 | 0 |  |  |  |  | 0 | _log(4, "modifier found: full_type: $full_type, type: $type, modifier: $modifier"); | 
| 467 |  |  |  |  |  |  | } | 
| 468 | 18 |  |  |  |  | 26 | my $dep_modtime; | 
| 469 | 18 | 50 |  |  |  | 113 | if($type =~ /^_/) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | #not a builtin - call an extension | 
| 471 | 0 |  |  |  |  | 0 | my ($module, $type, $spec) = split(/\|/, $dep, 3); | 
| 472 | 0 |  |  |  |  | 0 | $module =~ s/^_//; | 
| 473 | 0 |  |  |  |  | 0 | $module =~ s/\:\:/_/g; | 
| 474 |  |  |  |  |  |  |  | 
| 475 | 0 |  |  |  |  | 0 | _log(4, "here we are, extension, module: $module, type: $type spec: $spec"); | 
| 476 |  |  |  |  |  |  |  | 
| 477 | 0 |  |  |  |  | 0 | $dep_modtime = eval "Cache::Static::${module}_Util::modtime(\"$type\", \"$spec\")"; | 
| 478 | 0 | 0 |  |  |  | 0 | if($@) { | 
|  |  | 0 |  |  |  |  |  | 
| 479 | 0 |  |  |  |  | 0 | _log(3, "error calling Cache::Static::${module}_Util::modtime(\"$type\", \"$spec\"): $@"); | 
| 480 |  |  |  |  |  |  | } elsif(!$dep_modtime) { | 
| 481 | 0 |  |  |  |  | 0 | _log(4, "got non-true value from Cache::Static::${module}_Util::modtime(\"$type\", \"$spec\"): $@ $!"); | 
| 482 |  |  |  |  |  |  | } | 
| 483 |  |  |  |  |  |  | } elsif ($type eq 'file') { | 
| 484 | 8 |  |  |  |  | 26 | _log(4, "here we are, file spec: $spec"); | 
| 485 | 8 |  |  |  |  | 177 | my @t = stat($spec); | 
| 486 | 8 |  |  |  |  | 18 | $dep_modtime = $t[9]; | 
| 487 |  |  |  |  |  |  | } elsif ($type eq 'time') { | 
| 488 | 10 |  |  |  |  | 17 | my $spec_regex = '([0-9]*[hmdsw])+([0-9]*)?'; | 
| 489 | 10 | 50 |  |  |  | 254 | if ($spec =~ /^[0-9]{10}$/) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | #one-time timestamp expiration | 
| 491 | 0 |  |  |  |  | 0 | $dep_modtime = $spec; | 
| 492 |  |  |  |  |  |  | } elsif ($spec =~ /^$spec_regex$/) { | 
| 493 |  |  |  |  |  |  | #5w4d3h2m1s, e.g. 5 weeks, 4 days, ... | 
| 494 |  |  |  |  |  |  | #this is a bit backwards: now - spec > time of modification | 
| 495 | 4 |  |  |  |  | 15 | my $sex = get_seconds_from_timespec($spec); | 
| 496 | 4 | 50 |  |  |  | 10 | return @FALSE unless(defined($sex)); | 
| 497 | 4 |  |  |  |  | 15 | $dep_modtime = time - $sex; | 
| 498 |  |  |  |  |  |  | } elsif ($spec =~ /^[HMDW]:$spec_regex$/) { | 
| 499 |  |  |  |  |  |  | #cron-esque timespecs, e.g. {week|day|hour|min} boundary + $spec | 
| 500 |  |  |  |  |  |  | #or 3:57 on day 3 of the week (W:3d3h57m) | 
| 501 |  |  |  |  |  |  | # bound_before(now)+offset <=> request time | 
| 502 | 6 |  |  |  |  | 94 | my ($bound, $offset) = split(/:/, $spec); | 
| 503 | 6 |  |  |  |  | 23 | my $sex = get_seconds_from_timespec($offset); | 
| 504 | 6 | 50 |  |  |  | 18 | return @FALSE unless(defined($sex)); | 
| 505 | 6 |  |  |  |  | 29 | $dep_modtime = _find_bound_before_time(time, | 
| 506 |  |  |  |  |  |  | $sex, $bound); | 
| 507 | 6 | 50 |  |  |  | 22 | return @FALSE unless(defined($dep_modtime)); | 
| 508 |  |  |  |  |  |  | } else { | 
| 509 | 0 |  |  |  |  | 0 | _log(2, "Cache::Static: unrecognized time spec: ($spec), regenerating"); | 
| 510 | 0 |  |  |  |  | 0 | return @FALSE; | 
| 511 |  |  |  |  |  |  | } | 
| 512 |  |  |  |  |  |  | } elsif ($type eq 'HIT') { | 
| 513 | 0 |  |  |  |  | 0 | return @TRUE; | 
| 514 |  |  |  |  |  |  | } elsif ($type eq 'MISS') { | 
| 515 | 0 |  |  |  |  | 0 | return @FALSE; | 
| 516 |  |  |  |  |  |  | } else { | 
| 517 | 0 |  |  |  |  | 0 | my $ret = _get_conf($ns, 'unrecognized_dependency_returns'); | 
| 518 | 0 | 0 |  |  |  | 0 | _log(2, "Cache::Static: unrecognized dependency ($type)". | 
| 519 |  |  |  |  |  |  | ($ret ? ", serving anyway" : ", regenerating"). | 
| 520 |  |  |  |  |  |  | " as specified by conf option unrecognized_dependency_returns"); | 
| 521 | 0 | 0 |  |  |  | 0 | return ($ret ? @TRUE : @FALSE); | 
| 522 |  |  |  |  |  |  | } | 
| 523 |  |  |  |  |  |  | #always override the default if modifier exists | 
| 524 | 18 | 50 |  |  |  | 78 | my $bool = defined($modifier) ? $modifier : | 
| 525 |  |  |  |  |  |  | _get_conf($ns, 'dep_file_not_found_returns'); | 
| 526 | 18 | 100 |  |  |  | 70 | return ($bool ? @TRUE : @FALSE) unless($dep_modtime); | 
|  |  | 100 |  |  |  |  |  | 
| 527 | 14 | 100 |  |  |  | 76 | return @FALSE if($dep_modtime > $request_modtime); | 
| 528 |  |  |  |  |  |  | } | 
| 529 | 7 |  |  |  |  | 34 | return @TRUE; | 
| 530 |  |  |  |  |  |  | } | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | sub _get_conf { | 
| 533 | 18 |  |  | 18 |  | 32 | my ($ns, $var) = @_; | 
| 534 | 18 | 100 |  |  |  | 62 | _readconf("$ns") unless(defined($CONF{$ns})); | 
| 535 | 18 |  | 66 |  |  | 291 | return $CONF{$ns}->{$var} || $CONF{DEFAULT}->{$var}; | 
| 536 |  |  |  |  |  |  | } | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | #TODO: this whole function is a race condition... | 
| 539 |  |  |  |  |  |  | #is doing a regenerate if there was a change since _is_same best? | 
| 540 |  |  |  |  |  |  | #or should we try to save the version we thought we were gonna use? | 
| 541 |  |  |  |  |  |  | sub _get { | 
| 542 | 8 |  |  | 8 |  | 19 | my ($key, %args) = @_; | 
| 543 | 8 |  | 66 |  |  | 41 | my $ns = $args{namespace} || $namespace; | 
| 544 | 8 |  |  |  |  | 17 | _die_if_invalid_namespace($ns); | 
| 545 |  |  |  |  |  |  |  | 
| 546 | 8 | 50 |  |  |  | 381 | open(FH, "$ROOT/$ns/cache/$key") || return undef; | 
| 547 | 8 |  |  |  |  | 170 | my $t = join('', ); | 
| 548 | 8 |  |  |  |  | 82 | close(FH); | 
| 549 |  |  |  |  |  |  |  | 
| 550 | 8 |  |  |  |  | 30 | _log(3, "Cache::Static::get read $key"); | 
| 551 |  |  |  |  |  |  |  | 
| 552 | 8 |  |  |  |  | 59 | return $t; | 
| 553 |  |  |  |  |  |  | } | 
| 554 |  |  |  |  |  |  |  | 
| 555 |  |  |  |  |  |  | sub _write_spec_timestamp { | 
| 556 | 0 |  |  | 0 |  | 0 | my $spec = shift; | 
| 557 | 0 |  |  |  |  | 0 | _mkdirs_and_touch($ROOT.'/timestamps/'.md5_path($spec).'.ts', $spec); | 
| 558 |  |  |  |  |  |  | } | 
| 559 |  |  |  |  |  |  |  | 
| 560 |  |  |  |  |  |  | sub _unlink_spec_timestamp { | 
| 561 | 0 |  |  | 0 |  | 0 | my $spec = shift; | 
| 562 | 0 |  |  |  |  | 0 | my $file = $ROOT.'/timestamps/'.md5_path($spec).'.ts'; | 
| 563 | 0 |  |  |  |  | 0 | unlink($file); | 
| 564 | 0 | 0 |  |  |  | 0 | if(_get_conf($namespace, 'recursive_unlink')) { | 
| 565 | 0 |  |  |  |  | 0 | $file =~ s/\/[^\/]*$//; | 
| 566 | 0 | 0 |  |  |  | 0 | unless(opendir(DIR, $file)) { | 
| 567 | 0 |  |  |  |  | 0 | _log(3, "_unlink_spec_timestamp failed to opendir($file): (another process probably rmdir'd it):  $!"); | 
| 568 | 0 |  |  |  |  | 0 | return; | 
| 569 |  |  |  |  |  |  | } | 
| 570 | 0 |  |  |  |  | 0 | my @files = readdir(DIR); | 
| 571 | 0 | 0 |  |  |  | 0 | closedir(DIR) if(@files); | 
| 572 | 0 |  |  |  |  | 0 | while($#files == 1 ) { | 
| 573 | 0 | 0 |  |  |  | 0 | unless(rmdir $file) { | 
| 574 | 0 |  |  |  |  | 0 | _log(3, "_unlink_spec_timestamp failed to rmdir($file): (another process probably touched a file in it): $!"); | 
| 575 | 0 |  |  |  |  | 0 | return; | 
| 576 |  |  |  |  |  |  | } | 
| 577 | 0 |  |  |  |  | 0 | $file =~ s/\/[^\/]*$//; | 
| 578 | 0 | 0 |  |  |  | 0 | unless(opendir(DIR, $file)) { | 
| 579 | 0 |  |  |  |  | 0 | _log(3, "_unlink_spec_timestamp failed to opendir($file): (another process probably rmdir'd it):  $!"); | 
| 580 | 0 |  |  |  |  | 0 | return; | 
| 581 |  |  |  |  |  |  | } | 
| 582 | 0 |  |  |  |  | 0 | my @files = readdir(DIR); | 
| 583 | 0 | 0 |  |  |  | 0 | closedir(DIR) if(@files); | 
| 584 |  |  |  |  |  |  | } | 
| 585 |  |  |  |  |  |  | } | 
| 586 |  |  |  |  |  |  | } | 
| 587 |  |  |  |  |  |  |  | 
| 588 |  |  |  |  |  |  | #optional second argument indicates stuff to squirrel in the file | 
| 589 |  |  |  |  |  |  | #TODO: the name is misleading given the possibility of the 2nd arg | 
| 590 |  |  |  |  |  |  | sub _mkdirs_and_touch { | 
| 591 | 0 |  |  | 0 |  | 0 | my $file = shift; | 
| 592 | 0 |  | 0 |  |  | 0 | my $output = shift || ''; | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | #get rid of double slashes | 
| 595 | 0 |  |  |  |  | 0 | $file =~ s/\/\//\//g; | 
| 596 |  |  |  |  |  |  |  | 
| 597 |  |  |  |  |  |  | #split the dir and the filename | 
| 598 | 0 |  |  |  |  | 0 | my $dir = $file; | 
| 599 | 0 |  |  |  |  | 0 | $dir =~ s/\/[^\/]*$//; | 
| 600 |  |  |  |  |  |  |  | 
| 601 | 0 |  |  |  |  | 0 | my $err; | 
| 602 | 0 |  |  |  |  | 0 | eval { | 
| 603 |  |  |  |  |  |  | #mkdir -p | 
| 604 | 0 |  |  |  |  | 0 | _mkdir_p($dir); | 
| 605 | 0 | 0 |  |  |  | 0 | die "couldn't make/walk directories: $@" if($@); | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | #touch/write to the file | 
| 608 | 0 | 0 |  |  |  | 0 | open(FH, ">$file") || die "couldn't open $file: $!"; | 
| 609 | 0 | 0 |  |  |  | 0 | if($output) { | 
| 610 | 0 |  | 0 |  |  | 0 | print FH $output || die "couldn't print $output to $file: $!"; | 
| 611 |  |  |  |  |  |  | } | 
| 612 | 0 | 0 |  |  |  | 0 | close(FH) || die "couldn't close $file: $!"; | 
| 613 | 0 |  |  |  |  | 0 | chmod 0666, $file; | 
| 614 | 0 | 0 |  |  |  | 0 | }; if($@) { | 
| 615 | 0 |  |  |  |  | 0 | _log(2, "Cache::Static::_mkdirs_and_touch: couldn't update timestamps: $@"); | 
| 616 |  |  |  |  |  |  | } | 
| 617 |  |  |  |  |  |  | } | 
| 618 |  |  |  |  |  |  |  | 
| 619 |  |  |  |  |  |  | sub _log { | 
| 620 | 91 |  |  | 91 |  | 318 | my $severity = shift; | 
| 621 | 91 | 100 |  |  |  | 338 | return unless($severity <= $CONF{log_level}); | 
| 622 | 65 |  |  |  |  | 226 | my $args = join(' ', @_); | 
| 623 | 65 |  |  |  |  | 281 | $args =~ s/\n/ /mg; | 
| 624 | 65 |  |  |  |  | 642 | $args =~ s/\s+$//; | 
| 625 |  |  |  |  |  |  | #we don't need a full stack trace at level 3 | 
| 626 |  |  |  |  |  |  | #TODO: this regexp can be overly greedy | 
| 627 | 65 | 50 |  |  |  | 263 | $args =~ s/Stack:.*$//sg if($CONF{log_level} == 3); | 
| 628 | 65 |  |  |  |  | 4003 | my @lt = localtime(); | 
| 629 | 65 |  |  |  |  | 154 | $lt[4]++; #month starts at 0 for perl, 1 for humans | 
| 630 | 65 |  |  |  |  | 286 | @lt = map { sprintf("%02d", $_) } @lt; | 
|  | 585 |  |  |  |  | 2358 |  | 
| 631 | 65 |  |  |  |  | 871 | my $date = ($lt[5]+1900).'/'.$lt[4].'/'.$lt[3].' '.$lt[2].':'.$lt[1].':'.$lt[0]; | 
| 632 | 65 |  |  |  |  | 165 | my $level = $LOG_LEVEL_NAMES[$severity]; | 
| 633 | 65 |  |  |  |  | 309 | $level .= ' ' while(length($level) < 5); | 
| 634 |  |  |  |  |  |  |  | 
| 635 | 65 | 100 |  |  |  | 177 | if($have_fcntl) { | 
| 636 |  |  |  |  |  |  | #TODO: we don't need to open/close every time. | 
| 637 |  |  |  |  |  |  | #just flock(LOG, LOCK_EX), seek, flock(LOG, LOCK_UN); | 
| 638 |  |  |  |  |  |  | #benchmark and safety test this... | 
| 639 | 35 | 50 |  |  |  | 1551 | open(LOG, ">>$LOGFILE") || die "can't open log \"$LOGFILE\" $!"; | 
| 640 | 35 | 50 |  |  |  | 301 | flock(LOG, LOCK_EX) || die "can't lock log \"$LOGFILE\" $!"; | 
| 641 | 35 |  |  |  |  | 174 | seek(LOG, 0, 2); #seek to EOF if someone appended while we waited... | 
| 642 | 35 |  | 50 |  |  | 374 | print LOG "$level $date [$$] $args\n" || die "can't write to log \"$LOGFILE\": $!"; | 
| 643 |  |  |  |  |  |  | #close does implicit unlock | 
| 644 | 35 | 50 |  |  |  | 2098 | close(LOG) || die "can't close log \"$LOGFILE\": $!"; | 
| 645 |  |  |  |  |  |  | } else { | 
| 646 |  |  |  |  |  |  | #TODO: there must be a way to escape " such that the shell doesn't puke | 
| 647 | 30 |  |  |  |  | 86 | $args =~ s/\"/'/g; | 
| 648 | 30 |  |  |  |  | 282064 | `echo "$level $date [$$] $args" >>$LOGFILE`; | 
| 649 |  |  |  |  |  |  | } | 
| 650 |  |  |  |  |  |  | } | 
| 651 |  |  |  |  |  |  |  | 
| 652 |  |  |  |  |  |  | sub _mkdir_p { | 
| 653 | 44 |  |  | 44 |  | 596 | my $dir = shift; | 
| 654 | 44 |  |  |  |  | 600 | my @dirs = grep (/./, split(/\//, $dir)); | 
| 655 | 44 |  |  |  |  | 127 | my $dir_so_far = '/'; | 
| 656 | 44 |  |  |  |  | 98 | foreach my $d (@dirs) { | 
| 657 | 213 |  |  |  |  | 578 | $dir_so_far .= "$d/"; | 
| 658 | 213 | 100 |  |  |  | 5527 | unless(-e $dir_so_far) { | 
| 659 | 3 | 50 |  |  |  | 247 | mkdir($dir_so_far) || die "couldn't create $dir_so_far: $!"; | 
| 660 | 3 | 50 |  |  |  | 99 | chmod(0777, $dir_so_far) || die "couldn't change perms on $dir_so_far: $!"; | 
| 661 |  |  |  |  |  |  | } | 
| 662 |  |  |  |  |  |  | } | 
| 663 |  |  |  |  |  |  | } | 
| 664 |  |  |  |  |  |  |  | 
| 665 |  |  |  |  |  |  | 1; | 
| 666 |  |  |  |  |  |  | __END__ |