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/savedgroupingmanager.cpp
collection/groupbydialog.cpp
collection/collectiontask.cpp
playlist/playlist.cpp
playlist/playlistbackend.cpp

View File

@ -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);

View File

@ -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());

View File

@ -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<int> &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_;

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) {
const QStringList albums_list = albums.keys();
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;
for (const Song &s : all_of_album) {
if (!albums.contains(album, s.artist()) && !other_artists.contains(s.artist())) {

View File

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

View File

@ -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;
}

View File

@ -30,6 +30,7 @@
#include <QMetaType>
#include <QList>
#include <QSet>
#include <QMap>
#include <QVariant>
#include <QString>
#include <QStringList>
@ -396,10 +397,13 @@ class Song {
QSharedDataPointer<Private> d;
};
Q_DECLARE_METATYPE(Song)
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)
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(),
app_->task_manager(),
Song::Source_Device,
QString("device_%1_songs").arg(database_id),
QString("device_%1_fts").arg(database_id),

View File

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

View File

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

View File

@ -26,6 +26,7 @@
#include <QMimeData>
#include <QList>
#include <QSet>
#include <QMap>
#include <QVariant>
#include <QString>
#include <QPixmap>
@ -392,12 +393,11 @@ MimeData *InternetSearchModel::LoadTracks(const InternetSearchView::ResultList &
return nullptr;
}
SongList songs;
SongMap songs;
QList<QUrl> 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();
}

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;
@ -758,7 +758,7 @@ void InternetSearchView::AddArtists() {
MimeData *mimedata = SelectedMimeData();
if (!mimedata) return;
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();
if (!mimedata) return;
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 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);

View File

@ -23,6 +23,7 @@
#include <QtGlobal>
#include <QObject>
#include <QMetaType>
#include <QMap>
#include <QString>
#include <QUrl>
#include <QIcon>
@ -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());

View File

@ -21,6 +21,8 @@
#ifndef INTERNETSONGMIMEDATA_H
#define INTERNETSONGMIMEDATA_H
#include <QMap>
#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

View File

@ -21,6 +21,7 @@
#include <QtGlobal>
#include <QWidget>
#include <QMap>
#include <QString>
#include <QStackedWidget>
#include <QContextMenuEvent>
@ -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<SongList>::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);
}
}

View File

@ -24,6 +24,7 @@
#include <QObject>
#include <QWidget>
#include <QMap>
#include <QString>
#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_;

View File

@ -21,6 +21,7 @@
#include <QtGlobal>
#include <QWidget>
#include <QMap>
#include <QVariant>
#include <QString>
#include <QLabel>
@ -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<SongList>::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);
}
}

View File

@ -24,6 +24,7 @@
#include <QObject>
#include <QWidget>
#include <QMap>
#include <QString>
#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_;

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);
}
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)) {
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;
@ -230,7 +230,7 @@ QList<Song> PlaylistBackend::GetPlaylistSongs(const int playlist) {
q.BindValue(":playlist", playlist);
if (!q.Exec()) {
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

View File

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

View File

@ -21,6 +21,7 @@
#include <QObject>
#include <QPair>
#include <QMap>
#include <QByteArray>
#include <QString>
#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) {
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 &param : 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 &param : params) {

View File

@ -24,6 +24,7 @@
#include <QObject>
#include <QList>
#include <QMap>
#include <QVariant>
#include <QString>
@ -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_;

View File

@ -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_));
}
}

View File

@ -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<QString, Song> songs_;
SongMap songs_;
QStringList errors_;
bool no_results_;
QList<QNetworkReply*> replies_;

View File

@ -25,6 +25,7 @@
#include <QByteArray>
#include <QPair>
#include <QList>
#include <QMap>
#include <QString>
#include <QUrl>
#include <QUrlQuery>
@ -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<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::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);
}

View File

@ -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);

View File

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

View File

@ -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_));
}
}

View File

@ -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<QString, Song> songs_;
SongMap songs_;
QStringList errors_;
bool no_results_;
QList<QNetworkReply*> replies_;

View File

@ -26,6 +26,7 @@
#include <QByteArray>
#include <QPair>
#include <QList>
#include <QMap>
#include <QString>
#include <QVariant>
#include <QUrl>
@ -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);

View File

@ -28,6 +28,7 @@
#include <QPair>
#include <QSet>
#include <QList>
#include <QMap>
#include <QVariant>
#include <QByteArray>
#include <QString>
@ -96,7 +97,7 @@ class SubsonicService : public InternetService {
private slots:
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 SongsResultsReceived(const SongList &songs, const QString &error);
void SongsResultsReceived(const SongMap &songs, const QString &error);
private:
typedef QPair<QString, QString> Param;

View File

@ -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 &param : 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<QString, Song> 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());

View File

@ -24,6 +24,7 @@
#include <QObject>
#include <QList>
#include <QMap>
#include <QVariant>
#include <QString>
@ -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_;

View File

@ -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_));
}
}
}

View File

@ -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<QString, Song> songs_;
SongMap songs_;
QStringList errors_;
bool need_login_;
bool no_results_;

View File

@ -28,6 +28,7 @@
#include <QByteArray>
#include <QPair>
#include <QList>
#include <QMap>
#include <QString>
#include <QChar>
#include <QUrl>
@ -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<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);
@ -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);
}

View File

@ -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);

View File

@ -46,7 +46,7 @@ class CollectionBackendTest : public ::testing::Test {
void SetUp() override {
database_.reset(new MemoryDatabase(nullptr));
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) {
@ -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

View File

@ -51,7 +51,7 @@ class CollectionModelTest : public ::testing::Test {
void SetUp() override {
database_ = std::make_shared<MemoryDatabase>(nullptr);
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);
added_dir_ = false;