| 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__ |