Add new method for updating songs based on song ID

Show status updating database.

Fixes #750
This commit is contained in:
Jonas Kvinge 2021-09-19 15:41:36 +02:00
parent 120b18b399
commit d2d7f32c45
44 changed files with 650 additions and 194 deletions

View File

@ -76,6 +76,7 @@ set(SOURCES
collection/sqlrow.cpp collection/sqlrow.cpp
collection/savedgroupingmanager.cpp collection/savedgroupingmanager.cpp
collection/groupbydialog.cpp collection/groupbydialog.cpp
collection/collectiontask.cpp
playlist/playlist.cpp playlist/playlist.cpp
playlist/playlistbackend.cpp playlist/playlistbackend.cpp

View File

@ -62,7 +62,7 @@ SCollection::SCollection(Application *app, QObject *parent)
backend()->moveToThread(app->database()->thread()); backend()->moveToThread(app->database()->thread());
qLog(Debug) << backend_ << "moved to thread" << app->database()->thread(); qLog(Debug) << backend_ << "moved to thread" << app->database()->thread();
backend_->Init(app->database(), Song::Source_Collection, kSongsTable, kFtsTable, kDirsTable, kSubdirsTable); backend_->Init(app->database(), app->task_manager(), Song::Source_Collection, kSongsTable, kFtsTable, kDirsTable, kSubdirsTable);
model_ = new CollectionModel(backend_, app_, this); model_ = new CollectionModel(backend_, app_, this);

View File

@ -46,16 +46,19 @@
#include "core/logging.h" #include "core/logging.h"
#include "core/database.h" #include "core/database.h"
#include "core/scopedtransaction.h" #include "core/scopedtransaction.h"
#include "core/song.h"
#include "smartplaylists/smartplaylistsearch.h" #include "smartplaylists/smartplaylistsearch.h"
#include "directory.h" #include "directory.h"
#include "sqlrow.h"
#include "collectionbackend.h" #include "collectionbackend.h"
#include "collectionquery.h" #include "collectionquery.h"
#include "sqlrow.h" #include "collectiontask.h"
CollectionBackend::CollectionBackend(QObject *parent) CollectionBackend::CollectionBackend(QObject *parent)
: CollectionBackendInterface(parent), : CollectionBackendInterface(parent),
db_(nullptr), db_(nullptr),
task_manager_(nullptr),
source_(Song::Source_Unknown), source_(Song::Source_Unknown),
original_thread_(nullptr) { original_thread_(nullptr) {
@ -63,13 +66,16 @@ CollectionBackend::CollectionBackend(QObject *parent)
} }
void CollectionBackend::Init(Database *db, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table, const QString &subdirs_table) { void CollectionBackend::Init(Database *db, TaskManager *task_manager, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table, const QString &subdirs_table) {
db_ = db; db_ = db;
task_manager_ = task_manager;
source_ = source; source_ = source;
songs_table_ = songs_table; songs_table_ = songs_table;
dirs_table_ = dirs_table; dirs_table_ = dirs_table;
subdirs_table_ = subdirs_table; subdirs_table_ = subdirs_table;
fts_table_ = fts_table; fts_table_ = fts_table;
} }
void CollectionBackend::Close() { void CollectionBackend::Close() {
@ -623,6 +629,8 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
id = q.lastInsertId().toInt(); id = q.lastInsertId().toInt();
} }
if (id == -1) return;
{ // Add to the FTS index { // Add to the FTS index
SqlQuery q(db); SqlQuery q(db);
q.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_)); q.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_));
@ -634,9 +642,9 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
} }
} }
Song copy(song); Song song_copy(song);
copy.set_id(id); song_copy.set_id(id);
added_songs << copy; added_songs << song_copy;
} }
@ -651,6 +659,136 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
} }
void CollectionBackend::UpdateSongsBySongIDAsync(const SongMap &new_songs) {
QMetaObject::invokeMethod(this, "UpdateSongsBySongID", Qt::QueuedConnection, Q_ARG(SongMap, new_songs));
}
void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
CollectionTask task(task_manager_, tr("Updating %1 database.").arg(Song::TextForSource(source_)));
ScopedTransaction transaction(&db);
SongList added_songs;
SongList deleted_songs;
SongMap old_songs;
{
CollectionQuery query(db, songs_table_, fts_table_);
if (!ExecCollectionQuery(&query, old_songs)) {
ReportErrors(query);
return;
}
}
// Add or update songs.
for (const Song &new_song : new_songs) {
if (old_songs.contains(new_song.song_id())) {
Song old_song = old_songs[new_song.song_id()];
if (!new_song.IsMetadataEqual(old_song)) { // Update existing song.
{
SqlQuery q(db);
q.prepare(QString("UPDATE %1 SET " + Song::kUpdateSpec + " WHERE ROWID = :id").arg(songs_table_));
new_song.BindToQuery(&q);
q.BindValue(":id", old_song.id());
if (!q.Exec()) {
db_->ReportErrors(q);
return;
}
}
{
SqlQuery q(db);
q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_));
new_song.BindToFtsQuery(&q);
q.BindValue(":id", old_song.id());
if (!q.Exec()) {
db_->ReportErrors(q);
return;
}
}
deleted_songs << old_song;
Song new_song_copy(new_song);
new_song_copy.set_id(old_song.id());
added_songs << new_song_copy;
}
}
else { // Add new song
int id = -1;
{
SqlQuery q(db);
q.prepare(QString("INSERT INTO %1 (" + Song::kColumnSpec + ") VALUES (" + Song::kBindSpec + ")").arg(songs_table_));
new_song.BindToQuery(&q);
if (!q.Exec()) {
db_->ReportErrors(q);
return;
}
// Get the new ID
id = q.lastInsertId().toInt();
}
if (id == -1) return;
{ // Add to the FTS index
SqlQuery q(db);
q.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_));
q.BindValue(":id", id);
new_song.BindToFtsQuery(&q);
if (!q.Exec()) {
db_->ReportErrors(q);
return;
}
}
Song new_song_copy(new_song);
new_song_copy.set_id(id);
added_songs << new_song_copy;
}
}
// Delete songs
for (const Song &old_song : old_songs) {
if (!new_songs.contains(old_song.song_id())) {
{
SqlQuery q(db);
q.prepare(QString("DELETE FROM %1 WHERE ROWID = :id").arg(songs_table_));
q.BindValue(":id", old_song.id());
if (!q.Exec()) {
db_->ReportErrors(q);
return;
}
}
{
SqlQuery q(db);
q.prepare(QString("DELETE FROM %1 WHERE ROWID = :id").arg(fts_table_));
q.BindValue(":id", old_song.id());
if (!q.Exec()) {
db_->ReportErrors(q);
return;
}
}
deleted_songs << old_song;
}
}
transaction.Commit();
if (!deleted_songs.isEmpty()) emit SongsDeleted(deleted_songs);
if (!added_songs.isEmpty()) emit SongsDiscovered(added_songs);
UpdateTotalSongCountAsync();
UpdateTotalArtistCountAsync();
UpdateTotalAlbumCountAsync();
}
void CollectionBackend::UpdateMTimesOnly(const SongList &songs) { void CollectionBackend::UpdateMTimesOnly(const SongList &songs) {
QMutexLocker l(db_->Mutex()); QMutexLocker l(db_->Mutex());
@ -882,6 +1020,21 @@ bool CollectionBackend::ExecCollectionQuery(CollectionQuery *query, SongList &so
} }
bool CollectionBackend::ExecCollectionQuery(CollectionQuery *query, SongMap &songs) {
query->SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
if (!query->Exec()) return false;
while (query->Next()) {
Song song(source_);
song.InitFromQuery(*query, true);
songs.insert(song.song_id(), song);
}
return true;
}
Song CollectionBackend::GetSongById(const int id) { Song CollectionBackend::GetSongById(const int id) {
QMutexLocker l(db_->Mutex()); QMutexLocker l(db_->Mutex());

View File

@ -40,6 +40,7 @@
#include "directory.h" #include "directory.h"
class QThread; class QThread;
class TaskManager;
class Database; class Database;
class SmartPlaylistSearch; class SmartPlaylistSearch;
@ -127,7 +128,7 @@ class CollectionBackend : public CollectionBackendInterface {
Q_INVOKABLE explicit CollectionBackend(QObject *parent = nullptr); Q_INVOKABLE explicit CollectionBackend(QObject *parent = nullptr);
void Init(Database *db, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString()); void Init(Database *db, TaskManager *task_manager, const Song::Source source, const QString &songs_table, const QString &fts_table, const QString &dirs_table = QString(), const QString &subdirs_table = QString());
void Close(); void Close();
void ExitAsync(); void ExitAsync();
@ -184,6 +185,7 @@ class CollectionBackend : public CollectionBackendInterface {
void RemoveDirectory(const Directory &dir) override; void RemoveDirectory(const Directory &dir) override;
bool ExecCollectionQuery(CollectionQuery *query, SongList &songs); bool ExecCollectionQuery(CollectionQuery *query, SongList &songs);
bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs);
void IncrementPlayCountAsync(const int id); void IncrementPlayCountAsync(const int id);
void IncrementSkipCountAsync(const int id, const float progress); void IncrementSkipCountAsync(const int id, const float progress);
@ -202,6 +204,7 @@ class CollectionBackend : public CollectionBackendInterface {
Song::Source Source() const; Song::Source Source() const;
void AddOrUpdateSongsAsync(const SongList &songs); void AddOrUpdateSongsAsync(const SongList &songs);
void UpdateSongsBySongIDAsync(const SongMap &new_songs);
void UpdateSongRatingAsync(const int id, const double rating); void UpdateSongRatingAsync(const int id, const double rating);
void UpdateSongsRatingAsync(const QList<int> &ids, const double rating); void UpdateSongsRatingAsync(const QList<int> &ids, const double rating);
@ -213,6 +216,7 @@ class CollectionBackend : public CollectionBackendInterface {
void UpdateTotalArtistCount(); void UpdateTotalArtistCount();
void UpdateTotalAlbumCount(); void UpdateTotalAlbumCount();
void AddOrUpdateSongs(const SongList &songs); void AddOrUpdateSongs(const SongList &songs);
void UpdateSongsBySongID(const SongMap &new_songs);
void UpdateMTimesOnly(const SongList &songs); void UpdateMTimesOnly(const SongList &songs);
void DeleteSongs(const SongList &songs); void DeleteSongs(const SongList &songs);
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true); void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
@ -279,6 +283,7 @@ class CollectionBackend : public CollectionBackendInterface {
private: private:
Database *db_; Database *db_;
TaskManager *task_manager_;
Song::Source source_; Song::Source source_;
QString songs_table_; QString songs_table_;
QString dirs_table_; QString dirs_table_;

View File

@ -0,0 +1,35 @@
/*
* Strawberry Music Player
* Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QString>
#include "core/taskmanager.h"
#include "collectiontask.h"
CollectionTask::CollectionTask(TaskManager *task_manager, const QString &message) : task_manager_(task_manager), task_id_(-1) {
if (task_manager_) task_id_ = task_manager_->StartTask(message);
}
CollectionTask::~CollectionTask() {
if (task_manager_) task_manager_->SetTaskFinished(task_id_);
}

View File

@ -0,0 +1,40 @@
/*
* Strawberry Music Player
* Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLLECTIONTASK_H
#define COLLECTIONTASK_H
#include <QtGlobal>
#include <QString>
class TaskManager;
class CollectionTask {
public:
explicit CollectionTask(TaskManager *task_manager, const QString &message);
~CollectionTask();
private:
TaskManager *task_manager_;
int task_id_;
Q_DISABLE_COPY(CollectionTask)
};
#endif // COLLECTIONTASK_H

View File

@ -471,7 +471,7 @@ void CollectionView::SetShowInVarious(const bool on) {
if (on && albums.keys().count() == 1) { if (on && albums.keys().count() == 1) {
const QStringList albums_list = albums.keys(); const QStringList albums_list = albums.keys();
const QString album = albums_list.first(); const QString album = albums_list.first();
QList<Song> all_of_album = app_->collection_backend()->GetSongsByAlbum(album); SongList all_of_album = app_->collection_backend()->GetSongsByAlbum(album);
QSet<QString> other_artists; QSet<QString> other_artists;
for (const Song &s : all_of_album) { for (const Song &s : all_of_album) {
if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) { if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) {

View File

@ -99,8 +99,10 @@ void RegisterMetaTypes() {
qRegisterMetaType<Subdirectory>("Subdirectory"); qRegisterMetaType<Subdirectory>("Subdirectory");
qRegisterMetaType<SubdirectoryList>("SubdirectoryList"); qRegisterMetaType<SubdirectoryList>("SubdirectoryList");
qRegisterMetaType<Song>("Song"); qRegisterMetaType<Song>("Song");
qRegisterMetaType<QList<Song>>("QList<Song>");
qRegisterMetaType<SongList>("SongList"); qRegisterMetaType<SongList>("SongList");
qRegisterMetaType<SongMap>("SongMap");
qRegisterMetaType<QList<Song>>("QList<Song>");
qRegisterMetaType<QMap<QString, Song>>("QMap<QString, Song>");
qRegisterMetaType<Engine::EngineType>("EngineType"); qRegisterMetaType<Engine::EngineType>("EngineType");
qRegisterMetaType<Engine::SimpleMetaBundle>("Engine::SimpleMetaBundle"); qRegisterMetaType<Engine::SimpleMetaBundle>("Engine::SimpleMetaBundle");
qRegisterMetaType<Engine::State>("Engine::State"); qRegisterMetaType<Engine::State>("Engine::State");

View File

@ -25,19 +25,23 @@
#include "core/logging.h" #include "core/logging.h"
#include "scopedtransaction.h" #include "scopedtransaction.h"
ScopedTransaction::ScopedTransaction(QSqlDatabase *db) ScopedTransaction::ScopedTransaction(QSqlDatabase *db) : db_(db), pending_(true) {
: db_(db), pending_(true) {
db->transaction(); db->transaction();
} }
ScopedTransaction::~ScopedTransaction() { ScopedTransaction::~ScopedTransaction() {
if (pending_) { if (pending_) {
qLog(Warning) << "Rolling back transaction"; qLog(Warning) << "Rolling back transaction";
db_->rollback(); db_->rollback();
} }
} }
void ScopedTransaction::Commit() { void ScopedTransaction::Commit() {
if (!pending_) { if (!pending_) {
qLog(Warning) << "Tried to commit a ScopedTransaction twice"; qLog(Warning) << "Tried to commit a ScopedTransaction twice";
return; return;
@ -45,4 +49,5 @@ void ScopedTransaction::Commit() {
db_->commit(); db_->commit();
pending_ = false; pending_ = false;
} }

View File

@ -30,6 +30,7 @@
#include <QMetaType> #include <QMetaType>
#include <QList> #include <QList>
#include <QSet> #include <QSet>
#include <QMap>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
@ -396,10 +397,13 @@ class Song {
QSharedDataPointer<Private> d; QSharedDataPointer<Private> d;
}; };
Q_DECLARE_METATYPE(Song)
typedef QList<Song> SongList; typedef QList<Song> SongList;
Q_DECLARE_METATYPE(QList<Song>) typedef QMap<QString, Song> SongMap;
Q_DECLARE_METATYPE(Song)
Q_DECLARE_METATYPE(SongList)
Q_DECLARE_METATYPE(SongMap)
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
size_t qHash(const Song &song); size_t qHash(const Song &song);

View File

@ -63,6 +63,7 @@ ConnectedDevice::ConnectedDevice(const QUrl &url, DeviceLister *lister, const QS
} }
backend_->Init(app_->database(), backend_->Init(app_->database(),
app_->task_manager(),
Song::Source_Device, Song::Source_Device,
QString("device_%1_songs").arg(database_id), QString("device_%1_songs").arg(database_id),
QString("device_%1_fts").arg(database_id), QString("device_%1_fts").arg(database_id),

View File

@ -26,6 +26,7 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QAbstractItemView> #include <QAbstractItemView>
#include <QItemSelectionModel> #include <QItemSelectionModel>
#include <QMap>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>

View File

@ -28,6 +28,7 @@
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QAbstractItemView> #include <QAbstractItemView>
#include <QSet> #include <QSet>
#include <QMap>
#include <QString> #include <QString>
#include <QPixmap> #include <QPixmap>
@ -86,7 +87,7 @@ class InternetCollectionView : public AutoExpandingTreeView {
void TotalArtistCountUpdated_(); void TotalArtistCountUpdated_();
void TotalAlbumCountUpdated_(); void TotalAlbumCountUpdated_();
void Error(QString); void Error(QString);
void RemoveSongs(SongList); void RemoveSongs(SongList songs);
protected: protected:
// QWidget // QWidget

View File

@ -26,6 +26,7 @@
#include <QMimeData> #include <QMimeData>
#include <QList> #include <QList>
#include <QSet> #include <QSet>
#include <QMap>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
#include <QPixmap> #include <QPixmap>
@ -392,12 +393,11 @@ MimeData *InternetSearchModel::LoadTracks(const InternetSearchView::ResultList &
return nullptr; return nullptr;
} }
SongList songs; SongMap songs;
QList<QUrl> urls; QList<QUrl> urls;
songs.reserve(results.count());
urls.reserve(results.count()); urls.reserve(results.count());
for (const InternetSearchView::Result &result : results) { for (const InternetSearchView::Result &result : results) {
songs << result.metadata_; songs.insert(result.metadata_.song_id(), result.metadata_);
urls << result.metadata_.url(); urls << result.metadata_.url();
} }

View File

@ -487,7 +487,7 @@ void InternetSearchView::SearchAsync(const int id, const QString &query, const S
} }
void InternetSearchView::SearchDone(const int service_id, const SongList &songs, const QString &error) { void InternetSearchView::SearchDone(const int service_id, const SongMap &songs, const QString &error) {
if (!pending_searches_.contains(service_id)) return; if (!pending_searches_.contains(service_id)) return;
@ -758,7 +758,7 @@ void InternetSearchView::AddArtists() {
MimeData *mimedata = SelectedMimeData(); MimeData *mimedata = SelectedMimeData();
if (!mimedata) return; if (!mimedata) return;
if (const InternetSongMimeData *internet_song_data = qobject_cast<const InternetSongMimeData*>(mimedata)) { if (const InternetSongMimeData *internet_song_data = qobject_cast<const InternetSongMimeData*>(mimedata)) {
emit AddArtistsSignal(internet_song_data->songs); emit AddArtistsSignal(internet_song_data->songs.values());
} }
} }
@ -768,7 +768,7 @@ void InternetSearchView::AddAlbums() {
MimeData *mimedata = SelectedMimeData(); MimeData *mimedata = SelectedMimeData();
if (!mimedata) return; if (!mimedata) return;
if (const InternetSongMimeData *internet_song_data = qobject_cast<const InternetSongMimeData*>(mimedata)) { if (const InternetSongMimeData *internet_song_data = qobject_cast<const InternetSongMimeData*>(mimedata)) {
emit AddAlbumsSignal(internet_song_data->songs); emit AddAlbumsSignal(internet_song_data->songs.values());
} }
} }

View File

@ -140,13 +140,13 @@ class InternetSearchView : public QWidget {
void AddToPlaylist(QMimeData*); void AddToPlaylist(QMimeData*);
void AddArtistsSignal(SongList); void AddArtistsSignal(SongList);
void AddAlbumsSignal(SongList); void AddAlbumsSignal(SongList);
void AddSongsSignal(SongList); void AddSongsSignal(SongMap);
private slots: private slots:
void SwapModels(); void SwapModels();
void TextEdited(const QString &text); void TextEdited(const QString &text);
void StartSearch(const QString &query); void StartSearch(const QString &query);
void SearchDone(const int service_id, const SongList &songs, const QString &error); void SearchDone(const int service_id, const SongMap &songs, const QString &error);
void UpdateStatus(const int service_id, const QString &text); void UpdateStatus(const int service_id, const QString &text);
void ProgressSetMaximum(const int service_id, const int max); void ProgressSetMaximum(const int service_id, const int max);

View File

@ -23,6 +23,7 @@
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
#include <QMetaType> #include <QMetaType>
#include <QMap>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include <QIcon> #include <QIcon>
@ -95,38 +96,39 @@ class InternetService : public QObject {
void TestComplete(bool success, QString error = QString()); void TestComplete(bool success, QString error = QString());
void Error(QString error); void Error(QString error);
void Results(SongList songs, QString error); void Results(SongMap songs, QString error);
void UpdateStatus(QString text); void UpdateStatus(QString text);
void ProgressSetMaximum(int max); void ProgressSetMaximum(int max);
void UpdateProgress(int max); void UpdateProgress(int max);
void ArtistsResults(SongList songs, QString error); void ArtistsResults(SongMap songs, QString error);
void ArtistsUpdateStatus(QString text); void ArtistsUpdateStatus(QString text);
void ArtistsProgressSetMaximum(int max); void ArtistsProgressSetMaximum(int max);
void ArtistsUpdateProgress(int max); void ArtistsUpdateProgress(int max);
void AlbumsResults(SongList songs, QString error); void AlbumsResults(SongMap songs, QString error);
void AlbumsUpdateStatus(QString text); void AlbumsUpdateStatus(QString text);
void AlbumsProgressSetMaximum(int max); void AlbumsProgressSetMaximum(int max);
void AlbumsUpdateProgress(int max); void AlbumsUpdateProgress(int max);
void SongsResults(SongList songs, QString error); void SongsResults(SongMap songs, QString error);
void SongsUpdateStatus(QString text); void SongsUpdateStatus(QString text);
void SongsProgressSetMaximum(int max); void SongsProgressSetMaximum(int max);
void SongsUpdateProgress(int max); void SongsUpdateProgress(int max);
void SearchResults(int id, SongList songs, QString error); void SearchResults(int id, SongMap songs, QString error);
void SearchUpdateStatus(int id, QString text); void SearchUpdateStatus(int id, QString text);
void SearchProgressSetMaximum(int id, int max); void SearchProgressSetMaximum(int id, int max);
void SearchUpdateProgress(int id, int max); void SearchUpdateProgress(int id, int max);
void AddArtists(SongList); void AddArtists(SongList songs);
void AddAlbums(SongList); void AddAlbums(SongList songs);
void AddSongs(SongList); void AddSongs(SongMap songs);
void RemoveArtists(SongList); void RemoveArtists(SongList songs);
void RemoveAlbums(SongList); void RemoveAlbums(SongList songs);
void RemoveSongs(SongList); void RemoveSongs(SongList songs);
void RemoveSongs(SongMap songs);
void StreamURLFinished(QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate, int bit_depth, qint64 duration, QString error = QString()); void StreamURLFinished(QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate, int bit_depth, qint64 duration, QString error = QString());

View File

@ -21,6 +21,8 @@
#ifndef INTERNETSONGMIMEDATA_H #ifndef INTERNETSONGMIMEDATA_H
#define INTERNETSONGMIMEDATA_H #define INTERNETSONGMIMEDATA_H
#include <QMap>
#include "core/mimedata.h" #include "core/mimedata.h"
#include "core/song.h" #include "core/song.h"
@ -33,7 +35,7 @@ class InternetSongMimeData : public MimeData {
explicit InternetSongMimeData(InternetService *_service, QObject* = nullptr) : service(_service) {} explicit InternetSongMimeData(InternetService *_service, QObject* = nullptr) : service(_service) {}
InternetService *service; InternetService *service;
SongList songs; SongMap songs;
}; };
#endif // INTERNETSONGMIMEDATA_H #endif // INTERNETSONGMIMEDATA_H

View File

@ -21,6 +21,7 @@
#include <QtGlobal> #include <QtGlobal>
#include <QWidget> #include <QWidget>
#include <QMap>
#include <QString> #include <QString>
#include <QStackedWidget> #include <QStackedWidget>
#include <QContextMenuEvent> #include <QContextMenuEvent>
@ -62,7 +63,7 @@ InternetSongsView::InternetSongsView(Application *app, InternetService *service,
ui_->filter_widget->AddMenuAction(action_configure); ui_->filter_widget->AddMenuAction(action_configure);
QObject::connect(ui_->view, &InternetCollectionView::GetSongs, this, &InternetSongsView::GetSongs); QObject::connect(ui_->view, &InternetCollectionView::GetSongs, this, &InternetSongsView::GetSongs);
QObject::connect(ui_->view, &InternetCollectionView::RemoveSongs, service_, &InternetService::RemoveSongs); QObject::connect(ui_->view, &InternetCollectionView::RemoveSongs, service_, QOverload<SongList>::of(&InternetService::RemoveSongs));
QObject::connect(ui_->refresh, &QPushButton::clicked, this, &InternetSongsView::GetSongs); QObject::connect(ui_->refresh, &QPushButton::clicked, this, &InternetSongsView::GetSongs);
QObject::connect(ui_->close, &QPushButton::clicked, this, &InternetSongsView::AbortGetSongs); QObject::connect(ui_->close, &QPushButton::clicked, this, &InternetSongsView::AbortGetSongs);
@ -121,7 +122,7 @@ void InternetSongsView::AbortGetSongs() {
} }
void InternetSongsView::SongsFinished(const SongList &songs, const QString &error) { void InternetSongsView::SongsFinished(const SongMap &songs, const QString &error) {
if (songs.isEmpty() && !error.isEmpty()) { if (songs.isEmpty() && !error.isEmpty()) {
ui_->status->setText(error); ui_->status->setText(error);
@ -131,10 +132,9 @@ void InternetSongsView::SongsFinished(const SongList &songs, const QString &erro
ui_->close->show(); ui_->close->show();
} }
else { else {
service_->songs_collection_backend()->DeleteAll();
ui_->stacked->setCurrentWidget(ui_->internetcollection_page); ui_->stacked->setCurrentWidget(ui_->internetcollection_page);
ui_->status->clear(); ui_->status->clear();
service_->songs_collection_backend()->AddOrUpdateSongsAsync(songs); service_->songs_collection_backend()->UpdateSongsBySongIDAsync(songs);
} }
} }

View File

@ -24,6 +24,7 @@
#include <QObject> #include <QObject>
#include <QWidget> #include <QWidget>
#include <QMap>
#include <QString> #include <QString>
#include "core/song.h" #include "core/song.h"
@ -51,7 +52,7 @@ class InternetSongsView : public QWidget {
void OpenSettingsDialog(); void OpenSettingsDialog();
void GetSongs(); void GetSongs();
void AbortGetSongs(); void AbortGetSongs();
void SongsFinished(const SongList &songs, const QString &error); void SongsFinished(const SongMap &songs, const QString &error);
private: private:
Application *app_; Application *app_;

View File

@ -21,6 +21,7 @@
#include <QtGlobal> #include <QtGlobal>
#include <QWidget> #include <QWidget>
#include <QMap>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
#include <QLabel> #include <QLabel>
@ -137,7 +138,7 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, c
ui_->songs_collection->filter_widget()->AddMenuAction(action_configure); ui_->songs_collection->filter_widget()->AddMenuAction(action_configure);
QObject::connect(ui_->songs_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetSongs); QObject::connect(ui_->songs_collection->view(), &InternetCollectionView::GetSongs, this, &InternetTabsView::GetSongs);
QObject::connect(ui_->songs_collection->view(), &InternetCollectionView::RemoveSongs, service_, &InternetService::RemoveSongs); QObject::connect(ui_->songs_collection->view(), &InternetCollectionView::RemoveSongs, service_, QOverload<SongList>::of(&InternetService::RemoveSongs));
QObject::connect(ui_->songs_collection->button_refresh(), &QPushButton::clicked, this, &InternetTabsView::GetSongs); QObject::connect(ui_->songs_collection->button_refresh(), &QPushButton::clicked, this, &InternetTabsView::GetSongs);
QObject::connect(ui_->songs_collection->button_close(), &QPushButton::clicked, this, &InternetTabsView::AbortGetSongs); QObject::connect(ui_->songs_collection->button_close(), &QPushButton::clicked, this, &InternetTabsView::AbortGetSongs);
@ -230,7 +231,7 @@ void InternetTabsView::AbortGetArtists() {
} }
void InternetTabsView::ArtistsFinished(const SongList &songs, const QString &error) { void InternetTabsView::ArtistsFinished(const SongMap &songs, const QString &error) {
if (songs.isEmpty() && !error.isEmpty()) { if (songs.isEmpty() && !error.isEmpty()) {
ui_->artists_collection->status()->setText(error); ui_->artists_collection->status()->setText(error);
@ -240,10 +241,9 @@ void InternetTabsView::ArtistsFinished(const SongList &songs, const QString &err
ui_->artists_collection->button_close()->show(); ui_->artists_collection->button_close()->show();
} }
else { else {
service_->artists_collection_backend()->DeleteAll();
ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->internetcollection_page()); ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->internetcollection_page());
ui_->artists_collection->status()->clear(); ui_->artists_collection->status()->clear();
service_->artists_collection_backend()->AddOrUpdateSongsAsync(songs); service_->artists_collection_backend()->UpdateSongsBySongIDAsync(songs);
} }
} }
@ -273,7 +273,7 @@ void InternetTabsView::AbortGetAlbums() {
} }
void InternetTabsView::AlbumsFinished(const SongList &songs, const QString &error) { void InternetTabsView::AlbumsFinished(const SongMap &songs, const QString &error) {
if (songs.isEmpty() && !error.isEmpty()) { if (songs.isEmpty() && !error.isEmpty()) {
ui_->albums_collection->status()->setText(error); ui_->albums_collection->status()->setText(error);
@ -283,10 +283,9 @@ void InternetTabsView::AlbumsFinished(const SongList &songs, const QString &erro
ui_->albums_collection->button_close()->show(); ui_->albums_collection->button_close()->show();
} }
else { else {
service_->albums_collection_backend()->DeleteAll();
ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->internetcollection_page()); ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->internetcollection_page());
ui_->albums_collection->status()->clear(); ui_->albums_collection->status()->clear();
service_->albums_collection_backend()->AddOrUpdateSongsAsync(songs); service_->albums_collection_backend()->UpdateSongsBySongIDAsync(songs);
} }
} }
@ -316,7 +315,7 @@ void InternetTabsView::AbortGetSongs() {
} }
void InternetTabsView::SongsFinished(const SongList &songs, const QString &error) { void InternetTabsView::SongsFinished(const SongMap &songs, const QString &error) {
if (songs.isEmpty() && !error.isEmpty()) { if (songs.isEmpty() && !error.isEmpty()) {
ui_->songs_collection->status()->setText(error); ui_->songs_collection->status()->setText(error);
@ -326,10 +325,9 @@ void InternetTabsView::SongsFinished(const SongList &songs, const QString &error
ui_->songs_collection->button_close()->show(); ui_->songs_collection->button_close()->show();
} }
else { else {
service_->songs_collection_backend()->DeleteAll();
ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->internetcollection_page()); ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->internetcollection_page());
ui_->songs_collection->status()->clear(); ui_->songs_collection->status()->clear();
service_->songs_collection_backend()->AddOrUpdateSongsAsync(songs); service_->songs_collection_backend()->UpdateSongsBySongIDAsync(songs);
} }
} }

View File

@ -24,6 +24,7 @@
#include <QObject> #include <QObject>
#include <QWidget> #include <QWidget>
#include <QMap>
#include <QString> #include <QString>
#include "settings/settingsdialog.h" #include "settings/settingsdialog.h"
@ -60,9 +61,9 @@ class InternetTabsView : public QWidget {
void AbortGetArtists(); void AbortGetArtists();
void AbortGetAlbums(); void AbortGetAlbums();
void AbortGetSongs(); void AbortGetSongs();
void ArtistsFinished(const SongList &songs, const QString &error); void ArtistsFinished(const SongMap &songs, const QString &error);
void AlbumsFinished(const SongList &songs, const QString &error); void AlbumsFinished(const SongMap &songs, const QString &error);
void SongsFinished(const SongList &songs, const QString &error); void SongsFinished(const SongMap &songs, const QString &error);
private: private:
Application *app_; Application *app_;

View File

@ -797,7 +797,7 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, int ro
InsertSmartPlaylist(generator_data->generator_, row, play_now, enqueue_now, enqueue_next_now); InsertSmartPlaylist(generator_data->generator_, row, play_now, enqueue_now, enqueue_next_now);
} }
else if (const InternetSongMimeData *internet_song_data = qobject_cast<const InternetSongMimeData*>(data)) { else if (const InternetSongMimeData *internet_song_data = qobject_cast<const InternetSongMimeData*>(data)) {
InsertInternetItems(internet_song_data->service, internet_song_data->songs, row, play_now, enqueue_now, enqueue_next_now); InsertInternetItems(internet_song_data->service, internet_song_data->songs.values(), row, play_now, enqueue_now, enqueue_next_now);
} }
else if (const RadioMimeData *radio_data = qobject_cast<const RadioMimeData*>(data)) { else if (const RadioMimeData *radio_data = qobject_cast<const RadioMimeData*>(data)) {
InsertRadioItems(radio_data->songs, row, play_now, enqueue_now, enqueue_next_now); InsertRadioItems(radio_data->songs, row, play_now, enqueue_now, enqueue_next_now);

View File

@ -214,7 +214,7 @@ QList<PlaylistItemPtr> PlaylistBackend::GetPlaylistItems(const int playlist) {
} }
QList<Song> PlaylistBackend::GetPlaylistSongs(const int playlist) { SongList PlaylistBackend::GetPlaylistSongs(const int playlist) {
SongList songs; SongList songs;
@ -230,7 +230,7 @@ QList<Song> PlaylistBackend::GetPlaylistSongs(const int playlist) {
q.BindValue(":playlist", playlist); q.BindValue(":playlist", playlist);
if (!q.Exec()) { if (!q.Exec()) {
db_->ReportErrors(q); db_->ReportErrors(q);
return QList<Song>(); return SongList();
} }
// it's probable that we'll have a few songs associated with the same CUE so we're caching results of parsing CUEs // it's probable that we'll have a few songs associated with the same CUE so we're caching results of parsing CUEs

View File

@ -76,7 +76,7 @@ class PlaylistBackend : public QObject {
PlaylistBackend::Playlist GetPlaylist(const int id); PlaylistBackend::Playlist GetPlaylist(const int id);
QList<PlaylistItemPtr> GetPlaylistItems(const int playlist); QList<PlaylistItemPtr> GetPlaylistItems(const int playlist);
QList<Song> GetPlaylistSongs(const int playlist); SongList GetPlaylistSongs(const int playlist);
void SetPlaylistOrder(const QList<int> &ids); void SetPlaylistOrder(const QList<int> &ids);
void SetPlaylistUiPath(const int id, const QString &path); void SetPlaylistUiPath(const int id, const QString &path);

View File

@ -21,6 +21,7 @@
#include <QObject> #include <QObject>
#include <QPair> #include <QPair>
#include <QMap>
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
@ -66,6 +67,24 @@ QString QobuzFavoriteRequest::FavoriteText(const FavoriteType type) {
} }
QString QobuzFavoriteRequest::FavoriteMethod(const FavoriteType type) {
switch (type) {
case FavoriteType_Artists:
return "artist_ids";
break;
case FavoriteType_Albums:
return "album_ids";
break;
case FavoriteType_Songs:
return "track_ids";
break;
}
return QString();
}
void QobuzFavoriteRequest::AddArtists(const SongList &songs) { void QobuzFavoriteRequest::AddArtists(const SongList &songs) {
AddFavorites(FavoriteType_Artists, songs); AddFavorites(FavoriteType_Artists, songs);
} }
@ -74,27 +93,12 @@ void QobuzFavoriteRequest::AddAlbums(const SongList &songs) {
AddFavorites(FavoriteType_Albums, songs); AddFavorites(FavoriteType_Albums, songs);
} }
void QobuzFavoriteRequest::AddSongs(const SongList &songs) { void QobuzFavoriteRequest::AddSongs(const SongMap &songs) {
AddFavorites(FavoriteType_Songs, songs); AddFavoritesRequest(FavoriteType_Songs, songs.keys(), songs.values());
} }
void QobuzFavoriteRequest::AddFavorites(const FavoriteType type, const SongList &songs) { void QobuzFavoriteRequest::AddFavorites(const FavoriteType type, const SongList &songs) {
if (songs.isEmpty()) return;
QString text;
switch (type) {
case FavoriteType_Artists:
text = "artist_ids";
break;
case FavoriteType_Albums:
text = "album_ids";
break;
case FavoriteType_Songs:
text = "track_ids";
break;
}
QStringList ids_list; QStringList ids_list;
for (const Song &song : songs) { for (const Song &song : songs) {
QString id; QString id;
@ -112,18 +116,22 @@ void QobuzFavoriteRequest::AddFavorites(const FavoriteType type, const SongList
id = song.song_id(); id = song.song_id();
break; break;
} }
if (id.isEmpty()) continue; if (!id.isEmpty() && !ids_list.contains(id)) {
if (!ids_list.contains(id)) {
ids_list << id; ids_list << id;
} }
} }
if (ids_list.isEmpty()) return; if (ids_list.isEmpty()) return;
QString ids = ids_list.join(','); AddFavoritesRequest(type, ids_list, songs);
}
void QobuzFavoriteRequest::AddFavoritesRequest(const FavoriteType type, const QStringList &ids_list, const SongList &songs) {
ParamList params = ParamList() << Param("app_id", app_id()) ParamList params = ParamList() << Param("app_id", app_id())
<< Param("user_auth_token", user_auth_token()) << Param("user_auth_token", user_auth_token())
<< Param(text, ids); << Param(FavoriteMethod(type), ids_list.join(','));
QUrlQuery url_query; QUrlQuery url_query;
for (const Param &param : params) { for (const Param &param : params) {
@ -180,23 +188,12 @@ void QobuzFavoriteRequest::RemoveSongs(const SongList &songs) {
RemoveFavorites(FavoriteType_Songs, songs); RemoveFavorites(FavoriteType_Songs, songs);
} }
void QobuzFavoriteRequest::RemoveSongs(const SongMap &songs) {
RemoveFavoritesRequest(FavoriteType_Songs, songs.keys(), songs.values());
}
void QobuzFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongList &songs) { void QobuzFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongList &songs) {
if (songs.isEmpty()) return;
QString text;
switch (type) {
case FavoriteType_Artists:
text = "artist_ids";
break;
case FavoriteType_Albums:
text = "album_ids";
break;
case FavoriteType_Songs:
text = "track_ids";
break;
}
QStringList ids_list; QStringList ids_list;
for (const Song &song : songs) { for (const Song &song : songs) {
QString id; QString id;
@ -214,18 +211,22 @@ void QobuzFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongLi
id = song.song_id(); id = song.song_id();
break; break;
} }
if (id.isEmpty()) continue; if (!id.isEmpty() && !ids_list.contains(id)) {
if (!ids_list.contains(id)) {
ids_list << id; ids_list << id;
} }
} }
if (ids_list.isEmpty()) return; if (ids_list.isEmpty()) return;
QString ids = ids_list.join(','); RemoveFavoritesRequest(type, ids_list, songs);
}
void QobuzFavoriteRequest::RemoveFavoritesRequest(const FavoriteType type, const QStringList &ids_list, const SongList &songs) {
ParamList params = ParamList() << Param("app_id", app_id()) ParamList params = ParamList() << Param("app_id", app_id())
<< Param("user_auth_token", user_auth_token()) << Param("user_auth_token", user_auth_token())
<< Param(text, ids); << Param(FavoriteMethod(type), ids_list.join(','));
QUrlQuery url_query; QUrlQuery url_query;
for (const Param &param : params) { for (const Param &param : params) {

View File

@ -24,6 +24,7 @@
#include <QObject> #include <QObject>
#include <QList> #include <QList>
#include <QMap>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
@ -62,16 +63,20 @@ class QobuzFavoriteRequest : public QobuzBaseRequest {
public slots: public slots:
void AddArtists(const SongList &songs); void AddArtists(const SongList &songs);
void AddAlbums(const SongList &songs); void AddAlbums(const SongList &songs);
void AddSongs(const SongList &songs); void AddSongs(const SongMap &songs);
void RemoveArtists(const SongList &songs); void RemoveArtists(const SongList &songs);
void RemoveAlbums(const SongList &songs); void RemoveAlbums(const SongList &songs);
void RemoveSongs(const SongList &songs); void RemoveSongs(const SongList &songs);
void RemoveSongs(const SongMap &songs);
private: private:
void Error(const QString &error, const QVariant &debug = QVariant()); void Error(const QString &error, const QVariant &debug = QVariant());
static QString FavoriteText(const FavoriteType type); static QString FavoriteText(const FavoriteType type);
static QString FavoriteMethod(const FavoriteType type);
void AddFavorites(const FavoriteType type, const SongList &songs); void AddFavorites(const FavoriteType type, const SongList &songs);
void AddFavoritesRequest(const FavoriteType type, const QStringList &ids_list, const SongList &songs);
void RemoveFavorites(const FavoriteType type, const SongList &songs); void RemoveFavorites(const FavoriteType type, const SongList &songs);
void RemoveFavoritesRequest(const FavoriteType type, const QStringList &ids_list, const SongList &songs);
QobuzService *service_; QobuzService *service_;
NetworkAccessManager *network_; NetworkAccessManager *network_;

View File

@ -1315,15 +1315,15 @@ void QobuzRequest::FinishCheck() {
finished_ = true; finished_ = true;
if (no_results_ && songs_.isEmpty()) { if (no_results_ && songs_.isEmpty()) {
if (IsSearch()) if (IsSearch())
emit Results(query_id_, SongList(), tr("No match.")); emit Results(query_id_, SongMap(), tr("No match."));
else else
emit Results(query_id_, SongList(), QString()); emit Results(query_id_, SongMap(), QString());
} }
else { else {
if (songs_.isEmpty() && errors_.isEmpty()) if (songs_.isEmpty() && errors_.isEmpty())
emit Results(query_id_, songs_.values(), tr("Unknown error")); emit Results(query_id_, songs_, tr("Unknown error"));
else else
emit Results(query_id_, songs_.values(), ErrorsToHTML(errors_)); emit Results(query_id_, songs_, ErrorsToHTML(errors_));
} }
} }

View File

@ -62,7 +62,7 @@ class QobuzRequest : public QobuzBaseRequest {
signals: signals:
void LoginSuccess(); void LoginSuccess();
void LoginFailure(QString failure_reason); void LoginFailure(QString failure_reason);
void Results(int id, SongList songs, QString error); void Results(int id, SongMap songs, QString error);
void UpdateStatus(int id, QString text); void UpdateStatus(int id, QString text);
void ProgressSetMaximum(int id, int max); void ProgressSetMaximum(int id, int max);
void UpdateProgress(int id, int max); void UpdateProgress(int id, int max);
@ -191,7 +191,7 @@ class QobuzRequest : public QobuzBaseRequest {
int album_covers_requested_; int album_covers_requested_;
int album_covers_received_; int album_covers_received_;
QMap<QString, Song> songs_; SongMap songs_;
QStringList errors_; QStringList errors_;
bool no_results_; bool no_results_;
QList<QNetworkReply*> replies_; QList<QNetworkReply*> replies_;

View File

@ -25,6 +25,7 @@
#include <QByteArray> #include <QByteArray>
#include <QPair> #include <QPair>
#include <QList> #include <QList>
#include <QMap>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include <QUrlQuery> #include <QUrlQuery>
@ -111,15 +112,15 @@ QobuzService::QobuzService(Application *app, QObject *parent)
artists_collection_backend_ = new CollectionBackend(); artists_collection_backend_ = new CollectionBackend();
artists_collection_backend_->moveToThread(app_->database()->thread()); artists_collection_backend_->moveToThread(app_->database()->thread());
artists_collection_backend_->Init(app_->database(), Song::Source_Qobuz, kArtistsSongsTable, kArtistsSongsFtsTable); artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Qobuz, kArtistsSongsTable, kArtistsSongsFtsTable);
albums_collection_backend_ = new CollectionBackend(); albums_collection_backend_ = new CollectionBackend();
albums_collection_backend_->moveToThread(app_->database()->thread()); albums_collection_backend_->moveToThread(app_->database()->thread());
albums_collection_backend_->Init(app_->database(), Song::Source_Qobuz, kAlbumsSongsTable, kAlbumsSongsFtsTable); albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Qobuz, kAlbumsSongsTable, kAlbumsSongsFtsTable);
songs_collection_backend_ = new CollectionBackend(); songs_collection_backend_ = new CollectionBackend();
songs_collection_backend_->moveToThread(app_->database()->thread()); songs_collection_backend_->moveToThread(app_->database()->thread());
songs_collection_backend_->Init(app_->database(), Song::Source_Qobuz, kSongsTable, kSongsFtsTable); songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Qobuz, kSongsTable, kSongsFtsTable);
artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this); artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this);
albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this); albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this);
@ -160,7 +161,8 @@ QobuzService::QobuzService(Application *app, QObject *parent)
QObject::connect(this, &QobuzService::RemoveArtists, favorite_request_, &QobuzFavoriteRequest::RemoveArtists); QObject::connect(this, &QobuzService::RemoveArtists, favorite_request_, &QobuzFavoriteRequest::RemoveArtists);
QObject::connect(this, &QobuzService::RemoveAlbums, favorite_request_, &QobuzFavoriteRequest::RemoveAlbums); QObject::connect(this, &QobuzService::RemoveAlbums, favorite_request_, &QobuzFavoriteRequest::RemoveAlbums);
QObject::connect(this, &QobuzService::RemoveSongs, favorite_request_, &QobuzFavoriteRequest::RemoveSongs); QObject::connect(this, QOverload<SongList>::of(&QobuzService::RemoveSongs), favorite_request_, QOverload<const SongList&>::of(&QobuzFavoriteRequest::RemoveSongs));
QObject::connect(this, QOverload<SongMap>::of(&QobuzService::RemoveSongs), favorite_request_, QOverload<const SongMap&>::of(&QobuzFavoriteRequest::RemoveSongs));
QObject::connect(favorite_request_, &QobuzFavoriteRequest::ArtistsAdded, artists_collection_backend_, &CollectionBackend::AddOrUpdateSongs); QObject::connect(favorite_request_, &QobuzFavoriteRequest::ArtistsAdded, artists_collection_backend_, &CollectionBackend::AddOrUpdateSongs);
QObject::connect(favorite_request_, &QobuzFavoriteRequest::AlbumsAdded, albums_collection_backend_, &CollectionBackend::AddOrUpdateSongs); QObject::connect(favorite_request_, &QobuzFavoriteRequest::AlbumsAdded, albums_collection_backend_, &CollectionBackend::AddOrUpdateSongs);
@ -285,7 +287,7 @@ void QobuzService::SendLoginWithCredentials(const QString &app_id, const QString
QObject::connect(reply, &QNetworkReply::sslErrors, this, &QobuzService::HandleLoginSSLErrors); QObject::connect(reply, &QNetworkReply::sslErrors, this, &QobuzService::HandleLoginSSLErrors);
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply]() { HandleAuthReply(reply); }); QObject::connect(reply, &QNetworkReply::finished, this, [this, reply]() { HandleAuthReply(reply); });
qLog(Debug) << "Qobuz: Sending request" << url << query; //qLog(Debug) << "Qobuz: Sending request" << url << query;
} }
@ -496,12 +498,12 @@ void QobuzService::ResetArtistsRequest() {
void QobuzService::GetArtists() { void QobuzService::GetArtists() {
if (app_id().isEmpty()) { if (app_id().isEmpty()) {
emit ArtistsResults(SongList(), tr("Missing Qobuz app ID.")); emit ArtistsResults(SongMap(), tr("Missing Qobuz app ID."));
return; return;
} }
if (!authenticated()) { if (!authenticated()) {
emit ArtistsResults(SongList(), tr("Not authenticated with Qobuz.")); emit ArtistsResults(SongMap(), tr("Not authenticated with Qobuz."));
return; return;
} }
@ -518,7 +520,7 @@ void QobuzService::GetArtists() {
} }
void QobuzService::ArtistsResultsReceived(const int id, const SongList &songs, const QString &error) { void QobuzService::ArtistsResultsReceived(const int id, const SongMap &songs, const QString &error) {
Q_UNUSED(id); Q_UNUSED(id);
emit ArtistsResults(songs, error); emit ArtistsResults(songs, error);
} }
@ -551,12 +553,12 @@ void QobuzService::ResetAlbumsRequest() {
void QobuzService::GetAlbums() { void QobuzService::GetAlbums() {
if (app_id().isEmpty()) { if (app_id().isEmpty()) {
emit AlbumsResults(SongList(), tr("Missing Qobuz app ID.")); emit AlbumsResults(SongMap(), tr("Missing Qobuz app ID."));
return; return;
} }
if (!authenticated()) { if (!authenticated()) {
emit AlbumsResults(SongList(), tr("Not authenticated with Qobuz.")); emit AlbumsResults(SongMap(), tr("Not authenticated with Qobuz."));
return; return;
} }
@ -571,7 +573,7 @@ void QobuzService::GetAlbums() {
} }
void QobuzService::AlbumsResultsReceived(const int id, const SongList &songs, const QString &error) { void QobuzService::AlbumsResultsReceived(const int id, const SongMap &songs, const QString &error) {
Q_UNUSED(id); Q_UNUSED(id);
emit AlbumsResults(songs, error); emit AlbumsResults(songs, error);
} }
@ -604,12 +606,12 @@ void QobuzService::ResetSongsRequest() {
void QobuzService::GetSongs() { void QobuzService::GetSongs() {
if (app_id().isEmpty()) { if (app_id().isEmpty()) {
emit SongsResults(SongList(), tr("Missing Qobuz app ID.")); emit SongsResults(SongMap(), tr("Missing Qobuz app ID."));
return; return;
} }
if (!authenticated()) { if (!authenticated()) {
emit SongsResults(SongList(), tr("Not authenticated with Qobuz.")); emit SongsResults(SongMap(), tr("Not authenticated with Qobuz."));
return; return;
} }
@ -624,7 +626,7 @@ void QobuzService::GetSongs() {
} }
void QobuzService::SongsResultsReceived(const int id, const SongList &songs, const QString &error) { void QobuzService::SongsResultsReceived(const int id, const SongMap &songs, const QString &error) {
Q_UNUSED(id); Q_UNUSED(id);
emit SongsResults(songs, error); emit SongsResults(songs, error);
} }
@ -669,7 +671,7 @@ void QobuzService::StartSearch() {
search_text_ = pending_search_text_; search_text_ = pending_search_text_;
if (app_id_.isEmpty()) { // App ID is the only thing needed to search. if (app_id_.isEmpty()) { // App ID is the only thing needed to search.
emit SearchResults(search_id_, SongList(), tr("Missing Qobuz app ID.")); emit SearchResults(search_id_, SongMap(), tr("Missing Qobuz app ID."));
return; return;
} }
@ -708,7 +710,7 @@ void QobuzService::SendSearch() {
} }
void QobuzService::SearchResultsReceived(const int id, const SongList &songs, const QString &error) { void QobuzService::SearchResultsReceived(const int id, const SongMap &songs, const QString &error) {
emit SearchResults(id, songs, error); emit SearchResults(id, songs, error);
} }

View File

@ -125,10 +125,10 @@ class QobuzService : public InternetService {
void HandleAuthReply(QNetworkReply *reply); void HandleAuthReply(QNetworkReply *reply);
void ResetLoginAttempts(); void ResetLoginAttempts();
void StartSearch(); void StartSearch();
void ArtistsResultsReceived(const int id, const SongList &songs, const QString &error); void ArtistsResultsReceived(const int id, const SongMap &songs, const QString &error);
void AlbumsResultsReceived(const int id, const SongList &songs, const QString &error); void AlbumsResultsReceived(const int id, const SongMap &songs, const QString &error);
void SongsResultsReceived(const int id, const SongList &songs, const QString &error); void SongsResultsReceived(const int id, const SongMap &songs, const QString &error);
void SearchResultsReceived(const int id, const SongList &songs, const QString &error); void SearchResultsReceived(const int id, const SongMap &songs, const QString &error);
void ArtistsUpdateStatusReceived(const int id, const QString &text); void ArtistsUpdateStatusReceived(const int id, const QString &text);
void AlbumsUpdateStatusReceived(const int id, const QString &text); void AlbumsUpdateStatusReceived(const int id, const QString &text);
void SongsUpdateStatusReceived(const int id, const QString &text); void SongsUpdateStatusReceived(const int id, const QString &text);

View File

@ -151,7 +151,7 @@ void SomaFMService::GetStreamUrlsReply(QNetworkReply *reply, const int task_id,
reply->deleteLater(); reply->deleteLater();
PlaylistParser parser; PlaylistParser parser;
QList<Song> songs = parser.LoadFromDevice(reply); SongList songs = parser.LoadFromDevice(reply);
if (!songs.isEmpty()) { if (!songs.isEmpty()) {
channel.url = songs.first().url(); channel.url = songs.first().url();
} }

View File

@ -872,14 +872,14 @@ void SubsonicRequest::FinishCheck() {
) { ) {
finished_ = true; finished_ = true;
if (no_results_ && songs_.isEmpty()) { if (no_results_ && songs_.isEmpty()) {
emit Results(SongList(), QString()); emit Results(SongMap(), QString());
} }
else { else {
if (songs_.isEmpty() && errors_.isEmpty()) { if (songs_.isEmpty() && errors_.isEmpty()) {
emit Results(songs_.values(), tr("Unknown error")); emit Results(songs_, tr("Unknown error"));
} }
else { else {
emit Results(songs_.values(), ErrorsToHTML(errors_)); emit Results(songs_, ErrorsToHTML(errors_));
} }
} }

View File

@ -62,7 +62,7 @@ class SubsonicRequest : public SubsonicBaseRequest {
void Reset(); void Reset();
signals: signals:
void Results(SongList songs, QString error); void Results(SongMap songs, QString error);
void UpdateStatus(QString text); void UpdateStatus(QString text);
void ProgressSetMaximum(int max); void ProgressSetMaximum(int max);
void UpdateProgress(int max); void UpdateProgress(int max);
@ -140,7 +140,7 @@ class SubsonicRequest : public SubsonicBaseRequest {
int album_covers_requested_; int album_covers_requested_;
int album_covers_received_; int album_covers_received_;
QMap<QString, Song> songs_; SongMap songs_;
QStringList errors_; QStringList errors_;
bool no_results_; bool no_results_;
QList<QNetworkReply*> replies_; QList<QNetworkReply*> replies_;

View File

@ -26,6 +26,7 @@
#include <QByteArray> #include <QByteArray>
#include <QPair> #include <QPair>
#include <QList> #include <QList>
#include <QMap>
#include <QString> #include <QString>
#include <QVariant> #include <QVariant>
#include <QUrl> #include <QUrl>
@ -86,7 +87,7 @@ SubsonicService::SubsonicService(Application *app, QObject *parent)
collection_backend_ = new CollectionBackend(); collection_backend_ = new CollectionBackend();
collection_backend_->moveToThread(app_->database()->thread()); collection_backend_->moveToThread(app_->database()->thread());
collection_backend_->Init(app_->database(), Song::Source_Subsonic, kSongsTable, kSongsFtsTable); collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Subsonic, kSongsTable, kSongsFtsTable);
// Model // Model
@ -425,12 +426,12 @@ void SubsonicService::ResetSongsRequest() {
void SubsonicService::GetSongs() { void SubsonicService::GetSongs() {
if (!server_url().isValid()) { if (!server_url().isValid()) {
emit SongsResults(SongList(), tr("Server URL is invalid.")); emit SongsResults(SongMap(), tr("Server URL is invalid."));
return; return;
} }
if (username().isEmpty() || password().isEmpty()) { if (username().isEmpty() || password().isEmpty()) {
emit SongsResults(SongList(), tr("Missing username or password.")); emit SongsResults(SongMap(), tr("Missing username or password."));
return; return;
} }
@ -445,7 +446,7 @@ void SubsonicService::GetSongs() {
} }
void SubsonicService::SongsResultsReceived(const SongList &songs, const QString &error) { void SubsonicService::SongsResultsReceived(const SongMap &songs, const QString &error) {
emit SongsResults(songs, error); emit SongsResults(songs, error);

View File

@ -28,6 +28,7 @@
#include <QPair> #include <QPair>
#include <QSet> #include <QSet>
#include <QList> #include <QList>
#include <QMap>
#include <QVariant> #include <QVariant>
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
@ -96,7 +97,7 @@ class SubsonicService : public InternetService {
private slots: private slots:
void HandlePingSSLErrors(const QList<QSslError> &ssl_errors); void HandlePingSSLErrors(const QList<QSslError> &ssl_errors);
void HandlePingReply(QNetworkReply *reply, const QUrl &url, const QString &username, const QString &password, const SubsonicSettingsPage::AuthMethod auth_method); void HandlePingReply(QNetworkReply *reply, const QUrl &url, const QString &username, const QString &password, const SubsonicSettingsPage::AuthMethod auth_method);
void SongsResultsReceived(const SongList &songs, const QString &error); void SongsResultsReceived(const SongMap &songs, const QString &error);
private: private:
typedef QPair<QString, QString> Param; typedef QPair<QString, QString> Param;

View File

@ -66,10 +66,26 @@ QString TidalFavoriteRequest::FavoriteText(const FavoriteType type) {
case FavoriteType_Albums: case FavoriteType_Albums:
return "albums"; return "albums";
case FavoriteType_Songs: case FavoriteType_Songs:
default:
return "tracks"; return "tracks";
} }
return QString();
}
QString TidalFavoriteRequest::FavoriteMethod(const FavoriteType type) {
switch (type) {
case FavoriteType_Artists:
return "artistIds";
case FavoriteType_Albums:
return "albumIds";
case FavoriteType_Songs:
return "trackIds";
}
return QString();
} }
void TidalFavoriteRequest::AddArtists(const SongList &songs) { void TidalFavoriteRequest::AddArtists(const SongList &songs) {
@ -80,27 +96,12 @@ void TidalFavoriteRequest::AddAlbums(const SongList &songs) {
AddFavorites(FavoriteType_Albums, songs); AddFavorites(FavoriteType_Albums, songs);
} }
void TidalFavoriteRequest::AddSongs(const SongList &songs) { void TidalFavoriteRequest::AddSongs(const SongMap &songs) {
AddFavorites(FavoriteType_Songs, songs); AddFavoritesRequest(FavoriteType_Songs, songs.keys(), songs.values());
} }
void TidalFavoriteRequest::AddFavorites(const FavoriteType type, const SongList &songs) { void TidalFavoriteRequest::AddFavorites(const FavoriteType type, const SongList &songs) {
if (songs.isEmpty()) return;
QString text;
switch (type) {
case FavoriteType_Artists:
text = "artistIds";
break;
case FavoriteType_Albums:
text = "albumIds";
break;
case FavoriteType_Songs:
text = "trackIds";
break;
}
QStringList id_list; QStringList id_list;
for (const Song &song : songs) { for (const Song &song : songs) {
QString id; QString id;
@ -118,15 +119,21 @@ void TidalFavoriteRequest::AddFavorites(const FavoriteType type, const SongList
id = song.song_id(); id = song.song_id();
break; break;
} }
if (id.isEmpty()) continue; if (!id.isEmpty() && !id_list.contains(id)) {
if (!id_list.contains(id)) {
id_list << id; id_list << id;
} }
} }
if (id_list.isEmpty()) return; if (id_list.isEmpty()) return;
AddFavoritesRequest(type, id_list, songs);
}
void TidalFavoriteRequest::AddFavoritesRequest(const FavoriteType type, const QStringList &id_list, const SongList &songs) {
ParamList params = ParamList() << Param("countryCode", country_code()) ParamList params = ParamList() << Param("countryCode", country_code())
<< Param(text, id_list.join(',')); << Param(FavoriteMethod(type), id_list.join(','));
QUrlQuery url_query; QUrlQuery url_query;
for (const Param &param : params) { for (const Param &param : params) {
@ -197,11 +204,17 @@ void TidalFavoriteRequest::RemoveSongs(const SongList &songs) {
RemoveFavorites(FavoriteType_Songs, songs); RemoveFavorites(FavoriteType_Songs, songs);
} }
void TidalFavoriteRequest::RemoveSongs(const SongMap &songs) {
SongList songs_list = songs.values();
for (const Song &song : songs_list) {
RemoveFavoritesRequest(FavoriteType_Songs, song.song_id(), SongList() << song);
}
}
void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongList &songs) { void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongList &songs) {
if (songs.isEmpty()) return;
QStringList ids;
QMultiMap<QString, Song> songs_map; QMultiMap<QString, Song> songs_map;
for (const Song &song : songs) { for (const Song &song : songs) {
QString id; QString id;
@ -219,18 +232,19 @@ void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongLi
id = song.song_id(); id = song.song_id();
break; break;
} }
if (!ids.contains(id)) ids << id; if (!id.isEmpty()) {
songs_map.insert(id, song); songs_map.insert(id, song);
}
} }
QStringList ids = songs_map.uniqueKeys();
for (const QString &id : ids) { for (const QString &id : ids) {
SongList songs_list = songs_map.values(id); RemoveFavoritesRequest(type, id, songs_map.values(id));
RemoveFavorites(type, id, songs_list);
} }
} }
void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const QString &id, const SongList &songs) { void TidalFavoriteRequest::RemoveFavoritesRequest(const FavoriteType type, const QString &id, const SongList &songs) {
ParamList params = ParamList() << Param("countryCode", country_code()); ParamList params = ParamList() << Param("countryCode", country_code());

View File

@ -24,6 +24,7 @@
#include <QObject> #include <QObject>
#include <QList> #include <QList>
#include <QMap>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
@ -65,18 +66,22 @@ class TidalFavoriteRequest : public TidalBaseRequest {
public slots: public slots:
void AddArtists(const SongList &songs); void AddArtists(const SongList &songs);
void AddAlbums(const SongList &songs); void AddAlbums(const SongList &songs);
void AddSongs(const SongList &songs); void AddSongs(const SongMap &songs);
void RemoveArtists(const SongList &songs); void RemoveArtists(const SongList &songs);
void RemoveAlbums(const SongList &songs); void RemoveAlbums(const SongList &songs);
void RemoveSongs(const SongList &songs); void RemoveSongs(const SongList &songs);
void RemoveSongs(const SongMap &songs);
private: private:
void Error(const QString &error, const QVariant &debug = QVariant()) override; void Error(const QString &error, const QVariant &debug = QVariant()) override;
static QString FavoriteText(const FavoriteType type); static QString FavoriteText(const FavoriteType type);
static QString FavoriteMethod(const FavoriteType type);
void AddFavorites(const FavoriteType type, const SongList &songs); void AddFavorites(const FavoriteType type, const SongList &songs);
void AddFavoritesRequest(const FavoriteType type, const QStringList &id_list, const SongList &songs);
void RemoveFavorites(const FavoriteType type, const SongList &songs); void RemoveFavorites(const FavoriteType type, const SongList &songs);
void RemoveFavorites(const FavoriteType type, const QString &id, const SongList &songs); void RemoveFavorites(const FavoriteType type, const QString &id, const SongList &songs);
void RemoveFavoritesRequest(const FavoriteType type, const QString &id, const SongList &songs);
TidalService *service_; TidalService *service_;
NetworkAccessManager *network_; NetworkAccessManager *network_;

View File

@ -1270,15 +1270,17 @@ void TidalRequest::FinishCheck() {
finished_ = true; finished_ = true;
if (no_results_ && songs_.isEmpty()) { if (no_results_ && songs_.isEmpty()) {
if (IsSearch()) if (IsSearch())
emit Results(query_id_, SongList(), tr("No match.")); emit Results(query_id_, SongMap(), tr("No match."));
else else
emit Results(query_id_, SongList(), QString()); emit Results(query_id_, SongMap(), QString());
} }
else { else {
if (songs_.isEmpty() && errors_.isEmpty()) if (songs_.isEmpty() && errors_.isEmpty()) {
emit Results(query_id_, songs_.values(), tr("Unknown error")); emit Results(query_id_, songs_, tr("Unknown error"));
else }
emit Results(query_id_, songs_.values(), ErrorsToHTML(errors_)); else {
emit Results(query_id_, songs_, ErrorsToHTML(errors_));
}
} }
} }

View File

@ -81,7 +81,7 @@ class TidalRequest : public TidalBaseRequest {
signals: signals:
void LoginSuccess(); void LoginSuccess();
void LoginFailure(QString failure_reason); void LoginFailure(QString failure_reason);
void Results(int id, SongList songs, QString error); void Results(int id, SongMap songs, QString error);
void UpdateStatus(int id, QString text); void UpdateStatus(int id, QString text);
void ProgressSetMaximum(int id, int max); void ProgressSetMaximum(int id, int max);
void UpdateProgress(int id, int max); void UpdateProgress(int id, int max);
@ -199,7 +199,7 @@ class TidalRequest : public TidalBaseRequest {
int album_covers_requested_; int album_covers_requested_;
int album_covers_received_; int album_covers_received_;
QMap<QString, Song> songs_; SongMap songs_;
QStringList errors_; QStringList errors_;
bool need_login_; bool need_login_;
bool no_results_; bool no_results_;

View File

@ -28,6 +28,7 @@
#include <QByteArray> #include <QByteArray>
#include <QPair> #include <QPair>
#include <QList> #include <QList>
#include <QMap>
#include <QString> #include <QString>
#include <QChar> #include <QChar>
#include <QUrl> #include <QUrl>
@ -127,15 +128,15 @@ TidalService::TidalService(Application *app, QObject *parent)
artists_collection_backend_ = new CollectionBackend(); artists_collection_backend_ = new CollectionBackend();
artists_collection_backend_->moveToThread(app_->database()->thread()); artists_collection_backend_->moveToThread(app_->database()->thread());
artists_collection_backend_->Init(app_->database(), Song::Source_Tidal, kArtistsSongsTable, kArtistsSongsFtsTable); artists_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Tidal, kArtistsSongsTable, kArtistsSongsFtsTable);
albums_collection_backend_ = new CollectionBackend(); albums_collection_backend_ = new CollectionBackend();
albums_collection_backend_->moveToThread(app_->database()->thread()); albums_collection_backend_->moveToThread(app_->database()->thread());
albums_collection_backend_->Init(app_->database(), Song::Source_Tidal, kAlbumsSongsTable, kAlbumsSongsFtsTable); albums_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Tidal, kAlbumsSongsTable, kAlbumsSongsFtsTable);
songs_collection_backend_ = new CollectionBackend(); songs_collection_backend_ = new CollectionBackend();
songs_collection_backend_->moveToThread(app_->database()->thread()); songs_collection_backend_->moveToThread(app_->database()->thread());
songs_collection_backend_->Init(app_->database(), Song::Source_Tidal, kSongsTable, kSongsFtsTable); songs_collection_backend_->Init(app_->database(), app->task_manager(), Song::Source_Tidal, kSongsTable, kSongsFtsTable);
artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this); artists_collection_model_ = new CollectionModel(artists_collection_backend_, app_, this);
albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this); albums_collection_model_ = new CollectionModel(albums_collection_backend_, app_, this);
@ -180,7 +181,8 @@ TidalService::TidalService(Application *app, QObject *parent)
QObject::connect(this, &TidalService::RemoveArtists, favorite_request_, &TidalFavoriteRequest::RemoveArtists); QObject::connect(this, &TidalService::RemoveArtists, favorite_request_, &TidalFavoriteRequest::RemoveArtists);
QObject::connect(this, &TidalService::RemoveAlbums, favorite_request_, &TidalFavoriteRequest::RemoveAlbums); QObject::connect(this, &TidalService::RemoveAlbums, favorite_request_, &TidalFavoriteRequest::RemoveAlbums);
QObject::connect(this, &TidalService::RemoveSongs, favorite_request_, &TidalFavoriteRequest::RemoveSongs); QObject::connect(this, QOverload<SongList>::of(&TidalService::RemoveSongs), favorite_request_, QOverload<const SongList&>::of(&TidalFavoriteRequest::RemoveSongs));
QObject::connect(this, QOverload<SongMap>::of(&TidalService::RemoveSongs), favorite_request_, QOverload<const SongMap&>::of(&TidalFavoriteRequest::RemoveSongs));
QObject::connect(favorite_request_, &TidalFavoriteRequest::RequestLogin, this, &TidalService::SendLogin); QObject::connect(favorite_request_, &TidalFavoriteRequest::RequestLogin, this, &TidalService::SendLogin);
@ -743,12 +745,12 @@ void TidalService::GetArtists() {
if (!authenticated()) { if (!authenticated()) {
if (oauth_) { if (oauth_) {
emit ArtistsResults(SongList(), tr("Not authenticated with Tidal.")); emit ArtistsResults(SongMap(), tr("Not authenticated with Tidal."));
ShowConfig(); ShowConfig();
return; return;
} }
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) { else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
emit ArtistsResults(SongList(), tr("Missing Tidal API token, username or password.")); emit ArtistsResults(SongMap(), tr("Missing Tidal API token, username or password."));
ShowConfig(); ShowConfig();
return; return;
} }
@ -769,7 +771,7 @@ void TidalService::GetArtists() {
} }
void TidalService::ArtistsResultsReceived(const int id, const SongList &songs, const QString &error) { void TidalService::ArtistsResultsReceived(const int id, const SongMap &songs, const QString &error) {
Q_UNUSED(id); Q_UNUSED(id);
emit ArtistsResults(songs, error); emit ArtistsResults(songs, error);
} }
@ -803,12 +805,12 @@ void TidalService::GetAlbums() {
if (!authenticated()) { if (!authenticated()) {
if (oauth_) { if (oauth_) {
emit AlbumsResults(SongList(), tr("Not authenticated with Tidal.")); emit AlbumsResults(SongMap(), tr("Not authenticated with Tidal."));
ShowConfig(); ShowConfig();
return; return;
} }
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) { else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
emit AlbumsResults(SongList(), tr("Missing Tidal API token, username or password.")); emit AlbumsResults(SongMap(), tr("Missing Tidal API token, username or password."));
ShowConfig(); ShowConfig();
return; return;
} }
@ -827,7 +829,7 @@ void TidalService::GetAlbums() {
} }
void TidalService::AlbumsResultsReceived(const int id, const SongList &songs, const QString &error) { void TidalService::AlbumsResultsReceived(const int id, const SongMap &songs, const QString &error) {
Q_UNUSED(id); Q_UNUSED(id);
emit AlbumsResults(songs, error); emit AlbumsResults(songs, error);
} }
@ -861,12 +863,12 @@ void TidalService::GetSongs() {
if (!authenticated()) { if (!authenticated()) {
if (oauth_) { if (oauth_) {
emit SongsResults(SongList(), tr("Not authenticated with Tidal.")); emit SongsResults(SongMap(), tr("Not authenticated with Tidal."));
ShowConfig(); ShowConfig();
return; return;
} }
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) { else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
emit SongsResults(SongList(), tr("Missing Tidal API token, username or password.")); emit SongsResults(SongMap(), tr("Missing Tidal API token, username or password."));
ShowConfig(); ShowConfig();
return; return;
} }
@ -885,7 +887,7 @@ void TidalService::GetSongs() {
} }
void TidalService::SongsResultsReceived(const int id, const SongList &songs, const QString &error) { void TidalService::SongsResultsReceived(const int id, const SongMap &songs, const QString &error) {
Q_UNUSED(id); Q_UNUSED(id);
emit SongsResults(songs, error); emit SongsResults(songs, error);
} }
@ -927,12 +929,12 @@ void TidalService::StartSearch() {
if (!authenticated()) { if (!authenticated()) {
if (oauth_) { if (oauth_) {
emit SearchResults(pending_search_id_, SongList(), tr("Not authenticated with Tidal.")); emit SearchResults(pending_search_id_, SongMap(), tr("Not authenticated with Tidal."));
ShowConfig(); ShowConfig();
return; return;
} }
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) { else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
emit SearchResults(pending_search_id_, SongList(), tr("Missing Tidal API token, username or password.")); emit SearchResults(pending_search_id_, SongMap(), tr("Missing Tidal API token, username or password."));
ShowConfig(); ShowConfig();
return; return;
} }
@ -981,7 +983,7 @@ void TidalService::SendSearch() {
} }
void TidalService::SearchResultsReceived(const int id, const SongList &songs, const QString &error) { void TidalService::SearchResultsReceived(const int id, const SongMap &songs, const QString &error) {
emit SearchResults(id, songs, error); emit SearchResults(id, songs, error);
} }

View File

@ -138,10 +138,10 @@ class TidalService : public InternetService {
void HandleAuthReply(QNetworkReply *reply); void HandleAuthReply(QNetworkReply *reply);
void ResetLoginAttempts(); void ResetLoginAttempts();
void StartSearch(); void StartSearch();
void ArtistsResultsReceived(const int id, const SongList &songs, const QString &error); void ArtistsResultsReceived(const int id, const SongMap &songs, const QString &error);
void AlbumsResultsReceived(const int id, const SongList &songs, const QString &error); void AlbumsResultsReceived(const int id, const SongMap &songs, const QString &error);
void SongsResultsReceived(const int id, const SongList &songs, const QString &error); void SongsResultsReceived(const int id, const SongMap &songs, const QString &error);
void SearchResultsReceived(const int id, const SongList &songs, const QString &error); void SearchResultsReceived(const int id, const SongMap &songs, const QString &error);
void ArtistsUpdateStatusReceived(const int id, const QString &text); void ArtistsUpdateStatusReceived(const int id, const QString &text);
void AlbumsUpdateStatusReceived(const int id, const QString &text); void AlbumsUpdateStatusReceived(const int id, const QString &text);
void SongsUpdateStatusReceived(const int id, const QString &text); void SongsUpdateStatusReceived(const int id, const QString &text);

View File

@ -46,7 +46,7 @@ class CollectionBackendTest : public ::testing::Test {
void SetUp() override { void SetUp() override {
database_.reset(new MemoryDatabase(nullptr)); database_.reset(new MemoryDatabase(nullptr));
backend_ = std::make_unique<CollectionBackend>(); backend_ = std::make_unique<CollectionBackend>();
backend_->Init(database_.get(), Song::Source_Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable); backend_->Init(database_.get(), nullptr, Song::Source_Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
} }
static Song MakeDummySong(int directory_id) { static Song MakeDummySong(int directory_id) {
@ -431,4 +431,175 @@ TEST_F(SingleSong, TestUrls) {
} }
TEST_F(CollectionBackendTest, UpdateSongsBySongID) {
QStringList song_ids = QStringList() << "song1"
<< "song2"
<< "song3"
<< "song4"
<< "song5"
<< "song6";
{ // Add songs
SongMap songs;
for (const QString &song_id : song_ids) {
QUrl url;
url.setScheme("file");
url.setPath("/music/" + song_id);
Song song(Song::Source_Collection);
song.set_song_id(song_id);
song.set_directory_id(1);
song.set_title("Test Title " + song_id);
song.set_album("Test Album");
song.set_artist("Test Artist");
song.set_url(url);
song.set_length_nanosec(kNsecPerSec);
song.set_mtime(1);
song.set_ctime(1);
song.set_filesize(1);
song.set_valid(true);
songs.insert(song_id, song);
}
QSignalSpy spy(backend_.get(), &CollectionBackend::SongsDiscovered);
backend_->UpdateSongsBySongID(songs);
ASSERT_EQ(1, spy.count());
SongList new_songs = spy[0][0].value<SongList>();
EXPECT_EQ(new_songs.count(), song_ids.count());
EXPECT_EQ(song_ids[0], new_songs[0].song_id());
EXPECT_EQ(song_ids[1], new_songs[1].song_id());
EXPECT_EQ(song_ids[2], new_songs[2].song_id());
EXPECT_EQ(song_ids[3], new_songs[3].song_id());
EXPECT_EQ(song_ids[4], new_songs[4].song_id());
EXPECT_EQ(song_ids[5], new_songs[5].song_id());
}
{ // Check that all songs are added.
SongMap songs;
{
QSqlDatabase db(database_->Connect());
CollectionQuery query(db, SCollection::kSongsTable, SCollection::kFtsTable);
EXPECT_TRUE(backend_->ExecCollectionQuery(&query, songs));
}
EXPECT_EQ(songs.count(), song_ids.count());
for (QMap<QString, Song>::const_iterator it = songs.begin() ; it != songs.end() ; ++it) {
EXPECT_EQ(it.key(), it.value().song_id());
}
for (const QString &song_id : song_ids) {
EXPECT_TRUE(songs.contains(song_id));
}
}
{ // Remove some songs
QSignalSpy spy1(backend_.get(), &CollectionBackend::SongsDiscovered);
QSignalSpy spy2(backend_.get(), &CollectionBackend::SongsDeleted);
SongMap songs;
QStringList song_ids2 = QStringList() << "song1"
<< "song4"
<< "song5"
<< "song6";
for (const QString &song_id : song_ids2) {
QUrl url;
url.setScheme("file");
url.setPath("/music/" + song_id);
Song song(Song::Source_Collection);
song.set_song_id(song_id);
song.set_directory_id(1);
song.set_title("Test Title " + song_id);
song.set_album("Test Album");
song.set_artist("Test Artist");
song.set_url(url);
song.set_length_nanosec(kNsecPerSec);
song.set_mtime(1);
song.set_ctime(1);
song.set_filesize(1);
song.set_valid(true);
songs.insert(song_id, song);
}
backend_->UpdateSongsBySongID(songs);
ASSERT_EQ(0, spy1.count());
ASSERT_EQ(1, spy2.count());
SongList deleted_songs = spy2[0][0].value<SongList>();
EXPECT_EQ(deleted_songs.count(), 2);
EXPECT_EQ(deleted_songs[0].song_id(), "song2");
EXPECT_EQ(deleted_songs[1].song_id(), "song3");
}
{ // Update some songs
QSignalSpy spy1(backend_.get(), &CollectionBackend::SongsDeleted);
QSignalSpy spy2(backend_.get(), &CollectionBackend::SongsDiscovered);
SongMap songs;
QStringList song_ids2 = QStringList() << "song1"
<< "song4"
<< "song5"
<< "song6";
for (const QString &song_id : song_ids2) {
QUrl url;
url.setScheme("file");
url.setPath("/music/" + song_id);
Song song(Song::Source_Collection);
song.set_song_id(song_id);
song.set_directory_id(1);
song.set_title("Test Title " + song_id);
song.set_album("Test Album");
song.set_artist("Test Artist");
song.set_url(url);
song.set_length_nanosec(kNsecPerSec);
song.set_mtime(1);
song.set_ctime(1);
song.set_filesize(1);
song.set_valid(true);
songs.insert(song_id, song);
}
songs["song1"].set_artist("New artist");
songs["song6"].set_artist("New artist");
backend_->UpdateSongsBySongID(songs);
ASSERT_EQ(1, spy1.count());
ASSERT_EQ(1, spy2.count());
SongList deleted_songs = spy1[0][0].value<SongList>();
SongList added_songs = spy2[0][0].value<SongList>();
EXPECT_EQ(deleted_songs.count(), 2);
EXPECT_EQ(added_songs.count(), 2);
EXPECT_EQ(deleted_songs[0].song_id(), "song1");
EXPECT_EQ(deleted_songs[1].song_id(), "song6");
EXPECT_EQ(added_songs[0].song_id(), "song1");
EXPECT_EQ(added_songs[1].song_id(), "song6");
}
}
} // namespace } // namespace

View File

@ -51,7 +51,7 @@ class CollectionModelTest : public ::testing::Test {
void SetUp() override { void SetUp() override {
database_ = std::make_shared<MemoryDatabase>(nullptr); database_ = std::make_shared<MemoryDatabase>(nullptr);
backend_ = std::make_unique<CollectionBackend>(); backend_ = std::make_unique<CollectionBackend>();
backend_->Init(database_.get(), Song::Source_Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable); backend_->Init(database_.get(), nullptr, Song::Source_Collection, SCollection::kSongsTable, SCollection::kFtsTable, SCollection::kDirsTable, SCollection::kSubdirsTable);
model_ = std::make_unique<CollectionModel>(backend_.get(), nullptr); model_ = std::make_unique<CollectionModel>(backend_.get(), nullptr);
added_dir_ = false; added_dir_ = false;