File Coverage

blib/lib/CDDB.pm
Criterion Covered Total %
statement 339 394 86.0
branch 111 210 52.8
condition 8 17 47.0
subroutine 28 29 96.5
pod 13 22 59.0
total 499 672 74.2


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__