2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine .
* Copyright 2010 , David Sansome < me @ davidsansome . com >
2023-05-14 11:34:55 +02:00
* Copyright 2018 - 2023 , Jonas Kvinge < jonas @ jkvinge . net >
2018-02-27 18:06:05 +01:00
*
* 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/>.
2018-08-09 18:39:44 +02:00
*
2018-02-27 18:06:05 +01:00
*/
# include "config.h"
2021-09-27 19:29:12 +02:00
# include <optional>
2018-05-01 00:41:33 +02:00
# include <QtGlobal>
# include <QObject>
2019-07-24 19:16:51 +02:00
# include <QApplication>
# include <QThread>
2018-05-01 00:41:33 +02:00
# include <QMutex>
# include <QSet>
# include <QMap>
2020-02-08 03:40:30 +01:00
# include <QVector>
2018-02-27 18:06:05 +01:00
# include <QVariant>
2020-02-08 03:40:30 +01:00
# include <QByteArray>
2018-05-01 00:41:33 +02:00
# include <QString>
# include <QStringList>
# include <QUrl>
2020-02-08 03:40:30 +01:00
# include <QFileInfo>
# include <QDateTime>
2020-07-18 04:05:07 +02:00
# include <QRegularExpression>
2018-05-01 00:41:33 +02:00
# include <QSqlDatabase>
# include <QSqlQuery>
2021-09-09 21:45:46 +02:00
# include <QSqlError>
2018-02-27 18:06:05 +01:00
2023-07-21 05:55:24 +02:00
# include "core/shared_ptr.h"
2020-08-30 18:09:13 +02:00
# include "core/logging.h"
2018-02-27 18:06:05 +01:00
# include "core/database.h"
# include "core/scopedtransaction.h"
2021-09-19 15:41:36 +02:00
# include "core/song.h"
2022-05-13 18:15:04 +02:00
# include "core/sqlrow.h"
2020-09-17 17:50:17 +02:00
# include "smartplaylists/smartplaylistsearch.h"
2018-02-27 18:06:05 +01:00
2023-01-08 15:40:54 +01:00
# include "collectiondirectory.h"
2018-05-01 00:41:33 +02:00
# include "collectionbackend.h"
2023-01-08 15:40:54 +01:00
# include "collectionfilteroptions.h"
2018-05-01 00:41:33 +02:00
# include "collectionquery.h"
2021-09-19 15:41:36 +02:00
# include "collectiontask.h"
2018-05-01 00:41:33 +02:00
2021-07-11 09:49:38 +02:00
CollectionBackend : : CollectionBackend ( QObject * parent )
: CollectionBackendInterface ( parent ) ,
db_ ( nullptr ) ,
2021-09-19 15:41:36 +02:00
task_manager_ ( nullptr ) ,
2023-02-18 14:09:27 +01:00
source_ ( Song : : Source : : Unknown ) ,
2021-07-11 09:49:38 +02:00
original_thread_ ( nullptr ) {
2019-07-24 19:16:51 +02:00
original_thread_ = thread ( ) ;
}
2023-07-21 05:55:24 +02:00
CollectionBackend : : ~ CollectionBackend ( ) {
qLog ( Debug ) < < " Collection backend " < < this < < " for " < < Song : : TextForSource ( source_ ) < < " deleted " ;
}
void CollectionBackend : : Init ( SharedPtr < Database > db , SharedPtr < TaskManager > task_manager , const Song : : Source source , const QString & songs_table , const QString & fts_table , const QString & dirs_table , const QString & subdirs_table ) {
2021-09-19 15:41:36 +02:00
2018-02-27 18:06:05 +01:00
db_ = db ;
2021-09-19 15:41:36 +02:00
task_manager_ = task_manager ;
2019-07-02 00:48:40 +02:00
source_ = source ;
2018-02-27 18:06:05 +01:00
songs_table_ = songs_table ;
dirs_table_ = dirs_table ;
subdirs_table_ = subdirs_table ;
fts_table_ = fts_table ;
2021-09-19 15:41:36 +02:00
2018-02-27 18:06:05 +01:00
}
2019-07-24 19:16:51 +02:00
void CollectionBackend : : Close ( ) {
if ( db_ ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
db_ - > Close ( ) ;
}
}
void CollectionBackend : : ExitAsync ( ) {
2023-05-03 20:08:51 +02:00
QMetaObject : : invokeMethod ( this , & CollectionBackend : : Exit , Qt : : QueuedConnection ) ;
2019-07-24 19:16:51 +02:00
}
void CollectionBackend : : Exit ( ) {
2021-09-27 19:09:18 +02:00
Q_ASSERT ( QThread : : currentThread ( ) = = thread ( ) ) ;
2019-07-24 19:16:51 +02:00
moveToThread ( original_thread_ ) ;
emit ExitFinished ( ) ;
}
2021-09-09 21:45:46 +02:00
void CollectionBackend : : ReportErrors ( const CollectionQuery & query ) {
const QSqlError sql_error = query . lastError ( ) ;
if ( sql_error . isValid ( ) ) {
2023-04-07 20:54:15 +02:00
qLog ( Error ) < < " Unable to execute collection SQL query: " < < sql_error ;
qLog ( Error ) < < " Failed SQL query: " < < query . lastQuery ( ) ;
qLog ( Error ) < < " Bound SQL values: " < < query . boundValues ( ) ;
emit Error ( tr ( " Unable to execute collection SQL query: %1 " ) . arg ( sql_error . text ( ) ) ) ;
emit Error ( tr ( " Failed SQL query: %1 " ) . arg ( query . lastQuery ( ) ) ) ;
2021-09-09 21:45:46 +02:00
}
}
2018-02-27 18:06:05 +01:00
void CollectionBackend : : LoadDirectoriesAsync ( ) {
2023-05-03 20:08:51 +02:00
QMetaObject : : invokeMethod ( this , & CollectionBackend : : LoadDirectories , Qt : : QueuedConnection ) ;
2018-02-27 18:06:05 +01:00
}
void CollectionBackend : : UpdateTotalSongCountAsync ( ) {
2023-05-03 20:08:51 +02:00
QMetaObject : : invokeMethod ( this , & CollectionBackend : : UpdateTotalSongCount , Qt : : QueuedConnection ) ;
2018-02-27 18:06:05 +01:00
}
void CollectionBackend : : UpdateTotalArtistCountAsync ( ) {
2023-05-03 20:08:51 +02:00
QMetaObject : : invokeMethod ( this , & CollectionBackend : : UpdateTotalArtistCount , Qt : : QueuedConnection ) ;
2018-02-27 18:06:05 +01:00
}
void CollectionBackend : : UpdateTotalAlbumCountAsync ( ) {
2023-05-03 20:08:51 +02:00
QMetaObject : : invokeMethod ( this , & CollectionBackend : : UpdateTotalAlbumCount , Qt : : QueuedConnection ) ;
2018-02-27 18:06:05 +01:00
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : IncrementPlayCountAsync ( const int id ) {
2021-09-09 21:53:14 +02:00
QMetaObject : : invokeMethod ( this , " IncrementPlayCount " , Qt : : QueuedConnection , Q_ARG ( int , id ) ) ;
2018-02-27 18:06:05 +01:00
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : IncrementSkipCountAsync ( const int id , const float progress ) {
2021-09-09 21:53:14 +02:00
QMetaObject : : invokeMethod ( this , " IncrementSkipCount " , Qt : : QueuedConnection , Q_ARG ( int , id ) , Q_ARG ( float , progress ) ) ;
2018-02-27 18:06:05 +01:00
}
2023-03-18 20:03:07 +01:00
void CollectionBackend : : ResetPlayStatisticsAsync ( const int id , const bool save_tags ) {
QMetaObject : : invokeMethod ( this , " ResetPlayStatistics " , Qt : : QueuedConnection , Q_ARG ( int , id ) , Q_ARG ( bool , save_tags ) ) ;
}
void CollectionBackend : : ResetPlayStatisticsAsync ( const QList < int > & id_list , const bool save_tags ) {
QMetaObject : : invokeMethod ( this , " ResetPlayStatistics " , Qt : : QueuedConnection , Q_ARG ( QList < int > , id_list ) , Q_ARG ( bool , save_tags ) ) ;
2018-02-27 18:06:05 +01:00
}
void CollectionBackend : : LoadDirectories ( ) {
2023-01-08 15:40:54 +01:00
CollectionDirectoryList dirs = GetAllDirectories ( ) ;
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2023-01-08 15:40:54 +01:00
for ( const CollectionDirectory & dir : dirs ) {
2018-02-27 18:06:05 +01:00
emit DirectoryDiscovered ( dir , SubdirsInDirectory ( dir . id , db ) ) ;
}
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : ChangeDirPath ( const int id , const QString & old_path , const QString & new_path ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
ScopedTransaction t ( & db ) ;
// Do the dirs table
2018-09-14 23:05:58 +02:00
{
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET path=:path WHERE ROWID=:id " ) . arg ( dirs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :path " ) , new_path ) ;
q . BindValue ( QStringLiteral ( " :id " ) , id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-09-14 23:05:58 +02:00
}
2018-02-27 18:06:05 +01:00
const QByteArray old_url = QUrl : : fromLocalFile ( old_path ) . toEncoded ( ) ;
const QByteArray new_url = QUrl : : fromLocalFile ( new_path ) . toEncoded ( ) ;
2021-10-30 02:21:29 +02:00
const qint64 path_len = old_url . length ( ) ;
2018-02-27 18:06:05 +01:00
// Do the subdirs table
2018-09-14 23:05:58 +02:00
{
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET path=:path || substr(path, %2) WHERE directory=:id " ) . arg ( subdirs_table_ ) . arg ( path_len ) ) ;
q . BindValue ( QStringLiteral ( " :path " ) , new_url ) ;
q . BindValue ( QStringLiteral ( " :id " ) , id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-09-14 23:05:58 +02:00
}
2018-02-27 18:06:05 +01:00
// Do the songs table
2018-09-14 23:05:58 +02:00
{
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET url=:path || substr(url, %2) WHERE directory=:id " ) . arg ( songs_table_ ) . arg ( path_len ) ) ;
q . BindValue ( QStringLiteral ( " :path " ) , new_url ) ;
q . BindValue ( QStringLiteral ( " :id " ) , id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-09-14 23:05:58 +02:00
}
2018-02-27 18:06:05 +01:00
t . Commit ( ) ;
}
2023-01-08 15:40:54 +01:00
CollectionDirectoryList CollectionBackend : : GetAllDirectories ( ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2023-01-08 15:40:54 +01:00
CollectionDirectoryList ret ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, path FROM %1 " ) . arg ( dirs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ret ;
}
2018-02-27 18:06:05 +01:00
while ( q . next ( ) ) {
2023-01-08 15:40:54 +01:00
CollectionDirectory dir ;
2018-02-27 18:06:05 +01:00
dir . id = q . value ( 0 ) . toInt ( ) ;
dir . path = q . value ( 1 ) . toString ( ) ;
ret < < dir ;
}
return ret ;
}
2023-01-08 15:40:54 +01:00
CollectionSubdirectoryList CollectionBackend : : SubdirsInDirectory ( const int id ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db = db_ - > Connect ( ) ;
return SubdirsInDirectory ( id , db ) ;
}
2023-01-08 15:40:54 +01:00
CollectionSubdirectoryList CollectionBackend : : SubdirsInDirectory ( const int id , QSqlDatabase & db ) {
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT path, mtime FROM %1 WHERE directory_id = :dir " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :dir " ) , id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
2023-01-08 15:40:54 +01:00
return CollectionSubdirectoryList ( ) ;
2021-09-09 21:45:46 +02:00
}
2018-02-27 18:06:05 +01:00
2023-01-08 15:40:54 +01:00
CollectionSubdirectoryList subdirs ;
2018-02-27 18:06:05 +01:00
while ( q . next ( ) ) {
2023-01-08 15:40:54 +01:00
CollectionSubdirectory subdir ;
2018-02-27 18:06:05 +01:00
subdir . directory_id = id ;
subdir . path = q . value ( 0 ) . toString ( ) ;
2020-08-13 21:09:06 +02:00
subdir . mtime = q . value ( 1 ) . toLongLong ( ) ;
2018-02-27 18:06:05 +01:00
subdirs < < subdir ;
}
return subdirs ;
}
void CollectionBackend : : UpdateTotalSongCount ( ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT COUNT(*) FROM %1 WHERE unavailable = 0 " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
if ( ! q . next ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
emit TotalSongCountUpdated ( q . value ( 0 ) . toInt ( ) ) ;
}
void CollectionBackend : : UpdateTotalArtistCount ( ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT COUNT(DISTINCT artist) FROM %1 WHERE unavailable = 0 " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
if ( ! q . next ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
emit TotalArtistCountUpdated ( q . value ( 0 ) . toInt ( ) ) ;
}
void CollectionBackend : : UpdateTotalAlbumCount ( ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT COUNT(*) FROM (SELECT DISTINCT effective_albumartist, album FROM %1 WHERE unavailable = 0) " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
if ( ! q . next ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
emit TotalAlbumCountUpdated ( q . value ( 0 ) . toInt ( ) ) ;
}
void CollectionBackend : : AddDirectory ( const QString & path ) {
QString canonical_path = QFileInfo ( path ) . canonicalFilePath ( ) ;
QString db_path = canonical_path ;
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " INSERT INTO %1 (path, subdirs) VALUES (:path, 1) " ) . arg ( dirs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :path " ) , db_path ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
2022-03-22 21:09:05 +01:00
db_ - > ReportErrors ( q ) ;
return ;
2021-09-09 21:45:46 +02:00
}
2018-02-27 18:06:05 +01:00
2023-01-08 15:40:54 +01:00
CollectionDirectory dir ;
2018-02-27 18:06:05 +01:00
dir . path = canonical_path ;
dir . id = q . lastInsertId ( ) . toInt ( ) ;
2023-01-08 15:40:54 +01:00
emit DirectoryDiscovered ( dir , CollectionSubdirectoryList ( ) ) ;
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
2023-01-08 15:40:54 +01:00
void CollectionBackend : : RemoveDirectory ( const CollectionDirectory & dir ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
// Remove songs first
DeleteSongs ( FindSongsInDirectory ( dir . id ) ) ;
ScopedTransaction transaction ( & db ) ;
// Delete the subdirs that were in this directory
2021-09-09 21:45:46 +02:00
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " DELETE FROM %1 WHERE directory_id = :id " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , dir . id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
2018-02-27 18:06:05 +01:00
// Now remove the directory itself
2021-09-09 21:45:46 +02:00
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " DELETE FROM %1 WHERE ROWID = :id " ) . arg ( dirs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , dir . id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
2018-02-27 18:06:05 +01:00
emit DirectoryDeleted ( dir ) ;
transaction . Commit ( ) ;
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
2020-09-10 22:05:12 +02:00
SongList CollectionBackend : : FindSongsInDirectory ( const int id ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :directory_id " ) , id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SongList ( ) ;
}
2018-02-27 18:06:05 +01:00
SongList ret ;
while ( q . next ( ) ) {
2019-11-14 00:09:35 +01:00
Song song ( source_ ) ;
2018-02-27 18:06:05 +01:00
song . InitFromQuery ( q , true ) ;
ret < < song ;
}
return ret ;
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
2021-04-25 21:16:44 +02:00
SongList CollectionBackend : : SongsWithMissingFingerprint ( const int id ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (fingerprint IS NULL OR fingerprint = '') " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :directory_id " ) , id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SongList ( ) ;
}
2021-04-25 21:16:44 +02:00
SongList ret ;
while ( q . next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( q , true ) ;
ret < < song ;
}
return ret ;
2023-06-27 04:01:26 +02:00
}
SongList CollectionBackend : : SongsWithMissingLoudnessCharacteristics ( const int id ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (ebur128_integrated_loudness_lufs IS NULL OR ebur128_loudness_range_lu IS NULL) " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :directory_id " ) , id ) ;
2023-06-27 04:01:26 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SongList ( ) ;
}
SongList ret ;
while ( q . next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( q , true ) ;
ret < < song ;
}
return ret ;
2021-04-25 21:16:44 +02:00
}
2021-09-27 19:29:12 +02:00
void CollectionBackend : : SongPathChanged ( const Song & song , const QFileInfo & new_file , const std : : optional < int > new_collection_directory_id ) {
2019-03-25 00:53:12 +01:00
// Take a song and update its path
Song updated_song = song ;
2019-07-02 00:48:40 +02:00
updated_song . set_source ( source_ ) ;
2021-07-02 01:15:16 +02:00
updated_song . set_url ( QUrl : : fromLocalFile ( new_file . absoluteFilePath ( ) ) ) ;
updated_song . set_basefilename ( new_file . fileName ( ) ) ;
updated_song . InitArtManual ( ) ;
2021-09-27 19:29:12 +02:00
if ( updated_song . is_collection_song ( ) & & new_collection_directory_id ) {
updated_song . set_directory_id ( new_collection_directory_id . value ( ) ) ;
}
2021-07-02 01:15:16 +02:00
AddOrUpdateSongs ( SongList ( ) < < updated_song ) ;
2019-03-25 00:53:12 +01:00
}
2023-01-08 15:40:54 +01:00
void CollectionBackend : : AddOrUpdateSubdirs ( const CollectionSubdirectoryList & subdirs ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
ScopedTransaction transaction ( & db ) ;
2023-01-08 15:40:54 +01:00
for ( const CollectionSubdirectory & subdir : subdirs ) {
2018-02-27 18:06:05 +01:00
if ( subdir . mtime = = 0 ) {
// Delete the subdirectory
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " DELETE FROM %1 WHERE directory_id = :id AND path = :path " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , subdir . directory_id ) ;
q . BindValue ( QStringLiteral ( " :path " ) , subdir . path ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
}
else {
// See if this subdirectory already exists in the database
2021-09-09 21:45:46 +02:00
bool exists = false ;
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID FROM %1 WHERE directory_id = :id AND path = :path " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , subdir . directory_id ) ;
q . BindValue ( QStringLiteral ( " :path " ) , subdir . path ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
exists = q . next ( ) ;
}
if ( exists ) {
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET mtime = :mtime WHERE directory_id = :id AND path = :path " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :mtime " ) , subdir . mtime ) ;
q . BindValue ( QStringLiteral ( " :id " ) , subdir . directory_id ) ;
q . BindValue ( QStringLiteral ( " :path " ) , subdir . path ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
}
else {
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " INSERT INTO %1 (directory_id, path, mtime) VALUES (:id, :path, :mtime) " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , subdir . directory_id ) ;
q . BindValue ( QStringLiteral ( " :path " ) , subdir . path ) ;
q . BindValue ( QStringLiteral ( " :mtime " ) , subdir . mtime ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
}
}
}
2021-09-09 21:45:46 +02:00
2018-02-27 18:06:05 +01:00
transaction . Commit ( ) ;
}
2021-10-24 16:08:17 +02:00
SongList CollectionBackend : : GetAllSongs ( ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
2021-10-24 16:08:17 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SongList ( ) ;
}
SongList songs ;
while ( q . next ( ) ) {
Song song ;
song . InitFromQuery ( q , true ) ;
songs < < song ;
}
return songs ;
}
2020-05-12 18:45:24 +02:00
void CollectionBackend : : AddOrUpdateSongsAsync ( const SongList & songs ) {
2021-09-09 21:53:14 +02:00
QMetaObject : : invokeMethod ( this , " AddOrUpdateSongs " , Qt : : QueuedConnection , Q_ARG ( SongList , songs ) ) ;
2020-05-12 18:45:24 +02:00
}
2018-02-27 18:06:05 +01:00
void CollectionBackend : : AddOrUpdateSongs ( const SongList & songs ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
ScopedTransaction transaction ( & db ) ;
SongList added_songs ;
SongList deleted_songs ;
for ( const Song & song : songs ) {
2019-06-07 23:02:43 +02:00
2018-02-27 18:06:05 +01:00
// Do a sanity check first - make sure the song's directory still exists
2018-05-01 00:41:33 +02:00
// This is to fix a possible race condition when a directory is removed while CollectionWatcher is scanning it.
2018-02-27 18:06:05 +01:00
if ( ! dirs_table_ . isEmpty ( ) ) {
2021-09-09 21:45:46 +02:00
SqlQuery check_dir ( db ) ;
2024-04-09 23:20:26 +02:00
check_dir . prepare ( QStringLiteral ( " SELECT ROWID FROM %1 WHERE ROWID = :id " ) . arg ( dirs_table_ ) ) ;
check_dir . BindValue ( QStringLiteral ( " :id " ) , song . directory_id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! check_dir . Exec ( ) ) {
db_ - > ReportErrors ( check_dir ) ;
return ;
}
if ( ! check_dir . next ( ) ) continue ;
2018-02-27 18:06:05 +01:00
}
2019-06-07 23:02:43 +02:00
if ( song . id ( ) ! = - 1 ) { // This song exists in the DB.
2018-02-27 18:06:05 +01:00
// Get the previous song data first
Song old_song ( GetSongById ( song . id ( ) ) ) ;
if ( ! old_song . is_valid ( ) ) continue ;
// Update
2021-09-09 21:45:46 +02:00
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET %2 WHERE ROWID = :id " ) . arg ( songs_table_ , Song : : kUpdateSpec ) ) ;
2021-09-09 21:45:46 +02:00
song . BindToQuery ( & q ) ;
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :id " ) , song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET %2 WHERE ROWID = :id " ) . arg ( fts_table_ , Song : : kFtsUpdateSpec ) ) ;
2021-09-09 21:45:46 +02:00
song . BindToFtsQuery ( & q ) ;
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :id " ) , song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
2018-02-27 18:06:05 +01:00
deleted_songs < < old_song ;
added_songs < < song ;
2019-06-07 23:02:43 +02:00
continue ;
}
2020-04-13 04:17:45 +02:00
else if ( ! song . song_id ( ) . isEmpty ( ) ) { // Song has a unique id, check if the song exists.
2019-06-07 23:02:43 +02:00
// Get the previous song data first
Song old_song ( GetSongBySongId ( song . song_id ( ) ) ) ;
if ( old_song . is_valid ( ) & & old_song . id ( ) ! = - 1 ) {
Song new_song = song ;
new_song . set_id ( old_song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
// Update
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET %2 WHERE ROWID = :id " ) . arg ( songs_table_ , Song : : kUpdateSpec ) ) ;
2021-09-09 21:45:46 +02:00
new_song . BindToQuery ( & q ) ;
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :id " ) , new_song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET %2 WHERE ROWID = :id " ) . arg ( fts_table_ , Song : : kFtsUpdateSpec ) ) ;
2021-09-09 21:45:46 +02:00
new_song . BindToFtsQuery ( & q ) ;
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :id " ) , new_song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
2019-06-07 23:02:43 +02:00
deleted_songs < < old_song ;
added_songs < < new_song ;
continue ;
}
2018-02-27 18:06:05 +01:00
}
2019-06-07 23:02:43 +02:00
// Create new song
2021-09-09 21:45:46 +02:00
int id = - 1 ;
2022-03-22 21:09:05 +01:00
{ // Insert the row and create a new ID
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " INSERT INTO %1 (%2) VALUES (%3) " ) . arg ( songs_table_ , Song : : kColumnSpec , Song : : kBindSpec ) ) ;
2021-09-09 21:45:46 +02:00
song . BindToQuery ( & q ) ;
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
// Get the new ID
id = q . lastInsertId ( ) . toInt ( ) ;
}
2019-06-07 23:02:43 +02:00
2021-09-19 15:41:36 +02:00
if ( id = = - 1 ) return ;
2022-03-22 21:09:05 +01:00
{ // Add to the FTS index
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " INSERT INTO %1 (ROWID, %2) VALUES (:id, %3) " ) . arg ( fts_table_ , Song : : kFtsColumnSpec , Song : : kFtsBindSpec ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , id ) ;
2021-09-09 21:45:46 +02:00
song . BindToFtsQuery ( & q ) ;
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
2019-06-07 23:02:43 +02:00
2021-09-19 15:41:36 +02:00
Song song_copy ( song ) ;
song_copy . set_id ( id ) ;
added_songs < < song_copy ;
2019-06-07 23:02:43 +02:00
2018-02-27 18:06:05 +01:00
}
transaction . Commit ( ) ;
if ( ! deleted_songs . isEmpty ( ) ) emit SongsDeleted ( deleted_songs ) ;
if ( ! added_songs . isEmpty ( ) ) emit SongsDiscovered ( added_songs ) ;
UpdateTotalSongCountAsync ( ) ;
UpdateTotalArtistCountAsync ( ) ;
UpdateTotalAlbumCountAsync ( ) ;
}
2021-09-19 15:41:36 +02:00
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.
2021-10-30 02:21:29 +02:00
QList new_songs_list = new_songs . values ( ) ;
for ( const Song & new_song : new_songs_list ) {
2021-09-19 15:41:36 +02:00
if ( old_songs . contains ( new_song . song_id ( ) ) ) {
Song old_song = old_songs [ new_song . song_id ( ) ] ;
2023-03-25 18:35:14 +01:00
if ( ! new_song . IsAllMetadataEqual ( old_song ) | | ! new_song . IsFingerprintEqual ( old_song ) ) { // Update existing song.
2021-09-19 15:41:36 +02:00
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET %2 WHERE ROWID = :id " ) . arg ( songs_table_ , Song : : kUpdateSpec ) ) ;
2021-09-19 15:41:36 +02:00
new_song . BindToQuery ( & q ) ;
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :id " ) , old_song . id ( ) ) ;
2021-09-19 15:41:36 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET %2 WHERE ROWID = :id " ) . arg ( fts_table_ , Song : : kFtsUpdateSpec ) ) ;
2021-09-19 15:41:36 +02:00
new_song . BindToFtsQuery ( & q ) ;
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :id " ) , old_song . id ( ) ) ;
2021-09-19 15:41:36 +02:00
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 ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " INSERT INTO %1 (%2) VALUES (%3) " ) . arg ( songs_table_ , Song : : kColumnSpec , Song : : kBindSpec ) ) ;
2021-09-19 15:41:36 +02:00
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 ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " INSERT INTO %1 (ROWID, %2) VALUES (:id, %3) " ) . arg ( fts_table_ , Song : : kFtsColumnSpec , Song : : kFtsBindSpec ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , id ) ;
2021-09-19 15:41:36 +02:00
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
2021-10-30 02:21:29 +02:00
QList old_songs_list = old_songs . values ( ) ;
for ( const Song & old_song : old_songs_list ) {
2021-09-19 15:41:36 +02:00
if ( ! new_songs . contains ( old_song . song_id ( ) ) ) {
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " DELETE FROM %1 WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , old_song . id ( ) ) ;
2021-09-19 15:41:36 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
{
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " DELETE FROM %1 WHERE ROWID = :id " ) . arg ( fts_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , old_song . id ( ) ) ;
2021-09-19 15:41:36 +02:00
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 ( ) ;
}
2018-02-27 18:06:05 +01:00
void CollectionBackend : : UpdateMTimesOnly ( const SongList & songs ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET mtime = :mtime WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
2018-02-27 18:06:05 +01:00
ScopedTransaction transaction ( & db ) ;
for ( const Song & song : songs ) {
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :mtime " ) , song . mtime ( ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
}
transaction . Commit ( ) ;
}
void CollectionBackend : : DeleteSongs ( const SongList & songs ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery remove ( db ) ;
2024-04-09 23:20:26 +02:00
remove . prepare ( QStringLiteral ( " DELETE FROM %1 WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery remove_fts ( db ) ;
2024-04-09 23:20:26 +02:00
remove_fts . prepare ( QStringLiteral ( " DELETE FROM %1 WHERE ROWID = :id " ) . arg ( fts_table_ ) ) ;
2018-02-27 18:06:05 +01:00
ScopedTransaction transaction ( & db ) ;
for ( const Song & song : songs ) {
2024-04-09 23:20:26 +02:00
remove . BindValue ( QStringLiteral ( " :id " ) , song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! remove . Exec ( ) ) {
db_ - > ReportErrors ( remove ) ;
return ;
}
2018-02-27 18:06:05 +01:00
2024-04-09 23:20:26 +02:00
remove_fts . BindValue ( QStringLiteral ( " :id " ) , song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! remove_fts . Exec ( ) ) {
db_ - > ReportErrors ( remove_fts ) ;
return ;
}
2018-02-27 18:06:05 +01:00
}
transaction . Commit ( ) ;
emit SongsDeleted ( songs ) ;
UpdateTotalSongCountAsync ( ) ;
UpdateTotalArtistCountAsync ( ) ;
UpdateTotalAlbumCountAsync ( ) ;
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : MarkSongsUnavailable ( const SongList & songs , const bool unavailable ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2024-01-03 00:44:54 +01:00
SqlQuery query ( db ) ;
2024-04-09 23:20:26 +02:00
query . prepare ( QStringLiteral ( " UPDATE %1 SET unavailable = %2 WHERE ROWID = :id " ) . arg ( songs_table_ ) . arg ( static_cast < int > ( unavailable ) ) ) ;
2018-02-27 18:06:05 +01:00
ScopedTransaction transaction ( & db ) ;
for ( const Song & song : songs ) {
2024-04-09 23:20:26 +02:00
query . BindValue ( QStringLiteral ( " :id " ) , song . id ( ) ) ;
2024-01-03 00:44:54 +01:00
if ( ! query . Exec ( ) ) {
db_ - > ReportErrors ( query ) ;
2021-09-09 21:45:46 +02:00
return ;
}
2018-02-27 18:06:05 +01:00
}
transaction . Commit ( ) ;
2021-03-14 23:08:05 +01:00
if ( unavailable ) {
emit SongsDeleted ( songs ) ;
}
else {
emit SongsDiscovered ( songs ) ;
}
2018-02-27 18:06:05 +01:00
UpdateTotalSongCountAsync ( ) ;
UpdateTotalArtistCountAsync ( ) ;
UpdateTotalAlbumCountAsync ( ) ;
}
2023-01-08 15:40:54 +01:00
QStringList CollectionBackend : : GetAll ( const QString & column , const CollectionFilterOptions & filter_options ) {
2018-10-02 00:38:52 +02:00
2021-04-10 03:20:25 +02:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2023-01-08 15:40:54 +01:00
CollectionQuery query ( db , songs_table_ , fts_table_ , filter_options ) ;
2024-04-11 02:56:01 +02:00
query . SetColumnSpec ( QStringLiteral ( " DISTINCT " ) + column ) ;
2018-02-27 18:06:05 +01:00
query . AddCompilationRequirement ( false ) ;
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return QStringList ( ) ;
}
2018-02-27 18:06:05 +01:00
QStringList ret ;
while ( query . Next ( ) ) {
ret < < query . Value ( 0 ) . toString ( ) ;
}
return ret ;
}
2023-01-08 15:40:54 +01:00
QStringList CollectionBackend : : GetAllArtists ( const CollectionFilterOptions & opt ) {
2018-08-29 21:42:24 +02:00
2024-04-09 23:20:26 +02:00
return GetAll ( QStringLiteral ( " artist " ) , opt ) ;
2018-02-27 18:06:05 +01:00
}
2023-01-08 15:40:54 +01:00
QStringList CollectionBackend : : GetAllArtistsWithAlbums ( const CollectionFilterOptions & opt ) {
2018-02-27 18:06:05 +01:00
2021-04-10 03:20:25 +02:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2018-02-27 18:06:05 +01:00
// Albums with 'albumartist' field set:
2021-04-10 03:20:25 +02:00
CollectionQuery query ( db , songs_table_ , fts_table_ , opt ) ;
2024-04-09 23:20:26 +02:00
query . SetColumnSpec ( QStringLiteral ( " DISTINCT albumartist " ) ) ;
2018-02-27 18:06:05 +01:00
query . AddCompilationRequirement ( false ) ;
2024-04-11 02:56:01 +02:00
query . AddWhere ( QStringLiteral ( " album " ) , QLatin1String ( " " ) , QStringLiteral ( " != " ) ) ;
2018-02-27 18:06:05 +01:00
2019-06-07 23:02:43 +02:00
// Albums with no 'albumartist' (extract 'artist'):
2021-04-10 03:20:25 +02:00
CollectionQuery query2 ( db , songs_table_ , fts_table_ , opt ) ;
2024-04-09 23:20:26 +02:00
query2 . SetColumnSpec ( QStringLiteral ( " DISTINCT artist " ) ) ;
2018-02-27 18:06:05 +01:00
query2 . AddCompilationRequirement ( false ) ;
2024-04-11 02:56:01 +02:00
query2 . AddWhere ( QStringLiteral ( " album " ) , QLatin1String ( " " ) , QStringLiteral ( " != " ) ) ;
query2 . AddWhere ( QStringLiteral ( " albumartist " ) , QLatin1String ( " " ) , QStringLiteral ( " = " ) ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return QStringList ( ) ;
}
if ( ! query2 . Exec ( ) ) {
ReportErrors ( query2 ) ;
2021-04-10 03:20:25 +02:00
return QStringList ( ) ;
2018-02-27 18:06:05 +01:00
}
QSet < QString > artists ;
while ( query . Next ( ) ) {
artists < < query . Value ( 0 ) . toString ( ) ;
}
while ( query2 . Next ( ) ) {
artists < < query2 . Value ( 0 ) . toString ( ) ;
}
2019-12-21 18:19:09 +01:00
return QStringList ( artists . values ( ) ) ;
2019-03-25 00:53:12 +01:00
2018-02-27 18:06:05 +01:00
}
2023-01-08 15:40:54 +01:00
CollectionBackend : : AlbumList CollectionBackend : : GetAllAlbums ( const CollectionFilterOptions & opt ) {
2019-04-18 00:45:32 +02:00
return GetAlbums ( QString ( ) , false , opt ) ;
2018-02-27 18:06:05 +01:00
}
2023-01-08 15:40:54 +01:00
CollectionBackend : : AlbumList CollectionBackend : : GetAlbumsByArtist ( const QString & artist , const CollectionFilterOptions & opt ) {
2019-04-18 00:45:32 +02:00
return GetAlbums ( artist , false , opt ) ;
2018-02-27 18:06:05 +01:00
}
2023-01-08 15:40:54 +01:00
SongList CollectionBackend : : GetArtistSongs ( const QString & effective_albumartist , const CollectionFilterOptions & opt ) {
2021-02-26 21:03:51 +01:00
2021-04-10 03:20:25 +02:00
QSqlDatabase db ( db_ - > Connect ( ) ) ;
QMutexLocker l ( db_ - > Mutex ( ) ) ;
CollectionQuery query ( db , songs_table_ , fts_table_ , opt ) ;
2021-02-26 21:03:51 +01:00
query . AddCompilationRequirement ( false ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " effective_albumartist " ) , effective_albumartist ) ;
2021-04-10 03:20:25 +02:00
2021-09-09 21:45:46 +02:00
SongList songs ;
if ( ! ExecCollectionQuery ( & query , songs ) ) {
ReportErrors ( query ) ;
}
return songs ;
2021-02-26 21:03:51 +01:00
}
2023-01-08 15:40:54 +01:00
SongList CollectionBackend : : GetAlbumSongs ( const QString & effective_albumartist , const QString & album , const CollectionFilterOptions & opt ) {
2021-02-26 21:03:51 +01:00
2021-04-10 03:20:25 +02:00
QSqlDatabase db ( db_ - > Connect ( ) ) ;
QMutexLocker l ( db_ - > Mutex ( ) ) ;
CollectionQuery query ( db , songs_table_ , fts_table_ , opt ) ;
2018-02-27 18:06:05 +01:00
query . AddCompilationRequirement ( false ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " effective_albumartist " ) , effective_albumartist ) ;
query . AddWhere ( QStringLiteral ( " album " ) , album ) ;
2021-04-10 03:20:25 +02:00
2021-09-09 21:45:46 +02:00
SongList songs ;
if ( ! ExecCollectionQuery ( & query , songs ) ) {
ReportErrors ( query ) ;
}
return songs ;
2021-02-26 21:03:51 +01:00
2018-02-27 18:06:05 +01:00
}
2023-01-08 15:40:54 +01:00
SongList CollectionBackend : : GetSongsByAlbum ( const QString & album , const CollectionFilterOptions & opt ) {
2021-02-26 21:03:51 +01:00
2021-04-10 03:20:25 +02:00
QSqlDatabase db ( db_ - > Connect ( ) ) ;
QMutexLocker l ( db_ - > Mutex ( ) ) ;
CollectionQuery query ( db , songs_table_ , fts_table_ , opt ) ;
2018-02-27 18:06:05 +01:00
query . AddCompilationRequirement ( false ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " album " ) , album ) ;
2021-04-10 03:20:25 +02:00
2021-09-09 21:45:46 +02:00
SongList songs ;
if ( ! ExecCollectionQuery ( & query , songs ) ) {
ReportErrors ( query ) ;
}
return songs ;
2021-02-26 21:03:51 +01:00
2018-02-27 18:06:05 +01:00
}
2021-09-09 21:45:46 +02:00
bool CollectionBackend : : ExecCollectionQuery ( CollectionQuery * query , SongList & songs ) {
2018-02-27 18:06:05 +01:00
2024-04-11 02:56:01 +02:00
query - > SetColumnSpec ( QStringLiteral ( " %songs_table.ROWID, " ) + Song : : kColumnSpec ) ;
2021-04-10 03:20:25 +02:00
2021-09-09 21:45:46 +02:00
if ( ! query - > Exec ( ) ) return false ;
2018-02-27 18:06:05 +01:00
while ( query - > Next ( ) ) {
2019-11-14 00:09:35 +01:00
Song song ( source_ ) ;
2018-02-27 18:06:05 +01:00
song . InitFromQuery ( * query , true ) ;
2021-09-09 21:45:46 +02:00
songs < < song ;
2018-02-27 18:06:05 +01:00
}
2021-09-09 21:45:46 +02:00
return true ;
2018-08-29 21:42:24 +02:00
2018-02-27 18:06:05 +01:00
}
2021-09-19 15:41:36 +02:00
bool CollectionBackend : : ExecCollectionQuery ( CollectionQuery * query , SongMap & songs ) {
2024-04-11 02:56:01 +02:00
query - > SetColumnSpec ( QStringLiteral ( " %songs_table.ROWID, " ) + Song : : kColumnSpec ) ;
2021-09-19 15:41:36 +02:00
if ( ! query - > Exec ( ) ) return false ;
while ( query - > Next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( * query , true ) ;
songs . insert ( song . song_id ( ) , song ) ;
}
return true ;
}
2020-09-10 22:05:12 +02:00
Song CollectionBackend : : GetSongById ( const int id ) {
2021-04-10 03:20:25 +02:00
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
return GetSongById ( id , db ) ;
2021-04-10 03:20:25 +02:00
2018-02-27 18:06:05 +01:00
}
SongList CollectionBackend : : GetSongsById ( const QList < int > & ids ) {
2021-04-10 03:20:25 +02:00
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
QStringList str_ids ;
2021-06-20 19:04:08 +02:00
str_ids . reserve ( ids . count ( ) ) ;
for ( const int id : ids ) {
2018-02-27 18:06:05 +01:00
str_ids < < QString : : number ( id ) ;
}
return GetSongsById ( str_ids , db ) ;
2021-04-10 03:20:25 +02:00
2018-02-27 18:06:05 +01:00
}
SongList CollectionBackend : : GetSongsById ( const QStringList & ids ) {
2021-04-10 03:20:25 +02:00
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
return GetSongsById ( ids , db ) ;
2021-04-10 03:20:25 +02:00
2018-02-27 18:06:05 +01:00
}
SongList CollectionBackend : : GetSongsByForeignId ( const QStringList & ids , const QString & table , const QString & column ) {
2019-03-22 23:14:25 +01:00
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2024-04-09 23:20:26 +02:00
QString in = ids . join ( QStringLiteral ( " , " ) ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT %3.ROWID, %2, %3.%4 FROM %3, %1 WHERE %3.%4 IN (in) AND %1.ROWID = %3.ROWID AND unavailable = 0 " ) . arg ( songs_table_ , Song : : kColumnSpec , table , column , in ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SongList ( ) ;
}
2018-02-27 18:06:05 +01:00
QVector < Song > ret ( ids . count ( ) ) ;
while ( q . next ( ) ) {
2021-03-21 18:53:02 +01:00
const QString foreign_id = q . value ( static_cast < int > ( Song : : kColumns . count ( ) ) + 1 ) . toString ( ) ;
2021-10-30 02:21:29 +02:00
const qint64 index = ids . indexOf ( foreign_id ) ;
2018-02-27 18:06:05 +01:00
if ( index = = - 1 ) continue ;
ret [ index ] . InitFromQuery ( q , true ) ;
}
return ret . toList ( ) ;
2019-03-22 23:14:25 +01:00
2018-02-27 18:06:05 +01:00
}
2020-09-10 22:05:12 +02:00
Song CollectionBackend : : GetSongById ( const int id , QSqlDatabase & db ) {
2021-04-10 03:20:25 +02:00
2018-02-27 18:06:05 +01:00
SongList list = GetSongsById ( QStringList ( ) < < QString : : number ( id ) , db ) ;
if ( list . isEmpty ( ) ) return Song ( ) ;
return list . first ( ) ;
2021-04-10 03:20:25 +02:00
2018-02-27 18:06:05 +01:00
}
SongList CollectionBackend : : GetSongsById ( const QStringList & ids , QSqlDatabase & db ) {
2019-03-22 23:14:25 +01:00
2024-04-09 23:20:26 +02:00
QString in = ids . join ( QStringLiteral ( " , " ) ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE ROWID IN (%3) " ) . arg ( Song : : kColumnSpec , songs_table_ , in ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SongList ( ) ;
}
2018-02-27 18:06:05 +01:00
SongList ret ;
while ( q . next ( ) ) {
2019-11-14 00:09:35 +01:00
Song song ( source_ ) ;
2018-02-27 18:06:05 +01:00
song . InitFromQuery ( q , true ) ;
ret < < song ;
}
return ret ;
2019-03-22 23:14:25 +01:00
2018-02-27 18:06:05 +01:00
}
2019-11-25 00:28:49 +01:00
Song CollectionBackend : : GetSongByUrl ( const QUrl & url , const qint64 beginning ) {
2019-03-22 23:14:25 +01:00
2019-11-25 22:29:12 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND beginning = :beginning AND unavailable = 0 " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
2019-11-25 22:29:12 +01:00
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :url1 " ) , url ) ;
q . BindValue ( QStringLiteral ( " :url2 " ) , url . toString ( ) ) ;
q . BindValue ( QStringLiteral ( " :url3 " ) , url . toString ( QUrl : : FullyEncoded ) ) ;
q . BindValue ( QStringLiteral ( " :url4 " ) , url . toEncoded ( ) ) ;
q . BindValue ( QStringLiteral ( " :beginning " ) , beginning ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return Song ( ) ;
2018-02-27 18:06:05 +01:00
}
2019-11-25 22:29:12 +01:00
2021-09-09 21:45:46 +02:00
if ( ! q . next ( ) ) {
return Song ( ) ;
}
Song song ( source_ ) ;
song . InitFromQuery ( q , true ) ;
2018-02-27 18:06:05 +01:00
return song ;
2019-03-22 23:14:25 +01:00
2018-02-27 18:06:05 +01:00
}
2024-04-04 22:22:02 +02:00
Song CollectionBackend : : GetSongByUrlAndTrack ( const QUrl & url , const int track ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND track = :track AND unavailable = 0 " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
2024-04-04 22:22:02 +02:00
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :url1 " ) , url ) ;
q . BindValue ( QStringLiteral ( " :url2 " ) , url . toString ( ) ) ;
q . BindValue ( QStringLiteral ( " :url3 " ) , url . toString ( QUrl : : FullyEncoded ) ) ;
q . BindValue ( QStringLiteral ( " :url4 " ) , url . toEncoded ( ) ) ;
q . BindValue ( QStringLiteral ( " :track " ) , track ) ;
2024-04-04 22:22:02 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return Song ( ) ;
}
if ( ! q . next ( ) ) {
return Song ( ) ;
}
Song song ( source_ ) ;
song . InitFromQuery ( q , true ) ;
return song ;
}
2021-06-05 02:50:20 +02:00
SongList CollectionBackend : : GetSongsByUrl ( const QUrl & url , const bool unavailable ) {
2019-03-22 23:14:25 +01:00
2019-11-25 22:29:12 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = :unavailable " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
2019-11-25 22:29:12 +01:00
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :url1 " ) , url ) ;
q . BindValue ( QStringLiteral ( " :url2 " ) , url . toString ( ) ) ;
q . BindValue ( QStringLiteral ( " :url3 " ) , url . toString ( QUrl : : FullyEncoded ) ) ;
q . BindValue ( QStringLiteral ( " :url4 " ) , url . toEncoded ( ) ) ;
q . BindValue ( QStringLiteral ( " :unavailable " ) , ( unavailable ? 1 : 0 ) ) ;
2019-11-25 22:29:12 +01:00
SongList songs ;
2021-09-09 21:45:46 +02:00
if ( q . Exec ( ) ) {
2019-11-25 22:43:09 +01:00
while ( q . next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( q , true ) ;
songs < < song ;
}
2018-02-27 18:06:05 +01:00
}
2021-09-09 21:45:46 +02:00
else {
db_ - > ReportErrors ( q ) ;
}
2019-11-25 22:29:12 +01:00
return songs ;
2019-03-22 23:14:25 +01:00
2018-02-27 18:06:05 +01:00
}
2019-06-07 23:02:43 +02:00
2020-04-13 03:39:51 +02:00
Song CollectionBackend : : GetSongBySongId ( const QString & song_id ) {
2019-06-07 23:02:43 +02:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2020-04-13 03:39:51 +02:00
return GetSongBySongId ( song_id , db ) ;
2019-06-07 23:02:43 +02:00
}
SongList CollectionBackend : : GetSongsBySongId ( const QStringList & song_ids ) {
2020-04-13 03:39:51 +02:00
2019-06-07 23:02:43 +02:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
return GetSongsBySongId ( song_ids , db ) ;
2020-04-13 03:39:51 +02:00
2019-06-07 23:02:43 +02:00
}
2020-04-13 03:39:51 +02:00
Song CollectionBackend : : GetSongBySongId ( const QString & song_id , QSqlDatabase & db ) {
SongList list = GetSongsBySongId ( QStringList ( ) < < song_id , db ) ;
2019-06-07 23:02:43 +02:00
if ( list . isEmpty ( ) ) return Song ( ) ;
return list . first ( ) ;
2020-04-13 03:39:51 +02:00
2019-06-07 23:02:43 +02:00
}
SongList CollectionBackend : : GetSongsBySongId ( const QStringList & song_ids , QSqlDatabase & db ) {
2020-10-10 23:44:42 +02:00
QStringList song_ids2 ;
2021-06-20 19:04:08 +02:00
song_ids2 . reserve ( song_ids . count ( ) ) ;
2020-10-10 23:44:42 +02:00
for ( const QString & song_id : song_ids ) {
2024-04-11 02:56:01 +02:00
song_ids2 < < QLatin1Char ( ' \' ' ) + song_id + QLatin1Char ( ' \' ' ) ;
2020-10-10 23:44:42 +02:00
}
2024-04-11 02:56:01 +02:00
QString in = song_ids2 . join ( QLatin1Char ( ' , ' ) ) ;
2019-06-07 23:02:43 +02:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE SONG_ID IN (%3) " ) . arg ( Song : : kColumnSpec , songs_table_ , in ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SongList ( ) ;
}
2019-06-07 23:02:43 +02:00
SongList ret ;
while ( q . next ( ) ) {
2019-11-14 00:09:35 +01:00
Song song ( source_ ) ;
2019-06-07 23:02:43 +02:00
song . InitFromQuery ( q , true ) ;
ret < < song ;
}
2021-09-09 21:45:46 +02:00
2019-06-07 23:02:43 +02:00
return ret ;
}
2021-04-25 21:16:44 +02:00
SongList CollectionBackend : : GetSongsByFingerprint ( const QString & fingerprint ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE fingerprint = :fingerprint " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :fingerprint " ) , fingerprint ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SongList ( ) ;
}
2021-04-25 21:16:44 +02:00
SongList songs ;
while ( q . next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( q , true ) ;
songs < < song ;
}
return songs ;
}
2023-01-08 15:40:54 +01:00
CollectionBackend : : AlbumList CollectionBackend : : GetCompilationAlbums ( const CollectionFilterOptions & opt ) {
2019-04-18 00:45:32 +02:00
return GetAlbums ( QString ( ) , true , opt ) ;
2018-02-27 18:06:05 +01:00
}
2023-01-08 15:40:54 +01:00
SongList CollectionBackend : : GetCompilationSongs ( const QString & album , const CollectionFilterOptions & opt ) {
2018-02-27 18:06:05 +01:00
2021-04-10 03:20:25 +02:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
CollectionQuery query ( db , songs_table_ , fts_table_ , opt ) ;
2024-04-11 02:56:01 +02:00
query . SetColumnSpec ( QStringLiteral ( " %songs_table.ROWID, " ) + Song : : kColumnSpec ) ;
2018-02-27 18:06:05 +01:00
query . AddCompilationRequirement ( true ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " album " ) , album ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return SongList ( ) ;
}
2018-02-27 18:06:05 +01:00
SongList ret ;
while ( query . Next ( ) ) {
2019-11-14 00:09:35 +01:00
Song song ( source_ ) ;
2018-02-27 18:06:05 +01:00
song . InitFromQuery ( query , true ) ;
ret < < song ;
}
return ret ;
2018-08-29 21:42:24 +02:00
2018-02-27 18:06:05 +01:00
}
2021-01-26 16:48:04 +01:00
void CollectionBackend : : CompilationsNeedUpdating ( ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2018-05-01 00:41:33 +02:00
// Look for albums that have songs by more than one 'effective album artist' in the same directory
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT effective_albumartist, album, url, compilation_detected FROM %1 WHERE unavailable = 0 ORDER BY album " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
QMap < QString , CompilationInfo > compilation_info ;
while ( q . next ( ) ) {
QString artist = q . value ( 0 ) . toString ( ) ;
QString album = q . value ( 1 ) . toString ( ) ;
2019-11-03 23:23:04 +01:00
QUrl url = QUrl : : fromEncoded ( q . value ( 2 ) . toString ( ) . toUtf8 ( ) ) ;
2018-02-27 18:06:05 +01:00
bool compilation_detected = q . value ( 3 ) . toBool ( ) ;
// Ignore songs that don't have an album field set
if ( album . isEmpty ( ) ) continue ;
// Find the directory the song is in
2022-03-22 21:09:05 +01:00
QString directory = url . toString ( QUrl : : PreferLocalFile | QUrl : : RemoveFilename ) ;
2018-02-27 18:06:05 +01:00
2019-11-03 23:23:04 +01:00
CompilationInfo & info = compilation_info [ directory + album ] ;
info . urls < < url ;
2021-09-09 21:45:46 +02:00
if ( ! info . artists . contains ( artist ) ) {
2019-11-13 21:27:04 +01:00
info . artists < < artist ;
2021-09-09 21:45:46 +02:00
}
2019-11-03 23:23:04 +01:00
if ( compilation_detected ) info . has_compilation_detected + + ;
else info . has_not_compilation_detected + + ;
2018-02-27 18:06:05 +01:00
}
// Now mark the songs that we think are in compilations
SongList deleted_songs ;
SongList added_songs ;
ScopedTransaction transaction ( & db ) ;
QMap < QString , CompilationInfo > : : const_iterator it = compilation_info . constBegin ( ) ;
for ( ; it ! = compilation_info . constEnd ( ) ; + + it ) {
const CompilationInfo & info = it . value ( ) ;
2019-11-13 21:27:04 +01:00
// If there were more than one 'effective album artist' for this album directory, then it's a compilation.
2018-02-27 18:06:05 +01:00
2019-11-03 23:23:04 +01:00
for ( const QUrl & url : info . urls ) {
if ( info . artists . count ( ) > 1 ) { // This directory+album is a compilation.
2021-09-09 21:45:46 +02:00
if ( info . has_not_compilation_detected > 0 ) { // Run updates if any of the songs is not marked as compilations.
UpdateCompilations ( db , deleted_songs , added_songs , url , true ) ;
}
2019-11-03 23:23:04 +01:00
}
else {
2021-09-09 21:45:46 +02:00
if ( info . has_compilation_detected > 0 ) {
UpdateCompilations ( db , deleted_songs , added_songs , url , false ) ;
}
2019-11-03 23:23:04 +01:00
}
2018-02-27 18:06:05 +01:00
}
}
transaction . Commit ( ) ;
if ( ! deleted_songs . isEmpty ( ) ) {
emit SongsDeleted ( deleted_songs ) ;
emit SongsDiscovered ( added_songs ) ;
}
2019-11-03 23:23:04 +01:00
2018-02-27 18:06:05 +01:00
}
2021-09-09 21:45:46 +02:00
bool CollectionBackend : : UpdateCompilations ( const QSqlDatabase & db , SongList & deleted_songs , SongList & added_songs , const QUrl & url , const bool compilation_detected ) {
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
{ // Get song, so we can tell the model its updated
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0 " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :url1 " ) , url ) ;
q . BindValue ( QStringLiteral ( " :url2 " ) , url . toString ( ) ) ;
q . BindValue ( QStringLiteral ( " :url3 " ) , url . toString ( QUrl : : FullyEncoded ) ) ;
q . BindValue ( QStringLiteral ( " :url4 " ) , url . toEncoded ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( q . Exec ( ) ) {
while ( q . next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( q , true ) ;
deleted_songs < < song ;
song . set_compilation_detected ( compilation_detected ) ;
added_songs < < song ;
}
}
else {
db_ - > ReportErrors ( q ) ;
return false ;
2019-11-25 22:43:09 +01:00
}
2018-02-27 18:06:05 +01:00
}
2019-11-03 23:23:04 +01:00
// Update the song
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET compilation_detected = :compilation_detected, compilation_effective = ((compilation OR :compilation_detected OR compilation_on) AND NOT compilation_off) + 0 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0 " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :compilation_detected " ) , static_cast < int > ( compilation_detected ) ) ;
q . BindValue ( QStringLiteral ( " :url1 " ) , url ) ;
q . BindValue ( QStringLiteral ( " :url2 " ) , url . toString ( ) ) ;
q . BindValue ( QStringLiteral ( " :url3 " ) , url . toString ( QUrl : : FullyEncoded ) ) ;
q . BindValue ( QStringLiteral ( " :url4 " ) , url . toEncoded ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return false ;
}
return true ;
2019-04-18 00:45:32 +02:00
2018-02-27 18:06:05 +01:00
}
2023-01-08 15:40:54 +01:00
CollectionBackend : : AlbumList CollectionBackend : : GetAlbums ( const QString & artist , const bool compilation_required , const CollectionFilterOptions & opt ) {
2018-02-27 18:06:05 +01:00
2021-04-10 03:20:25 +02:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
CollectionQuery query ( db , songs_table_ , fts_table_ , opt ) ;
2024-04-09 23:20:26 +02:00
query . SetColumnSpec ( QStringLiteral ( " url, filetype, cue_path, effective_albumartist, album, compilation_effective, art_embedded, art_automatic, art_manual, art_unset " ) ) ;
query . SetOrderBy ( QStringLiteral ( " effective_albumartist, album, url " ) ) ;
2018-02-27 18:06:05 +01:00
2020-04-23 21:08:28 +02:00
if ( compilation_required ) {
2018-02-27 18:06:05 +01:00
query . AddCompilationRequirement ( true ) ;
}
2019-04-18 00:45:32 +02:00
else if ( ! artist . isEmpty ( ) ) {
2018-02-27 18:06:05 +01:00
query . AddCompilationRequirement ( false ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " effective_albumartist " ) , artist ) ;
2018-02-27 18:06:05 +01:00
}
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return AlbumList ( ) ;
}
2018-02-27 18:06:05 +01:00
2020-09-05 18:59:35 +02:00
QMap < QString , Album > albums ;
2018-02-27 18:06:05 +01:00
while ( query . Next ( ) ) {
2023-05-14 11:34:55 +02:00
Album album_info ;
2021-02-26 21:03:51 +01:00
QUrl url = QUrl : : fromEncoded ( query . Value ( 0 ) . toByteArray ( ) ) ;
2023-05-14 11:34:55 +02:00
album_info . filetype = static_cast < Song : : FileType > ( query . Value ( 1 ) . toInt ( ) ) ;
const QString filetype = Song : : TextForFiletype ( album_info . filetype ) ;
album_info . cue_path = query . Value ( 2 ) . toString ( ) ;
const bool is_compilation = query . Value ( 5 ) . toBool ( ) ;
2020-09-10 21:27:07 +02:00
if ( ! is_compilation ) {
2023-05-14 11:34:55 +02:00
album_info . album_artist = query . Value ( 3 ) . toString ( ) ;
2020-09-05 18:59:35 +02:00
}
2018-02-27 18:06:05 +01:00
2023-05-14 11:34:55 +02:00
album_info . album = query . Value ( 4 ) . toString ( ) ;
album_info . art_embedded = query . Value ( 6 ) . toBool ( ) ;
const QString art_automatic = query . Value ( 7 ) . toString ( ) ;
2024-04-09 23:20:26 +02:00
if ( art_automatic . contains ( QRegularExpression ( QStringLiteral ( " ..+:.* " ) ) ) ) {
2023-05-14 11:34:55 +02:00
album_info . art_automatic = QUrl : : fromEncoded ( art_automatic . toUtf8 ( ) ) ;
2019-07-08 22:10:43 +02:00
}
else {
2023-05-14 11:34:55 +02:00
album_info . art_automatic = QUrl : : fromLocalFile ( art_automatic ) ;
2019-07-08 22:10:43 +02:00
}
2023-05-14 11:34:55 +02:00
const QString art_manual = query . Value ( 8 ) . toString ( ) ;
2024-04-09 23:20:26 +02:00
if ( art_manual . contains ( QRegularExpression ( QStringLiteral ( " ..+:.* " ) ) ) ) {
2023-05-14 11:34:55 +02:00
album_info . art_manual = QUrl : : fromEncoded ( art_manual . toUtf8 ( ) ) ;
2019-07-08 22:10:43 +02:00
}
else {
2023-05-14 11:34:55 +02:00
album_info . art_manual = QUrl : : fromLocalFile ( art_manual ) ;
2019-07-08 22:10:43 +02:00
}
2023-05-14 11:34:55 +02:00
album_info . art_unset = query . Value ( 9 ) . toBool ( ) ;
2021-02-26 21:03:51 +01:00
2020-09-05 18:59:35 +02:00
QString key ;
2023-05-14 11:34:55 +02:00
if ( ! album_info . album_artist . isEmpty ( ) ) {
key . append ( album_info . album_artist ) ;
2020-09-05 18:59:35 +02:00
}
2023-05-14 11:34:55 +02:00
if ( ! album_info . album . isEmpty ( ) ) {
2024-04-11 02:56:01 +02:00
if ( ! key . isEmpty ( ) ) key . append ( QLatin1Char ( ' - ' ) ) ;
2023-05-14 11:34:55 +02:00
key . append ( album_info . album ) ;
2021-02-26 21:03:51 +01:00
}
if ( ! filetype . isEmpty ( ) ) {
key . append ( filetype ) ;
2020-09-05 18:59:35 +02:00
}
2018-02-27 18:06:05 +01:00
2020-09-05 18:59:35 +02:00
if ( key . isEmpty ( ) ) continue ;
2021-02-26 21:03:51 +01:00
if ( albums . contains ( key ) ) {
albums [ key ] . urls . append ( url ) ;
}
else {
2023-05-14 11:34:55 +02:00
album_info . urls < < url ;
albums . insert ( key , album_info ) ;
2021-02-26 21:03:51 +01:00
}
2018-02-27 18:06:05 +01:00
}
2021-06-25 18:19:37 +02:00
return albums . values ( ) ;
2018-02-27 18:06:05 +01:00
}
2021-02-26 21:03:51 +01:00
CollectionBackend : : Album CollectionBackend : : GetAlbumArt ( const QString & effective_albumartist , const QString & album ) {
2018-02-27 18:06:05 +01:00
2021-04-10 03:20:25 +02:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2018-02-27 18:06:05 +01:00
Album ret ;
2021-02-26 21:03:51 +01:00
ret . album = album ;
ret . album_artist = effective_albumartist ;
2018-02-27 18:06:05 +01:00
2023-01-08 15:40:54 +01:00
CollectionQuery query ( db , songs_table_ , fts_table_ ) ;
2024-04-09 23:20:26 +02:00
query . SetColumnSpec ( QStringLiteral ( " url, art_embedded, art_automatic, art_manual, art_unset " ) ) ;
2021-02-26 21:03:51 +01:00
if ( ! effective_albumartist . isEmpty ( ) ) {
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " effective_albumartist " ) , effective_albumartist ) ;
2018-02-27 18:06:05 +01:00
}
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " album " ) , album ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ret ;
}
2018-02-27 18:06:05 +01:00
if ( query . Next ( ) ) {
2023-05-14 11:34:55 +02:00
ret . urls < < QUrl : : fromEncoded ( query . Value ( 0 ) . toByteArray ( ) ) ;
ret . art_embedded = query . Value ( 1 ) . toInt ( ) = = 1 ;
ret . art_automatic = QUrl : : fromEncoded ( query . Value ( 2 ) . toByteArray ( ) ) ;
ret . art_manual = QUrl : : fromEncoded ( query . Value ( 3 ) . toByteArray ( ) ) ;
ret . art_unset = query . Value ( 4 ) . toInt ( ) = = 1 ;
2018-02-27 18:06:05 +01:00
}
return ret ;
}
2023-05-14 11:34:55 +02:00
void CollectionBackend : : UpdateEmbeddedAlbumArtAsync ( const QString & effective_albumartist , const QString & album , const bool art_embedded ) {
2018-08-29 21:42:24 +02:00
2023-05-14 11:34:55 +02:00
QMetaObject : : invokeMethod ( this , " UpdateEmbeddedAlbumArt " , Qt : : QueuedConnection , Q_ARG ( QString , effective_albumartist ) , Q_ARG ( QString , album ) , Q_ARG ( bool , art_embedded ) ) ;
2018-02-27 18:06:05 +01:00
}
2023-05-14 11:34:55 +02:00
void CollectionBackend : : UpdateEmbeddedAlbumArt ( const QString & effective_albumartist , const QString & album , const bool art_embedded ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
// Get the songs before they're updated
2021-04-10 03:20:25 +02:00
CollectionQuery query ( db , songs_table_ , fts_table_ ) ;
2024-04-11 02:56:01 +02:00
query . SetColumnSpec ( QStringLiteral ( " ROWID, " ) + Song : : kColumnSpec ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " effective_albumartist " ) , effective_albumartist ) ;
query . AddWhere ( QStringLiteral ( " album " ) , album ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
2018-02-27 18:06:05 +01:00
SongList deleted_songs ;
while ( query . Next ( ) ) {
2019-11-14 00:09:35 +01:00
Song song ( source_ ) ;
2018-02-27 18:06:05 +01:00
song . InitFromQuery ( query , true ) ;
deleted_songs < < song ;
}
// Update the songs
2024-04-09 23:20:26 +02:00
QString sql = QStringLiteral ( " UPDATE %1 SET art_embedded = :art_embedded, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0 " ) . arg ( songs_table_ ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( sql ) ;
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :art_embedded " ) , art_embedded ? 1 : 0 ) ;
q . BindValue ( QStringLiteral ( " :effective_albumartist " ) , effective_albumartist ) ;
q . BindValue ( QStringLiteral ( " :album " ) , album ) ;
2021-02-26 21:03:51 +01:00
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2021-02-26 21:03:51 +01:00
// Now get the updated songs
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
2021-02-26 21:03:51 +01:00
SongList added_songs ;
while ( query . Next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( query , true ) ;
added_songs < < song ;
2018-02-27 18:06:05 +01:00
}
2021-02-26 21:03:51 +01:00
if ( ! added_songs . isEmpty ( ) | | ! deleted_songs . isEmpty ( ) ) {
emit SongsDeleted ( deleted_songs ) ;
emit SongsDiscovered ( added_songs ) ;
2018-02-27 18:06:05 +01:00
}
2021-02-26 21:03:51 +01:00
}
2023-05-14 11:34:55 +02:00
void CollectionBackend : : UpdateManualAlbumArtAsync ( const QString & effective_albumartist , const QString & album , const QUrl & art_manual ) {
2021-02-26 21:03:51 +01:00
2023-05-14 11:34:55 +02:00
QMetaObject : : invokeMethod ( this , " UpdateManualAlbumArt " , Qt : : QueuedConnection , Q_ARG ( QString , effective_albumartist ) , Q_ARG ( QString , album ) , Q_ARG ( QUrl , art_manual ) ) ;
2021-02-26 21:03:51 +01:00
}
2023-05-14 11:34:55 +02:00
void CollectionBackend : : UpdateManualAlbumArt ( const QString & effective_albumartist , const QString & album , const QUrl & art_manual ) {
2021-02-26 21:03:51 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-04-10 03:20:25 +02:00
CollectionQuery query ( db , songs_table_ , fts_table_ ) ;
2024-04-11 02:56:01 +02:00
query . SetColumnSpec ( QStringLiteral ( " ROWID, " ) + Song : : kColumnSpec ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " effective_albumartist " ) , effective_albumartist ) ;
query . AddWhere ( QStringLiteral ( " album " ) , album ) ;
2021-02-26 21:03:51 +01:00
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
2021-02-26 21:03:51 +01:00
SongList deleted_songs ;
while ( query . Next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( query , true ) ;
deleted_songs < < song ;
}
2023-05-14 11:34:55 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET art_manual = :art_manual, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0 " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :art_manual " ) , art_manual . isValid ( ) ? art_manual . toString ( QUrl : : FullyEncoded ) : QLatin1String ( " " ) ) ;
q . BindValue ( QStringLiteral ( " :effective_albumartist " ) , effective_albumartist ) ;
q . BindValue ( QStringLiteral ( " :album " ) , album ) ;
2023-05-14 11:34:55 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
SongList added_songs ;
while ( query . Next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( query , true ) ;
added_songs < < song ;
}
if ( ! added_songs . isEmpty ( ) | | ! deleted_songs . isEmpty ( ) ) {
emit SongsDeleted ( deleted_songs ) ;
emit SongsDiscovered ( added_songs ) ;
}
}
void CollectionBackend : : UnsetAlbumArtAsync ( const QString & effective_albumartist , const QString & album ) {
QMetaObject : : invokeMethod ( this , " UnsetAlbumArt " , Qt : : QueuedConnection , Q_ARG ( QString , effective_albumartist ) , Q_ARG ( QString , album ) ) ;
}
void CollectionBackend : : UnsetAlbumArt ( const QString & effective_albumartist , const QString & album ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
CollectionQuery query ( db , songs_table_ , fts_table_ ) ;
2024-04-11 02:56:01 +02:00
query . SetColumnSpec ( QStringLiteral ( " ROWID, " ) + Song : : kColumnSpec ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " effective_albumartist " ) , effective_albumartist ) ;
query . AddWhere ( QStringLiteral ( " album " ) , album ) ;
2023-05-14 11:34:55 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
SongList deleted_songs ;
while ( query . Next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( query , true ) ;
deleted_songs < < song ;
2022-01-06 02:13:24 +01:00
}
2021-02-26 21:03:51 +01:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET art_unset = 1, art_manual = '', art_automatic = '', art_embedded = '' WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0 " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :effective_albumartist " ) , effective_albumartist ) ;
q . BindValue ( QStringLiteral ( " :album " ) , album ) ;
2023-05-14 11:34:55 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
SongList added_songs ;
while ( query . Next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( query , true ) ;
added_songs < < song ;
}
if ( ! added_songs . isEmpty ( ) | | ! deleted_songs . isEmpty ( ) ) {
emit SongsDeleted ( deleted_songs ) ;
emit SongsDiscovered ( added_songs ) ;
}
}
void CollectionBackend : : ClearAlbumArtAsync ( const QString & effective_albumartist , const QString & album , const bool unset ) {
QMetaObject : : invokeMethod ( this , " ClearAlbumArt " , Qt : : QueuedConnection , Q_ARG ( QString , effective_albumartist ) , Q_ARG ( QString , album ) , Q_ARG ( bool , unset ) ) ;
}
void CollectionBackend : : ClearAlbumArt ( const QString & effective_albumartist , const QString & album , const bool art_unset ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
CollectionQuery query ( db , songs_table_ , fts_table_ ) ;
2024-04-11 02:56:01 +02:00
query . SetColumnSpec ( QStringLiteral ( " ROWID, " ) + Song : : kColumnSpec ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " effective_albumartist " ) , effective_albumartist ) ;
query . AddWhere ( QStringLiteral ( " album " ) , album ) ;
2023-05-14 11:34:55 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
SongList deleted_songs ;
while ( query . Next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( query , true ) ;
deleted_songs < < song ;
}
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET art_embedded = 0, art_automatic = '', art_manual = '', art_unset = :art_unset WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0 " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :art_unset " ) , art_unset ? 1 : 0 ) ;
q . BindValue ( QStringLiteral ( " :effective_albumartist " ) , effective_albumartist ) ;
q . BindValue ( QStringLiteral ( " :album " ) , album ) ;
2021-02-26 21:03:51 +01:00
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
2018-02-27 18:06:05 +01:00
SongList added_songs ;
while ( query . Next ( ) ) {
2019-11-14 00:09:35 +01:00
Song song ( source_ ) ;
2018-02-27 18:06:05 +01:00
song . InitFromQuery ( query , true ) ;
added_songs < < song ;
}
if ( ! added_songs . isEmpty ( ) | | ! deleted_songs . isEmpty ( ) ) {
emit SongsDeleted ( deleted_songs ) ;
emit SongsDiscovered ( added_songs ) ;
}
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : ForceCompilation ( const QString & album , const QList < QString > & artists , const bool on ) {
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
SongList deleted_songs , added_songs ;
for ( const QString & artist : artists ) {
// Get the songs before they're updated
2021-04-10 03:20:25 +02:00
CollectionQuery query ( db , songs_table_ , fts_table_ ) ;
2024-04-11 02:56:01 +02:00
query . SetColumnSpec ( QStringLiteral ( " ROWID, " ) + Song : : kColumnSpec ) ;
2024-04-09 23:20:26 +02:00
query . AddWhere ( QStringLiteral ( " album " ) , album ) ;
if ( ! artist . isEmpty ( ) ) query . AddWhere ( QStringLiteral ( " artist " ) , artist ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
2018-02-27 18:06:05 +01:00
while ( query . Next ( ) ) {
2019-11-14 00:09:35 +01:00
Song song ( source_ ) ;
2018-02-27 18:06:05 +01:00
song . InitFromQuery ( query , true ) ;
deleted_songs < < song ;
}
// Update the songs
2024-04-09 23:20:26 +02:00
QString sql ( QStringLiteral ( " UPDATE %1 SET compilation_on = :compilation_on, compilation_off = :compilation_off, compilation_effective = ((compilation OR compilation_detected OR :compilation_on) AND NOT : compilation_off ) + 0 WHERE album = : album AND unavailable = 0 " ).arg(songs_table_)) ;
if ( ! artist . isEmpty ( ) ) sql + = QLatin1String ( " AND artist = :artist " ) ;
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( sql ) ;
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :compilation_on " ) , on ? 1 : 0 ) ;
q . BindValue ( QStringLiteral ( " :compilation_off " ) , on ? 0 : 1 ) ;
q . BindValue ( QStringLiteral ( " :album " ) , album ) ;
if ( ! artist . isEmpty ( ) ) q . BindValue ( QStringLiteral ( " :artist " ) , artist ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
// Now get the updated songs
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
ReportErrors ( query ) ;
return ;
}
2018-02-27 18:06:05 +01:00
while ( query . Next ( ) ) {
2019-11-14 00:09:35 +01:00
Song song ( source_ ) ;
2018-02-27 18:06:05 +01:00
song . InitFromQuery ( query , true ) ;
added_songs < < song ;
}
}
if ( ! added_songs . isEmpty ( ) | | ! deleted_songs . isEmpty ( ) ) {
emit SongsDeleted ( deleted_songs ) ;
emit SongsDiscovered ( added_songs ) ;
}
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : IncrementPlayCount ( const int id ) {
2019-04-19 14:02:28 +02:00
2018-02-27 18:06:05 +01:00
if ( id = = - 1 ) return ;
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET playcount = playcount + 1, lastplayed = :now WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :now " ) , QDateTime : : currentDateTime ( ) . toSecsSinceEpoch ( ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
Song new_song = GetSongById ( id , db ) ;
2019-04-19 14:02:28 +02:00
emit SongsStatisticsChanged ( SongList ( ) < < new_song ) ;
2018-02-27 18:06:05 +01:00
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : IncrementSkipCount ( const int id , const float progress ) {
2018-02-27 18:06:05 +01:00
2019-09-15 20:27:32 +02:00
Q_UNUSED ( progress ) ;
2018-02-27 18:06:05 +01:00
if ( id = = - 1 ) return ;
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET skipcount = skipcount + 1 WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :id " ) , id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
Song new_song = GetSongById ( id , db ) ;
2019-04-19 14:02:28 +02:00
emit SongsStatisticsChanged ( SongList ( ) < < new_song ) ;
2018-02-27 18:06:05 +01:00
}
2023-03-18 20:03:07 +01:00
void CollectionBackend : : ResetPlayStatistics ( const int id , const bool save_tags ) {
2018-02-27 18:06:05 +01:00
if ( id = = - 1 ) return ;
2023-03-18 20:03:07 +01:00
ResetPlayStatistics ( QList < int > ( ) < < id , save_tags ) ;
}
void CollectionBackend : : ResetPlayStatistics ( const QList < int > & id_list , const bool save_tags ) {
if ( id_list . isEmpty ( ) ) return ;
QStringList id_str_list ;
id_str_list . reserve ( id_list . count ( ) ) ;
for ( const int id : id_list ) {
id_str_list < < QString : : number ( id ) ;
}
const bool success = ResetPlayStatistics ( id_str_list ) ;
if ( success ) {
const SongList songs = GetSongsById ( id_list ) ;
emit SongsStatisticsChanged ( songs , save_tags ) ;
}
}
bool CollectionBackend : : ResetPlayStatistics ( const QStringList & id_str_list ) {
if ( id_str_list . isEmpty ( ) ) return false ;
2018-02-27 18:06:05 +01:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID IN (:ids) " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :ids " ) , id_str_list . join ( QStringLiteral ( " , " ) ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
2023-03-18 20:03:07 +01:00
return false ;
2021-09-09 21:45:46 +02:00
}
2018-02-27 18:06:05 +01:00
2023-03-18 20:03:07 +01:00
return true ;
2018-02-27 18:06:05 +01:00
}
2022-01-28 21:25:44 +01:00
void CollectionBackend : : DeleteAllAsync ( ) {
2023-05-03 20:08:51 +02:00
QMetaObject : : invokeMethod ( this , & CollectionBackend : : DeleteAll , Qt : : QueuedConnection ) ;
2022-01-28 21:25:44 +01:00
}
2018-02-27 18:06:05 +01:00
void CollectionBackend : : DeleteAll ( ) {
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
{
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
ScopedTransaction t ( & db ) ;
2021-09-09 21:45:46 +02:00
{
SqlQuery q ( db ) ;
2024-04-11 02:56:01 +02:00
q . prepare ( QStringLiteral ( " DELETE FROM " ) + songs_table_ ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
{
SqlQuery q ( db ) ;
2024-04-11 02:56:01 +02:00
q . prepare ( QStringLiteral ( " DELETE FROM " ) + fts_table_ ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
2018-02-27 18:06:05 +01:00
t . Commit ( ) ;
}
emit DatabaseReset ( ) ;
}
2020-08-30 18:09:13 +02:00
2021-06-28 00:16:22 +02:00
SongList CollectionBackend : : SmartPlaylistsFindSongs ( const SmartPlaylistSearch & search ) {
2020-09-17 17:50:17 +02:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
// Build the query
QString sql = search . ToSql ( songs_table ( ) ) ;
// Run the query
SongList ret ;
2021-09-09 21:45:46 +02:00
SqlQuery query ( db ) ;
2020-09-17 17:50:17 +02:00
query . prepare ( sql ) ;
2021-09-09 21:45:46 +02:00
if ( ! query . Exec ( ) ) {
db_ - > ReportErrors ( query ) ;
return ret ;
}
2020-09-17 17:50:17 +02:00
// Read the results
while ( query . next ( ) ) {
Song song ;
song . InitFromQuery ( query , true ) ;
ret < < song ;
}
return ret ;
}
2021-06-28 00:16:22 +02:00
SongList CollectionBackend : : SmartPlaylistsGetAllSongs ( ) {
2020-09-17 17:50:17 +02:00
// Get all the songs!
2023-02-18 14:09:27 +01:00
return SmartPlaylistsFindSongs ( SmartPlaylistSearch ( SmartPlaylistSearch : : SearchType : : All , SmartPlaylistSearch : : TermList ( ) , SmartPlaylistSearch : : SortType : : FieldAsc , SmartPlaylistSearchTerm : : Field : : Artist , - 1 ) ) ;
2020-09-17 17:50:17 +02:00
}
2020-08-30 18:09:13 +02:00
SongList CollectionBackend : : GetSongsBy ( const QString & artist , const QString & album , const QString & title ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
SongList songs ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2020-08-30 18:09:13 +02:00
if ( album . isEmpty ( ) ) {
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE artist = :artist COLLATE NOCASE AND title = :title COLLATE NOCASE " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
2020-08-30 18:09:13 +02:00
}
else {
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " SELECT ROWID, %1 FROM %2 WHERE artist = :artist COLLATE NOCASE AND album = :album COLLATE NOCASE AND title = :title COLLATE NOCASE " ) . arg ( Song : : kColumnSpec , songs_table_ ) ) ;
2020-08-30 18:09:13 +02:00
}
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :artist " ) , artist ) ;
if ( ! album . isEmpty ( ) ) q . BindValue ( QStringLiteral ( " :album " ) , album ) ;
q . BindValue ( QStringLiteral ( " :title " ) , title ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SongList ( ) ;
}
2020-08-30 18:09:13 +02:00
while ( q . next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( q , true ) ;
songs < < song ;
}
return songs ;
}
2020-11-22 19:01:53 +01:00
void CollectionBackend : : UpdateLastPlayed ( const QString & artist , const QString & album , const QString & title , const qint64 lastplayed ) {
2020-08-30 18:09:13 +02:00
SongList songs = GetSongsBy ( artist , album , title ) ;
if ( songs . isEmpty ( ) ) {
qLog ( Debug ) < < " Could not find a matching song in the database for " < < artist < < album < < title ;
return ;
}
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
for ( const Song & song : songs ) {
2021-01-04 20:43:43 +01:00
if ( song . lastplayed ( ) > = lastplayed ) {
continue ;
}
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET lastplayed = :lastplayed WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :lastplayed " ) , lastplayed ) ;
q . BindValue ( QStringLiteral ( " :id " ) , song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
continue ;
}
2020-08-30 18:09:13 +02:00
}
emit SongsStatisticsChanged ( SongList ( ) < < songs ) ;
}
2023-02-10 22:51:48 +01:00
void CollectionBackend : : UpdatePlayCount ( const QString & artist , const QString & title , const int playcount , const bool save_tags ) {
2020-08-30 18:09:13 +02:00
SongList songs = GetSongsBy ( artist , QString ( ) , title ) ;
if ( songs . isEmpty ( ) ) {
qLog ( Debug ) < < " Could not find a matching song in the database for " < < artist < < title ;
return ;
}
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
for ( const Song & song : songs ) {
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET playcount = :playcount WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :playcount " ) , playcount ) ;
q . BindValue ( QStringLiteral ( " :id " ) , song . id ( ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2020-08-30 18:09:13 +02:00
}
2023-02-10 22:51:48 +01:00
emit SongsStatisticsChanged ( SongList ( ) < < songs , save_tags ) ;
2020-08-30 18:09:13 +02:00
}
2020-09-17 17:50:17 +02:00
2021-10-30 18:53:14 +02:00
void CollectionBackend : : UpdateSongRating ( const int id , const float rating , const bool save_tags ) {
2020-09-17 17:50:17 +02:00
if ( id = = - 1 ) return ;
2021-10-24 16:08:17 +02:00
UpdateSongsRating ( QList < int > ( ) < < id , rating , save_tags ) ;
2020-09-17 17:50:17 +02:00
}
2021-10-30 18:53:14 +02:00
void CollectionBackend : : UpdateSongsRating ( const QList < int > & id_list , const float rating , const bool save_tags ) {
2020-09-17 17:50:17 +02:00
if ( id_list . isEmpty ( ) ) return ;
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
QStringList id_str_list ;
2021-06-20 19:04:08 +02:00
id_str_list . reserve ( id_list . count ( ) ) ;
2020-09-17 17:50:17 +02:00
for ( int i : id_list ) {
id_str_list < < QString : : number ( i ) ;
}
2024-04-09 23:20:26 +02:00
QString ids = id_str_list . join ( QStringLiteral ( " , " ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET rating = :rating WHERE ROWID IN (%2) " ) . arg ( songs_table_ , ids ) ) ;
q . BindValue ( QStringLiteral ( " :rating " ) , rating ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2020-09-17 17:50:17 +02:00
SongList new_song_list = GetSongsById ( id_str_list , db ) ;
2021-09-09 21:45:46 +02:00
2021-10-24 16:08:17 +02:00
emit SongsRatingChanged ( new_song_list , save_tags ) ;
2020-09-17 17:50:17 +02:00
}
2021-10-30 18:53:14 +02:00
void CollectionBackend : : UpdateSongRatingAsync ( const int id , const float rating , const bool save_tags ) {
QMetaObject : : invokeMethod ( this , " UpdateSongRating " , Qt : : QueuedConnection , Q_ARG ( int , id ) , Q_ARG ( float , rating ) , Q_ARG ( bool , save_tags ) ) ;
2020-09-17 17:50:17 +02:00
}
2021-10-30 18:53:14 +02:00
void CollectionBackend : : UpdateSongsRatingAsync ( const QList < int > & ids , const float rating , const bool save_tags ) {
QMetaObject : : invokeMethod ( this , " UpdateSongsRating " , Qt : : QueuedConnection , Q_ARG ( QList < int > , ids ) , Q_ARG ( float , rating ) , Q_ARG ( bool , save_tags ) ) ;
2020-09-17 17:50:17 +02:00
}
2021-04-25 21:16:44 +02:00
void CollectionBackend : : UpdateLastSeen ( const int directory_id , const int expire_unavailable_songs_days ) {
{
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-09 23:20:26 +02:00
q . prepare ( QStringLiteral ( " UPDATE %1 SET lastseen = :lastseen WHERE directory_id = :directory_id AND unavailable = 0 " ) . arg ( songs_table_ ) ) ;
q . BindValue ( QStringLiteral ( " :lastseen " ) , QDateTime : : currentDateTime ( ) . toSecsSinceEpoch ( ) ) ;
q . BindValue ( QStringLiteral ( " :directory_id " ) , directory_id ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2021-04-25 21:16:44 +02:00
}
if ( expire_unavailable_songs_days > 0 ) ExpireSongs ( directory_id , expire_unavailable_songs_days ) ;
}
void CollectionBackend : : ExpireSongs ( const int directory_id , const int expire_unavailable_songs_days ) {
SongList songs ;
{
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2024-04-11 02:56:01 +02:00
q . prepare ( QStringLiteral ( " SELECT %1.ROWID, " ) . arg ( songs_table_ ) + Song : : JoinSpec ( songs_table_ ) + QStringLiteral ( " FROM %1 LEFT JOIN playlist_items ON %1.ROWID = playlist_items.collection_id WHERE %1.directory_id = :directory_id AND %1.unavailable = 1 AND %1.lastseen > 0 AND %1.lastseen < :time AND playlist_items.collection_id IS NULL " ) . arg ( songs_table_ ) ) ;
2024-04-09 23:20:26 +02:00
q . BindValue ( QStringLiteral ( " :directory_id " ) , directory_id ) ;
q . BindValue ( QStringLiteral ( " :time " ) , QDateTime : : currentDateTime ( ) . toSecsSinceEpoch ( ) - ( expire_unavailable_songs_days * 86400 ) ) ;
2021-09-09 21:45:46 +02:00
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2021-04-25 21:16:44 +02:00
while ( q . next ( ) ) {
Song song ( source_ ) ;
song . InitFromQuery ( q , true ) ;
songs < < song ;
}
}
if ( ! songs . isEmpty ( ) ) DeleteSongs ( songs ) ;
}
2021-09-09 21:45:46 +02:00
2021-10-24 16:08:17 +02:00