/* Copyright (c) 2007 Roland Sch�r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.rolandschaer.ascrblr.scrobbler.impl;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Logger;
import ch.rolandschaer.ascrblr.Session;
import ch.rolandschaer.ascrblr.scrobbler.TrackInfo;
import ch.rolandschaer.ascrblr.util.AuthenticationException;
import ch.rolandschaer.ascrblr.util.MD5Util;
import ch.rolandschaer.ascrblr.util.ResponseUtil;
import ch.rolandschaer.ascrblr.util.ServiceException;
import ch.rolandschaer.ascrblr.util.SessionExpiredException;
public class Audioscrobbler12Impl implements AudioscrobblerImpl {
/** Logger instance */
public static Logger logger = Logger.getLogger(Audioscrobbler12Impl.class.getName());
/** Base URL for requests */
private static final String SERVICE_BASEURL = "http://post.audioscrobbler.com/";
/** Protocol version */
private static final String PROTOCOLVERSION = "1.2";
/** Response type */
public enum ResponseType {
BADAUTH, BADSESSION, BADTIME, BANNED, FAILED, OK
}
/** Authentication token */
private AuthToken authToken;
/** Session token */
private SessionToken sessionToken;
public void handleErrorResponse(String[] response) throws IOException, ServiceException {
String responseType = response[0];
logger.info("Handling error response " + responseType);
if(responseType.equals("")) {
throw new ServiceException("No valid response from server.");
} else if( responseType.startsWith(ResponseType.BADAUTH.toString()) ) {
throw new AuthenticationException("The submitted credentials are not valid.");
} else if (responseType.startsWith(ResponseType.BANNED.toString()) ) {
throw new ServiceException("You were banned from the service because of a protocol violation.");
} else if( responseType.startsWith(ResponseType.BADTIME.toString()) ) {
throw new ServiceException("The timestamp provided seems not valid. Try to correct the system clock.");
} else if( responseType.startsWith(ResponseType.FAILED.toString()) ) {
throw new ServiceException(responseType.substring(7));
} else if (responseType.startsWith(ResponseType.BADSESSION.toString()) ) {
throw new SessionExpiredException("Your session has expired please re-handshake service.");
}
}
public void handleResponse(InputStream responseStream) throws IOException, ServiceException {
String response = ResponseUtil.getResponseString(responseStream);
logger.info("Received response " + response.toString());
String[] lines = response.toString().split("\\n");
if(lines[0].equalsIgnoreCase(ResponseType.OK.toString()) ) {
//Handle handshake response
if(lines.length>1){
sessionToken = new SessionToken(lines[1], lines[3], lines[2]);
}
} else {
handleErrorResponse(lines);
}
}
public String handshake(String clientId, String clientVersion) throws ServiceException {
long timestamp = System.currentTimeMillis() / 1000;
return SERVICE_BASEURL +
"?hs=true" +
"&p=" + PROTOCOLVERSION +
"&c=" + clientId +
"&v=" + clientVersion +
"&u=" + authToken.getUsername() +
"&t=" + timestamp +
"&a=" + authToken.getAuthToken(timestamp);
}
public String notifyNew(TrackInfo trackInfo) throws ServiceException {
return sessionToken.getNotificationUrl() +
"?s=" + sessionToken.getSessionId() +
"&a=" + trackInfo.getArtist() +
"&t=" + trackInfo.getTrack() +
"&b=" + trackInfo.getAlbum() +
"&l=" + trackInfo.getLength() +
"&n=" + trackInfo.getTrackNo() +
"&m=" + trackInfo.getMbTrackId();
}
public String submit(TrackInfo trackInfo) throws ServiceException {
Long startTime = Long.valueOf(trackInfo.getStartTime())/1000;
return sessionToken.getSubmissionUrl() +
"?s=" + sessionToken.getSessionId() +
"&a[0]=" + trackInfo.getArtist() +
"&t[0]=" + trackInfo.getTrack() +
"&i[0]=" + startTime +
"&o[0]=" + trackInfo.getSource() +
"&r[0]=" + trackInfo.getRating() +
"&l[0]=" + trackInfo.getLength() +
"&b[0]=" + trackInfo.getAlbum() +
"&n[0]=" + trackInfo.getTrackNo() +
"&m[0]=" + trackInfo.getMbTrackId();
}
public void setCredentials(String username, String password) throws IOException, ServiceException {
authToken = new AuthToken(username, password);
}
public Session getSession() {
return sessionToken;
}
public String getVersion() {
return PROTOCOLVERSION;
}
/**
* Encapsulates session data provided by the service
*/
public static class SessionToken implements Session {
private String sessionId;
private String submissionUrl;
private String notificationUrl;
public SessionToken(String sessionId, String submissionUrl) {
this.sessionId = sessionId;
this.submissionUrl = submissionUrl;
}
public SessionToken(String sessionId, String submissionUrl, String noficationUrl) {
this.sessionId = sessionId;
this.submissionUrl = submissionUrl;
this.notificationUrl = noficationUrl;
}
public String getSessionId() {
return sessionId;
}
public String getSubmissionUrl() {
return submissionUrl;
}
public String getNotificationUrl() {
return notificationUrl;
}
}
/**
* Encapsulates authentication token which is used during handshake.
*/
public static class AuthToken {
private String username;
private String passwordHash;
public AuthToken(String username, String password) throws ServiceException {
this.username = username;
this.passwordHash = MD5Util.getMD5(password);
}
public String getUsername() { return username; }
/**
* Authentication token used by the service. The token is built as
* follows: <code>md5(md5(password) + time stamp)
* The time stamp has to be the same provided in the handshake request as
* URL parameter t. The + indicates a string concatenation
*
* @param timestamp Unix time stamp
* @return
* @throws ServiceException
*/
public String getAuthToken(long timestamp) throws ServiceException {
return MD5Util.getMD5(passwordHash + timestamp);
}
}
}