/*
* Copyright (c) 2008, 2009, 2010 Denis Tulskiy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with this work. If not, see <http://www.gnu.org/licenses/>.
*/
package com.tulskiy.musique.audio;
import com.tulskiy.musique.audio.player.Player;
import com.tulskiy.musique.audio.player.PlayerEvent;
import com.tulskiy.musique.audio.player.PlayerListener;
import com.tulskiy.musique.playlist.Track;
import com.tulskiy.musique.playlist.TrackData;
import com.tulskiy.musique.system.Application;
import com.tulskiy.musique.system.configuration.Configuration;
import com.tulskiy.musique.util.AudioMath;
import com.tulskiy.musique.util.Util;
import de.umass.lastfm.scrobble.ResponseStatus;
import de.umass.lastfm.scrobble.Source;
import de.umass.lastfm.scrobble.SubmissionData;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.logging.Logger;
import static de.umass.lastfm.scrobble.Scrobbler.newScrobbler;
/**
* Author: Denis Tulskiy
* Date: Sep 3, 2010
*/
public class Scrobbler {
private static final String CLIENT_ID = "mqe";
private static final String CLIENT_VERSION = "1.0";
private Logger logger = Logger.getLogger(getClass().getName());
private de.umass.lastfm.scrobble.Scrobbler scrobbler;
private Application app = Application.getInstance();
private Configuration config = app.getConfiguration();
private boolean authorized;
private Player player;
private SubmissionData nowPlaying;
private int nowPlayingLength;
private final Queue<SubmissionData> submitQueue = new LinkedList<SubmissionData>();
public void start() {
Thread submitThread = new Thread(new SubmitSender());
submitThread.start();
authorized = false;
config.addPropertyChangeListener("lastfm.user", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
authorized = false;
}
});
config.addPropertyChangeListener("lastfm.password", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
authorized = false;
}
});
player = app.getPlayer();
player.addListener(new PlayerListener() {
@Override
public void onEvent(PlayerEvent e) {
if (config.getBoolean("lastfm.enabled", false)) {
SubmissionData data = nowPlaying;
switch (e.getEventCode()) {
case FILE_OPENED:
initNowPlaying(player.getTrack());
// no need to break here!
case STOPPED:
if (data != null)
submit(data);
}
}
}
});
}
private void submit(SubmissionData data) {
int time = (int) (player.getPlaybackTime() / 1000);
if (time >= 240 || time >= nowPlayingLength / 2) {
synchronized (submitQueue) {
submitQueue.add(data);
submitQueue.notify();
}
}
}
private void initNowPlaying(Track track) {
TrackData trackData = track.getTrackData();
String artist = trackData.getArtist();
String title = trackData.getTitle();
String album = trackData.getAlbum();
long start = System.currentTimeMillis() / 1000;
int trackNumber = -1;
try {
trackNumber = Integer.valueOf(trackData.getTrackNumber());
} catch (NumberFormatException ignored) {
}
nowPlayingLength = (int) (AudioMath.samplesToMillis(trackData.getTotalSamples(), trackData.getSampleRate()) / 1000);
if (Util.isEmpty(artist) || Util.isEmpty(title) || nowPlayingLength < 30) {
// do not submit this
nowPlaying = null;
} else {
nowPlaying = new SubmissionData(artist, title, album, nowPlayingLength,
trackNumber, Source.USER, start);
}
}
private void auth() {
String user = config.getString("lastfm.user", null);
String password = config.getString("lastfm.password", null);
if (Util.isEmpty(user) || Util.isEmpty(password)) {
authorized = false;
scrobbler = null;
} else {
try {
logger.fine("Authorizing user: " + user);
scrobbler = newScrobbler(CLIENT_ID, CLIENT_VERSION, user);
ResponseStatus status = scrobbler.handshake(password);
authorized = status.ok();
if (!authorized) {
switch (status.getStatus()) {
case ResponseStatus.BADAUTH:
case ResponseStatus.BANNED:
config.setBoolean("lastfm.enabled", false);
}
logger.warning("Scrobbler handshake returned error: " + status.getMessage());
}
} catch (IOException e) {
e.printStackTrace();
authorized = false;
}
}
}
class SubmitSender implements Runnable {
private int MAX_WAIT_TIME = 7200000;
private int MIN_WAIT_TIME = 60000;
private int waitTime = MIN_WAIT_TIME;
@Override
public void run() {
while (true) {
try {
SubmissionData data;
synchronized (submitQueue) {
while (!config.getBoolean("lastfm.enabled", false) ||
submitQueue.isEmpty()) {
submitQueue.wait();
}
data = submitQueue.peek();
}
if (!authorized) {
auth();
if (!authorized) {
Thread.sleep(waitTime);
waitTime = Math.min(waitTime * 2, MAX_WAIT_TIME);
continue;
}
}
logger.fine("Submitting data: " + data.toString());
ResponseStatus status = scrobbler.submit(data);
if (status.ok()) {
waitTime = MIN_WAIT_TIME;
synchronized (submitQueue) {
submitQueue.poll();
}
} else {
switch (status.getStatus()) {
case ResponseStatus.BADSESSION:
case ResponseStatus.FAILED:
authorized = false;
continue;
case ResponseStatus.BANNED:
logger.warning("Last.fm says that we're banned :(");
config.setBoolean("lastfm.enabled", false);
submitQueue.clear();
return;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
authorized = false;
try {
Thread.sleep(waitTime);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}
}
}