File Coverage

blib/lib/Video/TeletextDB/Access.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Video::TeletextDB::Access;
2 1     1   1529 use 5.006001;
  1         4  
  1         40  
3 1     1   5 use strict;
  1         2  
  1         71  
4 1     1   6 use warnings;
  1         1  
  1         37  
5 1     1   7 use Carp;
  1         2  
  1         81  
6 1     1   425 use DB_File;
  0            
  0            
7             use POSIX qw(ENOENT EWOULDBLOCK);
8             use Fcntl qw(F_GETFL O_CREAT O_RDWR O_RDONLY O_ACCMODE LOCK_NB LOCK_EX);
9             # use AutoLoader qw(AUTOLOAD);
10              
11             use Video::TeletextDB::Constants qw(:BdbPrefixes :VTX :VBI DB_VERSION);
12             use Video::TeletextDB::Page qw(vote $epoch_time);
13              
14             our $VERSION = "0.02";
15             use base qw(Video::TeletextDB::Parameters);
16              
17             use Exporter::Tidy
18             functions => [qw(tilde)],
19             variables => [qw($default_cache_dir $default_page_versions)];
20              
21             use constant MIN_STORES => 10000; # Must have at least 10000 stores
22             use constant DB_RO => "Video::TeletextDB::DB_RO";
23             use constant DB_RW => "Video::TeletextDB::DB_RW";
24              
25             our @CARP_NOT = qw(Video::TeletextDB::Options);
26              
27             our $default_cache_dir = "~/.TeletextDB/cache";
28             our $default_page_versions = 5;
29              
30             # Database format:
31             # V. => a* (version)
32             # s. => NNN (start time, number of stores, last store time)
33             # S. => C (page_versions)
34             # c.nn (page, subpage) => CN (last_counter, last_time)
35             # There is a fake c."\xff"x4 at the end to make scanning easier
36             # p.nnC (page, subpage, counter) =>
37             # Na* (store time, join \xa, raw rows (without \xa))
38             # There is a fake p."\xff"x5 at the end to make scanning easier
39              
40             sub tilde {
41             defined(my $file = shift) || croak "Undefined file";
42             my ($user, $rest) = $file =~ m!^~([^/]*)(.*)\z!s or return $file;
43             if ($user ne "") {
44             my @pw = getpwnam($user) or croak "Could not find user $user";
45             $user = $pw[7];
46             } elsif (!defined($user = $ENV{HOME})) {
47             my @pw = getpwuid($>) or
48             croak "Could not determine who you are";
49             $user = $pw[7];
50             }
51             croak "Home directory is the empty string" if $user eq "";
52             $user =~ s!/*\z!$rest!;
53             $user = "/" if $user eq "";
54             # Restore taintedness
55             return $user . substr($file, 0, 0);
56             }
57              
58             # Prepare a directory to contain databases
59             sub prepare {
60             my ($class, $tele, $params) = @_;
61              
62             my $mkpath = exists $params->{mkpath} ?
63             delete $params->{mkpath} : !exists $params->{cache_dir};
64             my $dir = delete $params->{cache_dir};
65             $dir = $default_cache_dir unless defined $dir;
66             $dir = tilde($dir);
67             if ($dir !~ m!\A/!) {
68             require Cwd;
69             my $prefix = Cwd::getcwd();
70             $dir = $prefix =~ m!/\z! ? $prefix . $dir : "$prefix/$dir";
71             }
72             $dir .= "/" unless $dir =~ m!/\z!;
73             if (!-d $dir) {
74             croak "No visible directory named '$dir'" unless $mkpath;
75             require File::Path;
76             my $old_mask = umask($tele->{umask}) if defined($tele->{umask});
77             eval { File::Path::mkpath($dir) };
78             my $err = $@;
79             umask($old_mask) if defined($tele->{umask});
80             die $err if $err;
81             }
82             $tele->{cache_dir} = $dir;
83             }
84              
85             # Opening a db file with O_CREAT can give you RW access even if you didn't
86             # ask for that. Use this to fix the state.
87             sub db_maybe_rw {
88             my $db = shift->{db};
89             open(my $fh, "+<&", $db->fd) || croak "Could not dup db fileno: $!";
90             my $flags = fcntl($fh, F_GETFL, 0) ||
91             croak "Could not fcntl db handle: $!";
92             $flags &= O_ACCMODE;
93             return 0 if $flags == O_RDONLY;
94             croak "Don't know how to handle a database opened in mode $flags" unless
95             $flags == O_RDWR;
96             bless $db, DB_RW;
97             return 1;
98             }
99              
100             sub db_check {
101             my $access = shift;
102             my $db = $access->{db};
103              
104             if (!$db->get(VERSION, my $version)) {
105             croak("Wanted version ", DB_VERSION, " differs from current $version for ", $access->db_file) if $version ne DB_VERSION;
106             } else {
107             $db = $access->upgrade(1);
108             croak "Storage problem" if $db->put(VERSION, DB_VERSION);
109             }
110              
111             my $versions_wanted = $access->page_versions;
112             if ($db->get(PAGE_VERSIONS, my $page_versions) == 0) {
113             $page_versions = unpack("C", $page_versions);
114             croak("Wanted versions $versions_wanted differs from current $page_versions for ", $access->db_file) if defined($versions_wanted) && $versions_wanted != $page_versions;
115             $access->{page_versions} = $page_versions;
116             } else {
117             $db = $access->upgrade(1);
118             $access->{page_versions} = $versions_wanted || $default_page_versions;
119             croak "Storage problem" if
120             $db->put(PAGE_VERSIONS, pack("C", $access->{page_versions}));
121             }
122              
123             my $value;
124             if ($db->get(PAGE . "\xff" x 5, $value)) {
125             # No PAGE terminator
126             $db = $access->upgrade(1);
127             croak "Storage problem" if $db->put(PAGE . "\xff" x 5, "\xff" x 4);
128             }
129              
130             if ($db->get(COUNTER . "\xff" x 4, $value) ||
131             $value ne "\x0" . "\xff" x 4) {
132             # No COUNTER terminator
133             $db = $access->upgrade(1);
134             croak "Storage problem" if $db->put(COUNTER . "\xff" x 4, "\x00" . "\xff" x 4);
135             }
136             }
137              
138             sub init {
139             my ($access, $params) = @_;
140              
141             my $acquire = exists $params->{acquire} ? delete $params->{acquire} : 1;
142             $access->SUPER::init($params);
143             $access->{stores} = 0;
144             $access->acquire if $acquire;
145              
146             return $access;
147             }
148              
149             sub cache_dir {
150             return shift->{parent}->cache_dir;
151             }
152              
153             sub teletext_db {
154             return shift->{parent};
155             }
156              
157             sub db {
158             return shift->{db};
159             }
160              
161             sub stale_period {
162             return shift->{stale_period};
163             }
164              
165             sub expire_period {
166             return shift->{expire_period};
167             }
168              
169             sub channel {
170             croak "You can't change the channel on a $_[0]" if @_ >= 2;
171             return shift->{channel};
172             }
173              
174             sub page_versions {
175             croak "You can't change the page_versions on a $_[0]" if @_ >= 2;
176             return shift->{page_versions};
177             }
178              
179             sub delete {
180             my ($access, %options) = shift;
181             defined($access->{channel}) || croak "No channel";
182              
183             # We won't check lockfile unlinks since they are not really
184             # part of the semantics of a channel existing, and there actually is no
185             # clean way to make things look atomic in that case anyways.
186             my $want_file = $access->want_file;
187             my $lock_file = $access->lock_file;
188             my $want_fh = $access->{want_fh};
189             my $lock_fh = $access->{lock_fh};
190              
191             my $rc;
192             my $old_mask = $access->{creat} && defined($access->{umask}) ?
193             umask($access->{umask}) : undef;
194             eval {
195             my $db_file = $access->db_file;
196             $want_fh ||= $access->{want} && $access->get_lock($want_file, 1);
197             $lock_fh ||= $access->get_lock($lock_file, 1);
198             if (unlink($db_file)) {
199             $rc = 1;
200             } elsif ($! != ENOENT) {
201             croak "Could not unlink $db_file: $!";
202             }
203             if (my $db = delete $access->{db}) {
204             # This is pure evil.
205             $db->DESTROY;
206             bless $db, "Video::TeletextDB::Bug";
207             }
208             unlink($lock_file);
209             delete $access->{lock_fh};
210             if ($want_fh) {
211             unlink($want_file);
212             delete $access->{want_fh};
213             }
214             };
215             umask($old_mask) if defined $old_mask;
216             return $rc || () unless $@;
217              
218             unlink($lock_file) if $lock_fh && !$access->{lock_fh};
219             unlink($want_file) if $want_fh && !$access->{want_fh};
220             die $@ if $@;
221             }
222              
223             sub unwant {
224             my $access = shift;
225             croak "You don't have the database" unless $access->{db};
226             croak "You don't have the database lock" unless $access->{lock_fh};
227             croak "You don't have the database want" unless $access->{want_fh};
228             close delete $access->{want_fh};
229             }
230              
231             sub rewant {
232             my $access = shift;
233             croak "You don't have the database" unless $access->{db};
234             croak "You don't have the database lock" unless $access->{lock_fh};
235             croak "You already have the database want" if $access->{want_fh};
236              
237             my $want_file = $access->want_file;
238             sysopen(my $fh, $want_file, $access->{creat} ? O_RDWR | O_CREAT : O_RDWR)||
239             croak "Could not open/create '$want_file': $!";
240             if (flock($fh, LOCK_NB | LOCK_EX)) {
241             my $oldfh = select $fh;
242             $| = 1;
243             print "$$\n";
244             truncate $fh, tell($fh);
245             select $oldfh;
246             $access->{want_fh} = $fh;
247             return;
248             }
249             croak "Could not lock '$want_file': $!" unless $! == EWOULDBLOCK;
250             close $fh;
251              
252             $access->release;
253             local $access->{want} = 1;
254             $access->acquire;
255             return 1;
256             }
257              
258             sub restart {
259             my $access = shift;
260             delete $access->{start_time};
261             delete $access->{end_time};
262             $access->{stores} = 0;
263             }
264              
265             sub start_time {
266             croak 'Too many arguments for start_time method' if @_ > 1;
267             return shift->{start_time} || croak "Time doesn't seem to have started";
268             }
269              
270             sub end_time {
271             croak 'Too many arguments for end_time method' if @_ > 1;
272             return shift->{end_time} || croak "Time doesn't seem to have ended";
273             }
274              
275             sub stores {
276             croak 'Too many arguments for stores method' if @_ > 1;
277             return shift->{stores};
278             }
279              
280             sub acquire {
281             my $access = shift;
282              
283             croak "You already have the database" if $access->{db};
284             croak "You already have the database lock" if $access->{lock_fh};
285             croak "You already have the database want" if $access->{want_fh};
286              
287             my $old_mask = $access->{creat} && defined($access->{umask}) ?
288             umask($access->{umask}) : undef;
289             eval {
290             $access->{want_fh} = $access->want(1) if $access->{want};
291             $access->{lock_fh} = $access->lock(1);
292              
293             $access->{db} = ($access->{RW} ? DB_RW : DB_RO)->TIEHASH
294             ($access->db_file,
295             ($access->{RW} ? O_RDWR : O_RDONLY) |
296             ($access->{creat} ? O_CREAT : 0), 0666, $DB_BTREE) ||
297             croak "Could not db_open ", $access->db_file, ": $!";
298             $access->db_maybe_rw if $access->{creat} && !$access->{RW};
299             $access->db_check;
300             $access->downgrade if !$access->{RW} && defined $access->{RW} &&
301             $access->{db}->isa(DB_RW);
302              
303             return if $access->{db}->get(STORES, my $stores);
304             (my $end, $stores) = unpack("NN", $stores);
305             $access->{stale} = $end - $access->{stale_period};
306             $access->{expire} =
307             $stores < MIN_STORES ? -9**9**9 : $end - $access->{expire_period};
308             };
309             umask($old_mask) if defined $old_mask;
310             return $access->{db} unless $@;
311              
312             my $err = $@;
313             $access->release;
314             die $err;
315             }
316              
317             sub upgrade {
318             my $access = shift;
319              
320             $access->{db} || croak "You don't have the database";
321             return $access->{db} if $access->{db}->isa(DB_RW);
322             croak "Can't upgrade pure readonly access" if
323             !$access->{RW} && defined $access->{RW} &&
324             !($access->{creat} && shift);
325              
326             my $db = delete $access->{db};
327             # This is pure evil.
328             $db->DESTROY;
329             bless $db, "Video::TeletextDB::Bug";
330              
331             my $old_mask = $access->{creat} && defined($access->{umask}) ?
332             umask($access->{umask}) : undef;
333             eval {
334             $access->{db} = DB_RW->TIEHASH
335             ($access->db_file, $access->{creat} ? O_RDWR | O_CREAT : O_RDWR,
336             0666, $DB_BTREE) ||
337             croak "Could not db_open ", $access->db_file, ": $!";
338             $access->db_check;
339             };
340             umask($old_mask) if defined $old_mask;
341             return $access->{db} unless $@;
342              
343             my $err = $@;
344             $access->release;
345             die $err;
346             }
347              
348             sub downgrade {
349             my $access = shift;
350              
351             $access->{db} || croak "You don't have the database";
352             return $access->{db} if $access->{db}->isa(DB_RO);
353              
354             my $db = delete $access->{db};
355             # This is pure evil.
356             $db->DESTROY;
357             bless $db, "Video::TeletextDB::Bug";
358              
359             my $old_mask = $access->{creat} && defined($access->{umask}) ?
360             umask($access->{umask}) : undef;
361             eval {
362             while (1) {
363             $access->{db} = DB_RO->TIEHASH
364             ($access->db_file,
365             $access->{creat} ? O_CREAT | O_RDONLY : O_RDONLY,
366             0666, $DB_BTREE) ||
367             croak "Could not db_open ", $access->db_file, ": $!";
368             $access->db_maybe_rw if $access->{creat};
369             $access->db_check;
370             last if $access->{db}->isa(DB_RO);
371              
372             if ($access->{db} = DB_RO->TIEHASH
373             ($access->db_file, O_RDONLY, 0666, $DB_BTREE)) {
374             $access->db_check;
375             # check may have caused an upgrade again
376             last if $access->{db}->isa(DB_RO);
377             } elsif ($! != ENOENT) {
378             croak "Could not db_open ", $access->db_file, ": $!";
379             }
380             # Someone must have undone us. Retry.
381             }
382             };
383             umask($old_mask) if defined $old_mask;
384             return $access->{db} unless $@;
385              
386             my $err = $@;
387             $access->release;
388             die $err;
389             }
390              
391             sub release {
392             my $access = shift;
393             # Make sure things get closed in the right order
394             if (my $db = delete $access->{db}) {
395             # This is pure evil.
396             $db->DESTROY;
397             bless $db, "Video::TeletextDB::Bug";
398             }
399             my $fh = delete $access->{lock_fh};
400             close($fh) if $fh;
401             $fh = delete $access->{want_fh};
402             close($fh) if $fh;
403             }
404              
405             sub cache_status {
406             my $access = shift;
407             my $db = $access->{db} || croak "You don't have the database";
408             return if $db->get(STORES, my $update);
409             my ($end, $stores, $start) = unpack("NNN", $update);
410             return {
411             channel => $access->{channel},
412             start_time => $start+$epoch_time,
413             end_time => $end +$epoch_time,
414             stores => $stores,
415             };
416             }
417              
418             sub expire {
419             my $access = shift;
420             my $db = $access->upgrade;
421             for my $page (@_) {
422             croak "Delete problem" if $db->del($page);
423             $db->del(PAGE . substr($page, 1) . pack("C", $_)) for
424             0..$access->{page_versions}-1;
425             }
426             return $db;
427             }
428              
429             sub db_subpages {
430             my ($access, $page) = @_;
431              
432             my $db = $access->{db} || croak "You don't have the database";
433              
434             my $key = my $prefix = COUNTER . $page;
435             return wantarray ? () : 0 if $db->seq($key, my $counter, R_CURSOR);
436              
437             my $updatable = $access->{RW} || !defined $access->{RW};
438             my (@good_pages, @bad, $stale);
439             my $zero_time = my $non_zero_time = 0;
440              
441             while (substr($key, 0, 3) eq $prefix) {
442             my ($c, $time) = unpack("CN", $counter);
443             if ($time <= $access->{stale}) {
444             #print STDERR ("Expiring ",unpack("n", $page),"/",unpack("n", $_),
445             # " (", scalar localtime($time),
446             # ") versus ", scalar localtime($expire), "\n");
447             push @bad, $key if $updatable && $time <= $access->{expire};
448             } else {
449             #print STDERR ("good ", unpack("n", $page),"/",unpack("n", $_),
450             # " with date ",
451             # scalar localtime($time), "\n");
452             my $subpage_nr = unpack("x3n", $key);
453             if (sprintf("%x", $subpage_nr) !~ /[a-fA-F]/) {
454             push @good_pages, $subpage_nr;
455             if ($good_pages[-1]) {
456             $non_zero_time = $time if $non_zero_time < $time;
457             } else {
458             $zero_time = $time;
459             }
460             }
461             }
462             croak "Unexpected sequence end" if $db->seq($key, $counter, R_NEXT);
463             }
464             # print STDERR "returning @{[unpack('n*', $good_pages)]} instead of @{[unpack('n*', $subpages)]}\n";
465             $access->expire(@bad) if @bad;
466             return @good_pages unless $zero_time && $non_zero_time;
467             # Here we assume that a 0 page and a 1-n page are mutually exclusive
468             return wantarray ? 0 : 1 if $zero_time >= $non_zero_time;
469             return wantarray ? grep $_, @good_pages : @good_pages - 1;
470             }
471              
472             sub subpages {
473             my $access = shift;
474             my $page = pack("n", shift);
475             return $access->db_subpages($page, @_);
476             }
477              
478             sub raw_fetch_page {
479             my $access = shift;
480             my $page = pack("nn", @_);
481             my $db = $access->{db} || croak "You don't have the database";
482              
483             return if $db->get(COUNTER . $page, my $counter);
484             my $time = unpack("xN", $counter);
485             if ($access->{stale} < $time) {
486             my $content;
487             return sort { $b cmp $a } map
488             $db->get(PAGE . $page . pack("C", $_), $content) ? () : $content,
489             0..$access->{page_versions}-1;
490             }
491             return if !$access->{RW} && defined $access->{RW} ||
492             $access->{expire} < $time;
493             $db = $access->upgrade;
494             croak "Delete problem" if $db->del(COUNTER . $page);
495             $db->del(PAGE . $page . pack("C", $_)) for 0..$access->{page_versions}-1;
496             }
497              
498             sub fetch_page {
499             my $access = shift;
500             return vote($access->{channel}, @_[0..1], $access->raw_fetch_page(@_));
501             }
502              
503             sub fetch_page_versions {
504             my $access = shift;
505             return map {
506             my ($time, @rows) = unpack "N(C/a)*", $_;
507             bless {
508             time => $time+$epoch_time,
509             raw_rows => \@rows,
510             channel => $access->{channel},
511             page_nr => $_[0],
512             subpage_nr => $_[1],
513             }, "Video::TeletextDB::Page";
514             } $access->raw_fetch_page(@_);
515             }
516              
517             sub scan_page {
518             my ($access, $step, $from) = @_;
519             my $db = $access->{db} || croak "You don't have the database";
520             croak "Zero step" unless $step;
521             my $updatable = $access->{RW} || !defined $access->{RW};
522             my @bad;
523             if ($step >= 0) {
524             $from ||= 0;
525             croak "Too high page $from" if $from >= 0x900;
526             my $base = $from;
527             my $end = 0xffff;
528             while (1) {
529             # print STDERR "from=$from, base=$base, end=$end\n";
530             my $key = my $start = COUNTER . pack("n", $base) . "\xffff";
531             croak "No followup after $from" if
532             $db->seq($key, my $counter, R_CURSOR);
533             # One more step if we hit the element itself
534             croak "No followup after $from" if
535             substr($key, 0, 3) eq $start &&
536             $db->seq($key, $counter, R_NEXT);
537             while (unpack("xN", $counter) <= $access->{stale}) {
538             push @bad, $key if
539             $updatable && unpack("xN", $counter) <= $access->{expire};
540             croak "No followup after $from" if
541             $db->seq($key, $counter, R_NEXT);
542             }
543             my $hex = unpack("xH4", $key);
544             # print STDERR "Considering 0x$hex\n";
545             unless ($hex =~ s/(\D.*)/"f" x length $1/eg) {
546             # We found a non-hex page
547             $access->expire(@bad) if @bad;
548             return hex $hex > $end ? () : hex $hex;
549             }
550             $base = hex $hex;
551             if ($base == 0xffff) {
552             unless ($end == 0xffff) {
553             $access->expire(@bad) if @bad;
554             return;
555             }
556             # wrap
557             $end = $from;
558             $base = 0;
559             }
560             }
561             } else {
562             $from ||= 0xffff;
563             croak "Too low page $from" if $from < 0x100;
564             my $base = $from;
565             my $end = 0;
566             START:
567             while (1) {
568             # print STDERR "from=$from, base=$base, end=$end\n";
569             my $key = my $start = COUNTER . pack("n", $base);
570             croak "No followup after $from" if
571             $db->seq($key, my $counter, R_CURSOR);
572             # print STDERR "found ", unpack("H*", $key), "\n";
573             # and step back
574             until ($db->seq($key, $counter, R_PREV) ||
575             substr($key, 0, 1) ne COUNTER) {
576             if (unpack("xN", $counter) <= $access->{stale}) {
577             push @bad, $key if $updatable &&
578             unpack("xN", $counter) <= $access->{expire};
579             next;
580             }
581              
582             my $hex = unpack("xH4", $key);
583             # print STDERR "Considering 0x$hex\n";
584             # We found a non-hex page
585             unless ($hex =~ s/(\D.*)/"9" x length $1/eg) {
586             $access->expire(@bad) if @bad;
587             return hex $hex < $end ? () : hex $hex;
588             }
589             $base = hex($hex)+1;
590             next START;
591             }
592             if ($end) {
593             $access->expire(@bad) if @bad;
594             return;
595             }
596             # wrap
597             $end = $from;
598             $base = 0xffff;
599             }
600             }
601             }
602              
603             sub page_ids {
604             my $access = shift;
605             my $db = $access->{db} || croak "You don't have the database";
606             my $updatable = $access->{RW} || !defined $access->{RW};
607             my (@keys, $time, @bad);
608             croak "No followup after ", COUNTER if
609             $db->seq(my $key = COUNTER, my $value, R_CURSOR);
610             while ($key ne COUNTER . "\xff" x 4) {
611             $time = unpack("xN", $value);
612             if ($access->{stale} < $time) {
613             my $page_id = sprintf("%03x/%02x", unpack("xnn", $key));
614             push @keys, $page_id unless $page_id =~ /[a-fA-F]/;
615             } elsif ($updatable && $time <= $access->{expire}) {
616             push @bad, $key;
617             }
618             croak "No followup" if $db->seq($key, $value, R_NEXT);
619             }
620             $access->expire(@bad) if @bad;
621             return @keys;
622             }
623              
624             sub write_pages {
625             my ($access, %params) = @_;
626             my $time = exists $params{time} ? delete $params{time} : time;
627             defined(my $pages = delete $params{pages}) ||
628             croak "No pages parameter";
629             croak("Unknown parameters ", join(", ", keys %params)) if %params;
630             return unless @$pages;
631              
632             $access->{start_time} = $time if
633             !defined $access->{start_time} || $time < $access->{start_time};
634             $access->{end_time} = $time if
635             !defined $access->{end_time} || $time > $access->{end_time};
636             $time -= $epoch_time;
637             my $t = pack("N", $time);
638              
639             my $db = $access->upgrade;
640              
641             my $counter;
642             for (@$pages) {
643             my $main_page = $_->{page};
644             # Maybe caller should do this...
645             die "Bad page nr $main_page" if $main_page >= 0x800;
646             $main_page += 0x800 if $main_page < 0x100;
647             $main_page = pack("n", $main_page);
648             my $subpage = pack("n", $_->{ctrl} & VTX_SUB);
649             my $page = $main_page . $subpage;
650              
651             $counter = pack("C", $access->{page_versions}-1) if
652             $db->get(COUNTER . $page, $counter);
653             $counter = pack "C", (1 + unpack "C", $counter) % $access->{page_versions};
654             my $rc = $db->put(PAGE . $page . $counter, do {
655             no warnings "uninitialized";
656             pack "a*(C/a*)*", $t, @{$_->{packet}};
657             });
658             $rc == 0 || croak "Storage problem (rc=$rc)";
659             $db->put(COUNTER . $page, $counter . $t) == 0 ||
660             croak "Storage problem";
661             ++$access->{stores};
662             }
663              
664             if ($db->get(STORES, $counter) == 0) {
665             my ($old_end, $old_stores, $old_start) = unpack("NNN", $counter);
666             if ($old_start <= $time && $time <= $old_end) {
667             $db->put(STORES, pack("NNN", $old_end, $old_stores + @$pages,
668             $old_start)) == 0 || croak "Storage problem";
669             return;
670             }
671             return if $access->{end_time} < $old_end+$epoch_time;
672             return if $access->{stores} < MIN_STORES;
673             }
674             $db->put(STORES, pack("NNN",
675             $access->{end_time} - $epoch_time,
676             $access->{stores},
677             $access->{start_time} - $epoch_time)) == 0 ||
678             croak "Storage problem";
679             }
680              
681             sub write_feed {
682             my ($access, %params) = @_;
683             my $time = exists $params{time} ? delete $params{time} : time;
684             defined(my $fields = delete $params{decoded_fields}) ||
685             croak "No decoded_fields parameter";
686             croak("Unknown parameters ", join(", ", keys %params)) if %params;
687             return unless @$fields;
688              
689             my @pages;
690             for (@$fields) {
691             next unless $_->[0] == VBI_VT;
692             # Currently only handle teletext
693             my $y = $_->[2];
694             if ($y == 0) {
695             if ($access->{curpage}{page}) {
696             if ($_->[5] & VTX_C11 ||
697             ($access->{curpage}->{page} ^ $_->[4]) & 0xf00) {
698             push @pages, $access->{curpage} unless
699             ($access->{curpage}->{page} & 0xff) == 0xff;
700             }
701             }
702             $access->{curpage} = {
703             packet => [$_->[3]],
704             page => $_->[4],
705             ctrl => $_->[5],
706             };
707             } elsif ($y <= 25) {
708             $access->{curpage}{packet}[$y] = $_->[3];
709             }
710             # We currently ignore packets 26 and higher
711             }
712             $access->write_pages(time => $time, pages => \@pages) if @pages;
713             }
714              
715             sub next_page {
716             return shift->scan_page(+1, @_);
717             }
718              
719             sub previous_page {
720             return shift->scan_page(-1, @_);
721             }
722              
723             sub DESTROY {
724             shift->release;
725             }
726              
727             package Video::TeletextDB::DB_RW;
728             our @ISA = qw(DB_File);
729              
730             package Video::TeletextDB::DB_RO;
731             our @ISA = qw(DB_File);
732              
733             package Video::TeletextDB::Access;
734              
735             1;
736             __END__