21 query.
prepare(
"SELECT directory_id, path FROM music_directories");
31 query.
prepare(
"SELECT genre_id, LOWER(genre) FROM music_genres");
41 query.
prepare(
"SELECT artist_id, LOWER(artist_name) FROM music_artists");
51 query.
prepare(
"SELECT album_id, artist_id, LOWER(album_name) FROM music_albums");
80 d.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
82 QFileInfoList list =
d.entryInfoList();
88 for (
const auto& fi : std::as_const(list))
90 QString
filename = fi.absoluteFilePath();
110 LOG(VB_GENERAL, LOG_ERR,
111 QString(
"Failed to get directory id for path %1")
136 LOG(VB_GENERAL, LOG_INFO,
137 QString(
"Found file with unsupported extension %1")
147 QString extension = fi.suffix().toLower();
151 return !extension.isEmpty() && nameFilter.indexOf(extension.toLower()) > -1;
157 QString extension = fi.suffix().toLower();
160 return !extension.isEmpty() && nameFilter.indexOf(extension.toLower()) > -1;
175 if (directory.isEmpty())
181 query.
prepare(
"SELECT directory_id FROM music_directories "
182 "WHERE path = BINARY :DIRECTORY ;");
183 query.
bindValue(
":DIRECTORY", directory);
194 return query.
value(0).toInt();
198 query.
prepare(
"INSERT INTO music_directories (path, parent_id) "
199 "VALUES (:DIRECTORY, :PARENTID);");
200 query.
bindValue(
":DIRECTORY", directory);
221 const QString &
filename,
const QString &date_modified)
224 QDateTime dt = fi.lastModified();
228 return !old_dt.isValid() || (dt > old_dt);
230 LOG(VB_GENERAL, LOG_ERR, QString(
"Failed to stat file: %1")
252 QString extension =
filename.section(
'.', -1 ) ;
254 directory.remove(0, startDir.length());
255 directory = directory.section(
'/', 0, -2);
260 if (nameFilter.indexOf(extension.toLower()) > -1)
262 QString name =
filename.section(
'/', -1);
265 query.
prepare(
"INSERT INTO music_albumart "
266 "SET filename = :FILE, directory_id = :DIRID, "
267 "imagetype = :TYPE, hostname = :HOSTNAME;");
286 LOG(VB_GENERAL, LOG_WARNING, QString(
"Ignoring filename with unsupported filename: '%1'").arg(
filename));
290 LOG(VB_FILE, LOG_INFO, QString(
"Reading metadata from %1").arg(
filename));
297 QString album_cache_string;
310 album_cache_string = QString::number(data->
getArtistId()) +
"#"
311 + data->
Album().toLower();
338 album_cache_string = QString::number(data->
getArtistId()) +
"#"
339 + data->
Album().toLower();
370 LOG(VB_GENERAL, LOG_INFO,
"Cleaning old entries from music database");
376 if (!query.
exec(
"SELECT g.genre_id FROM music_genres g "
377 "LEFT JOIN music_songs s ON g.genre_id=s.genre_id "
378 "WHERE s.genre_id IS NULL;"))
379 MythDB::DBError(
"MusicFileScanner::cleanDB - select music_genres", query);
381 deletequery.
prepare(
"DELETE FROM music_genres WHERE genre_id=:GENREID");
384 int genreid = query.
value(0).toInt();
385 deletequery.
bindValue(
":GENREID", genreid);
386 if (!deletequery.
exec())
392 if (!query.
exec(
"SELECT a.album_id FROM music_albums a "
393 "LEFT JOIN music_songs s ON a.album_id=s.album_id "
394 "WHERE s.album_id IS NULL;"))
395 MythDB::DBError(
"MusicFileScanner::cleanDB - select music_albums", query);
397 deletequery.
prepare(
"DELETE FROM music_albums WHERE album_id=:ALBUMID");
400 int albumid = query.
value(0).toInt();
401 deletequery.
bindValue(
":ALBUMID", albumid);
402 if (!deletequery.
exec())
408 if (!query.
exec(
"SELECT a.artist_id FROM music_artists a "
409 "LEFT JOIN music_songs s ON a.artist_id=s.artist_id "
410 "LEFT JOIN music_albums l ON a.artist_id=l.artist_id "
411 "WHERE s.artist_id IS NULL AND l.artist_id IS NULL"))
412 MythDB::DBError(
"MusicFileScanner::cleanDB - select music_artists", query);
415 deletequery.
prepare(
"DELETE FROM music_artists WHERE artist_id=:ARTISTID");
418 int artistid = query.
value(0).toInt();
419 deletequery.
bindValue(
":ARTISTID", artistid);
420 if (!deletequery.
exec())
431 if (!query.
exec(
"SELECT d.directory_id, d.parent_id FROM music_directories d "
432 "LEFT JOIN music_songs s ON d.directory_id=s.directory_id "
433 "WHERE s.directory_id IS NULL ORDER BY directory_id DESC;"))
434 MythDB::DBError(
"MusicFileScanner::cleanDB - select music_directories", query);
436 deletequery.
prepare(
"DELETE FROM music_directories WHERE directory_id=:DIRECTORYID");
439 parentquery.
prepare(
"SELECT COUNT(*) FROM music_directories "
440 "WHERE parent_id=:DIRECTORYID ");
443 dirnamequery.
prepare(
"SELECT path FROM music_directories "
444 "WHERE directory_id=:DIRECTORYID ");
446 int deletedCount = 1;
448 while (deletedCount > 0)
457 int directoryid = query.
value(0).toInt();
460 parentquery.
bindValue(
":DIRECTORYID", directoryid);
461 if (!parentquery.
exec())
463 MythDB::DBError(
"MusicFileScanner::cleanDB - get parent directory count",
467 if (!parentquery.
next())
469 int parentCount = parentquery.
value(0).toInt();
470 if (parentCount != 0)
475 dirnamequery.
bindValue(
":DIRECTORYID", directoryid);
476 if (dirnamequery.
exec() && dirnamequery.
next())
478 LOG(VB_GENERAL, LOG_DEBUG,
479 QString(
"MusicFileScanner deleted directory %1 %2")
480 .arg(directoryid,5).arg(dirnamequery.
value(0).toString()));
483 deletequery.
bindValue(
":DIRECTORYID", directoryid);
484 if (!deletequery.
exec())
485 MythDB::DBError(
"MusicFileScanner::cleanDB - delete music_directories",
489 LOG(VB_GENERAL, LOG_INFO,
490 QString(
"MusicFileScanner deleted %1 directory entries")
495 if (!query.
exec(
"SELECT a.albumart_id FROM music_albumart a LEFT JOIN "
496 "music_songs s ON a.song_id=s.song_id WHERE "
497 "embedded='1' AND s.song_id IS NULL;"))
498 MythDB::DBError(
"MusicFileScanner::cleanDB - select music_albumart", query);
500 deletequery.
prepare(
"DELETE FROM music_albumart WHERE albumart_id=:ALBUMARTID");
503 int albumartid = query.
value(0).toInt();
504 deletequery.
bindValue(
":ALBUMARTID", albumartid);
505 if (!deletequery.
exec())
524 sqlfilename.remove(0, startDir.length());
526 QString directory = sqlfilename.section(
'/', 0, -2 ) ;
527 sqlfilename = sqlfilename.section(
'/', -1 ) ;
529 QString extension = sqlfilename.section(
'.', -1 ) ;
532 "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
534 if (nameFilter.indexOf(extension.toLower()) > -1)
537 query.
prepare(
"DELETE FROM music_albumart WHERE filename= :FILE AND "
538 "directory_id= :DIRID;");
553 query.
prepare(
"DELETE FROM music_songs WHERE filename = :NAME ;");
556 MythDB::DBError(
"MusicFileScanner::RemoveFileFromDB - deleting music_songs",
575 dbFilename.remove(0, startDir.length());
578 directory.remove(0, startDir.length());
579 directory = directory.section(
'/', 0, -2);
584 if (db_meta && disk_meta)
586 if (db_meta->
ID() <= 0)
588 LOG(VB_GENERAL, LOG_ERR, QString(
"Asked to update track with "
590 .arg(db_meta->
ID()));
596 disk_meta->
setID(db_meta->
ID());
601 QString album_cache_string;
614 album_cache_string = QString::number(disk_meta->
getArtistId()) +
"#" +
615 disk_meta->
Album().toLower();
643 album_cache_string = QString::number(disk_meta->
getArtistId()) +
"#" +
644 disk_meta->
Album().toLower();
670 if (!lastRun.isEmpty())
673 if (dtLastRun.isValid())
675 static constexpr int64_t kOneHour {60LL * 60};
678 LOG(VB_GENERAL, LOG_INFO,
"Music file scanner has been running for more than 60 minutes. Lets reset and try again");
682 std::this_thread::sleep_for(5s);
686 LOG(VB_GENERAL, LOG_INFO,
"Music file scanner is already running");
696 LOG(VB_GENERAL, LOG_INFO,
"Music file scanner started");
700 QString status = QString(
"running");
708 MusicLoadedMap::Iterator iter;
710 for (
int x = 0; x < dirList.count(); x++)
712 QString startDir = dirList[x];
714 LOG(VB_GENERAL, LOG_INFO, QString(
"Searching '%1' for music files").arg(startDir));
725 LOG(VB_GENERAL, LOG_INFO,
"Updating database");
740 for (iter = music_files.begin(); iter != music_files.end(); iter++)
757 for (iter = art_files.begin(); iter != art_files.end(); iter++)
777 QString trackStatus = QString(
"total tracks found: %1 (unchanged: %2, added: %3, removed: %4, updated %5)")
780 QString coverartStatus = QString(
"total coverart found: %1 (unchanged: %2, added: %3, removed: %4, updated %5)")
785 LOG(VB_GENERAL, LOG_INFO,
"Music file scanner finished ");
786 LOG(VB_GENERAL, LOG_INFO, trackStatus);
787 LOG(VB_GENERAL, LOG_INFO, coverartStatus);
794 status = QString(
"success - %1 - %2").arg(trackStatus, coverartStatus);
807 MusicLoadedMap::Iterator iter;
810 query.
prepare(
"SELECT CONCAT_WS('/', path, filename), date_modified "
811 "FROM music_songs LEFT JOIN music_directories ON "
812 "music_songs.directory_id=music_directories.directory_id "
813 "WHERE filename NOT LIKE BINARY ('%://%') "
814 "AND hostname = :HOSTNAME");
821 LOG(VB_GENERAL, LOG_INFO,
"Checking tracks");
832 iter = music_files.find(name);
833 if (iter != music_files.end())
837 if (iter != music_files.end())
848 music_files.erase(iter);
868 MusicLoadedMap::Iterator iter;
871 query.
prepare(
"SELECT CONCAT_WS('/', path, filename) "
872 "FROM music_albumart "
873 "LEFT JOIN music_directories ON music_albumart.directory_id=music_directories.directory_id "
874 "WHERE music_albumart.embedded = 0 "
875 "AND music_albumart.hostname = :HOSTNAME");
882 LOG(VB_GENERAL, LOG_INFO,
"Checking artwork");
893 iter = music_files.find(name);
894 if (iter != music_files.end())
898 if (iter != music_files.end())
903 music_files.erase(iter);
void dumpToDatabase(void)
saves or updates the image details in the DB
static ImageType guessImageType(const QString &filename)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
int numRowsAffected() const
bool isActive(void) const
bool seek(int where, bool relative=false)
Wrap QSqlQuery::seek(int,bool)
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
QVariant lastInsertId()
Return the id of the last inserted row.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
static void updateLastRunEnd(void)
QMap< QString, MusicFileData > MusicLoadedMap
void UpdateFileInDB(const QString &filename, const QString &startDir)
Updates a file in the database.
void RemoveFileFromDB(const QString &filename, const QString &startDir)
Removes a file from the database.
static bool IsArtFile(const QString &filename)
static void updateLastRunStart(void)
void SearchDirs(const QStringList &dirList)
Scan a list of directories recursively for music and albumart. Inserts, updates and removes any files...
static bool HasFileChanged(const QString &filename, const QString &date_modified)
Check if file has been modified since given date/time.
static int GetDirectoryId(const QString &directory, int parentid)
Get an ID for the given directory from the database. If it doesn't already exist in the database,...
MusicFileScanner(bool force=false)
void ScanMusic(MusicLoadedMap &music_files)
Check a list of files against musics files already in the database.
void BuildFileList(QString &directory, MusicLoadedMap &music_files, MusicLoadedMap &art_files, int parentid)
Builds a list of all the files found descending recursively into the given directory.
void AddFileToDB(const QString &filename, const QString &startDir)
Insert file details into database. If it is an audio file, read the metadata and insert that informat...
static bool IsRunning(void)
static void updateLastRunStatus(QString &status)
static bool IsMusicFile(const QString &filename)
static void cleanDB()
Clear orphaned entries from the genre, artist, album and albumart tables.
void ScanArtwork(MusicLoadedMap &music_files)
Check a list of files against images already in the database.
QString GetHostName(void)
void SaveSetting(const QString &key, int newValue)
QString GetSetting(const QString &key, const QString &defaultval="")
void SendMessage(const QString &message)
static void DBError(const QString &where, const MSqlQuery &query)
static const iso6937table * d
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
MusicFileLocation location