Add new method for updating songs based on song ID
Show status updating database. Fixes #750
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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_;
|
||||
|
||||
35
src/collection/collectiontask.cpp
Normal file
35
src/collection/collectiontask.cpp
Normal 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_);
|
||||
|
||||
}
|
||||
40
src/collection/collectiontask.h
Normal file
40
src/collection/collectiontask.h
Normal 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
|
||||
@@ -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())) {
|
||||
|
||||
Reference in New Issue
Block a user