2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine .
* Copyright 2010 , David Sansome < me @ davidsansome . com >
2021-03-20 21:14:47 +01:00
* Copyright 2018 - 2021 , 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"
2020-06-14 23:54:18 +02:00
# include <cassert>
2019-07-24 19:16:51 +02:00
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
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"
2020-09-17 17:50:17 +02:00
# include "smartplaylists/smartplaylistsearch.h"
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
# include "directory.h"
# include "collectionbackend.h"
# include "collectionquery.h"
# include "sqlrow.h"
2021-07-11 09:49:38 +02:00
CollectionBackend : : CollectionBackend ( QObject * parent )
: CollectionBackendInterface ( parent ) ,
db_ ( nullptr ) ,
source_ ( Song : : Source_Unknown ) ,
original_thread_ ( nullptr ) {
2019-07-24 19:16:51 +02:00
original_thread_ = thread ( ) ;
}
2021-06-28 00:03:36 +02:00
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 ) {
2018-02-27 18:06:05 +01:00
db_ = db ;
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 ;
}
2019-07-24 19:16:51 +02:00
void CollectionBackend : : Close ( ) {
if ( db_ ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
db_ - > Close ( ) ;
}
}
void CollectionBackend : : ExitAsync ( ) {
metaObject ( ) - > invokeMethod ( this , " Exit " , Qt : : QueuedConnection ) ;
}
void CollectionBackend : : Exit ( ) {
assert ( QThread : : currentThread ( ) = = thread ( ) ) ;
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 ( ) ) {
qLog ( Error ) < < " Unable to execute collection SQL query: " < < sql_error ;
qLog ( Error ) < < " Faulty SQL query: " < < query . lastQuery ( ) ;
qLog ( Error ) < < " Bound SQL values: " < < query . boundValues ( ) ;
QString error ;
error + = " Unable to execute collection SQL query: " + sql_error . text ( ) + " <br /> " ;
error + = " Faulty SQL query: " + query . lastQuery ( ) ;
emit Error ( error ) ;
}
}
2018-02-27 18:06:05 +01:00
void CollectionBackend : : LoadDirectoriesAsync ( ) {
metaObject ( ) - > invokeMethod ( this , " LoadDirectories " , Qt : : QueuedConnection ) ;
}
void CollectionBackend : : UpdateTotalSongCountAsync ( ) {
metaObject ( ) - > invokeMethod ( this , " UpdateTotalSongCount " , Qt : : QueuedConnection ) ;
}
void CollectionBackend : : UpdateTotalArtistCountAsync ( ) {
metaObject ( ) - > invokeMethod ( this , " UpdateTotalArtistCount " , Qt : : QueuedConnection ) ;
}
void CollectionBackend : : UpdateTotalAlbumCountAsync ( ) {
metaObject ( ) - > invokeMethod ( this , " UpdateTotalAlbumCount " , Qt : : QueuedConnection ) ;
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : IncrementPlayCountAsync ( const int id ) {
2018-02-27 18:06:05 +01:00
metaObject ( ) - > invokeMethod ( this , " IncrementPlayCount " , Qt : : QueuedConnection , Q_ARG ( int , id ) ) ;
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : IncrementSkipCountAsync ( const int id , const float progress ) {
2018-02-27 18:06:05 +01:00
metaObject ( ) - > invokeMethod ( this , " IncrementSkipCount " , Qt : : QueuedConnection , Q_ARG ( int , id ) , Q_ARG ( float , progress ) ) ;
}
2020-09-10 22:05:12 +02:00
void CollectionBackend : : ResetStatisticsAsync ( const int id ) {
2018-02-27 18:06:05 +01:00
metaObject ( ) - > invokeMethod ( this , " ResetStatistics " , Qt : : QueuedConnection , Q_ARG ( int , id ) ) ;
}
void CollectionBackend : : LoadDirectories ( ) {
DirectoryList dirs = GetAllDirectories ( ) ;
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
for ( const Directory & dir : dirs ) {
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 ) ;
2018-09-14 23:05:58 +02:00
q . prepare ( QString ( " UPDATE %1 SET path=:path WHERE ROWID=:id " ) . arg ( dirs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :path " , new_path ) ;
q . BindValue ( " :id " , id ) ;
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 ( ) ;
const int path_len = old_url . length ( ) ;
// Do the subdirs table
2018-09-14 23:05:58 +02:00
{
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2018-09-14 23:05:58 +02:00
q . prepare ( QString ( " UPDATE %1 SET path=:path || substr(path, %2) WHERE directory=:id " ) . arg ( subdirs_table_ ) . arg ( path_len ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :path " , new_url ) ;
q . BindValue ( " :id " , id ) ;
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 ) ;
2019-06-20 02:14:44 +02:00
q . prepare ( QString ( " UPDATE %1 SET url=:path || substr(url, %2) WHERE directory=:id " ) . arg ( songs_table_ ) . arg ( path_len ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :path " , new_url ) ;
q . BindValue ( " :id " , id ) ;
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 ( ) ;
}
DirectoryList CollectionBackend : : GetAllDirectories ( ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
DirectoryList ret ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " 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 ( ) ) {
Directory dir ;
dir . id = q . value ( 0 ) . toInt ( ) ;
dir . path = q . value ( 1 ) . toString ( ) ;
ret < < dir ;
}
return ret ;
}
2020-09-10 22:05:12 +02:00
SubdirectoryList 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 ) ;
}
2020-09-10 22:05:12 +02:00
SubdirectoryList 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 ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " SELECT path, mtime FROM %1 WHERE directory_id = :dir " ) . arg ( subdirs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :dir " , id ) ;
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return SubdirectoryList ( ) ;
}
2018-02-27 18:06:05 +01:00
SubdirectoryList subdirs ;
while ( q . next ( ) ) {
Subdirectory subdir ;
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 ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " 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 ) ;
2021-01-15 21:17:32 +01:00
q . prepare ( QString ( " 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 ) ;
2021-01-15 21:17:32 +01:00
q . prepare ( QString ( " 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 ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " INSERT INTO %1 (path, subdirs) VALUES (:path, 1) " ) . arg ( dirs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :path " , db_path ) ;
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2018-02-27 18:06:05 +01:00
Directory dir ;
dir . path = canonical_path ;
dir . id = q . lastInsertId ( ) . toInt ( ) ;
emit DirectoryDiscovered ( dir , SubdirectoryList ( ) ) ;
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
void CollectionBackend : : RemoveDirectory ( const Directory & dir ) {
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 ) ;
q . prepare ( QString ( " DELETE FROM %1 WHERE directory_id = :id " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( " :id " , dir . id ) ;
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 ) ;
q . prepare ( QString ( " DELETE FROM %1 WHERE ROWID = :id " ) . arg ( dirs_table_ ) ) ;
q . BindValue ( " :id " , dir . id ) ;
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 ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE directory_id = :directory_id " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :directory_id " , id ) ;
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 ) ;
2021-06-06 00:26:11 +02:00
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE directory_id = :directory_id AND unavailable = 0 AND (fingerprint IS NULL OR fingerprint = '') " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :directory_id " , id ) ;
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 ;
}
2019-03-25 00:53:12 +01:00
void CollectionBackend : : SongPathChanged ( const Song & song , const QFileInfo & new_file ) {
// 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 ( ) ;
AddOrUpdateSongs ( SongList ( ) < < updated_song ) ;
2019-03-25 00:53:12 +01:00
}
2018-02-27 18:06:05 +01:00
void CollectionBackend : : AddOrUpdateSubdirs ( const SubdirectoryList & subdirs ) {
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
ScopedTransaction transaction ( & db ) ;
for ( const Subdirectory & subdir : subdirs ) {
if ( subdir . mtime = = 0 ) {
// Delete the subdirectory
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
q . prepare ( QString ( " DELETE FROM %1 WHERE directory_id = :id AND path = :path " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( " :id " , subdir . directory_id ) ;
q . BindValue ( " :path " , subdir . path ) ;
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 ) ;
q . prepare ( QString ( " SELECT ROWID FROM %1 WHERE directory_id = :id AND path = :path " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( " :id " , subdir . directory_id ) ;
q . BindValue ( " :path " , subdir . path ) ;
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
exists = q . next ( ) ;
}
if ( exists ) {
SqlQuery q ( db ) ;
q . prepare ( QString ( " UPDATE %1 SET mtime = :mtime WHERE directory_id = :id AND path = :path " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( " :mtime " , subdir . mtime ) ;
q . BindValue ( " :id " , subdir . directory_id ) ;
q . BindValue ( " :path " , subdir . path ) ;
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 ) ;
q . prepare ( QString ( " INSERT INTO %1 (directory_id, path, mtime) VALUES (:id, :path, :mtime) " ) . arg ( subdirs_table_ ) ) ;
q . BindValue ( " :id " , subdir . directory_id ) ;
q . BindValue ( " :path " , subdir . path ) ;
q . BindValue ( " :mtime " , subdir . mtime ) ;
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 ( ) ;
}
2020-05-12 18:45:24 +02:00
void CollectionBackend : : AddOrUpdateSongsAsync ( const SongList & songs ) {
metaObject ( ) - > invokeMethod ( this , " AddOrUpdateSongs " , Qt : : QueuedConnection , Q_ARG ( SongList , songs ) ) ;
}
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 ) ;
check_dir . prepare ( QString ( " SELECT ROWID FROM %1 WHERE ROWID = :id " ) . arg ( dirs_table_ ) ) ;
check_dir . BindValue ( " :id " , song . directory_id ( ) ) ;
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 ) ;
q . prepare ( QString ( " UPDATE %1 SET " + Song : : kUpdateSpec + " WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
song . BindToQuery ( & q ) ;
q . BindValue ( " :id " , song . id ( ) ) ;
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 ) ;
q . prepare ( QString ( " UPDATE %1 SET " + Song : : kFtsUpdateSpec + " WHERE ROWID = :id " ) . arg ( fts_table_ ) ) ;
song . BindToFtsQuery ( & q ) ;
q . BindValue ( " :id " , song . id ( ) ) ;
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 ) ;
q . prepare ( QString ( " UPDATE %1 SET " + Song : : kUpdateSpec + " WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
new_song . BindToQuery ( & q ) ;
q . BindValue ( " :id " , new_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 " , new_song . id ( ) ) ;
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 ;
{ // Insert the row and create a new ID
SqlQuery q ( db ) ;
q . prepare ( QString ( " INSERT INTO %1 ( " + Song : : kColumnSpec + " ) VALUES ( " + Song : : kBindSpec + " ) " ) . arg ( songs_table_ ) ) ;
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-09 21:45:46 +02:00
{ // 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 ) ;
song . BindToFtsQuery ( & q ) ;
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
}
2019-06-07 23:02:43 +02:00
Song copy ( song ) ;
copy . set_id ( id ) ;
added_songs < < copy ;
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 ( ) ;
}
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 ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " UPDATE %1 SET mtime = :mtime WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
ScopedTransaction transaction ( & db ) ;
for ( const Song & song : songs ) {
2021-09-09 21:45:46 +02:00
q . BindValue ( " :mtime " , song . mtime ( ) ) ;
q . BindValue ( " :id " , song . id ( ) ) ;
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 ) ;
2018-02-27 18:06:05 +01:00
remove . prepare ( QString ( " DELETE FROM %1 WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery remove_fts ( db ) ;
2018-02-27 18:06:05 +01:00
remove_fts . prepare ( QString ( " DELETE FROM %1 WHERE ROWID = :id " ) . arg ( fts_table_ ) ) ;
ScopedTransaction transaction ( & db ) ;
for ( const Song & song : songs ) {
2021-09-09 21:45:46 +02:00
remove . BindValue ( " :id " , song . id ( ) ) ;
if ( ! remove . Exec ( ) ) {
db_ - > ReportErrors ( remove ) ;
return ;
}
2018-02-27 18:06:05 +01:00
2021-09-09 21:45:46 +02:00
remove_fts . BindValue ( " :id " , song . id ( ) ) ;
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 ( ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery remove ( db ) ;
2018-02-27 18:06:05 +01:00
remove . prepare ( QString ( " UPDATE %1 SET unavailable = %2 WHERE ROWID = :id " ) . arg ( songs_table_ ) . arg ( int ( unavailable ) ) ) ;
ScopedTransaction transaction ( & db ) ;
for ( const Song & song : songs ) {
2021-09-09 21:45:46 +02:00
remove . BindValue ( " :id " , song . id ( ) ) ;
if ( ! remove . Exec ( ) ) {
db_ - > ReportErrors ( remove ) ;
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 ( ) ;
}
QStringList CollectionBackend : : GetAll ( const QString & column , const QueryOptions & opt ) {
2018-10-02 00:38:52 +02: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 ) ;
2018-02-27 18:06:05 +01:00
query . SetColumnSpec ( " DISTINCT " + column ) ;
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 ;
}
QStringList CollectionBackend : : GetAllArtists ( const QueryOptions & opt ) {
2018-08-29 21:42:24 +02:00
2018-02-27 18:06:05 +01:00
return GetAll ( " artist " , opt ) ;
}
QStringList CollectionBackend : : GetAllArtistsWithAlbums ( const QueryOptions & opt ) {
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 ) ;
2018-02-27 18:06:05 +01:00
query . SetColumnSpec ( " DISTINCT albumartist " ) ;
query . AddCompilationRequirement ( false ) ;
query . AddWhere ( " album " , " " , " != " ) ;
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 ) ;
2018-02-27 18:06:05 +01:00
query2 . SetColumnSpec ( " DISTINCT artist " ) ;
query2 . AddCompilationRequirement ( false ) ;
query2 . AddWhere ( " album " , " " , " != " ) ;
query2 . AddWhere ( " albumartist " , " " , " = " ) ;
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
}
CollectionBackend : : AlbumList CollectionBackend : : GetAllAlbums ( const QueryOptions & opt ) {
2019-04-18 00:45:32 +02:00
return GetAlbums ( QString ( ) , false , opt ) ;
2018-02-27 18:06:05 +01:00
}
CollectionBackend : : AlbumList CollectionBackend : : GetAlbumsByArtist ( const QString & artist , const QueryOptions & opt ) {
2019-04-18 00:45:32 +02:00
return GetAlbums ( artist , false , opt ) ;
2018-02-27 18:06:05 +01:00
}
2021-02-26 21:03:51 +01:00
SongList CollectionBackend : : GetArtistSongs ( const QString & effective_albumartist , const QueryOptions & opt ) {
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 ) ;
query . AddWhere ( " 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
}
SongList CollectionBackend : : GetAlbumSongs ( const QString & effective_albumartist , const QString & album , const QueryOptions & opt ) {
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 ) ;
2021-02-26 21:03:51 +01:00
query . AddWhere ( " effective_albumartist " , effective_albumartist ) ;
2018-02-27 18:06:05 +01:00
query . AddWhere ( " 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-02-26 21:03:51 +01:00
SongList CollectionBackend : : GetSongsByAlbum ( const QString & album , const QueryOptions & opt ) {
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 ) ;
query . AddWhere ( " 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
query - > SetColumnSpec ( " %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
}
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 ( ) ) ;
QString in = ids . join ( " , " ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " SELECT %2.ROWID, " + Song : : kColumnSpec + " , %2.%3 FROM %2, %1 WHERE %2.%3 IN (%4) AND %1.ROWID = %2.ROWID AND unavailable = 0 " ) . arg ( songs_table_ , 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 ( ) ;
2018-02-27 18:06:05 +01:00
const int index = ids . indexOf ( foreign_id ) ;
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
2018-02-27 18:06:05 +01:00
QString in = ids . join ( " , " ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE ROWID IN (%2) " ) . arg ( 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 ) ;
2019-11-25 22:29:12 +01:00
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND beginning = :beginning AND unavailable = 0 " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :url1 " , url ) ;
q . BindValue ( " :url2 " , url . toString ( ) ) ;
q . BindValue ( " :url3 " , url . toString ( QUrl : : FullyEncoded ) ) ;
q . BindValue ( " :url4 " , url . toEncoded ( ) ) ;
q . BindValue ( " :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
}
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 ) ;
2021-06-05 02:50:20 +02:00
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = :unavailable " ) . arg ( songs_table_ ) ) ;
2019-11-25 22:29:12 +01:00
2021-09-09 21:45:46 +02:00
q . BindValue ( " :url1 " , url ) ;
q . BindValue ( " :url2 " , url . toString ( ) ) ;
q . BindValue ( " :url3 " , url . toString ( QUrl : : FullyEncoded ) ) ;
q . BindValue ( " :url4 " , url . toEncoded ( ) ) ;
q . BindValue ( " :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 ) {
song_ids2 < < " ' " + song_id + " ' " ;
}
QString in = song_ids2 . join ( " , " ) ;
2019-06-07 23:02:43 +02:00
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2019-06-07 23:02:43 +02:00
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE SONG_ID IN (%2) " ) . arg ( 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 ) ;
2021-04-25 21:16:44 +02:00
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE fingerprint = :fingerprint " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :fingerprint " , fingerprint ) ;
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 ;
}
2018-02-27 18:06:05 +01:00
CollectionBackend : : AlbumList CollectionBackend : : GetCompilationAlbums ( const QueryOptions & opt ) {
2019-04-18 00:45:32 +02:00
return GetAlbums ( QString ( ) , true , opt ) ;
2018-02-27 18:06:05 +01:00
}
SongList CollectionBackend : : GetCompilationSongs ( const QString & album , const QueryOptions & opt ) {
2021-04-10 03:20:25 +02:00
QMutexLocker l ( db_ - > Mutex ( ) ) ;
QSqlDatabase db ( db_ - > Connect ( ) ) ;
CollectionQuery query ( db , songs_table_ , fts_table_ , opt ) ;
2018-02-27 18:06:05 +01:00
query . SetColumnSpec ( " %songs_table.ROWID, " + Song : : kColumnSpec ) ;
query . AddCompilationRequirement ( true ) ;
query . AddWhere ( " album " , album ) ;
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
}
2020-02-07 23:18:18 +01:00
Song : : Source CollectionBackend : : Source ( ) const {
return source_ ;
}
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 ) ;
2019-06-20 02:14:44 +02:00
q . prepare ( QString ( " 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
2019-11-13 21:27:04 +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 ) ;
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0 " ) . arg ( songs_table_ ) ) ;
q . BindValue ( " :url1 " , url ) ;
q . BindValue ( " :url2 " , url . toString ( ) ) ;
q . BindValue ( " :url3 " , url . toString ( QUrl : : FullyEncoded ) ) ;
q . BindValue ( " :url4 " , url . toEncoded ( ) ) ;
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 ) ;
q . prepare ( QString ( " 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 ( " :compilation_detected " , int ( compilation_detected ) ) ;
q . BindValue ( " :url1 " , url ) ;
q . BindValue ( " :url2 " , url . toString ( ) ) ;
q . BindValue ( " :url3 " , url . toString ( QUrl : : FullyEncoded ) ) ;
q . BindValue ( " :url4 " , url . toEncoded ( ) ) ;
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
}
2020-04-23 21:08:28 +02:00
CollectionBackend : : AlbumList CollectionBackend : : GetAlbums ( const QString & artist , const bool compilation_required , const QueryOptions & 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 ) ;
2021-02-26 21:03:51 +01:00
query . SetColumnSpec ( " url, effective_albumartist, album, compilation_effective, art_automatic, art_manual, filetype, cue_path " ) ;
query . SetOrderBy ( " 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 ) ;
2021-02-26 21:03:51 +01:00
query . AddWhere ( " 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 ( ) ) {
2021-02-26 21:03:51 +01:00
bool is_compilation = query . Value ( 3 ) . toBool ( ) ;
2018-02-27 18:06:05 +01:00
Album info ;
2021-02-26 21:03:51 +01:00
QUrl url = QUrl : : fromEncoded ( query . Value ( 0 ) . toByteArray ( ) ) ;
2020-09-10 21:27:07 +02:00
if ( ! is_compilation ) {
2021-02-26 21:03:51 +01:00
info . album_artist = query . Value ( 1 ) . toString ( ) ;
2020-09-05 18:59:35 +02:00
}
2021-02-26 21:03:51 +01:00
info . album = query . Value ( 2 ) . toString ( ) ;
2018-02-27 18:06:05 +01:00
2021-02-26 21:03:51 +01:00
QString art_automatic = query . Value ( 4 ) . toString ( ) ;
2020-07-18 04:05:07 +02:00
if ( art_automatic . contains ( QRegularExpression ( " ..+:.* " ) ) ) {
2019-07-08 22:10:43 +02:00
info . art_automatic = QUrl : : fromEncoded ( art_automatic . toUtf8 ( ) ) ;
}
else {
2019-11-25 22:29:12 +01:00
info . art_automatic = QUrl : : fromLocalFile ( art_automatic ) ;
2019-07-08 22:10:43 +02:00
}
2021-02-26 21:03:51 +01:00
QString art_manual = query . Value ( 5 ) . toString ( ) ;
2020-07-18 04:05:07 +02:00
if ( art_manual . contains ( QRegularExpression ( " ..+:.* " ) ) ) {
2019-07-08 22:10:43 +02:00
info . art_manual = QUrl : : fromEncoded ( art_manual . toUtf8 ( ) ) ;
}
else {
2019-11-25 22:29:12 +01:00
info . art_manual = QUrl : : fromLocalFile ( art_manual ) ;
2019-07-08 22:10:43 +02:00
}
2021-02-26 21:03:51 +01:00
info . filetype = Song : : FileType ( query . Value ( 6 ) . toInt ( ) ) ;
QString filetype = Song : : TextForFiletype ( info . filetype ) ;
info . cue_path = query . Value ( 7 ) . toString ( ) ;
2020-09-05 18:59:35 +02:00
QString key ;
2021-02-26 21:03:51 +01:00
if ( ! info . album_artist . isEmpty ( ) ) {
key . append ( info . album_artist ) ;
2020-09-05 18:59:35 +02:00
}
2021-02-26 21:03:51 +01:00
if ( ! info . album . isEmpty ( ) ) {
2020-09-05 18:59:35 +02:00
if ( ! key . isEmpty ( ) ) key . append ( " - " ) ;
2021-02-26 21:03:51 +01:00
key . append ( info . album ) ;
}
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 {
info . urls < < url ;
albums . insert ( key , info ) ;
}
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
2021-04-10 03:20:25 +02:00
CollectionQuery query ( db , songs_table_ , fts_table_ , QueryOptions ( ) ) ;
2019-06-20 02:14:44 +02:00
query . SetColumnSpec ( " art_automatic, art_manual, url " ) ;
2021-02-26 21:03:51 +01:00
if ( ! effective_albumartist . isEmpty ( ) ) {
query . AddWhere ( " effective_albumartist " , effective_albumartist ) ;
2018-02-27 18:06:05 +01:00
}
query . AddWhere ( " album " , album ) ;
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 ( ) ) {
2019-11-25 22:29:12 +01:00
ret . art_automatic = QUrl : : fromEncoded ( query . Value ( 0 ) . toByteArray ( ) ) ;
ret . art_manual = QUrl : : fromEncoded ( query . Value ( 1 ) . toByteArray ( ) ) ;
2021-02-26 21:03:51 +01:00
ret . urls < < QUrl : : fromEncoded ( query . Value ( 2 ) . toByteArray ( ) ) ;
2018-02-27 18:06:05 +01:00
}
return ret ;
}
2021-02-26 21:03:51 +01:00
void CollectionBackend : : UpdateManualAlbumArtAsync ( const QString & effective_albumartist , const QString & album , const QUrl & cover_url , const bool clear_art_automatic ) {
2018-08-29 21:42:24 +02:00
2021-02-26 21:03:51 +01:00
metaObject ( ) - > invokeMethod ( this , " UpdateManualAlbumArt " , Qt : : QueuedConnection , Q_ARG ( QString , effective_albumartist ) , Q_ARG ( QString , album ) , Q_ARG ( QUrl , cover_url ) , Q_ARG ( bool , clear_art_automatic ) ) ;
2018-02-27 18:06:05 +01:00
}
2021-02-26 21:03:51 +01:00
void CollectionBackend : : UpdateManualAlbumArt ( const QString & effective_albumartist , const QString & album , const QUrl & cover_url , const bool clear_art_automatic ) {
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_ ) ;
2018-02-27 18:06:05 +01:00
query . SetColumnSpec ( " ROWID, " + Song : : kColumnSpec ) ;
2021-02-26 21:03:51 +01:00
query . AddWhere ( " effective_albumartist " , effective_albumartist ) ;
2018-02-27 18:06:05 +01:00
query . AddWhere ( " album " , album ) ;
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
2021-03-07 05:37:07 +01:00
QString sql ( QString ( " UPDATE %1 SET art_manual = :cover " ) . arg ( songs_table_ ) ) ;
2021-02-26 21:03:51 +01:00
if ( clear_art_automatic ) {
2021-03-07 05:37:07 +01:00
sql + = " , art_automatic = '' " ;
2018-02-27 18:06:05 +01:00
}
2021-03-07 05:37:07 +01:00
sql + = " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0 " ;
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 ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :cover " , cover_url . isValid ( ) ? cover_url . toString ( QUrl : : FullyEncoded ) : " " ) ;
q . BindValue ( " :effective_albumartist " , effective_albumartist ) ;
q . BindValue ( " :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
}
void CollectionBackend : : UpdateAutomaticAlbumArtAsync ( const QString & effective_albumartist , const QString & album , const QUrl & cover_url ) {
metaObject ( ) - > invokeMethod ( this , " UpdateAutomaticAlbumArt " , Qt : : QueuedConnection , Q_ARG ( QString , effective_albumartist ) , Q_ARG ( QString , album ) , Q_ARG ( QUrl , cover_url ) ) ;
}
void CollectionBackend : : UpdateAutomaticAlbumArt ( const QString & effective_albumartist , const QString & album , const QUrl & cover_url ) {
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_ ) ;
2021-02-26 21:03:51 +01:00
query . SetColumnSpec ( " ROWID, " + Song : : kColumnSpec ) ;
query . AddWhere ( " effective_albumartist " , effective_albumartist ) ;
query . AddWhere ( " album " , album ) ;
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 ;
}
// Update the songs
QString sql ( QString ( " UPDATE %1 SET art_automatic = :cover WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0 " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2021-02-26 21:03:51 +01:00
q . prepare ( sql ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :cover " , cover_url . isValid ( ) ? cover_url . toString ( QUrl : : FullyEncoded ) : " " ) ;
q . BindValue ( " :effective_albumartist " , effective_albumartist ) ;
q . BindValue ( " :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
// 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
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_ ) ;
2018-02-27 18:06:05 +01:00
query . SetColumnSpec ( " ROWID, " + Song : : kColumnSpec ) ;
query . AddWhere ( " album " , album ) ;
2020-09-10 22:05:12 +02:00
if ( ! artist . isEmpty ( ) ) query . AddWhere ( " 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
QString sql ( QString ( " 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 + = " AND artist = :artist " ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( sql ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :compilation_on " , on ? 1 : 0 ) ;
q . BindValue ( " :compilation_off " , on ? 0 : 1 ) ;
q . BindValue ( " :album " , album ) ;
if ( ! artist . isEmpty ( ) ) q . BindValue ( " :artist " , artist ) ;
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 ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " UPDATE %1 SET playcount = playcount + 1, lastplayed = :now WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :now " , QDateTime : : currentDateTime ( ) . toSecsSinceEpoch ( ) ) ;
q . BindValue ( " :id " , id ) ;
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 ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " UPDATE %1 SET skipcount = skipcount + 1 WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :id " , id ) ;
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 : : ResetStatistics ( const int id ) {
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 ) ;
2018-02-27 18:06:05 +01:00
q . prepare ( QString ( " UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :id " , id ) ;
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
}
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 ) ;
q . prepare ( " DELETE FROM " + songs_table_ ) ;
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 ) ;
q . prepare ( " DELETE FROM " + fts_table_ ) ;
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!
2021-06-28 00:16:22 +02:00
return SmartPlaylistsFindSongs ( SmartPlaylistSearch ( SmartPlaylistSearch : : Type_All , SmartPlaylistSearch : : TermList ( ) , SmartPlaylistSearch : : Sort_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 ( ) ) {
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND title = :title COLLATE NOCASE " ) . arg ( songs_table_ ) ) ;
}
else {
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND album = :album COLLATE NOCASE AND title = :title COLLATE NOCASE " ) . arg ( songs_table_ ) ) ;
}
2021-09-09 21:45:46 +02:00
q . BindValue ( " :artist " , artist ) ;
if ( ! album . isEmpty ( ) ) q . BindValue ( " :album " , album ) ;
q . BindValue ( " :title " , title ) ;
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 ) ;
2020-08-30 18:09:13 +02:00
q . prepare ( QString ( " UPDATE %1 SET lastplayed = :lastplayed WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :lastplayed " , lastplayed ) ;
q . BindValue ( " :id " , song . id ( ) ) ;
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
continue ;
}
2020-08-30 18:09:13 +02:00
}
emit SongsStatisticsChanged ( SongList ( ) < < songs ) ;
}
void CollectionBackend : : UpdatePlayCount ( const QString & artist , const QString & title , const int playcount ) {
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 ) ;
2020-08-30 18:09:13 +02:00
q . prepare ( QString ( " UPDATE %1 SET playcount = :playcount WHERE ROWID = :id " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :playcount " , playcount ) ;
q . BindValue ( " :id " , song . id ( ) ) ;
if ( ! q . Exec ( ) ) {
db_ - > ReportErrors ( q ) ;
return ;
}
2020-08-30 18:09:13 +02:00
}
emit SongsStatisticsChanged ( SongList ( ) < < songs ) ;
}
2020-09-17 17:50:17 +02:00
2021-03-21 18:53:02 +01:00
void CollectionBackend : : UpdateSongRating ( const int id , const double rating ) {
2020-09-17 17:50:17 +02:00
if ( id = = - 1 ) return ;
QList < int > id_list ;
id_list < < id ;
UpdateSongsRating ( id_list , rating ) ;
}
2021-03-21 18:53:02 +01:00
void CollectionBackend : : UpdateSongsRating ( const QList < int > & id_list , const double rating ) {
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 ) ;
}
QString ids = id_str_list . join ( " , " ) ;
2021-09-09 21:45:46 +02:00
SqlQuery q ( db ) ;
2020-09-17 17:50:17 +02:00
q . prepare ( QString ( " UPDATE %1 SET rating = :rating WHERE ROWID IN (%2) " ) . arg ( songs_table_ , ids ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :rating " , rating ) ;
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
2020-09-17 17:50:17 +02:00
emit SongsRatingChanged ( new_song_list ) ;
}
2021-03-21 18:53:02 +01:00
void CollectionBackend : : UpdateSongRatingAsync ( const int id , const double rating ) {
metaObject ( ) - > invokeMethod ( this , " UpdateSongRating " , Qt : : QueuedConnection , Q_ARG ( int , id ) , Q_ARG ( double , rating ) ) ;
2020-09-17 17:50:17 +02:00
}
2021-06-12 20:53:23 +02:00
void CollectionBackend : : UpdateSongsRatingAsync ( const QList < int > & ids , const double rating ) {
2021-03-21 18:53:02 +01:00
metaObject ( ) - > invokeMethod ( this , " UpdateSongsRating " , Qt : : QueuedConnection , Q_ARG ( QList < int > , ids ) , Q_ARG ( double , rating ) ) ;
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 ) ;
2021-04-25 21:16:44 +02:00
q . prepare ( QString ( " UPDATE %1 SET lastseen = :lastseen WHERE directory_id = :directory_id AND unavailable = 0 " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :lastseen " , QDateTime : : currentDateTime ( ) . toSecsSinceEpoch ( ) ) ;
q . BindValue ( " :directory_id " , directory_id ) ;
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 ) ;
2021-04-25 21:16:44 +02:00
q . prepare ( QString ( " SELECT ROWID, " + Song : : kColumnSpec + " FROM %1 WHERE directory_id = :directory_id AND unavailable = 1 AND lastseen > 0 AND lastseen < :time " ) . arg ( songs_table_ ) ) ;
2021-09-09 21:45:46 +02:00
q . BindValue ( " :directory_id " , directory_id ) ;
q . BindValue ( " :time " , QDateTime : : currentDateTime ( ) . toSecsSinceEpoch ( ) - ( expire_unavailable_songs_days * 86400 ) ) ;
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