Put the Jamendo songs in a separate database that gets attached to the main database. "Reload" it by deleting the file, which is much faster than DELETEing all the songs inside. Make reloading the Jamendo, Icecast and Magnatune databases always bypass the network cache. Store the Jamendo track IDs in a separate table - for some reason this makes inserts faster. Fix the Jamendo model after the inital database load. Make the Jamendo dymanic playlist use the entries that are already in the local database rather than fetching the song metadata again.

This commit is contained in:
David Sansome 2010-11-27 16:14:09 +00:00
parent 421c04b6ec
commit b85ddbb380
52 changed files with 491 additions and 143 deletions

View File

@ -290,5 +290,6 @@
<file>schema/schema-21.sql</file>
<file>providers/jamendo.png</file>
<file>schema/schema-22.sql</file>
<file>schema/jamendo.sql</file>
</qresource>
</RCC>

54
data/schema/jamendo.sql Normal file
View File

@ -0,0 +1,54 @@
/* Schema should be kept identical to the "songs" table, even though most of
it isn't used by jamendo */
CREATE TABLE jamendo.songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
composer TEXT,
track INTEGER,
disc INTEGER,
bpm REAL,
year INTEGER,
genre TEXT,
comment TEXT,
compilation INTEGER,
length INTEGER,
bitrate INTEGER,
samplerate INTEGER,
directory INTEGER NOT NULL,
filename TEXT NOT NULL,
mtime INTEGER NOT NULL,
ctime INTEGER NOT NULL,
filesize INTEGER NOT NULL,
sampler INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
filetype INTEGER NOT NULL DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER,
rating INTEGER,
forced_compilation_on INTEGER NOT NULL DEFAULT 0,
forced_compilation_off INTEGER NOT NULL DEFAULT 0,
effective_compilation NOT NULL DEFAULT 0,
skipcount NOT NULL DEFAULT 0,
score NOT NULL DEFAULT 0
);
CREATE VIRTUAL TABLE jamendo.songs_fts USING fts3(
ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsgenre, ftscomment,
tokenize=unicode
);
CREATE INDEX jamendo.idx_jamendo_comp_artist ON songs (effective_compilation, artist);
CREATE TABLE jamendo.track_ids (
songs_row_id INTEGER PRIMARY KEY,
track_id INTEGER
);
CREATE INDEX jamendo.idx_jamendo_track_id ON track_ids(track_id);

View File

@ -1,49 +1,4 @@
/* Schema should be kept identical to the "songs" table, even though most of
it isn't used by jamendo */
CREATE TABLE jamendo_songs (
title TEXT,
album TEXT,
artist TEXT,
albumartist TEXT,
composer TEXT,
track INTEGER,
disc INTEGER,
bpm REAL,
year INTEGER,
genre TEXT,
comment TEXT,
compilation INTEGER,
length INTEGER,
bitrate INTEGER,
samplerate INTEGER,
directory INTEGER NOT NULL,
filename TEXT NOT NULL,
mtime INTEGER NOT NULL,
ctime INTEGER NOT NULL,
filesize INTEGER NOT NULL,
sampler INTEGER NOT NULL DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
filetype INTEGER NOT NULL DEFAULT 0,
playcount INTEGER NOT NULL DEFAULT 0,
lastplayed INTEGER,
rating INTEGER,
forced_compilation_on INTEGER NOT NULL DEFAULT 0,
forced_compilation_off INTEGER NOT NULL DEFAULT 0,
effective_compilation NOT NULL DEFAULT 0,
skipcount NOT NULL DEFAULT 0,
score NOT NULL DEFAULT 0
);
CREATE VIRTUAL TABLE jamendo_songs_fts USING fts3(
ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsgenre, ftscomment,
tokenize=unicode
);
CREATE INDEX idx_jamendo_comp_artist ON jamendo_songs (effective_compilation, artist);
UPDATE schema_version SET version=21;
/* This file used to contain Jamendo schema, but it is now included in an
attached external database and created by jamendo.sql */
SELECT 0;

View File

