File Coverage

blib/lib/Cache/Repository/Filesys.pm
Criterion Covered Total %
statement 190 221 85.9
branch 46 80 57.5
condition 15 27 55.5
subroutine 28 31 90.3
pod 10 10 100.0
total 289 369 78.3


line stmt bran cond sub pod time code
1             package Cache::Repository::Filesys;
2              
3 1     1   2097 use base 'Cache::Repository';
  1         21  
  1         156  
4              
5             our $VERSION = '0.04';
6              
7 1     1   8 use strict;
  1         3  
  1         646  
8 1     1   7 use warnings;
  1         1  
  1         38  
9 1     1   13 use File::Spec;
  1         2  
  1         23  
10 1     1   5 use File::Path;
  1         2  
  1         65  
11 1     1   6 use File::Basename;
  1         1  
  1         60  
12 1     1   964 use File::stat;
  1         9492  
  1         10  
13 1     1   72 use File::Find;
  1         2  
  1         62  
14 1     1   8 use Fcntl qw(:flock);
  1         2  
  1         142  
15 1     1   7 use Carp;
  1         2  
  1         2854  
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 5 my $class = shift;
122 2   50     16 $class = ref $class || $class || __PACKAGE__;
123 2         8 my %opts = @_;
124              
125 2         3 my $self = \%opts;
126 2         8 bless $self, $class;
127              
128 2 50 33     14 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     12 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     12 $self->{sector_size} ||= $self->_default_blocksize();
140 2   33     12 $self->{symlink_size} ||= $self->_default_blocksize();
141              
142 2 100       7 if (delete $self->{clear})
143             {
144 1         4 $self->_clear_repository();
145             }
146 2         17 $self;
147             }
148              
149             my $_has_statvfs = -1;
150             sub _default_blocksize
151             {
152 4     4   6 my $self = shift;
153 4 100       11 eval {
154 1         1048 require Filesys::Statvfs;
155 1         778 $_has_statvfs = 1;
156 1         47 my ($bsize) = Filesys::Statvfs::statvfs($self->{path});
157 1         22 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         3 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         5 require File::Path;
171              
172 1 50       23 if (-d $path)
173             {
174 1         2643 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   23 my $self = shift;
188 13         18 my $tag = shift;
189              
190 13 50       31 croak "No tag given" unless $tag;
191              
192 13         13 my $subdir;
193 13 50       28 if ($self->{dir_mapping})
194             {
195 0         0 $subdir = $self->{dir_mapping}->($tag);
196             }
197             else
198             {
199 13         69 require Digest::MD5;
200 13         63 $tag = Digest::MD5::md5_hex($tag);
201 13         143 $subdir = File::Spec->catdir(
202             substr($tag,0,2),
203             substr($tag,2,2),
204             $tag
205             );
206             }
207 13         122 File::Spec->catdir(
208             $self->{path},
209             $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         15 my %opts = @_;
219              
220             #$self->{r}{$opts{tag}}{$opts{filename}} = undef;
221 3         18 $self->set_meta(tag => '_r',
222             meta => {
223             $opts{tag} => {
224             $opts{filename} => {
225             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   14 my $self = shift;
247 10   100     37 my $mode = shift || 'r';
248              
249 10         11 my $meta_name = do {
250 10 100       30 unless (exists $self->{metaname})
251             {
252 1         12 $self->{metaname} = File::Spec->catfile($self->{path}, 'meta.info');
253             }
254 10         24 $self->{metaname};
255             };
256              
257 10         55 my $fh = IO::File->new($meta_name, $mode);
258 10 100       1165 if ($fh)
259             {
260 9 100       83 flock($fh, $mode eq 'r' ? LOCK_SH : LOCK_EX);
261             }
262 10         25 $fh;
263             }
264              
265             sub _load_meta
266             {
267 6     6   13 my $self = shift;
268 6         17 my $fh = $self->_lock_meta();
269              
270             # only load it if it's been changed since the last load.
271 6         23 my $s = stat($self->{metaname});
272 6 50 100     806 if ($s and
      66        
      66        
273             $s->mtime() >= ($self->{metastamp} || 0) and
274             $fh)
275             {
276 5         81 local $/;
277 5         126 my $data = join '', $fh->getlines();
278 5         391 $self->{metastamp} = time();
279 5         19 $fh->close(); # release lock
280              
281 5         85 $self->{meta} = $self->_thaw($data);
282             }
283             }
284              
285             sub _save_meta
286             {
287 4     4   6 my $self = shift;
288 4         9 my $fh = $self->_lock_meta('w');
289              
290 4         21 $fh->print($self->_freeze($self->{meta}));
291 4         651 $fh->close();
292             }
293              
294             sub _thaw
295             {
296 5     5   9 my $self = shift;
297 5         9 my $data = shift;
298 5         422 eval 'my ' . $data;
299             }
300              
301             sub _freeze
302             {
303 4     4   6 my $self = shift;
304 4         7 my $data = shift;
305 4         2924 require Data::Dumper;
306 4         7925 local $Data::Dumper::Indent = 0;
307 4         9 local $Data::Dumper::Purity = 1;
308 4         31 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 53 my $self = shift;
320 2         5 my %opts = @_;
321              
322 2         8 $self->_load_meta();
323 2 50       19 unless (exists $self->{meta}{$opts{tag}})
324             {
325 0         0 $self->{meta}{$opts{tag}} = {}
326             }
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 902 my $self = shift;
339 4         15 my %opts = @_;
340              
341             #my $fh = $self->_lock_meta('w');
342              
343 4         14 $self->_load_meta();
344 4 50       33 if ($opts{'reset'})
345             {
346 0         0 $self->{meta}{$opts{tag}} = {};
347             }
348              
349 2         10 $self->{meta}{$opts{tag}} = {
350 4         20 $self->{meta}{$opts{tag}} ? %{$self->{meta}{$opts{tag}}} : (),
351 4 100       21 $opts{meta} ? %{$opts{meta}} : (),
    50          
352             };
353 4         16 $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 2339 my $self = shift;
401 3         16 my %opts = @_;
402 3         24 my $dir = $self->_dir($opts{tag});
403              
404 3 50       20 return 0 unless $self->_is_filename_ok($opts{filename});
405              
406 3         18 my $dstfile = File::Spec->catdir($dir, $opts{filename});
407              
408 3         1392 mkpath(dirname($dstfile));
409             #my $rc = copy($opts{filehandle}, $dstfile);
410 3         7 my $rc = 0;
411             {
412 3         5 local $/ = \32768;
  3         14  
413 3         5 local $_;
414              
415 3 50       330 if (open my $dst_h, '>', $dstfile)
416             {
417 3         9 binmode $dst_h;
418 3         9 my $in_h = $opts{filehandle};
419 3         130 print $dst_h $_ while <$in_h>;
420 3         196 $rc = 1;
421             }
422             }
423              
424 3 100       106 chmod $opts{mode}, $dstfile if exists $opts{mode};
425 3 100 66     98 chown $opts{owner}, $opts{group}, $dstfile
426             if exists $opts{owner} and exists $opts{group};
427 3 50       9 if ($rc)
428             {
429 3         18 $self->_add_file(%opts);
430             }
431 3         414 $rc;
432             }
433              
434             =item retrieve_with_callback
435              
436             =cut
437              
438             sub retrieve_with_callback
439             {
440 2     2 1 106 my $self = shift;
441 2         8 my %opts = @_;
442              
443 2         6 my $callback = $opts{callback};
444 2         4 my @files_to_extract;
445              
446 2         9 my $repos_dir = $self->_dir($opts{tag});
447 2 50       67 return undef unless -d $repos_dir;
448              
449 2 100       7 if (exists $opts{files})
450             {
451 1 50       6 @files_to_extract = ref $opts{files} ? @{$opts{files}} : ($opts{files});
  1         12  
452             }
453             else
454             {
455 1         8 @files_to_extract = $self->list_files(%opts);
456             }
457              
458 2         6 foreach my $file (@files_to_extract)
459             {
460 3         31 my $srcname = File::Spec->catfile($repos_dir, $file);
461 3         15 my $s = stat($srcname);
462              
463 3 50       353 return 0 unless $s;
464              
465 3         54 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       113 if (-l $srcname)
473             {
474 0 0       0 $callback->(%cb_opts, target => readlink($srcname)) or return 0;
475             }
476             else
477             {
478 3 50       18 my $fh = IO::File->new($srcname, 'r') or return 0;
479 3         288 binmode $fh;
480              
481 3         4 my $buf;
482 3         45 while (my $r = sysread($fh, $buf, 32 * 1024))
483             {
484 3 50       14 $callback->(%cb_opts, data => $buf) or return 0;
485 3         21 delete $cb_opts{start};
486             }
487 3         5 $buf = undef;
488 3 50       10 $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 1988 my $self = shift;
501 1         4 my %opts = @_;
502              
503 1         4 my $repos_dir = $self->_dir($opts{tag});
504 1 50       33 return 0 unless -d $repos_dir;
505              
506 1         3 my @files;
507              
508 1 50       5 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         3 my $size;
518 1         3 my $dir = $self->_dir($opts{tag});
519 1         3 foreach my $f (@files)
520             {
521 2         3 my $s;
522 2         11 my $fullname = File::Spec->catdir($dir, $f);
523 2 50       55 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         4 $s -= $s % 1024;
533 2         3 $s += 1024;
534             }
535             }
536 2         5 $size += $s;
537             }
538 1         6 $size;
539             }
540              
541             =item list_files
542              
543             =cut
544              
545             sub list_files
546             {
547 3     3 1 435 my $self = shift;
548 3         8 my %opts = @_;
549              
550 3         9 my $dir = $self->_dir($opts{tag});
551 3         6 my @files;
552              
553             find(
554             {
555             wanted => sub {
556 10 100   10   700 return unless -f $File::Find::name;
557 5         14 my $name = substr(
558             $File::Find::name,
559             length($dir) + 1
560             );
561 5         64 push @files, $name;
562             },
563 3 50       361 no_chdir => 1,
564             },
565             $dir
566             ) if -d $dir;
567 3 50       28 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 430 my $self = shift;
579 1         2 my %opts = @_;
580              
581 1         6 my $r = $self->get_meta(tag=>'_r');
582 1         4 my @t = keys %$r;
583 1 50       7 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