DataStore-CAS-FS
view release on metacpan or search on metacpan
lib/DataStore/CAS/FS.pm view on Meta::CPAN
my $ent= $nodes->[-1]{dirent};
my $dir;
# Support for "symlink" is always UNIX-based (or compatible)
# As support for other systems' symbolic paths are added, they
# will be given unique '->type' values, and appropriate handling.
if ($ent->type eq 'symlink' and $flags->{follow_symlinks}) {
# Sanity check on symlink entry
my $target= $ent->ref;
defined $target and length $target
or return 'Invalid symbolic link "'.$ent->name.'"';
unshift @path, split('/', $target, -1);
pop @$nodes;
# If an absolute link, we start over from the root
@$nodes= ( $nodes->[0] )
if $path[0] eq '';
next;
}
if ($ent->type ne 'dir') {
return 'Cannot descend into directory entry "'.$ent->name.'" of type "'.$ent->type.'"'
unless ($flags->{mkdir}||0) > 1;
# Here, mkdir flag converts entry into a directory
$nodes->[-1]{dirent}= $ent->clone(@{ $mkdir_defaults ||= _build_mkdir_defaults($flags)});
}
# Get the next path component, ignoring empty and '.'
my $name= shift @path;
next unless defined $name and length $name and ($name ne '.');
# We handle '..' procedurally, moving up one real directory and *not* backing out of a symlink.
# This is the same way the kernel does it, but perhaps shell behavior is preferred...
if ($name eq '..') {
return "Cannot access '..' at root directory"
unless @$nodes > 1;
pop @$nodes;
next;
}
# If this directory has an in-memory override for this name, use it
my $subnode;
if ($nodes->[-1]{subtree}) {
my $key= $self->case_insensitive? uc $name : $name;
$subnode= $nodes->[-1]{subtree}{$key};
}
# Else we need to find the name within the current directory
if (!defined $subnode && (defined $nodes->[-1]{dir} || defined $ent->ref)) {
# load it if it isn't cached
($nodes->[-1]{dir} ||= $self->get_dir($ent->ref))
or return 'Failed to open directory "'.$ent->name.' ('.$ent->ref.')"';
# See if the directory contains this entry
if (defined (my $subent= $nodes->[-1]{dir}->get_entry($name))) {
$subnode= { dirent => $subent };
my $key= $self->case_insensitive? uc $name : $name;
# Weak reference, until _apply_overrides is called.
Scalar::Util::weaken( $nodes->[-1]{subtree}{$key}= $subnode );
}
}
# If we haven't found one, or if it is 0 (deleted), either create or die.
if (!$subnode) {
# If we're supposed to create virtual entries, do so
if ($flags->{mkdir} or $flags->{partial}) {
$subnode= {
invalid => 1, # not valid until _apply_overrides
dirent => DataStore::CAS::FS::DirEnt->new(
name => $name,
# It is a directory if there are more path components to resolve.
(@path? @{ $mkdir_defaults ||= _build_mkdir_defaults($flags)} : ())
)
};
}
# Else it doesn't exist and we fail.
else {
my $dir_path= File::Spec->catdir(map { $_->{dirent}->name } @$nodes);
return "Directory \"$dir_path\" is not present in storage"
unless defined $nodes->[-1]{dir};
return "No such directory entry \"$name\" at \"$dir_path\"";
}
}
push @$nodes, $subnode;
}
$nodes;
}
sub get_dir_entries {
my ($self, $path)= @_;
my $nodes= $self->_resolve_path(undef, $path);
ref $nodes
or croak $nodes;
return $self->_get_dir_entries($nodes->[-1]);
}
sub readdir {
my $self= shift;
my @names= map { $_->name } @{ $self->get_dir_entries(@_) };
return wantarray? @names : \@names;
}
# This method combines the original directory with its overrides.
sub _get_dir_entries {
my ($self, $node)= @_;
my $ent= $node->{dirent};
croak "Can't get listing for non-directory"
unless $ent->type eq 'dir';
my %dirents;
# load dir if it isn't cached
if (!defined $node->{dir} && defined $ent->ref) {
defined ( $node->{dir}= $self->get_dir($ent->ref) )
or return 'Failed to open directory "'.$ent->name.' ('.$ent->ref.')"';
}
my $caseless= $self->case_insensitive;
if (defined $node->{dir}) {
lib/DataStore/CAS/FS.pm view on Meta::CPAN
# maintain an array of resolved path nodes, and an array of
# arrays of names-to-iterate for each directory
@{$self->{path_nodes}}= @$x;
@{$self->{names}}= map { $_->{dirent}->name } @$x;
@{$self->{dirstack}}= ([]) x @$x;
push @{$self->{dirstack}[-1]}, $self->{names}[-1];
}
sub reset {
$_[0]->($_[0])->_init;
1;
}
sub skip_dir {
my $self= $_[0]->($_[0]);
@{$self->{dirstack}[-1]}= ()
if @{$self->{dirstack}};
1;
}
package DataStore::CAS::FS::DirCache;
use strict;
use warnings;
sub size {
if (@_ > 1) {
my ($self, $new_size)= @_;
$self->{size}= $new_size;
$self->{_recent}= [];
$self->{_recent_idx}= 0;
}
$_[0]{size};
}
sub new {
my $class= shift;
my %p= ref($_[0])? %{$_[0]} : @_;
$p{size} ||= 32;
$p{_by_hash} ||= {};
$p{_recent} ||= [];
$p{_recent_idx} ||= 0;
bless \%p, $class;
}
sub clear {
$_= undef for @{$_[0]{_recent}};
$_[0]{_by_hash}= {};
}
sub get {
return $_[0]{_by_hash}{$_[1]};
}
sub put {
my ($self, $dir)= @_;
# Hold onto a strong reference for a while.
$self->{_recent}[ $self->{_recent_idx}++ ]= $dir;
$self->{_recent_idx}= 0 if $self->{_recent_idx} > @{$self->{_recent}};
# Index it using a weak reference.
Scalar::Util::weaken( $self->{_by_hash}{$dir->hash}= $dir );
# Now, a nifty hack: we attach an object to watch for the destriction of the
# directory. Lazy references will get rid of the dir object, but this cleans
# up our _by_hash index.
$dir->{'#DataStore::CAS::FS::DirCacheCleanup'}=
bless [ $self->{_by_hash}, $dir->hash ], 'DataStore::CAS::FS::DirCacheCleanup';
}
package DataStore::CAS::FS::DirCacheCleanup;
use strict;
use warnings;
sub DESTROY { delete $_[0][0]{$_[0][1]}; }
1;
__END__
=pod
=head1 NAME
DataStore::CAS::FS - Virtual Filesystem backed by Content-Addressable Storage
=head1 VERSION
version 0.011000
=head1 SYNOPSIS
# Create a new empty filesystem
my $casfs= DataStore::CAS::FS->new(
store => DataStore::CAS::Simple->new(
path => './foo/bar',
create => 1,
digest => 'SHA-256'
)
);
# Open an existing root directory on an existing store
$casfs= DataStore::CAS::FS->new( store => $cas, root => $digest_hash );
# --- These pass through to the $cas module
$hash= $casfs->put("Blah");
$hash= $casfs->put_file("./foo/bar/baz");
$file= $casfs->get($hash);
# Open a path within the filesystem
$handle= $casfs->path('1','2','3','myfile')->open;
# Make some changes
$casfs->apply_path(['1', '2', 'myfile'], { ref => $some_new_file });
$casfs->apply_path(['1', '2', 'myfile_copy'], { ref => $some_new_file });
# Commit them
$casfs->commit();
=head1 DESCRIPTION
DataStore::CAS::FS extends the L<DataStore::CAS> API to support directory
objects which let you store store traditional file hierarchies in the CAS,
( run in 1.773 second using v1.01-cache-2.11-cpan-39bf76dae61 )