File Coverage

blib/lib/Cache/Repository/Filesys.pm
Criterion Covered Total %
statement 190 221 85.9
branch 45 80 56.2
condition 15 27 55.5
subroutine 28 31 90.3
pod 10 10 100.0
total 288 369 78.0


line stmt bran cond sub pod time code
1             package Cache::Repository::Filesys;
2              
3 1     1   1706 use base 'Cache::Repository';
  1         3  
  1         103  
4              
5             our $VERSION = '0.04';
6              
7 1     1   5 use strict;
  1         2  
  1         22  
8 1     1   5 use warnings;
  1         1  
  1         28  
9 1     1   4 use File::Spec;
  1         2  
  1         25  
10 1     1   4 use File::Path;
  1         2  
  1         58  
11 1     1   5 use File::Basename;
  1         2  
  1         59  
12 1     1   868 use File::stat;
  1         7268  
  1         6  
13 1     1   62 use File::Find;
  1         4  
  1         52  
14 1     1   5 use Fcntl qw(:flock);
  1         2  
  1         115  
15 1     1   6 use Carp;
  1         2  
  1         2265  
16              
17             =head1 NAME
18              
19             Cache::Repository::Filesys - Filesystem driver for Cache::Repository
20              
21             =head1 SYNOPSIS
22              
23             my $rep = Cache::Repository->new(
24             style => 'Filesys',
25             # options for the F::R driver
26             );
27             $rep->add_files(tag => 'groupname',
28             files => \@filenames,
29             basedir => '/tmp',
30             move => 1,
31             );
32             $rep->add_filehandle(tag => 'anothergroup',
33             filename => 'blah',
34             filehandle => $fh,
35             mode => 0755);
36             $rep->set_meta(tag => 'groupname',
37             meta => {
38             title => 'blah',
39             author => 'foo',
40             });
41              
42             $rep->retrieve(tag => 'groupname', dest => '/newdir');
43             my $data = $rep->get_meta(tag => 'groupname');
44              
45             =head1 DESCRIPTION
46              
47             Caching in a locally-mounted filesystem. Eventually, this will include
48             NFS-level locking, but for now, this module assuming only a single process
49             accessing the repository in write mode at a time.
50              
51             =head1 FUNCTIONS
52              
53             =over 4
54              
55             =item new
56              
57             Cache::Repository::Filesys constructor.
58              
59             my $r = Cache::Repository::Filesys->new(
60             path => '/some/path/with/enough/space',
61             );
62              
63             or
64              
65             my $r = Cache::Repository->new(
66             style => 'Filesys',
67             path => '/some/path/with/enough/space',
68             );
69              
70             Parameters:
71              
72             =over 4
73              
74             =item path
75              
76             The path in which to store the repository.
77              
78             =item clear
79              
80             If true, clear the repository (if it exists) to start anew. Existing files
81             and meta information will all be removed.
82              
83             =item compress
84              
85             The compress option is ignored in the current version.
86              
87             =item dir_mapping
88              
89             This is a code ref which is given a tag name, and maps it to a relative
90             directory that should contain the tag. The default is to use an MD5 hash of
91             the tag, and use that to create a directory hierarchy for the tag's contents.
92             You can override this to, for example, provide a more-easily-debuggable
93             path such as:
94              
95             dir_mapping => sub {
96             my $tag = shift;
97             $tag =~ s:/:_:;
98             $tag;
99             },
100              
101             =item sector_size
102              
103             =item symlink_size
104              
105             Options for L. Defaults to the blocksize of the
106             directory holding the repository if L is installed,
107             or just simply 1024 if L is not installed.
108              
109             Use 1 to get exact numbers for total file size, but this is rarely what
110             you really want (unless you're planning to put it in a tarball).
111              
112             =back
113              
114             Returns: The Cache::Repository::Filesys object, or undef if the driver failed
115             to initialise.
116              
117             =cut
118              
119             sub new
120             {
121 2     2 1 4 my $class = shift;
122 2   50     13 $class = ref $class || $class || __PACKAGE__;
123 2         6 my %opts = @_;
124              
125 2         3 my $self = \%opts;
126 2         4 bless $self, $class;
127              
128 2 50 33     11 if (exists $self->{sector_size} and $self->{sector_size} < 1)
129             {
130 0         0 require Carp;
131 0         0 croak "sector_size must be > 0";
132             }
133 2 50 33     9 if (exists $self->{symlink_size} and $self->{symlink_size} < 1)
134             {
135 0         0 require Carp;
136 0         0 croak "symlink_size must be > 0";
137             }
138              
139 2   33     8 $self->{sector_size} ||= $self->_default_blocksize();
140 2   33     10 $self->{symlink_size} ||= $self->_default_blocksize();
141              
142 2 100       7 if (delete $self->{clear})
143             {
144 1         3 $self->_clear_repository();
145             }
146 2         13 $self;
147             }
148              
149             my $_has_statvfs = -1;
150             sub _default_blocksize
151             {
152 4     4   6 my $self = shift;
153 4 100       9 eval {
154 1         784 require Filesys::Statvfs;
155 1         657 $_has_statvfs = 1;
156 1         42 my ($bsize) = Filesys::Statvfs::statvfs($self->{path});
157 1         20 return $bsize;
158             } if $_has_statvfs;
159 4         6 $_has_statvfs = 0;
160 4         12 1024;
161             }
162              
163             sub _clear_repository
164             {
165 1     1   2 my $self = shift;
166 1         2 my $path = $self->{path};
167              
168             # since $path could be a symlink, we can't blow it away. Thus,
169             # we must find everything under it, and blow those away.
170 1         6 require File::Path;
171              
172 1 50       21 if (-d $path)
173             {
174 1         563 rmtree([glob File::Spec->catfile($path, '*')]);
175             }
176             else
177             {
178 0         0 mkpath([$path]);
179             }
180             }
181              
182             # figuring out the dir from the tag - that's something we would like to
183             # be able to change - so we'll put all such constructs here to keep it
184             # malleable.
185             sub _dir
186             {
187 13     13   19 my $self = shift;
188 13         22 my $tag = shift;
189              
190 13 50       30 croak "No tag given" unless $tag;
191              
192 13         13 my $subdir;
193 13 50       34 if ($self->{dir_mapping})
194             {
195 0         0 $subdir = $self->{dir_mapping}->($tag);
196             }
197             else
198             {
199 13         66 require Digest::MD5;
200 13         55 $tag = Digest::MD5::md5_hex($tag);
201 13         136 $subdir = File::Spec->catdir(
202             substr($tag,0,2),
203             substr($tag,2,2),
204             $tag
205             );
206             }
207             File::Spec->catdir(
208             $self->{path},
209 13         113 $subdir,
210             );
211             }
212              
213             # when we add a file to a tag, we may want to store meta-info about it.
214             # filter all completed requests through here.
215             sub _add_file
216             {
217 3     3   6 my $self = shift;
218 3         12 my %opts = @_;
219              
220             #$self->{r}{$opts{tag}}{$opts{filename}} = undef;
221             $self->set_meta(tag => '_r',
222             meta => {
223             $opts{tag} => {
224             $opts{filename} => {
225 3         17 dir => $self->_dir(%opts),
226             },
227             },
228             },
229             );
230             }
231              
232             sub _remove_tag
233             {
234 0     0   0 my $self = shift;
235 0         0 my %opts = @_;
236              
237 0         0 my $data = $self->get_meta(tag => '_r');
238 0         0 delete $data->{$opts{tag}};
239 0         0 $self->set_meta(tag => '_r',
240             reset => 1,
241             meta => $data);
242             }
243              
244             sub _lock_meta
245             {
246 10     10   13 my $self = shift;
247 10   100     39 my $mode = shift || 'r';
248              
249 10         15 my $meta_name = do {
250 10 100       23 unless (exists $self->{metaname})
251             {
252 1         19 $self->{metaname} = File::Spec->catfile($self->{path}, 'meta.info');
253             }
254 10         21 $self->{metaname};
255             };
256              
257 10         55 my $fh = IO::File->new($meta_name, $mode);
258 10 100       1051 if ($fh)
259             {
260 9 100       61 flock($fh, $mode eq 'r' ? LOCK_SH : LOCK_EX);
261             }
262 10         25 $fh;
263             }
264              
265             sub _load_meta
266             {
267 6     6   9 my $self = shift;
268 6         16 my $fh = $self->_lock_meta();
269              
270             # only load it if it's been changed since the last load.
271 6         22 my $s = stat($self->{metaname});
272 6 50 100     792 if ($s and
      66        
      66        
273             $s->mtime() >= ($self->{metastamp} || 0) and
274             $fh)
275             {
276 5         75 local $/;
277 5         120 my $data = join '', $fh->getlines();
278 5         352 $self->{metastamp} = time();
279 5         17 $fh->close(); # release lock
280              
281 5         68 $self->{meta} = $self->_thaw($data);
282             }
283             }
284              
285             sub _save_meta
286             {
287 4     4   6 my $self = shift;
288 4         10 my $fh = $self->_lock_meta('w');
289              
290 4         13 $fh->print($self->_freeze($self->{meta}));
291 4         501 $fh->close();
292             }
293              
294             sub _thaw
295             {
296 5     5   9 my $self = shift;
297 5         8 my $data = shift;
298 5         403 eval 'my ' . $data;
299             }
300              
301             sub _freeze
302             {
303 4     4   6 my $self = shift;
304 4         5 my $data = shift;
305 4         1161 require Data::Dumper;
306 4         6980 local $Data::Dumper::Indent = 0;
307 4         7 local $Data::Dumper::Purity = 1;
308 4         27 join '', Data::Dumper::Dumper($data);
309             }
310              
311             =item get_meta
312              
313             Overrides L's get_meta function
314              
315             =cut
316              
317             sub get_meta
318             {
319 2     2 1 47 my $self = shift;
320 2         5 my %opts = @_;
321              
322 2         7 $self->_load_meta();
323 2 50       18 unless (exists $self->{meta}{$opts{tag}})
324             {
325             $self->{meta}{$opts{tag}} = {}
326 0         0 }
327 2         8 $self->{meta}{$opts{tag}};
328             }
329              
330             =item set_meta
331              
332             Overrides L's set_meta function
333              
334             =cut
335              
336             sub set_meta
337             {
338 4     4 1 911 my $self = shift;
339 4         13 my %opts = @_;
340              
341             #my $fh = $self->_lock_meta('w');
342              
343 4         13 $self->_load_meta();
344 4 50       24 if ($opts{'reset'})
345             {
346 0         0 $self->{meta}{$opts{tag}} = {};
347             }
348              
349             $self->{meta}{$opts{tag}} = {
350 2         9 $self->{meta}{$opts{tag}} ? %{$self->{meta}{$opts{tag}}} : (),
351 4 100       20 $opts{meta} ? %{$opts{meta}} : (),
  4 50       18  
352             };
353 4         17 $self->_save_meta();
354             }
355              
356             =item clear_tag
357              
358             =cut
359              
360             sub clear_tag
361             {
362 0     0 1 0 my $self = shift;
363 0         0 my %opts = @_;
364              
365 0         0 my $path = $self->_dir($opts{tag});
366              
367 0         0 rmtree([glob ($path . '*')]);
368             }
369              
370             =item add_symlink
371              
372             =cut
373              
374             sub add_symlink
375             {
376 0     0 1 0 my $self = shift;
377 0         0 my %opts = @_;
378              
379 0 0       0 return 0 unless $self->_is_filename_ok($opts{filename});
380              
381 0         0 my $dir = $self->_dir($opts{tag});
382 0         0 my $dstfile = File::Spec->catdir($dir, $opts{filename});
383 0         0 mkpath(dirname($dstfile));
384              
385 0 0       0 if (symlink($opts{target}, $dstfile))
386             {
387 0         0 $self->_add_file(%opts);
388 0         0 return 1;
389             }
390 0         0 undef;
391             }
392              
393             =item add_files
394             =item add_filehandle
395              
396             =cut
397              
398             sub add_filehandle
399             {
400 3     3 1 1891 my $self = shift;
401 3         12 my %opts = @_;
402 3         12 my $dir = $self->_dir($opts{tag});
403              
404 3 50       19 return 0 unless $self->_is_filename_ok($opts{filename});
405              
406 3         20 my $dstfile = File::Spec->catdir($dir, $opts{filename});
407              
408 3         1194 mkpath(dirname($dstfile));
409             #my $rc = copy($opts{filehandle}, $dstfile);
410 3         7 my $rc = 0;
411             {
412 3         5 local $/ = \32768;
  3         12  
413 3         6 local $_;
414              
415 3 50       277 if (open my $dst_h, '>', $dstfile)
416             {
417 3         7 binmode $dst_h;
418 3         6 my $in_h = $opts{filehandle};
419 3         86 print $dst_h $_ while <$in_h>;
420 3         121 $rc = 1;
421             }
422             }
423              
424 3 100       103 chmod $opts{mode}, $dstfile if exists $opts{mode};
425             chown $opts{owner}, $opts{group}, $dstfile
426 3 50 66     97 if exists $opts{owner} and exists $opts{group};
427 3 50       8 if ($rc)
428             {
429 3         17 $self->_add_file(%opts);
430             }
431 3         222 $rc;
432             }
433              
434             =item retrieve_with_callback
435              
436             =cut
437              
438             sub retrieve_with_callback
439             {
440 2     2 1 35 my $self = shift;
441 2         7 my %opts = @_;
442              
443 2         4 my $callback = $opts{callback};
444 2         3 my @files_to_extract;
445              
446 2         7 my $repos_dir = $self->_dir($opts{tag});
447 2 50       66 return undef unless -d $repos_dir;
448              
449 2 100       6 if (exists $opts{files})
450             {
451 1 50       5 @files_to_extract = ref $opts{files} ? @{$opts{files}} : ($opts{files});
  1         4  
452             }
453             else
454             {
455 1         6 @files_to_extract = $self->list_files(%opts);
456             }
457              
458 2         5 foreach my $file (@files_to_extract)
459             {
460 3         33 my $srcname = File::Spec->catfile($repos_dir, $file);
461 3         13 my $s = stat($srcname);
462              
463 3 50       388 return 0 unless $s;
464              
465 3         62 my %cb_opts = (
466             mode => $s->mode(),
467             owner => $s->uid(),
468             group => $s->gid(),
469             filename => $file,
470             start => 1,
471             );
472 3 50       121 if (-l $srcname)
473             {
474 0 0       0 $callback->(%cb_opts, target => readlink($srcname)) or return 0;
475             }
476             else
477             {
478 3 50       14 my $fh = IO::File->new($srcname, 'r') or return 0;
479 3         265 binmode $fh;
480              
481 3         3 my $buf;
482 3         30 while (my $r = sysread($fh, $buf, 32 * 1024))
483             {
484 3 50       14 $callback->(%cb_opts, data => $buf) or return 0;
485 3         19 delete $cb_opts{start};
486             }
487 3         5 $buf = undef;
488 3 50       12 $callback->(%cb_opts, data => undef, end => 1) or return 0;
489             }
490             }
491 2         16 return 1;
492             }
493              
494             =item get_size
495              
496             =cut
497              
498             sub get_size
499             {
500 1     1 1 2200 my $self = shift;
501 1         3 my %opts = @_;
502              
503 1         4 my $repos_dir = $self->_dir($opts{tag});
504 1 50       34 return 0 unless -d $repos_dir;
505              
506 1         2 my @files;
507              
508 1 50       6 if (exists $opts{files})
509             {
510 0 0       0 @files = ref $opts{files} ? @{$opts{files}} : ($opts{files});
  0         0  
511             }
512             else
513             {
514 1         5 @files = $self->list_files(%opts);
515             }
516              
517 1         2 my $size;
518 1         4 my $dir = $self->_dir($opts{tag});
519 1         4 foreach my $f (@files)
520             {
521 2         4 my $s;
522 2         12 my $fullname = File::Spec->catdir($dir, $f);
523 2 50       53 if (-l $fullname)
524             {
525 0         0 $s = 1024;
526             }
527             else
528             {
529 2         3 $s = -s _;
530 2 50       7 if ($s % 1024)
531             {
532 2         3 $s -= $s % 1024;
533 2         4 $s += 1024;
534             }
535             }
536 2         4 $size += $s;
537             }
538 1         5 $size;
539             }
540              
541             =item list_files
542              
543             =cut
544              
545             sub list_files
546             {
547 3     3 1 381 my $self = shift;
548 3         8 my %opts = @_;
549              
550 3         8 my $dir = $self->_dir($opts{tag});
551 3         7 my @files;
552              
553             find(
554             {
555             wanted => sub {
556 10 100   10   723 return unless -f $File::Find::name;
557 5         15 my $name = substr(
558             $File::Find::name,
559             length($dir) + 1
560             );
561 5         62 push @files, $name;
562             },
563 3 50       346 no_chdir => 1,
564             },
565             $dir
566             ) if -d $dir;
567 3 50       26 wantarray ? @files : \@files;
568             }
569              
570             =item list_tags
571              
572             See L for documentation on these.
573              
574             =cut
575              
576             sub list_tags
577             {
578 1     1 1 423 my $self = shift;
579 1         3 my %opts = @_;
580              
581 1         5 my $r = $self->get_meta(tag=>'_r');
582 1         5 my @t = keys %$r;
583 1 50       6 wantarray ? @t : \@t;
584             }
585              
586             =back
587              
588             =head1 AUTHOR
589              
590             Darin McBride - dmcbride@cpan.org
591              
592             =head1 COPYRIGHT
593              
594             Copyright 2005 Darin McBride.
595              
596             You may distribute under the terms of either the GNU General Public
597             License or the Artistic License, as specified in the Perl README file.
598              
599             =head1 BUGS
600              
601             See TODO file.
602              
603             =cut
604              
605             1;
606