Untabifying, widening, nixed wacky client version parsing.

git-svn-id: https://samskivert.googlecode.com/svn/trunk@2728 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
samskivert
2010-02-09 21:22:40 +00:00
parent 053d4c6196
commit ddd0b0b72a
+283 -328
View File
@@ -28,10 +28,8 @@ import java.util.ArrayList;
import java.util.StringTokenizer; import java.util.StringTokenizer;
/** /**
* The CDDB class provides access to the information provided by servers * The CDDB class provides access to the information provided by servers compliant with the
* compliant with the * <a href="http://www.freedb.org/software/old/CDDBPROTO">CDDB protocol</a>.
* <a href="http://www.freedb.org/software/old/CDDBPROTO">CDDB
* protocol</a>.
*/ */
public class CDDB public class CDDB
{ {
@@ -48,149 +46,134 @@ public class CDDB
/** /**
* The client version reported to the CDDB server. * The client version reported to the CDDB server.
*/ */
public static String CLIENT_VERSION; // assigned during static init public static final String CLIENT_VERSION = "1.0";
/** /**
* This class encapsulates the information needed to look up a full * This class encapsulates the information needed to look up a full CDDB record for a
* CDDB record for a particular disc. An array of them are returned in * particular disc. An array of them are returned in response to a CDDB query.
* response to a CDDB query.
*/ */
public static class Entry public static class Entry
{ {
/** The category to which this entry belongs. */ /** The category to which this entry belongs. */
public String category; public String category;
/** The unique identifier for this entry. */ /** The unique identifier for this entry. */
public String cdid; public String cdid;
/** The human readable title of this entry. */ /** The human readable title of this entry. */
public String title; public String title;
/** /**
* Parses values for this entry from the supplied source * Parses values for this entry from the supplied source string. The source string should
* string. The source string should contain an entry description * contain an entry description as formatted by the CDDB server.
* as formatted by the CDDB server. *
* * @exception CDDBException Thrown if the entry is not properly formatted.
* @exception CDDBException Thrown if the entry is not properly */
* formatted. public void parse (String source)
*/ throws CDDBException
public void parse (String source) {
throws CDDBException int sidx1 = source.indexOf(" ");
{ int sidx2 = source.indexOf(" ", sidx1+1);
int sidx1 = source.indexOf(" ");
int sidx2 = source.indexOf(" ", sidx1+1);
if (sidx1 == -1 || sidx2 == -1) { if (sidx1 == -1 || sidx2 == -1) {
throw new CDDBException(499, "Malformed entry '" + throw new CDDBException(499, "Malformed entry '" + source + "'");
source + "'"); }
}
category = source.substring(0, sidx1); category = source.substring(0, sidx1);
cdid = source.substring(sidx1+1, sidx2); cdid = source.substring(sidx1+1, sidx2);
title = source.substring(sidx2+1); title = source.substring(sidx2+1);
} }
} }
/** /**
* Connects this CDDB instance to the CDDB server running on the * Connects this CDDB instance to the CDDB server running on the supplied host using the
* supplied host using the standard port. * standard port.
* *
* <p> <b>Note well:</b> you must close a CDDB connection once you are * <p> <b>Note well:</b> you must close a CDDB connection once you are done with it, otherwise
* done with it, otherwise the socket connection will remain open and * the socket connection will remain open and pointlessly consume machine resources.
* pointlessly consume machine resources.
* *
* @param hostname The host to which to connect. * @param hostname The host to which to connect.
* *
* @exception IOException Thrown if a network error occurs attempting * @exception IOException Thrown if a network error occurs attempting to connect to the host.
* to connect to the host. * @exception CDDBException Thrown if an error occurs after identifying ourselves to the host.
* @exception CDDBException Thrown if an error occurs after
* identifying ourselves to the host.
* *
* @return The message supplied with the succesful connection * @return The message supplied with the succesful connection response.
* response.
* *
* @see #STANDARD_PORT * @see #STANDARD_PORT
* @see #close * @see #close
*/ */
public String connect (String hostname) public String connect (String hostname)
throws IOException, CDDBException throws IOException, CDDBException
{ {
return connect(hostname, STANDARD_PORT); return connect(hostname, STANDARD_PORT);
} }
/** /**
* Connects this CDDB instance to the CDDB server running on the * Connects this CDDB instance to the CDDB server running on the supplied host using the
* supplied host using the specified port. * specified port.
* *
* <p> <b>Note well:</b> you must close a CDDB connection once you are * <p> <b>Note well:</b> you must close a CDDB connection once you are done with it, otherwise
* done with it, otherwise the socket connection will remain open and * the socket connection will remain open and pointlessly consume machine resources.
* pointlessly consume machine resources.
* *
* @param hostname The host to which to connect. * @param hostname The host to which to connect.
* @param port The port number on which to connect to the host. * @param port The port number on which to connect to the host.
* *
* @exception IOException Thrown if a network error occurs attempting * @exception IOException Thrown if a network error occurs attempting to connect to the host.
* to connect to the host. * @exception CDDBException Thrown if an error occurs after identifying ourselves to the host.
* @exception CDDBException Thrown if an error occurs after
* identifying ourselves to the host.
* *
* @return The message supplied with the succesful connection * @return The message supplied with the succesful connection response.
* response.
* *
* @see #close * @see #close
*/ */
public String connect (String hostname, int port) public String connect (String hostname, int port)
throws IOException, CDDBException throws IOException, CDDBException
{ {
// obtain the necessary information we'll need to identify // obtain the necessary information we'll need to identify
// ourselves to the CDDB server // ourselves to the CDDB server
String localhost = InetAddress.getLocalHost().getHostName(); String localhost = InetAddress.getLocalHost().getHostName();
String username = System.getProperty("user.name"); String username = System.getProperty("user.name");
if (username == null) { if (username == null) {
username = "anonymous"; username = "anonymous";
} }
// establish our socket connection and IO streams // establish our socket connection and IO streams
InetAddress addr = InetAddress.getByName(hostname); InetAddress addr = InetAddress.getByName(hostname);
_sock = new Socket(addr, port); _sock = new Socket(addr, port);
_in = new BufferedReader( _in = new BufferedReader(new InputStreamReader(_sock.getInputStream()));
new InputStreamReader(_sock.getInputStream())); _out = new PrintStream(new BufferedOutputStream(_sock.getOutputStream()));
_out = new PrintStream(
new BufferedOutputStream(_sock.getOutputStream()));
// first read (and discard) the banner string // first read (and discard) the banner string
_in.readLine(); _in.readLine();
// send a hello request // send a hello request
StringBuilder req = new StringBuilder("cddb hello "); StringBuilder req = new StringBuilder("cddb hello ");
req.append(username).append(" "); req.append(username).append(" ");
req.append(localhost).append(" "); req.append(localhost).append(" ");
req.append(CLIENT_NAME).append(" "); req.append(CLIENT_NAME).append(" ");
req.append(CLIENT_VERSION); req.append(CLIENT_VERSION);
Response rsp = request(req.toString()); Response rsp = request(req.toString());
// confirm that the response was a successful one // confirm that the response was a successful one
if (CDDBProtocol.codeFamily(rsp.code) != CDDBProtocol.OK && if (CDDBProtocol.codeFamily(rsp.code) != CDDBProtocol.OK &&
rsp.code != 402 /* already shook hands */) { rsp.code != 402 /* already shook hands */) {
throw new CDDBException(rsp.code, rsp.message); throw new CDDBException(rsp.code, rsp.message);
} }
return rsp.message; return rsp.message;
} }
/** /**
* Specifies the number of milliseconds that the client should wait * Specifies the number of milliseconds that the client should wait for a response from the
* for a response from the server before aborting. This must be called * server before aborting. This must be called <em>after</em> a successful call to connect,
* <em>after</em> a successful call to connect, otherwise the timeout * otherwise the timeout will not be set.
* will not be set.
*/ */
public void setTimeout (int timeout) public void setTimeout (int timeout)
throws SocketException throws SocketException
{ {
if (_sock != null) { if (_sock != null) {
_sock.setSoTimeout(timeout); _sock.setSoTimeout(timeout);
} }
} }
/** /**
@@ -198,255 +181,280 @@ public class CDDB
*/ */
public boolean connected () public boolean connected ()
{ {
return _sock != null; return _sock != null;
} }
/** /**
* Closes the connection to the CDDB server previously opened with a * Closes the connection to the CDDB server previously opened with a call to {@link #connect}.
* call to <code>connect()</code>. If the connection was not * If the connection was not previously established, this member function does nothing.
* previously established, this member function does nothing.
*
* @see #connect
*/ */
public void close () public void close ()
throws IOException throws IOException
{ {
if (_sock != null) { if (_sock != null) {
_sock.close(); _sock.close();
// clear out our references // clear out our references
_sock = null; _sock = null;
_in = null; _in = null;
_out = null; _out = null;
} }
} }
/** /**
* Issues a query to the CDDB server using the supplied CD identifying * Issues a query to the CDDB server using the supplied CD identifying information.
* information.
* *
* @param discid The disc identifier (information on how to compute * @param discid The disc identifier (information on how to compute the disc ID is available <a
* the disc ID is available * href="http://www.freedb.org/sections.php?op=viewarticle&artid=6"> here</a>.
* <a href="http://www.freedb.org/sections.php?op=viewarticle&artid=6"> * @param frameOffsets The frame offset of each track. The length of this array is assumed to
* here</a>. * be the number of tracks on the CD and is used in the query.
* @param frameOffsets The frame offset of each track. The length of
* this array is assumed to be the number of tracks on the CD and is
* used in the query.
* @param length The length (in seconds) of the CD. * @param length The length (in seconds) of the CD.
* *
* @return If no entry matches the query, null is returned. Otherwise * @return If no entry matches the query, null is returned. Otherwise one or more entries is
* one or more entries is returned that matched the query parameters. * returned that matched the query parameters.
*/ */
public Entry[] query (String discid, int[] frameOffsets, int length) public Entry[] query (String discid, int[] frameOffsets, int length)
throws IOException, CDDBException throws IOException, CDDBException
{ {
// sanity check // sanity check
if (_sock == null) { if (_sock == null) {
throw new CDDBException(500, "Not connected"); throw new CDDBException(500, "Not connected");
} }
// construct the query parameter // construct the query parameter
StringBuilder req = new StringBuilder("cddb query "); StringBuilder req = new StringBuilder("cddb query ");
req.append(discid).append(" "); req.append(discid).append(" ");
req.append(frameOffsets.length).append(" "); req.append(frameOffsets.length).append(" ");
for (int i = 0; i < frameOffsets.length; i++) { for (int i = 0; i < frameOffsets.length; i++) {
req.append(frameOffsets[i]).append(" "); req.append(frameOffsets[i]).append(" ");
} }
req.append(length); req.append(length);
// make the request // make the request
Response rsp = request(req.toString()); Response rsp = request(req.toString());
Entry[] entries = null; Entry[] entries = null;
// if this is an exact match, parse the entry and return it // if this is an exact match, parse the entry and return it
if (rsp.code == 200 /* exact match */) { if (rsp.code == 200 /* exact match */) {
entries = new Entry[1]; entries = new Entry[1];
entries[0] = new Entry(); entries[0] = new Entry();
entries[0].parse(rsp.message); entries[0].parse(rsp.message);
} else if (rsp.code == 211 /* inexact matches */) { } else if (rsp.code == 211 /* inexact matches */) {
// read the matches from the server // read the matches from the server
ArrayList<Entry> list = new ArrayList<Entry>(); ArrayList<Entry> list = new ArrayList<Entry>();
String input = _in.readLine(); String input = _in.readLine();
System.out.println("...: " + input); System.out.println("...: " + input);
while (!input.equals(CDDBProtocol.TERMINATOR)) { while (!input.equals(CDDBProtocol.TERMINATOR)) {
Entry e = new Entry(); Entry e = new Entry();
e.parse(input); e.parse(input);
list.add(e); list.add(e);
input = _in.readLine(); input = _in.readLine();
System.out.println("...: " + input); System.out.println("...: " + input);
} }
entries = new Entry[list.size()]; entries = new Entry[list.size()];
list.toArray(entries); list.toArray(entries);
} else if (CDDBProtocol.codeFamily(rsp.code) != CDDBProtocol.OK) { } else if (CDDBProtocol.codeFamily(rsp.code) != CDDBProtocol.OK) {
throw new CDDBException(rsp.code, rsp.message); throw new CDDBException(rsp.code, rsp.message);
} }
return entries; return entries;
} }
/** /**
* A detail object contains all of the detailed information about a * A detail object contains all of the detailed information about a particular CD as retrieved
* particular CD as retrieved from the CDDB server. * from the CDDB server.
*/ */
public static class Detail public static class Detail
{ {
/** The unique identifier for this CD. */ /** The unique identifier for this CD. */
public String discid; public String discid;
/** The category to which this CD belongs. */ /** The category to which this CD belongs. */
public String category; public String category;
/** The title of this CD. */ /** The title of this CD. */
public String title; public String title;
/** The track names. */ /** The track names. */
public String[] trackNames; public String[] trackNames;
/** The extended data for the CD. */ /** The extended data for the CD. */
public String extendedData; public String extendedData;
/** The extended data for each track. */ /** The extended data for each track. */
public String[] extendedTrackData; public String[] extendedTrackData;
} }
/** /**
* Requests the detail information for a particular disc in a * Requests the detail information for a particular disc in a particular category from the CDDB
* particular category from the CDDB server. * server.
* *
* @return A detail instance filled with the information for the * @return A detail instance filled with the information for the requested CD.
* requested CD.
*/ */
public Detail read (String category, String discid) public Detail read (String category, String discid)
throws IOException, CDDBException throws IOException, CDDBException
{ {
// sanity check // sanity check
if (_sock == null) { if (_sock == null) {
throw new CDDBException(500, "Not connected"); throw new CDDBException(500, "Not connected");
} }
// construct the query // construct the query
StringBuilder req = new StringBuilder("cddb read "); StringBuilder req = new StringBuilder("cddb read ");
req.append(category).append(" "); req.append(category).append(" ");
req.append(discid); req.append(discid);
// make the request // make the request
Response rsp = request(req.toString()); Response rsp = request(req.toString());
// anything other than an OK response earns an exception // anything other than an OK response earns an exception
if (rsp.code != 210 /* OK, entry follows */) { if (rsp.code != 210 /* OK, entry follows */) {
throw new CDDBException(rsp.code, rsp.message); throw new CDDBException(rsp.code, rsp.message);
} }
Detail detail = new Detail(); Detail detail = new Detail();
// parse the category and discid from the response string // parse the category and discid from the response string
int sidx = rsp.message.indexOf(" "); int sidx = rsp.message.indexOf(" ");
if (sidx == -1) { if (sidx == -1) {
throw new CDDBException(500, "Malformed read response: " + throw new CDDBException(500, "Malformed read response: " +
rsp.message); rsp.message);
} }
detail.category = rsp.message.substring(0, sidx); detail.category = rsp.message.substring(0, sidx);
detail.discid = rsp.message.substring(sidx+1); detail.discid = rsp.message.substring(sidx+1);
ArrayList<String> tnames = new ArrayList<String>(); ArrayList<String> tnames = new ArrayList<String>();
ArrayList<String> texts = new ArrayList<String>(); ArrayList<String> texts = new ArrayList<String>();
// now parse the contents // now parse the contents
String input = _in.readLine(); String input = _in.readLine();
for (int lno = 0; !input.equals(CDDBProtocol.TERMINATOR); lno++) { for (int lno = 0; !input.equals(CDDBProtocol.TERMINATOR); lno++) {
if (input.startsWith("#")) {
// skip comments
if (input.startsWith("#")) { } else if (input.startsWith("DTITLE")) {
// skip comments if (detail.title == null) {
detail.title = contents(input, lno);
} else {
detail.title += contents(input, lno);
}
} else if (input.startsWith("DTITLE")) { } else if (input.startsWith("EXTD")) {
if (detail.title == null) { if (detail.extendedData == null) {
detail.title = contents(input, lno); detail.extendedData = contents(input, lno);
} else { } else {
detail.title += contents(input, lno); detail.extendedData += contents(input, lno);
} }
} else if (input.startsWith("EXTD")) { } else if (input.startsWith("TTITLE")) {
if (detail.extendedData == null) { append(tnames, index(input, "TTITLE", lno), contents(input, lno));
detail.extendedData = contents(input, lno);
} else {
detail.extendedData += contents(input, lno);
}
} else if (input.startsWith("TTITLE")) { } else if (input.startsWith("EXTT")) {
append(tnames, index(input, "TTITLE", lno), append(texts, index(input, "EXTT", lno), contents(input, lno));
contents(input, lno)); }
} else if (input.startsWith("EXTT")) { // read in the next line of input
append(texts, index(input, "EXTT", lno), input = _in.readLine();
contents(input, lno)); }
}
// read in the next line of input // convert the lists into arrays
input = _in.readLine(); detail.trackNames = new String[tnames.size()];
} tnames.toArray(detail.trackNames);
detail.extendedTrackData = new String[texts.size()];
texts.toArray(detail.extendedTrackData);
// convert the lists into arrays return detail;
detail.trackNames = new String[tnames.size()];
tnames.toArray(detail.trackNames);
detail.extendedTrackData = new String[texts.size()];
texts.toArray(detail.extendedTrackData);
return detail;
} }
/** /**
* Extracts the track index of the supplied line (the number * Extracts the track index of the supplied line (the number immediately following the supplied
* immediately following the supplied prefix and preceding the equals * prefix and preceding the equals sign) or throws a CDDBException if the line contains no
* sign) or throws a CDDBException if the line contains no equals sign * equals sign or track index.
* or track index.
*/ */
protected final int index (String source, String prefix, int lineno) protected final int index (String source, String prefix, int lineno)
throws CDDBException throws CDDBException
{ {
int eidx = source.indexOf("=", prefix.length()); int eidx = source.indexOf("=", prefix.length());
if (eidx == -1) { if (eidx == -1) {
throw new CDDBException(500, "Malformed line '" + source + throw new CDDBException(500, "Malformed line '" + source + "' number " + lineno);
"' number " + lineno); }
}
try { try {
return Integer.parseInt(source.substring(prefix.length(), eidx)); return Integer.parseInt(source.substring(prefix.length(), eidx));
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
throw new CDDBException(500, "Malformed line '" + source + throw new CDDBException(500, "Malformed line '" + source + "' number " + lineno);
"' number " + lineno); }
}
} }
/** /**
* Extracts the contents of the supplied line (everything after the * Extracts the contents of the supplied line (everything after the equals sign) or throws a
* equals sign) or throws a CDDBException if the line contains no * CDDBException if the line contains no equals sign.
* equals sign.
*/ */
protected final String contents (String source, int lineno) protected final String contents (String source, int lineno)
throws CDDBException throws CDDBException
{ {
int eidx = source.indexOf("="); int eidx = source.indexOf("=");
if (eidx == -1) { if (eidx == -1) {
throw new CDDBException(500, "Malformed line '" + source + throw new CDDBException(500, "Malformed line '" + source + "' number " + lineno);
"' number " + lineno); }
} return source.substring(eidx+1);
return source.substring(eidx+1);
} }
/** /**
* Appends the supplied string to the contents of the list at the * Appends the supplied string to the contents of the list at the supplied index. If the list
* supplied index. If the list has no contents at the supplied * has no contents at the supplied index, the supplied value becomes the contents at that
* index, the supplied value becomes the contents at that index. * index.
*/ */
protected final void append ( protected final void append (
ArrayList<String> list, int index, String value) ArrayList<String> list, int index, String value)
{ {
// expand the list as necessary // expand the list as necessary
while (list.size() <= index) { while (list.size() <= index) {
list.add(""); list.add("");
} }
list.set(index, list.get(index) + value); list.set(index, list.get(index) + value);
}
/**
* Issues a request to the CDDB server and parses the response.
*/
protected Response request (String req)
throws IOException
{
System.err.println("REQ:" + req);
// send the request to the server
_out.println(req);
_out.flush();
// now read the response
String rspstr = _in.readLine();
// if they closed the connection on us, we should deal
if (rspstr == null) {
throw new EOFException();
}
System.err.println("RSP:" + rspstr);
// parse the response
Response rsp = new Response();
String codestr = rspstr;
// assume the response is just a code unless we see a space
int sidx = rspstr.indexOf(" ");
if (sidx != -1) {
codestr = rspstr.substring(0, sidx);
rsp.message = rspstr.substring(sidx+1);
}
try {
rsp.code = Integer.parseInt(codestr);
} catch (NumberFormatException nfe) {
rsp.code = 599;
rsp.message = "Unparseable response";
}
return rsp;
} }
/** /**
@@ -454,61 +462,8 @@ public class CDDB
*/ */
protected class Response protected class Response
{ {
public int code; public int code;
public String message; public String message;
}
/**
* Issues a request to the CDDB server and parses the response.
*/
protected Response request (String req)
throws IOException
{
System.err.println("REQ:" + req);
// send the request to the server
_out.println(req);
_out.flush();
// now read the response
String rspstr = _in.readLine();
// if they closed the connection on us, we should deal
if (rspstr == null) {
throw new EOFException();
}
System.err.println("RSP:" + rspstr);
// parse the response
Response rsp = new Response();
String codestr = rspstr;
// assume the response is just a code unless we see a space
int sidx = rspstr.indexOf(" ");
if (sidx != -1) {
codestr = rspstr.substring(0, sidx);
rsp.message = rspstr.substring(sidx+1);
}
try {
rsp.code = Integer.parseInt(codestr);
} catch (NumberFormatException nfe) {
rsp.code = 599;
rsp.message = "Unparseable response";
}
return rsp;
}
/**
* The client version number is extracted from the version control
* revision of this file from a string that is managed by the version
* control system.
*/
static
{
StringTokenizer tok = new StringTokenizer("$Revision: 1.8 $");
tok.nextToken();
CLIENT_VERSION = tok.nextToken();
} }
protected Socket _sock; protected Socket _sock;