File Coverage

blib/lib/Catalyst/Controller/AutoAssets/Handler.pm
Criterion Covered Total %
statement 162 206 78.6
branch 39 70 55.7
condition 11 38 28.9
subroutine 43 53 81.1
pod 0 26 0.0
total 255 393 64.8


line stmt bran cond sub pod time code
1             package Catalyst::Controller::AutoAssets::Handler;
2 4     4   2490 use strict;
  4         7  
  4         109  
3 4     4   13 use warnings;
  4         4  
  4         95  
4              
5             # VERSION
6              
7 4     4   16 use Moose::Role;
  4         3  
  4         33  
8 4     4   8932 use namespace::autoclean;
  4         6  
  4         28  
9              
10             requires qw(
11             asset_request
12             write_built_file
13             );
14              
15 4     4   273 use Cwd;
  4         4  
  4         266  
16 4     4   16 use Path::Class 0.32 qw( dir file );
  4         82  
  4         171  
17 4     4   16 use Fcntl qw( :DEFAULT :flock );
  4         5  
  4         1470  
18 4     4   19 use Carp;
  4         6  
  4         210  
19 4     4   17 use File::stat qw(stat);
  4         5  
  4         47  
20 4     4   229 use Catalyst::Utils;
  4         4  
  4         87  
21 4     4   12 use Time::HiRes qw(gettimeofday tv_interval);
  4         6  
  4         34  
22 4     4   363 use Storable qw(store retrieve);
  4         5  
  4         247  
23 4     4   16 use Try::Tiny;
  4         3  
  4         168  
24 4     4   1699 use Data::Dumper::Concise 'Dumper';
  4         19630  
  4         10022  
