/***************************************************************************
* Copyright (C) 2008 by Ilya Kotov *
* forkotov02@hotmail.ru *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <QMenu>
#include <QHttp>
#include <QByteArray>
#include <QCryptographicHash>
#include <QUrl>
#include <QTime>
#include <QSettings>
#include <QDir>
#include "scrobbler.h"
#define SCROBBLER_HS_URL "post.audioscrobbler.com"
#define PROTOCOL_VER "1.2"
#define CLIENT_ID "qmm"
#define CLIENT_VER "0.1"
Scrobbler::Scrobbler(QObject *parent)
: General(parent)
{
m_http = new QHttp(this);
m_http->setHost(SCROBBLER_HS_URL, 80);
m_state = General::Stopped;
QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat);
settings.beginGroup("Scrobbler");
m_login = settings.value("login").toString();
m_passw = settings.value("password").toString();
settings.endGroup();
//use global proxy settings
if (settings.value ("Proxy/use_proxy", FALSE).toBool())
{
if (settings.value ("Proxy/authentication", FALSE).toBool())
m_http->setProxy(settings.value("Proxy/host").toString(),
settings.value("Proxy/port").toInt(),
settings.value("Proxy/user").toString(),
settings.value("Proxy/passw").toString());
else
m_http->setProxy(settings.value("Proxy/host").toString(),
settings.value("Proxy/port").toInt());
}
m_disabled = m_login.isEmpty() || m_passw.isEmpty();
m_passw = QString(QCryptographicHash::hash(m_passw.toAscii(), QCryptographicHash::Md5).toHex());
connect(m_http, SIGNAL(requestFinished (int, bool)), SLOT(processResponse(int, bool)));
connect(m_http, SIGNAL(readyRead (const QHttpResponseHeader&)),
SLOT(readResponse(const QHttpResponseHeader&)));
m_time = new QTime();
m_submitedSongs = 0;
m_handshakeid = 0;
m_submitid = 0;
if (!m_disabled)
handshake();
}
Scrobbler::~Scrobbler()
{
delete m_time;
}
void Scrobbler::setState(const uint &state)
{
m_state = state;
if (m_disabled)
return;
switch ((uint) state)
{
case General::Playing:
{
m_start_ts = time(NULL);
m_time->restart();
if (!isReady() && m_handshakeid == 0)
handshake();
break;
}
case General::Paused:
{
break;
}
case General::Stopped:
{
if (!m_song.isEmpty()
&& ((m_time->elapsed ()/1000 > 240)
|| (m_time->elapsed ()/1000 > int(m_song.length()/2)))
&& (m_time->elapsed ()/1000 > 60))
{
m_songCache << m_song;
m_timeStamps << m_start_ts;
}
m_song.clear();
if (m_songCache.isEmpty())
break;
if (m_http->error() != QHttp::NoError)
m_http->clearPendingRequests ();
if (isReady() && m_submitid == 0)
{
submit();
}
break;
}
}
}
void Scrobbler::setSongInfo(const SongInfo &song)
{
if (m_state == General::Playing
&& !song.title().isEmpty() //skip empty tags
&& !song.artist().isEmpty()
&& !song.isStream() //skip stream
&& !song.artist().contains("&") //skip tags with special symbols
&& !song.title().contains("&")
&& !song.album().contains("&")
&& !song.artist().contains("=")
&& !song.title().contains("=")
&& !song.album().contains("="))
{
m_song = song;
}
}
void Scrobbler::processResponse(int id, bool error)
{
if (error)
{
qWarning("Scrobbler: %s", qPrintable(m_http->errorString ()));
//TODO hard failure handling
if (id == m_submitid)
m_submitid = 0;
else if (id == m_handshakeid)
m_handshakeid = 0;
return;
}
QString str(m_array);
QStringList strlist = str.split("\n");
if (id == m_handshakeid)
{
m_handshakeid = 0;
if (!strlist[0].contains("OK") || strlist.size() < 4)
{
qWarning("Scrobbler: handshake phase error: %s", qPrintable(strlist[0]));
//TODO badtime handling
return;
}
if (strlist.size() > 3) //process handshake response
{
qDebug("Scrobbler: reading handshake response");
qDebug("Scrobbler: Session ID: %s",qPrintable(strlist[1]));
qDebug("Scrobbler: Now-Playing URL: %s",qPrintable(strlist[2]));
qDebug("Scrobbler: Submission URL: %s",qPrintable(strlist[3]));
m_submitUrl = strlist[3];
m_session = strlist[1];
return;
}
}
else if (id == m_submitid)
{
m_submitid = 0;
if (!strlist[0].contains("OK"))
{
qWarning("Scrobbler: submit error: %s", qPrintable(strlist[0]));
//TODO badsession handling
return;
}
qWarning("Scrobbler: submited %d song(s)", m_submitedSongs);
while (m_submitedSongs)
{
m_submitedSongs--;
m_timeStamps.removeFirst ();
m_songCache.removeFirst ();
}
}
m_array.clear();
}
void Scrobbler::readResponse(const QHttpResponseHeader &header)
{
if (header.statusCode () != 200)
{
qWarning("Scrobbler: error: %s",qPrintable(header.reasonPhrase ()));
//TODO Failure Handling
return;
}
m_array = m_http->readAll();
}
void Scrobbler::handshake()
{
qDebug("Scrobbler::handshake()");
time_t ts = time(NULL);
qDebug("Scrobbler: current time stamp %ld",ts);
QString auth_tmp = QString("%1%2").arg(m_passw).arg(ts);
QByteArray auth = QCryptographicHash::hash(auth_tmp.toAscii (), QCryptographicHash::Md5);
auth = auth.toHex();
QString url = QString("%1?hs=true&p=%2&c=%3&v=%4&u=%5&t=%6&a=%7")
.arg("/")
.arg(PROTOCOL_VER)
.arg(CLIENT_ID)
.arg(CLIENT_VER)
.arg(m_login)
.arg(ts)
.arg(QString(auth));
qDebug("Scrobbler: request url: %s",qPrintable(url));
m_http->setHost(SCROBBLER_HS_URL, 80);
m_handshakeid = m_http->get(url);
}
void Scrobbler::submit()
{
qDebug("Scrobbler::submit()");
if (m_songCache.isEmpty())
return;
m_submitedSongs = m_songCache.size();
QString body = QString("s=%1").arg(m_session);
for (int i = 0; i < qMin(m_songCache.size(), 25); ++i)
{
SongInfo info = m_songCache[i];
body += QString("&a[%9]=%1&t[%9]=%2&i[%9]=%3&o[%9]=%4&r[%9]=%5&l[%9]=%6&b[%9]=%7&n[%9]=%8&m[%9]=")
.arg(info.artist())
.arg(info.title())
.arg( m_timeStamps[i])
.arg("P")
.arg("")
.arg(info.length())
.arg(info.album())
.arg(info.track())
.arg(i);
}
QUrl url(m_submitUrl);
m_http->setHost(url.host(), url.port());
QHttpRequestHeader header("POST", url.path());
header.setContentType("application/x-www-form-urlencoded");
header.setValue("User-Agent","iScrobbler/1.5.1qmmp-plugins/0.2");
header.setValue("Host",url.host());
header.setValue("Accept", "*/*");
header.setContentLength(QUrl::toPercentEncoding(body,":/[]&=").size());
qDebug("Scrobbler: submit request header");
qDebug(qPrintable(header.toString()));
qDebug("*****************************");
m_submitid = m_http->request(header, QUrl::toPercentEncoding(body,":/[]&="));
}
bool Scrobbler::isReady()
{
return !m_submitUrl.isEmpty() && !m_session.isEmpty();
}