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

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