Files
cantata/online/onlineservice.cpp
2013-08-12 18:33:50 +00:00

398 lines
10 KiB
C++

/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* 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; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "onlineservice.h"
#include "onlineservicesmodel.h"
#include "musiclibrarymodel.h"
#include "utils.h"
#include "networkaccessmanager.h"
#include "mpdparseutils.h"
#include "covers.h"
#include "qtiocompressor/qtiocompressor.h"
#include "thread.h"
#include "settings.h"
#include <QFile>
#include <QXmlStreamReader>
static QString cacheFileName(OnlineService *srv, bool create=false)
{
return Utils::cacheDir(srv->id().toLower(), create)+QLatin1String("library")+MusicLibraryModel::constLibraryCompressedExt;
}
OnlineMusicLoader::OnlineMusicLoader(const QUrl &src)
: source(src)
, library(0)
, network(0)
, downloadJob(0)
, stopRequested(false)
, lastProg(-1)
{
connect(this, SIGNAL(load()), this, SLOT(doLoad()));
thread=new Thread(metaObject()->className());
moveToThread(thread);
thread->start();
}
void OnlineMusicLoader::start()
{
emit load();
}
void OnlineMusicLoader::doLoad()
{
if (library) {
delete library;
}
library = new MusicLibraryItemRoot;
if (!readFromCache()) {
emit status(i18n("Dowloading"), 0);
if (!network) {
network=new NetworkAccessManager(this);
}
downloadJob=network->get(source);
connect(downloadJob, SIGNAL(finished()), SLOT(downloadFinished()));
connect(downloadJob, SIGNAL(downloadProgress(qint64,qint64)), SLOT(downloadProgress(qint64,qint64)));
}
}
void OnlineMusicLoader::stop()
{
stopRequested=true;
thread->stop();
}
MusicLibraryItemRoot * OnlineMusicLoader::takeLibrary()
{
MusicLibraryItemRoot *lib=library;
library=0;
return lib;
}
bool OnlineMusicLoader::readFromCache()
{
if (!cache.isEmpty() && QFile::exists(cache)) {
emit status(i18n("Reading cache"), 0);
if (library->fromXML(cache, QDateTime(), QString(), this)) {
if (!stopRequested) {
fixLibrary();
emit status(i18n("Updating display"), -100);
emit loaded();
}
return true;
}
}
return false;
}
void OnlineMusicLoader::fixLibrary()
{
emit status(i18n("Grouping tracks"), -100);
if (MPDParseUtils::groupSingle()) {
library->groupSingleTracks();
}
if (MPDParseUtils::groupMultiple()) {
library->groupMultipleArtists();
}
}
void OnlineMusicLoader::downloadFinished()
{
QNetworkReply *reply=qobject_cast<QNetworkReply *>(sender());
if (!reply) {
return;
}
reply->deleteLater();
if (stopRequested) {
return;
}
if(QNetworkReply::NoError==reply->error()) {
emit status(i18n("Parsing response"), -100);
QtIOCompressor comp(reply);
comp.setStreamFormat(QtIOCompressor::GzipFormat);
if (comp.open(QIODevice::ReadOnly)) {
QXmlStreamReader reader;
reader.setDevice(&comp);
if (parse(reader)) {
fixLibrary();
emit status(i18n("Saving cache"), 0);
library->toXML(cache, QDateTime(), this);
emit loaded();
} else {
emit error(i18n("Failed to parse"));
}
} else {
emit error(i18n("Failed to parse"));
}
} else {
emit error(i18n("Failed to download"));
}
}
void OnlineMusicLoader::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
progressReport(i18n("Dowloading"), 0==bytesTotal ? 0 : ((bytesReceived*100)/bytesTotal));
}
void OnlineMusicLoader::readProgress(double pc)
{
progressReport(i18n("Reading cache"), (int)pc);
}
void OnlineMusicLoader::writeProgress(double pc)
{
progressReport(i18n("Saving cache"), (int)pc);
}
void OnlineMusicLoader::progressReport(const QString &str, int prog)
{
if (stopRequested) {
return;
}
if (prog!=lastProg) {
lastProg=prog;
emit status(str, lastProg);
}
}
static const QString constUrlGuard=QLatin1String("#{SONG_DETAILS}");
static const QString constDeliminator=QLatin1String("<@>");
Song OnlineService::encode(const Song &song)
{
Song encoded=song;
encoded.file=song.file+constUrlGuard+
encoded.artist.replace(constDeliminator, " ")+constDeliminator+
encoded.title.replace(constDeliminator, " ")+constDeliminator+
encoded.genre.replace(constDeliminator, " ")+constDeliminator+
QString::number(song.time)+constDeliminator+
QString::number(song.year)+constDeliminator+
QString::number(song.track);
return encoded;
}
bool OnlineService::decode(Song &song)
{
if (!song.file.startsWith(QLatin1String("http://"))) {
return false;
}
int pos=song.file.indexOf(constUrlGuard);
if (pos>0) {
QStringList parts=song.file.mid(pos+constUrlGuard.length()+1).split(constDeliminator);
if (parts.length()>=6) {
song.artist=parts.at(0);
song.title=parts.at(1);
song.genre=parts.at(2);
song.time=parts.at(3).toUInt();
song.year=parts.at(4).toUInt();
song.track=parts.at(5).toUInt();
song.fillEmptyFields();
song.file=song.file.left(pos);
return true;
}
}
return false;
}
OnlineService::OnlineService(MusicModel *m, const QString &name)
: MusicLibraryItemRoot(name, false)
, configured(false)
, update(0)
, lProgress(0.0)
, loaded(false)
, loader(0)
{
setUseArtistImages(Settings::self()->libraryArtistImage());
setUseAlbumImages(true);
m_model=m;
icn.addFile(":"+name.toLower());
}
void OnlineService::destroy()
{
stopLoader();
deleteLater();
}
void OnlineService::stopLoader()
{
// Load music in a separate thread...
if (loader) {
disconnect(loader, SIGNAL(error(QString)), this, SLOT(loaderError(QString)));
disconnect(loader, SIGNAL(status(QString,int)), this, SLOT(loaderstatus(QString,int)));
disconnect(loader, SIGNAL(loaded()), this, SLOT(loaderFinished()));
loader->stop();
loader->deleteLater();
loader=0;
}
}
void OnlineService::reload(const bool fromCache)
{
if (loader) {
return;
}
clear();
QString cache=cacheFileName(this, true);
if (!fromCache && QFile::exists(cache)) {
QFile::remove(cache);
}
createLoader();
if (!loader) {
return;
}
connect(loader, SIGNAL(error(QString)), this, SLOT(loaderError(QString)));
connect(loader, SIGNAL(status(QString,int)), this, SLOT(loaderstatus(QString,int)));
connect(loader, SIGNAL(loaded()), this, SLOT(loaderFinished()));
loader->setCacheFileName(cache);
loader->start();
setBusy(true);
}
void OnlineService::toggle()
{
if (loaded) {
clear();
} else {
reload(true);
}
}
void OnlineService::clear()
{
loaded=false;
int count=childCount();
if (count>0) {
m_model->beginRemoveRows(index(), 0, count-1);
clearItems();
m_model->endRemoveRows();
}
lProgress=0.0;
setStatusMessage(QString());
static_cast<OnlineServicesModel *>(m_model)->updateGenres();
}
void OnlineService::removeCache()
{
QString cn=cacheFileName(this);
if (QFile::exists(cn)) {
QFile::remove(cn);
}
}
void OnlineService::applyUpdate()
{
if (update) {
/*if (m_childItems.count() && update && update->childCount()) {
QSet<Song> currentSongs=allSongs();
QSet<Song> updateSongs=update->allSongs();
QSet<Song> removed=currentSongs-updateSongs;
QSet<Song> added=updateSongs-currentSongs;
foreach (const Song &s, removed) {
removeSongFromList(s);
}
foreach (const Song &s, added) {
addSongToList(s);
}
delete update;
} else*/ {
int oldCount=childCount();
if (oldCount>0) {
m_model->beginRemoveRows(index(), 0, oldCount-1);
clearItems();
m_model->endRemoveRows();
}
int newCount=newRows();
if (newCount>0) {
m_model->beginInsertRows(index(), 0, newCount-1);
foreach (MusicLibraryItem *item, update->childItems()) {
item->setParent(this);
}
refreshIndexes();
m_model->endInsertRows();
}
}
delete update;
update=0;
static_cast<OnlineServicesModel *>(m_model)->updateGenres();
emitUpdated();
}
setBusy(false);
}
void OnlineService::loaderError(const QString &msg)
{
lProgress=0;
setStatusMessage(msg);
stopLoader();
setBusy(false);
}
void OnlineService::loaderstatus(const QString &msg, int prog)
{
lProgress=-1==prog ? 0.0 : (abs(prog)/100.0);
if (prog<0) {
setStatusMessage(msg);
} else {
setStatusMessage(i18nc("Message percent", "%1 %2%", msg, prog));
}
}
void OnlineService::loaderFinished()
{
lProgress=100;
loaded=true;
setStatusMessage(QString());
update=loader->takeLibrary();
stopLoader();
applyUpdate();
}
void OnlineService::setStatusMessage(const QString &msg)
{
statusMsg=msg;
QModelIndex modelIndex=index();
emit m_model->dataChanged(modelIndex, modelIndex);
}
QModelIndex OnlineService::index() const
{
return m_model->createIndex(m_model->row((void *)this), 0, (void *)this);
}
void OnlineService::emitUpdated()
{
emit static_cast<OnlineServicesModel *>(m_model)->updated(index());
}
void OnlineService::setBusy(bool b)
{
static_cast<OnlineServicesModel *>(m_model)->setBusy(id(), b);
}