You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
465 lines
11 KiB
C++
465 lines
11 KiB
C++
#include "csubsonic.h"
|
|
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkRequest>
|
|
#include <QNetworkReply>
|
|
#include <QEventLoop>
|
|
#include <QUrl>
|
|
|
|
#include <QDomDocument>
|
|
|
|
#include <QCryptographicHash>
|
|
|
|
#include <QDateTime>
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
cSubsonic::cSubsonic() :
|
|
m_server(""),
|
|
m_user(""),
|
|
m_password(""),
|
|
m_version("1.15.0")
|
|
{
|
|
verify();
|
|
}
|
|
|
|
cSubsonic::cSubsonic(const QString& server, const QString& user, const QString& password) :
|
|
m_server(server),
|
|
m_user(user),
|
|
m_password(password),
|
|
m_version("1.15.0")
|
|
{
|
|
verify();
|
|
}
|
|
|
|
void cSubsonic::setServer(const QString& server)
|
|
{
|
|
m_server = server;
|
|
}
|
|
|
|
QString cSubsonic::server()
|
|
{
|
|
return(m_server);
|
|
}
|
|
|
|
void cSubsonic::setUser(const QString& user)
|
|
{
|
|
m_user = user;
|
|
}
|
|
|
|
QString cSubsonic::user()
|
|
{
|
|
return(m_user);
|
|
}
|
|
|
|
void cSubsonic::setPassword(const QString& password)
|
|
{
|
|
m_password = password;
|
|
}
|
|
|
|
bool cSubsonic::verify()
|
|
{
|
|
QDomElement element;
|
|
|
|
if(!sendRequest("ping.view", element))
|
|
{
|
|
m_version = element.attribute("version");
|
|
|
|
if(!sendRequest("ping.view", element))
|
|
return(false);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool cSubsonic::getLicense(bool& valid, QString& email, QDateTime& licenseExpires)
|
|
{
|
|
QDomElement element;
|
|
|
|
if(!sendRequest("getLicense", element, "license"))
|
|
return(false);
|
|
|
|
valid = element.attribute("valid") == "true";
|
|
email = element.attribute("email");
|
|
licenseExpires = QDateTime::fromString(element.attribute("licenseExpires"), "yyyy-MM-ddThh:mm:ss.zzzZ");
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool cSubsonic::getMusicFolders(QMap<qint32, QString>& folders)
|
|
{
|
|
QDomElement element;
|
|
|
|
if(!sendRequest("getMusicFolders", element, "musicFolders"))
|
|
return(false);
|
|
|
|
QDomNode child = element.firstChild();
|
|
QDomElement childElement;
|
|
qint32 id;
|
|
QString name;
|
|
|
|
while(!child.isNull())
|
|
{
|
|
childElement = child.toElement();
|
|
|
|
if(!childElement.tagName().compare("musicFolder"))
|
|
{
|
|
id = childElement.attribute("id").toInt();
|
|
name = childElement.attribute("name");
|
|
folders.insert(id, name);
|
|
}
|
|
child = child.nextSibling();
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool cSubsonic::getIndexes(QMap<qint32, QString>& shortcuts, QStringList& indexes, QMap<qint32, INDEX> &artists, const qint32 id, const QDate& modifiedSince)
|
|
{
|
|
QMap<QString, QString> parameter;
|
|
QDomElement element;
|
|
|
|
if(id != -1)
|
|
parameter.insert("musicFolderId", QString::number(id));
|
|
|
|
if(modifiedSince.isValid())
|
|
parameter.insert("ifModifiedSince", QString::number(QDateTime(modifiedSince, QTime(0, 0)).toMSecsSinceEpoch()));
|
|
|
|
if(!sendRequest("getIndexes", element, "indexes", parameter))
|
|
return(false);
|
|
|
|
QDomNode child = element.firstChild();
|
|
QDomElement childElement;
|
|
QDomElement artistElement;
|
|
|
|
qint32 shortcutID;
|
|
QString shortcutName;
|
|
QString indexName;
|
|
qint32 artistID;
|
|
QString artistName;
|
|
|
|
while(!child.isNull())
|
|
{
|
|
childElement = child.toElement();
|
|
|
|
if(!childElement.tagName().compare("shortcut"))
|
|
{
|
|
shortcutID = childElement.attribute("id").toInt();
|
|
shortcutName = childElement.attribute("name");
|
|
shortcuts.insert(shortcutID, shortcutName);
|
|
}
|
|
else if(!childElement.tagName().compare("index"))
|
|
{
|
|
indexName = childElement.attribute("name");
|
|
indexes.append(indexName);
|
|
|
|
QDomNode artistChild = childElement.firstChild();
|
|
|
|
while(!artistChild.isNull())
|
|
{
|
|
artistElement = artistChild.toElement();
|
|
|
|
if(!artistElement.tagName().compare("artist"))
|
|
{
|
|
artistID = artistElement.attribute("id").toInt();
|
|
artistName = artistElement.attribute("name");
|
|
|
|
INDEX artist = { indexName, artistName, -1 };
|
|
artists.insert(artistID, artist);
|
|
}
|
|
|
|
artistChild = artistChild.nextSibling();
|
|
}
|
|
}
|
|
|
|
child = child.nextSibling();
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool cSubsonic::getMusicDirectory(const qint32 id, QString &name, qint32 &playCount, QMap<qint32, ALBUM>& albums)
|
|
{
|
|
QMap<QString, QString> parameter;
|
|
QDomElement element;
|
|
|
|
parameter.insert("id", QString::number(id));
|
|
|
|
if(!sendRequest("getMusicDirectory", element, "directory", parameter))
|
|
return(false);
|
|
|
|
name = element.attribute("name");
|
|
playCount = element.attribute("playCount").toInt();
|
|
|
|
QDomNode child = element.firstChild();
|
|
QDomElement childElement;
|
|
QDomElement artistElement;
|
|
|
|
while(!child.isNull())
|
|
{
|
|
childElement = child.toElement();
|
|
|
|
if(!childElement.tagName().compare("child"))
|
|
{
|
|
if(!childElement.hasAttribute("contentType"))
|
|
{
|
|
ALBUM album;
|
|
qint32 albumID;
|
|
|
|
albumID = childElement.attribute("id").toInt();
|
|
album.parent = childElement.attribute("parent").toInt();
|
|
album.title = childElement.attribute("title");
|
|
album.album = childElement.attribute("album");
|
|
album.artist = childElement.attribute("artist");
|
|
album.year = childElement.attribute("year").toInt();
|
|
album.genre = childElement.attribute("genre");
|
|
album.coverArt = childElement.attribute("coverArt").toInt();
|
|
album.playCount = childElement.attribute("playCount").toInt();
|
|
album.created = QDateTime::fromString(childElement.attribute("created"), "yyyy-MM-ddThh:mm:ss.zzzZ");
|
|
|
|
albums.insert(albumID, album);
|
|
}
|
|
}
|
|
|
|
child = child.nextSibling();
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool cSubsonic::getMusicDirectory(const qint32 id, qint32& parent, QString& name, qint32& playCount, QMap<qint32, TRACK>& tracks)
|
|
{
|
|
QMap<QString, QString> parameter;
|
|
QDomElement element;
|
|
|
|
parameter.insert("id", QString::number(id));
|
|
|
|
if(!sendRequest("getMusicDirectory", element, "directory", parameter))
|
|
return(false);
|
|
|
|
parent = element.attribute("parent").toInt();
|
|
name = element.attribute("name");
|
|
playCount = element.attribute("playCount").toInt();
|
|
|
|
QDomNode child = element.firstChild();
|
|
QDomElement childElement;
|
|
QDomElement artistElement;
|
|
|
|
while(!child.isNull())
|
|
{
|
|
childElement = child.toElement();
|
|
|
|
if(!childElement.tagName().compare("child"))
|
|
{
|
|
if(childElement.hasAttribute("contentType"))
|
|
{
|
|
TRACK track;
|
|
qint32 trackID;
|
|
|
|
trackID = childElement.attribute("id").toInt();
|
|
track.parent = childElement.attribute("parent").toInt();
|
|
track.title = childElement.attribute("title");
|
|
track.album = childElement.attribute("album");
|
|
track.artist = childElement.attribute("artist");
|
|
track.track = childElement.attribute("track");
|
|
track.year = childElement.attribute("year").toInt();
|
|
track.genre = childElement.attribute("genre");
|
|
track.coverArt = childElement.attribute("coverArt").toInt();
|
|
track.size = childElement.attribute("size").toInt();
|
|
track.contentType = childElement.attribute("contentType");
|
|
track.suffix = childElement.attribute("suffix");
|
|
track.duration = childElement.attribute("duration").toInt();
|
|
track.bitRate = childElement.attribute("duration").toInt();
|
|
track.path = childElement.attribute("path");
|
|
track.isVideo = childElement.attribute("isVideo") == "true";
|
|
track.playCount = childElement.attribute("playCount").toInt();
|
|
track.discNumber = childElement.attribute("discNumber");
|
|
track.created = QDateTime::fromString(childElement.attribute("created"), "yyyy-MM-ddThh:mm:ss.zzzZ");
|
|
track.albumID = childElement.attribute("albumID").toInt();
|
|
track.artistID = childElement.attribute("artistID").toInt();
|
|
track.type = childElement.attribute("type");
|
|
|
|
tracks.insert(trackID, track);
|
|
}
|
|
}
|
|
|
|
child = child.nextSibling();
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool cSubsonic::getGenres(QMap<QString, GENRE>& genres)
|
|
{
|
|
QDomElement element;
|
|
|
|
if(!sendRequest("getGenres", element, "genres"))
|
|
return(false);
|
|
|
|
QDomNode child = element.firstChild();
|
|
QDomElement childElement;
|
|
|
|
while(!child.isNull())
|
|
{
|
|
childElement = child.toElement();
|
|
|
|
if(!childElement.tagName().compare("genre"))
|
|
{
|
|
GENRE genre;
|
|
QString name;
|
|
|
|
genre.songCount = childElement.attribute("songCount").toInt();
|
|
genre.albumCount = childElement.attribute("albumCount").toInt();
|
|
name = childElement.text();
|
|
|
|
genres.insert(name, genre);
|
|
}
|
|
|
|
child = child.nextSibling();
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool cSubsonic::getArtists(QStringList& indexes, QMap<qint32, ARTIST>& artists, const qint32 id)
|
|
{
|
|
QMap<QString, QString> parameter;
|
|
QDomElement element;
|
|
|
|
if(id != -1)
|
|
parameter.insert("musicFolderId", QString::number(id));
|
|
|
|
if(!sendRequest("getArtists", element, "artists", parameter))
|
|
return(false);
|
|
|
|
QDomNode child = element.firstChild();
|
|
QDomElement childElement;
|
|
QDomElement artistElement;
|
|
|
|
QString index;
|
|
|
|
while(!child.isNull())
|
|
{
|
|
childElement = child.toElement();
|
|
|
|
if(!childElement.tagName().compare("index"))
|
|
{
|
|
index = childElement.attribute("name");
|
|
indexes.append(index);
|
|
|
|
QDomNode artistChild = childElement.firstChild();
|
|
|
|
while(!artistChild.isNull())
|
|
{
|
|
artistElement = artistChild.toElement();
|
|
|
|
if(!artistElement.tagName().compare("artist"))
|
|
{
|
|
ARTIST artist;
|
|
|
|
artist.index = index;
|
|
artist.name = artistElement.attribute("name");
|
|
artist.coverArt = artistElement.attribute("coverArt");
|
|
artist.albumCount = artistElement.attribute("albumCount").toInt();
|
|
|
|
artists.insert(artistElement.attribute("id").toInt(), artist);
|
|
}
|
|
|
|
artistChild = artistChild.nextSibling();
|
|
}
|
|
}
|
|
|
|
child = child.nextSibling();
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
QString cSubsonic::randomString()
|
|
{
|
|
const QString possibleCharacters("abcdef0123456789");
|
|
const int randomStringLength = 6;
|
|
|
|
QString randomString;
|
|
|
|
for(int i = 0; i < randomStringLength; ++i)
|
|
{
|
|
int index = rand() % possibleCharacters.length();
|
|
QChar nextChar = possibleCharacters.at(index);
|
|
randomString.append(nextChar);
|
|
}
|
|
return(randomString);
|
|
}
|
|
|
|
bool cSubsonic::sendRequest(const QString& request, QDomElement& element, const QString& verify, const QMap<QString, QString>& parameter)
|
|
{
|
|
QNetworkAccessManager networkManager;
|
|
QString salt = randomString();
|
|
QString token = QCryptographicHash::hash(QString(m_password+salt).toUtf8(), QCryptographicHash::Md5).toHex();
|
|
QString strRequest = QString("%1/rest/%2?u=%3&t=%4&s=%5&v=%6&c=subJuke").arg(m_server, request, m_user, token, salt, m_version);
|
|
|
|
if(parameter.count())
|
|
{
|
|
QString param;
|
|
|
|
QMapIterator<QString, QString> i(parameter);
|
|
while(i.hasNext())
|
|
{
|
|
i.next();
|
|
param.append(QString("&%1=%2").arg(i.key(), i.value()));
|
|
}
|
|
|
|
strRequest.append(param);
|
|
}
|
|
|
|
// qDebug() << strRequest;
|
|
|
|
QUrl url(strRequest);
|
|
QNetworkRequest networkRequest(url);
|
|
QNetworkReply* reply = networkManager.get(networkRequest);
|
|
QEventLoop loop;
|
|
|
|
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
|
loop.exec();
|
|
|
|
if(reply->error() == QNetworkReply::NoError)
|
|
{
|
|
QDomDocument doc;
|
|
QString errorStr;
|
|
int errorLine;
|
|
int errorColumn;
|
|
if(!doc.setContent(reply->readAll(), false, &errorStr, &errorLine, &errorColumn))
|
|
return(false);
|
|
|
|
// qDebug() << doc.toString();
|
|
element = doc.documentElement();
|
|
if(element.tagName().compare("subsonic-response"))
|
|
return(false);
|
|
|
|
QString status = element.attribute("status");
|
|
if(status == "failed")
|
|
return(false);
|
|
|
|
if(verify.isEmpty())
|
|
{
|
|
if(element.isNull())
|
|
return(false);
|
|
return(true);
|
|
}
|
|
|
|
element = element.firstChild().toElement();
|
|
if(element.isNull())
|
|
return(false);
|
|
|
|
if(element.tagName().compare(verify))
|
|
return(false);
|
|
return(true);
|
|
}
|
|
else
|
|
{
|
|
// qDebug() << reply->errorString();
|
|
return(false);
|
|
}
|
|
}
|