File Coverage

blib/lib/File/System/Layered.pm
Criterion Covered Total %
statement 182 207 87.9
branch 24 46 52.1
condition 2 3 66.6
subroutine 32 34 94.1
pod 29 29 100.0
total 269 319 84.3


line stmt bran cond sub pod time code
1             package File::System::Layered;
2              
3 1     1   6 use strict;
  1         2  
  1         36  
4 1     1   5 use warnings;
  1         1  
  1         40  
5              
6 1     1   4 use base 'File::System::Object';
  1         2  
  1         93  
7              
8 1     1   5 use Carp;
  1         1  
  1         73  
9 1     1   5 use File::System;
  1         1  
  1         2046  
10              
11             our $VERSION = '1.16';
12              
13             =head1 NAME
14              
15             File::System::Layered - A file system implementation with "layered" roots
16              
17             =head1 SYNOPSIS
18              
19             use File::System;
20              
21             my $root = File::System->new('Layered',
22             [ 'Real', root => '/usr/local' ],
23             [ 'Real', root => '/usr' ],
24             [ 'Real', root => '/cw/usr/local' ],
25             [ 'Real', root => '/sw/usr/local' ],
26             );
27              
28             my $dir = $root->lookup('/bin');
29             print "All files:\n";
30             print map({ " - $_\n" } $root->children_paths);
31              
32             =head1 DESCRIPTION
33              
34             This file system allows for the layering of other file systems. A layerd file system contains one or more other file systems such that the list of files available at a certain path in the tree is the union of the files available in all the contained file systems. When reading from or writing to file content, the file system with the highest priority is given preference.
35              
36             The priority of the file systems is determined during construction, and may be modified later.
37              
38             =head2 LAYERED API
39              
40             The constructor of this module provides the initial layer prioritization. The C package also provides methods for altering the layers after the file system has been established.
41              
42             =over
43              
44             =item $root = File::System-Enew('Layered', @file_systems)
45              
46             The constructor establishes the initial layout of the file system. Each element of C<@file_systems> is either a file system object or is a reference to an array that may be passed to C to construct a file system object.
47              
48             The layers are prioritized by the order given in C<@file_systems>. The file systems listed first are given the higher priority.
49              
50             =cut
51              
52             sub new {
53 1     1 1 2 my $class = shift;
54              
55             @_
56 1 50       4 or croak "No file systems given.";
57              
58 1         3 my $self = bless { }, $class;
59              
60 1         4 $self->set_layers(@_);
61              
62 1         2 $self->{here} = $self->{layers}[0];
63              
64 1         3 return $self;
65             }
66              
67             =item @layers = $obj-Eget_layers
68              
69             Returns the list of the file system layers in descending order of priority. By using this method to get the list of layers, they can be reordered, removed, added to and then passed back to C to alter the file system.
70              
71             =cut
72              
73             sub get_layers {
74 0     0 1 0 my $self = shift;
75              
76 0         0 return @{ $self->{layers} };
  0         0  
77             }
78              
79             =item $obj-Eset_layers(@layers)
80              
81             Reset the layers of the file system in descending order of priority. This effectively reinitializes the file system. The semantics are the same as that of the constructor.
82              
83             =cut
84              
85             sub set_layers {
86 1     1 1 2 my $self = shift;
87              
88             @_
89 1 50       3 or croak "No file systems given.";
90              
91 1         4 my @layers;
92 1         3 for my $fs (@_) {
93            
94 2         2 my $init_fs;
95 2 50       8 if (UNIVERSAL::isa($fs, 'File::System::Object')) {
    0          
96 2         2 $init_fs = $fs;
97             } elsif (ref $fs eq 'ARRAY') {
98 0         0 $init_fs = File::System->new(@$fs);
99             } else {
100 0         0 croak "File system must be an array reference or an actual File::System::Object. '$fs' is neither of these. See the documentation of File::System::Layer for details.";
101             }
102              
103 2         4 push @layers, $init_fs;
104             }
105              
106 1         71 $self->{layers} = \@layers;
107              
108 1         2 return @layers;
109             }
110              
111             sub root {
112 12     12 1 24 my $self = shift;
113              
114 12         77 return bless {
115             here => $self->{layers}[0],
116             layers => $self->{layers},
117             }, ref $self;
118             }
119              
120             sub exists {
121 30     30 1 6632 my $self = shift;
122 30   66     82 my $path = shift || $self->path;
123              
124 30         31 for my $layer (@{ $self->{layers} }) {
  30         70  
125 40         127 my $res = $layer->exists($path);
126 40 100       197 return $res if $res;
127             }
128              
129 0         0 return '';
130             }
131              
132             sub lookup {
133 227     227 1 31430 my $self = shift;
134 227         702 my $path = $self->normalize_path(shift);
135              
136 227         308 for my $layer (@{ $self->{layers} }) {
  227         536  
137 291         836 my $res = $layer->lookup($path);
138 291 100       2161 return bless {
139             here => $res,
140             layers => $self->{layers},
141             }, ref $self if defined $res;
142             }
143              
144 0         0 return undef;
145             }
146              
147             sub glob {
148 88     88 1 102 my $self = shift;
149 88         240 my $glob = $self->normalize_path(shift);
150              
151 88         127 my %results;
152 88         87 for my $layer (reverse @{ $self->{layers} }) {
  88         205  
153 176         675 my @matches = $layer->glob($glob);
154 176         505 for my $match (@matches) {
155 96         257 $results{$match->path} = $match;
156             }
157             }
158              
159             return
160 88         353 map { bless { here => $_, layers => $self->{layers} }, ref $self }
  74         378  
161             sort values %results;
162             }
163              
164             sub find {
165 88     88 1 126 my $self = shift;
166 88         95 my $want = shift;
167              
168 88 100       178 if (@_) {
169 44         91 @_ = map { $self->normalize_path("$_") } @_;
  44         115  
170             } else {
171 44         132 @_ = ("$self");
172             }
173              
174 88         110 my %results;
175 88         108 for my $layer (reverse @{ $self->{layers} }) {
  88         180  
176 176         675 my @matches = $layer->find($want, @_);
177              
178 176         476 for my $match (@matches) {
179 96         267 $results{$match->path} = $match;
180             }
181             }
182              
183             return
184 88         380 map { bless { here => $_, layers => $self->{layers} }, ref $self }
  74         429  
185             sort values %results;
186             }
187              
188             sub is_creatable {
189 0     0 1 0 my $self = shift;
190 0         0 my $path = shift;
191 0         0 my $type = shift;
192              
193 0         0 for my $layer (@{ $self->{layers} }) {
  0         0  
194 0         0 my $res = $layer->is_creatable($path, $type);
195 0 0       0 return $res if $res;
196             }
197              
198 0         0 return '';
199             }
200              
201             sub create {
202 9     9 1 5124 my $self = shift;
203 9         17 my $path = shift;
204 9         17 my $type = shift;
205              
206 9 50       32 defined $path
207             or croak "No path argument given.";
208              
209 9 50       32 defined $type
210             or croak "No type argument given.";
211              
212 9         16 for my $layer (@{ $self->{layers} }) {
  9         28  
213 9 50       52 if ($layer->is_creatable($path, $type)) {
214 9         49 my $obj = $layer->create($path, $type);
215 9 50       31 if (defined $obj) {
216 9         64 return bless {
217             here => $obj,
218             layers => $self->{layers},
219             }, ref $self;
220             } else {
221 0         0 return undef;
222             }
223             }
224             }
225              
226 0         0 return undef;
227             }
228              
229             sub is_valid {
230 9     9 1 15 my $self = shift;
231              
232 9         20 for my $layer (@{ $self->{layers} }) {
  9         36  
233 18         67 my $obj = $layer->lookup($self->{here}->path);
234 18 50       72 next unless defined $obj;
235 0         0 my $res = $obj->is_valid;
236 0 0       0 return $res if $res;
237             }
238              
239 9         57 return '';
240             }
241              
242             sub properties {
243 185     185 1 7845 my $self = shift;
244              
245 185         224 my %result;
246 185         177 for my $layer (reverse @{ $self->{layers} }) {
  185         920  
247 370         875 my @props = $layer->properties;
248 370         708 for my $prop (@props) {
249 6290         9710 $result{$prop}++;
250             }
251             }
252              
253 185         3169 return sort keys %result;
254             }
255              
256             sub settable_properties {
257 26     26 1 49 my $self = shift;
258              
259 26         38 my %result;
260 26         39 for my $layer (reverse @{ $self->{layers} }) {
  26         69  
261 52         165 my @props = $layer->settable_properties;
262 52         94 for my $prop (@props) {
263 260         482 $result{$prop}++;
264             }
265             }
266              
267 26         289 return sort keys %result;
268             }
269              
270             sub get_property {
271 4401     4401 1 4983 my $self = shift;
272 4401         12604 return $self->{here}->get_property(@_);
273             }
274              
275             sub set_property {
276 78     78 1 101 my $self = shift;
277 78         311 $self->{here}->set_property(@_);
278             }
279              
280             sub rename {
281 18     18 1 24 my $self = shift;
282 18         70 $self->{here}->rename(@_);
283             }
284              
285             sub move {
286 18     18 1 32 my $self = shift;
287 18         23 my $to = shift;
288            
289 18         20 my $layer_to;
290 18 100       59 if (!$self->{here}->exists($to->path)) {
291 1 50       6 if ($self->{here}->is_creatable($to->path, 'd')) {
    0          
292 1         44 $layer_to = $self->{here}->create($to->path, 'd');
293             } elsif ($self->{here}->is_creatable($to->path, 'df')) {
294 0         0 $layer_to = $self->{here}->create($to->path, 'df');
295             } else {
296 0         0 croak "Move failed; no path '$to' exists in the same layer as $self.";
297             }
298             } else {
299 17         58 $layer_to = $self->{here}->lookup($to->path);
300             }
301              
302 18         97 $self->{here}->move($layer_to, @_);
303              
304 18         94 return $self;
305             }
306              
307             sub copy {
308 9     9 1 18 my $self = shift;
309 9         15 my $to = shift;
310              
311 9         11 my $layer_to;
312 9 50       29 if (!$self->{here}->exists($to->path)) {
313 0 0       0 if ($self->{here}->is_creatable($to->path, 'd')) {
    0          
314 0         0 $layer_to = $self->{here}->create($to->path, 'd');
315             } elsif ($self->{here}->is_creatable($to->path, 'df')) {
316 0         0 $layer_to = $self->{here}->create($to->path, 'df');
317             } else {
318 0         0 croak "Copy failed; no path '$to' exists in the same layer as $self.";
319             }
320             } else {
321 9         37 $layer_to = $self->{here}->lookup($to->path);
322             }
323              
324 9         55 return bless {
325             here => $self->{here}->copy($layer_to, @_),
326             layers => $self->{layers},
327             }, ref $self;
328             }
329              
330             sub remove {
331 18     18 1 3657 my $self = shift;
332 18         64 $self->{here}->remove(@_);
333             }
334              
335             my @delegates = qw/
336             is_readable
337             is_seekable
338             is_writable
339             is_appendable
340             open
341             content
342             /;
343              
344             for my $name (@delegates) {
345 64     64 1 88 eval q(
  64     16 1 192  
  16     16 1 26  
  16     16 1 71  
  16     16 1 29  
  16     48 1 79  
  16         33  
  16         63  
  16         27  
  16         70  
  48         271  
  48         164  
346             sub ).$name.q( {
347             my $self = shift;
348             return $self->{here}->).$name.q((@_);
349             }
350             );
351              
352             die $@ if $@;
353             }
354              
355             sub has_children {
356 10     10 1 16 my $self = shift;
357              
358 10         27 my $path = $self->path;
359             my @layers
360 20         46 = grep { defined }
  20         56  
361 10         24 map { $_->lookup($path) }
362 10         17 reverse @{ $self->{layers} };
363              
364 10         23 for my $layer (@layers) {
365 10         28 my $res = $layer->has_children;
366 10 100       47 return $res if $res;
367             }
368              
369 4         20 return '';
370             }
371              
372             sub children_paths {
373 10     10 1 14 my $self = shift;
374              
375 10         24 my $path = $self->path;
376              
377 10         15 my %results;
378             my @layers
379 20         52 = grep { defined }
  20         56  
380 10         26 map { $_->lookup($path) }
381 10         13 reverse @{ $self->{layers} };
382              
383 10         23 for my $layer (@layers) {
384 14         48 my @paths = $layer->children_paths;
385 14         29 for my $path (@paths) {
386 38         88 $results{$path}++;
387             }
388             }
389              
390 10         102 return sort keys %results;
391             }
392              
393             sub children {
394 10     10 1 15 my $self = shift;
395              
396 10         31 my $path = $self->path;
397              
398 10         12 my %results;
399             my @layers
400 20         61 = grep { defined }
  20         55  
401 10         21 map { $_->lookup($path) }
402 10         19 reverse @{ $self->{layers} };
403              
404 10         19 for my $layer (@layers) {
405 14         46 my @children = $layer->children;
406 14         33 for my $child (@children) {
407 10         27 $results{$child->path} = $child;
408             }
409             }
410              
411 10         52 return map { bless { here => $_, layers => $self->{layers} }, ref $self }
  9         69  
412             sort values %results;
413             }
414              
415             sub child {
416 26     26 1 271 my $self = shift;
417 26         75 my $path = $self->normalize_path(shift);
418              
419 26         29 my $child;
420 26         33 for my $layer (@{ $self->{layers} }) {
  26         54  
421 35         95 $child = $layer->lookup($path);
422 35 100       109 last if defined $child;
423             }
424              
425 26 100       47 if (defined $child) {
426 25         133 return bless {
427             here => $child,
428             layers => $self->{layers},
429             }, ref $self;
430             } else {
431 1         5 return undef;
432             }
433             }
434              
435             =back
436              
437             =head1 BUGS
438              
439             This list includes things that aren't always bugs, but eccentricities of the implementation forced by the the nature of the service provided. This provides an explanation for anything that might not be obvious. I've tried to make the implementations work in a simple and natural way, but a few decisions were arbitrary.
440              
441             The C, C, and C methods are stuck within the file system they are in. That is, if you move, rename, or copy a file, the new file, location, or duplicate will be stored within the same layer as the original. If you attempt to move or copy to a location that exists in one layer, but not another, those methods will attempt to use C to create the needed directory in the other layer. Due to these kinds of complications, these methods haven't yet been fully tested.
442              
443             Removing a file or directory might not have the expected effect. If there are two layers with the same file or directory, removal will just remove the version in the highest layer, so the file or directory will still appear to exist.
444              
445             The C method returns true if I layer returns true. The C method uses the C of each layer to find out if the file can be created and will create the file on the first layer it finds where it is true.
446              
447             The C and C methods rely upon the slowish defaults. This situation could probably be improved with a little bit of effort.
448              
449             =head1 SEE ALSO
450              
451             L, L, L, L
452              
453             =head1 AUTHOR
454              
455             Andrew Sterling Hanenkamp, Ehanenkamp@users.sourceforge.netE
456              
457             =head1 COPYRIGHT AND LICENSE
458              
459             Copyright 2005 Andrew Sterling Hanenkamp. All Rights Reserved.
460              
461             This library is distributed and licensed under the same terms as Perl itself.
462              
463             =cut
464              
465             1