@ -335,6 +335,9 @@ Database::Database(QObject* parent, const QString& database_name)
directory_ = QDir::toNativeSeparators(
QDir::homePath() + "/.config/" + QCoreApplication::organizationName());
attached_databases_["jamendo"] = AttachedDatabase(
directory_ + "/jamendo.db", ":/schema/jamendo.sql");
QMutexLocker l(&mutex_);
Connect();
}
@ -405,6 +408,25 @@ QSqlDatabase Database::Connect() {
UpdateDatabaseSchema(0, db);
}
// Attach external databases
foreach (const QString& key, attached_databases_.keys()) {
const QString filename = attached_databases_[key].filename_;
const bool already_exists = QFile::exists(filename);
// Attach the db
QSqlQuery q("ATTACH DATABASE :filename AS :alias", db);
q.bindValue(":filename", filename);
q.bindValue(":alias", key);
if (!q.exec()) {
qFatal("Couldn't attach external database '%s'", key.toAscii().constData());
}
// Set up initial schema if it didn't exist already
if (!already_exists) {
ExecFromFile(attached_databases_[key].schema_, db);
}
}
// Get the database's schema version
QSqlQuery q("SELECT version FROM schema_version", db);
int schema_version = 0;
@ -425,6 +447,38 @@ QSqlDatabase Database::Connect() {
return db;
}
void Database::RecreateAttachedDb(const QString& database_name) {
if (!attached_databases_.contains(database_name)) {
qWarning() << "Attached database does not exist:" << database_name;
return;
}
const QString filename = attached_databases_[database_name].filename_;
QMutexLocker l(&mutex_);
{
QSqlDatabase db(Connect());
QSqlQuery q("DETACH DATABASE :alias", db);
q.bindValue(":alias", database_name);
if (!q.exec()) {
qWarning() << "Failed to detach database" << database_name;
return;
}
if (!QFile::remove(filename)) {
qWarning() << "Failed to remove file" << filename;
}
}
// We can't just re-attach the database now because it needs to be done for
// each thread. Close all the database connections, so each thread will
// re-attach it when they next connect.
foreach (const QString& name, QSqlDatabase::connectionNames()) {
QSqlDatabase::removeDatabase(name);
}
}
void Database::UpdateDatabaseSchema(int version, QSqlDatabase &db) {
QString filename;
if (version == 0)

View File

@ -18,10 +18,11 @@
#ifndef DATABASE_H
#define DATABASE_H
#include <QMap>
#include <QMutex>
#include <QObject>
#include <QSqlDatabase>
#include <QSqlError>
#include <QMutex>
#include <QStringList>
#include <sqlite3.h>
@ -52,6 +53,7 @@ class Database : public QObject {
bool CheckErrors(const QSqlError& error);
QMutex* Mutex() { return &mutex_; }
void RecreateAttachedDb(const QString& database_name);
void ExecFromFile(const QString& filename, QSqlDatabase &db);
void ExecCommands(const QString& commands, QSqlDatabase &db);
@ -62,6 +64,18 @@ class Database : public QObject {
void UpdateDatabaseSchema(int version, QSqlDatabase& db);
QStringList SongsTables(QSqlDatabase& db) const;
struct AttachedDatabase {
AttachedDatabase() {}
AttachedDatabase(const QString& filename, const QString& schema)
: filename_(filename), schema_(schema) {}
QString filename_;
QString schema_;
};
// Alias -> filename
QMap<QString, AttachedDatabase> attached_databases_;
QString directory_;
QMutex connect_mutex_;
QMutex mutex_;

View File

@ -88,8 +88,13 @@ QNetworkReply* NetworkAccessManager::createRequest(
new_request.setRawHeader("User-Agent", QString("%1 %2").arg(
QCoreApplication::applicationName(),
QCoreApplication::applicationVersion()).toUtf8());
new_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::PreferCache);
// Prefer the cache unless the caller has changed the setting already
if (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt()
== QNetworkRequest::PreferNetwork) {
new_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::PreferCache);
}
return QNetworkAccessManager::createRequest(op, new_request, outgoingData);
}

View File

@ -287,7 +287,7 @@ void LibraryBackend::AddOrUpdateSubdirs(const SubdirectoryList& subdirs) {
transaction.Commit();
}
void LibraryBackend::AddOrUpdateSongs(const SongList& songs, bool insert_with_id) {
void LibraryBackend::AddOrUpdateSongs(const SongList& songs) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
@ -296,9 +296,6 @@ void LibraryBackend::AddOrUpdateSongs(const SongList& songs, bool insert_with_id
QSqlQuery add_song(QString("INSERT INTO %1 (" + Song::kColumnSpec + ")"
" VALUES (" + Song::kBindSpec + ")")
.arg(songs_table_), db);
QSqlQuery add_song_id(QString("INSERT INTO %1 (ROWID, " + Song::kColumnSpec + ")"
" VALUES (:id, " + Song::kBindSpec + ")")
.arg(songs_table_), db);
QSqlQuery update_song(QString("UPDATE %1 SET " + Song::kUpdateSpec +
" WHERE ROWID = :id").arg(songs_table_), db);
QSqlQuery add_song_fts(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ")"
@ -325,26 +322,16 @@ void LibraryBackend::AddOrUpdateSongs(const SongList& songs, bool insert_with_id
continue; // Directory didn't exist
}
if (insert_with_id || song.id() == -1) {
if (song.id() == -1) {
// Create
int id = song.id();
if (insert_with_id) {
// Insert the row with the existing ID
add_song_id.bindValue(":id", song.id());
song.BindToQuery(&add_song_id);
add_song_id.exec();
if (db_->CheckErrors(add_song_id.lastError())) continue;
} else {
// Insert the row and create a new ID
song.BindToQuery(&add_song);
add_song.exec();
if (db_->CheckErrors(add_song.lastError())) continue;
// Insert the row and create a new ID
song.BindToQuery(&add_song);
add_song.exec();
if (db_->CheckErrors(add_song.lastError())) continue;
// Get the new ID
id = add_song.lastInsertId().toInt();
}
// Get the new ID
const int id = add_song.lastInsertId().toInt();
// Add to the FTS index
add_song_fts.bindValue(":id", id);
@ -493,22 +480,71 @@ SongList LibraryBackend::GetSongs(const QString& artist, const QString& album, c
Song LibraryBackend::GetSongById(int id) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
return GetSongById(id, db);
}
Song LibraryBackend::GetSongById(int id, QSqlDatabase& db) {
QSqlQuery q(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1"
" WHERE ROWID = :id").arg(songs_table_), db);
q.bindValue(":id", id);
SongList LibraryBackend::GetSongsById(const QList<int>& ids) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
QStringList str_ids;
foreach (int id, ids) {
str_ids << QString::number(id);
}
return GetSongsById(str_ids, db);
}
SongList LibraryBackend::GetSongsById(const QStringList& ids) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
return GetSongsById(ids, db);
}
SongList LibraryBackend::GetSongsByForeignId(
const QStringList& ids, const QString& table, const QString& column) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
QString in = ids.join(",");
QSqlQuery q(QString("SELECT %2.ROWID, " + Song::kColumnSpec +
" FROM %1, %2"
" WHERE %1.ROWID = %2.ROWID AND %2.%3 IN (%4)")
.arg(songs_table_, table, column, in), db);
q.exec();
if (db_->CheckErrors(q.lastError())) return Song();
if (db_->CheckErrors(q.lastError())) return SongList();
q.next();
SongList ret;
while (q.next()) {
Song song;
song.InitFromQuery(q);
ret << song;
}
return ret;
}
Song ret;
if (q.isValid()) {
ret.InitFromQuery(q);
Song LibraryBackend::GetSongById(int id, QSqlDatabase& db) {
SongList list = GetSongsById(QStringList() << QString::number(id), db);
if (list.isEmpty())
return Song();
return list.first();
}
SongList LibraryBackend::GetSongsById(const QStringList& ids, QSqlDatabase& db) {
QString in = ids.join(",");
QSqlQuery q(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1"
" WHERE ROWID IN (%2)").arg(songs_table_, in), db);
q.exec();
if (db_->CheckErrors(q.lastError())) return SongList();
SongList ret;
while (q.next()) {
Song song;
song.InitFromQuery(q);
ret << song;
}
return ret;
}

View File

@ -129,6 +129,10 @@ class LibraryBackend : public LibraryBackendInterface {
Album GetAlbumArt(const QString& artist, const QString& album);
Song GetSongById(int id);
SongList GetSongsById(const QList<int>& ids);
SongList GetSongsById(const QStringList& ids);
SongList GetSongsByForeignId(const QStringList& ids, const QString& table,
const QString& column);
void AddDirectory(const QString& path);
void RemoveDirectory(const Directory& dir);
@ -145,7 +149,7 @@ class LibraryBackend : public LibraryBackendInterface {
public slots:
void LoadDirectories();
void UpdateTotalSongCount();
void AddOrUpdateSongs(const SongList& songs, bool insert_with_id = false);
void AddOrUpdateSongs(const SongList& songs);
void UpdateMTimesOnly(const SongList& songs);
void DeleteSongs(const SongList& songs);
void AddOrUpdateSubdirs(const SubdirectoryList& subdirs);
@ -186,7 +190,9 @@ class LibraryBackend : public LibraryBackendInterface {
AlbumList GetAlbums(const QString& artist, bool compilation = false,
const QueryOptions& opt = QueryOptions());
SubdirectoryList SubdirsInDirectory(int id, QSqlDatabase& db);
Song GetSongById(int id, QSqlDatabase& db);
SongList GetSongsById(const QStringList& ids, QSqlDatabase& db);
private:
boost::shared_ptr<Database> db_;

View File

@ -86,8 +86,6 @@ QFuture<PlaylistItemPtr> PlaylistBackend::GetPlaylistItems(int playlist) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
PlaylistItemList ret;
QSqlQuery q("SELECT songs.ROWID, " + Song::JoinSpec("songs") + ","
" magnatune_songs.ROWID, " + Song::JoinSpec("magnatune_songs") + ","
" jamendo_songs.ROWID, " + Song::JoinSpec("jamendo_songs") + ","
@ -98,7 +96,7 @@ QFuture<PlaylistItemPtr> PlaylistBackend::GetPlaylistItems(int playlist) {
" ON p.library_id = songs.ROWID"
" LEFT JOIN magnatune_songs"
" ON p.library_id = magnatune_songs.ROWID"
" LEFT JOIN jamendo_songs"
" LEFT JOIN jamendo.songs AS jamendo_songs"
" ON p.library_id = jamendo_songs.ROWID"
" WHERE p.playlist = :playlist", db);
q.bindValue(":playlist", playlist);

View File

@ -90,6 +90,9 @@ void IcecastService::LazyPopulate(RadioItem* item) {
void IcecastService::LoadDirectory() {
QNetworkRequest req = QNetworkRequest(QUrl(kDirectoryUrl));
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::AlwaysNetwork);
QNetworkReply* reply = network_->get(req);
connect(reply, SIGNAL(finished()), SLOT(DownloadDirectoryFinished()));

View File

@ -1,31 +1,41 @@
#include "jamendodynamicplaylist.h"
#include <QEventLoop>
#include <QNetworkReply>
#include <QtDebug>
#include "core/network.h"
#include "core/songloader.h"
#include "library/librarybackend.h"
#include "radio/jamendoplaylistitem.h"
#include "radio/jamendoservice.h"
const char* JamendoDynamicPlaylist::kTopTracksMonthUrl =
"http://api.jamendo.com/get2/id+name+url+stream+album_name+"
"album_url+album_id+artist_id+artist_name/track/xspf/"
"track_album+album_artist/?order=ratingmonth_desc&streamencoding=ogg2";
const char* JamendoDynamicPlaylist::kUrl =
"http://api.jamendo.com/get2/id/track/plain/";
JamendoDynamicPlaylist::JamendoDynamicPlaylist()
: network_(new NetworkAccessManager(this)),
: order_by_(OrderBy_Rating),
order_direction_(Order_Descending),
current_page_(0),
current_index_(0) {
}
void JamendoDynamicPlaylist::Load(const QByteArray& data) {
QDataStream s(data);
s >> *this;
}
void JamendoDynamicPlaylist::Load(OrderBy order_by, OrderDirection order_direction) {
order_by_ = order_by;
order_direction_ = order_direction;
}
QByteArray JamendoDynamicPlaylist::Save() const {
return QByteArray();
QByteArray ret;
QDataStream s(&ret, QIODevice::WriteOnly);
s << *this;
return ret;
}
PlaylistItemList JamendoDynamicPlaylist::Generate() {
@ -49,37 +59,69 @@ PlaylistItemList JamendoDynamicPlaylist::GenerateMore(int count) {
return items;
}
QString JamendoDynamicPlaylist::OrderSpec(OrderBy by, OrderDirection dir) {
QString ret;
switch (by) {
case OrderBy_Listened: ret += "listened"; break;
case OrderBy_Rating: ret += "rating"; break;
case OrderBy_RatingMonth: ret += "ratingmonth"; break;
case OrderBy_RatingWeek: ret += "ratingweek"; break;
}
switch (dir) {
case Order_Ascending: ret += "_asc"; break;
case Order_Descending: ret += "_desc"; break;
}
return ret;
}
void JamendoDynamicPlaylist::Fetch() {
QUrl url = QUrl(kTopTracksMonthUrl);
QUrl url(kUrl);
url.addQueryItem("pn", QString::number(current_page_++));
url.addQueryItem("n", QString::number(kPageSize));
url.addQueryItem("order", OrderSpec(order_by_, order_direction_));
qDebug() << url;
SongLoader loader(backend_);
SongLoader::Result result = loader.Load(url);
if (result != SongLoader::WillLoadAsync) {
qWarning() << "Jamendo dynamic playlist fetch failed with:"
<< url;
return;
}
// Have to make a new NetworkAccessManager here because we're in a different
// thread.
NetworkAccessManager network;
QNetworkReply* reply = network.get(QNetworkRequest(url));
// Blocking wait for reply.
{
QEventLoop event_loop;
connect(&loader, SIGNAL(LoadFinished(bool)), &event_loop, SLOT(quit()));
connect(reply, SIGNAL(finished()), &event_loop, SLOT(quit()));
event_loop.exec();
}
const SongList& songs = loader.songs();
// The reply will contain one track ID per line
QStringList lines = QString::fromAscii(reply->readAll()).split('\n');
// Get the songs from the database
SongList songs = backend_->GetSongsByForeignId(
lines, JamendoService::kTrackIdsTable, JamendoService::kTrackIdsColumn);
if (songs.empty()) {
qWarning() << "No songs returned from Jamendo:"
<< url;
<< url.toString();
return;
}
current_items_.clear();
foreach (const Song& song, songs) {
current_items_ << PlaylistItemPtr(new JamendoPlaylistItem(song));
if (song.is_valid())
current_items_ << PlaylistItemPtr(new JamendoPlaylistItem(song));
}
current_index_ = 0;
}
QDataStream& operator <<(QDataStream& s, const JamendoDynamicPlaylist& p) {
s << quint8(p.order_by_) << quint8(p.order_direction_);
return s;
}
QDataStream& operator >>(QDataStream& s, JamendoDynamicPlaylist& p) {
quint8 order_by, order_direction;
s >> order_by >> order_direction;
p.order_by_ = JamendoDynamicPlaylist::OrderBy(order_by);
p.order_direction_ = JamendoDynamicPlaylist::OrderDirection(order_direction);
return s;
}

View File

@ -3,33 +3,56 @@
#include "smartplaylists/generator.h"
class NetworkAccessManager;
class JamendoDynamicPlaylist : public smart_playlists::Generator {
Q_OBJECT
public:
friend QDataStream& operator <<(QDataStream& s, const JamendoDynamicPlaylist& p);
friend QDataStream& operator >>(QDataStream& s, JamendoDynamicPlaylist& p);
public:
JamendoDynamicPlaylist();
virtual QString type() const { return "Jamendo"; }
virtual void Load(const QByteArray& data);
virtual QByteArray Save() const;
// These values are persisted - only add to the end
enum OrderBy {
OrderBy_Rating = 0,
OrderBy_RatingWeek = 1,
OrderBy_RatingMonth = 2,
OrderBy_Listened = 3,
};
virtual PlaylistItemList Generate();
// These values are persisted - only add to the end
enum OrderDirection {
Order_Ascending = 0,
Order_Descending = 1,
};
virtual bool is_dynamic() const { return true; }
virtual PlaylistItemList GenerateMore(int count);
QString type() const { return "Jamendo"; }
private:
void Load(const QByteArray& data);
void Load(OrderBy order_by, OrderDirection order_direction = Order_Descending);
QByteArray Save() const;
PlaylistItemList Generate();
bool is_dynamic() const { return true; }
PlaylistItemList GenerateMore(int count);
private:
void Fetch();
static QString OrderSpec(OrderBy by, OrderDirection dir);
private:
OrderBy order_by_;
OrderDirection order_direction_;
NetworkAccessManager* network_;
int current_page_;
PlaylistItemList current_items_;
int current_index_;
static const int kPageSize = 20;
static const char* kTopTracksMonthUrl;
static const int kPageSize = 100;
static const char* kUrl;
};
QDataStream& operator <<(QDataStream& s, const JamendoDynamicPlaylist& p);
QDataStream& operator >>(QDataStream& s, JamendoDynamicPlaylist& p);
#endif

View File

@ -26,8 +26,10 @@
#include <QXmlStreamReader>
#include "qtiocompressor.h"
#include "core/database.h"
#include "core/mergedproxymodel.h"
#include "core/network.h"
#include "core/scopedtransaction.h"
#include "core/taskmanager.h"
#include "library/librarybackend.h"
#include "library/libraryfilterwidget.h"
@ -50,8 +52,10 @@ const char* JamendoService::kHomepage = "http://www.jamendo.com/";
const char* JamendoService::kAlbumInfoUrl = "http://www.jamendo.com/album/%1";
const char* JamendoService::kDownloadAlbumUrl = "http://www.jamendo.com/download/album/%1";
const char* JamendoService::kSongsTable = "jamendo_songs";
const char* JamendoService::kFtsTable = "jamendo_songs_fts";
const char* JamendoService::kSongsTable = "jamendo.songs";
const char* JamendoService::kFtsTable = "jamendo.songs_fts";
const char* JamendoService::kTrackIdsTable = "jamendo.track_ids";
const char* JamendoService::kTrackIdsColumn = "track_id";
const char* JamendoService::kSettingsGroup = "Jamendo";
@ -114,15 +118,17 @@ void JamendoService::LazyPopulate(RadioItem* item) {
}
void JamendoService::UpdateTotalSongCount(int count) {
qDebug() << Q_FUNC_INFO << count;
total_song_count_ = count;
if (total_song_count_ == 0) {
if (total_song_count_ == 0 && !load_database_task_id_) {
DownloadDirectory();
}
}
void JamendoService::DownloadDirectory() {
QNetworkRequest req = QNetworkRequest(QUrl(kDirectoryUrl));
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::AlwaysNetwork);
QNetworkReply* reply = network_->get(req);
connect(reply, SIGNAL(finished()), SLOT(DownloadDirectoryFinished()));
connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
@ -172,24 +178,31 @@ void JamendoService::ParseDirectory(QIODevice* device) const {
// Bit of a hack: don't update the model while we're parsing the xml
disconnect(library_backend_, SIGNAL(SongsDiscovered(SongList)),
library_model_, SLOT(SongsDiscovered(SongList)));
disconnect(library_backend_, SIGNAL(TotalSongCountUpdated(int)),
this, SLOT(UpdateTotalSongCount(int)));
// Delete all existing songs in the db
library_backend_->DeleteAll();
// Delete the database and recreate it. This is faster than dropping tables
// or removing rows.
library_backend_->db()->RecreateAttachedDb("jamendo");
TrackIdList track_ids;
SongList songs;
QXmlStreamReader reader(device);
while (!reader.atEnd()) {
reader.readNext();
if (reader.tokenType() == QXmlStreamReader::StartElement &&
reader.name() == "artist") {
songs << ReadArtist(&reader);
songs << ReadArtist(&reader, &track_ids);
}
if (songs.count() >= kBatchSize) {
// Add the songs to the database in batches
library_backend_->AddOrUpdateSongs(songs, true);
library_backend_->AddOrUpdateSongs(songs);
InsertTrackIds(track_ids);
total_count += songs.count();
songs.clear();
track_ids.clear();
// Update progress info
model()->task_manager()->SetTaskProgress(
@ -197,17 +210,38 @@ void JamendoService::ParseDirectory(QIODevice* device) const {
}
}
library_backend_->AddOrUpdateSongs(songs, true);
library_backend_->AddOrUpdateSongs(songs);
InsertTrackIds(track_ids);
connect(library_backend_, SIGNAL(SongsDiscovered(SongList)),
library_model_, SLOT(SongsDiscovered(SongList)));
library_model_->Reset();
connect(library_backend_, SIGNAL(TotalSongCountUpdated(int)),
SLOT(UpdateTotalSongCount(int)));
library_backend_->UpdateTotalSongCount();
}
SongList JamendoService::ReadArtist(QXmlStreamReader* reader) const {
void JamendoService::InsertTrackIds(const TrackIdList& ids) const {
QMutexLocker l(library_backend_->db()->Mutex());
QSqlDatabase db(library_backend_->db()->Connect());
ScopedTransaction t(&db);
QSqlQuery insert(QString("INSERT INTO jamendo.%1 (%2) VALUES (:id)")
.arg(kTrackIdsTable, kTrackIdsColumn), db);
foreach (int id, ids) {
insert.bindValue(":id", id);
insert.exec();
}
t.Commit();
}
SongList JamendoService::ReadArtist(QXmlStreamReader* reader,
TrackIdList* track_ids) const {
SongList ret;
QString current_artist;
QString current_album;
while (!reader->atEnd()) {
reader->readNext();
@ -217,7 +251,7 @@ SongList JamendoService::ReadArtist(QXmlStreamReader* reader) const {
if (name == "name") {
current_artist = reader->readElementText().trimmed();
} else if (name == "album") {
ret << ReadAlbum(current_artist, reader);
ret << ReadAlbum(current_artist, reader, track_ids);
}
} else if (reader->isEndElement() && reader->name() == "artist") {
break;
@ -228,7 +262,7 @@ SongList JamendoService::ReadArtist(QXmlStreamReader* reader) const {
}
SongList JamendoService::ReadAlbum(
const QString& artist, QXmlStreamReader* reader) const {
const QString& artist, QXmlStreamReader* reader, TrackIdList* track_ids) const {
SongList ret;
QString current_album;
QString cover;
@ -245,7 +279,8 @@ SongList JamendoService::ReadAlbum(
cover = QString(kAlbumCoverUrl).arg(id);
current_album_id = id.toInt();
} else if (reader->name() == "track") {
ret << ReadTrack(artist, current_album, cover, current_album_id, reader);
ret << ReadTrack(artist, current_album, cover, current_album_id,
reader, track_ids);
}
} else if (reader->isEndElement() && reader->name() == "album") {
break;
@ -258,7 +293,8 @@ Song JamendoService::ReadTrack(const QString& artist,
const QString& album,
const QString& album_cover,
int album_id,
QXmlStreamReader* reader) const {
QXmlStreamReader* reader,
TrackIdList* track_ids) const {
Song song;
song.set_artist(artist);
song.set_album(album);
@ -296,10 +332,11 @@ Song JamendoService::ReadTrack(const QString& artist,
QString ogg_url = QString(kOggStreamUrl).arg(id_text);
song.set_filename(ogg_url);
song.set_art_automatic(album_cover);
song.set_id(id);
song.set_valid(true);
// Rely on songs getting added in this exact order
track_ids->append(id);
}
} else if (reader->isEndElement() && reader->name() == "track") {
break;
@ -312,6 +349,8 @@ void JamendoService::ParseDirectoryFinished() {
QFutureWatcher<void>* watcher = static_cast<QFutureWatcher<void>*>(sender());
delete watcher;
library_model_->Reset();
model()->task_manager()->SetTaskFinished(load_database_task_id_);
load_database_task_id_ = 0;
}

View File

@ -57,6 +57,8 @@ class JamendoService : public RadioService {
static const char* kSongsTable;
static const char* kFtsTable;
static const char* kTrackIdsTable;
static const char* kTrackIdsColumn;
static const char* kSettingsGroup;
@ -66,13 +68,18 @@ class JamendoService : public RadioService {
private:
void ParseDirectory(QIODevice* device) const;
SongList ReadArtist(QXmlStreamReader* reader) const;
SongList ReadAlbum(const QString& artist, QXmlStreamReader* reader) const;
typedef QList<int> TrackIdList;
SongList ReadArtist(QXmlStreamReader* reader, TrackIdList* track_ids) const;
SongList ReadAlbum(const QString& artist, QXmlStreamReader* reader,
TrackIdList* track_ids) const;
Song ReadTrack(const QString& artist,
const QString& album,
const QString& album_cover,
int album_id,
QXmlStreamReader* reader) const;
QXmlStreamReader* reader,
TrackIdList* track_ids) const;
void InsertTrackIds(const TrackIdList& ids) const;
void EnsureMenuCreated();

View File

@ -133,8 +133,8 @@ void MagnatuneService::LazyPopulate(RadioItem *item) {
void MagnatuneService::ReloadDatabase() {
QNetworkRequest request = QNetworkRequest(QUrl(kDatabaseUrl));
request.setRawHeader("User-Agent", QString("%1 %2").arg(
QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8());
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::AlwaysNetwork);
QNetworkReply* reply = network_->get(request);
connect(reply, SIGNAL(finished()), SLOT(ReloadDatabaseFinished()));

View File

@ -1096,6 +1096,9 @@ msgstr "خدمة غير متاحة"
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1100,6 +1100,9 @@ msgstr ""
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1121,6 +1121,9 @@ msgstr "Servei invàlid"
msgid "Invalid session key"
msgstr "Sessió no vàlida"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Salta a la pista que s'esta reproduint"

View File

@ -1100,6 +1100,9 @@ msgstr "Neplatná služba"
msgid "Invalid session key"
msgstr "Neplatný klíč relace"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1096,6 +1096,9 @@ msgstr ""
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1101,6 +1101,9 @@ msgstr "Ugyldig tjeneste"
msgid "Invalid session key"
msgstr "Ugyldig sessionsnøgle"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1124,6 +1124,9 @@ msgstr "Ungültiger Dienst"
msgid "Invalid session key"
msgstr "Ungültiger Sitzungsschlüssel"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Zum aktuellen Stück springen"

View File

@ -1135,6 +1135,9 @@ msgstr "Εσφαλμένη υπηρεσία"
msgid "Invalid session key"
msgstr "Εσφαλμένο κλειδί συνεδρίας"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Μετάβαση στο τρέχον κομμάτι που παίζει"

View File

@ -1100,6 +1100,9 @@ msgstr "Invalid service"
msgid "Invalid session key"
msgstr "Invalid session key"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Jump to the currently playing track"

View File

@ -1098,6 +1098,9 @@ msgstr "Invalid service"
msgid "Invalid session key"
msgstr "Invalid session key"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1096,6 +1096,9 @@ msgstr ""
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1133,6 +1133,9 @@ msgstr "Servicio inválido"
msgid "Invalid session key"
msgstr "Clave de sesión inválida"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Ir a la pista actualmente reproduciéndose"

View File

@ -1098,6 +1098,9 @@ msgstr ""
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1097,6 +1097,9 @@ msgstr ""
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1130,6 +1130,9 @@ msgstr "Service invalide"
msgid "Invalid session key"
msgstr "Clé de session invalide"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Aller à la piste jouée actuellement"

View File

@ -1101,6 +1101,9 @@ msgstr "Servizo Inválido"
msgid "Invalid session key"
msgstr "Chave de sesón non válida"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1127,6 +1127,9 @@ msgstr "Érvénytelen szolgáltatás"
msgid "Invalid session key"
msgstr "Érvénytelen munkafolyamat kulcs"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Ugrás a most lejátszott számra"

View File

@ -1134,6 +1134,9 @@ msgstr "Servizio non valido"
msgid "Invalid session key"
msgstr "Chiave di sessione non valida"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Salta alla traccia in riproduzione"

View File

@ -1121,6 +1121,9 @@ msgstr "不正なサービスです"
msgid "Invalid session key"
msgstr "不正なセッション キーです"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "現在再生中のトラックへジャンプする"

View File

@ -1098,6 +1098,9 @@ msgstr ""
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1096,6 +1096,9 @@ msgstr ""
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1110,6 +1110,9 @@ msgstr "Ukjent tjeneste"
msgid "Invalid session key"
msgstr "Ugyldig sesjonsnøkkel"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1124,6 +1124,9 @@ msgstr "Ongeldige service"
msgid "Invalid session key"
msgstr "Ongeldige sessiesleutel"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Spring naar de nu spelende track"

View File

@ -1096,6 +1096,9 @@ msgstr ""
msgid "Invalid session key"
msgstr "Clau de sesilha invalida"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1121,6 +1121,9 @@ msgstr "Błędna usługa"
msgid "Invalid session key"
msgstr "Zły klucz sesji"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Przeskocz do teraz odtwarzanej ścieżki"

View File

@ -1130,6 +1130,9 @@ msgstr "Serviço inválido"
msgid "Invalid session key"
msgstr "Chave de sessão inválida"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Ir para a faixa de reprodução atual"

View File

@ -1110,6 +1110,9 @@ msgstr "Serviço inválido"
msgid "Invalid session key"
msgstr "chave de sessão inválida"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Pular para a faixa em execução"

View File

@ -1097,6 +1097,9 @@ msgstr "Serviciu invalid"
msgid "Invalid session key"
msgstr "Cheie de sesiune invalidă"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1117,6 +1117,9 @@ msgstr "Неправильная служба"
msgid "Invalid session key"
msgstr "Неправильный ключ сессии"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Перейти к текущей композиции"

View File

@ -1121,6 +1121,9 @@ msgstr "Nefunkčná služba"
msgid "Invalid session key"
msgstr "nefunkčný kľúč sedenia"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Skočiť na práve prehrávanú skladbu"

View File

@ -1123,6 +1123,9 @@ msgstr "Neveljavna storitev"
msgid "Invalid session key"
msgstr "Neveljavni ključ seje"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Skoči na trenutno predvajano skladbo"

View File

@ -1100,6 +1100,9 @@ msgstr ""
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Скочи на нумеру која се тренутно пушта"

View File

@ -1122,6 +1122,9 @@ msgstr "Ogiltig tjänst"
msgid "Invalid session key"
msgstr "Felaktig sessionsnyckel"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Hoppa till spåret som spelas för närvarande"

View File

@ -1118,6 +1118,9 @@ msgstr "Geçersiz servis"
msgid "Invalid session key"
msgstr "Geçersiz oturum anahtarı"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Şu anda çalınan parçaya atla"

View File

@ -1122,6 +1122,9 @@ msgstr "Нечинна служба"
msgid "Invalid session key"
msgstr "Неправильний ключ сеансу"
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "Перейти до відтворюваної доріжки"

View File

@ -1098,6 +1098,9 @@ msgstr ""
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr ""

View File

@ -1101,6 +1101,9 @@ msgstr "無效的服務"
msgid "Invalid session key"
msgstr ""
msgid "Jamendo Top Tracks of the Month"
msgstr ""
msgid "Jump to the currently playing track"
msgstr "跳轉到目前播放的歌曲"