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