25              
26             require Digest::SHA1;
27             require MIME::Types;
28             require Module::Runtime;
29              
30             has 'Controller' => (
31             is => 'ro', required => 1,
32             isa => 'Catalyst::Controller::AutoAssets',
33             handles => [qw(type _app action_namespace unknown_asset _build_params _module_version)],
34             );
35              
36             # Directories to include
37             has 'include', is => 'ro', isa => 'ScalarRef|Str|ArrayRef[ScalarRef|Str]', required => 1;
38              
39             # Optional regex to require files to match to be included
40             has 'include_regex', is => 'ro', isa => 'Maybe[Str]', default => undef;
41              
42             # Optional regex to exclude files
43             has 'exclude_regex', is => 'ro', isa => 'Maybe[Str]', default => undef;
44              
45             # Whether or not to use qr/$regex/i or qr/$regex/
46             has 'regex_ignore_case', is => 'ro', isa => 'Bool', default => 0;
47              
48             # Whether or not to make the current asset available via 307 redirect to the
49             # real, current checksum/fingerprint asset path
50             has 'current_redirect', is => 'ro', isa => 'Bool', default => 1;
51              
52             # What string to use for the 'current' redirect
53             has 'current_alias', is => 'ro', isa => 'Str', default => 'current';
54              
55             # Whether or not to make the current asset available via a static path
56             # with no benefit of caching
57             has 'allow_static_requests', is => 'ro', isa => 'Bool', default => 0;
58              
59             # What string to use for the 'static' path
60             has 'static_alias', is => 'ro', isa => 'Str', default => 'static';
61              
62             # Extra custom response headers for current/static requests
63             has 'current_response_headers', is => 'ro', isa => 'HashRef', default => sub {{}};
64             has 'static_response_headers', is => 'ro', isa => 'HashRef', default => sub {{}};
65              
66             # Whether or not to set 'Etag' response headers and check 'If-None-Match' request headers
67             # Very useful when using 'static' paths
68             has 'use_etags', is => 'ro', isa => 'Bool', default => 0;
69              
70             # Max number of seconds before recalculating the fingerprint (sha1 checksum)
71             # regardless of whether or not the mtime has changed. 0 means infinite/disabled
72             has 'max_fingerprint_calc_age', is => 'ro', isa => 'Int', default => sub {0};
73              
74             # Max number of seconds to wait to obtain a lock (to be thread safe)
75             has 'max_lock_wait', is => 'ro', isa => 'Int', default => 120;
76              
77             has 'cache_control_header', is => 'ro', isa => 'Str',
78             default => sub { 'public, max-age=31536000, s-max-age=31536000' }; # 31536000 = 1 year
79              
80             # Whether or not to use stored state data across restarts to avoid rebuilding.
81             has 'persist_state', is => 'ro', isa => 'Bool', default => sub{0};
82              
83             # Optional shorter checksum
84             has 'sha1_string_length', is => 'ro', isa => 'Int', default => sub{40};
85              
86             # directory to use for relative includes (defaults to the Catalyst home dir);
87             # TODO: coerce from Str
88             has 'include_relative_dir', isa => 'Path::Class::Dir', is => 'ro', lazy => 1, default => sub {
89             my $self = shift;
90             my $home = $self->_app->config->{home};
91             $home = $home && -d $home ? $self->_app->config->{home} : cwd();
92             return dir( $home );
93             };
94              
95              
96              
97             ######################################
98              
99              
100       0 0   sub BUILD {}
101             before BUILD => sub {
102             my $self = shift;
103            
104             # optionally initialize state data from the copy stored on disk for fast
105             # startup (avoids having to always rebuild after every app restart):
106             $self->_restore_state if($self->persist_state);
107              
108             # init includes
109             $self->includes;
110            
111             Catalyst::Exception->throw("Must include at least one file/directory")
112             unless (scalar @{$self->includes} > 0);
113              
114             # if the user picks something lower than 5 it is probably a mistake (really, anything
115             # lower than 8 is probably not a good idea. But the full 40 is probably way overkill)
116             Catalyst::Exception->throw("sha1_string_length must be between 5 and 40")
117             unless ($self->sha1_string_length >= 5 && $self->sha1_string_length <= 40);
118              
119             # init work_dir:
120             $self->work_dir->mkpath($self->_app->debug);
121             $self->work_dir->resolve;
122            
123             $self->prepare_asset;
124             };
125              
126             # Main code entry point:
127             sub request {
128 18     18 0 221 my ( $self, $c, @args ) = @_;
129 18         29 my $sha1 = $args[0];
130            
131 18 100       76 return $self->current_request($c, @args) if (
132             $self->is_current_request_arg(@args)
133             );
134            
135 14 50 33     506 return $self->static_request($c, @args) if (
136             $self->allow_static_requests
137             && $self->static_alias eq $sha1
138             );
139            
140 14         70 return $self->handle_asset_request($c, @args);
141             }
142              
143             sub is_current_request_arg {
144 5     5 0 7 my ($self, $arg) = @_;
145 5 100       172 return $arg eq $self->current_alias ? 1 : 0;
146             }
147              
148             sub current_request {
149 4     4 0 8 my ( $self, $c, $arg, @args ) = @_;
150             my %headers = (
151             'Cache-Control' => 'no-cache',
152 4         9 %{$self->current_response_headers}
  4         143  
153             );
154 4         83 $c->response->header( $_ => $headers{$_} ) for (keys %headers);
155 4         1027 $c->response->redirect(join('/',$self->asset_path,@args), 307);
156 4         695 return $c->detach;
157             }
158              
159             sub static_request {
160 0     0 0 0 my ( $self, $c, $arg, @args ) = @_;
161             my %headers = (
162             'Cache-Control' => 'no-cache',
163 0         0 %{$self->static_response_headers}
  0         0  
164             );
165 0         0 $c->response->header( $_ => $headers{$_} ) for (keys %headers);
166             # Simulate a request to the current sha1 checksum:
167 0         0 return $self->handle_asset_request($c, $self->asset_name, @args);
168             }
169              
170              
171             sub handle_asset_request {
172 14     14 0 27 my ( $self, $c, $arg, @args ) = @_;
173            
174 14         49 $self->prepare_asset(@args);
175            
176 14 50 33     489 if($self->use_etags && $self->client_current_etag($c, $arg, @args)) {
177             # Set 304 Not Modified:
178 0         0 $c->response->status(304);
179             }
180             else {
181 14         59 $self->asset_request($c, $arg, @args);
182             }
183 12         257 return $c->detach;
184             }
185              
186             sub client_current_etag {
187 0     0 0 0 my ( $self, $c, $arg, @args ) = @_;
188            
189 0         0 my $etag = $self->etag_value(@args);
190 0         0 $c->response->header( Etag => $etag );
191 0         0 my $client_etag = $c->request->headers->{'if-none-match'};
192 0 0 0     0 return ($client_etag && $client_etag eq $etag) ? 1 : 0;
193             }
194              
195             sub etag_value {
196 0     0 0 0 my $self = shift;
197 0         0 return '"' . join('/',$self->asset_name,@_) . '"';
198             }
199              
200              
201             ############################
202              
203              
204             has 'work_dir', is => 'ro', isa => 'Path::Class::Dir', lazy => 1, default => sub {
205             my $self = shift;
206             my $c = $self->_app;
207            
208             my $tmpdir = Catalyst::Utils::class2tempdir($c)
209             || Catalyst::Exception->throw("Can't determine tempdir for $c");
210            
211             return dir($tmpdir, "AutoAssets", $self->action_namespace($c));
212             };
213              
214             has 'built_file', is => 'ro', isa => 'Path::Class::File', lazy => 1, default => sub {
215             my $self = shift;
216             my $filename = 'built_file';
217             return file($self->work_dir,$filename);
218             };
219              
220             has 'scratch_dir', is => 'ro', isa => 'Path::Class::Dir', lazy => 1, default => sub {
221             my $self = shift;
222            
223             my $Dir = dir($self->work_dir,'_scratch');
224             $Dir->rmtree if (-d $Dir);
225             $Dir->mkpath;
226            
227             return $Dir
228             };
229              
230             has 'fingerprint_file', is => 'ro', isa => 'Path::Class::File', lazy => 1, default => sub {
231             my $self = shift;
232             return file($self->work_dir,'fingerprint');
233             };
234              
235             has 'lock_file', is => 'ro', isa => 'Path::Class::File', lazy => 1, default => sub {
236             my $self = shift;
237             return file($self->work_dir,'lockfile');
238             };
239              
240              
241             has 'includes', is => 'ro', isa => 'ArrayRef', lazy => 1, default => sub {
242             my $self = shift;
243             my $rel = $self->include_relative_dir;
244              
245             my @list = ((ref $self->include)||'') eq 'ARRAY' ? @{$self->include} : $self->include;
246             my $i = 0;
247             return [ map {
248             my $inc; $i++;
249             if((ref($_)||'') eq 'SCALAR') {
250             # New support for ScalarRef includes ... we pre-dump them to a temp file
251             $inc = file( $self->scratch_dir, join('','_generated_include_file_',$i) );
252             $inc->spew(iomode => '>:raw', $$_);
253             }
254             else {
255             $inc = file($_);
256             }
257             $inc = $rel->file($inc) unless ($inc->is_absolute);
258             $inc = dir($inc) if (-d $inc); #<-- convert to Path::Class::Dir
259             $inc->resolve
260             } @list ];
261             };
262              
263              
264              
265              
266             sub get_include_files {
267 18     18 0 23 my $self = shift;
268            
269 18         31 my @excluded = ();
270 18         29 my @files = ();
271 18         25 for my $inc (@{$self->includes}) {
  18         629  
272 18 100       109 if($inc->is_dir) {
273             $inc->recurse(
274             preorder => 1,
275             depthfirst => 1,
276             callback => sub {
277 41     41   6634 my $child = shift;
278 41 100       89 $self->_valid_include_file($child)
279             ? push @files, $child->absolute
280             : push @excluded, $child->absolute;
281             }
282 12         130 );
283             }
284             else {
285 6 50       37 $self->_valid_include_file($inc)
286             ? push @files, $inc->absolute
287             : push @excluded, $inc->absolute;
288             }
289             }
290            
291             # Some handlers (like Directory) need to know about excluded files
292 18         1149 $self->_record_excluded_files(\@excluded);
293            
294             # force consistent ordering of files:
295 18         74 return [sort @files];
296             }
297              
298             # optional hook for excluded files:
299       17     sub _record_excluded_files {}
300              
301              
302             has '_include_regexp', is => 'ro', isa => 'Maybe[RegexpRef]',
303             lazy => 1, init_arg => undef, default => sub {
304             my $self = shift;
305             my $str = $self->include_regex or return undef;
306             return $self->regex_ignore_case ? qr/$str/i : qr/$str/;
307             };
308             has '_exclude_regexp', is => 'ro', isa => 'Maybe[RegexpRef]',
309             lazy => 1, init_arg => undef, default => sub {
310             my $self = shift;
311             my $str = $self->exclude_regex or return undef;
312             return $self->regex_ignore_case ? qr/$str/i : qr/$str/;
313             };
314              
315             sub _valid_include_file {
316 47     47   53 my ($self, $file) = @_;
317             return (
318 47 100 33     84 $file->is_dir
319             || ($self->include_regex && ! ($file =~ $self->_include_regexp))
320             || ($self->exclude_regex && $file =~ $self->_exclude_regexp)
321             ) ? 0 : 1;
322             }
323              
324             has 'last_fingerprint_calculated', is => 'rw', isa => 'Maybe[Int]', default => sub{undef};
325              
326             has 'built_mtime', is => 'rw', isa => 'Maybe[Str]', default => sub{undef};
327             sub get_built_mtime {
328 37     37 0 54 my $self = shift;
329 37 100       1317 return -f $self->built_file ? $self->built_file->stat->mtime : undef;
330             }
331              
332             # inc_mtimes are the mtime(s) of the include files. For directory assets
333             # this is *only* the mtime of the top directory (see subfile_meta below)
334             has 'inc_mtimes', is => 'rw', isa => 'Maybe[Str]', default => undef;
335             sub get_inc_mtime_concat {
336 29     29 0 45 my $self = shift;
337 29         40 my $list = shift;
338 29         46 return join('-', map { $_->stat->mtime } @$list );
  40         1764  
339             }
340              
341              
342             sub calculate_fingerprint {
343 8     8 0 11 my $self = shift;
344 8         11 my $list = shift;
345             # include both the include (source) and built (output) in the fingerprint:
346 8         284 my $sha1 = $self->file_checksum(@$list,$self->built_file);
347 8 50       318 $self->last_fingerprint_calculated(time) if ($sha1);
348 8         22 return $sha1;
349             }
350              
351             sub current_fingerprint {
352 26     26 0 36 my $self = shift;
353 26 100       914 return undef unless (-f $self->fingerprint_file);
354 22         1733 my $fingerprint = $self->fingerprint_file->slurp(iomode => '<:raw');
355 22         4941 return $fingerprint;
356             }
357              
358             sub save_fingerprint {
359 4     4 0 8 my $self = shift;
360 4 50       11 my $fingerprint = shift or die "Expected fingerprint/checksum argument";
361 4         136 return $self->fingerprint_file->spew(iomode => '>:raw', $fingerprint);
362             }
363              
364             sub calculate_save_fingerprint {
365 4     4 0 7 my $self = shift;
366 4 50       17 my $fingerprint = $self->calculate_fingerprint(@_) or return 0;
367 4         15 return $self->save_fingerprint($fingerprint);
368             }
369              
370             sub fingerprint_calc_current {
371 25     25 0 29 my $self = shift;
372 25 50       872 my $last = $self->last_fingerprint_calculated or return 0;
373 25 50       808 return 1 if ($self->max_fingerprint_calc_age == 0); # <-- 0 means infinite
374 0 0       0 return 1 if (time - $last < $self->max_fingerprint_calc_age);
375 0         0 return 0;
376             }
377              
378             # -----
379             # Quick and dirty state persistence for faster startup
380             has 'persist_state_file', is => 'ro', isa => 'Path::Class::File', lazy => 1, default => sub {
381             my $self = shift;
382             return file($self->work_dir,'state.dat');
383             };
384              
385             has '_persist_attrs', is => 'ro', isa => 'ArrayRef', default => sub{[qw(
386             built_mtime
387             inc_mtimes
388             last_fingerprint_calculated
389             )]};
390              
391             sub _persist_state {
392 4     4   6 my $self = shift;
393 4 50       135 return undef unless ($self->persist_state);
394 0         0 my $data = { map { $_ => $self->$_ } @{$self->_persist_attrs} };
  0         0  
  0         0  
395 0         0 $data->{_module_version} = $self->_module_version;
396 0         0 $data->{_build_params} = $self->_build_params;
397 0         0 store $data, $self->persist_state_file;
398 0         0 return $data;
399             }
400              
401             sub _restore_state {
402 0     0   0 my $self = shift;
403 0 0       0 return 0 unless (-f $self->persist_state_file);
404 0         0 my $data;
405             try {
406 0     0   0 $data = retrieve $self->persist_state_file;
407 0 0       0 if($self->_valid_state_data($data)) {
408 0         0 $self->$_($data->{$_}) for (@{$self->_persist_attrs});
  0         0  
409             }
410             }
411             catch {
412 0     0   0 $self->clear_asset; #<-- make sure no partial state data is used
413 0         0 $self->_app->log->warn(
414             'Failed to restore state from ' . $self->persist_state_file
415             );
416 0         0 };
417 0         0 return $data;
418             }
419              
420             sub _valid_state_data {
421 0     0   0 my ($self, $data) = @_;
422            
423             # Make sure the version and config params hasn't changed
424             return (
425             $self->_module_version eq $data->{_module_version}
426             && Dumper($self->_build_params) eq Dumper($data->{_build_params})
427 0 0 0     0 ) ? 1 : 0;
428             }
429             # -----
430              
431              
432             # force rebuild on next request/prepare_asset
433             sub clear_asset {
434 0     0 0 0 my $self = shift;
435 0         0 $self->inc_mtimes(undef);
436             }
437              
438             sub _build_required {
439 29     29   39 my ($self, $d) = @_;
440             return (
441             $self->inc_mtimes && $self->built_mtime &&
442             $d->{inc_mtimes} && $d->{built_mtime} &&
443             $self->inc_mtimes eq $d->{inc_mtimes} &&
444             $self->built_mtime eq $d->{built_mtime} &&
445 29 100 33     970 $self->fingerprint_calc_current
446             ) ? 0 : 1;
447             }
448              
449              
450             # Gets the data used throughout the prepare_asset process:
451             sub get_prepare_data {
452 17     17 0 24 my $self = shift;
453            
454 17         49 my $files = $self->get_include_files;
455 17         427 my $inc_mtimes = $self->get_inc_mtime_concat($files);
456 17         2696 my $built_mtime = $self->get_built_mtime;
457            
458             return {
459 17         1843 files => $files,
460             inc_mtimes => $inc_mtimes,
461             built_mtime => $built_mtime
462             };
463             }
464              
465       17 0   sub before_prepare_asset {}
466              
467             sub prepare_asset {
468 29     29 0 44 my $self = shift;
469 29         131 my $start = [gettimeofday];
470              
471             # Optional hook:
472 29         124 $self->before_prepare_asset(@_);
473              
474 29         132 my $opt = $self->get_prepare_data;
475 29 100       93 return 1 unless $self->_build_required($opt);
476              
477             #### -----
478             #### The code above this line happens on every request and is designed
479             #### to be as fast as possible
480             ####
481             #### The code below this line is (comparatively) expensive and only
482             #### happens when a rebuild is needed which should be rare--only when
483             #### content is modified, or on app startup (unless 'persist_state' is set)
484             #### -----
485              
486             ### Do a rebuild:
487              
488             # --- Blocks for up to max_lock_wait seconds waiting to get an exclusive lock
489             # The lock is held until it goes out of scope.
490             # If we fail to get the lock, we just continue anyway in hopes that the second
491             # build won't corrupt the first, which is arguably better than killing the
492             # request.
493 4     4   59 my $lock= try { $self->_get_lock($self->lock_file, $self->max_lock_wait); };
  4         281  
494             # ---
495            
496 4         79 $self->build_asset($opt);
497            
498 4 50       705 $self->_app->log->debug(
499             "Built asset: " . $self->base_path . '/' . $self->asset_name .
500             ' in ' . sprintf("%.3f", tv_interval($start) ) . 's'
501             ) if ($self->_app->debug);
502              
503             # Release the lock and return:
504 4         187 $self->_persist_state;
505 4         61 return 1;
506             }
507              
508             sub build_asset {
509 4     4 0 9 my ($self, $opt) = @_;
510            
511 4   33     21 my $files = $opt->{files} || $self->get_include_files;
512 4   33     15 my $inc_mtimes = $opt->{inc_mtimes} || $self->get_inc_mtime_concat($files);
513 4   33     29 my $built_mtime = $opt->{built_mtime} || $self->get_built_mtime;
514            
515             # Check the fingerprint to see if we can avoid a full rebuild (if mtimes changed
516             # but the actual content hasn't by comparing the fingerprint/checksum):
517 4         302 my $fingerprint = $self->calculate_fingerprint($files);
518 4         13 my $cur_fingerprint = $self->current_fingerprint;
519 4 50 33     234 if($fingerprint && $cur_fingerprint && $cur_fingerprint eq $fingerprint) {
      33        
520             # If the mtimes changed but the fingerprint matches we don't need to regenerate.
521             # This will happen if another process just built the files while we were waiting
522             # for the lock and on the very first time after the application starts up
523 0         0 $self->inc_mtimes($inc_mtimes);
524 0         0 $self->built_mtime($built_mtime);
525 0         0 $self->_persist_state;
526 0         0 return 1;
527             }
528              
529             ### Ok, we really need to do a full rebuild:
530              
531 4 50       184 my $fd = $self->built_file->openw or die $!;
532 4         634 binmode $fd;
533 4         29 $self->write_built_file($fd,$files);
534 4 100       390 $fd->close if ($fd->opened);
535            
536             # Update the fingerprint (global) and cached mtimes (specific to the current process)
537 4         391 $self->inc_mtimes($opt->{inc_mtimes});
538 4         10 $self->built_mtime($self->get_built_mtime);
539             # we're calculating the fingerprint again because the built_file, which was just
540             # regenerated, is included in the checksum data. This could probably be optimized,
541             # however, this only happens on rebuild which rarely happens (should never happen)
542             # in production so an extra second is no big deal in this case.
543 4         104 $self->calculate_save_fingerprint($opt->{files});
544             }
545              
546             sub file_checksum {
547 8     8 0 13 my $self = shift;
548 8 50       26 my $files = ref $_[0] eq 'ARRAY' ? $_[0] : \@_;
549            
550 8         78 my $Sha1 = Digest::SHA1->new;
551 8         16 foreach my $file ( grep { -f $_ } @$files ) {
  26         680  
552 22 50       435 my $fh = $file->openr or die "$! : $file\n";
553 22         2731 binmode $fh;
554 22         831 $Sha1->addfile($fh);
555 22         81 $fh->close;
556             }
557              
558 8         431 return substr $Sha1->hexdigest, 0, $self->sha1_string_length;
559             }
560              
561 8     8 0 515 sub asset_name { (shift)->current_fingerprint }
562              
563             sub base_path {
564 8     8 0 10 my $self = shift;
565 8   50 8   47 my $pfx = try{RapidApp->active_request_context->mount_url} || '';
  8         259  
566 8         127 return join('/',$pfx,$self->action_namespace($self->_app));
567             }
568              
569             # this is just used for some internal optimization to avoid calling stat
570             # duplicate times. It is basically me being lazy, adding an internal extra param
571             # to asset_path() without changing its public API/arg list
572             has '_asset_path_skip_prepare', is => 'rw', isa => 'Bool', default => 0;
573             before asset_path => sub {
574             my $self = shift;
575             $self->prepare_asset(@_) unless ($self->_asset_path_skip_prepare);
576             };
577             sub asset_path {
578 8     8 0 18 my $self = shift;
579 8         25 return $self->base_path . '/' . $self->asset_name;
580             }
581              
582 0     0 0 0 sub html_head_tags { undef }
583              
584             # This locks a file or dies trying, and on success, returns a "lock object"
585             # which will release the lock if it goes out of scope. At the moment, this
586             # "object" is just a file handle.
587             #
588             # This lock is specifically *not* inherited by child processes (thanks to
589             # fcntl(FL_CLOEXEC), and in fact, this design principle gives it
590             # cross-platform compatibility that most lock module sdon't have.
591             #
592             sub _get_lock {
593 4     4   8 my ($self, $fname, $timeout)= @_;
594 4         5 my $fh;
595 4 50 33     37 sysopen($fh, $fname, O_RDWR|O_CREAT|O_EXCL, 0644)
596             or sysopen($fh, $fname, O_RDWR)
597             or croak "Unable to create or open $fname";
598            
599 4     4   179 try { fcntl($fh, F_SETFD, FD_CLOEXEC) }
600 4 50       113117 or carp "Failed to set close-on-exec for $fname (see BUGS in Catalyst::Controller::AutoAssets)";
601            
602             # Try to get lock until timeout. We poll because there isn't a sensible
603             # way to wait for the lock. (I don't consider SIGALRM to be very sensible)
604 4         89 my $deadline= Time::HiRes::time() + $timeout;
605 4         8 my $locked= 0;
606 4         7 while (1) {
607 4 50       48 last if flock($fh, LOCK_EX|LOCK_NB);
608 0 0       0 croak "Can't get lock on $fname after $timeout seconds" if Time::HiRes::time() >= $deadline;
609 0         0 Time::HiRes::sleep(0.4);
610             }
611            
612             # Succeeded in getting the lock, so write our pid.
613 4         25 my $data= "$$";
614 4 50       248 syswrite($fh, $data, length($data)) or croak "Failed to write pid to $fname";
615 4 50       155 truncate($fh, length($data)) or croak "Failed to resize $fname";
616            
617 4         25 return $fh;
618             }
619              
620             1;
621              
622             __END__
623              
624             =pod
625              
626             =head1 NAME
627              
628             Catalyst::Controller::AutoAssets::Handler - Handler type Role and default namespace
629              
630             =head1 DESCRIPTION
631              
632             This is the base Role for C<Catalyst::Controller::AutoAssets> Handler classes and is
633             where the majority of the work is done for the AutoAssets module. The Handler class is
634             specified in the 'type' config param and is relative to this namespace. Absolute class
635             names can also be specified with the '+' prefix, so the following are equivalent:
636              
637             type => 'Directory'
638            
639             type => '+Catalyst::Controller::AutoAssets::Handler::Directory'
640              
641             Custom Handler classes can be written and used as long as they consume this Role. For examples
642             of how to write custom Handlers, see the existing Handlers below for reference.
643              
644             =head1 TYPE HANDLERS
645              
646             These are the current built in handler classes:
647              
648             =over
649              
650             =item L<Catalyst::Controller::AutoAssets::Handler::Directory>
651              
652             =item L<Catalyst::Controller::AutoAssets::Handler::CSS>
653              
654             =item L<Catalyst::Controller::AutoAssets::Handler::JS>
655              
656             =item L<Catalyst::Controller::AutoAssets::Handler::ImageSet>
657              
658             =item L<Catalyst::Controller::AutoAssets::Handler::IconSet>
659              
660             =back
661              
662             =head1 AUTHOR
663              
664             Henry Van Styn <vanstyn@cpan.org>
665              
666             =head1 COPYRIGHT AND LICENSE
667              
668             This software is copyright (c) 2013 by IntelliTree Solutions llc.
669              
670             This is free software; you can redistribute it and/or modify it under
671             the same terms as the Perl 5 programming language system itself.
672              
673             =cut
674              
675