File Coverage

perllib/Arch/SharedIndex.pm
Criterion Covered Total %
statement 212 242 87.6
branch 54 96 56.2
condition 23 34 67.6
subroutine 32 37 86.4
pod 17 19 89.4
total 338 428 78.9


line stmt bran cond sub pod time code
1             # Arch Perl library, Copyright (C) 2004 Mikhael Goikhman
2             #
3             # This program is free software; you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation; either version 2 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program; if not, write to the Free Software
15             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16              
17 31     31   794 use 5.005;
  31         109  
  31         1412  
18 31     31   186 use strict;
  31         58  
  31         179514  
19              
20             package Arch::SharedIndex;
21              
22             sub new ($%) {
23 48     48 1 278 my $class = shift;
24 48         1368 my %init = @_;
25              
26 48 50       366 my $file = $init{file} or die "No index file given\n";
27 48 50       308 my $can_create = exists $init{can_create}? $init{can_create}: 1;
28 48 50       330 my $time_renewal = exists $init{time_renewal}? $init{time_renewal}:
    50          
29             $init{max_size}? 1: 0;
30              
31 48   50     2364 my $self = {
      50        
      100        
      100        
      100        
32             file => $file,
33             can_create => $can_create,
34             max_size => int($init{max_size} || 0),
35             expiration => int($init{expiration} || 0),
36             time_renewal => $time_renewal,
37             perl_data => $init{perl_data} || 0,
38             perl_data_indent => $init{perl_data_indent} || 0,
39             perl_data_pair => $init{perl_data_pair} || "=>",
40             };
41              
42 48         374 bless $self, $class;
43 48         490 return $self;
44             }
45              
46             sub encode_value ($$) {
47 4264     4264 1 4778 my $self = shift;
48 4264 100       12128 return unless $self->{perl_data};
49 2193         2415 my $value = shift;
50             # Data::Dumper is one of the silly-API modules; configure every time.
51             # Object oriented API is a bit slower and less backward compatible.
52             # Avoid unused variable warnings by separate declaration/assignment.
53 2193         42771 require Data::Dumper;
54 2193         211546 local $Data::Dumper::Indent;
55 2193         2350 local $Data::Dumper::Pair;
56 2193         2414 local $Data::Dumper::Quotekeys;
57 2193         2021 local $Data::Dumper::Terse;
58 2193         3469 $Data::Dumper::Indent = $self->{perl_data_indent};
59 2193         3932 $Data::Dumper::Pair = $self->{perl_data_pair};
60 2193         3011 $Data::Dumper::Quotekeys = 0;
61 2193         2542 $Data::Dumper::Terse = 1;
62 2193         8953 $$value = Data::Dumper->Dump([$$value]);
63             }
64              
65             sub decode_value ($$) {
66 3422     3422 1 6040 my $self = shift;
67 3422 100       10542 return unless $self->{perl_data};
68 2598         3424 my $value = shift;
69 2598         221828 $$value = eval $$value;
70             }
71              
72             sub delete_value ($$$) {
73 116     116 1 142 my $self = shift;
74 116         214 my ($key, $token) = @_;
75             # super class implementation
76             }
77              
78             sub fetch_value ($$$) {
79 1252     1252 1 2351 my $self = shift;
80 1252         5736 my ($key, $token) = @_;
81              
82             # super class implementation
83 1252         7822 my $value = $token;
84 1252         3594 $self->decode_value(\$value);
85 1252         10460 return $value;
86             }
87              
88             sub store_value ($$$) {
89 2137     2137 1 2651 my $self = shift;
90 2137         3267 my ($key, $token, $value) = @_;
91              
92             # super class implementation
93 2137         4237 $self->encode_value(\$value);
94 2137         82085 $token = $value;
95 2137         4226 return $token;
96             }
97              
98             sub index_list_to_hash ($$) {
99 86     86 0 236 my $self = shift;
100 86         166 my $index_list = shift;
101              
102 86         222 my $index_hash = {};
103 86         413 foreach my $entry (@$index_list) {
104 3622         51485 $index_hash->{$entry->[0]} = $entry;
105             }
106 86         283 return $index_hash;
107             }
108              
109             sub _do_delete ($$$) {
110 8     8   35 my $self = shift;
111 8         27 my $index_list = shift;
112 8         26 my $keys = shift;
113              
114 8         60 my %keys = map { $_ => 1 } @$keys;
  242         782  
115 8   100     316 for (my $num = @$index_list - 1; %keys && $num >= 0; $num--) {
116 591         795 my $index_entry = $index_list->[$num];
117 591         860 my ($key, $token) = @$index_entry;
118 591 100       2906 next unless $keys{$key};
119 232         652 $self->delete_value($key, $token);
120 232         382 splice(@$index_list, $num, 1);
121 232         1685 delete $keys{$key};
122             }
123 8         68 return @$keys - keys %keys;
124             }
125              
126             sub _do_fetch ($$$) {
127 24     24   112 my $self = shift;
128 24         49 my $index_list = shift;
129 24         37 my $keys = shift;
130 24         115 my @values = ();
131 24         241 my $index_hash = $self->index_list_to_hash($index_list);
132              
133 24         49 my $time;
134 24         68 foreach my $key (@$keys) {
135 2144         4032 my $index_entry = $index_hash->{$key};
136 2144 100       5582 my $value = $index_entry?
137             $self->fetch_value(@$index_entry): undef;
138 2144 100 66     7978 if (defined $value && $self->{time_renewal}) {
139 1154   66     4902 $time ||= time();
140 1154         1900 $index_entry->[2] = $time;
141             }
142 2144         4056 push @values, $value;
143             }
144 24         591 return \@values;
145             }
146              
147             sub _do_store ($$$) {
148 62     62   125 my $self = shift;
149 62         105 my $index_list = shift;
150 62         96 my @new_key_values = @{shift()};
  62         1101  
151 62         150 my $entries_stored = 0;
152 62         429 my $index_hash = $self->index_list_to_hash($index_list);
153              
154 62         431 my $time = time;
155 62         177 my %seen = ();
156 62         486 while (my ($key, $value) = splice(@new_key_values, 0, 2)) {
157 4264 50       9531 next if $seen{$key}; $seen{$key} = 1;
  4264         8431  
158 4264         6102 my $old_entry = $index_hash->{$key};
159 4264 100       8335 my $old_token = $old_entry? $old_entry->[1]: undef;
160 4264         11948 my $new_token = $self->store_value($key, $old_token, $value);
161 4264 50       9616 next unless defined $new_token;
162              
163 4264         11588 my $new_entry = [ $key, $new_token, $time ];
164 4264 100       7838 if (defined $old_entry) {
165 1294         4936 @$old_entry = @$new_entry;
166             } else {
167 2970         5155 push @$index_list, $new_entry;
168             }
169 4264         16433 $entries_stored++;
170             }
171 62         2027 return $entries_stored;
172             }
173              
174             sub delete ($@) {
175 4     4 1 51 my $self = shift;
176 4 50       239 my $keys = ref($_[0]) eq 'ARRAY'? shift: [ @_ ];
177 4         133 my $entries_deleted;
178              
179             $self->query_index_list(sub ($) {
180 4     4   30 my $index_list = shift;
181 4         425 $entries_deleted = $self->_do_delete($index_list, $keys);
182 4         805 });
183 4         52 return $entries_deleted;
184             }
185              
186             sub fetch ($@) {
187 4     4 1 12 my $self = shift;
188 4         14 my $single_ref = ref($_[0]) eq 'ARRAY';
189 4 50       57 my $keys = $single_ref? shift: [ @_ ];
190 4         10 my $values = [];
191              
192             $self->query_index_list(sub ($) {
193 4     4   14 my $index_list = shift;
194 4         32 $values = $self->_do_fetch($index_list, $keys);
195 4         85 });
196 4 50       219 return $single_ref? $values: wantarray? @$values: $values->[0];
    50          
197             }
198              
199             sub store ($%) {
200 30     30 1 80 my $self = shift;
201 0         0 my $new_key_values =
202 30 50       760 ref($_[0]) eq 'HASH'? [ %{shift()} ]: # unordered
    50          
203             ref($_[0]) eq 'ARRAY'? shift: [ @_ ]; # ordered
204 30         60 my $entries_stored;
205              
206             $self->query_index_list(sub ($) {
207 30     30   165 my $index_list = shift;
208 30         155 $entries_stored = $self->_do_store($index_list, $new_key_values);
209 30         265 });
210 30         490 return $entries_stored;
211             }
212              
213             sub fetch_store ($$@) {
214 20     20 1 60 my $self = shift;
215 20   50     160 my $code = shift || die "No code given";
216 20         60 my $single_ref = ref($_[0]) eq 'ARRAY';
217 20 50       160 my $keys = $single_ref? shift: [ @_ ];
218 20         40 my $values;
219              
220             $self->query_index_list(sub ($) {
221 20     20   50 my $index_list = shift;
222 20         130 $values = $self->_do_fetch($index_list, $keys);
223 20         40 my (@new_keys, @missing_idxs);
224 20         40 my $run_idx = 0;
225 20 100       90 @new_keys = grep { (defined $values->[$run_idx]? 0:
  1980         3950  
226             push @missing_idxs, $run_idx) * ++$run_idx } @$keys;
227              
228 20 50 33     190 if ($ENV{DEBUG} && ("$ENV{DEBUG}" & "\2") ne "\0") {
229 0 0       0 my $status = @new_keys? @new_keys == @$keys? "miss":
    0          
230             "partial hit-miss": "hit";
231 0         0 my $keystr = join(', ', @$keys);
232 0 0       0 substr($keystr, 57) = "..." if length($keystr) > 60;
233 0         0 print STDERR "Shared fetch_store ($keystr): $status\n";
234             }
235 20 100       80 return unless @new_keys;
236              
237 10 50       30 my @new_key_values = map { $_ => ref($code) ne 'CODE'?
  990         2520  
238             $code: &$code($_) } @new_keys;
239 10         170 my $num_stored = $self->_do_store($index_list, \@new_key_values);
240 10 50       60 warn "fetch_store: not all new values are actually stored\n"
241             if $num_stored < @new_keys;
242 990         1660 @$values[@missing_idxs] =
243 10         100 @new_key_values[map { $_ * 2 + 1 } 0 .. @new_keys - 1];
244 20         220 });
245 20 50       1200 return $single_ref? $values: wantarray? @$values: $values->[0];
    50          
246             }
247              
248             sub keys ($) {
249 4     4 1 17 my $self = shift;
250 4         16 my @keys;
251              
252             $self->query_index_list(sub ($) {
253 4     4   14 my $index_list = shift;
254 4         24 @keys = map { $_->[0] } @$index_list;
  164         777  
255 4         89 });
256 4 50       181 return wantarray? @keys: \@keys;
257             }
258              
259             sub values ($) {
260 4     4 1 13 my $self = shift;
261 4         16 my @values;
262              
263             $self->query_index_list(sub ($) {
264 4     4   12 my $index_list = shift;
265 4         13 @values = map { $self->fetch_value(@$_) } @$index_list;
  164         682  
266 4         54 });
267 4 50       94 return wantarray? @values: \@values;
268             }
269              
270             sub hash ($) {
271 0     0 1 0 my $self = shift;
272 0         0 my %hash;
273              
274             $self->query_index_list(sub ($) {
275 0     0   0 my $index_list = shift;
276 0         0 %hash = map { $_->[0] => $self->fetch_value(@$_) } @$index_list;
  0         0  
277 0         0 });
278 0 0       0 return wantarray? %hash: \%hash;
279             }
280              
281             sub list ($) {
282 0     0 1 0 my $self = shift;
283 0         0 my @list;
284              
285             $self->query_index_list(sub ($) {
286 0     0   0 my $index_list = shift;
287 0         0 @list = map { [ $_->[0] => $self->fetch_value(@$_) ] }
  0         0  
288             @$index_list;
289 0         0 });
290 0 0       0 return wantarray? @list: \@list;
291             }
292              
293             sub grep ($;$) {
294 4     4 1 13 my $self = shift;
295 4   100 82   41 my $code = shift || sub { $_[1] };
  82         187  
296 4         244 my @keys;
297              
298             $self->query_index_list(sub ($) {
299 4     4   9 my $index_list = shift;
300 100         262 @keys = map { $_->[0] }
  164         588  
301 4         15 grep { &$code($_->[0], $self->fetch_value(@$_)) }
302             @$index_list;
303 4         51 });
304 4 50       226 return wantarray? @keys: \@keys;
305             }
306              
307             sub filter ($;$) {
308 4     4 1 34 my $self = shift;
309 4   50 0   38 my $code = shift || sub { $_[1] };
  0         0  
310 4         8 my @keys;
311              
312             $self->query_index_list(sub ($) {
313 4     4   498 my $index_list = shift;
314 46         268 @keys = map { $_->[0] }
  249         834  
315 4         20 grep { &$code($_->[0], $self->fetch_value(@$_)) }
316             @$index_list;
317 4         127 $self->_do_delete($index_list, \@keys);
318 4         543 });
319 4 50       145 return wantarray? @keys: \@keys;
320             }
321              
322             sub update ($$;$) {
323 22     22 1 124 my $self = shift;
324 22         72 my $code = shift;
325 22         110 my $grep_code = shift;
326 22 50       221 die "No code or value given" unless defined $code;
327 22         111 my $entries_updated;
328              
329             $self->query_index_list(sub ($) {
330 22     22   88 my $index_list = shift;
331 304 100       1616 $entries_updated = $self->_do_store($index_list, [
332 1478 50       15180 map { $_->[0] => ref($code) ne 'CODE'? $code:
333             &$code($_->[0], $self->fetch_value(@$_)) }
334 22         195 grep { $grep_code? &$grep_code(
335             $_->[0], $self->fetch_value(@$_)): 1 }
336             @$index_list
337             ]);
338 22         2030 });
339 22         509 return $entries_updated;
340             }
341              
342             sub query_index_list ($$) {
343 96     96 0 372 my $self = shift;
344 96         188 my $code = shift;
345              
346 96         584 my $file = $self->{file};
347 96 100 66     7243 if (!-f $file && $self->{can_create}) {
348 30 50       3765 open FH, ">$file" or die "Can't create index file ($file)\n";
349 30         455 close FH;
350             }
351 96 50       2466 -f $file or die "No index file ($file)\n";
352              
353 96 50       6746 open FH, "+<$file" or die "Can't open $file for updating: $!\n";
354 96         3429441 flock FH, 2; # wait for exclusive lock
355 96         937 seek FH, 0, 0; # rewind to beginning
356 96         19561 my @content = ; # get current content
357 96         721 chomp @content;
358              
359 9480 50       15226 my $index_list = [ grep { defined } map {
  4740         39577  
360 96         325 /^(\d+)\t(.+?)\t(.*)/? [ $2, $3, $1 ]:
361             warn("Corrupt line ($_) in $file; ignored\n"), undef
362             } @content ];
363              
364 96 50       804 if ($self->{expiration}) {
365 0         0 my $time = time();
366 0         0 my $diff = $self->{expiration};
367 0         0 my @expired_keys = map { $_->[0] }
  0         0  
368 0         0 grep { $time - $_->[2] > $diff } @$index_list;
369 0 0       0 $self->_do_delete($index_list, \@expired_keys) if @expired_keys;
370             }
371              
372             # apply callback filter
373 96         627 &$code($index_list);
374              
375 96 50 33     1917 if ($self->{max_size} && @$index_list > $self->{max_size}) {
376 0         0 my @excess_nums = (0 .. @$index_list - $self->{max_size} - 1);
377 0         0 my @excess_keys = map { $_->[0] } (@$index_list)[@excess_nums];
  0         0  
378 0         0 $self->_do_delete($index_list, \@excess_keys);
379             }
380              
381 96         713 my @new_content = map { "$_->[2]\t$_->[0]\t$_->[1]" } @$index_list;
  7478         95279  
382 96         2256 my $is_changed = join('', @content) ne join('', @new_content);
383              
384 96 100       327 if ($is_changed) {
385 58         617 seek FH, 0, 0; # rewind again
386 58         4975 truncate FH, 0; # empty the file
387 58         217 print FH map { "$_$/" } @new_content;
  4782         14527  
388             }
389 96         87627 close FH; # release file
390             }
391              
392             1;
393              
394             __END__