File Coverage

blib/lib/DataStore/CAS/FS.pm
Criterion Covered Total %
statement 376 430 87.4
branch 149 236 63.1
condition 89 176 50.5
subroutine 64 78 82.0
pod 21 23 91.3
total 699 943 74.1


line stmt bran cond sub pod time code
1             package DataStore::CAS::FS;
2 4     4   706838 use 5.008;
  4         15  
  4         173  
3 4     4   718 use Moo 1.000007;
  4         15264  
  4         32  
4 4     4   3119 use Carp;
  4         8  
  4         285  
5 4     4   787 use Try::Tiny 0.11;
  4         1373  
  4         235  
6 4     4   23 use File::Spec 3.33;
  4         107  
  4         96  
7 4     4   713 use DataStore::CAS 0.02;
  4         28825  
  4         25513  
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 21 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   975 my $self= shift;
49 2         26 my $empty= DataStore::CAS::FS::DirCodec::Minimal->encode([],{});
50 2         105 return $self->store->put_scalar($empty);
51             }
52              
53             sub _validate_cas {
54 7     7   181 my $cas= shift;
55 7 50 33     333 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 33614 my $class= shift;
61 7 50 33     70 my %p= (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_;
  0         0  
62             # Root is an alias for root_entry
63 7 50       35 if (defined $p{root}) {
64 7 50       30 defined $p{root_entry}
65             and croak "Specify only one of 'root' or 'root_entry'";
66 7         24 $p{root_entry}= delete $p{root};
67             }
68 7         176 return \%p;
69             }
70              
71             sub BUILD {
72 7     7 0 112 my ($self, $args)= @_;
73 7         24 my @invalid= grep { !$self->can($_) } keys %$args;
  14         82  
74 7 50       27 croak "Invalid param(s): ".join(', ', @invalid)
75             if @invalid;
76              
77 7 50 33     130 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         29 my $root= $self->root_entry;
82 7 50       27 defined $root
83             or croak "root_entry is required";
84 7 100 66     122 unless (ref $root && ref($root)->isa('DataStore::CAS::FS::DirEnt')) {
85 2 0       24 $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     435 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       306 $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 15 (shift)->store->get(@_);
113             }
114              
115              
116             sub get_dir {
117 124     124 1 806 my ($self, $hash_or_file, $flags)= @_;
118 124 50 33     710 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         648 my $dir= $self->dir_cache->get($hash);
123 124 100       667 return $dir if defined $dir;
124            
125             # Return undef if the directory doesn't exist.
126             return undef
127 48 50 33     315 unless defined ($file ||= $self->store->get($hash));
128            
129             # Deserialize directory. This can throw exceptions if it isn't a valid encoding.
130 48         1019 $dir= DataStore::CAS::FS::DirCodec->load($file);
131             # Cache it
132 48         343 $self->dir_cache->put($dir);
133 48         863 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 9148 bless { filesystem => (shift), path_names => [ map { File::Spec->splitdir($_) } @_ ] },
  18         247  
146             'DataStore::CAS::FS::Path';
147             }
148              
149              
150             sub path_if_exists {
151 2     2 1 6802 my $self= shift;
152 2         9 my $path= $self->path(@_);
153 2 100       13 $path->resolve({no_die => 1})? $path : undef;
154             }
155              
156              
157             sub tree_iterator {
158 3     3 1 7289 my $self= shift;
159 3 50 33     32 my %p= (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_;
  0         0  
160 3         34 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 552 my ($self, $path, $flags)= @_;
171 21   50     119 $flags ||= {};
172            
173 21         108 my $ret= $self->_resolve_path(undef, $path, { follow_symlinks => 1, %$flags });
174            
175             # Array means success, scalar means error.
176 21 50       92 if (ref($ret) eq 'ARRAY') {
177             # The user wants directory entries, not "nodes".
178 21         165 $_= $_->{dirent} for @$ret;
179 21         668 return $ret;
180             }
181              
182             # else, got an error...
183 0 0       0 ${$flags->{error_out}}= $ret
  0         0  
184             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   479 my ($self, $nodes, $path_names, $flags)= @_;
191              
192 241 100       1413 my @path= ref($path_names)? @$path_names : File::Spec->splitdir($path_names);
193 241   100     853 $nodes ||= [];
194 241 100 100     1005 push @$nodes, ($self->{_nodes} ||= { dirent => $self->root_entry })
195             unless @$nodes;
196            
197 241         304 my $mkdir_defaults;
198             sub _build_mkdir_defaults {
199 3     3   20 my $flags= shift;
200 3 50       13 my @ret= %{$flags->{mkdir_defaults}}
  0         0  
201             if defined $flags->{mkdir_defaults};
202 3         10 push @ret, type => 'dir', ref => undef;
203             \@ret
204 3         36 }
205              
206 241         670 while (@path) {
207 761         1704 my $ent= $nodes->[-1]{dirent};
208 761         882 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 100 66     7715507 if ($ent->type eq 'symlink' and $flags->{follow_symlinks}) {
214             # Sanity check on symlink entry
215 4         130 my $target= $ent->ref;
216 4 50 33     53 defined $target and length $target
217             or return 'Invalid symbolic link "'.$ent->name.'"';
218              
219 4         24 unshift @path, split('/', $target, -1);
220 4         10 pop @$nodes;
221            
222             # If an absolute link, we start over from the root
223 4 50       20 @$nodes= ( $nodes->[0] )
224             if $path[0] eq '';
225              
226 4         19 next;
227             }
228              
229 757 50       24407 if ($ent->type ne 'dir') {
230 0 0 0     0 return 'Cannot descend into directory entry "'.$ent->name.'" of type "'.$ent->type.'"'
231             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         2138 my $name= shift @path;
238 757 100 66     7539 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       1815 if ($name eq '..') {
243 9 50       29 return "Cannot access '..' at root directory"
244             unless @$nodes > 1;
245 9         12 pop @$nodes;
246 9         44 next;
247             }
248              
249             # If this directory has an in-memory override for this name, use it
250 629         749 my $subnode;
251 629 100       1821 if ($nodes->[-1]{subtree}) {
252 516 50       1881 my $key= $self->case_insensitive? uc $name : $name;
253 516         1593 $subnode= $nodes->[-1]{subtree}{$key};
254             }
255              
256             # Else we need to find the name within the current directory
257 629 100 100     4493 if (!defined $subnode && (defined $nodes->[-1]{dir} || defined $ent->ref)) {
      66        
258             # load it if it isn't cached
259 196 50 66     2928 ($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       870 if (defined (my $subent= $nodes->[-1]{dir}->get_entry($name))) {
264 190         577 $subnode= { dirent => $subent };
265 190 50       817 my $key= $self->case_insensitive? uc $name : $name;
266             # Weak reference, until _apply_overrides is called.
267 190         1647 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       1709 if (!$subnode) {
273             # If we're supposed to create virtual entries, do so
274 15 100 100     108 if ($flags->{mkdir} or $flags->{partial}) {
275 6   66     30 $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       83 (@path? @{ $mkdir_defaults ||= _build_mkdir_defaults($flags)} : ())
281             )
282             };
283             }
284             # Else it doesn't exist and we fail.
285             else {
286 3         11 my $dir_path= File::Spec->catdir(map { $_->{dirent}->name } @$nodes);
  4         150  
287 3 50       20 return "Directory \"$dir_path\" is not present in storage"
288             unless defined $nodes->[-1]{dir};
289 3         22 return "No such directory entry \"$name\" at \"$dir_path\"";
290             }
291             }
292              
293 626         2787 push @$nodes, $subnode;
294             }
295            
296 238         668 $nodes;
297             }
298              
299              
300             sub get_dir_entries {
301 10     10 1 26 my ($self, $path)= @_;
302 10         38 my $nodes= $self->_resolve_path(undef, $path);
303 10 50       52 ref $nodes
304             or croak $nodes;
305 10         56 return $self->_get_dir_entries($nodes->[-1]);
306             }
307              
308             sub readdir {
309 10     10 1 471 my $self= shift;
310 10         24 my @names= map { $_->name } @{ $self->get_dir_entries(@_) };
  17         550  
  10         43  
311 10 50       132 return wantarray? @names : \@names;
312             }
313              
314             # This method combines the original directory with its overrides.
315             sub _get_dir_entries {
316 52     52   94 my ($self, $node)= @_;
317 52         104 my $ent= $node->{dirent};
318 52 50       1992 croak "Can't get listing for non-directory"
319             unless $ent->type eq 'dir';
320 52         125 my %dirents;
321             # load dir if it isn't cached
322 52 100 100     1807 if (!defined $node->{dir} && defined $ent->ref) {
323 45 50       1578 defined ( $node->{dir}= $self->get_dir($ent->ref) )
324             or return 'Failed to open directory "'.$ent->name.' ('.$ent->ref.')"';
325             }
326 52         184 my $caseless= $self->case_insensitive;
327 52 100       156 if (defined $node->{dir}) {
328 47         212 my $iter= $node->{dir}->iterator;
329 47         132 my $dirent;
330 47 50       162 $dirents{$caseless? uc($dirent->name) : $dirent->name}= $dirent
331             while defined ($dirent= $iter->());
332             }
333 52 100       307 if (my $t= $node->{subtree}) {
334 6         31 for (keys %$t) {
335 8         15 my $subnode= $t->{$_};
336 8 50       25 next unless defined $subnode;
337 8 50 66     72 die "BUG" if ref $subnode && $subnode->{invalid};
338 8 100       25 if (ref $subnode) {
339 7 50       45 $dirents{$_}= $subnode->{dirent}
340             if $subnode->{changed};
341             } else {
342 1         4 delete $dirents{$_};
343             }
344             }
345             }
346 52         309 return [ map { $dirents{$_} } sort keys %dirents ];
  109         436  
347             }
348              
349              
350             sub set_path {
351 4     4 1 823 my ($self, $path, $newent, $flags)= @_;
352 4   50     32 $flags ||= {};
353 4         34 my $nodes= $self->_resolve_path(undef, $path, { follow_symlinks => 1, partial => 1, %$flags });
354 4 50       40 croak $nodes unless ref $nodes;
355             # replace the final entry, after applying defaults
356 4 100       26 if (!$newent) {
357             # unlink request. Ignore if node didn't exist.
358 1 50       5 return if $nodes->[-1]{invalid};
359              
360             # Can't unlink the root node
361 1 50       5 croak "Can't unlink root node"
362             unless @$nodes > 1;
363              
364 1         4 $nodes->[-1]{invalid}= 1;
365             # Recursively invalidate all nodes beneath this one
366 1         4 &_invalidate_subtree for ($nodes->[-1]);
367              
368             # Mark in prev node that this item is gone
369 1 50       37 my $key= $self->case_insensitive? uc $nodes->[-1]{dirent}->name : $nodes->[-1]{dirent}->name;
370 1         3 pop @$nodes;
371 1         6 $nodes->[-1]{subtree}{$key}= 0;
372             } else {
373 3 0 33     164 if (ref $newent eq 'HASH' or !defined $newent->name or !defined $newent->type) {
      33        
374 3 50       9 my %ent_hash= %{ref $newent eq 'HASH'? $newent : $newent->as_hash};
  3         38  
375 3 100       150 $ent_hash{name}= $nodes->[-1]{dirent}->name
376             unless defined $ent_hash{name};
377 3 50 33     32 defined $ent_hash{name} && length $ent_hash{name}
378             or die "No name for new dir entry";
379 3 100 50     96 $ent_hash{type}= $nodes->[-1]{dirent}->type || 'file'
380             unless defined $ent_hash{type};
381 3         23 $newent= DataStore::CAS::FS::DirEnt->new(\%ent_hash);
382             }
383 3         12 $nodes->[-1]{dirent}= $newent;
384 3         27 delete $nodes->[-1]{dir};
385             # Recursively invalidate all nodes beneath this one
386 3         19 &_invalidate_subtree for ($nodes->[-1]);
387             }
388             # Now connect nodes with strong references, and mark as changed
389 4         23 $self->_apply_overrides($nodes);
390             }
391             sub _invalidate_subtree {
392 23 100   23   108 if ($_->{subtree}) {
393 10   33     16 ++$_->{invalid} && &_invalidate_subtree for grep { ref $_ } values %{delete $_->{subtree}};
  11         60  
  10         31  
394             }
395             }
396              
397              
398             sub update_path {
399 2     2 1 419 my ($self, $path, $changes, $flags)= @_;
400 2   50     16 $flags ||= {};
401 2         17 my $nodes= $self->_resolve_path(undef, $path, { follow_symlinks => 1, partial => 1, %$flags });
402 2 50       12 croak $nodes unless ref $nodes;
403              
404             # update the final entry, after applying defaults
405 2         8 my $entref= \$nodes->[-1]{dirent};
406 2 100 66     72 my $old_dir_ref= defined $$entref->type && $$entref->type eq 'dir'? $$entref->ref : undef;
407 2 100       70 $$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       72 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     35 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         10 $self->_apply_overrides($nodes);
424             }
425              
426             sub _apply_overrides {
427 9     9   21 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         16 my $prev;
431 9         31 for (reverse @$nodes) {
432 25 100       80 if ($prev) {
433 16 50       605 my $key= $self->case_insensitive? uc($prev->{dirent}->name) : $prev->{dirent}->name;
434 16         84 $_->{subtree}{$key}= $prev;
435             }
436 25 100 66     119 last if $_->{changed} && !$_->{invalid};
437 21         47 delete $_->{invalid};
438 21         54 $_->{changed}= 1;
439 21         53 $prev= $_;
440             }
441             # Finally, make sure the root override is set
442 9         28 $self->{_nodes}= $nodes->[0];
443 9         49 1;
444             }
445              
446              
447             sub mkdir {
448 4     4 1 723 my ($self, $path)= @_;
449 4         24 my $nodes= $self->_resolve_path(undef, $path, { follow_symlinks => 1, mkdir => 1 });
450 4 50       21 croak $nodes unless ref $nodes;
451 4 100       151 unless (defined $nodes->[-1]{dirent}->type) {
452 3         51 $nodes->[-1]{dirent}= $nodes->[-1]{dirent}->clone(type => 'dir');
453 3         15 $self->_apply_overrides($nodes);
454             }
455 4         23 1;
456             }
457              
458              
459             sub touch {
460 1     1 1 691 my ($self, $path)= @_;
461 1         17 $self->update_path($path, { mtime => time() });
462             }
463              
464              
465             sub unlink {
466 1     1 1 3 my ($self, $path)= @_;
467 1         7 $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 7 my $self= shift;
486 2 50 33     41 if ($self->_nodes && $self->_nodes->{changed}) {
487 2         7 my $root_node= $self->_nodes;
488 2 50       87 croak "Root override must be a directory"
489             unless $root_node->{dirent}->type eq 'dir';
490 2         12 my $hash= $self->_commit_recursive($root_node);
491 2         144 $self->{root_entry}= $root_node->{dirent}->clone(ref => $hash);
492 2         10 $self->{_nodes}= undef;
493             }
494 2         18 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   14 my ($self, $node)= @_;
502              
503 10         12 my %changes;
504             my @entries;
505 10 50       27 if (my $subtree= $node->{subtree}) {
506 10         13 while (my ($k, $v)= each %{$node->{subtree}}) {
  21         74  
507 11 50 33     101 $changes{$k}= $v
      66        
508             if defined $v && ($v eq 0 || $v->{changed});
509             }
510             }
511            
512             # If no changes, return original directory (if it exists)
513 10 100 66     59 return $node->{dirent}->ref
514             if !%changes && defined $node->{dirent}->ref;
515            
516             # Walk the directory entries and filter out any that have been overridden.
517 9 100 66     184 if (defined $node->{dir} || defined $node->{dirent}->ref) {
518 4 50 33     17 ($node->{dir} ||= $self->get_dir($node->{dirent}->ref))
519             or croak 'Failed to open directory "'.$node->{dirent}->name.' ('.$node->{dirent}->ref.')"';
520            
521 4         7 my ($iter, $ent);
522 4         11 my $caseless= $self->case_insensitive;
523 4         20 for ($iter= $node->{dir}->iterator; defined ($ent= $iter->()); ) {
524 12 50       378 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         27 for (grep { ref $_ } values %changes) {
  10         24  
531             # Check if node is a dir and needs committed
532 10 50 66     243 if ($_->{subtree} and $_->{dirent}->type eq 'dir' and $_->{changed}) {
      66        
533 8         56 my $hash= $self->_commit_recursive($_);
534 8         6727 $_->{dirent}= $_->{dirent}->clone( ref => $hash );
535             }
536            
537 10         52 push @entries, $_->{dirent};
538             }
539              
540             # Invalidate all children of this node
541 9         31 &_invalidate_subtree for ($node);
542            
543             # Now re-encode the directory, using the same type as orig_dir
544 9 50       28 return $self->hash_of_empty_dir
545             unless @entries;
546 9 100       44 my $format= $node->{dir}->format
547             if $node->{dir};
548 9 100       32 $format= 'universal' unless defined $format;
549 9         66 return DataStore::CAS::FS::DirCodec->put($self->store, $format, \@entries, {});
550             }
551              
552             package DataStore::CAS::FS::Path;
553 4     4   76 use strict;
  4         9  
  4         139  
554 4     4   57 use warnings;
  4         13  
  4         5467  
555              
556              
557             # main attributes
558 5     5   38 sub path_names { $_[0]{path_names} }
559 7     7   54 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   14 sub path_dirent_list { map { $_->{dirent} } @{$_[0]->resolve} }
  67         145  
  13         33  
565 13     13   116 sub path_dirents { [ $_[0]->path_dirent_list ] }
566 5     5   30 sub dirent { $_[0]->resolve->[-1]{dirent} }
567 98     98   231 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   500 my $x= $_[0]->resolve;
586             # ignore name of root entry
587 92         288 return '/'.join('/', map { $_->{dirent}->name } @$x[1..$#$x]);
  399         12866  
588             }
589              
590              
591             sub resolve {
592             # See if we can re-use the previous result...
593 214     214   490 my $nodes= $_[0]{_node_path};
594 214 100 100     1449 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         169 my ($self, $flags)= @_;
598 103         141 my (@valid_nodes, $sub_path);
599 103 100       248 if ($nodes) {
600 2         8 for (@$nodes) {
601 4 100 66     35 last if !ref $_ || $_->{invalid};
602 2         9 push @valid_nodes, $_;
603             }
604 2 50       8 $sub_path= [ map { ref $_? $_->{dirent}->name : $_ } @{$nodes}[ scalar(@valid_nodes) .. $#$nodes ] ];
  2         16  
  2         9  
605             } else {
606 101         176 $sub_path= $self->{path_names};
607             }
608 103 100       346 $flags= { follow_symlinks => 1, $flags? %$flags : () };
609 103         330 $nodes= $self->{filesystem}->_resolve_path(\@valid_nodes, $sub_path, $flags);
610 103 100       306 if (ref $nodes) {
611 100         3675 return ($self->{_node_path}= $nodes);
612             } else {
613             # else, got an error...
614 3 50       20 ${$flags->{error_out}}= $nodes
  0         0  
615             if ref $flags->{error_out};
616 3 50       15 Carp::croak $nodes unless $flags->{no_die};
617 3         38 return undef;
618             }
619             }
620              
621              
622             sub path {
623 6     6   13 my $self= shift;
624 6         19 my @sub_names= map { File::Spec->splitdir($_) } @_;
  11         259  
625 6         84 bless {
626             filesystem => $self->{filesystem},
627 2         51 path_names => [ @{$self->{path_names}}, @sub_names ],
628 6 100       27 $self->{_node_path}? (_node_path => [ @{$self->{_node_path}}, @sub_names ]) : (),
629             }, ref $self;
630             }
631              
632             sub path_if_exists {
633 3     3   52 my $self= shift;
634 3         18 my $path= $self->path(@_);
635 3 100       22 $path->resolve({no_die => 1})? $path : undef;
636             }
637              
638              
639             sub mkdir {
640 1     1   2 my $self= shift;
641 1         6 $self->{filesystem}->mkdir($self->path_names, @_);
642 1         4 $self;
643             }
644              
645              
646             sub file {
647 2   33 2   15 $_[0]{file} ||= do {
648 2         11 my $ent= $_[0]->dirent;
649 2 50       70 $ent->type eq 'file' or Carp::croak "Path is not a file";
650 2 50       66 defined (my $hash= $ent->ref) or Carp::croak "File was not stored in CAS";
651 2         14 $_[0]->filesystem->get($hash);
652             };
653             }
654              
655             sub open {
656 2     2   27 $_[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   22 $_[0]{filesystem}->readdir($_[0]->path_names)
672             }
673              
674              
675             sub tree_iterator {
676 1     1   4 my $self= shift;
677 1 50 33     8 my %p= (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_;
  0         0  
678 1         14 $self->filesystem->tree_iterator(%p, path => $self->path_names);
679             }
680              
681             package DataStore::CAS::FS::TreeIterator;
682 4     4   30 use strict;
  4         8  
  4         131  
683 4     4   22 use warnings;
  4         8  
  4         98  
684 4     4   24 use Carp;
  4         6  
  4         3060  
685              
686              
687 0     0   0 sub _fields { $_[0]->($_[0]) }
688              
689             sub new {
690 3     3   8 my $class= shift;
691 3 50 33     56 my $self= bless { (@_ == 1 && ref $_[0] eq 'HASH')? %{$_[0]} : @_ }, $class;
  0         0  
692             defined $self->{$_} || croak "'$_' is required"
693 3   33     37 for qw( path fs );
694 3         12 $self->{path_nodes}= \my @nodes;
695 3         9 $self->{dirstack}= \my @dirstack;
696 3         10 $self->{names}= \my @names;
697 3         10 $self->{filterref}= \my $filter;
698 3         9 $filter= delete $self->{filter};
699 3         8 my $fs= $self->{fs};
700 3         15 $self->_init;
701             return bless sub {
702 101 100 66 101   649 return $self if @_ && ref($_[0]) eq $class;
703 97 50       315 return undef unless @dirstack;
704             # back out of a directory hierarchy that we have finished
705 97         195 while (!@{$dirstack[-1]}) {
  147         452  
706 55         105 pop @dirstack; # back out of directory
707 55         84 pop @nodes;
708 55         106 pop @names;
709 55 100       164 return undef unless @dirstack;
710             }
711             # Iterate path leaf, by removing last leaf, and resolving using the
712             # next name.
713 92         128 pop @nodes;
714 92         173 $names[-1]= shift @{$dirstack[-1]};
  92         231  
715 92         452 $fs->_resolve_path(\@nodes, [ $names[-1] ]);
716 92         635 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       225 if ($p->type eq 'dir') {
723 42         84 push @dirstack, [ map { $_->name } @{ $fs->_get_dir_entries($nodes[-1]) } ];
  92         2960  
  42         163  
724 42         140 push @nodes, undef;
725 42         75 push @names, undef;
726             }
727 92         384 return $p;
728 3         40 }, $class;
729             }
730              
731             sub _init {
732 5     5   13 my $self= shift;
733             # _resolve_path returns a string on failure
734 5         9 my $x;
735 5 50       67 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         13 @{$self->{path_nodes}}= @$x;
  5         18  
740 5         46 @{$self->{names}}= map { $_->{dirent}->name } @$x;
  5         30  
  13         390  
741 5         26 @{$self->{dirstack}}= ([]) x @$x;
  5         15  
742 5         46 push @{$self->{dirstack}[-1]}, $self->{names}[-1];
  5         30  
743             }
744              
745             sub reset {
746 2     2   2385 $_[0]->($_[0])->_init;
747 2         8 1;
748             }
749              
750             sub skip_dir {
751 2     2   16 my $self= $_[0]->($_[0]);
752 2         7 @{$self->{dirstack}[-1]}= ()
  2         10  
753 2 50       4 if @{$self->{dirstack}};
754 2         14 1;
755             }
756              
757             package DataStore::CAS::FS::DirCache;
758 4     4   26 use strict;
  4         9  
  4         115  
759 4     4   32 use warnings;
  4         8  
  4         1528  
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   18 my $class= shift;
774 7 50       35 my %p= ref($_[0])? %{$_[0]} : @_;
  0         0  
775 7   50     50 $p{size} ||= 32;
776 7   50     47 $p{_by_hash} ||= {};
777 7   50     46 $p{_recent} ||= [];
778 7   50     46 $p{_recent_idx} ||= 0;
779 7         187 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   613 return $_[0]{_by_hash}{$_[1]};
789             }
790              
791             sub put {
792 48     48   89 my ($self, $dir)= @_;
793             # Hold onto a strong reference for a while.
794 48         159 $self->{_recent}[ $self->{_recent_idx}++ ]= $dir;
795 48 50       87 $self->{_recent_idx}= 0 if $self->{_recent_idx} > @{$self->{_recent}};
  48         234  
796             # Index it using a weak reference.
797 48         203 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 48         560 $dir->{'#DataStore::CAS::FS::DirCacheCleanup'}=
802             bless [ $self->{_by_hash}, $dir->hash ], 'DataStore::CAS::FS::DirCacheCleanup';
803             }
804              
805             package DataStore::CAS::FS::DirCacheCleanup;
806 4     4   23 use strict;
  4         6  
  4         140  
807 4     4   23 use warnings;
  4         8  
  4         333  
808              
809 48     48   7602 sub DESTROY { delete $_[0][0]{$_[0][1]}; }
810              
811             1;
812              
813             __END__