File Coverage

blib/lib/DataStore/CAS/FS.pm
Criterion Covered Total %
statement 375 429 87.4
branch 148 236 62.7
condition 88 176 50.0
subroutine 64 78 82.0
pod 21 23 91.3
total 696 942 73.8


line stmt bran cond sub pod time code
1             package DataStore::CAS::FS;
2 4     4   67728 use 5.008;
  4         10  
3 4     4   430 use Moo 1.000007;
  4         9487  
  4         20  
4 4     4   1642 use Carp;
  4         13  
  4         209  
5 4     4   402 use Try::Tiny 0.11;
  4         975  
  4         182  
6 4     4   17 use File::Spec 3.33;
  4         60  
  4         79  
7 4     4   452 use DataStore::CAS 0.02;
  4         20503  
  4         10459  
8              
9             our $VERSION= '0.011000';
10              
11             require DataStore::CAS::FS::Dir;
12             require DataStore::CAS::FS::DirCodec::Universal;
13             require DataStore::CAS::FS::DirCodec::Minimal;
14             require DataStore::CAS::FS::DirCodec::Unix;
15              
16             # ABSTRACT: Virtual Filesystem backed by Content-Addressable Storage
17              
18              
19             has store => ( is => 'ro', required => 1, isa => \&_validate_cas );
20             has root_entry => ( is => 'rwp', required => 1 );
21             has case_insensitive => ( is => 'ro', default => sub { 0 } );
22              
23 2     2 1 45 sub hash_of_null { $_[0]->store->hash_of_null }
24             has hash_of_empty_dir => ( is => 'lazy' );
25              
26             has dir_cache => ( is => 'rw', default => sub { DataStore::CAS::FS::DirCache->new() } );
27              
28             # _nodes is a tree of nodes, each of the form:
29             # $node= {
30             # dirent => $Dir_Entry, # mandatory
31             # dir => $CAS_FS_Dir, # optional, created on demand
32             # subtree => {
33             # KEY1 => $node1,
34             # KEY2 => $node2,
35             # ...
36             # }
37             # changed => 1 # set if a path override has happened here, or in any child node
38             # invalid => 1 # set whenever a path override deletes this node
39             # }
40             #
41             # If 'case_insensitive' is true, the keys will all be upper-case, but the $Dir_Entry
42             # objects will contain the correct-case name.
43             #
44             has _nodes => ( is => 'rw' );
45              
46              
47             sub _build_hash_of_empty_dir {
48 2     2   10 my $self= shift;
49 2         12 my $empty= DataStore::CAS::FS::DirCodec::Minimal->encode([],{});
50 2         12 return $self->store->put_scalar($empty);
51             }
52              
53             sub _validate_cas {
54 7     7   67 my $cas= shift;
55 7 50 33     179 ref($cas) && ref($cas)->can('get') && ref($cas)->can('put')
      33        
56             or croak "Invalid CAS object: $cas"
57             };
58              
59             sub BUILDARGS {
60 7     7 0 15636 my $class= shift;
61 7 50 33     40 my %p= (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_;
  0         0  
62             # Root is an alias for root_entry
63 7 50       18 if (defined $p{root}) {
64             defined $p{root_entry}
65 7 50       17 and croak "Specify only one of 'root' or 'root_entry'";
66 7         14 $p{root_entry}= delete $p{root};
67             }
68 7         98 return \%p;
69             }
70              
71             sub BUILD {
72 7     7 0 52 my ($self, $args)= @_;
73 7         19 my @invalid= grep { !$self->can($_) } keys %$args;
  14         44  
74 7 50       19 croak "Invalid param(s): ".join(', ', @invalid)
75             if @invalid;
76              
77 7 50 33     57 croak "Missing/Invalid parameter 'dir_cache'"
78             unless defined $self->dir_cache and $self->dir_cache->can('clear');
79              
80             # coerce root_entry to an actual DirEnt object
81 7         14 my $root= $self->root_entry;
82 7 50       16 defined $root
83             or croak "root_entry is required";
84 7 100 66     55 unless (ref $root && ref($root)->isa('DataStore::CAS::FS::DirEnt')) {
85 2 0       44 $self->_set_root_entry(
    50          
    50          
86             DataStore::CAS::FS::DirEnt->new({
87             type => 'dir',
88             name => '',
89             # Assume scalars are digest_hash values.
90             !ref $root? ( ref => $root )
91             # Hashrefs might be empty, to indicate an empty directory
92             : ref $root eq 'HASH'? ( ref => $self->hash_of_empty_dir, %$root )
93             # Is it a ::File or ::Dir object?
94             : ref($root)->can('hash')? ( ref => $root->hash )
95             # Else take a guess that it is a digest_hash wrapped in an object
96             : ( ref => "$root" )
97             })
98             );
99             }
100 7 50 33     210 croak "Invalid parameter 'root_entry'"
      33        
      33        
101             unless ref $self->root_entry
102             and ref($self->root_entry)->can('type')
103             and $self->root_entry->type eq 'dir'
104             and defined $self->root_entry->ref;
105             # If they gave us a 'root_entry', make sure we can load it
106 7 50       158 $self->get_dir($self->root_entry->ref)
107             or croak "Unable to load root directory '".$self->root_entry->ref."'";
108             }
109              
110              
111             sub get {
112 2     2 1 10 (shift)->store->get(@_);
113             }
114              
115              
116             sub get_dir {
117 124     124 1 511 my ($self, $hash_or_file, $flags)= @_;
118 124 50 33     362 my ($hash, $file)= (ref $hash_or_file and $hash_or_file->can('hash'))
119             ? ( $hash_or_file->hash, $hash_or_file )
120             : ( $hash_or_file, undef );
121            
122 124         254 my $dir= $self->dir_cache->get($hash);
123 124 100       360 return $dir if defined $dir;
124            
125             # Return undef if the directory doesn't exist.
126             return undef
127 48 50 33     174 unless defined ($file ||= $self->store->get($hash));
128            
129             # Deserialize directory. This can throw exceptions if it isn't a valid encoding.
130 48         619 $dir= DataStore::CAS::FS::DirCodec->load($file);
131             # Cache it
132 48         204 $self->dir_cache->put($dir);
133 48         388 return $dir;
134             }
135              
136              
137 0     0 1 0 sub put { (shift)->store->put(@_) }
138 0     0 1 0 sub put_scalar { (shift)->store->put_scalar(@_) }
139 0     0 1 0 sub put_file { (shift)->store->put_file(@_) }
140 0     0 1 0 sub put_handle { (shift)->store->put_handle(@_) }
141 0     0 1 0 sub validate { (shift)->store->validate(@_) }
142              
143              
144             sub path {
145 10     10 1 2007 bless { filesystem => (shift), path_names => [ map { File::Spec->splitdir($_) } @_ ] },
  18         119  
146             'DataStore::CAS::FS::Path';
147             }
148              
149              
150             sub path_if_exists {
151 2     2 1 625 my $self= shift;
152 2         6 my $path= $self->path(@_);
153 2 100       7 $path->resolve({no_die => 1})? $path : undef;
154             }
155              
156              
157             sub tree_iterator {
158 3     3 1 1112 my $self= shift;
159 3 50 33     15 my %p= (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_;
  0         0  
160 3         19 return DataStore::CAS::FS::TreeIterator->new(
161             path => [],
162             %p,
163             fs => $self
164             );
165             }
166              
167              
168              
169             sub resolve_path {
170 21     21 1 368 my ($self, $path, $flags)= @_;
171 21   50     74 $flags ||= {};
172            
173 21         67 my $ret= $self->_resolve_path(undef, $path, { follow_symlinks => 1, %$flags });
174            
175             # Array means success, scalar means error.
176 21 50       50 if (ref($ret) eq 'ARRAY') {
177             # The user wants directory entries, not "nodes".
178 21         77 $_= $_->{dirent} for @$ret;
179 21         384 return $ret;
180             }
181              
182             # else, got an error...
183 0         0 ${$flags->{error_out}}= $ret
184 0 0       0 if ref $flags->{error_out};
185 0 0       0 croak $ret unless $flags->{no_die};
186 0         0 return undef;
187             }
188              
189             sub _resolve_path {
190 241     241   280 my ($self, $nodes, $path_names, $flags)= @_;
191              
192 241 100       779 my @path= ref($path_names)? @$path_names : File::Spec->splitdir($path_names);
193 241   100     407 $nodes ||= [];
194 241 100 100     542 push @$nodes, ($self->{_nodes} ||= { dirent => $self->root_entry })
195             unless @$nodes;
196            
197 241         164 my $mkdir_defaults;
198             sub _build_mkdir_defaults {
199 3     3   3 my $flags= shift;
200 0         0 my @ret= %{$flags->{mkdir_defaults}}
201 3 50       7 if defined $flags->{mkdir_defaults};
202 3         6 push @ret, type => 'dir', ref => undef;
203             \@ret
204 3         16 }
205              
206 241         342 while (@path) {
207 761         765 my $ent= $nodes->[-1]{dirent};
208 761         488 my $dir;
209              
210             # Support for "symlink" is always UNIX-based (or compatible)
211             # As support for other systems' symbolic paths are added, they
212             # will be given unique '->type' values, and appropriate handling.
213 761 50 66     14636 if ($ent->type eq 'symlink' and $flags->{follow_symlinks}) {
214             # Sanity check on symlink entry
215 4         72 my $target= $ent->ref;
216 4 50 33     24 defined $target and length $target
217             or return 'Invalid symbolic link "'.$ent->name.'"';
218              
219 4         15 unshift @path, split('/', $target, -1);
220 4         8 pop @$nodes;
221            
222             # If an absolute link, we start over from the root
223 4 50       10 @$nodes= ( $nodes->[0] )
224             if $path[0] eq '';
225              
226 4         11 next;
227             }
228              
229 757 50       13611 if ($ent->type ne 'dir') {
230             return 'Cannot descend into directory entry "'.$ent->name.'" of type "'.$ent->type.'"'
231 0 0 0     0 unless ($flags->{mkdir}||0) > 1;
232             # Here, mkdir flag converts entry into a directory
233 0   0     0 $nodes->[-1]{dirent}= $ent->clone(@{ $mkdir_defaults ||= _build_mkdir_defaults($flags)});
  0         0  
234             }
235              
236             # Get the next path component, ignoring empty and '.'
237 757         1101 my $name= shift @path;
238 757 100 66     4502 next unless defined $name and length $name and ($name ne '.');
      100        
239              
240             # We handle '..' procedurally, moving up one real directory and *not* backing out of a symlink.
241             # This is the same way the kernel does it, but perhaps shell behavior is preferred...
242 638 100       953 if ($name eq '..') {
243 9 50       17 return "Cannot access '..' at root directory"
244             unless @$nodes > 1;
245 9         10 pop @$nodes;
246 9         23 next;
247             }
248              
249             # If this directory has an in-memory override for this name, use it
250 629         453 my $subnode;
251 629 100       968 if ($nodes->[-1]{subtree}) {
252 516 50       971 my $key= $self->case_insensitive? uc $name : $name;
253 516         696 $subnode= $nodes->[-1]{subtree}{$key};
254             }
255              
256             # Else we need to find the name within the current directory
257 629 100 100     2578 if (!defined $subnode && (defined $nodes->[-1]{dir} || defined $ent->ref)) {
      66        
258             # load it if it isn't cached
259 196 50 66     1591 ($nodes->[-1]{dir} ||= $self->get_dir($ent->ref))
260             or return 'Failed to open directory "'.$ent->name.' ('.$ent->ref.')"';
261              
262             # See if the directory contains this entry
263 196 100       416 if (defined (my $subent= $nodes->[-1]{dir}->get_entry($name))) {
264 190         244 $subnode= { dirent => $subent };
265 190 50       347 my $key= $self->case_insensitive? uc $name : $name;
266             # Weak reference, until _apply_overrides is called.
267 190         559 Scalar::Util::weaken( $nodes->[-1]{subtree}{$key}= $subnode );
268             }
269             }
270              
271             # If we haven't found one, or if it is 0 (deleted), either create or die.
272 629 100       867 if (!$subnode) {
273             # If we're supposed to create virtual entries, do so
274 15 100 66     41 if ($flags->{mkdir} or $flags->{partial}) {
275             $subnode= {
276             invalid => 1, # not valid until _apply_overrides
277             dirent => DataStore::CAS::FS::DirEnt->new(
278             name => $name,
279             # It is a directory if there are more path components to resolve.
280 12 100 66     31 (@path? @{ $mkdir_defaults ||= _build_mkdir_defaults($flags)} : ())
  6         14  
281             )
282             };
283             }
284             # Else it doesn't exist and we fail.
285             else {
286 3         7 my $dir_path= File::Spec->catdir(map { $_->{dirent}->name } @$nodes);
  4         78  
287             return "Directory \"$dir_path\" is not present in storage"
288 3 50       10 unless defined $nodes->[-1]{dir};
289 3         13 return "No such directory entry \"$name\" at \"$dir_path\"";
290             }
291             }
292              
293 626         1456 push @$nodes, $subnode;
294             }
295            
296 238         356 $nodes;
297             }
298              
299              
300             sub get_dir_entries {
301 10     10 1 11 my ($self, $path)= @_;
302 10         33 my $nodes= $self->_resolve_path(undef, $path);
303 10 50       22 ref $nodes
304             or croak $nodes;
305 10         20 return $self->_get_dir_entries($nodes->[-1]);
306             }
307              
308             sub readdir {
309 10     10 1 352 my $self= shift;
310 10         11 my @names= map { $_->name } @{ $self->get_dir_entries(@_) };
  17         382  
  10         20  
311 10 50       67 return wantarray? @names : \@names;
312             }
313              
314             # This method combines the original directory with its overrides.
315             sub _get_dir_entries {
316 52     52   49 my ($self, $node)= @_;
317 52         55 my $ent= $node->{dirent};
318 52 50       936 croak "Can't get listing for non-directory"
319             unless $ent->type eq 'dir';
320 52         59 my %dirents;
321             # load dir if it isn't cached
322 52 100 100     917 if (!defined $node->{dir} && defined $ent->ref) {
323 45 50       782 defined ( $node->{dir}= $self->get_dir($ent->ref) )
324             or return 'Failed to open directory "'.$ent->name.' ('.$ent->ref.')"';
325             }
326 52         93 my $caseless= $self->case_insensitive;
327 52 100       88 if (defined $node->{dir}) {
328 47         102 my $iter= $node->{dir}->iterator;
329 47         46 my $dirent;
330 47 50       68 $dirents{$caseless? uc($dirent->name) : $dirent->name}= $dirent
331             while defined ($dirent= $iter->());
332             }
333 52 100       93 if (my $t= $node->{subtree}) {
334 6         15 for (keys %$t) {
335 8         7 my $subnode= $t->{$_};
336 8 50       14 next unless defined $subnode;
337 8 50 66     23 die "BUG" if ref $subnode && $subnode->{invalid};
338 8 100       20 if (ref $subnode) {
339             $dirents{$_}= $subnode->{dirent}
340 7 50       17 if $subnode->{changed};
341             } else {
342 1         3 delete $dirents{$_};
343             }
344             }
345             }
346 52         158 return [ map { $dirents{$_} } sort keys %dirents ];
  109         230  
347             }
348              
349              
350             sub set_path {
351 4     4 1 109 my ($self, $path, $newent, $flags)= @_;
352 4   50     22 $flags ||= {};
353 4         21 my $nodes= $self->_resolve_path(undef, $path, { follow_symlinks => 1, partial => 1, %$flags });
354 4 50       13 croak $nodes unless ref $nodes;
355             # replace the final entry, after applying defaults
356 4 100       9 if (!$newent) {
357             # unlink request. Ignore if node didn't exist.
358 1 50       3 return if $nodes->[-1]{invalid};
359              
360             # Can't unlink the root node
361 1 50       4 croak "Can't unlink root node"
362             unless @$nodes > 1;
363              
364 1         2 $nodes->[-1]{invalid}= 1;
365             # Recursively invalidate all nodes beneath this one
366 1         3 &_invalidate_subtree for ($nodes->[-1]);
367              
368             # Mark in prev node that this item is gone
369 1 50       24 my $key= $self->case_insensitive? uc $nodes->[-1]{dirent}->name : $nodes->[-1]{dirent}->name;
370 1         2 pop @$nodes;
371 1         3 $nodes->[-1]{subtree}{$key}= 0;
372             } else {
373 3 0 33     11 if (ref $newent eq 'HASH' or !defined $newent->name or !defined $newent->type) {
      33        
374 3 50       3 my %ent_hash= %{ref $newent eq 'HASH'? $newent : $newent->as_hash};
  3         17  
375             $ent_hash{name}= $nodes->[-1]{dirent}->name
376 3 100       64 unless defined $ent_hash{name};
377             defined $ent_hash{name} && length $ent_hash{name}
378 3 50 33     15 or die "No name for new dir entry";
379             $ent_hash{type}= $nodes->[-1]{dirent}->type || 'file'
380 3 100 50     44 unless defined $ent_hash{type};
381 3         10 $newent= DataStore::CAS::FS::DirEnt->new(\%ent_hash);
382             }
383 3         4 $nodes->[-1]{dirent}= $newent;
384 3         9 delete $nodes->[-1]{dir};
385             # Recursively invalidate all nodes beneath this one
386 3         9 &_invalidate_subtree for ($nodes->[-1]);
387             }
388             # Now connect nodes with strong references, and mark as changed
389 4         13 $self->_apply_overrides($nodes);
390             }
391             sub _invalidate_subtree {
392 23 100   23   47 if ($_->{subtree}) {
393 10   33     8 ++$_->{invalid} && &_invalidate_subtree for grep { ref $_ } values %{delete $_->{subtree}};
  11         34  
  10         15  
394             }
395             }
396              
397              
398             sub update_path {
399 2     2 1 313 my ($self, $path, $changes, $flags)= @_;
400 2   50     11 $flags ||= {};
401 2         9 my $nodes= $self->_resolve_path(undef, $path, { follow_symlinks => 1, partial => 1, %$flags });
402 2 50       7 croak $nodes unless ref $nodes;
403              
404             # update the final entry, after applying defaults
405 2         4 my $entref= \$nodes->[-1]{dirent};
406 2 100 66     39 my $old_dir_ref= defined $$entref->type && $$entref->type eq 'dir'? $$entref->ref : undef;
407 2 100       37 $$entref= $$entref->clone(
    0          
    50          
408             (defined $$entref->type? () : ( type => 'file' )),
409             ref $changes eq 'HASH'? %$changes
410             : ref $changes eq 'ARRAY'? @$changes
411             : croak 'parameter "changes" must be a hashref or arrayref'
412             );
413 2 100       40 my $new_dir_ref= $$entref->type eq 'dir'? $$entref->ref : undef;
414              
415             # If we changed the type of a directory, or changed which digest_hash it
416             # refers to, then we should clear the subtree under this node.
417 2 50 100     18 if (($old_dir_ref || '') ne ($new_dir_ref || '') && $nodes->[-1]{subtree}) {
      100        
      66        
418             # Recursively invalidate all nodes beneath this one
419 0         0 &_invalidate_subtree for ($nodes->[-1]);
420 0         0 delete $nodes->[-1]{dir};
421             }
422              
423 2         19 $self->_apply_overrides($nodes);
424             }
425              
426             sub _apply_overrides {
427 9     9   12 my ($self, $nodes)= @_;
428             # Ensure that each node is connected to the previous via 'subtree'.
429             # When we find the first changed node, we assume the rest are connected.
430 9         7 my $prev;
431 9         14 for (reverse @$nodes) {
432 25 100       35 if ($prev) {
433 16 50       312 my $key= $self->case_insensitive? uc($prev->{dirent}->name) : $prev->{dirent}->name;
434 16         32 $_->{subtree}{$key}= $prev;
435             }
436 25 100 66     61 last if $_->{changed} && !$_->{invalid};
437 21         22 delete $_->{invalid};
438 21         18 $_->{changed}= 1;
439 21         21 $prev= $_;
440             }
441             # Finally, make sure the root override is set
442 9         13 $self->{_nodes}= $nodes->[0];
443 9         23 1;
444             }
445              
446              
447             sub mkdir {
448 4     4 1 414 my ($self, $path)= @_;
449 4         13 my $nodes= $self->_resolve_path(undef, $path, { follow_symlinks => 1, mkdir => 1 });
450 4 50       12 croak $nodes unless ref $nodes;
451 4 100       72 unless (defined $nodes->[-1]{dirent}->type) {
452 3         8 $nodes->[-1]{dirent}= $nodes->[-1]{dirent}->clone(type => 'dir');
453 3         11 $self->_apply_overrides($nodes);
454             }
455 4         8 1;
456             }
457              
458              
459             sub touch {
460 1     1 1 440 my ($self, $path)= @_;
461 1         7 $self->update_path($path, { mtime => time() });
462             }
463              
464              
465             sub unlink {
466 1     1 1 2 my ($self, $path)= @_;
467 1         3 $self->set_path($path, undef);
468             }
469             *rmdir = *unlink;
470              
471             # TODO: write copy and move and rename
472              
473              
474             sub rollback {
475 0     0 1 0 my $self= shift;
476 0 0 0     0 if ($self->{_nodes} && $self->{_nodes}{changed}) {
477 0         0 &invalidate_node for ($self->{_nodes});
478 0         0 $self->{_nodes}= undef;
479             }
480 0         0 1;
481             }
482              
483              
484             sub commit {
485 2     2 1 4 my $self= shift;
486 2 50 33     21 if ($self->_nodes && $self->_nodes->{changed}) {
487 2         4 my $root_node= $self->_nodes;
488             croak "Root override must be a directory"
489 2 50       51 unless $root_node->{dirent}->type eq 'dir';
490 2         5 my $hash= $self->_commit_recursive($root_node);
491 2         81 $self->{root_entry}= $root_node->{dirent}->clone(ref => $hash);
492 2         5 $self->{_nodes}= undef;
493             }
494 2         8 1;
495             }
496              
497             # Takes a subtree of the datastructure generated by apply_path and encodes it
498             # as a directory, recursively encoding any subtrees first, then returns the
499             # hash of that subdir.
500             sub _commit_recursive {
501 10     10   9 my ($self, $node)= @_;
502              
503 10         10 my %changes;
504             my @entries;
505 10 50       16 if (my $subtree= $node->{subtree}) {
506 10         8 while (my ($k, $v)= each %{$node->{subtree}}) {
  21         67  
507             $changes{$k}= $v
508 11 50 33     56 if defined $v && ($v eq 0 || $v->{changed});
      66        
509             }
510             }
511            
512             # If no changes, return original directory (if it exists)
513             return $node->{dirent}->ref
514 10 100 66     37 if !%changes && defined $node->{dirent}->ref;
515            
516             # Walk the directory entries and filter out any that have been overridden.
517 9 100 66     105 if (defined $node->{dir} || defined $node->{dirent}->ref) {
518             ($node->{dir} ||= $self->get_dir($node->{dirent}->ref))
519 4 50 33     11 or croak 'Failed to open directory "'.$node->{dirent}->name.' ('.$node->{dirent}->ref.')"';
520            
521 4         4 my ($iter, $ent);
522 4         6 my $caseless= $self->case_insensitive;
523 4         8 for ($iter= $node->{dir}->iterator; defined ($ent= $iter->()); ) {
524 12 50       221 push @entries, $ent unless $changes{$caseless? uc($ent->name) : $ent->name};
    100          
525             }
526             }
527              
528             # Now append the modified entries.
529             # Skip the "0"s, which represent files to unlink.
530 9         12 for (grep { ref $_ } values %changes) {
  10         18  
531             # Check if node is a dir and needs committed
532 10 50 66     167 if ($_->{subtree} and $_->{dirent}->type eq 'dir' and $_->{changed}) {
      66        
533 8         32 my $hash= $self->_commit_recursive($_);
534 8         3387 $_->{dirent}= $_->{dirent}->clone( ref => $hash );
535             }
536            
537 10         22 push @entries, $_->{dirent};
538             }
539              
540             # Invalidate all children of this node
541 9         17 &_invalidate_subtree for ($node);
542            
543             # Now re-encode the directory, using the same type as orig_dir
544 9 50       15 return $self->hash_of_empty_dir
545             unless @entries;
546             my $format= $node->{dir}->format
547 9 100       19 if $node->{dir};
548 9 100       19 $format= 'universal' unless defined $format;
549 9         35 return DataStore::CAS::FS::DirCodec->put($self->store, $format, \@entries, {});
550             }
551              
552             package DataStore::CAS::FS::Path;
553 4     4   22 use strict;
  4         4  
  4         78  
554 4     4   13 use warnings;
  4         5  
  4         3140  
555              
556              
557             # main attributes
558 5     5   15 sub path_names { $_[0]{path_names} }
559 7     7   24 sub filesystem { $_[0]{filesystem} }
560             #sub _node_path { $_[0]{_node_path} }
561              
562             # convenience accessors
563 0     0   0 sub path_name_list { @{$_[0]->path_names} }
  0         0  
564 13     13   11 sub path_dirent_list { map { $_->{dirent} } @{$_[0]->resolve} }
  67         76  
  13         14  
565 13     13   44 sub path_dirents { [ $_[0]->path_dirent_list ] }
566 5     5   12 sub dirent { $_[0]->resolve->[-1]{dirent} }
567 98     98   132 sub type { $_[0]->resolve->[-1]{dirent}->type }
568 0     0   0 sub name { $_[0]->resolve->[-1]{dirent}->name }
569 0     0   0 sub depth { -1 + @{$_[0]->resolve} }
  0         0  
570              
571              
572             sub canonical_path {
573 0     0   0 my $self= shift;
574 0   0     0 $self->{canonical_path} ||= do {
575 0         0 my $name= $self->path_names;
576 0 0       0 my $path= '/'.join('/', grep { length && $_ ne '.' } @$name);
  0         0  
577 0 0 0     0 $path .= '/' if $name->[-1] eq '' || $name->[-1] eq '.';
578 0         0 $path =~ s|//+|/|g;
579 0         0 $path;
580             };
581             }
582              
583              
584             sub resolved_canonical_path {
585 92     92   273 my $x= $_[0]->resolve;
586             # ignore name of root entry
587 92         204 return '/'.join('/', map { $_->{dirent}->name } @$x[1..$#$x]);
  399         7169  
588             }
589              
590              
591             sub resolve {
592             # See if we can re-use the previous result...
593 214     214   228 my $nodes= $_[0]{_node_path};
594 214 100 100     914 return $nodes if $nodes && ref $nodes->[-1] && !$nodes->[-1]{invalid};
      66        
595             # Only re-resolve the nodes which have been invalidated.
596             # This is part of an optimization to create half-resolved path objects.
597 103         75 my ($self, $flags)= @_;
598 103         78 my (@valid_nodes, $sub_path);
599 103 100       121 if ($nodes) {
600 2         3 for (@$nodes) {
601 4 100 66     17 last if !ref $_ || $_->{invalid};
602 2         3 push @valid_nodes, $_;
603             }
604 2 50       5 $sub_path= [ map { ref $_? $_->{dirent}->name : $_ } @{$nodes}[ scalar(@valid_nodes) .. $#$nodes ] ];
  2         13  
  2         3  
605             } else {
606 101         88 $sub_path= $self->{path_names};
607             }
608 103 100       172 $flags= { follow_symlinks => 1, $flags? %$flags : () };
609 103         191 $nodes= $self->{filesystem}->_resolve_path(\@valid_nodes, $sub_path, $flags);
610 103 100       173 if (ref $nodes) {
611 100         2059 return ($self->{_node_path}= $nodes);
612             } else {
613             # else, got an error...
614 0         0 ${$flags->{error_out}}= $nodes
615 3 50       8 if ref $flags->{error_out};
616 3 50       6 Carp::croak $nodes unless $flags->{no_die};
617 3         23 return undef;
618             }
619             }
620              
621              
622             sub path {
623 6     6   7 my $self= shift;
624 6         9 my @sub_names= map { File::Spec->splitdir($_) } @_;
  11         35  
625             bless {
626             filesystem => $self->{filesystem},
627 6         32 path_names => [ @{$self->{path_names}}, @sub_names ],
628 6 100       11 $self->{_node_path}? (_node_path => [ @{$self->{_node_path}}, @sub_names ]) : (),
  2         19  
629             }, ref $self;
630             }
631              
632             sub path_if_exists {
633 3     3   17 my $self= shift;
634 3         7 my $path= $self->path(@_);
635 3 100       9 $path->resolve({no_die => 1})? $path : undef;
636             }
637              
638              
639             sub mkdir {
640 1     1   2 my $self= shift;
641 1         3 $self->{filesystem}->mkdir($self->path_names, @_);
642 1         2 $self;
643             }
644              
645              
646             sub file {
647 2   33 2   8 $_[0]{file} ||= do {
648 2         4 my $ent= $_[0]->dirent;
649 2 50       39 $ent->type eq 'file' or Carp::croak "Path is not a file";
650 2 50       36 defined (my $hash= $ent->ref) or Carp::croak "File was not stored in CAS";
651 2         5 $_[0]->filesystem->get($hash);
652             };
653             }
654              
655             sub open {
656 2     2   15 $_[0]->file->open
657             }
658              
659              
660             sub dir {
661 0   0 0   0 $_[0]{dir} ||= do {
662 0         0 my $ent= $_[0]->dirent;
663 0 0       0 $ent->type eq 'dir' or Carp::croak "Path is not a directory";
664 0 0       0 defined (my $hash= $ent->ref) or Carp::croak "Directory was not stored in CAS";
665 0         0 $_[0]->filesystem->get_dir($hash);
666             }
667             }
668              
669              
670             sub readdir {
671 2     2   9 $_[0]{filesystem}->readdir($_[0]->path_names)
672             }
673              
674              
675             sub tree_iterator {
676 1     1   2 my $self= shift;
677 1 50 33     6 my %p= (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_;
  0         0  
678 1         4 $self->filesystem->tree_iterator(%p, path => $self->path_names);
679             }
680              
681             package DataStore::CAS::FS::TreeIterator;
682 4     4   18 use strict;
  4         21  
  4         61  
683 4     4   12 use warnings;
  4         7  
  4         76  
684 4     4   12 use Carp;
  4         5  
  4         1968  
685              
686              
687 0     0   0 sub _fields { $_[0]->($_[0]) }
688              
689             sub new {
690 3     3   6 my $class= shift;
691 3 50 33     14 my $self= bless { (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_ }, $class;
  0         0  
692             defined $self->{$_} || croak "'$_' is required"
693 3   33     23 for qw( path fs );
694 3         6 $self->{path_nodes}= \my @nodes;
695 3         5 $self->{dirstack}= \my @dirstack;
696 3         5 $self->{names}= \my @names;
697 3         4 $self->{filterref}= \my $filter;
698 3         4 $filter= delete $self->{filter};
699 3         3 my $fs= $self->{fs};
700 3         6 $self->_init;
701             return bless sub {
702 101 100 66 101   430 return $self if @_ && ref($_[0]) eq $class;
703 97 50       163 return undef unless @dirstack;
704             # back out of a directory hierarchy that we have finished
705 97         76 while (!@{$dirstack[-1]}) {
  147         229  
706 55         39 pop @dirstack; # back out of directory
707 55         41 pop @nodes;
708 55         62 pop @names;
709 55 100       91 return undef unless @dirstack;
710             }
711             # Iterate path leaf, by removing last leaf, and resolving using the
712             # next name.
713 92         74 pop @nodes;
714 92         93 $names[-1]= shift @{$dirstack[-1]};
  92         150  
715 92         269 $fs->_resolve_path(\@nodes, [ $names[-1] ]);
716 92         243 my $p= bless {
717             path_names => \@names,
718             path_dirents => \@nodes,
719             filesystem => $fs,
720             }, 'DataStore::CAS::FS::Path';
721             # If a dir, push it onto the stack
722 92 100       135 if ($p->type eq 'dir') {
723 42         46 push @dirstack, [ map { $_->name } @{ $fs->_get_dir_entries($nodes[-1]) } ];
  92         1669  
  42         57  
724 42         69 push @nodes, undef;
725 42         48 push @names, undef;
726             }
727 92         184 return $p;
728 3         20 }, $class;
729             }
730              
731             sub _init {
732 5     5   5 my $self= shift;
733             # _resolve_path returns a string on failure
734 5         5 my $x;
735 5 50       10 ref( $x= $self->{fs}->_resolve_path(undef, $self->{path}) )
736             or croak $x;
737             # maintain an array of resolved path nodes, and an array of
738             # arrays of names-to-iterate for each directory
739 5         7 @{$self->{path_nodes}}= @$x;
  5         9  
740 5         7 @{$self->{names}}= map { $_->{dirent}->name } @$x;
  5         13  
  13         240  
741 5         11 @{$self->{dirstack}}= ([]) x @$x;
  5         7  
742 5         6 push @{$self->{dirstack}[-1]}, $self->{names}[-1];
  5         16  
743             }
744              
745             sub reset {
746 2     2   1170 $_[0]->($_[0])->_init;
747 2         3 1;
748             }
749              
750             sub skip_dir {
751 2     2   8 my $self= $_[0]->($_[0]);
752 2         6 @{$self->{dirstack}[-1]}= ()
753 2 50       2 if @{$self->{dirstack}};
  2         6  
754 2         6 1;
755             }
756              
757             package DataStore::CAS::FS::DirCache;
758 4     4   16 use strict;
  4         5  
  4         57  
759 4     4   12 use warnings;
  4         2  
  4         988  
760              
761              
762             sub size {
763 0 0   0   0 if (@_ > 1) {
764 0         0 my ($self, $new_size)= @_;
765 0         0 $self->{size}= $new_size;
766 0         0 $self->{_recent}= [];
767 0         0 $self->{_recent_idx}= 0;
768             }
769 0         0 $_[0]{size};
770             }
771              
772             sub new {
773 7     7   9 my $class= shift;
774 7 50       18 my %p= ref($_[0])? %{$_[0]} : @_;
  0         0  
775 7   50     35 $p{size} ||= 32;
776 7   50     24 $p{_by_hash} ||= {};
777 7   50     33 $p{_recent} ||= [];
778 7   50     21 $p{_recent_idx} ||= 0;
779 7         103 bless \%p, $class;
780             }
781              
782             sub clear {
783 0     0   0 $_= undef for @{$_[0]{_recent}};
  0         0  
784 0         0 $_[0]{_by_hash}= {};
785             }
786              
787             sub get {
788 124     124   173 return $_[0]{_by_hash}{$_[1]};
789             }
790              
791             sub put {
792 48     48   53 my ($self, $dir)= @_;
793             # Hold onto a strong reference for a while.
794 48         75 $self->{_recent}[ $self->{_recent_idx}++ ]= $dir;
795 48 50       42 $self->{_recent_idx}= 0 if $self->{_recent_idx} > @{$self->{_recent}};
  48         85  
796             # Index it using a weak reference.
797 48         107 Scalar::Util::weaken( $self->{_by_hash}{$dir->hash}= $dir );
798             # Now, a nifty hack: we attach an object to watch for the destriction of the
799             # directory. Lazy references will get rid of the dir object, but this cleans
800             # up our _by_hash index.
801             $dir->{'#DataStore::CAS::FS::DirCacheCleanup'}=
802 48         256 bless [ $self->{_by_hash}, $dir->hash ], 'DataStore::CAS::FS::DirCacheCleanup';
803             }
804              
805             package DataStore::CAS::FS::DirCacheCleanup;
806 4     4   19 use strict;
  4         3  
  4         59  
807 4     4   13 use warnings;
  4         4  
  4         201  
808              
809 48     48   4273 sub DESTROY { delete $_[0][0]{$_[0][1]}; }
810              
811             1;
812              
813             __END__