File Coverage

blib/lib/Tie/StorableDir.pm
Criterion Covered Total %
statement 141 154 91.5
branch 37 58 63.7
condition 5 15 33.3
subroutine 24 25 96.0
pod n/a
total 207 252 82.1


line stmt bran cond sub pod time code
1             package Tie::StorableDir;
2              
3 4     4   174968 use 5.008;
  4         18  
  4         161  
4 4     4   30 use strict;
  4         13  
  4         155  
5 4     4   23 use warnings;
  4         9  
  4         151  
6              
7 4     4   20 use Carp;
  4         16  
  4         503  
8 4     4   18309 use Tie::Hash;
  4         12055  
  4         136  
9 4     4   34 use File::Spec;
  4         7  
  4         92  
10 4     4   6837 use File::Spec::Functions;
  4         5566  
  4         427  
11 4     4   9610 use Storable;
  4         22015  
  4         315  
12 4     4   4667 use IO::Dir;
  4         172269  
  4         623  
13 4     4   71 use Scalar::Util qw(weaken);
  4         9  
  4         467  
14 4     4   3619 use Tie::StorableDir::Slot;
  4         16  
  4         13632  
15              
16             our @ISA = qw(Tie::Hash);
17             our $VERSION = 0.075;
18              
19             # if $not_exiting = 0, we don't save anything. This is set at the end of the
20             # END {} block lower. This prevents gc ordering problems from trashing the data.
21             our $not_exiting = 1;
22              
23             our %instances;
24              
25             sub _path_encode {
26 51     51   62 my $path = shift;
27 51         115 $path =~ s{([^0-9a-zA-Z. -])}{sprintf "_%02x", ord $1}ge;
  10         44  
28 51         131 return 'k'.$path;
29             }
30              
31             sub _path_decode {
32 7     7   11 my $path = shift;
33 7 100       41 $path =~ s/^k// or return undef;
34 6         15 $path =~ s{_([0-9a-zA-Z]{2})}{chr hex $1}ge;
  5         20  
35 6         13 return $path;
36             }
37              
38             sub TIEHASH {
39 8     8   7439 my ($class, %opts) = @_;
40 8   33     53 $class = ref $class || $class;
41 8         18 my $self = {};
42 8         26 bless $self, $class;
43              
44 8 100       28 if (!exists $opts{dirname}) {
45 1         211 croak "Missing required parameter dirname";
46             }
47 7 50       114 if (!-d $opts{dirname}) {
48 0         0 croak "dirname '$opts{dirname}' is not a directory.";
49             }
50 7         258 $self->{dirname} = File::Spec->rel2abs(delete $opts{dirname});
51 7         20 $self->{backedkeys} = {};
52 7 100       27 if (%opts) {
53 1         159 carp "One or more unrecognized options";
54             }
55 6         21 $instances{$self} = $self;
56 6         22 return $self;
57             }
58              
59             sub STORE {
60 26     26   1001 my ($self, $key, $value) = @_;
61 26 50 33     134 unless ($not_exiting && defined $self->{dirname}) {
62 0         0 carp "Exiting; STORE ignored.";
63 0         0 return;
64             }
65 26         56 my $ekey = _path_encode($key);
66 26         143 my $path = catfile($self->{dirname}, $ekey);
67 26         45 eval {
68 26 50       81 store \$value, $path
69             or die $!;
70             };
71 26 50       4940 if ($@) {
72 0         0 croak "Error storing: $!";
73             }
74 26 100       103 if (defined $self->{backedkeys}{$key}) {
75 17         35 my $slot = $self->{backedkeys}{$key};
76 17 50       84 $slot->disconnect if defined $slot;
77 17         134 delete $self->{backedkeys}{$key};
78             }
79             }
80              
81             sub FETCH {
82 24     24   1195 my ($self, $key) = @_;
83 24 100       72 if (defined $self->{backedkeys}{$key}) {
84 2         6 my $slot = $self->{backedkeys}{$key};
85 2         7 return $slot->getvalue;
86             }
87 22         43 my $ekey = _path_encode($key);
88 22         120 my $path = catfile($self->{dirname}, $ekey);
89 22 50       498 return undef if (!-e $path);
90 22         33 my $ref;
91 22         33 eval {
92 22         58 $ref = retrieve($path);
93             };
94 22 0 33     1801 if (!defined $ref && $@) {
95 0         0 croak "Error retrieving: $@";
96             }
97 22 100       59 if (!ref $$ref) {
98 4         34 return $$ref;
99             }
100 18         122 my $slot = new Tie::StorableDir::Slot($key, $$ref, $self);
101 18         58 my $v = $slot->getvalue;
102 18         48 $self->{backedkeys}{$key} = $slot;
103 18         65 weaken($self->{backedkeys}{$key});
104 18         116 return $v;
105             }
106              
107             sub EXISTS {
108 2     2   208 my ($self, $key) = @_;
109 2         5 $key = _path_encode($key);
110 2         11 my $path = catfile($self->{dirname}, $key);
111 2         55 return -e $path;
112             }
113              
114             sub FIRSTKEY {
115 4     4   58 my ($self) = @_;
116 4         9 delete $self->{iterator};
117 4         15 return $self->NEXTKEY;
118             }
119              
120             sub NEXTKEY {
121 10     10   15 my ($self) = @_;
122 10 100       39 if (!defined $self->{iterator}) {
123 4 50       33 $self->{iterator} = new IO::Dir($self->{dirname})
124             or croak "Cannot open directory for read: $!";
125             }
126 10         309 while (1) {
127 20         36 $! = 0;
128 20         84 my $ent = $self->{iterator}->read;
129 20 100       218 if (!defined $ent) {
130 4 50 33     19 if ($! != 0 && !($! =~ /file desc/)) {
131 0         0 croak "Cannot read directory entry: $!";
132             }
133 4         23 delete $self->{iterator};
134 4         126 return undef;
135             }
136 16         76 my $path = catfile($self->{dirname}, $ent);
137 16 100 33     664 next if (!-r $path || !-f $path);
138 7         18 my $key = _path_decode($ent);
139 7 100       14 next unless defined $key;
140 6         47 return $key;
141             }
142             }
143              
144             sub DELETE {
145 1     1   13 my ($self, $key) = @_;
146 1         4 my $oldv = $self->FETCH($key);
147 1         4 my $path = catfile($self->{dirname}, _path_encode($key));
148 1 50       24 return undef if (!-e $path);
149 1 50       133 unlink $path
150             or croak "Cannot unlink key: $!";
151 1 50       6 if (defined $self->{backedkeys}{$key}) {
152 1         3 my $slot = $self->{backedkeys}{$key};
153 1 50       8 $slot->disconnect if defined $slot;
154 1         3 delete $self->{backedkeys}{$key};
155             }
156 1         5 return $oldv;
157             }
158              
159             sub CLEAR {
160 1     1   3 my ($self) = @_;
161 1 50       9 my $dirh = new IO::Dir($self->{dirname})
162             or croak "Cannot open directory: $!";
163 1         94 while (defined($_ = $dirh->read)) {
164 7         105 my $path = catfile($self->{dirname}, $_);
165 7 100       148 next unless -f $path;
166 5 50       436 unlink $path
167             or croak "Cannot unlink $path: $!";
168             }
169 1         13 for (values %{$self->{backedkeys}}) {
  1         7  
170 0         0 my $slot = $_;
171 0 0       0 $slot->disconnect if defined $slot;
172             }
173 1         8 $self->{backedkeys} = {};
174             }
175              
176             sub SCALAR {
177 0     0   0 my ($self) = @_;
178 0         0 return $self;
179             }
180              
181             sub UNTIE {
182 1     1   13 my ($self) = @_;
183 1         2 for (values %{$self->{backedkeys}}) {
  1         5  
184 1 50       7 next unless defined $_;
185 1         3 $_->writeback;
186 1         3 $_->disconnect;
187             }
188 1         21 delete $self->{backedkeys};
189 1         3 delete $self->{dirname};
190 1         6 delete $instances{$self};
191             }
192              
193             sub DESTROY {
194 3     3   12 my $self = shift;
195 3         19 delete $instances{$self};
196             }
197              
198             END {
199 4     4   2428 for (values %instances) {
200 5         10 for (values %{$_->{backedkeys}}) {
  5         23  
201 0 0       0 next unless defined $_;
202 0         0 $_->writeback;
203 0         0 $_->disconnect;
204             }
205 5         19 delete $_->{backedkeys};
206             }
207 4         27 $not_exiting = 0;
208             }
209              
210             1;
211              
212             __END__