File Coverage

blib/lib/DirDB/Storable.pm
Criterion Covered Total %
statement 147 200 73.5
branch 52 110 47.2
condition 4 6 66.6
subroutine 16 21 76.1
pod 0 3 0.0
total 219 340 64.4


line stmt bran cond sub pod time code
1             package DirDB::Storable;
2              
3             require 5.005_62;
4 1     1   8775 use strict;
  1         2  
  1         39  
5 1     1   6 use warnings;
  1         3  
  1         33  
6 1     1   5 use Carp;
  1         6  
  1         90  
7              
8 1     1   1498 use Storable qw(nstore retrieve );
  1         5435  
  1         1247  
9              
10             our $VERSION = '0.07';
11              
12             sub TIEHASH {
13 4     4   249 my $self = shift;
14 4 50       16 my $rootpath = shift or croak "we need a rootpath";
15 4         17 $rootpath =~ s#/+$##; # lose trailing slash(es)
16 4 50 66     105 -d $rootpath or
17             mkdir $rootpath, 0777 or
18             croak "could not create dir $rootpath: $!";
19              
20 4         24 bless \"$rootpath/", $self;
21             };
22              
23             sub TIEARRAY {
24 0     0   0 confess "DirDB does not support arrays yet";
25             };
26              
27             sub TIESCALAR {
28 0     0   0 confess "DirDB does not support scalars yet -- try Tie::Slurp";
29             };
30              
31              
32             sub EXISTS {
33 0     0   0 my $rootpath = ${+shift};
  0         0  
34 0         0 my $key = shift;
35 0         0 $key =~ s/^ / /; #escape leading space into two spaces
36             # defined (my $key = shift) or return undef;
37 0 0       0 $key eq '' and $key = ' EMPTY';
38 0 0       0 -e "$rootpath$key" or -e "$rootpath LOCK$key";
39             };
40              
41             sub recursive_delete($);
42             sub recursive_delete($){
43             # unlink a file or rm -rf a directory tree
44 8     8 0 12 my $path = shift;
45 8 100 66     236 unless ( -d $path and ! -l $path ){
46 5         162 unlink $path;
47 5 50       56 -e $path and die "Could not unlink [$path]: $!\n";
48 5         14 return;
49             };
50 3 50       194 opendir FSDBFH, $path or croak "opendir $path: $!";
51 3         58 my @DirEnts = (readdir FSDBFH);
52 3         12 while(defined(my $entity = shift @DirEnts )){
53 8 100       55 $entity =~ /^\.\.?\Z/ and next;
54 2         12 recursive_delete "$path/$entity";
55             };
56 3 50       204 rmdir $path or die "could not rmdir [$path]: $!\n";
57              
58             };
59              
60             sub FETCH {
61 10     10   245 my $ref = shift;
62 10 50       26 defined (my $rootpath = $$ref) or croak "undefined rootpath";
63 10         15 my $key = shift;
64 10         15 $key =~ s/^ / /; #escape leading space into two spaces
65             # defined (my $key = shift) or return undef;
66 10 50       24 $key eq '' and $key = ' EMPTY';
67 10         128 sleep 1 while -e "$rootpath LOCK$key";
68 10 100       157 -e "$rootpath$key" or return undef;
69 8 100       96 if(-d "$rootpath$key"){
70            
71 3 100       47 if (-e "$rootpath$key/ Storable"){
72 2         12 return retrieve("$rootpath$key/ Storable")
73             };
74              
75 1         7 tie my %newhash, ref($ref),"$rootpath$key";
76 1         9 return \%newhash;
77             };
78              
79 5         40 local *FSDBFH;
80 5 50       164 open FSDBFH, "<$rootpath$key"
81             or croak "cannot open $rootpath$key: $!";
82              
83 5         22 local $/ = undef;
84 5         246 ;
85             };
86              
87             {
88             my %CircleTracker;
89             sub STORE {
90 11     11   565 my ($ref , $key, $value) = @_;
91 11         19 my $rootpath = $$ref;
92             # print "Storing $value to $key in $$ref\n";
93 11         157 my $rnd = join 'X',$$,time,rand(10000);
94            
95 11         22 $key =~ s/^ / /; #escape leading space into two spaces
96 11 50       25 $key eq '' and $key = ' EMPTY';
97 11         17 my $refvalue = ref $value;
98 11 100       23 if ($refvalue){
99              
100 3 50       13 if ( $CircleTracker{$value}++ ){
101 0         0 croak "$ref version $VERSION cannot store circular structures\n";
102             };
103              
104 3 100       9 unless ($refvalue eq 'HASH'){
105             # croak
106             # "$ref version $VERSION only stores references to HASH, not $refvalue\n";
107 2 50       151 mkdir "$rootpath TMP$rnd" or croak "mkdir failed: $!";
108 2         16 nstore $value, "$rootpath TMP$rnd/ Storable";
109 2         619 while( !mkdir "$rootpath LOCK$key",0777){
110             # print "lock conflivt: $!";
111 0         0 sleep 1;
112             };
113             {
114 1     1   11 no warnings;
  1         3  
  1         642  
  2         4  
115 2         32 rename "$rootpath$key", "$rootpath GARBAGE$rnd";
116             };
117 2         87 rename "$rootpath TMP$rnd", "$rootpath$key";
118 2         17 goto GC;
119            
120             }; # end Storable use
121            
122 1 50       5 if (tied (%$value)){
123             # recursive copy
124 0 0       0 tie my %tmp, ref($ref), "$rootpath TMP$rnd" or
125             croak "tie failed: $!";
126 0         0 eval{
127             # %tmp = %$value
128              
129 0         0 my ($k,$v);
130 0         0 while(($k,$v) = each %$value){
131 0         0 $tmp{$k}=$v;
132             };
133             };
134             # print "$rootpath TMP$rnd should now contain @{[%$value]}\n";
135 0 0       0 if($@){
136 0         0 my $message = $@;
137 0         0 eval {recursive_delete "$rootpath TMP$rnd"};
  0         0  
138 0         0 croak "trouble writing [$value] to [$rootpath$key]: $message";
139              
140             };
141            
142             # print "lock (tied)";
143 0         0 sleep 1 while !mkdir "$rootpath LOCK$key",0777;
144             {
145 1     1   6 no warnings;
  1         3  
  1         136  
  0         0  
146 0         0 rename "$rootpath$key", "$rootpath GARBAGE$rnd";
147             };
148 0         0 rename "$rootpath TMP$rnd", "$rootpath$key";
149              
150             }else{ # not tied
151            
152             # cache, bless, restore
153 1         5 my @cache = %$value;
154 1         3 %$value = ();
155             # print "lock (untied)";
156 1         57 while( !mkdir "$rootpath LOCK$key",0777){
157             # print "lock conflivt: $!";
158 0         0 sleep 1;
159             };
160             {
161 1     1   6 no warnings;
  1         2  
  1         2141  
  1         2  
162 1         20 rename "$rootpath$key", "$rootpath GARBAGE$rnd";
163             };
164 1 50       8 tie %$value, ref($ref), "$rootpath$key" or
165             warn "tie to [$rootpath$key] failed: $!";
166             # print "assignment";
167 1         5 %$value = @cache;
168             };
169            
170 3         338 GC:
171              
172             rmdir "$rootpath LOCK$key";
173              
174 3         10 delete $CircleTracker{$value};
175             # print "GC";
176 3         7 eval {recursive_delete "$rootpath GARBAGE$rnd"};
  3         13  
177 3 50       9 if($@){
178 0         0 croak "GC problem: $@";
179             };
180 3         12 return;
181              
182             }; # if refvalue
183              
184             # store a scalar using write-to-temp-and-rename
185 8         17 local *FSDBFH;
186 8 50       766 open FSDBFH,">$rootpath TMP$rnd" or croak $!;
187             # defined $value and print FSDBFH $value;
188             # this will work under -l without spurious newlines
189 8 50       293 defined $value and syswrite FSDBFH, $value;
190             # print FSDBFH qq{$value};
191 8         86 close FSDBFH;
192 8 50       609 rename "$rootpath TMP$rnd" , "$rootpath$key" or
193             croak
194             " could not rename temp file to [$rootpath$key]: $!";
195             };
196             };
197              
198             sub FETCHMETA {
199 0     0 0 0 my $ref = shift;
200 0 0       0 defined (my $rootpath = $$ref) or croak "undefined rootpath";
201 0         0 my $key = ' '.shift;
202 0 0       0 -e "$rootpath$key" or return undef;
203 0 0       0 if(-d "$rootpath$key"){
204              
205 0         0 confess "Complex metadata not supported in DirDB version $VERSION";
206              
207             };
208              
209 0         0 local $/ = undef;
210 0 0       0 open FSDBFH, "<$rootpath$key"
211             or croak "cannot open $rootpath$key: $!";
212 0         0 ;
213             };
214              
215             sub STOREMETA {
216 0     0 0 0 my $rootpath = ${+shift}; # RTFM! :)
  0         0  
217 0         0 my $key = ' '.shift;
218 0         0 my $value = shift;
219 0 0       0 ref $value and croak "DirDB does not support storing references in metadata at version $VERSION";
220 0 0       0 open FSDBFH,">$rootpath${$}TEMP$key" or croak $!;
221 0 0       0 defined $value and syswrite FSDBFH, $value;
222             # print FSDBFH $value;
223 0         0 close FSDBFH;
224 0 0       0 rename "$rootpath${$}TEMP$key", "$rootpath$key" or croak $!;
225             };
226              
227             sub DELETE {
228 7     7   186 my $ref = shift;
229 7         9 my $rootpath = ${$ref};
  7         14  
230 7         12 my $key = shift;
231 7         9 my $value;
232 7         13 $key =~ s/^ / /; #escape leading space into two spaces
233 7 50       16 $key eq '' and $key = ' EMPTY';
234              
235 7 50       103 -e "$rootpath$key" or return undef;
236              
237              
238 7 100       162 -d "$rootpath$key" and do {
239              
240 2         98 rename "$rootpath$key", "$rootpath DELETIA$key";
241              
242 2 50       8 if(defined wantarray){
243 2 100       28 if (-e "$rootpath DELETIA$key/ Storable"){
244 1         9 $value= retrieve("$rootpath DELETIA$key/ Storable");
245              
246 1         72 eval {recursive_delete "$rootpath DELETIA$key"};
  1         4  
247 1 50       5 $@ and croak "could not delete directory $rootpath$key: $@";
248 1         5 return $value;
249              
250            
251             };
252 1         2 my %rethash;
253 1         7 tie my %tmp, ref($ref), "$rootpath DELETIA$key";
254 1         5 my @keys = keys %tmp;
255 1         2 my $k;
256 1         675 for $k (@keys){
257 2         15 $rethash{$k} = delete $tmp{$k};
258             };
259            
260 1         3 eval {recursive_delete "$rootpath DELETIA$key"};
  1         4  
261 1 50       6 $@ and croak "could not delete directory $rootpath$key: $@";
262 1         7 return \%rethash;
263            
264             }else{
265 0         0 eval {recursive_delete "$rootpath DELETIA$key"};
  0         0  
266 0 0       0 $@ and croak "could not delete directory $rootpath$key: $@";
267 0         0 return {};
268             };
269             };
270              
271 5 50       12 if(defined wantarray){
272 5         17 local $/ = undef;
273 5         629 open FSDBFH, "<$rootpath$key";
274 5         117 $value = ;
275             };
276 5         189 unlink "$rootpath$key";
277 5         68 $value;
278             };
279              
280             sub CLEAR{
281 2     2   4 my $ref = shift;
282 2         5 my $path = $$ref;
283 2 50       200 opendir FSDBFH, $path or croak "opendir $path: $!";
284 2         61 my @ents = (readdir FSDBFH );
285 2         11 while(defined(my $entity = shift @ents )){
286 8 100       196 $entity =~ /^\.\.?\Z/ and next;
287 4         8 $entity = join('',$path,$entity);
288 4 100       52 if(-d $entity){
289 1         3 eval {recursive_delete $entity};
  1         78  
290 1 50       3 $@ and croak "could not delete (sub-container?) directory $entity: $@";
291             };
292 4         292 unlink $entity;
293             };
294             };
295              
296             {
297              
298             my %IteratorListings;
299              
300             sub FIRSTKEY {
301 2     2   12 my $ref = shift;
302 2         3 my $path = $$ref;
303 2 50       138 opendir FSDBFH, $path or croak "opendir $path: $!";
304 2         43 $IteratorListings{$ref} = [ grep {!($_ =~ /^\.\.?\Z/)} readdir FSDBFH ];
  6         37  
305              
306             #print "Keys in path <$path> will be shifted from <@{$IteratorListings{$ref}}>\n";
307            
308 2         9 $ref->NEXTKEY;
309             };
310              
311             sub NEXTKEY{
312 4     4   13 my $ref = shift;
313             #print "next key in path <$$ref> will be shifted from <@{$IteratorListings{$ref}}>\n";
314 4 100       6 @{$IteratorListings{$ref}} or return undef;
  4         20  
315 2         2 my $key = shift @{$IteratorListings{$ref}};
  2         5  
316 2 50       7 if ($key =~ s/^ //){
317 0 0       0 if ($key = m/^ /){
    0          
318             # we have unescaped a leading space.
319             }elsif ($key eq 'EMPTY'){
320 0         0 $key = ''
321             #}elsif($key eq 'REF'){
322             # return $ref->NEXTKEY(); # next
323             #}elsif($key =~ m/^ARRAY){
324             # return $ref->NEXTKEY(); # next
325             }else{
326             # per-container metadata does not
327             # appear in iterations through data.
328 0         0 return $ref->NEXTKEY(); # next
329             }
330             };
331 2 50       11 wantarray or return $key;
332 0         0 return @{[$key, $ref->FETCH($key)]};
  0         0  
333             };
334            
335             sub DESTROY{
336 4     4   427 delete $IteratorListings{$_[0]};
337             };
338            
339             };
340              
341              
342              
343              
344             1;
345             __END__