File Coverage

blib/lib/DataStore/CAS/FS/DirCodec/Minimal.pm
Criterion Covered Total %
statement 85 86 98.8
branch 34 42 80.9
condition 6 10 60.0
subroutine 16 17 94.1
pod 2 2 100.0
total 143 157 91.0


line stmt bran cond sub pod time code
1             package DataStore::CAS::FS::DirCodec::Minimal;
2 5     5   208111 use 5.008001;
  5         16  
  5         205  
3 5     5   31 use strict;
  5         11  
  5         155  
4 5     5   28 use warnings;
  5         11  
  5         135  
5 5     5   37 use Try::Tiny;
  5         9  
  5         352  
6 5     5   28 use Carp;
  5         9  
  5         343  
7 5     5   30 use JSON 2.53 ();
  5         1332  
  5         420  
8             require DataStore::CAS::FS::InvalidUTF8;
9             require DataStore::CAS::FS::Dir;
10             *decode_utf8= *DataStore::CAS::FS::InvalidUTF8::decode_utf8;
11              
12 5     5   35 use parent 'DataStore::CAS::FS::DirCodec';
  5         9  
  5         62  
13              
14             our $VERSION= '0.011000';
15              
16             __PACKAGE__->register_format('minimal' => __PACKAGE__);
17             __PACKAGE__->register_format('' => __PACKAGE__);
18              
19             # ABSTRACT: Directory representation with minimal metadata
20              
21              
22             our %_TypeToCode= ( file => 'f', dir => 'd', symlink => 'l', chardev => 'c', blockdev => 'b', pipe => 'p', socket => 's', whiteout => 'w' );
23             our %_CodeToType= map { $_TypeToCode{$_} => $_ } keys %_TypeToCode;
24             sub encode {
25 11     11 1 5371 my ($class, $entry_list, $metadata)= @_;
26 17 50       177 my @entries= map {
27 11         30 my ($type, $ref, $name)= ref $_ eq 'HASH'?
28             ( $_->{type}, $_->{ref}, $_->{name} )
29             : ( $_->type, $_->ref, $_->name );
30              
31 17 100       272 defined $type
32             or croak "'type' attribute is required";
33 16 100       312 my $code= $_TypeToCode{$type}
34             or croak "Unknown directory entry type '$type' for entry $_";
35 15 100       194 defined $name
36             or croak "'name' attribute is required";
37 14 100       28 _make_utf8($name)
38             or croak "'name' must be a unicode scalar or an InvalidUTF8 instance";
39 13 100       35 $ref= '' unless defined $ref;
40 13 100       27 _make_utf8($ref)
41             or croak "'ref' must be a unicode scalar or an InvalidUTF8 instance";
42              
43 12 50       29 croak "'name' too long: '$name'" if 255 < length $name;
44 12 50       47 croak "'ref' too long: '$ref'" if 255 < length $ref;
45 12         78 pack('CCA', length($name), length($ref), $code).$name."\0".$ref."\0"
46             } @$entry_list;
47            
48 6         14 my $ret= "CAS_Dir 00 \n";
49 6 100 50     45 if ($metadata and scalar keys %$metadata) {
50 1         35 my $enc= JSON->new->utf8->canonical->convert_blessed;
51 1         38 $ret .= $enc->encode($metadata);
52             }
53 6         17 $ret .= "\0";
54 6         31 $ret .= join('', sort { substr($a,3) cmp substr($b,3) } @entries );
  18         39  
55 6 50       26 croak "Accidental unicode concatenation"
56             if utf8::is_utf8($ret);
57 6         26 $ret;
58             }
59              
60             # Convert string in-place to utf-8 bytes, or return false.
61             # A less speed-obfuscated version might read:
62             # my $str= shift;
63             # if (ref $str) {
64             # return 0 unless ref($str)->can('TO_UTF8');
65             # $_[0]= $str->TO_UTF8;
66             # return 1;
67             # } elsif (utf8::is_utf8($str)) {
68             # utf8::encode($_[0]);
69             # return 1;
70             # } else {
71             # return !($_[0] =~ /[\x7F-\xFF]/);
72             # }
73             sub _make_utf8 {
74 27 100 33 27   575 ref $_[0]?
      100        
75             (ref($_[0])->can('TO_UTF8') && (($_[0]= $_[0]->TO_UTF8) || 1))
76             : &utf8::is_utf8 && (&utf8::encode || 1) || !($_[0] =~ /[\x80-\xFF]/);
77             }
78              
79              
80             sub decode {
81 6     6 1 1148 my ($class, $params)= @_;
82 6 100       41 $params->{format}= $class->_read_format($params)
83             unless defined $params->{format};
84 6         30 my $bytes= $params->{data};
85 6         16 my $handle= $params->{handle};
86             # This implementation just processes the file as a whole.
87             # Read it in if we don't have it yet.
88 6         49 my $header_len= $class->_calc_header_length($params->{format});
89 6 100       30 if (defined $bytes) {
90 1         3 substr($bytes, 0, $header_len)= '';
91             }
92             else {
93 5 50       19 defined $handle or $handle= $params->{file}->open;
94 5 50       34 seek($handle, $header_len, 0) or croak "seek: $!";
95 5         38 local $/= undef;
96 5         51 $bytes= <$handle>;
97             }
98            
99 6         33 my $meta_end= index($bytes, "\0");
100 6 50       131 $meta_end >= 0 or croak "Missing end of metadata";
101 6 100       28 if ($meta_end > 0) {
102 1         14 my $enc= JSON->new()->utf8->canonical->convert_blessed;
103 1         8 DataStore::CAS::FS::InvalidUTF8->add_json_filter($enc);
104 1         23 $params->{metadata}= $enc->decode(substr($bytes, 0, $meta_end));
105             } else {
106 5         21 $params->{metadata}= {};
107             }
108              
109 6         22 my $pos= $meta_end+1;
110 6         1273 my @ents;
111 6         51 while ($pos < length($bytes)) {
112 12         112 my ($nameLen, $refLen, $code)= unpack('CCA', substr($bytes, $pos, 3));
113 12         50 my $end= $pos + 3 + $nameLen + 1 + $refLen + 1;
114 12 50       41 ($end <= length($bytes))
115             or croak "Unexpected end of file";
116 12         58 my $name= decode_utf8(substr($bytes, $pos+3, $nameLen));
117 12 100       73 my $ref= $refLen? decode_utf8(substr($bytes, $pos+3+$nameLen+1, $refLen)) : undef;
118 12         87 push @ents, bless [ $code, $name, $ref ], __PACKAGE__.'::Entry';
119 12         70 $pos= $end;
120             }
121 6         77 return DataStore::CAS::FS::Dir->new(
122             file => $params->{file},
123             format => 'minimal', # we encode with format string '', but this is what we want the user to see.
124             entries => \@ents,
125             metadata => $params->{metadata}
126             );
127             }
128              
129             package DataStore::CAS::FS::DirCodec::Minimal::Entry;
130 5     5   8435 use strict;
  5         9  
  5         172  
131 5     5   79 use warnings;
  5         10  
  5         159  
132 5     5   26 use parent 'DataStore::CAS::FS::DirEnt';
  5         12  
  5         37  
133              
134 12     12   66 sub type { $_CodeToType{$_[0][0]} }
135 13     13   172 sub name { $_[0][1] }
136 0     0   0 sub ref { $_[0][2] }
137             sub as_hash {
138 12     12   31 my $self= shift;
139 12 100 50     104 return $self->[3] ||= {
140             type => $self->type,
141             name => $self->name,
142             (defined $self->[2]? (ref => $self->[2]) : ())
143             };
144             }
145              
146             1;
147              
148             __END__