diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 715675e1..92b7f84f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,7 @@ set(SOURCES collection/sqlrow.cpp collection/savedgroupingmanager.cpp collection/groupbydialog.cpp + collection/collectiontask.cpp playlist/playlist.cpp playlist/playlistbackend.cpp diff --git a/src/collection/collection.cpp b/src/collection/collection.cpp index 6d5b051a..8c4349f8 100644 --- a/src/collection/collection.cpp +++ b/src/collection/collection.cpp @@ -62,7 +62,7 @@ SCollection::SCollection(Application *app, QObject *parent) backend()->moveToThread(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); diff --git a/src/collection/collectionbackend.cpp b/src/collection/collectionbackend.cpp index 25a950ff..9f7ddb0c 100644 --- a/src/collection/collectionbackend.cpp +++ b/src/collection/collectionbackend.cpp @@ -46,16 +46,19 @@ #include "core/logging.h" #include "core/database.h" #include "core/scopedtransaction.h" +#include "core/song.h" #include "smartplaylists/smartplaylistsearch.h" #include "directory.h" +#include "sqlrow.h" #include "collectionbackend.h" #include "collectionquery.h" -#include "sqlrow.h" +#include "collectiontask.h" CollectionBackend::CollectionBackend(QObject *parent) : CollectionBackendInterface(parent), db_(nullptr), + task_manager_(nullptr), source_(Song::Source_Unknown), 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; + task_manager_ = task_manager; source_ = source; songs_table_ = songs_table; dirs_table_ = dirs_table; subdirs_table_ = subdirs_table; fts_table_ = fts_table; + } void CollectionBackend::Close() { @@ -623,6 +629,8 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) { 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_)); @@ -634,9 +642,9 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) { } } - Song copy(song); - copy.set_id(id); - added_songs << copy; + Song song_copy(song); + song_copy.set_id(id); + 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) { 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) { QMutexLocker l(db_->Mutex()); diff --git a/src/collection/collectionbackend.h b/src/collection/collectionbackend.h index fe9101a6..46d0a532 100644 --- a/src/collection/collectionbackend.h +++ b/src/collection/collectionbackend.h @@ -40,6 +40,7 @@ #include "directory.h" class QThread; +class TaskManager; class Database; class SmartPlaylistSearch; @@ -127,7 +128,7 @@ class CollectionBackend : public CollectionBackendInterface { 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 ExitAsync(); @@ -184,6 +185,7 @@ class CollectionBackend : public CollectionBackendInterface { void RemoveDirectory(const Directory &dir) override; bool ExecCollectionQuery(CollectionQuery *query, SongList &songs); + bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs); void IncrementPlayCountAsync(const int id); void IncrementSkipCountAsync(const int id, const float progress); @@ -202,6 +204,7 @@ class CollectionBackend : public CollectionBackendInterface { Song::Source Source() const; void AddOrUpdateSongsAsync(const SongList &songs); + void UpdateSongsBySongIDAsync(const SongMap &new_songs); void UpdateSongRatingAsync(const int id, const double rating); void UpdateSongsRatingAsync(const QList &ids, const double rating); @@ -213,6 +216,7 @@ class CollectionBackend : public CollectionBackendInterface { void UpdateTotalArtistCount(); void UpdateTotalAlbumCount(); void AddOrUpdateSongs(const SongList &songs); + void UpdateSongsBySongID(const SongMap &new_songs); void UpdateMTimesOnly(const SongList &songs); void DeleteSongs(const SongList &songs); void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true); @@ -279,6 +283,7 @@ class CollectionBackend : public CollectionBackendInterface { private: Database *db_; + TaskManager *task_manager_; Song::Source source_; QString songs_table_; QString dirs_table_; diff --git a/src/collection/collectiontask.cpp b/src/collection/collectiontask.cpp new file mode 100644 index 00000000..6bcc55a1 --- /dev/null +++ b/src/collection/collectiontask.cpp @@ -0,0 +1,35 @@ +/* + * Strawberry Music Player + * Copyright 2021, Jonas Kvinge + * + * 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 . + * + */ + +#include + +#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_); + +} diff --git a/src/collection/collectiontask.h b/src/collection/collectiontask.h new file mode 100644 index 00000000..c0d218cc --- /dev/null +++ b/src/collection/collectiontask.h @@ -0,0 +1,40 @@ +/* + * Strawberry Music Player + * Copyright 2021, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef COLLECTIONTASK_H +#define COLLECTIONTASK_H + +#include +#include + +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 diff --git a/src/collection/collectionview.cpp b/src/collection/collectionview.cpp index c06ae066..79bb2b67 100644 --- a/src/collection/collectionview.cpp +++ b/src/collection/collectionview.cpp @@ -471,7 +471,7 @@ void CollectionView::SetShowInVarious(const bool on) { if (on && albums.keys().count() == 1) { const QStringList albums_list = albums.keys(); const QString album = albums_list.first(); - QList all_of_album = app_->collection_backend()->GetSongsByAlbum(album); + SongList all_of_album = app_->collection_backend()->GetSongsByAlbum(album); QSet other_artists; for (const Song &s : all_of_album) { if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) { diff --git a/src/core/metatypes.cpp b/src/core/metatypes.cpp index 30f344c0..2ffb7799 100644 --- a/src/core/metatypes.cpp +++ b/src/core/metatypes.cpp @@ -99,8 +99,10 @@ void RegisterMetaTypes() { qRegisterMetaType("Subdirectory"); qRegisterMetaType("SubdirectoryList"); qRegisterMetaType("Song"); - qRegisterMetaType>("QList"); qRegisterMetaType("SongList"); + qRegisterMetaType("SongMap"); + qRegisterMetaType>("QList"); + qRegisterMetaType>("QMap"); qRegisterMetaType("EngineType"); qRegisterMetaType("Engine::SimpleMetaBundle"); qRegisterMetaType("Engine::State"); diff --git a/src/core/scopedtransaction.cpp b/src/core/scopedtransaction.cpp index 8c1c61de..5b203fa6 100644 --- a/src/core/scopedtransaction.cpp +++ b/src/core/scopedtransaction.cpp @@ -25,19 +25,23 @@ #include "core/logging.h" #include "scopedtransaction.h" -ScopedTransaction::ScopedTransaction(QSqlDatabase *db) - : db_(db), pending_(true) { +ScopedTransaction::ScopedTransaction(QSqlDatabase *db) : db_(db), pending_(true) { + db->transaction(); + } ScopedTransaction::~ScopedTransaction() { + if (pending_) { qLog(Warning) << "Rolling back transaction"; db_->rollback(); } + } void ScopedTransaction::Commit() { + if (!pending_) { qLog(Warning) << "Tried to commit a ScopedTransaction twice"; return; @@ -45,4 +49,5 @@ void ScopedTransaction::Commit() { db_->commit(); pending_ = false; + } diff --git a/src/core/song.h b/src/core/song.h index 7f6873d5..5c82daf0 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -396,10 +397,13 @@ class Song { QSharedDataPointer d; }; -Q_DECLARE_METATYPE(Song) typedef QList SongList; -Q_DECLARE_METATYPE(QList) +typedef QMap SongMap; + +Q_DECLARE_METATYPE(Song) +Q_DECLARE_METATYPE(SongList) +Q_DECLARE_METATYPE(SongMap) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) size_t qHash(const Song &song); diff --git a/src/device/connecteddevice.cpp b/src/device/connecteddevice.cpp index f761123e..e36023a3 100644 --- a/src/device/connecteddevice.cpp +++ b/src/device/connecteddevice.cpp @@ -63,6 +63,7 @@ ConnectedDevice::ConnectedDevice(const QUrl &url, DeviceLister *lister, const QS } backend_->Init(app_->database(), + app_->task_manager(), Song::Source_Device, QString("device_%1_songs").arg(database_id), QString("device_%1_fts").arg(database_id), diff --git a/src/internet/internetcollectionview.cpp b/src/internet/internetcollectionview.cpp index 3f2e275d..66ad0c45 100644 --- a/src/internet/internetcollectionview.cpp +++ b/src/internet/internetcollectionview.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/src/internet/internetcollectionview.h b/src/internet/internetcollectionview.h index dce44673..f39f65d5 100644 --- a/src/internet/internetcollectionview.h +++ b/src/internet/internetcollectionview.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -86,7 +87,7 @@ class InternetCollectionView : public AutoExpandingTreeView { void TotalArtistCountUpdated_(); void TotalAlbumCountUpdated_(); void Error(QString); - void RemoveSongs(SongList); + void RemoveSongs(SongList songs); protected: // QWidget diff --git a/src/internet/internetsearchmodel.cpp b/src/internet/internetsearchmodel.cpp index 2179c689..2905fb37 100644 --- a/src/internet/internetsearchmodel.cpp +++ b/src/internet/internetsearchmodel.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -392,12 +393,11 @@ MimeData *InternetSearchModel::LoadTracks(const InternetSearchView::ResultList & return nullptr; } - SongList songs; + SongMap songs; QList urls; - songs.reserve(results.count()); urls.reserve(results.count()); for (const InternetSearchView::Result &result : results) { - songs << result.metadata_; + songs.insert(result.metadata_.song_id(), result.metadata_); urls << result.metadata_.url(); } diff --git a/src/internet/internetsearchview.cpp b/src/internet/internetsearchview.cpp index 815b1274..f0b4f9e0 100644 --- a/src/internet/internetsearchview.cpp +++ b/src/internet/internetsearchview.cpp @@ -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; @@ -758,7 +758,7 @@ void InternetSearchView::AddArtists() { MimeData *mimedata = SelectedMimeData(); if (!mimedata) return; if (const InternetSongMimeData *internet_song_data = qobject_cast(mimedata)) { - emit AddArtistsSignal(internet_song_data->songs); + emit AddArtistsSignal(internet_song_data->songs.values()); } } @@ -768,7 +768,7 @@ void InternetSearchView::AddAlbums() { MimeData *mimedata = SelectedMimeData(); if (!mimedata) return; if (const InternetSongMimeData *internet_song_data = qobject_cast(mimedata)) { - emit AddAlbumsSignal(internet_song_data->songs); + emit AddAlbumsSignal(internet_song_data->songs.values()); } } diff --git a/src/internet/internetsearchview.h b/src/internet/internetsearchview.h index eb798fe4..d49758fb 100644 --- a/src/internet/internetsearchview.h +++ b/src/internet/internetsearchview.h @@ -140,13 +140,13 @@ class InternetSearchView : public QWidget { void AddToPlaylist(QMimeData*); void AddArtistsSignal(SongList); void AddAlbumsSignal(SongList); - void AddSongsSignal(SongList); + void AddSongsSignal(SongMap); private slots: void SwapModels(); void TextEdited(const QString &text); 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 ProgressSetMaximum(const int service_id, const int max); diff --git a/src/internet/internetservice.h b/src/internet/internetservice.h index 001fe913..304fe485 100644 --- a/src/internet/internetservice.h +++ b/src/internet/internetservice.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -95,38 +96,39 @@ class InternetService : public QObject { void TestComplete(bool success, QString error = QString()); void Error(QString error); - void Results(SongList songs, QString error); + void Results(SongMap songs, QString error); void UpdateStatus(QString text); void ProgressSetMaximum(int max); void UpdateProgress(int max); - void ArtistsResults(SongList songs, QString error); + void ArtistsResults(SongMap songs, QString error); void ArtistsUpdateStatus(QString text); void ArtistsProgressSetMaximum(int max); void ArtistsUpdateProgress(int max); - void AlbumsResults(SongList songs, QString error); + void AlbumsResults(SongMap songs, QString error); void AlbumsUpdateStatus(QString text); void AlbumsProgressSetMaximum(int max); void AlbumsUpdateProgress(int max); - void SongsResults(SongList songs, QString error); + void SongsResults(SongMap songs, QString error); void SongsUpdateStatus(QString text); void SongsProgressSetMaximum(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 SearchProgressSetMaximum(int id, int max); void SearchUpdateProgress(int id, int max); - void AddArtists(SongList); - void AddAlbums(SongList); - void AddSongs(SongList); + void AddArtists(SongList songs); + void AddAlbums(SongList songs); + void AddSongs(SongMap songs); - void RemoveArtists(SongList); - void RemoveAlbums(SongList); - void RemoveSongs(SongList); + void RemoveArtists(SongList songs); + void RemoveAlbums(SongList songs); + 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()); diff --git a/src/internet/internetsongmimedata.h b/src/internet/internetsongmimedata.h index 05fc2a33..e51b6e2e 100644 --- a/src/internet/internetsongmimedata.h +++ b/src/internet/internetsongmimedata.h @@ -21,6 +21,8 @@ #ifndef INTERNETSONGMIMEDATA_H #define INTERNETSONGMIMEDATA_H +#include + #include "core/mimedata.h" #include "core/song.h" @@ -33,7 +35,7 @@ class InternetSongMimeData : public MimeData { explicit InternetSongMimeData(InternetService *_service, QObject* = nullptr) : service(_service) {} InternetService *service; - SongList songs; + SongMap songs; }; #endif // INTERNETSONGMIMEDATA_H diff --git a/src/internet/internetsongsview.cpp b/src/internet/internetsongsview.cpp index 78a700fe..069b1603 100644 --- a/src/internet/internetsongsview.cpp +++ b/src/internet/internetsongsview.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -62,7 +63,7 @@ InternetSongsView::InternetSongsView(Application *app, InternetService *service, ui_->filter_widget->AddMenuAction(action_configure); 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::of(&InternetService::RemoveSongs)); QObject::connect(ui_->refresh, &QPushButton::clicked, this, &InternetSongsView::GetSongs); 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()) { ui_->status->setText(error); @@ -131,10 +132,9 @@ void InternetSongsView::SongsFinished(const SongList &songs, const QString &erro ui_->close->show(); } else { - service_->songs_collection_backend()->DeleteAll(); ui_->stacked->setCurrentWidget(ui_->internetcollection_page); ui_->status->clear(); - service_->songs_collection_backend()->AddOrUpdateSongsAsync(songs); + service_->songs_collection_backend()->UpdateSongsBySongIDAsync(songs); } } diff --git a/src/internet/internetsongsview.h b/src/internet/internetsongsview.h index c5c1c1ee..bb063df6 100644 --- a/src/internet/internetsongsview.h +++ b/src/internet/internetsongsview.h @@ -24,6 +24,7 @@ #include #include +#include #include #include "core/song.h" @@ -51,7 +52,7 @@ class InternetSongsView : public QWidget { void OpenSettingsDialog(); void GetSongs(); void AbortGetSongs(); - void SongsFinished(const SongList &songs, const QString &error); + void SongsFinished(const SongMap &songs, const QString &error); private: Application *app_; diff --git a/src/internet/internettabsview.cpp b/src/internet/internettabsview.cpp index b1c2ee48..44bf39d8 100644 --- a/src/internet/internettabsview.cpp +++ b/src/internet/internettabsview.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -137,7 +138,7 @@ InternetTabsView::InternetTabsView(Application *app, InternetService *service, c 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::RemoveSongs, service_, &InternetService::RemoveSongs); + QObject::connect(ui_->songs_collection->view(), &InternetCollectionView::RemoveSongs, service_, QOverload::of(&InternetService::RemoveSongs)); QObject::connect(ui_->songs_collection->button_refresh(), &QPushButton::clicked, this, &InternetTabsView::GetSongs); 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()) { 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(); } else { - service_->artists_collection_backend()->DeleteAll(); ui_->artists_collection->stacked()->setCurrentWidget(ui_->artists_collection->internetcollection_page()); 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()) { 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(); } else { - service_->albums_collection_backend()->DeleteAll(); ui_->albums_collection->stacked()->setCurrentWidget(ui_->albums_collection->internetcollection_page()); 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()) { 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(); } else { - service_->songs_collection_backend()->DeleteAll(); ui_->songs_collection->stacked()->setCurrentWidget(ui_->songs_collection->internetcollection_page()); ui_->songs_collection->status()->clear(); - service_->songs_collection_backend()->AddOrUpdateSongsAsync(songs); + service_->songs_collection_backend()->UpdateSongsBySongIDAsync(songs); } } diff --git a/src/internet/internettabsview.h b/src/internet/internettabsview.h index 435a0e92..e437668a 100644 --- a/src/internet/internettabsview.h +++ b/src/internet/internettabsview.h @@ -24,6 +24,7 @@ #include #include +#include #include #include "settings/settingsdialog.h" @@ -60,9 +61,9 @@ class InternetTabsView : public QWidget { void AbortGetArtists(); void AbortGetAlbums(); void AbortGetSongs(); - void ArtistsFinished(const SongList &songs, const QString &error); - void AlbumsFinished(const SongList &songs, const QString &error); - void SongsFinished(const SongList &songs, const QString &error); + void ArtistsFinished(const SongMap &songs, const QString &error); + void AlbumsFinished(const SongMap &songs, const QString &error); + void SongsFinished(const SongMap &songs, const QString &error); private: Application *app_; diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 2c6ca673..8a548e11 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -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); } else if (const InternetSongMimeData *internet_song_data = qobject_cast(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(data)) { InsertRadioItems(radio_data->songs, row, play_now, enqueue_now, enqueue_next_now); diff --git a/src/playlist/playlistbackend.cpp b/src/playlist/playlistbackend.cpp index 5216da12..98b0c083 100644 --- a/src/playlist/playlistbackend.cpp +++ b/src/playlist/playlistbackend.cpp @@ -214,7 +214,7 @@ QList PlaylistBackend::GetPlaylistItems(const int playlist) { } -QList PlaylistBackend::GetPlaylistSongs(const int playlist) { +SongList PlaylistBackend::GetPlaylistSongs(const int playlist) { SongList songs; @@ -230,7 +230,7 @@ QList PlaylistBackend::GetPlaylistSongs(const int playlist) { q.BindValue(":playlist", playlist); if (!q.Exec()) { db_->ReportErrors(q); - return QList(); + 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 diff --git a/src/playlist/playlistbackend.h b/src/playlist/playlistbackend.h index b212ea76..8362cc7b 100644 --- a/src/playlist/playlistbackend.h +++ b/src/playlist/playlistbackend.h @@ -76,7 +76,7 @@ class PlaylistBackend : public QObject { PlaylistBackend::Playlist GetPlaylist(const int id); QList GetPlaylistItems(const int playlist); - QList GetPlaylistSongs(const int playlist); + SongList GetPlaylistSongs(const int playlist); void SetPlaylistOrder(const QList &ids); void SetPlaylistUiPath(const int id, const QString &path); diff --git a/src/qobuz/qobuzfavoriterequest.cpp b/src/qobuz/qobuzfavoriterequest.cpp index 31dee432..76283326 100644 --- a/src/qobuz/qobuzfavoriterequest.cpp +++ b/src/qobuz/qobuzfavoriterequest.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -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) { AddFavorites(FavoriteType_Artists, songs); } @@ -74,27 +93,12 @@ void QobuzFavoriteRequest::AddAlbums(const SongList &songs) { AddFavorites(FavoriteType_Albums, songs); } -void QobuzFavoriteRequest::AddSongs(const SongList &songs) { - AddFavorites(FavoriteType_Songs, songs); +void QobuzFavoriteRequest::AddSongs(const SongMap &songs) { + AddFavoritesRequest(FavoriteType_Songs, songs.keys(), songs.values()); } 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; for (const Song &song : songs) { QString id; @@ -112,18 +116,22 @@ void QobuzFavoriteRequest::AddFavorites(const FavoriteType type, const SongList id = song.song_id(); break; } - if (id.isEmpty()) continue; - if (!ids_list.contains(id)) { + if (!id.isEmpty() && !ids_list.contains(id)) { ids_list << id; } } + 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()) << Param("user_auth_token", user_auth_token()) - << Param(text, ids); + << Param(FavoriteMethod(type), ids_list.join(',')); QUrlQuery url_query; for (const Param ¶m : params) { @@ -180,23 +188,12 @@ void QobuzFavoriteRequest::RemoveSongs(const SongList &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) { - 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; for (const Song &song : songs) { QString id; @@ -214,18 +211,22 @@ void QobuzFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongLi id = song.song_id(); break; } - if (id.isEmpty()) continue; - if (!ids_list.contains(id)) { + if (!id.isEmpty() && !ids_list.contains(id)) { ids_list << id; } } + 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()) << Param("user_auth_token", user_auth_token()) - << Param(text, ids); + << Param(FavoriteMethod(type), ids_list.join(',')); QUrlQuery url_query; for (const Param ¶m : params) { diff --git a/src/qobuz/qobuzfavoriterequest.h b/src/qobuz/qobuzfavoriterequest.h index 8b194739..5d521d0d 100644 --- a/src/qobuz/qobuzfavoriterequest.h +++ b/src/qobuz/qobuzfavoriterequest.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -62,16 +63,20 @@ class QobuzFavoriteRequest : public QobuzBaseRequest { public slots: void AddArtists(const SongList &songs); void AddAlbums(const SongList &songs); - void AddSongs(const SongList &songs); + void AddSongs(const SongMap &songs); void RemoveArtists(const SongList &songs); void RemoveAlbums(const SongList &songs); void RemoveSongs(const SongList &songs); + void RemoveSongs(const SongMap &songs); private: void Error(const QString &error, const QVariant &debug = QVariant()); static QString FavoriteText(const FavoriteType type); + static QString FavoriteMethod(const FavoriteType type); 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 RemoveFavoritesRequest(const FavoriteType type, const QStringList &ids_list, const SongList &songs); QobuzService *service_; NetworkAccessManager *network_; diff --git a/src/qobuz/qobuzrequest.cpp b/src/qobuz/qobuzrequest.cpp index d84790c9..10e9fe2b 100644 --- a/src/qobuz/qobuzrequest.cpp +++ b/src/qobuz/qobuzrequest.cpp @@ -1315,15 +1315,15 @@ void QobuzRequest::FinishCheck() { finished_ = true; if (no_results_ && songs_.isEmpty()) { if (IsSearch()) - emit Results(query_id_, SongList(), tr("No match.")); + emit Results(query_id_, SongMap(), tr("No match.")); else - emit Results(query_id_, SongList(), QString()); + emit Results(query_id_, SongMap(), QString()); } else { 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_)); + emit Results(query_id_, songs_, ErrorsToHTML(errors_)); } } diff --git a/src/qobuz/qobuzrequest.h b/src/qobuz/qobuzrequest.h index a3062a33..6878ce0f 100644 --- a/src/qobuz/qobuzrequest.h +++ b/src/qobuz/qobuzrequest.h @@ -62,7 +62,7 @@ class QobuzRequest : public QobuzBaseRequest { signals: void LoginSuccess(); 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 ProgressSetMaximum(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_received_; - QMap songs_; + SongMap songs_; QStringList errors_; bool no_results_; QList replies_; diff --git a/src/qobuz/qobuzservice.cpp b/src/qobuz/qobuzservice.cpp index cfba8e27..38abbe04 100644 --- a/src/qobuz/qobuzservice.cpp +++ b/src/qobuz/qobuzservice.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -111,15 +112,15 @@ QobuzService::QobuzService(Application *app, QObject *parent) artists_collection_backend_ = new CollectionBackend(); 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_->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_->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); 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::RemoveAlbums, favorite_request_, &QobuzFavoriteRequest::RemoveAlbums); - QObject::connect(this, &QobuzService::RemoveSongs, favorite_request_, &QobuzFavoriteRequest::RemoveSongs); + QObject::connect(this, QOverload::of(&QobuzService::RemoveSongs), favorite_request_, QOverload::of(&QobuzFavoriteRequest::RemoveSongs)); + QObject::connect(this, QOverload::of(&QobuzService::RemoveSongs), favorite_request_, QOverload::of(&QobuzFavoriteRequest::RemoveSongs)); QObject::connect(favorite_request_, &QobuzFavoriteRequest::ArtistsAdded, artists_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::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() { if (app_id().isEmpty()) { - emit ArtistsResults(SongList(), tr("Missing Qobuz app ID.")); + emit ArtistsResults(SongMap(), tr("Missing Qobuz app ID.")); return; } if (!authenticated()) { - emit ArtistsResults(SongList(), tr("Not authenticated with Qobuz.")); + emit ArtistsResults(SongMap(), tr("Not authenticated with Qobuz.")); 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); emit ArtistsResults(songs, error); } @@ -551,12 +553,12 @@ void QobuzService::ResetAlbumsRequest() { void QobuzService::GetAlbums() { if (app_id().isEmpty()) { - emit AlbumsResults(SongList(), tr("Missing Qobuz app ID.")); + emit AlbumsResults(SongMap(), tr("Missing Qobuz app ID.")); return; } if (!authenticated()) { - emit AlbumsResults(SongList(), tr("Not authenticated with Qobuz.")); + emit AlbumsResults(SongMap(), tr("Not authenticated with Qobuz.")); 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); emit AlbumsResults(songs, error); } @@ -604,12 +606,12 @@ void QobuzService::ResetSongsRequest() { void QobuzService::GetSongs() { if (app_id().isEmpty()) { - emit SongsResults(SongList(), tr("Missing Qobuz app ID.")); + emit SongsResults(SongMap(), tr("Missing Qobuz app ID.")); return; } if (!authenticated()) { - emit SongsResults(SongList(), tr("Not authenticated with Qobuz.")); + emit SongsResults(SongMap(), tr("Not authenticated with Qobuz.")); 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); emit SongsResults(songs, error); } @@ -669,7 +671,7 @@ void QobuzService::StartSearch() { search_text_ = pending_search_text_; 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; } @@ -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); } diff --git a/src/qobuz/qobuzservice.h b/src/qobuz/qobuzservice.h index 7c25f028..e3898798 100644 --- a/src/qobuz/qobuzservice.h +++ b/src/qobuz/qobuzservice.h @@ -125,10 +125,10 @@ class QobuzService : public InternetService { void HandleAuthReply(QNetworkReply *reply); void ResetLoginAttempts(); void StartSearch(); - void ArtistsResultsReceived(const int id, const SongList &songs, const QString &error); - void AlbumsResultsReceived(const int id, const SongList &songs, const QString &error); - void SongsResultsReceived(const int id, const SongList &songs, const QString &error); - void SearchResultsReceived(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 SongMap &songs, const QString &error); + void SongsResultsReceived(const int id, const SongMap &songs, const QString &error); + void SearchResultsReceived(const int id, const SongMap &songs, const QString &error); void ArtistsUpdateStatusReceived(const int id, const QString &text); void AlbumsUpdateStatusReceived(const int id, const QString &text); void SongsUpdateStatusReceived(const int id, const QString &text); diff --git a/src/radios/somafmservice.cpp b/src/radios/somafmservice.cpp index 7ae89c79..6d0e5995 100644 --- a/src/radios/somafmservice.cpp +++ b/src/radios/somafmservice.cpp @@ -151,7 +151,7 @@ void SomaFMService::GetStreamUrlsReply(QNetworkReply *reply, const int task_id, reply->deleteLater(); PlaylistParser parser; - QList songs = parser.LoadFromDevice(reply); + SongList songs = parser.LoadFromDevice(reply); if (!songs.isEmpty()) { channel.url = songs.first().url(); } diff --git a/src/subsonic/subsonicrequest.cpp b/src/subsonic/subsonicrequest.cpp index dabe3505..6d329a93 100644 --- a/src/subsonic/subsonicrequest.cpp +++ b/src/subsonic/subsonicrequest.cpp @@ -872,14 +872,14 @@ void SubsonicRequest::FinishCheck() { ) { finished_ = true; if (no_results_ && songs_.isEmpty()) { - emit Results(SongList(), QString()); + emit Results(SongMap(), QString()); } else { if (songs_.isEmpty() && errors_.isEmpty()) { - emit Results(songs_.values(), tr("Unknown error")); + emit Results(songs_, tr("Unknown error")); } else { - emit Results(songs_.values(), ErrorsToHTML(errors_)); + emit Results(songs_, ErrorsToHTML(errors_)); } } diff --git a/src/subsonic/subsonicrequest.h b/src/subsonic/subsonicrequest.h index c9bc345f..6f61a55a 100644 --- a/src/subsonic/subsonicrequest.h +++ b/src/subsonic/subsonicrequest.h @@ -62,7 +62,7 @@ class SubsonicRequest : public SubsonicBaseRequest { void Reset(); signals: - void Results(SongList songs, QString error); + void Results(SongMap songs, QString error); void UpdateStatus(QString text); void ProgressSetMaximum(int max); void UpdateProgress(int max); @@ -140,7 +140,7 @@ class SubsonicRequest : public SubsonicBaseRequest { int album_covers_requested_; int album_covers_received_; - QMap songs_; + SongMap songs_; QStringList errors_; bool no_results_; QList replies_; diff --git a/src/subsonic/subsonicservice.cpp b/src/subsonic/subsonicservice.cpp index 09009f3d..192e96c3 100644 --- a/src/subsonic/subsonicservice.cpp +++ b/src/subsonic/subsonicservice.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -86,7 +87,7 @@ SubsonicService::SubsonicService(Application *app, QObject *parent) collection_backend_ = new CollectionBackend(); 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 @@ -425,12 +426,12 @@ void SubsonicService::ResetSongsRequest() { void SubsonicService::GetSongs() { if (!server_url().isValid()) { - emit SongsResults(SongList(), tr("Server URL is invalid.")); + emit SongsResults(SongMap(), tr("Server URL is invalid.")); return; } if (username().isEmpty() || password().isEmpty()) { - emit SongsResults(SongList(), tr("Missing username or password.")); + emit SongsResults(SongMap(), tr("Missing username or password.")); 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); diff --git a/src/subsonic/subsonicservice.h b/src/subsonic/subsonicservice.h index 493c202c..b465b678 100644 --- a/src/subsonic/subsonicservice.h +++ b/src/subsonic/subsonicservice.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -96,7 +97,7 @@ class SubsonicService : public InternetService { private slots: void HandlePingSSLErrors(const QList &ssl_errors); 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: typedef QPair Param; diff --git a/src/tidal/tidalfavoriterequest.cpp b/src/tidal/tidalfavoriterequest.cpp index 9c6ee1ef..7a590e28 100644 --- a/src/tidal/tidalfavoriterequest.cpp +++ b/src/tidal/tidalfavoriterequest.cpp @@ -66,10 +66,26 @@ QString TidalFavoriteRequest::FavoriteText(const FavoriteType type) { case FavoriteType_Albums: return "albums"; case FavoriteType_Songs: - default: 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) { @@ -80,27 +96,12 @@ void TidalFavoriteRequest::AddAlbums(const SongList &songs) { AddFavorites(FavoriteType_Albums, songs); } -void TidalFavoriteRequest::AddSongs(const SongList &songs) { - AddFavorites(FavoriteType_Songs, songs); +void TidalFavoriteRequest::AddSongs(const SongMap &songs) { + AddFavoritesRequest(FavoriteType_Songs, songs.keys(), songs.values()); } 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; for (const Song &song : songs) { QString id; @@ -118,15 +119,21 @@ void TidalFavoriteRequest::AddFavorites(const FavoriteType type, const SongList id = song.song_id(); break; } - if (id.isEmpty()) continue; - if (!id_list.contains(id)) { + if (!id.isEmpty() && !id_list.contains(id)) { id_list << id; } } + 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()) - << Param(text, id_list.join(',')); + << Param(FavoriteMethod(type), id_list.join(',')); QUrlQuery url_query; for (const Param ¶m : params) { @@ -197,11 +204,17 @@ void TidalFavoriteRequest::RemoveSongs(const SongList &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) { - if (songs.isEmpty()) return; - - QStringList ids; QMultiMap songs_map; for (const Song &song : songs) { QString id; @@ -219,18 +232,19 @@ void TidalFavoriteRequest::RemoveFavorites(const FavoriteType type, const SongLi id = song.song_id(); break; } - if (!ids.contains(id)) ids << id; - songs_map.insert(id, song); + if (!id.isEmpty()) { + songs_map.insert(id, song); + } } + QStringList ids = songs_map.uniqueKeys(); for (const QString &id : ids) { - SongList songs_list = songs_map.values(id); - RemoveFavorites(type, id, songs_list); + RemoveFavoritesRequest(type, id, songs_map.values(id)); } } -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()); diff --git a/src/tidal/tidalfavoriterequest.h b/src/tidal/tidalfavoriterequest.h index 9ab4ba60..636a91b5 100644 --- a/src/tidal/tidalfavoriterequest.h +++ b/src/tidal/tidalfavoriterequest.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -65,18 +66,22 @@ class TidalFavoriteRequest : public TidalBaseRequest { public slots: void AddArtists(const SongList &songs); void AddAlbums(const SongList &songs); - void AddSongs(const SongList &songs); + void AddSongs(const SongMap &songs); void RemoveArtists(const SongList &songs); void RemoveAlbums(const SongList &songs); void RemoveSongs(const SongList &songs); + void RemoveSongs(const SongMap &songs); private: void Error(const QString &error, const QVariant &debug = QVariant()) override; static QString FavoriteText(const FavoriteType type); + static QString FavoriteMethod(const FavoriteType type); 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 QString &id, const SongList &songs); + void RemoveFavoritesRequest(const FavoriteType type, const QString &id, const SongList &songs); TidalService *service_; NetworkAccessManager *network_; diff --git a/src/tidal/tidalrequest.cpp b/src/tidal/tidalrequest.cpp index 8cc6e82d..3d4fe859 100644 --- a/src/tidal/tidalrequest.cpp +++ b/src/tidal/tidalrequest.cpp @@ -1270,15 +1270,17 @@ void TidalRequest::FinishCheck() { finished_ = true; if (no_results_ && songs_.isEmpty()) { if (IsSearch()) - emit Results(query_id_, SongList(), tr("No match.")); + emit Results(query_id_, SongMap(), tr("No match.")); else - emit Results(query_id_, SongList(), QString()); + emit Results(query_id_, SongMap(), QString()); } else { - if (songs_.isEmpty() && errors_.isEmpty()) - emit Results(query_id_, songs_.values(), tr("Unknown error")); - else - emit Results(query_id_, songs_.values(), ErrorsToHTML(errors_)); + if (songs_.isEmpty() && errors_.isEmpty()) { + emit Results(query_id_, songs_, tr("Unknown error")); + } + else { + emit Results(query_id_, songs_, ErrorsToHTML(errors_)); + } } } diff --git a/src/tidal/tidalrequest.h b/src/tidal/tidalrequest.h index 3a9b6880..a6100366 100644 --- a/src/tidal/tidalrequest.h +++ b/src/tidal/tidalrequest.h @@ -81,7 +81,7 @@ class TidalRequest : public TidalBaseRequest { signals: void LoginSuccess(); 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 ProgressSetMaximum(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_received_; - QMap songs_; + SongMap songs_; QStringList errors_; bool need_login_; bool no_results_; diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp index 5212cea0..5c89f3e3 100644 --- a/src/tidal/tidalservice.cpp +++ b/src/tidal/tidalservice.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -127,15 +128,15 @@ TidalService::TidalService(Application *app, QObject *parent) artists_collection_backend_ = new CollectionBackend(); 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_->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_->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); 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::RemoveAlbums, favorite_request_, &TidalFavoriteRequest::RemoveAlbums); - QObject::connect(this, &TidalService::RemoveSongs, favorite_request_, &TidalFavoriteRequest::RemoveSongs); + QObject::connect(this, QOverload::of(&TidalService::RemoveSongs), favorite_request_, QOverload::of(&TidalFavoriteRequest::RemoveSongs)); + QObject::connect(this, QOverload::of(&TidalService::RemoveSongs), favorite_request_, QOverload::of(&TidalFavoriteRequest::RemoveSongs)); QObject::connect(favorite_request_, &TidalFavoriteRequest::RequestLogin, this, &TidalService::SendLogin); @@ -743,12 +745,12 @@ void TidalService::GetArtists() { if (!authenticated()) { if (oauth_) { - emit ArtistsResults(SongList(), tr("Not authenticated with Tidal.")); + emit ArtistsResults(SongMap(), tr("Not authenticated with Tidal.")); ShowConfig(); return; } 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(); 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); emit ArtistsResults(songs, error); } @@ -803,12 +805,12 @@ void TidalService::GetAlbums() { if (!authenticated()) { if (oauth_) { - emit AlbumsResults(SongList(), tr("Not authenticated with Tidal.")); + emit AlbumsResults(SongMap(), tr("Not authenticated with Tidal.")); ShowConfig(); return; } 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(); 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); emit AlbumsResults(songs, error); } @@ -861,12 +863,12 @@ void TidalService::GetSongs() { if (!authenticated()) { if (oauth_) { - emit SongsResults(SongList(), tr("Not authenticated with Tidal.")); + emit SongsResults(SongMap(), tr("Not authenticated with Tidal.")); ShowConfig(); return; } 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(); 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); emit SongsResults(songs, error); } @@ -927,12 +929,12 @@ void TidalService::StartSearch() { if (!authenticated()) { 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(); return; } 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(); 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); } diff --git a/src/tidal/tidalservice.h b/src/tidal/tidalservice.h index ec1f4a28..0e6b4bc3 100644 --- a/src/tidal/tidalservice.h +++ b/src/tidal/tidalservice.h @@ -138,10 +138,10 @@ class TidalService : public InternetService { void HandleAuthReply(QNetworkReply *reply); void ResetLoginAttempts(); void StartSearch(); - void ArtistsResultsReceived(const int id, const SongList &songs, const QString &error); - void AlbumsResultsReceived(const int id, const SongList &songs, const QString &error); - void SongsResultsReceived(const int id, const SongList &songs, const QString &error); - void SearchResultsReceived(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 SongMap &songs, const QString &error); + void SongsResultsReceived(const int id, const SongMap &songs, const QString &error); + void SearchResultsReceived(const int id, const SongMap &songs, const QString &error); void ArtistsUpdateStatusReceived(const int id, const QString &text); void AlbumsUpdateStatusReceived(const int id, const QString &text); void SongsUpdateStatusReceived(const int id, const QString &text); diff --git a/tests/src/collectionbackend_test.cpp b/tests/src/collectionbackend_test.cpp index 7b4e3d51..70727896 100644 --- a/tests/src/collectionbackend_test.cpp +++ b/tests/src/collectionbackend_test.cpp @@ -46,7 +46,7 @@ class CollectionBackendTest : public ::testing::Test { void SetUp() override { database_.reset(new MemoryDatabase(nullptr)); backend_ = std::make_unique(); - 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) { @@ -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(); + 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::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(); + 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 added_songs = spy2[0][0].value(); + 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 diff --git a/tests/src/collectionmodel_test.cpp b/tests/src/collectionmodel_test.cpp index f046e2a5..4461f668 100644 --- a/tests/src/collectionmodel_test.cpp +++ b/tests/src/collectionmodel_test.cpp @@ -51,7 +51,7 @@ class CollectionModelTest : public ::testing::Test { void SetUp() override { database_ = std::make_shared(nullptr); backend_ = std::make_unique(); - 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(backend_.get(), nullptr); added_dir_ = false;