line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Documentation and Copyright exist after __END__ |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package CDDB; |
4
|
|
|
|
|
|
|
require 5.001; |
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
863
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
33
|
|
7
|
1
|
|
|
1
|
|
4
|
use vars qw($VERSION); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
64
|
|
8
|
1
|
|
|
1
|
|
6
|
use Carp; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
114
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
$VERSION = '1.220'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
BEGIN { |
13
|
1
|
50
|
|
1
|
|
6
|
if ($^O eq 'MSWin32') { |
14
|
0
|
|
|
|
|
0
|
eval 'sub USING_WINDOWS () { 1 }'; |
15
|
|
|
|
|
|
|
} |
16
|
|
|
|
|
|
|
else { |
17
|
1
|
|
|
|
|
59
|
eval 'sub USING_WINDOWS () { 0 }'; |
18
|
|
|
|
|
|
|
} |
19
|
|
|
|
|
|
|
} |
20
|
|
|
|
|
|
|
|
21
|
1
|
|
|
1
|
|
945
|
use IO::Socket; |
|
1
|
|
|
|
|
25200
|
|
|
1
|
|
|
|
|
5
|
|
22
|
1
|
|
|
1
|
|
1308
|
use Sys::Hostname; |
|
1
|
|
|
|
|
1253
|
|
|
1
|
|
|
|
|
4960
|
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
# A list of known freedb servers. I've stopped using Gracenote's CDDB |
25
|
|
|
|
|
|
|
# because they never return my e-mail about becoming a developer. To |
26
|
|
|
|
|
|
|
# top it off, they've started denying CDDB.pm users. |
27
|
|
|
|
|
|
|
# TODO: Fetch the list from freedb.freedb.org, which is a round-robin |
28
|
|
|
|
|
|
|
# for all the others anyway. |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
my $cddbp_host_selector = 0; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
my @cddbp_hosts = ( |
33
|
|
|
|
|
|
|
[ 'localhost' => 8880 ], |
34
|
|
|
|
|
|
|
[ 'freedb.freedb.org' => 8880 ], |
35
|
|
|
|
|
|
|
[ 'us.freedb.org', => 8880 ], |
36
|
|
|
|
|
|
|
[ 'ca.freedb.org', => 8880 ], |
37
|
|
|
|
|
|
|
[ 'ca2.freedb.org', => 8880 ], |
38
|
|
|
|
|
|
|
[ 'uk.freedb.org' => 8880 ], |
39
|
|
|
|
|
|
|
[ 'no.freedb.org' => 8880 ], |
40
|
|
|
|
|
|
|
[ 'de.freedb.org' => 8880 ], |
41
|
|
|
|
|
|
|
[ 'at.freedb.org' => 8880 ], |
42
|
|
|
|
|
|
|
[ 'freedb.freedb.de' => 8880 ], |
43
|
|
|
|
|
|
|
); |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
46
|
|
|
|
|
|
|
# Determine whether we can submit changes by e-mail. |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
my $imported_mail = 0; |
49
|
|
|
|
|
|
|
eval { |
50
|
|
|
|
|
|
|
require Mail::Internet; |
51
|
|
|
|
|
|
|
require Mail::Header; |
52
|
|
|
|
|
|
|
require MIME::QuotedPrint; |
53
|
|
|
|
|
|
|
$imported_mail = 1; |
54
|
|
|
|
|
|
|
}; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
57
|
|
|
|
|
|
|
# Determine whether we can use HTTP for requests and submissions. |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
my $imported_http = 0; |
60
|
|
|
|
|
|
|
eval { |
61
|
|
|
|
|
|
|
require LWP; |
62
|
|
|
|
|
|
|
require HTTP::Request; |
63
|
|
|
|
|
|
|
$imported_http = 1; |
64
|
|
|
|
|
|
|
}; |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
67
|
|
|
|
|
|
|
# Send a command. If we're not connected, try to connect first. |
68
|
|
|
|
|
|
|
# Returns 1 if the command is sent ok; 0 if there was a problem. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
sub command { |
71
|
16
|
|
|
16
|
0
|
41
|
my $self = shift; |
72
|
16
|
|
|
|
|
76
|
my $str = join(' ', @_); |
73
|
|
|
|
|
|
|
|
74
|
16
|
100
|
|
|
|
86
|
unless ($self->{handle}) { |
75
|
3
|
50
|
|
|
|
16
|
$self->connect() or return 0; |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
|
78
|
16
|
|
|
|
|
67
|
$self->debug_print(0, '>>> ', $str); |
79
|
|
|
|
|
|
|
|
80
|
16
|
|
|
|
|
42
|
my $len = length($str .= "\x0D\x0A"); |
81
|
|
|
|
|
|
|
|
82
|
16
|
50
|
|
|
|
497
|
local $SIG{PIPE} = 'IGNORE' unless ($^O eq 'MacOS'); |
83
|
16
|
50
|
|
|
|
3291
|
return 0 unless(syswrite($self->{handle}, $str, $len) == $len); |
84
|
16
|
|
|
|
|
202
|
return 1; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
88
|
|
|
|
|
|
|
# Retrieve a line from the server. Uses a buffer to allow for |
89
|
|
|
|
|
|
|
# ungetting lines. Returns the next line or undef if there is a |
90
|
|
|
|
|
|
|
# problem. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
sub getline { |
93
|
129
|
|
|
129
|
0
|
202
|
my $self = shift; |
94
|
|
|
|
|
|
|
|
95
|
129
|
100
|
|
|
|
160
|
if (@{$self->{lines}}) { |
|
129
|
|
|
|
|
715
|
|
96
|
106
|
|
|
|
|
136
|
my $line = shift @{$self->{lines}}; |
|
106
|
|
|
|
|
309
|
|
97
|
106
|
|
|
|
|
279
|
$self->debug_print(0, '<<< ', $line); |
98
|
106
|
|
|
|
|
453
|
return $line; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
23
|
|
|
|
|
59
|
my $socket = $self->{handle}; |
102
|
23
|
50
|
|
|
|
70
|
return unless defined $socket; |
103
|
|
|
|
|
|
|
|
104
|
23
|
|
|
|
|
56
|
my $fd = fileno($socket); |
105
|
23
|
50
|
|
|
|
60
|
return unless defined $fd; |
106
|
|
|
|
|
|
|
|
107
|
23
|
|
|
|
|
147
|
vec(my $rin = '', $fd, 1) = 1; |
108
|
23
|
|
50
|
|
|
197
|
my $timeout = $self->{timeout} || undef; |
109
|
23
|
|
|
|
|
55
|
my $frame = $self->{frame}; |
110
|
|
|
|
|
|
|
|
111
|
23
|
|
|
|
|
32
|
until (@{$self->{lines}}) { |
|
46
|
|
|
|
|
2490
|
|
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# Fail if the socket is inactive for the timeout period. Fail |
114
|
|
|
|
|
|
|
# also if sysread returns nothing. |
115
|
|
|
|
|
|
|
|
116
|
23
|
50
|
|
|
|
1050311
|
return unless select(my $rout=$rin, undef, undef, $timeout); |
117
|
23
|
50
|
|
|
|
972
|
return unless defined sysread($socket, my $buf='', 1024); |
118
|
|
|
|
|
|
|
|
119
|
23
|
|
|
|
|
178
|
$frame .= $buf; |
120
|
23
|
|
|
|
|
707
|
my @lines = split(/\x0D?\x0A/, $frame); |
121
|
23
|
100
|
66
|
|
|
439
|
$frame = ( |
122
|
|
|
|
|
|
|
(length($buf) == 0 || substr($buf, -1, 1) eq "\x0A") |
123
|
|
|
|
|
|
|
? '' |
124
|
|
|
|
|
|
|
: pop(@lines) |
125
|
|
|
|
|
|
|
); |
126
|
23
|
|
|
|
|
103
|
push @{$self->{lines}}, map { decode('utf8', $_) } @lines; |
|
23
|
|
|
|
|
173
|
|
|
129
|
|
|
|
|
14280
|
|
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
23
|
|
|
|
|
82
|
$self->{frame} = $frame; |
130
|
|
|
|
|
|
|
|
131
|
23
|
|
|
|
|
41
|
my $line = shift @{$self->{lines}}; |
|
23
|
|
|
|
|
78
|
|
132
|
23
|
|
|
|
|
147
|
$self->debug_print(0, '<<< ', $line); |
133
|
23
|
|
|
|
|
128
|
return $line; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
137
|
|
|
|
|
|
|
# Receive a server response, and parse it into its numeric code and |
138
|
|
|
|
|
|
|
# text message. Return the code's first character, which usually |
139
|
|
|
|
|
|
|
# indicates the response class (ok, error, information, warning, |
140
|
|
|
|
|
|
|
# etc.). Returns undef on failure. |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub response { |
143
|
19
|
|
|
19
|
0
|
47
|
my $self = shift; |
144
|
19
|
|
|
|
|
31
|
my ($code, $text); |
145
|
|
|
|
|
|
|
|
146
|
19
|
|
|
|
|
71
|
my $str = $self->getline(); |
147
|
|
|
|
|
|
|
|
148
|
19
|
50
|
|
|
|
59
|
return unless defined($str); |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
# Fail if the line we get isn't the proper format. |
151
|
19
|
50
|
|
|
|
431
|
return unless ( ($code, $text) = ($str =~ /^(\d+)\s*(.*?)\s*$/) ); |
152
|
|
|
|
|
|
|
|
153
|
19
|
|
|
|
|
72
|
$self->{response_code} = $code; |
154
|
19
|
|
|
|
|
63
|
$self->{response_text} = $text; |
155
|
19
|
|
|
|
|
165
|
substr($code, 0, 1); |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
159
|
|
|
|
|
|
|
# Accessors to retrieve the last response() call's code and text |
160
|
|
|
|
|
|
|
# separately. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
sub code { |
163
|
20
|
|
|
20
|
0
|
29
|
my $self = shift; |
164
|
20
|
|
|
|
|
117
|
$self->{response_code}; |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub text { |
168
|
1
|
|
|
1
|
0
|
3
|
my $self = shift; |
169
|
1
|
|
|
|
|
15
|
$self->{response_text}; |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
173
|
|
|
|
|
|
|
# Helper to print stuff for debugging. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub debug_print { |
176
|
154
|
|
|
154
|
0
|
344
|
my $self = shift; |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# Don't bother if not debugging. |
179
|
154
|
50
|
|
|
|
607
|
return unless $self->{debug}; |
180
|
|
|
|
|
|
|
|
181
|
0
|
|
|
|
|
0
|
my $level = shift; |
182
|
0
|
|
|
|
|
0
|
my $text = join('', @_); |
183
|
0
|
|
|
|
|
0
|
print STDERR $text, "\n"; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
187
|
|
|
|
|
|
|
# Read data until it's terminated by a single dot on its own line. |
188
|
|
|
|
|
|
|
# Two dots at the start of a line are replaced by one. Returns an |
189
|
|
|
|
|
|
|
# ARRAY reference containing the lines received, or undef on error. |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub read_until_dot { |
192
|
6
|
|
|
6
|
0
|
14
|
my $self = shift; |
193
|
6
|
|
|
|
|
15
|
my @lines; |
194
|
|
|
|
|
|
|
|
195
|
6
|
|
|
|
|
11
|
while ('true') { |
196
|
110
|
50
|
|
|
|
263
|
my $line = $self->getline() or return; |
197
|
110
|
100
|
|
|
|
418
|
last if ($line =~ /^\.$/); |
198
|
104
|
|
|
|
|
573
|
$line =~ s/^\.\././; |
199
|
104
|
|
|
|
|
192
|
push @lines, $line; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
6
|
|
|
|
|
35
|
\@lines; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
206
|
|
|
|
|
|
|
# Create an object to represent one or more cddbp sessions. |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub new { |
209
|
1
|
|
|
1
|
1
|
16
|
my $type = shift; |
210
|
1
|
|
|
|
|
8
|
my %param = @_; |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
# Attempt to suss our hostname. |
213
|
1
|
|
|
|
|
6
|
my $hostname = &hostname(); |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
# Attempt to suss our login ID. |
216
|
1
|
|
33
|
|
|
23
|
my $login = $param{Login} || $ENV{LOGNAME} || $ENV{USER}; |
217
|
1
|
50
|
|
|
|
7
|
if (not defined $login) { |
218
|
1
|
|
|
|
|
1
|
if (USING_WINDOWS) { |
219
|
|
|
|
|
|
|
carp( |
220
|
|
|
|
|
|
|
"Can't get login ID. Use Login parameter or " . |
221
|
|
|
|
|
|
|
"set LOGNAME or USER environment variable. Using default login " . |
222
|
|
|
|
|
|
|
"ID 'win32usr'" |
223
|
|
|
|
|
|
|
); |
224
|
|
|
|
|
|
|
$login = 'win32usr'; |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
else { |
227
|
1
|
50
|
|
|
|
933
|
$login = getpwuid($>) |
228
|
|
|
|
|
|
|
or croak( |
229
|
|
|
|
|
|
|
"Can't get login ID. " . |
230
|
|
|
|
|
|
|
"Set LOGNAME or USER environment variable and try again: $!" |
231
|
|
|
|
|
|
|
); |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# Debugging flag. |
236
|
1
|
|
|
|
|
4
|
my $debug = $param{Debug}; |
237
|
1
|
50
|
|
|
|
4
|
$debug = 0 unless defined $debug; |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# Choose a particular cddbp host. |
240
|
1
|
|
|
|
|
3
|
my $host = $param{Host}; |
241
|
1
|
50
|
|
|
|
2
|
$host = '' unless defined $host; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
# Choose a particular cddbp port. |
244
|
1
|
|
|
|
|
3
|
my $port = $param{Port}; |
245
|
1
|
50
|
|
|
|
2
|
$port = 8880 unless $port; |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# Choose a particular cddbp submission address. |
248
|
1
|
|
|
|
|
9
|
my $submit_to = $param{Submit_Address}; |
249
|
1
|
50
|
|
|
|
4
|
$submit_to = 'freedb-submit@freedb.org' unless defined $submit_to; |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Change the cddbp client name. |
252
|
1
|
|
|
|
|
3
|
my $client_name = $param{Client_Name}; |
253
|
1
|
50
|
|
|
|
3
|
$client_name = 'CDDB.pm' unless defined $client_name; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
# Change the cddbp client version. |
256
|
1
|
|
|
|
|
2
|
my $client_version = $param{Client_Version}; |
257
|
1
|
50
|
|
|
|
4
|
$client_version = $VERSION unless defined $client_version; |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# Whether to use utf-8 for submission |
260
|
1
|
|
|
|
|
1
|
my $utf8 = $param{Utf8}; |
261
|
1
|
50
|
|
|
|
4
|
$utf8 = 1 unless defined $utf8; |
262
|
1
|
50
|
|
|
|
4
|
if ($utf8) { |
263
|
1
|
|
|
|
|
2
|
eval { |
264
|
1
|
|
|
|
|
1072
|
require Encode; |
265
|
1
|
|
|
|
|
11160
|
import Encode; |
266
|
|
|
|
|
|
|
}; |
267
|
1
|
50
|
|
|
|
32
|
if ( $@ ) { |
268
|
0
|
|
|
|
|
0
|
carp 'Unable to load the Encode module, falling back to ascii'; |
269
|
0
|
|
|
|
|
0
|
$utf8 = 0; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
} |
272
|
|
|
|
|
|
|
|
273
|
1
|
50
|
|
|
|
4
|
eval 'sub encode { $_[1] };sub decode { $_[1] }' unless $utf8; |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# Change the cddbp protocol level. |
276
|
1
|
|
|
|
|
2
|
my $cddb_protocol = $param{Protocol_Version}; |
277
|
1
|
50
|
|
|
|
7
|
$cddb_protocol = ($utf8 ? 6 : 1) unless defined $cddb_protocol; |
|
|
50
|
|
|
|
|
|
278
|
1
|
50
|
33
|
|
|
8
|
carp <
|
279
|
|
|
|
|
|
|
You have requested protocol level $cddb_protocol. However, |
280
|
|
|
|
|
|
|
utf-8 support is only available starting from level 6 |
281
|
|
|
|
|
|
|
EOF |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# Mac Freaks Got Spaces! Augh! |
284
|
1
|
|
|
|
|
3
|
$login =~ s/\s+/_/g; |
285
|
|
|
|
|
|
|
|
286
|
1
|
|
|
|
|
19
|
my $self = bless { |
287
|
|
|
|
|
|
|
hostname => $hostname, |
288
|
|
|
|
|
|
|
login => $login, |
289
|
|
|
|
|
|
|
mail_from => undef, |
290
|
|
|
|
|
|
|
mail_host => undef, |
291
|
|
|
|
|
|
|
libname => $client_name, |
292
|
|
|
|
|
|
|
libver => $client_version, |
293
|
|
|
|
|
|
|
cddbmail => $submit_to, |
294
|
|
|
|
|
|
|
debug => $debug, |
295
|
|
|
|
|
|
|
host => $host, |
296
|
|
|
|
|
|
|
port => $port, |
297
|
|
|
|
|
|
|
cddb_protocol => $cddb_protocol, |
298
|
|
|
|
|
|
|
utf8 => $utf8, |
299
|
|
|
|
|
|
|
lines => [], |
300
|
|
|
|
|
|
|
frame => '', |
301
|
|
|
|
|
|
|
response_code => '000', |
302
|
|
|
|
|
|
|
response_text => '', |
303
|
|
|
|
|
|
|
}, $type; |
304
|
|
|
|
|
|
|
|
305
|
1
|
|
|
|
|
8
|
$self; |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
309
|
|
|
|
|
|
|
# Disconnect from a cddbp server. This is needed sometimes when a |
310
|
|
|
|
|
|
|
# server decides a session has performed enough requests. |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
sub disconnect { |
313
|
3
|
|
|
3
|
0
|
3165
|
my $self = shift; |
314
|
3
|
50
|
|
|
|
21
|
if ($self->{handle}) { |
315
|
3
|
|
|
|
|
11
|
$self->command('quit'); # quit |
316
|
3
|
|
|
|
|
14
|
$self->response(); # wait for any response |
317
|
3
|
|
|
|
|
6299
|
delete $self->{handle}; # close the socket |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
else { |
320
|
0
|
|
|
|
|
0
|
$self->debug_print( 0, '--- disconnect on unconnected handle' ); |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
325
|
|
|
|
|
|
|
# Connect to a cddbp server. Connecting and disconnecting are done |
326
|
|
|
|
|
|
|
# transparently and are performed on the basis of need. Furthermore, |
327
|
|
|
|
|
|
|
# this routine will cycle through servers until one connects or it has |
328
|
|
|
|
|
|
|
# exhausted all its possibilities. Returns true if successful, or |
329
|
|
|
|
|
|
|
# false if failed. |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
sub connect { |
332
|
3
|
|
|
3
|
0
|
6
|
my $self = shift; |
333
|
3
|
|
|
|
|
4
|
my $cddbp_host; |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# Try to get our hostname yet again, in case it failed during the |
336
|
|
|
|
|
|
|
# constructor call. |
337
|
3
|
50
|
|
|
|
23
|
unless (defined $self->{hostname}) { |
338
|
0
|
0
|
|
|
|
0
|
$self->{hostname} = &hostname() or croak "can't get hostname: $!"; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# The handshake loop tries to complete an entire connection |
342
|
|
|
|
|
|
|
# negociation. It loops until success, or until HOST returns |
343
|
|
|
|
|
|
|
# because all the hosts have failed us. |
344
|
|
|
|
|
|
|
|
345
|
3
|
|
|
|
|
6
|
HANDSHAKE: while ('true') { |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# Loop through the CDDB protocol hosts list up to twice in order |
348
|
|
|
|
|
|
|
# to find a server that will respond. This implements a 2x retry. |
349
|
|
|
|
|
|
|
|
350
|
3
|
|
|
|
|
17
|
HOST: for (1..(@cddbp_hosts * 2)) { |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
# Hard disconnect here to prevent recursion. |
353
|
4
|
|
|
|
|
11
|
delete $self->{handle}; |
354
|
|
|
|
|
|
|
|
355
|
4
|
|
|
|
|
6
|
($self->{host}, $self->{port}) = @{$cddbp_hosts[$cddbp_host_selector]}; |
|
4
|
|
|
|
|
27
|
|
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
# Assign the host we selected, and attempt a connection. |
358
|
4
|
|
|
|
|
30
|
$self->debug_print( |
359
|
|
|
|
|
|
|
0, |
360
|
|
|
|
|
|
|
"=== connecting to $self->{host} port $self->{port}" |
361
|
|
|
|
|
|
|
); |
362
|
4
|
|
|
|
|
54
|
$self->{handle} = new IO::Socket::INET( |
363
|
|
|
|
|
|
|
PeerAddr => $self->{host}, |
364
|
|
|
|
|
|
|
PeerPort => $self->{port}, |
365
|
|
|
|
|
|
|
Proto => 'tcp', |
366
|
|
|
|
|
|
|
Timeout => 30, |
367
|
|
|
|
|
|
|
); |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
# The host did not answer. Clean up after the failed attempt |
370
|
|
|
|
|
|
|
# and cycle to the next host. |
371
|
4
|
100
|
|
|
|
129340
|
unless (defined $self->{handle}) { |
372
|
1
|
|
|
|
|
15
|
$self->debug_print( |
373
|
|
|
|
|
|
|
0, |
374
|
|
|
|
|
|
|
"--- error connecting to $self->{host} port $self->{port}: $!" |
375
|
|
|
|
|
|
|
); |
376
|
|
|
|
|
|
|
|
377
|
1
|
|
|
|
|
4
|
delete $self->{handle}; |
378
|
1
|
|
|
|
|
4
|
$self->{host} = $self->{port} = ''; |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
# Try the next host in the list. Wrap if necessary. |
381
|
1
|
50
|
|
|
|
6
|
$cddbp_host_selector = 0 if ++$cddbp_host_selector > @cddbp_hosts; |
382
|
|
|
|
|
|
|
|
383
|
1
|
|
|
|
|
4
|
next HOST; |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
# The host accepted our connection. We'll push it back on the |
387
|
|
|
|
|
|
|
# list of known cddbp hosts so it can be tried later. And we're |
388
|
|
|
|
|
|
|
# done with the host list cycle for now. |
389
|
|
|
|
|
|
|
$self->debug_print( |
390
|
3
|
|
|
|
|
39
|
0, |
391
|
|
|
|
|
|
|
"+++ successfully connected to $self->{host} port $self->{port}" |
392
|
|
|
|
|
|
|
); |
393
|
|
|
|
|
|
|
|
394
|
3
|
|
|
|
|
14
|
last HOST; |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# Tried the whole list twice without success? Time to give up. |
398
|
3
|
50
|
|
|
|
16
|
unless (defined $self->{handle}) { |
399
|
0
|
|
|
|
|
0
|
$self->debug_print( 0, "--- all cddbp servers failed to answer" ); |
400
|
0
|
0
|
|
|
|
0
|
warn "No cddb protocol servers answer. Is your network OK?\n" |
401
|
|
|
|
|
|
|
unless $self->{debug}; |
402
|
0
|
|
|
|
|
0
|
return; |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
# Turn off buffering on the socket handle. |
406
|
3
|
|
|
|
|
42
|
select((select($self->{handle}), $|=1)[0]); |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
# Get the server's banner message. Try reconnecting if it's bad. |
409
|
3
|
|
|
|
|
16
|
my $code = $self->response(); |
410
|
3
|
50
|
|
|
|
20
|
if ($code != 2) { |
411
|
0
|
|
|
|
|
0
|
$self->debug_print( |
412
|
|
|
|
|
|
|
0, "--- bad cddbp response: ", |
413
|
|
|
|
|
|
|
$self->code(), ' ', $self->text() |
414
|
|
|
|
|
|
|
); |
415
|
0
|
|
|
|
|
0
|
next HANDSHAKE; |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# Say hello, and wait for a response. |
419
|
|
|
|
|
|
|
$self->command( |
420
|
3
|
|
|
|
|
28
|
'cddb hello', |
421
|
|
|
|
|
|
|
$self->{login}, $self->{hostname}, |
422
|
|
|
|
|
|
|
$self->{libname}, $self->{libver} |
423
|
|
|
|
|
|
|
); |
424
|
3
|
|
|
|
|
16
|
$code = $self->response(); |
425
|
3
|
50
|
|
|
|
28
|
if ($code == 4) { |
426
|
0
|
|
|
|
|
0
|
$self->debug_print( |
427
|
|
|
|
|
|
|
0, "--- the server denies us: ", |
428
|
|
|
|
|
|
|
$self->code(), ' ', $self->text() |
429
|
|
|
|
|
|
|
); |
430
|
0
|
|
|
|
|
0
|
return; |
431
|
|
|
|
|
|
|
} |
432
|
3
|
50
|
|
|
|
13
|
if ($code != 2) { |
433
|
0
|
|
|
|
|
0
|
$self->debug_print( |
434
|
|
|
|
|
|
|
0, "--- the server didn't handshake: ", |
435
|
|
|
|
|
|
|
$self->code(), ' ', $self->text() |
436
|
|
|
|
|
|
|
); |
437
|
0
|
|
|
|
|
0
|
next HANDSHAKE; |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# Set the protocol level. |
441
|
3
|
50
|
|
|
|
39
|
if ($self->{cddb_protocol} != 1) { |
442
|
3
|
|
|
|
|
19
|
$self->command( 'proto', $self->{cddb_protocol} ); |
443
|
3
|
|
|
|
|
15
|
$code = $self->response(); |
444
|
3
|
50
|
|
|
|
46
|
if ($code != 2) { |
445
|
0
|
|
|
|
|
0
|
$self->debug_print( |
446
|
|
|
|
|
|
|
0, "--- can't set protocol level ", |
447
|
|
|
|
|
|
|
$self->{cddb_protocol}, ' ', |
448
|
|
|
|
|
|
|
$self->code(), ' ', $self->text() |
449
|
|
|
|
|
|
|
); |
450
|
0
|
|
|
|
|
0
|
return; |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
# If we get here, everything succeeded. |
455
|
3
|
|
|
|
|
19
|
return 1; |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
# Destroying the cddbp object disconnects from the server. |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
sub DESTROY { |
462
|
1
|
|
|
1
|
|
1312
|
my $self = shift; |
463
|
1
|
|
|
|
|
6
|
$self->disconnect(); |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
############################################################################### |
467
|
|
|
|
|
|
|
# High-level cddbp functions. |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
470
|
|
|
|
|
|
|
# Get a list of available genres. Returns an array of genre names, or |
471
|
|
|
|
|
|
|
# undef on failure. |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
sub get_genres { |
474
|
1
|
|
|
1
|
1
|
833
|
my $self = shift; |
475
|
1
|
|
|
|
|
2
|
my @genres; |
476
|
|
|
|
|
|
|
|
477
|
1
|
|
|
|
|
5
|
$self->command('cddb lscat'); |
478
|
1
|
|
|
|
|
8
|
my $code = $self->response(); |
479
|
1
|
50
|
|
|
|
11
|
return unless $code; |
480
|
|
|
|
|
|
|
|
481
|
1
|
50
|
|
|
|
7
|
if ($code == 2) { |
482
|
1
|
|
|
|
|
4
|
my $genres = $self->read_until_dot(); |
483
|
1
|
50
|
|
|
|
17
|
return @$genres if defined $genres; |
484
|
0
|
|
|
|
|
0
|
return; |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
$self->debug_print( |
488
|
0
|
|
|
|
|
0
|
0, '--- error listing categories: ', |
489
|
|
|
|
|
|
|
$self->code(), ' ', $self->text() |
490
|
|
|
|
|
|
|
); |
491
|
0
|
|
|
|
|
0
|
return; |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
495
|
|
|
|
|
|
|
# Calculate a cddbp ID based on a text table of contents. The text |
496
|
|
|
|
|
|
|
# format was chosen because it was straightforward and easy to |
497
|
|
|
|
|
|
|
# generate. In a scalar context, this returns just the cddbp ID. In |
498
|
|
|
|
|
|
|
# a list context it returns several things: a listref of track |
499
|
|
|
|
|
|
|
# numbers, a listref of track lengths (MM:SS format), a listref of |
500
|
|
|
|
|
|
|
# track offsets (in seconds), and the disc's total playing time in |
501
|
|
|
|
|
|
|
# seconds. In either context it returns undef on failure. |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
sub calculate_id { |
504
|
2
|
|
|
2
|
1
|
1717
|
my $self = shift; |
505
|
2
|
|
|
|
|
9
|
my @toc = @_; |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
my ( |
508
|
2
|
|
|
|
|
12
|
$seconds_previous, $seconds_first, $seconds_last, $cddbp_sum, |
509
|
|
|
|
|
|
|
@track_numbers, @track_lengths, @track_offsets, |
510
|
|
|
|
|
|
|
); |
511
|
|
|
|
|
|
|
|
512
|
2
|
|
|
|
|
7
|
foreach my $line (@toc) { |
513
|
4
|
|
|
|
|
27
|
my ($track, $mm_begin, $ss_begin, $ff_begin) = split(/\s+/, $line, 4); |
514
|
4
|
|
|
|
|
14
|
my $frame_offset = (($mm_begin * 60 + $ss_begin) * 75) + $ff_begin; |
515
|
4
|
|
|
|
|
9
|
my $seconds_begin = int($frame_offset / 75); |
516
|
|
|
|
|
|
|
|
517
|
4
|
100
|
|
|
|
12
|
if (defined $seconds_previous) { |
518
|
2
|
|
|
|
|
289
|
my $elapsed = $seconds_begin - $seconds_previous; |
519
|
2
|
|
|
|
|
15
|
push( |
520
|
|
|
|
|
|
|
@track_lengths, |
521
|
|
|
|
|
|
|
sprintf("%02d:%02d", int($elapsed / 60), $elapsed % 60) |
522
|
|
|
|
|
|
|
); |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
else { |
525
|
2
|
|
|
|
|
4
|
$seconds_first = $seconds_begin; |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
# Track 999 was chosen for the lead-out information. |
529
|
4
|
100
|
|
|
|
14
|
if ($track == 999) { |
530
|
2
|
|
|
|
|
3
|
$seconds_last = $seconds_begin; |
531
|
2
|
|
|
|
|
6
|
last; |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
# Track 1000 was chosen for error information. |
535
|
2
|
50
|
|
|
|
6
|
if ($track == 1000) { |
536
|
0
|
|
|
|
|
0
|
$self->debug_print( 0, "error in TOC: $ff_begin" ); |
537
|
0
|
|
|
|
|
0
|
return; |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
|
540
|
2
|
|
|
|
|
6
|
map { $cddbp_sum += $_; } split(//, $seconds_begin); |
|
2
|
|
|
|
|
5
|
|
541
|
2
|
|
|
|
|
5
|
push @track_offsets, $frame_offset; |
542
|
2
|
|
|
|
|
14
|
push @track_numbers, sprintf("%03d", $track); |
543
|
2
|
|
|
|
|
3
|
$seconds_previous = $seconds_begin; |
544
|
|
|
|
|
|
|
} |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
# Calculate the ID. Whee! |
547
|
2
|
|
|
|
|
9
|
my $id = sprintf( |
548
|
|
|
|
|
|
|
"%02x%04x%02x", |
549
|
|
|
|
|
|
|
($cddbp_sum % 255), |
550
|
|
|
|
|
|
|
$seconds_last - $seconds_first, |
551
|
|
|
|
|
|
|
scalar(@track_offsets) |
552
|
|
|
|
|
|
|
); |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
# In list context, we return several things. Some of them are |
555
|
|
|
|
|
|
|
# useful for generating filenames or playlists (the padded track |
556
|
|
|
|
|
|
|
# numbers). Others are needed for cddbp queries. |
557
|
|
|
|
|
|
|
return ( |
558
|
2
|
50
|
|
|
|
22
|
$id, \@track_numbers, \@track_lengths, \@track_offsets, $seconds_last |
559
|
|
|
|
|
|
|
) if wantarray(); |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
# Just return the cddbp ID in scalar context. |
562
|
0
|
|
|
|
|
0
|
return $id; |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
566
|
|
|
|
|
|
|
# Parse cdinfo's output so calculate_id() can eat it. |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
sub parse_cdinfo { |
569
|
0
|
|
|
0
|
1
|
0
|
my ($self, $command) = @_; |
570
|
0
|
0
|
|
|
|
0
|
open(FH, $command) or croak "could not open `$command': $!"; |
571
|
|
|
|
|
|
|
|
572
|
0
|
|
|
|
|
0
|
my @toc; |
573
|
0
|
|
|
|
|
0
|
while () { |
574
|
0
|
0
|
|
|
|
0
|
if (/(\d+):\s+(\d+):(\d+):(\d+)/) { |
575
|
0
|
|
|
|
|
0
|
my @track = ($1,$2,$3,$4); |
576
|
0
|
0
|
|
|
|
0
|
$track[0] = 999 if /leadout/; |
577
|
0
|
|
|
|
|
0
|
push @toc, "@track"; |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
} |
580
|
0
|
|
|
|
|
0
|
close FH; |
581
|
0
|
|
|
|
|
0
|
return @toc; |
582
|
|
|
|
|
|
|
} |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
585
|
|
|
|
|
|
|
# Get a list of discs that match a particular CD's table of contents. |
586
|
|
|
|
|
|
|
# This accepts the TOC information as returned by calculate_id(). It |
587
|
|
|
|
|
|
|
# will also accept information in mp3 format, but I forget what that |
588
|
|
|
|
|
|
|
# is. Pudge asked for it, so he'd know. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
sub get_discs { |
591
|
5
|
|
|
5
|
1
|
10431
|
my $self = shift; |
592
|
5
|
|
|
|
|
134
|
my ($id, $offsets, $total_seconds) = @_; |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
# Accept the TOC in CDDB.pm format. |
595
|
5
|
|
|
|
|
10
|
my ($track_count, $offsets_string); |
596
|
5
|
50
|
|
|
|
126
|
if (ref($offsets) eq 'ARRAY') { |
597
|
5
|
|
|
|
|
11
|
$track_count = scalar(@$offsets); |
598
|
5
|
|
|
|
|
20
|
$offsets_string = join ' ', @$offsets; |
599
|
|
|
|
|
|
|
} |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
# Accept the TOC in mp3 format, for pudge. |
602
|
|
|
|
|
|
|
else { |
603
|
0
|
|
|
|
|
0
|
$offsets =~ /^(\d+?)\s+(.*)$/; |
604
|
0
|
|
|
|
|
0
|
$track_count = $1; |
605
|
0
|
|
|
|
|
0
|
$offsets_string = $2; |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
# Make repeated attempts to query the server. I do this to drive |
609
|
|
|
|
|
|
|
# the hidden server cycling. |
610
|
5
|
|
|
|
|
8
|
my $code; |
611
|
|
|
|
|
|
|
|
612
|
5
|
|
|
|
|
10
|
ATTEMPT: while ('true') { |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
# Send a cddbp query command. |
615
|
5
|
50
|
|
|
|
89
|
$self->command( |
616
|
|
|
|
|
|
|
'cddb query', $id, $track_count, |
617
|
|
|
|
|
|
|
$offsets_string, $total_seconds |
618
|
|
|
|
|
|
|
) or return; |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
# Get the response. Try again if the server is temporarly |
621
|
|
|
|
|
|
|
# unavailable. |
622
|
5
|
|
|
|
|
26
|
$code = $self->response(); |
623
|
5
|
50
|
|
|
|
33
|
next ATTEMPT if $self->code() == 417; |
624
|
5
|
|
|
|
|
19
|
last ATTEMPT; |
625
|
|
|
|
|
|
|
} |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
# Return undef if there's a problem. |
628
|
5
|
50
|
33
|
|
|
60
|
return unless defined $code and $code == 2; |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# Single matching disc. |
631
|
5
|
100
|
|
|
|
14
|
if ($self->code() == 200) { |
632
|
1
|
|
|
|
|
7
|
my ($genre, $cddbp_id, $title) = ( |
633
|
|
|
|
|
|
|
$self->text() =~ /^(\S+)\s*(\S+)\s*(.*?)\s*$/ |
634
|
|
|
|
|
|
|
); |
635
|
1
|
|
|
|
|
10
|
return [ $genre, $cddbp_id, $title ]; |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
# No matching discs. |
639
|
4
|
50
|
|
|
|
14
|
return if $self->code() == 202; |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
# Multiple matching discs. |
642
|
|
|
|
|
|
|
# 210 Found exact matches, list follows (...) [proto>=4] |
643
|
|
|
|
|
|
|
# 211 Found inexact matches, list follows (...) [proto>=1] |
644
|
4
|
50
|
66
|
|
|
14
|
if ($self->code() == 210 or $self->code() == 211) { |
645
|
4
|
|
|
|
|
15
|
my $discs = $self->read_until_dot(); |
646
|
4
|
50
|
|
|
|
15
|
return unless defined $discs; |
647
|
|
|
|
|
|
|
|
648
|
4
|
|
|
|
|
7
|
my @matches; |
649
|
4
|
|
|
|
|
16
|
foreach my $disc (@$discs) { |
650
|
74
|
|
|
|
|
955
|
my ($genre, $cddbp_id, $title) = ($disc =~ /^(\S+)\s*(\S+)\s*(.*?)\s*$/); |
651
|
74
|
|
|
|
|
289
|
push(@matches, [ $genre, $cddbp_id, $title ]); |
652
|
|
|
|
|
|
|
} |
653
|
|
|
|
|
|
|
|
654
|
4
|
|
|
|
|
81
|
return @matches; |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
# What the heck? |
658
|
|
|
|
|
|
|
$self->debug_print( |
659
|
0
|
|
|
|
|
0
|
0, "--- unknown cddbp response: ", |
660
|
|
|
|
|
|
|
$self->code(), ' ', $self->text() |
661
|
|
|
|
|
|
|
); |
662
|
0
|
|
|
|
|
0
|
return; |
663
|
|
|
|
|
|
|
} |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
666
|
|
|
|
|
|
|
# A little helper to combine list-context calculate_id() with |
667
|
|
|
|
|
|
|
# get_discs(). |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
sub get_discs_by_toc { |
670
|
1
|
|
|
1
|
1
|
10
|
my $self = shift; |
671
|
1
|
|
|
|
|
3
|
my (@info, @discs); |
672
|
1
|
50
|
|
|
|
6
|
if (@info = $self->calculate_id(@_)) { |
673
|
1
|
|
|
|
|
8
|
@discs = $self->get_discs(@info[0, 3, 4]); |
674
|
|
|
|
|
|
|
} |
675
|
1
|
|
|
|
|
11
|
@discs; |
676
|
|
|
|
|
|
|
} |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
679
|
|
|
|
|
|
|
# A little helper to get discs from an existing query string. |
680
|
|
|
|
|
|
|
# Contributed by Ron Grabowski. |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
sub get_discs_by_query { |
683
|
1
|
|
|
1
|
1
|
791
|
my ($self, $query) = @_; |
684
|
1
|
|
|
|
|
10
|
my (undef, undef, $cddbp_id, $tracks, @offsets) = split /\s+/, $query; |
685
|
1
|
|
|
|
|
4
|
my $total_seconds = pop @offsets; |
686
|
1
|
|
|
|
|
6
|
my @discs = $self->get_discs($cddbp_id, \@offsets, $total_seconds); |
687
|
1
|
|
|
|
|
37
|
return @discs; |
688
|
|
|
|
|
|
|
} |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
691
|
|
|
|
|
|
|
# Retrieve the database record for a particular genre/id combination. |
692
|
|
|
|
|
|
|
# Returns a moderately complex hashref representing the cddbp record, |
693
|
|
|
|
|
|
|
# or undef on failure. |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
sub get_disc_details { |
696
|
1
|
|
|
1
|
1
|
21
|
my $self = shift; |
697
|
1
|
|
|
|
|
9
|
my ($genre, $id) = @_; |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
# Because cddbp only allows one detail query per connection, we |
700
|
|
|
|
|
|
|
# force a disconnect/reconnect here if we already did one. |
701
|
1
|
50
|
|
|
|
11
|
if (exists $self->{'got tracks before'}) { |
702
|
0
|
|
|
|
|
0
|
$self->disconnect(); |
703
|
0
|
0
|
|
|
|
0
|
$self->connect() or return; |
704
|
|
|
|
|
|
|
} |
705
|
1
|
|
|
|
|
6
|
$self->{'got tracks before'} = 'yes'; |
706
|
|
|
|
|
|
|
|
707
|
1
|
|
|
|
|
8
|
$self->command('cddb read', $genre, $id); |
708
|
1
|
|
|
|
|
7
|
my $code = $self->response(); |
709
|
1
|
50
|
|
|
|
34
|
if ($code != 2) { |
710
|
0
|
|
|
|
|
0
|
$self->debug_print( |
711
|
|
|
|
|
|
|
0, "--- cddbp host could not read the disc record: ", |
712
|
|
|
|
|
|
|
$self->code(), ' ', $self->text() |
713
|
|
|
|
|
|
|
); |
714
|
0
|
|
|
|
|
0
|
return; |
715
|
|
|
|
|
|
|
} |
716
|
|
|
|
|
|
|
|
717
|
1
|
|
|
|
|
3
|
my $track_file; |
718
|
1
|
50
|
|
|
|
5
|
unless (defined($track_file = $self->read_until_dot())) { |
719
|
0
|
|
|
|
|
0
|
$self->debug_print( 0, "--- cddbp disc record interrupted" ); |
720
|
0
|
|
|
|
|
0
|
return; |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
# Parse that puppy. |
724
|
1
|
|
|
|
|
8
|
return parse_xmcd_file($track_file, $genre); |
725
|
|
|
|
|
|
|
} |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
# Arf! |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
sub parse_xmcd_file { |
730
|
1
|
|
|
1
|
1
|
5
|
my ($track_file, $genre) = @_; |
731
|
|
|
|
|
|
|
|
732
|
1
|
|
|
|
|
8
|
my %details = ( |
733
|
|
|
|
|
|
|
offsets => [ ], |
734
|
|
|
|
|
|
|
seconds => [ ], |
735
|
|
|
|
|
|
|
); |
736
|
1
|
|
|
|
|
3
|
my $state = 'beginning'; |
737
|
1
|
|
|
|
|
5
|
foreach my $line (@$track_file) { |
738
|
|
|
|
|
|
|
# Keep returned so-called xmcd record... |
739
|
19
|
|
|
|
|
47
|
$details{xmcd_record} .= $line . "\n"; |
740
|
|
|
|
|
|
|
|
741
|
19
|
100
|
|
|
|
39
|
if ($state eq 'beginning') { |
742
|
3
|
100
|
|
|
|
38
|
if ($line =~ /track\s*frame\s*off/i) { |
743
|
1
|
|
|
|
|
3
|
$state = 'offsets'; |
744
|
|
|
|
|
|
|
} |
745
|
3
|
|
|
|
|
6
|
next; |
746
|
|
|
|
|
|
|
} |
747
|
|
|
|
|
|
|
|
748
|
16
|
100
|
|
|
|
34
|
if ($state eq 'offsets') { |
749
|
2
|
100
|
|
|
|
12
|
if ($line =~ /^\#\s*(\d+)/) { |
750
|
1
|
|
|
|
|
3
|
push @{$details{offsets}}, $1; |
|
1
|
|
|
|
|
6
|
|
751
|
1
|
|
|
|
|
3
|
next; |
752
|
|
|
|
|
|
|
} |
753
|
1
|
|
|
|
|
2
|
$state = 'headers'; |
754
|
|
|
|
|
|
|
# This passes through on purpose. |
755
|
|
|
|
|
|
|
} |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
# This is not an elsif on purpose. |
758
|
15
|
100
|
|
|
|
29
|
if ($state eq 'headers') { |
759
|
8
|
100
|
|
|
|
23
|
if ($line =~ /^\#/) { |
760
|
7
|
|
|
|
|
37
|
$line =~ s/\s+/ /g; |
761
|
7
|
100
|
|
|
|
54
|
if (my ($header, $value) = ($line =~ /^\#\s*(.*?)\:\s*(.*?)\s*$/)) { |
762
|
4
|
|
|
|
|
28
|
$details{lc($header)} = $value; |
763
|
|
|
|
|
|
|
} |
764
|
7
|
|
|
|
|
12
|
next; |
765
|
|
|
|
|
|
|
} |
766
|
1
|
|
|
|
|
3
|
$state = 'data'; |
767
|
|
|
|
|
|
|
# This passes through on purpose. |
768
|
|
|
|
|
|
|
} |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
# This is not an elsif on purpose. |
771
|
8
|
50
|
|
|
|
17
|
if ($state eq 'data') { |
772
|
|
|
|
|
|
|
next unless ( |
773
|
8
|
100
|
|
|
|
62
|
my ($tag, $idx, $val) = ($line =~ /^\s*(.+?)(\d*)\s*\=\s*(.+?)\s*$/) |
774
|
|
|
|
|
|
|
); |
775
|
6
|
|
|
|
|
11
|
$tag = lc($tag); |
776
|
|
|
|
|
|
|
|
777
|
6
|
100
|
|
|
|
13
|
if ($idx ne '') { |
778
|
1
|
|
|
|
|
2
|
$tag .= 's'; |
779
|
1
|
50
|
|
|
|
6
|
$details{$tag} = [ ] unless exists $details{$tag}; |
780
|
1
|
|
|
|
|
5
|
$details{$tag}->[$idx] .= $val; |
781
|
1
|
|
|
|
|
5
|
$details{$tag}->[$idx] =~ s/^\s+//; |
782
|
1
|
|
|
|
|
11
|
$details{$tag}->[$idx] =~ s/\s+$//; |
783
|
1
|
|
|
|
|
5
|
$details{$tag}->[$idx] =~ s/\s+/ /g; |
784
|
|
|
|
|
|
|
} |
785
|
|
|
|
|
|
|
else { |
786
|
5
|
|
|
|
|
16
|
$details{$tag} .= $val; |
787
|
5
|
|
|
|
|
13
|
$details{$tag} =~ s/^\s+//; |
788
|
5
|
|
|
|
|
15
|
$details{$tag} =~ s/\s+$//; |
789
|
5
|
|
|
|
|
20
|
$details{$tag} =~ s/\s+/ /g; |
790
|
|
|
|
|
|
|
} |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
# Translate disc offsets into seconds. This builds a virtual track |
795
|
|
|
|
|
|
|
# 0, which is the time from the beginning of the disc to the |
796
|
|
|
|
|
|
|
# beginning of the first song. That time's used later to calculate |
797
|
|
|
|
|
|
|
# the final track's length. |
798
|
|
|
|
|
|
|
|
799
|
1
|
|
|
|
|
2
|
my $last_offset = 0; |
800
|
1
|
|
|
|
|
2
|
foreach (@{$details{offsets}}) { |
|
1
|
|
|
|
|
3
|
|
801
|
1
|
|
|
|
|
2
|
push @{$details{seconds}}, int(($_ - $last_offset) / 75); |
|
1
|
|
|
|
|
6
|
|
802
|
1
|
|
|
|
|
3
|
$last_offset = $_; |
803
|
|
|
|
|
|
|
} |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
# Create the final track length from the disc length. Remove the |
806
|
|
|
|
|
|
|
# virtual track 0 in the process. |
807
|
|
|
|
|
|
|
|
808
|
1
|
|
|
|
|
2
|
my $disc_length = $details{"disc length"}; |
809
|
1
|
|
|
|
|
8
|
$disc_length =~ s/ .*$//; |
810
|
|
|
|
|
|
|
|
811
|
1
|
|
|
|
|
2
|
my $first_start = shift @{$details{seconds}}; |
|
1
|
|
|
|
|
10
|
|
812
|
1
|
|
|
|
|
6
|
push( |
813
|
1
|
|
|
|
|
2
|
@{$details{seconds}}, |
814
|
|
|
|
|
|
|
$disc_length - int($details{offsets}->[-1] / 75) + 1 - $first_start |
815
|
|
|
|
|
|
|
); |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
# Add the genre, if we have it. |
818
|
1
|
|
|
|
|
2
|
$details{genre} = $genre; |
819
|
|
|
|
|
|
|
|
820
|
1
|
|
|
|
|
8
|
return \%details; |
821
|
|
|
|
|
|
|
} |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
############################################################################### |
824
|
|
|
|
|
|
|
# Evil voodoo e-mail submission stuff. |
825
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
827
|
|
|
|
|
|
|
# Return true/false whether the libraries needed to submit discs are |
828
|
|
|
|
|
|
|
# present. |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
sub can_submit_disc { |
831
|
1
|
|
|
1
|
1
|
997
|
my $self = shift; |
832
|
1
|
|
|
|
|
3
|
$imported_mail; |
833
|
|
|
|
|
|
|
} |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
836
|
|
|
|
|
|
|
# Build an e-mail address, and return it. Caches the last built |
837
|
|
|
|
|
|
|
# address, and returns that on subsequent calls. |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
sub get_mail_address { |
840
|
1
|
|
|
1
|
1
|
3
|
my $self = shift; |
841
|
1
|
50
|
|
|
|
6
|
return $self->{mail_from} if defined $self->{mail_from}; |
842
|
1
|
|
|
|
|
8
|
return $self->{mail_from} = $self->{login} . '@' . $self->{hostname}; |
843
|
|
|
|
|
|
|
} |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
846
|
|
|
|
|
|
|
# Build an e-mail host, and return it. Caches the last built e-mail |
847
|
|
|
|
|
|
|
# host, and returns that on subsequent calls. |
848
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
sub get_mail_host { |
850
|
1
|
|
|
1
|
1
|
3
|
my $self = shift; |
851
|
|
|
|
|
|
|
|
852
|
1
|
50
|
|
|
|
6
|
return $self->{mail_host} if defined $self->{mail_host}; |
853
|
|
|
|
|
|
|
|
854
|
1
|
50
|
|
|
|
1051
|
if (exists $ENV{SMTPHOSTS}) { |
|
|
50
|
|
|
|
|
|
855
|
0
|
|
|
|
|
0
|
$self->{mail_host} = $ENV{SMTPHOSTS}; |
856
|
|
|
|
|
|
|
} |
857
|
|
|
|
|
|
|
elsif (defined inet_aton('mail')) { |
858
|
1
|
|
|
|
|
6
|
$self->{mail_host} = 'mail'; |
859
|
|
|
|
|
|
|
} |
860
|
|
|
|
|
|
|
else { |
861
|
0
|
|
|
|
|
0
|
$self->{mail_host} = 'localhost'; |
862
|
|
|
|
|
|
|
} |
863
|
1
|
|
|
|
|
7
|
return $self->{mail_host}; |
864
|
|
|
|
|
|
|
} |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
# Build a cddbp disc submission and try to e-mail it. |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
sub submit_disc { |
869
|
1
|
|
|
1
|
1
|
13
|
my $self = shift; |
870
|
1
|
|
|
|
|
10
|
my %params = @_; |
871
|
|
|
|
|
|
|
|
872
|
1
|
50
|
|
|
|
8
|
croak( |
873
|
|
|
|
|
|
|
"submit_disc needs Mail::Internet, Mail::Header, and MIME::QuotedPrint" |
874
|
|
|
|
|
|
|
) unless $imported_mail; |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
# Try yet again to fetch the hostname. Fail if we cannot. |
877
|
1
|
50
|
|
|
|
14
|
unless (defined $self->{hostname}) { |
878
|
0
|
0
|
|
|
|
0
|
$self->{hostname} = &hostname() or croak "can't get hostname: $!"; |
879
|
|
|
|
|
|
|
} |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
# Validate the required submission fields. XXX Duplicated code. |
882
|
1
|
50
|
|
|
|
88
|
(exists $params{Genre}) or croak "submit_disc needs a Genre"; |
883
|
1
|
50
|
|
|
|
7
|
(exists $params{Id}) or croak "submit_disc needs an Id"; |
884
|
1
|
50
|
|
|
|
14
|
(exists $params{Artist}) or croak "submit_disc needs an Artist"; |
885
|
1
|
50
|
|
|
|
6
|
(exists $params{DiscTitle}) or croak "submit_disc needs a DiscTitle"; |
886
|
1
|
50
|
|
|
|
4
|
(exists $params{TrackTitles}) or croak "submit_disc needs TrackTitles"; |
887
|
1
|
50
|
|
|
|
6
|
(exists $params{Offsets}) or croak "submit_disc needs Offsets"; |
888
|
1
|
50
|
|
|
|
5
|
(exists $params{Revision}) or croak "submit_disc needs a Revision"; |
889
|
1
|
50
|
|
|
|
6
|
if (exists $params{Year}) { |
890
|
0
|
0
|
|
|
|
0
|
unless ($params{Year} =~ /^\d{4}$/) { |
891
|
0
|
|
|
|
|
0
|
croak "submit_disc needs a 4 digit year"; |
892
|
|
|
|
|
|
|
} |
893
|
|
|
|
|
|
|
} |
894
|
1
|
50
|
|
|
|
5
|
if (exists $params{GenreLong}) { |
895
|
0
|
0
|
|
|
|
0
|
unless ($params{GenreLong} =~ /^([A-Z][a-zA-Z0-9]*\s?)+$/) { |
896
|
0
|
|
|
|
|
0
|
croak( |
897
|
|
|
|
|
|
|
"GenreLong must start with a capital letter and contain only " . |
898
|
|
|
|
|
|
|
"letters and numbers" |
899
|
|
|
|
|
|
|
); |
900
|
|
|
|
|
|
|
} |
901
|
|
|
|
|
|
|
} |
902
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
# Try to find a mail host. We could probably grab the MX record for |
904
|
|
|
|
|
|
|
# the current machine, but that would require yet more strange |
905
|
|
|
|
|
|
|
# modules. TODO: Use Net::DNS if it's available (why not?) and just |
906
|
|
|
|
|
|
|
# bypass it if it isn't installed. |
907
|
|
|
|
|
|
|
|
908
|
1
|
50
|
|
|
|
6
|
$self->{mail_host} = $params{Host} if exists $params{Host}; |
909
|
1
|
|
|
|
|
5
|
my $host = $self->get_mail_host(); |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
# Override the sender's e-mail address with whatever was specified |
912
|
|
|
|
|
|
|
# during the object's constructor call. |
913
|
1
|
50
|
|
|
|
7
|
$self->{mail_from} = $params{From} if exists $params{From}; |
914
|
1
|
|
|
|
|
7
|
my $from = $self->get_mail_address(); |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
# Build the submission's headers. |
917
|
1
|
|
|
|
|
242
|
my $header = new Mail::Header; |
918
|
1
|
|
|
|
|
824
|
$header->add( 'MIME-Version' => '1.0' ); |
919
|
1
|
50
|
|
|
|
684
|
my $charset = $self->{'utf8'} ? 'utf-8' : 'iso-8859-1'; |
920
|
1
|
|
|
|
|
9
|
$header->add( 'Content-Type' => "text/plain; charset=$charset" ); |
921
|
1
|
|
|
|
|
181
|
$header->add( 'Content-Disposition' => 'inline' ); |
922
|
1
|
|
|
|
|
166
|
$header->add( 'Content-Transfer-Encoding' => 'quoted-printable' ); |
923
|
1
|
|
|
|
|
154
|
$header->add( From => $from ); |
924
|
1
|
|
|
|
|
2493
|
$header->add( To => $self->{cddbmail} ); |
925
|
|
|
|
|
|
|
# send a copy to ourselves if we are debugging |
926
|
1
|
50
|
|
|
|
133
|
$header->add( Cc => $from ) if $self->{debug}; |
927
|
1
|
|
|
|
|
9
|
$header->add( Subject => "cddb $params{Genre} $params{Id}" ); |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
# Build the submission's body. |
930
|
1
|
|
|
|
|
26
|
my @message_body = ( |
931
|
|
|
|
|
|
|
'# xmcd', |
932
|
|
|
|
|
|
|
'#', |
933
|
|
|
|
|
|
|
'# Track frame offsets:', |
934
|
1
|
|
|
|
|
128
|
map({ "#\t" . $_; } @{$params{Offsets}}), |
|
1
|
|
|
|
|
221
|
|
935
|
|
|
|
|
|
|
'#', |
936
|
|
|
|
|
|
|
'# Disc length: ' . (hex(substr($params{Id},2,4))+2) . ' seconds', |
937
|
|
|
|
|
|
|
'#', |
938
|
|
|
|
|
|
|
"# Revision: " . $params{Revision}, |
939
|
|
|
|
|
|
|
'# Submitted via: ' . $self->{libname} . ' ' . $self->{libver}, |
940
|
|
|
|
|
|
|
'#', |
941
|
|
|
|
|
|
|
'DISCID=' . $params{Id}, |
942
|
|
|
|
|
|
|
'DTITLE=' . $params{Artist} . ' / ' . $params{DiscTitle}, |
943
|
|
|
|
|
|
|
); |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
# add year and genre |
946
|
1
|
50
|
|
|
|
7
|
if (exists $params{Year}) { |
947
|
0
|
|
|
|
|
0
|
push @message_body, 'DYEAR='.$params{Year}; |
948
|
|
|
|
|
|
|
} |
949
|
1
|
50
|
|
|
|
6
|
if (exists $params{GenreLong}) { |
950
|
0
|
|
|
|
|
0
|
push @message_body, 'DGENRE='.$params{GenreLong}; |
951
|
|
|
|
|
|
|
} |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
# Dump the track titles. |
954
|
1
|
|
|
|
|
3
|
my $number = 0; |
955
|
1
|
|
|
|
|
4
|
foreach my $title (@{$params{TrackTitles}}) { |
|
1
|
|
|
|
|
29
|
|
956
|
1
|
|
|
|
|
2
|
my $copy = $title; |
957
|
1
|
|
|
|
|
7
|
while ($copy ne '') { |
958
|
1
|
|
|
|
|
8
|
push( @message_body, 'TTITLE' . $number . '=' . substr($copy, 0, 69)); |
959
|
1
|
|
|
|
|
7
|
substr($copy, 0, 69) = ''; |
960
|
|
|
|
|
|
|
} |
961
|
1
|
|
|
|
|
4
|
$number++; |
962
|
|
|
|
|
|
|
} |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
# Dump extended information. |
965
|
1
|
|
|
|
|
5
|
push @message_body, 'EXTD='; |
966
|
1
|
|
|
|
|
5
|
push @message_body, map { "EXTT$_="; } (0..--$number); |
|
1
|
|
|
|
|
5
|
|
967
|
1
|
|
|
|
|
4
|
push @message_body, 'PLAYORDER='; |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
# Translate the message body to quoted printable. TODO: How can I |
970
|
|
|
|
|
|
|
# ensure that the quoted printable characters are within ISO-8859-1? |
971
|
|
|
|
|
|
|
# The cddbp submissions daemon will barf if it's not. |
972
|
1
|
|
|
|
|
3
|
foreach my $line (@message_body) { |
973
|
16
|
|
|
|
|
495
|
$line .= "\n"; |
974
|
16
|
|
|
|
|
45
|
$line = MIME::QuotedPrint::encode_qp(encode('utf8', $line)); |
975
|
|
|
|
|
|
|
} |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
# Bundle the headers and body into an Internet mail. |
978
|
1
|
|
|
|
|
37
|
my $mail = new Mail::Internet( |
979
|
|
|
|
|
|
|
undef, |
980
|
|
|
|
|
|
|
Header => $header, |
981
|
|
|
|
|
|
|
Body => \@message_body, |
982
|
|
|
|
|
|
|
); |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
# Try to send it using the "mail" utility. This is commented out: |
985
|
|
|
|
|
|
|
# it strips the MIME headers from the message, invalidating the |
986
|
|
|
|
|
|
|
# submission. |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
#eval { |
989
|
|
|
|
|
|
|
# die unless $mail->send( 'mail' ); |
990
|
|
|
|
|
|
|
#}; |
991
|
|
|
|
|
|
|
#return 1 unless $@; |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
# Try to send it using "sendmail". |
994
|
1
|
|
|
|
|
93
|
eval { |
995
|
1
|
0
|
|
|
|
68
|
die unless $mail->send( 'sendmail' ); |
996
|
|
|
|
|
|
|
}; |
997
|
1
|
50
|
|
|
|
64814
|
return 1 unless $@; |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
# Try to send it by making a direct SMTP connection. |
1000
|
1
|
|
|
|
|
2
|
eval { |
1001
|
1
|
0
|
|
|
|
8
|
die unless $mail->send( smtp => Server => $host ); |
1002
|
|
|
|
|
|
|
}; |
1003
|
1
|
50
|
|
|
|
10627
|
return 1 unless $@; |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
# Augh! Everything failed! |
1006
|
1
|
|
|
|
|
321
|
$self->debug_print( 0, '--- could not find a way to submit a disc' ); |
1007
|
1
|
|
|
|
|
33
|
return; |
1008
|
|
|
|
|
|
|
} |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
1; |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
__END__ |