File Coverage

blib/lib/DirDB.pm
Criterion Covered Total %
statement 170 254 66.9
branch 66 146 45.2
condition 5 12 41.6
subroutine 19 26 73.0
pod 1 4 25.0
total 261 442 59.0


line stmt bran cond sub pod time code
1             package DirDB;
2              
3             require 5.005_62;
4 1     1   6610 use strict;
  1         2  
  1         40  
5 1     1   6 use warnings;
  1         8  
  1         40  
6 1     1   5 use Carp;
  1         8  
  1         6195  
7              
8             our $VERSION = '0.12';
9              
10             my $DefaultArrayImpl = ['Tie::File' =>DATAPATH => recsep => "\0"]; # may change
11             my %ArrayImpl;
12             sub TIEHASH {
13 7     7   140 my $self = shift;
14 7 50       18 my $rootpath = shift or croak "we need a rootpath";
15 7         30 $rootpath =~ s#/+$##; # lose trailing slash(es)
16 7 50 66     349 -d $rootpath or
17             mkdir $rootpath, 0777 or
18             croak "could not create dir $rootpath: $!";
19 7         28 my $me = bless \"$rootpath/", $self;
20              
21 7         17 my %moreargs = @_;
22 7   33     45 $ArrayImpl{$me} = $moreargs{ARRAY} || $DefaultArrayImpl;
23 7         30 $me;
24             };
25              
26             sub TIEARRAY {
27 0     0   0 confess "DirDB does not support arrays yet, although as of version 0.11 you may store and retrieve array references";
28             };
29              
30             sub TIESCALAR {
31 0     0   0 confess "DirDB does not support scalars yet -- try Tie::Slurp";
32             };
33              
34              
35             sub EXISTS {
36 0     0   0 my $rootpath = ${+shift};
  0         0  
37 0         0 my $key = shift;
38 0         0 $key =~ s/^ / /; #escape leading space into two spaces
39             # defined (my $key = shift) or return undef;
40 0 0       0 $key eq '' and $key = ' EMPTY';
41 0 0       0 -e "$rootpath$key" or -e "$rootpath LOCK$key";
42             };
43              
44             sub recursive_delete($);
45             sub recursive_delete($){
46             # unlink a file or rm -rf a directory tree
47 6     6 0 9 my $path = shift;
48 6 100 66     130 unless ( -d $path and ! -l $path ){
49 4         145 unlink $path;
50 4 50       59 -e $path and die "Could not unlink [$path]: $!\n";
51 4         15 return;
52             };
53 2 50       65 opendir FSDBFH, $path or croak "opendir $path: $!";
54 2         43 my @DirEnts = (readdir FSDBFH);
55             #warn "direrctoy $path contains [@DirEnts]";
56 2         19 closedir FSDBFH;
57 2         9 while(defined(my $entity = shift @DirEnts )){
58 6 100       34 $entity =~ /^\.\.?\Z/ and next;
59 2         13 recursive_delete "$path/$entity";
60             };
61             #1 && do{
62             # opendir FSDBFH, $path or croak "opendir $path: $!";
63             # my @DirEnts = (readdir FSDBFH);
64             # warn "after deleting, direrctoy $path contains [@DirEnts]";
65             # closedir FSDBFH;
66             # };
67 2 50       192 rmdir $path or die "could not rmdir [$path]: $!\n";
68              
69             };
70              
71             sub FETCH {
72 11     11   107 my $ref = shift;
73 11 50       32 defined (my $rootpath = $$ref) or croak "undefined rootpath";
74 11         14 my $key = shift;
75             # warn "fetching $key from $rootpath";
76 11         21 $key =~ s/^ / /; #escape leading space into two spaces
77             # defined (my $key = shift) or return undef;
78 11 50       24 $key eq '' and $key = ' EMPTY';
79 11         216 sleep 1 while -e "$rootpath LOCK$key";
80 11 100       158 -e "$rootpath$key" or return undef;
81 9 100       8547 if(-d "$rootpath$key"){
82            
83 3         15 tie my %newhash, ref($ref),"$rootpath$key";
84 3 50       47 -f "$rootpath$key/ ARRAY" and do {
85 0         0 my @TieArgs= split /\n/,tied(%newhash)->FETCHMETA('ARRAY');
86 0         0 my $classname = shift @TieArgs;
87 0         0 my @newarr;
88 0         0 eval{
89 0 0       0 tie @newarr, $classname,
90             map {
91 0 0       0 $_ eq 'DATAPATH' ?
92             "$rootpath$key/DATA" : $_
93             } @TieArgs or die <
94             Tie <<$classname, @TieArgs>> Failed with error <<$!>>
95             EOF
96 0 0       0 }; $@ and croak "string tie problem: $@";
97             return
98 0 0       0 -f "$rootpath$key/ BLESS"
99             ?
100             bless \@newarr, tied(%newhash)->FETCHMETA('BLESS')
101             :
102             \@newarr;
103              
104             };
105 3         10 $ArrayImpl{tied %newhash} = $ArrayImpl{$ref};
106             return
107 3 100       58 -f "$rootpath$key/ BLESS"
108             ?
109             bless \%newhash, tied(%newhash)->FETCHMETA('BLESS')
110             :
111             \%newhash;
112             };
113              
114 6         18 local *FSDBFH;
115 6 50       224 open FSDBFH, "<$rootpath$key"
116             or croak "cannot open $rootpath$key: $!";
117              
118 6         26 local $/ = undef;
119 6         229 ;
120             };
121              
122             {
123             my %CircleTracker;
124             sub STORE {
125 11     11   269 my ($ref , $key, $value,$Xbless) = @_;
126 11         19 my $rootpath = $$ref;
127 11         16 my ($bless, $underly);
128             # print "Storing $value to $key in $$ref\n";
129 11         156 my $rnd = join 'X',$$,time,rand(10000);
130            
131 11         19 $key =~ s/^ / /; #escape leading space into two spaces
132 11 50       27 $key eq '' and $key = ' EMPTY';
133 11         16 my $refvalue = ref $value;
134 11 100       26 if ($refvalue){
135              
136 3 50       8 if ($refvalue eq 'ARRAY'){
137 0         0 my %newhash;
138 0         0 tie %newhash, DirDB=>"$rootpath A$rnd";
139 0         0 tied(%newhash)->STOREMETA('ARRAY', join "\n", @{$ArrayImpl{$ref}});
  0         0  
140 0 0       0 my @TieArgs = map {
141 0         0 $_ eq 'DATAPATH' ? "$rootpath$key/DATA" : $_
142 0         0 } @{$ArrayImpl{$ref}};
143 0 0       0 $Xbless and
144             tied(%newhash)->STOREMETA('BLESS', $Xbless);
145 0         0 sleep 1 while !mkdir "$rootpath LOCK$key",0777;
146             {
147 1     1   16 no warnings;
  1         2  
  1         347  
  0         0  
148 0         0 rename "$rootpath$key", "$rootpath GARBAGE$rnd";
149             };
150 0         0 rename "$rootpath A$rnd","$rootpath$key";
151 0         0 my @NewArr = @$value;
152 0         0 my $CN = shift @TieArgs;
153 0         0 eval{
154 0 0       0 tie @$value,
155             # $TieArgs[0],@TieArgs[1..$#TieArgs]
156             $CN,@TieArgs
157             or die "array tie to <<$CN, @TieArgs>> failed <<$!>>\n";
158 0         0 @$value = @NewArr;
159 0         0 }; my $ERR = $@;
160 0         0 rmdir "$rootpath LOCK$key";
161 0 0       0 $ERR and croak "DirDB arrayref store problem: $ERR";
162 0         0 eval {recursive_delete "$rootpath GARBAGE$rnd"};
  0         0  
163 0 0       0 if($@){
164 0         0 croak "GC problem: $@";
165             };
166 0         0 return;
167             };
168              
169 3 50       13 if ( $CircleTracker{$value}++ ){
170 0         0 croak "$ref version $VERSION cannot store circular structures\n";
171             };
172              
173              
174 3 100       8 unless ($refvalue eq 'HASH'){
175 2         12 ($bless,$underly) = ( "$value" =~ /^(.+)=([A-Z]+)\(/ );
176             {
177 1     1   6 no warnings; #suppress uninitalized value warning
  1         2  
  1         281  
  2         3  
178 2 100       13 $underly eq 'HASH' and goto gottahash;
179 1 50       3 $underly eq 'ARRAY' and do{
180 0         0 STORE($ref, $key, [@$value],$bless);
181 0         0 return;
182             };
183 1         201 croak
184             "$ref version $VERSION only stores references to HASH, not $underly blessed to $refvalue\n";
185             }
186            
187             };
188             gottahash:
189 2 50       6 if (tied (%$value)){
190             # recursive copy
191 0 0       0 tie my %tmp, ref($ref), "$rootpath TMP$rnd" or
192             croak "tie failed: $!";
193 0         0 eval{
194             # %tmp = %$value
195              
196 0         0 my ($k,$v);
197 0         0 while(($k,$v) = each %$value){
198 0         0 $tmp{$k}=$v;
199             };
200             };
201             # print "$rootpath TMP$rnd should now contain @{[%$value]}\n";
202 0 0       0 if($@){
203 0         0 my $message = $@;
204 0         0 eval {recursive_delete "$rootpath TMP$rnd"};
  0         0  
205 0         0 croak "trouble writing [$value] to [$rootpath$key]: $message";
206              
207             };
208            
209             # print "lock (tied)";
210 0         0 sleep 1 while !mkdir "$rootpath LOCK$key",0777;
211             {
212 1     1   6 no warnings;
  1         4  
  1         672  
  0         0  
213 0         0 rename "$rootpath$key", "$rootpath GARBAGE$rnd";
214             };
215 0         0 rename "$rootpath TMP$rnd", "$rootpath$key";
216              
217             }else{ # not tied
218            
219             # cache, bless, restore
220 2         9 my @cache = %$value;
221 2         6 %$value = ();
222             # print "lock (untied)";
223 2         156 while( !mkdir "$rootpath LOCK$key",0777){
224             # print "lock conflivt: $!";
225 0         0 sleep 1;
226             };
227             {
228 1     1   8 no warnings;
  1         2  
  1         4942  
  2         3  
229 2         39 rename "$rootpath$key", "$rootpath GARBAGE$rnd";
230             };
231 2 50       15 tie %$value, ref($ref), "$rootpath$key" or
232             warn "tie to [$rootpath$key] failed: $!";
233             # print "assignment";
234             };
235              
236 2 100       7 if(defined($bless)){
237 1         6 tied(%$value)->STOREMETA('BLESS',$bless);
238             # bless $value, $bless; not needed; this is why we are here!
239             };
240              
241 2         178 GC:
242              
243             rmdir "$rootpath LOCK$key";
244              
245 2         8 delete $CircleTracker{$value};
246             # print "GC";
247 2         5 eval {recursive_delete "$rootpath GARBAGE$rnd"};
  2         9  
248 2 50       7 if($@){
249 0         0 croak "GC problem: $@";
250             };
251            
252 2         8 return;
253              
254             }; # if refvalue
255              
256             # store a scalar using write-to-temp-and-rename
257 8         18 local *FSDBFH;
258 8 50       672 open FSDBFH,">$rootpath TMP$rnd" or croak $!;
259             # defined $value and print FSDBFH $value;
260             # this will work under -l without spurious newlines
261 8 50       262 defined $value and syswrite FSDBFH, $value;
262             # print FSDBFH qq{$value};
263 8         86 close FSDBFH;
264 8 50       460 rename "$rootpath TMP$rnd" , "$rootpath$key" or
265             croak
266             " could not rename temp file to [$rootpath$key]: $!";
267             };
268             };
269              
270             sub FETCHMETA {
271 2     2 0 4 my $ref = shift;
272 2 50       10 defined (my $rootpath = $$ref) or croak "undefined rootpath";
273 2         4 my $key = ' '.shift;
274 2 50       30 -e "$rootpath$key" or return undef;
275 2 50       27 if(-d "$rootpath$key"){
276              
277 0         0 confess "Complex metadata not supported in DirDB version $VERSION";
278              
279             };
280              
281 2         8 local $/ = undef;
282 2 50       63 open FSDBFH, "<$rootpath$key"
283             or croak "cannot open $rootpath$key: $!";
284 2         55 my $result = ;
285 2         20 close FSDBFH;
286 2         20 $result;
287             };
288              
289             sub STOREMETA {
290 1     1 0 2 my $rootpath = ${+shift}; # RTFM! :)
  1         4  
291 1         2 my $key = ' '.shift;
292 1         2 my $value = shift;
293 1 50       4 ref $value and croak "DirDB does not support storing references in metadata at version $VERSION";
294 1 50       72 open FSDBFH,">$rootpath${$}TEMP$key" or croak $!;
295 1 50       31 defined $value and syswrite FSDBFH, $value;
296             # print FSDBFH $value;
297 1         11 close FSDBFH;
298 1 50       53 rename "$rootpath${$}TEMP$key", "$rootpath$key" or croak $!;
299             };
300              
301             sub DELETE {
302 5     5   117 my $ref = shift;
303 5         9 my $rootpath = ${$ref};
  5         29  
304 5         10 my $key = shift;
305 5         6 my $value;
306 5         11 $key =~ s/^ / /; #escape leading space into two spaces
307 5 50       13 $key eq '' and $key = ' EMPTY';
308              
309 5 50       74 -e "$rootpath$key" or return undef;
310             #warn "DELETING $rootpath$key";
311 5 100       65 -d "$rootpath$key" and do {
312             #warn "DELETING directory $rootpath$key";
313              
314 1 50       51 rename "$rootpath$key", "$rootpath DELETIA$key" or die "rename: $!";
315              
316 1 50       4 if(defined wantarray){
317              
318 1         2 my %rethash;
319 1         6 tie my %tmp, ref($ref), "$rootpath DELETIA$key";
320 1         5 my @keys = keys %tmp;
321 1         2 my $k;
322 1         3 for $k (@keys){
323 1         10 $rethash{$k} = delete $tmp{$k};
324             };
325            
326 1         2 eval {recursive_delete "$rootpath DELETIA$key"};
  1         5  
327 1 50       4 $@ and croak "could not delete directory $rootpath$key: $@";
328 1         5 return \%rethash;
329            
330             }else{
331 0         0 eval {recursive_delete "$rootpath DELETIA$key"};
  0         0  
332 0 0       0 $@ and croak "could not delete directory $rootpath$key: $@";
333 0         0 return {};
334             };
335             };
336              
337 4 50       11 if(defined wantarray){
338 4         14 local $/ = undef;
339 4         243 open FSDBFH, "<$rootpath$key";
340 4         101 $value = ;
341 4         48 close FSDBFH;
342             };
343 4 50       317 unlink "$rootpath$key" or die "could not unlink $rootpath$key: $!";
344 4         37 $value;
345             };
346              
347             sub CLEAR{
348 2     2   5 my $ref = shift;
349 2         4 my $path = $$ref;
350 2 50       70 opendir FSDBFH, $path or croak "opendir $path: $!";
351 2         44 my @ents = (readdir FSDBFH );
352 2         12 while(defined(my $entity = shift @ents )){
353 8 100       49 $entity =~ /^\.\.?\Z/ and next;
354 4         11 $entity = join('',$path,$entity);
355 4 100       66 if(-d $entity){
356 1         3 eval {recursive_delete $entity};
  1         3  
357 1 50       5 $@ and croak "could not delete (sub-container?) directory $entity: $@";
358             };
359 4         240 unlink $entity;
360             };
361             };
362              
363             {
364              
365             my %IteratorListings;
366              
367             sub FIRSTKEY {
368 5     5   23 my $ref = shift;
369 5         7 my $path = $$ref;
370 5 50       152 opendir FSDBFH, $path or croak "opendir $path: $!";
371 5 50       81 $IteratorListings{$ref} = [ grep { defined $_ and !($_ =~ /^\.\.?\Z/)} readdir FSDBFH ];
  11         87  
372              
373             #print "Keys in path <$path> will be shifted from <@{$IteratorListings{$ref}}>\n";
374            
375 5         16 $ref->NEXTKEY;
376             };
377              
378             sub NEXTKEY{
379 6     6   8 my $ref = shift;
380             #print "next key in path <$$ref> will be shifted from <@{$IteratorListings{$ref}}>\n";
381 6 100       7 @{$IteratorListings{$ref}} or return undef;
  6         39  
382             # warn join '|','BEGIN',@{$IteratorListings{$ref}},"END";
383 1         2 my $key = shift @{$IteratorListings{$ref}};
  1         3  
384             # warn "key: <$key>";
385 1 50       5 if ($key =~ s/^ //){
386             # warn "key: <$key>";
387 0 0       0 if ($key =~ m/^ /){
    0          
388             # we have unescaped a leading space.
389             }elsif ($key eq 'EMPTY'){
390 0         0 $key = ''
391             #}elsif($key eq 'REF'){
392             # return $ref->NEXTKEY(); # next
393             #}elsif($key =~ m/^ARRAY){
394             # return $ref->NEXTKEY(); # next
395             }else{
396             # per-container metadata does not
397             # appear in iterations through data.
398 0         0 return $ref->NEXTKEY(); # next
399             }
400             };
401 1 50       9 wantarray or return $key;
402 0         0 return @{[$key, $ref->FETCH($key)]};
  0         0  
403             };
404            
405             sub DESTROY{
406 7     7   246 delete $IteratorListings{$_[0]};
407 7         155 delete $ArrayImpl{$_[0]};
408             };
409            
410             }; # end visibility of %IteratorListings
411              
412             sub lock{
413 0     0 1   my $path = ${shift @_};
  0            
414 0           my $key= '';
415 0 0         if(@_){
416 0           $key = shift;
417 0 0         length $key or $key = ' EMPTY';
418             };
419 0           return obtain DirDB::lock "$path$key";
420             };
421              
422             package DirDB::lock;
423 1     1   14 use Carp;
  1         1  
  1         314  
424             my %OldLocks;
425             sub obtain{
426 0     0     my $path = shift;
427 0           while(!mkdir "$path LOCK",0777){
428 0           select(undef,undef,undef,0.2);
429             };
430 0           bless \$path;
431             };
432             sub release{
433 0 0   0     rmdir "$$_[0] LOCK" or croak "failure releasing $$_[0]: $!";
434 0           $OldLocks{"$_[0]"} = 1;
435             };
436             sub DESTROY{
437 0 0 0 0     delete $OldLocks{"$_[0]"} or
438             rmdir "$$_[0] LOCK" or croak "failure releasing $$_[0]: $!";
439             };
440              
441             1;
442             __END__