File Coverage

blib/lib/DiaColloDB/EnumFile.pm
Criterion Covered Total %
statement 12 288 4.1
branch 0 172 0.0
condition 0 101 0.0
subroutine 4 35 11.4
pod 27 28 96.4
total 43 624 6.8


line stmt bran cond sub pod time code
1             ## -*- Mode: CPerl -*-
2             ## File: DiaColloDB::EnumFile.pm
3             ## Author: Bryan Jurish <moocow@cpan.org>
4             ## Description: collocation db, symbol<->integer enum
5              
6             package DiaColloDB::EnumFile;
7 1     1   8 use DiaColloDB::Persistent;
  1         2  
  1         39  
8 1     1   6 use DiaColloDB::Utils qw(:fcntl :file :pack :json :regex);
  1         2  
  1         38  
9 1     1   385 use Fcntl qw(:DEFAULT :seek);
  1         2  
  1         35  
10 1     1   482 use strict;
  1         3  
  1         4250  
11              
12             ##==============================================================================
13             ## Globals & Constants
14              
15             our @ISA = qw(DiaColloDB::Persistent);
16              
17             ##==============================================================================
18             ## Constructors etc.
19              
20             ## $cldb = CLASS_OR_OBJECT->new(%args)
21             ## + %args, object structure:
22             ## (
23             ## base => $base, ##-- database basename; use files "${base}.es", "${base}.esx", "${base}.eix", "${base}.hdr"
24             ## perms => $perms, ##-- default: 0666 & ~umask
25             ## flags => $flags, ##-- default: 'r'
26             ## pack_i => $pack_i, ##-- integer pack template (default='N')
27             ## pack_o => $pack_o, ##-- file offset pack template (default='N')
28             ## pack_l => $pack_l, ##-- string-length pack template (default='n')
29             ## pack_s => $pack_s, ##-- string pack template (default=undef) for text i/o
30             ## size => $size, ##-- number of mapped symbols, like scalar(@i2s)
31             ## utf8 => $bool, ##-- true iff strings are stored as utf8 (default, used by re2i())
32             ## ##
33             ## ##-- in-memory construction and caching
34             ## s2i => \%s2i, ##-- maps symbols to integers
35             ## i2s => \@i2s, ##-- maps integers to symbols
36             ## dirty => $bool, ##-- true if in-memory structures are not in-sync with file data
37             ## loaded => $bool, ##-- true if file data has been loaded to memory
38             ## shared => $bool, ##-- true to avoid closing filehandles on close() or DESTROY() (default=false)
39             ## ##
40             ## ##-- pack lengths (after open())
41             ## len_i => $len_i, ##-- packsize($pack_i)
42             ## len_o => $len_o, ##-- packsize($pack_o)
43             ## len_l => $len_l, ##-- packsize($pack_l)
44             ## len_sx => $len_sx, ##-- $len_o + $len_i
45             ## ##
46             ## ##-- filehandles (after open())
47             ## sfh => $sfh, ##-- $base.es : pack("(${pack_l}/A)*", @$i2s)
48             ## ixfh => $ixfh, ##-- $base.eix : [$i] => pack("${pack_o}", $offset_in_sfh_of_string_with_id_i)
49             ## sxfh => $sxfh, ##-- $base.esx : [$j] => pack("${pack_o}${pack_i}", $offset_in_sfh_of_string_with_sortindex_j_and_id_i, $i)
50             ## )
51             sub new {
52 0     0 1   my $that = shift;
53 0   0       my $enum = bless({
54             base => undef,
55             perms => (0666 & ~umask),
56             flags => 'r',
57             utf8 => 1,
58             size => 0,
59             pack_i => 'N',
60             pack_o => 'N',
61             pack_l => 'n',
62             pack_s => undef,
63              
64             s2i => {},
65             i2s => [],
66             dirty=>0,
67             loaded=>0,
68              
69             #len_i => undef,
70             #len_o => undef,
71             #len_l => undef,
72             #len_sx => undef,
73              
74             #sfh =>undef,
75             #ixfh =>undef,
76             #sxfh =>undef,
77              
78             @_, ##-- user arguments
79             },
80             ref($that)||$that);
81 0           $enum->{class} = ref($enum);
82 0   0       $enum->{s2i} //= {};
83 0   0       $enum->{i2s} //= [];
84 0 0         return defined($enum->{base}) ? $enum->open($enum->{base}) : $enum;
85             }
86              
87             sub DESTROY {
88 0 0   0     $_[0]->close() if ($_[0]->opened);
89             }
90              
91             ## $enum = $enum->promote($class,$force)
92             ## + promote to $class
93             ## + if $force is false (default), promotion to CLASS::MMap will be disabled
94             sub promote {
95 0     0 1   my ($enum,$class,$force) = @_;
96 0 0 0       return $enum if (UNIVERSAL::isa($enum,$class)
      0        
      0        
97             || (!$force && UNIVERSAL::isa((ref($enum)||$enum)."::MMap", $class)));
98 0 0         return $class->new() if (!ref($enum));
99 0 0         %$enum = ((UNIVERSAL::can($class,'new') ? %{$class->new} : qw()),%$enum);
  0            
100 0           return bless($enum,$class);
101             }
102              
103             ##==============================================================================
104             ## I/O
105              
106             ##--------------------------------------------------------------
107             ## I/O: open/close (file)
108              
109             ## $enum_or_undef = $enum->open($base,$flags)
110             ## $enum_or_undef = $enum->open($base)
111             ## $enum_or_undef = $enum->open()
112             ## + opens file(s), clears {loaded} flag
113             sub open {
114 0     0 1   my ($enum,$base,$flags) = @_;
115 0   0       $base //= $enum->{base};
116 0   0       $flags //= $enum->{flags};
117 0 0         $enum->close() if ($enum->opened);
118 0           $enum->{base} = $base;
119 0           $enum->{flags} = $flags = fcflags($flags);
120 0 0 0       if (fcread($flags) && !fctrunc($flags)) {
121 0 0         $enum->loadHeader()
122             or $enum->logconess("failed to load header from '$enum->{base}.hdr': $!");
123             return $enum->promote($enum->{hclass})->open($base,$flags)
124 0 0 0       if ($enum->{hclass} && !$enum->isa($enum->{hclass})); ##-- auto-promote based on header data
125             }
126              
127             $enum->{sfh} = fcopen("$base.es", $flags, $enum->{perms})
128 0 0         or $enum->logconfess("open failed for $base.es: $!");
129             $enum->{ixfh} = fcopen("$base.eix", $flags, $enum->{perms})
130 0 0         or $enum->logconfess("open failed for $base.eix: $!");
131             $enum->{sxfh} = fcopen("$base.esx", $flags, $enum->{perms})
132 0 0         or $enum->logconfess("open failed for $base.esx: $!");
133 0           binmode($_,':raw') foreach (@$enum{qw(sfh ixfh sxfh)});
134              
135             ##-- pack lengths
136             #use bytes; ##-- deprecated in perl v5.18.2
137 0           $enum->{len_i} = packsize($enum->{pack_i});
138 0           $enum->{len_o} = packsize($enum->{pack_o});
139 0           $enum->{len_l} = packsize($enum->{pack_l});
140 0           $enum->{len_sx} = $enum->{len_o} + $enum->{len_i};
141              
142             ##-- flags
143 0           $enum->{loaded} = 0;
144              
145 0           return $enum;
146             }
147              
148             ## $enum_or_undef = $enum->close()
149             sub close {
150 0     0 1   my $enum = shift;
151 0 0 0       if ($enum->opened && fcwrite($enum->{flags})) {
152 0 0         $enum->flush() or return undef;
153             }
154 0 0         if (!$enum->{shared}) {
155 0 0 0       !defined($enum->{sxfh}) or $enum->{sxfh}->close() or return undef;
156 0 0 0       !defined($enum->{ixfh}) or $enum->{ixfh}->close() or return undef;
157 0 0 0       !defined($enum->{sfh}) or $enum->{sfh}->close() or return undef;
158             }
159 0           delete @$enum{qw(sxfh ixfh sfh)};
160 0   0       $enum->{s2i} //= {};
161 0   0       $enum->{i2s} //= [];
162 0           undef $enum->{base};
163 0           return $enum;
164             }
165              
166             ## $bool = $enum->opened()
167             sub opened {
168 0     0 1   my $enum = shift;
169             return
170             (
171             #defined($enum->{base}) &&
172             defined($enum->{sfh})
173             && defined($enum->{ixfh})
174             && defined($enum->{sxfh})
175 0   0       );
176             }
177              
178             ## $bool = $enum->reopen()
179             ## + re-opens datafiles
180             sub reopen {
181 0     0 0   my $enum = shift;
182 0   0       my $base = $enum->{base} || "$enum";
183             return (
184             $enum->opened
185             && fh_reopen($enum->{sfh}, "$base.es")
186             && fh_reopen($enum->{ixfh}, "$base.eix")
187 0   0       && fh_reopen($enum->{sxfh}, "$base.esx")
188             );
189             }
190              
191             ## $bool = $enum->dirty()
192             ## + returns true iff some in-memory structures haven't been flushed to disk
193             sub dirty {
194 0     0 1   return $_[0]{dirty}; #@{$_[0]{i2s}} || %{$_[0]{s2i}};
195             }
196              
197             ## $bool = $enum->loaded()
198             ## + returns true iff in-memory structures have been populated from disk
199             sub loaded {
200 0     0 1   return $_[0]{loaded};
201             }
202              
203             ## $bool = $enum->rollback()
204             ## + drops in-memory structures
205             ## + invalidates any old references to {s2i}, {i2s} (but doesn't empty them if you need to keep a reference)
206             ## + clears {dirty} flag
207             sub rollback {
208 0     0 1   my $enum = shift;
209 0           $enum->{i2s} = [];
210 0           $enum->{s2i} = {};
211 0           $enum->{dirty} = 0;
212 0           return $enum;
213             }
214              
215             ## $bool = $enum->flush()
216             ## $bool = $enum->flush($force)
217             ## + flush in-memory structures to disk
218             ## + no-op unless $force or $enum->dirty() is true
219             ## + clobbers any old disk-file contents with in-memory maps
220             ## + enum must be opened in write-mode
221             ## + invalidates any old references to {s2i}, {i2s} (but doesn't empty them if you need to keep a reference)
222             ## + clears {dirty} flag
223             sub flush {
224 0     0 1   my ($enum,$force) = @_;
225 0 0 0       return undef if (!$enum->opened || !fcwrite($enum->{flags}));
226 0 0 0       return $enum if (!$force && !$enum->dirty);
227              
228             ##-- save header
229 0 0         $enum->saveHeader()
230             or $enum->logconfess("flush(): failed to store header $enum->{base}.hdr: $!");
231              
232             #use bytes; ##-- deprecated in perl v5.18.2
233 0           my ($sfh,$ixfh,$sxfh) = @$enum{qw(sfh ixfh sxfh)};
234 0           $sfh->seek(0,SEEK_SET);
235 0           $ixfh->seek(0,SEEK_SET);
236 0           $sxfh->seek(0,SEEK_SET);
237              
238             ##-- dump $base.es, $base.eix
239             #no warnings 'uninitialized';
240 0           my $i2s = $enum->{i2s};
241 0           my $utf8 = $enum->{utf8};
242 0           my ($pack_o,$pack_l,$len_l) = @$enum{qw(pack_o pack_l len_l)};
243 0           my $i2off = []; ##-- >[$i] => $offset
244 0           my $off = 0;
245 0           my $i = 0;
246 0           my ($s);
247 0           foreach (@$i2s) {
248 0   0       $s = ($_ //= '');
249 0 0 0       utf8::encode($s) if ($utf8 && utf8::is_utf8($s));
250 0 0         $sfh->print(pack("${pack_l}/A", $s))
251             or $enum->logconfess("flush(): failed to write string '$s' at offset $off to $enum->{base}.es");
252 0 0         $ixfh->print(pack($pack_o,$off))
253             or $enum->logconfess("flush(): failed to write ix-record for id=$i to $enum->{base}.eix");
254 0           push(@$i2off, $off);
255 0           $off += $len_l + length($s);
256 0           ++$i;
257             }
258 0           CORE::truncate($sfh, $sfh->tell());
259 0           CORE::truncate($ixfh, $ixfh->tell());
260              
261             ##-- dump $base.esx
262 0           my $pack_sx = $enum->{pack_o}.$enum->{pack_i};
263 0           foreach $i (sort {$i2s->[$a] cmp $i2s->[$b]} (0..$#$i2s)) {
  0            
264 0 0         $sxfh->print(pack($pack_sx, $i2off->[$i], $i))
265             or $enum->logconfess("flush(): failed to dump sx-record for id $i to $enum->{base}.esx");
266             }
267 0           CORE::truncate($sxfh, $sxfh->tell());
268              
269             ##-- clear in-memory structures (but don't clobber existing references; used for xenum by DiaColloDB::create())
270 0           $enum->rollback();
271 0 0 0       $enum->reopen() or return undef if ((caller(1))[3] !~ /::close$/);
272 0           return $enum;
273             }
274              
275              
276             ##--------------------------------------------------------------
277             ## I/O: memory <-> file
278              
279             ## \@i2s = $enum->toArray()
280             ## + array items are still encoded
281             sub toArray {
282 0     0 1   my $enum = shift;
283 0 0 0       return $enum->{i2s} if ($enum->loaded || !$enum->opened);
284             #use bytes; ##-- deprecated in perl v5.18.2
285 0           my $pack_l = $enum->{pack_l};
286 0           my $len_l = $enum->{len_l};
287 0           my $sfh = $enum->{sfh};
288 0           my @i2s = qw();
289 0           my ($buf,$len_s);
290 0           for (CORE::seek($sfh,0,SEEK_SET); !eof($sfh); ) {
291 0 0         CORE::read($sfh, $buf, $len_l)==$len_l
292             or $enum->logconfess("toArray(): read() failed on $enum->{base}.es for string length at offset ", tell($sfh));
293 0           $len_s = unpack($pack_l, $buf);
294              
295 0 0         CORE::read($sfh, $buf, $len_s)==$len_s
296             or $enum->logconfess("toArray(): read() failed on $enum->{base}.es for string of length $len_s at offset ", tell($sfh));
297 0           push(@i2s, $buf);
298             }
299 0 0         push(@i2s, @{$enum->{i2s}}[scalar(@i2s)..$#{$enum->{i2s}}]) if ($enum->dirty);
  0            
  0            
300 0           return \@i2s;
301             }
302              
303             ## $enum = $enum->fromArray(\@i2s)
304             ## + clobbers $enum contents, steals \@i2s
305             sub fromArray {
306 0     0 1   my ($enum,$i2s) = @_;
307 0           $enum->{i2s} = $i2s;
308 0           my $i = 0;
309 0           foreach (@$i2s) {
310 0 0         next if (!defined($_));
311 0           $enum->{s2i}{$_} = $i++;
312             }
313 0           $enum->{size} = scalar(@{$enum->{i2s}});
  0            
314 0           $enum->{dirty} = 1;
315 0           return $enum;
316             }
317              
318             ## $enum = $enum->fromHash(\%s2i)
319             ## + clobbers $enum contents, steals \%s2i
320             sub fromHash {
321 0     0 1   my ($enum,$s2i) = @_;
322 0           $enum->{s2i} = $s2i;
323 0           @{$enum->{i2s}}[values %$s2i] = keys %$s2i;
  0            
324 0           $enum->{size} = scalar(@{$enum->{i2s}});
  0            
325 0           $enum->{dirty} = 1;
326 0           return $enum;
327             }
328              
329              
330             ## $enum = $enum->fromEnum($enum2)
331             ## + clobbers $enum contents, does NOT steal $enum2->{i2s}
332             sub fromEnum {
333 0     0 1   my ($enum,$e2) = @_;
334 0 0 0       if ($e2->opened && !$e2->loaded) {
335             ##-- file->mem
336 0           return $enum->fromArray($e2->toArray);
337             } else {
338             ##-- mem->mem
339 0           @{$enum->{i2s}} = @{$e2->{i2s}};
  0            
  0            
340 0           %{$enum->{s2i}} = %{$e2->{s2i}};
  0            
  0            
341 0           $enum->{dirty} = 1;
342             }
343 0           return $enum;
344             }
345              
346             ## $bool = $enum->load()
347             ## + loads files to memory; must be opened
348             sub load {
349 0     0 1   my $enum = shift;
350 0           my $dirty = $enum->{dirty};
351 0 0         $enum->fromArray($enum->toArray) or return undef;
352 0           $enum->{loaded} = 1;
353 0           $enum->{dirty} = $dirty;
354 0           return $enum;
355             }
356              
357             ## $enum = $enum->save()
358             ## $enum = $enum->save($base)
359             ## + saves enum to $base; really just a wrapper for open() and flush()
360             sub save {
361 0     0 1   my ($enum,$base) = @_;
362 0 0         $enum->open($base,'rw') if (defined($base));
363 0 0         $enum->logconfess("save(): cannot save un-opened enum") if (!$enum->opened);
364 0 0         $enum->flush() or $enum->logconfess("save(): failed to flush to $enum->{base}: $!");
365 0           return $enum;
366             }
367              
368              
369             ##--------------------------------------------------------------
370             ## I/O: header
371             ## + see also DiaColloDB::Persistent
372              
373             ## @keys = $coldb->headerKeys()
374             ## + keys to save as header
375             sub headerKeys {
376 0   0 0 1   return grep {!ref($_[0]{$_}) && $_ !~ m{^(?:flags|perms|base|loaded|dirty|hclass)$}} keys %{$_[0]};
  0            
  0            
377             }
378              
379             ## $bool = $enum->loadHeaderData($hdr)
380             ## + instantiates header data from $hdr
381             ## + overrides DiaColloDB::Persistent implementation
382             sub loadHeaderData {
383 0     0 1   my ($enum,$hdr) = @_;
384 0 0 0       if (!defined($hdr) && !fccreat($enum->{flags})) {
    0          
385 0           $enum->logconfess("loadHeaderData() failed to load header data from ", $enum->headerFile, ": $!");
386             }
387             elsif (defined($hdr)) {
388 0           $enum->{hclass} = $hdr->{class}; ##-- save stored header-class
389 0           $enum->SUPER::loadHeaderData($hdr);
390             }
391 0           return $enum;
392             }
393              
394             ## $bool = $enum->saveHeader()
395             ## + inherited from DiaColloDB::Persistent
396              
397             ##--------------------------------------------------------------
398             ## I/O: text
399             ## + largely INHERITED from DiaColloDB::Persistent
400              
401             ## $bool = $obj->loadTextFile($filename_or_handle, %opts)
402             ## + wraps loadTextFh()
403             ## + INHERITED from DiaColloDB::Persistent
404              
405             ## $enum = $CLASS_OR_OBJECT->loadTextFh($fh)
406             ## $enum = $CLASS_OR_OBJECT->loadTextFh($fh, %opts)
407             ## + loads from text file with lines of the form "ID SYMBOL..."
408             ## + clobbers enum contents
409             ## + %opts locally clobber %$enum, especially:
410             ## pack_s => $pack_s
411             sub loadTextFh {
412 0     0 1   my ($enum,$fh,%opts) = @_;
413 0 0         $enum = $enum->new(%opts) if (!ref($enum));
414 0 0         my $pack_s = exists($opts{pack_s}) ? $opts{pack_s} : $enum->{pack_s};
415 0 0 0 0     my $packsub = $pack_s && !UNIVERSAL::isa($pack_s,'CODE') ? sub { pack($pack_s,split(/\t/,$_[0])) } : $pack_s;
  0            
416 0           my @i2s = qw();
417 0           my ($i,$s);
418 0           while (defined($_=<$fh>)) {
419 0           chomp;
420 0 0 0       next if (/^%%/ || /^$/);
421 0           ($i,$s) = split(/\s/,$_,2);
422 0 0         $s = $packsub->($s) if ($packsub);
423 0           $i2s[$i] = $s;
424             }
425              
426             ##-- clobber enum
427 0           return $enum->fromArray(\@i2s);
428             }
429              
430             ## $bool = $obj->saveTextFile($filename_or_fh, %opts)
431             ## + wraps saveTextFh()
432             ## + INHERITED from DiaColloDB::Persistent
433              
434             ## $bool = $enum->saveTextFh($fh,%opts)
435             ## + save from text file with lines of the form "ID SYMBOL..."
436             ## + %opts locally clobber %$enum, especially:
437             ## pack_s => $pack_s
438             sub saveTextFh {
439 0     0 1   my ($enum,$fh,%opts) = @_;
440 0 0         my $pack_s = exists($opts{pack_s}) ? $opts{pack_s} : $enum->{pack_s};
441 0 0 0 0     my $packsub = $pack_s && !UNIVERSAL::isa($pack_s,'CODE') ? sub { join("\t", unpack($pack_s,$_[0])) } : $pack_s;
  0            
442 0           my $i2s = $enum->toArray;
443 0           my $i = 0;
444 0           foreach (@$i2s) {
445 0 0         if (defined($_)) {
446 0 0         $fh->print($i, "\t", ($packsub ? $packsub->($_) : $_), "\n");
447             }
448 0           ++$i;
449             }
450 0           return $enum;
451             }
452              
453              
454             ##==============================================================================
455             ## Methods: population (in-memory only)
456              
457             ## $size = $enum->size()
458             ## + wraps {size} key
459 0     0 1   sub size { return $_[0]{size}; }
460              
461             ## $newsize = $enum->setsize($newsize)
462             ## + realy just wraps {size} key
463 0     0 1   sub setsize { return $_[0]{size}=$_[1]; }
464              
465             ## $newsize = $enum->addSymbols(@symbols)
466             ## $newsize = $enum->addSymbols(\@symbols)
467             ## + adds all symbols in @symbols which don't already exist
468             ## + enum must be loaded to memory
469             sub addSymbols {
470 0     0 1   my $enum = shift;
471 0 0         my $symbols = UNIVERSAL::isa($_[0],'ARRAY') ? $_[0] : \@_;
472 0           my $n = $enum->{size};
473 0           my $s2i = $enum->{s2i};
474 0           my $i2s = $enum->{i2s};
475 0           foreach (@$symbols) {
476 0 0         next if (exists $s2i->{$_});
477 0           $s2i->{$_} = $n;
478 0           $i2s->[$n] = $_;
479 0           ++$n;
480             }
481 0           $enum->{dirty} = 1;
482 0           return $enum->{size}=$n;
483             }
484              
485             ## $newsize = $enum->appendSymbols(@symbols)
486             ## $newsize = $enum->appendSymbols(\@symbols)
487             ## + adds all symbols in @symbols in order, messily re-mapping them if they already exist
488             sub appendSymbols {
489 0     0 1   my $enum = shift;
490 0 0         my $symbols = UNIVERSAL::isa($_[0],'ARRAY') ? $_[0] : \@_;
491 0           my $n = $enum->{size};
492 0           my $s2i = $enum->{s2i};
493 0           my $i2s = $enum->{i2s};
494 0           foreach (@$symbols) {
495 0           $s2i->{$_} = $n;
496 0           $i2s->[$n] = $_;
497 0           ++$n;
498             }
499 0           $enum->{dirty} = 1;
500 0           return $enum->{size}=$n;
501             }
502              
503             ## $newsize = $enum->addEnum($enum2_or_undef)
504             ## + ensures all symbols from $enum2_or_undef are defined (undef:'')
505             sub addEnum {
506 0     0 1   my ($e1,$e2) = @_;
507 0 0         return $e1->addSymbols(defined($e2) ? $e2->toArray : '');
508             }
509              
510             ##==============================================================================
511             ## Methods: lookup
512              
513             ## $s_or_undef = $enum->i2s($i)
514             ## + in-memory cache overrides file contents
515             sub i2s {
516 0     0 1   my ($enum,$i) = @_;
517 0 0         return undef if ($i >= $enum->{size});
518 0           my ($buf,$soff,$slen);
519 0 0         return $buf if (defined($buf=$enum->{i2s}[$i]));
520              
521 0 0         CORE::seek($enum->{ixfh}, $i*$enum->{len_o}, SEEK_SET)
522             or $enum->logconfess("i2s(): seek() failed on $enum->{base}.eix for i=$i");
523             CORE::read($enum->{ixfh},$buf,$enum->{len_o})==$enum->{len_o}
524 0 0         or $enum->logconfess("i2s(): read() failed on $enum->{base}.eix for i=$i");
525 0           $soff = unpack($enum->{pack_o},$buf);
526              
527 0 0         CORE::seek($enum->{sfh}, $soff, SEEK_SET)
528             or $enum->logconfess("i2s(): seek() failed on $enum->{base}.es for offset $soff");
529             CORE::read($enum->{sfh}, $buf,$enum->{len_l})==$enum->{len_l}
530 0 0         or $enum->logconfess("i2s(): read() failed on $enum->{base}.es for string length at offset $soff");
531 0           $slen = unpack($enum->{pack_l},$buf);
532              
533 0 0         CORE::read($enum->{sfh}, $buf, $slen)==$slen
534             or $enum->logconfess("i2s(): read() failed on $enum->{base}.es for string of length $slen at offset $soff");
535              
536 0 0         utf8::decode($buf) if ($enum->{utf8});
537 0           return $buf;
538             }
539              
540             ## $i_or_undef = $enum->s2i($s)
541             ## $i_or_undef = $enum->s2i($s, $ilo,$ihi)
542             ## + binary search; in-memory cache overrides file contents
543             sub s2i {
544 0     0 1   my ($enum,$key,$ilo,$ihi) = @_;
545              
546 0           my ($sxfh,$sfh,$len_sx,$pack_o,$len_o,$pack_l,$len_l) = @$enum{qw(sxfh sfh len_sx pack_o len_o pack_l len_l)};
547 0   0       $ilo //= 0;
548 0 0 0       $ihi //= $enum->{dirty} ? ((-s $sxfh)/$len_sx) : $enum->{size};
549              
550 0           my ($imid,$buf,$soff,$slen,$si);
551 0 0         return $buf if (defined($buf=$enum->{s2i}{$key}));
552              
553 0 0 0       utf8::encode($key) if ($enum->{utf8} && utf8::is_utf8($key));
554 0           while ($ilo < $ihi) {
555 0           $imid = ($ihi+$ilo) >> 1;
556              
557             ##-- get sx-record @ $imid
558 0 0         CORE::seek($sxfh, $imid*$len_sx, SEEK_SET)
559             or $enum->logconfess("s2i(): seek() failed on $enum->{base}.esx for item $imid");
560 0 0         CORE::read($sxfh, $buf, $len_o)==$len_o
561             or $enum->logconfess("s2i(): read() failed on $enum->{base}.esx for item $imid");
562 0           $soff = unpack($pack_o, $buf);
563              
564             ##-- get string for sx-record
565 0 0         CORE::seek($sfh, $soff, SEEK_SET)
566             or $enum->logconfess("s2i(): seek() failed on $enum->{base}.es for offset $soff");
567 0 0         CORE::read($sfh, $buf, $len_l)==$len_l
568             or $enum->logconfess("s2i(): read() failed on $enum->{base}.es for string length at offset $soff");
569 0           $slen = unpack($pack_l, $buf);
570 0 0         CORE::read($sfh, $buf, $slen)==$slen
571             or $enum->logconfess("s2i(): read() failed on $enum->{base}.es for string of length $slen at offset $soff");
572              
573 0 0         if ($buf lt $key) {
574 0           $ilo = $imid + 1;
575             } else {
576 0           $ihi = $imid;
577             }
578             }
579              
580             ##-- output
581 0 0         if ($ilo==$ihi) {
582             ##-- get sx-record @ $ilo
583 0 0         CORE::seek($sxfh, $ilo*$len_sx, SEEK_SET)
584             or $enum->logconfess("s2i(): seek() failed on $enum->{base}.esx for item $ilo");
585 0 0         return undef if ($sxfh->eof);
586 0 0         CORE::read($sxfh, $buf, $len_sx)==$len_sx
587             or $enum->logconfess("s2i(): read() failed on $enum->{base}.esx for item $ilo");
588 0           ($soff,$si) = unpack($enum->{pack_o}.$enum->{pack_i}, $buf);
589              
590             ##-- get string for sx-record
591 0 0         CORE::seek($sfh, $soff, SEEK_SET)
592             or $enum->logconfess("s2i(): seek() failed on $enum->{base}.es for offset $soff");
593 0 0         CORE::read($sfh, $buf, $len_l)==$len_l
594             or $enum->logconfess("s2i(): read() failed on $enum->{base}.es for string length at offset $soff");
595 0           $slen = unpack($pack_l, $buf);
596 0 0         CORE::read($sfh, $buf, $slen)==$slen
597             or $enum->logconfess("s2i(): read() failed on $enum->{base}.es for string of length $slen at offset $soff");
598              
599 0 0         return $si if ($buf eq $key);
600             }
601              
602 0           return undef;
603             }
604              
605             ## \@is = $enum->re2i($regex)
606             ## + gets indices for all strings matching $regex
607             sub re2i {
608 0     0 1   my ($enum,$re) = @_;
609 0           my $utf8 = $enum->{utf8};
610              
611 0 0         if (!ref($re)) {
612 0 0 0       utf8::decode($re) if ($utf8 && !utf8::is_utf8($re));
613 0           $re = regex($re);
614             }
615              
616 0           my $i2s = $enum->{i2s};
617 0 0 0       if ($enum->loaded || !$enum->opened) {
618             ##-- easy answer: loaded
619 0 0         return [grep {utf8::decode($_) if ($utf8); $i2s->[$_] =~ $re} (0..$#$i2s)];
  0            
  0            
620             }
621              
622             ##-- iteration a la toArray
623             #use bytes; ##-- deprecated in perl v5.18.2
624 0           my $pack_l = $enum->{pack_l};
625 0           my $len_l = $enum->{len_l};
626 0           my $sfh = $enum->{sfh};
627 0           my @is = qw();
628 0           my $i = 0;
629 0           my ($buf,$len_s);
630 0           for ($i=0, CORE::seek($sfh,0,SEEK_SET); !eof($sfh); ++$i) {
631 0 0         CORE::read($sfh, $buf, $len_l)==$len_l
632             or $enum->logconfess("re2i(): read() failed on $enum->{base}.es for string length at offset ", tell($sfh));
633 0           $len_s = unpack($pack_l, $buf);
634              
635 0 0         CORE::read($sfh, $buf, $len_s)==$len_s
636             or $enum->logconfess("re2i(): read() failed on $enum->{base}.es for string of length $len_s at offset ", tell($sfh));
637              
638 0 0         utf8::decode($buf) if ($utf8);
639 0 0         push(@is, $i) if ($buf =~ $re);
640             }
641              
642 0 0         push(@is, grep {utf8::decode($_) if ($utf8); $i2s->[$_] =~ $re} (((-s $enum->{ixfh})/$enum->{len_o})..$#{$enum->{i2s}})) if ($enum->dirty);
  0 0          
  0            
  0            
643 0           return \@is;
644             }
645              
646              
647             ##==============================================================================
648             ## Footer
649             1;
650              
651             __END__