File Coverage

blib/lib/Tie/SymlinkTree.pm
Criterion Covered Total %
statement 132 248 53.2
branch 42 102 41.1
condition 17 46 36.9
subroutine 25 41 60.9
pod 0 7 0.0
total 216 444 48.6


line stmt bran cond sub pod time code
1             package Tie::SymlinkTree;
2 1     1   5848 use strict;
  1         2  
  1         41  
3 1     1   1075 use bytes;
  1         10  
  1         6  
4 1     1   103800 use Encode;
  1         139953  
  1         112  
5 1     1   660 use Tie::Indexer;
  1         4  
  1         193  
6              
7             our $VERSION = '1.1';
8              
9             {
10             package Tie::SymlinkTree::Array;
11 0     0   0 sub id { tied(@{shift()})->id(@_) }
  0         0  
12 0     0   0 sub search { tied(@{shift()})->search(@_) }
  0         0  
13             }
14              
15             {
16             package Tie::SymlinkTree::Hash;
17 0     0   0 sub id { tied(%{shift()})->id(@_) }
  0         0  
18 0     0   0 sub search { tied(%{shift()})->search(@_) }
  0         0  
19             }
20              
21             sub encode_value {
22 1     1   9 no bytes;
  1         3  
  1         10  
23 2     2 0 4 my $val = shift;
24 2 50       3 return undef if !defined $val;
25 2         5 $val =~ s#\x{feff}#\x{feff}feff#g;
26 2         3 $val =~ s#\x{0000}#\x{feff}0000#g;
27 2 50       4 $val = "\x{feff}" if (length($val) == 0);
28 2         5 $val = encode_utf8($val);
29 2         91 return $val;
30             }
31              
32             sub decode_value {
33 1     1   130 no bytes;
  1         1  
  1         4  
34 3     3 0 4 my $val = shift;
35 3 100       10 return undef if !defined $val;
36 2         11 $val = decode_utf8($val);
37 2 50       72 $val = "" if ($val eq "\x{feff}");
38 2         4 $val =~ s#\x{feff}0000#\x{0000}#g;
39 2         3 $val =~ s#\x{feff}feff#\x{feff}#g;
40 2         12 return $val;
41             }
42              
43             sub encode_key {
44 1     1   124 no bytes;
  1         2  
  1         4  
45 10     10 0 16 my $key = shift;
46 10 50       16 $key = '' if !defined $key;
47 10         23 $key =~ s#\x{feff}#\x{feff}feff#g;
48 10         11 $key =~ s#\x{0000}#\x{feff}0000#g;
49 10         14 $key =~ s#/#\x{feff}002f#g;
50 10         16 $key =~ s#^\.#\x{feff}002e#g;
51 10 100       26 $key = "\x{feff}" if (length($key) == 0);
52 10         21 $key = encode_utf8($key);
53 10         54 return $key;
54             }
55              
56             sub decode_key {
57 1     1   180 no bytes;
  1         2  
  1         4  
58 3     3 0 3 my $key = shift;
59 3 50       10 return undef if !defined $key;
60 3         6 $key = decode_utf8($key);
61 3 50       50 $key = "" if ($key eq "\x{feff}");
62 3         7 $key =~ s#\x{feff}002e#.#g;
63 3         2 $key =~ s#\x{feff}002f#/#g;
64 3         3 $key =~ s#\x{feff}0000#\x{0000}#g;
65 3         6 $key =~ s#\x{feff}feff#\x{feff}#g;
66 3         54 return $key;
67             }
68              
69             sub TIEARRAY {
70 0     0   0 my ($package, $path) = @_;
71 0 0       0 my $self = (ref $package?$package:bless {}, $package);
72 0         0 $self->{ARRAY} = 1;
73 0         0 return $self->TIEHASH($path);
74             }
75              
76             sub TIEHASH {
77 3     3   73 my ($package, $path) = @_;
78 3 50       10 die "usage: tie(%hash, 'Tie::SymlinkTree', \$path)" if @_ != 2;
79 3 50       9 my $self = (ref $package?$package:bless {}, $package);
80            
81 3         19 $path =~ s#/*$#/#;
82 3 50       9 die "$path is invalid" if $path =~ m#/\.\.?(/|$)#;
83 3 50 66     66 die "$path is not a directory" if -e $path and -l $path;
84 3 100       29 if (! -e $path) {
85 1 50 33     66 mkdir $path or -d $path or die "Can't create $path: $!";
86 1 50       11 symlink(".",$path.".array") if $self->{ARRAY};
87             } # race condition: assigning array and hash to one location at the same time
88 3 50 25     46 die "$path has wrong type" if (-e $path.".array" xor $self->{ARRAY});
89 3         27 $self->{PATH} = $path;
90            
91 3         12 return $self;
92             }
93              
94             sub FETCH {
95 4     4   84 my ($self, $key) = @_;
96 4         6 $key = encode_key($key);
97 4 100       65 if (-d $self->{PATH}.$key) {
98 1 50       11 if (-e $self->{PATH}.$key."/.array") {
99 0         0 my @tmp;
100 0         0 tie @tmp, ref($self), $self->{PATH}.$key;
101 0         0 return bless \@tmp, 'Tie::SymlinkTree::Array';
102             } else {
103 1         1 my %tmp;
104 1         5 tie %tmp, ref($self), $self->{PATH}.$key;
105 1         10 return bless \%tmp, 'Tie::SymlinkTree::Hash';
106             }
107             } else {
108 3         34 return decode_value(readlink($self->{PATH}.$key));
109             }
110             }
111              
112              
113             sub STORE {
114 3     3   6 my ($self, $key, $val, $recursion) = @_;
115 3         5 $key = encode_key($key);
116 3 50 66     23 die "no objects allowed" if ref($val) && ref($val) ne 'HASH' && ref($val) ne 'ARRAY';
      33        
117 3         10 Tie::Indexer::deindex_node($self,$val,$key);
118 3 50 33     16 if (!defined($val)) {
    100          
    50          
119 0 0       0 open(my $fh,'>',$self->{PATH}.".$$~".$key) || die "Error while storing: $!";
120 0         0 close($fh);
121 0 0 0     0 rename($self->{PATH}.".$$~".$key,$self->{PATH}.$key) or $recursion or do {$self->DELETE($_[1]);$self->STORE($_[1],$val,1);};
  0         0  
  0         0  
122             } elsif (!ref($val)) {
123 2 50       5 symlink(encode_value($val),$self->{PATH}.".$$~".$key) || die "Error while storing: $!";
124 2 50 33     91 rename($self->{PATH}.".$$~".$key,$self->{PATH}.$key) or $recursion or do {$self->DELETE($_[1]);$self->STORE($_[1],$val,1);};
  0         0  
  0         0  
125             } elsif (ref($val) eq 'ARRAY' || ref($val) eq 'Tie::SymlinkTree::Array') {
126 0         0 my @tmp = @$val;
127 0         0 eval { tie @$val, ref($self), $self->{PATH}.$key; };
  0         0  
128 0 0 0     0 if (!$recursion && $@) {$self->DELETE($key);$self->STORE($_[1],$val,1);}
  0         0  
  0         0  
129 0         0 @$val = @tmp;
130             } else {
131 1         3 my %tmp = %$val;
132 1         3 eval { tie %$val, ref($self), $self->{PATH}.$key; };
  1         6  
133 1 50 33     5 if (!$recursion && $@) {$self->DELETE($key);$self->STORE($_[1],$val,1);}
  0         0  
  0         0  
134 1         4 %$val = %tmp;
135             }
136 3         11 Tie::Indexer::index_node($self,$val,$key);
137             }
138              
139              
140             sub DELETE {
141 0     0   0 my ($self, $key) = @_;
142 0         0 $key = encode_key($key);
143 0         0 my $val = $self->FETCH($key);
144 0         0 Tie::Indexer::deindex_node($self,$val,$key);
145 0 0       0 if (UNIVERSAL::isa($val,'ARRAY')) {
    0          
146 0         0 my @tmp = @$val;
147 0         0 for my $i (0..$#tmp) {
148 0         0 $tmp[$i] = delete $val->[$i];
149             }
150 0         0 $val = \@tmp;
151 0         0 unlink $self->{PATH}.$key."/.array";
152             } elsif (UNIVERSAL::isa($val,'HASH')) {
153 0         0 my %tmp = %$val;
154 0         0 for my $k (keys %tmp) {
155 0         0 $tmp{$k} = delete $val->{$k};
156             }
157 0         0 $val = \%tmp;
158             } else {
159 0 0 0     0 if (substr($self->id,0,1) ne '.' && -d $self->{PATH}."../.index-$key") {
160 0         0 my $path = $self->{PATH};
161 0         0 $path =~ s#[^/]*/$##;
162 0         0 tie my %index, ref($self), $path.".index-$key/";
163 0         0 delete $index{$val}{$self->id};
164             }
165             }
166 0         0 unlink $self->{PATH}.$key;
167 0         0 rmdir $self->{PATH}.$key;
168 0         0 return $val;
169             }
170              
171             sub _clear {
172 3     3   3 my ($dir) = @_;
173 3 50       7 die "empty directory" unless $dir;
174 3         3 my $dh;
175 3         71 opendir($dh,$dir);
176 3         53 while (defined (my $subdir = readdir($dh))) {
177 11 100 100     64 next if ($subdir eq '.' || $subdir eq '..');
178 5 100       239 unlink($dir.$subdir) or do {
179 1         5 _clear($dir.$subdir."/");
180 1         130 rmdir($dir.$subdir);
181             }
182             }
183 3         34 closedir($dh);
184             }
185              
186             sub CLEAR {
187 2     2   3 my ($self) = @_;
188 2         6 $self->lock;
189 2         7 _clear($self->{PATH});
190 2         7 $self->unlock;
191             }
192              
193             sub EXISTS {
194 3     3   31 my ($self, $key) = @_;
195 3         7 $key = encode_key($key);
196 3   66     74 return -e $self->{PATH}.$key || -l $self->{PATH}.$key;
197             }
198              
199              
200 0     0   0 sub DESTROY {
201             }
202              
203              
204             sub FIRSTKEY {
205 2     2   10 my ($self) = @_;
206            
207 2         3 my $dh;
208 2         44 opendir($dh,$self->{PATH});
209 2         4 $self->{HANDLE} = $dh;
210 2         11 my $entry;
211 2         32 while (defined ($entry = readdir($self->{HANDLE}))) {
212 3 100       21 return decode_key($entry) unless (substr($entry,0,1) eq '.');
213             }
214 1         9 return;
215             }
216              
217              
218             sub NEXTKEY {
219 3     3   6 my ($self) = @_;
220 3         4 my $entry;
221 3         1691 while (defined ($entry = readdir($self->{HANDLE}))) {
222 4 100       21 return decode_key($entry) unless (substr($entry,0,1) eq '.');
223             }
224 1         15 return;
225             }
226              
227             sub FETCHSIZE {
228 0     0   0 my ($self) = @_;
229 0         0 my $dh;
230 0         0 opendir($dh,$self->{PATH});
231 0         0 my $max = -1;
232 0         0 my $entry;
233 0         0 while (defined ($entry = readdir($dh))) {
234 0 0       0 next if substr($entry,0,1) eq '.';
235 0 0       0 $max = int($entry) if $entry > $max;
236             }
237 0         0 return $max+1;
238             }
239              
240             sub STORESIZE {
241 0     0   0 my ($self, $size) = @_;
242 0         0 $self->lock;
243 0         0 $size = int($size);
244 0         0 while (-e $self->{PATH}.$size) {
245 0         0 $self->DELETE($size);
246 0         0 $size++;
247             }
248 0         0 $self->unlock;
249             }
250              
251 0     0   0 sub EXTEND { }
252 0     0   0 sub UNSHIFT { scalar shift->SPLICE(0,0,@_) }
253 0     0   0 sub SHIFT { shift->SPLICE(0,1) }
254              
255             sub PUSH {
256 0     0   0 my ($self, $value) = @_;
257 0         0 $self->lock;
258 0         0 my $key = $self->FETCHSIZE;
259 0         0 $self->STORE($key,$value);
260 0         0 $self->unlock;
261 0         0 return $key+1;
262             }
263              
264             sub POP {
265 0     0   0 my ($self, $value) = @_;
266 0         0 $self->lock;
267 0         0 my $key = $self->FETCHSIZE-1;
268 0         0 my $val = $self->FETCH($key);
269 0         0 $self->DELETE($key);
270 0         0 $self->unlock;
271 0         0 return $val;
272             }
273              
274             sub SPLICE {
275 0     0   0 my $self = shift;
276 0         0 $self->lock;
277 0         0 my $size = $self->FETCHSIZE;
278 0 0       0 my $off = (@_) ? shift : 0;
279 0 0       0 $off += $size if ($off < 0);
280 0 0       0 my $len = (@_) ? shift : $size - $off;
281 0 0       0 $len += $size - $off if $len < 0;
282 0         0 my @result;
283 0         0 for (my $i = 0; $i < $len; $i++) {
284 0         0 push(@result,$self->FETCH($off+$i));
285             }
286 0 0       0 $off = $size if $off > $size;
287 0 0       0 $len -= $off + $len - $size if $off + $len > $size;
288 0 0       0 if (@_ > $len) {
    0          
289             # Move items up to make room
290 0         0 my $d = @_ - $len;
291 0         0 my $e = $off+$len;
292 0         0 for (my $i=$size-1; $i >= $e; $i--) {
293 0         0 rename($self->{PATH}.$i,$self->{PATH}.($i+$d));
294             }
295             }
296             elsif (@_ < $len) {
297             # Move items down to close the gap
298 0         0 my $d = $len - @_;
299 0         0 my $e = $off+$len;
300 0         0 for (my $i=$off+$len; $i < $size; $i++) {
301 0         0 rename($self->{PATH}.$i,$self->{PATH}.($i-$d));
302             }
303             }
304 0         0 for (my $i=0; $i < @_; $i++) {
305 0         0 $self->STORE($off+$i,$_[$i]);
306             }
307 0         0 $self->unlock;
308 0 0       0 return wantarray ? @result : pop @result;
309             }
310              
311             sub lock {
312 2     2 0 2 my ($self) = @_;
313 2 50       8 if (!$self->{locked}++) {
314 2         2 my $i = 0;
315 2   33     140 while (!symlink($$,$self->{PATH}.".lock") && $i++ < 40) {
316 0         0 select('','','',.25);
317             }
318             }
319             }
320              
321             sub unlock {
322 2     2 0 2 my ($self) = @_;
323 2 50       9 if (!--$self->{locked}) {
324 2         34 unlink($self->{PATH}.".lock");
325             }
326             }
327              
328             sub id {
329 0     0 0 0 my ($self) = @_;
330 0         0 return ($self->{PATH} =~ m{/([^/]+)/$})[0];
331             }
332              
333             sub _get_index {
334 6     6   7 my ($tie, $create) = @_;
335 6 50 33     94 return undef if (!$create && ! -d $tie->{PATH}.".index/");
336 0           tie my %index, ref($tie), $tie->{PATH}.".index/";
337 0           return \%index;
338             }
339              
340             BEGIN {
341 1     1   2750 *search = \&Tie::Indexer::search;
342             }
343              
344 1     1   7 no warnings;
  1         2  
  1         62  
345             "Dahut!";
346             __END__