MythTV master
musicfilescanner.cpp
Go to the documentation of this file.
1#include <thread>
2
3// Qt headers
4#include <QDir>
5
6// MythTV headers
11
12#include "musicmetadata.h"
13#include "metaio.h"
14#include "musicfilescanner.h"
15
17{
19
20 // Cache the directory ids from the database
21 query.prepare("SELECT directory_id, path FROM music_directories");
22 if (query.exec())
23 {
24 while(query.next())
25 {
26 m_directoryid[query.value(1).toString()] = query.value(0).toInt();
27 }
28 }
29
30 // Cache the genre ids from the database
31 query.prepare("SELECT genre_id, LOWER(genre) FROM music_genres");
32 if (query.exec())
33 {
34 while(query.next())
35 {
36 m_genreid[query.value(1).toString()] = query.value(0).toInt();
37 }
38 }
39
40 // Cache the artist ids from the database
41 query.prepare("SELECT artist_id, LOWER(artist_name) FROM music_artists");
42 if (query.exec() || query.isActive())
43 {
44 while(query.next())
45 {
46 m_artistid[query.value(1).toString()] = query.value(0).toInt();
47 }
48 }
49
50 // Cache the album ids from the database
51 query.prepare("SELECT album_id, artist_id, LOWER(album_name) FROM music_albums");
52 if (query.exec())
53 {
54 while(query.next())
55 {
56 m_albumid[query.value(1).toString() + "#" + query.value(2).toString()] = query.value(0).toInt();
57 }
58 }
59}
60
73void MusicFileScanner::BuildFileList(QString &directory, MusicLoadedMap &music_files, MusicLoadedMap &art_files, int parentid)
74{
75 QDir d(directory);
76
77 if (!d.exists())
78 return;
79
80 d.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
81
82 QFileInfoList list = d.entryInfoList();
83 if (list.isEmpty())
84 return;
85
86 // Recursively traverse directory
87 int newparentid = 0;
88 for (const auto& fi : std::as_const(list))
89 {
90 QString filename = fi.absoluteFilePath();
91 if (fi.isDir())
92 {
93
94 QString dir(filename);
95 dir.remove(0, m_startDirs.last().length());
96
97 newparentid = m_directoryid[dir];
98
99 if (newparentid == 0)
100 {
101 int id = GetDirectoryId(dir, parentid);
102 m_directoryid[dir] = id;
103
104 if (id > 0)
105 {
106 newparentid = id;
107 }
108 else
109 {
110 LOG(VB_GENERAL, LOG_ERR,
111 QString("Failed to get directory id for path %1")
112 .arg(dir));
113 }
114 }
115
116 BuildFileList(filename, music_files, art_files, newparentid);
117 }
118 else
119 {
120 if (IsArtFile(filename))
121 {
122 MusicFileData fdata;
123 fdata.startDir = m_startDirs.last();
125 art_files[filename] = fdata;
126 }
127 else if (IsMusicFile(filename))
128 {
129 MusicFileData fdata;
130 fdata.startDir = m_startDirs.last();
132 music_files[filename] = fdata;
133 }
134 else
135 {
136 LOG(VB_GENERAL, LOG_INFO,
137 QString("Found file with unsupported extension %1")
138 .arg(filename));
139 }
140 }
141 }
142}
143
145{
146 QFileInfo fi(filename);
147 QString extension = fi.suffix().toLower();
148 QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter", "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
149
150
151 return !extension.isEmpty() && nameFilter.indexOf(extension.toLower()) > -1;
152}
153
155{
156 QFileInfo fi(filename);
157 QString extension = fi.suffix().toLower();
158 QString nameFilter = MetaIO::kValidFileExtensions;
159
160 return !extension.isEmpty() && nameFilter.indexOf(extension.toLower()) > -1;
161}
162
173int MusicFileScanner::GetDirectoryId(const QString &directory, int parentid)
174{
175 if (directory.isEmpty())
176 return 0;
177
179
180 // Load the directory id or insert it and get the id
181 query.prepare("SELECT directory_id FROM music_directories "
182 "WHERE path = BINARY :DIRECTORY ;");
183 query.bindValue(":DIRECTORY", directory);
184
185 if (!query.exec())
186 {
187 MythDB::DBError("music select directory id", query);
188 return -1;
189 }
190
191 if (query.next())
192 {
193 // we have found the directory already in the DB
194 return query.value(0).toInt();
195 }
196
197 // directory is not in the DB so insert it
198 query.prepare("INSERT INTO music_directories (path, parent_id) "
199 "VALUES (:DIRECTORY, :PARENTID);");
200 query.bindValue(":DIRECTORY", directory);
201 query.bindValue(":PARENTID", parentid);
202
203 if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
204 {
205 MythDB::DBError("music insert directory", query);
206 return -1;
207 }
208
209 return query.lastInsertId().toInt();
210}
211
221 const QString &filename, const QString &date_modified)
222{
223 QFileInfo fi(filename);
224 QDateTime dt = fi.lastModified();
225 if (dt.isValid())
226 {
227 QDateTime old_dt = MythDate::fromString(date_modified);
228 return !old_dt.isValid() || (dt > old_dt);
229 }
230 LOG(VB_GENERAL, LOG_ERR, QString("Failed to stat file: %1")
231 .arg(filename));
232 return false;
233}
234
250void MusicFileScanner::AddFileToDB(const QString &filename, const QString &startDir)
251{
252 QString extension = filename.section( '.', -1 ) ;
253 QString directory = filename;
254 directory.remove(0, startDir.length());
255 directory = directory.section( '/', 0, -2);
256
257 QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter", "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
258
259 // If this file is an image, insert the details into the music_albumart table
260 if (nameFilter.indexOf(extension.toLower()) > -1)
261 {
262 QString name = filename.section( '/', -1);
263
265 query.prepare("INSERT INTO music_albumart "
266 "SET filename = :FILE, directory_id = :DIRID, "
267 "imagetype = :TYPE, hostname = :HOSTNAME;");
268
269 query.bindValue(":FILE", name);
270 query.bindValue(":DIRID", m_directoryid[directory]);
271 query.bindValue(":TYPE", AlbumArtImages::guessImageType(name));
272 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
273
274 if (!query.exec() || query.numRowsAffected() <= 0)
275 {
276 MythDB::DBError("music insert artwork", query);
277 }
278
280
281 return;
282 }
283
284 if (extension.isEmpty() || !MetaIO::kValidFileExtensions.contains(extension.toLower()))
285 {
286 LOG(VB_GENERAL, LOG_WARNING, QString("Ignoring filename with unsupported filename: '%1'").arg(filename));
287 return;
288 }
289
290 LOG(VB_FILE, LOG_INFO, QString("Reading metadata from %1").arg(filename));
292 if (data)
293 {
294 data->setFileSize((quint64)QFileInfo(filename).size());
296
297 QString album_cache_string;
298
299 // Set values from cache
300 int did = m_directoryid[directory];
301 if (did >= 0)
302 data->setDirectoryId(did);
303
304 int aid = m_artistid[data->Artist().toLower()];
305 if (aid > 0)
306 {
307 data->setArtistId(aid);
308
309 // The album cache depends on the artist id
310 album_cache_string = QString::number(data->getArtistId()) + "#"
311 + data->Album().toLower();
312
313 if (m_albumid[album_cache_string] > 0)
314 data->setAlbumId(m_albumid[album_cache_string]);
315 }
316
317 int caid = m_artistid[data->CompilationArtist().toLower()];
318 if (caid > 0)
319 data->setCompilationArtistId(caid);
320
321 int gid = m_genreid[data->Genre().toLower()];
322 if (gid > 0)
323 data->setGenreId(gid);
324
325 // Commit track info to database
326 data->dumpToDatabase();
327
328 // Update the cache
329 m_artistid[data->Artist().toLower()] =
330 data->getArtistId();
331
332 m_artistid[data->CompilationArtist().toLower()] =
334
335 m_genreid[data->Genre().toLower()] =
336 data->getGenreId();
337
338 album_cache_string = QString::number(data->getArtistId()) + "#"
339 + data->Album().toLower();
340 m_albumid[album_cache_string] = data->getAlbumId();
341
342 // read any embedded images from the tag
344
345 if (tagger)
346 {
347 if (tagger->supportsEmbeddedImages())
348 {
349 AlbumArtList artList = tagger->getAlbumArtList(data->Filename());
350 data->setEmbeddedAlbumArt(artList);
352 }
353 delete tagger;
354 }
355
356 delete data;
357
359 }
360}
361
369{
370 LOG(VB_GENERAL, LOG_INFO, "Cleaning old entries from music database");
371
373 MSqlQuery deletequery(MSqlQuery::InitCon());
374
375 // delete unused genre_ids from music_genres
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);
380
381 deletequery.prepare("DELETE FROM music_genres WHERE genre_id=:GENREID");
382 while (query.next())
383 {
384 int genreid = query.value(0).toInt();
385 deletequery.bindValue(":GENREID", genreid);
386 if (!deletequery.exec())
387 MythDB::DBError("MusicFileScanner::cleanDB - delete music_genres",
388 deletequery);
389 }
390
391 // delete unused album_ids from music_albums
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);
396
397 deletequery.prepare("DELETE FROM music_albums WHERE album_id=:ALBUMID");
398 while (query.next())
399 {
400 int albumid = query.value(0).toInt();
401 deletequery.bindValue(":ALBUMID", albumid);
402 if (!deletequery.exec())
403 MythDB::DBError("MusicFileScanner::cleanDB - delete music_albums",
404 deletequery);
405 }
406
407 // delete unused artist_ids from music_artists
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);
413
414
415 deletequery.prepare("DELETE FROM music_artists WHERE artist_id=:ARTISTID");
416 while (query.next())
417 {
418 int artistid = query.value(0).toInt();
419 deletequery.bindValue(":ARTISTID", artistid);
420 if (!deletequery.exec())
421 MythDB::DBError("MusicFileScanner::cleanDB - delete music_artists",
422 deletequery);
423 }
424
425 // delete unused directory_ids from music_directories
426 //
427 // Get a list of directory_ids not referenced in music_songs.
428 // This list will contain any directory that is only used for
429 // organization. I.E. If your songs are organized by artist and
430 // then by album, this will contain all of the artist directories.
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);
435
436 deletequery.prepare("DELETE FROM music_directories WHERE directory_id=:DIRECTORYID");
437
438 MSqlQuery parentquery(MSqlQuery::InitCon());
439 parentquery.prepare("SELECT COUNT(*) FROM music_directories "
440 "WHERE parent_id=:DIRECTORYID ");
441
442 MSqlQuery dirnamequery(MSqlQuery::InitCon());
443 dirnamequery.prepare("SELECT path FROM music_directories "
444 "WHERE directory_id=:DIRECTORYID ");
445
446 int deletedCount = 1;
447
448 while (deletedCount > 0)
449 {
450 deletedCount = 0;
451 query.seek(-1);
452
453 // loop through the list of unused directory_ids deleting any which
454 // aren't referenced by any other directories parent_id
455 while (query.next())
456 {
457 int directoryid = query.value(0).toInt();
458
459 // have we still got references to this directory_id from other directories
460 parentquery.bindValue(":DIRECTORYID", directoryid);
461 if (!parentquery.exec())
462 {
463 MythDB::DBError("MusicFileScanner::cleanDB - get parent directory count",
464 parentquery);
465 continue;
466 }
467 if (!parentquery.next())
468 continue;
469 int parentCount = parentquery.value(0).toInt();
470 if (parentCount != 0)
471 // Still has child directories
472 continue;
473 if(VERBOSE_LEVEL_CHECK(VB_GENERAL, LOG_DEBUG))
474 {
475 dirnamequery.bindValue(":DIRECTORYID", directoryid);
476 if (dirnamequery.exec() && dirnamequery.next())
477 {
478 LOG(VB_GENERAL, LOG_DEBUG,
479 QString("MusicFileScanner deleted directory %1 %2")
480 .arg(directoryid,5).arg(dirnamequery.value(0).toString()));
481 }
482 }
483 deletequery.bindValue(":DIRECTORYID", directoryid);
484 if (!deletequery.exec())
485 MythDB::DBError("MusicFileScanner::cleanDB - delete music_directories",
486 deletequery);
487 deletedCount += deletequery.numRowsAffected();
488 }
489 LOG(VB_GENERAL, LOG_INFO,
490 QString("MusicFileScanner deleted %1 directory entries")
491 .arg(deletedCount));
492 }
493
494 // delete unused albumart_ids from music_albumart (embedded images)
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);
499
500 deletequery.prepare("DELETE FROM music_albumart WHERE albumart_id=:ALBUMARTID");
501 while (query.next())
502 {
503 int albumartid = query.value(0).toInt();
504 deletequery.bindValue(":ALBUMARTID", albumartid);
505 if (!deletequery.exec())
506 MythDB::DBError("MusicFileScanner::cleanDB - delete music_albumart",
507 deletequery);
508 }
509}
510
521void MusicFileScanner::RemoveFileFromDB(const QString &filename, const QString &startDir)
522{
523 QString sqlfilename(filename);
524 sqlfilename.remove(0, startDir.length());
525 // We know that the filename will not contain :// as the SQL limits this
526 QString directory = sqlfilename.section( '/', 0, -2 ) ;
527 sqlfilename = sqlfilename.section( '/', -1 ) ;
528
529 QString extension = sqlfilename.section( '.', -1 ) ;
530
531 QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter",
532 "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
533
534 if (nameFilter.indexOf(extension.toLower()) > -1)
535 {
537 query.prepare("DELETE FROM music_albumart WHERE filename= :FILE AND "
538 "directory_id= :DIRID;");
539 query.bindValue(":FILE", sqlfilename);
540 query.bindValue(":DIRID", m_directoryid[directory]);
541
542 if (!query.exec() || query.numRowsAffected() <= 0)
543 {
544 MythDB::DBError("music delete artwork", query);
545 }
546
548
549 return;
550 }
551
553 query.prepare("DELETE FROM music_songs WHERE filename = :NAME ;");
554 query.bindValue(":NAME", sqlfilename);
555 if (!query.exec())
556 MythDB::DBError("MusicFileScanner::RemoveFileFromDB - deleting music_songs",
557 query);
558
560}
561
572void MusicFileScanner::UpdateFileInDB(const QString &filename, const QString &startDir)
573{
574 QString dbFilename = filename;
575 dbFilename.remove(0, startDir.length());
576
577 QString directory = filename;
578 directory.remove(0, startDir.length());
579 directory = directory.section( '/', 0, -2);
580
581 MusicMetadata *db_meta = MetaIO::getMetadata(dbFilename);
583
584 if (db_meta && disk_meta)
585 {
586 if (db_meta->ID() <= 0)
587 {
588 LOG(VB_GENERAL, LOG_ERR, QString("Asked to update track with "
589 "invalid ID - %1")
590 .arg(db_meta->ID()));
591 delete disk_meta;
592 delete db_meta;
593 return;
594 }
595
596 disk_meta->setID(db_meta->ID());
597 disk_meta->setRating(db_meta->Rating());
598 if (db_meta->PlayCount() > disk_meta->PlayCount())
599 disk_meta->setPlaycount(db_meta->Playcount());
600
601 QString album_cache_string;
602
603 // Set values from cache
604 int did = m_directoryid[directory];
605 if (did > 0)
606 disk_meta->setDirectoryId(did);
607
608 int aid = m_artistid[disk_meta->Artist().toLower()];
609 if (aid > 0)
610 {
611 disk_meta->setArtistId(aid);
612
613 // The album cache depends on the artist id
614 album_cache_string = QString::number(disk_meta->getArtistId()) + "#" +
615 disk_meta->Album().toLower();
616
617 if (m_albumid[album_cache_string] > 0)
618 disk_meta->setAlbumId(m_albumid[album_cache_string]);
619 }
620
621 int caid = m_artistid[disk_meta->CompilationArtist().toLower()];
622 if (caid > 0)
623 disk_meta->setCompilationArtistId(caid);
624
625 int gid = m_genreid[disk_meta->Genre().toLower()];
626 if (gid > 0)
627 disk_meta->setGenreId(gid);
628
629 disk_meta->setFileSize((quint64)QFileInfo(filename).size());
630
631 disk_meta->setHostname(gCoreContext->GetHostName());
632
633 // Commit track info to database
634 disk_meta->dumpToDatabase();
635
636 // Update the cache
637 m_artistid[disk_meta->Artist().toLower()]
638 = disk_meta->getArtistId();
639 m_artistid[disk_meta->CompilationArtist().toLower()]
640 = disk_meta->getCompilationArtistId();
641 m_genreid[disk_meta->Genre().toLower()]
642 = disk_meta->getGenreId();
643 album_cache_string = QString::number(disk_meta->getArtistId()) + "#" +
644 disk_meta->Album().toLower();
645 m_albumid[album_cache_string] = disk_meta->getAlbumId();
646 }
647
648 delete disk_meta;
649 delete db_meta;
650}
651
661void MusicFileScanner::SearchDirs(const QStringList &dirList)
662{
663 QString host = gCoreContext->GetHostName();
664
665 if (IsRunning())
666 {
667 // check how long the scanner has been running
668 // if it's more than 60 minutes assume something went wrong
669 QString lastRun = gCoreContext->GetSetting("MusicScannerLastRunStart", "");
670 if (!lastRun.isEmpty())
671 {
672 QDateTime dtLastRun = QDateTime::fromString(lastRun, Qt::ISODate);
673 if (dtLastRun.isValid())
674 {
675 static constexpr int64_t kOneHour {60LL * 60};
676 if (MythDate::current() > dtLastRun.addSecs(kOneHour))
677 {
678 LOG(VB_GENERAL, LOG_INFO, "Music file scanner has been running for more than 60 minutes. Lets reset and try again");
679 gCoreContext->SendMessage(QString("MUSIC_SCANNER_ERROR %1 %2").arg(host, "Stalled"));
680
681 // give the user time to read the notification before restarting the scan
682 std::this_thread::sleep_for(5s);
683 }
684 else
685 {
686 LOG(VB_GENERAL, LOG_INFO, "Music file scanner is already running");
687 gCoreContext->SendMessage(QString("MUSIC_SCANNER_ERROR %1 %2").arg(host, "Already_Running"));
688 return;
689 }
690 }
691 }
692 }
693
694 //TODO: could sanity check the directory exists and is readable here?
695
696 LOG(VB_GENERAL, LOG_INFO, "Music file scanner started");
697 gCoreContext->SendMessage(QString("MUSIC_SCANNER_STARTED %1").arg(host));
698
700 QString status = QString("running");
701 updateLastRunStatus(status);
702
705
706 MusicLoadedMap music_files;
707 MusicLoadedMap art_files;
708 MusicLoadedMap::Iterator iter;
709
710 for (int x = 0; x < dirList.count(); x++)
711 {
712 QString startDir = dirList[x];
713 m_startDirs.append(startDir + '/');
714 LOG(VB_GENERAL, LOG_INFO, QString("Searching '%1' for music files").arg(startDir));
715
716 BuildFileList(startDir, music_files, art_files, 0);
717 }
718
719 m_tracksTotal = music_files.count();
720 m_coverartTotal = art_files.count();
721
722 ScanMusic(music_files);
723 ScanArtwork(art_files);
724
725 LOG(VB_GENERAL, LOG_INFO, "Updating database");
726
727 /*
728 This can be optimised quite a bit by consolidating all commands
729 via a lot of refactoring.
730
731 1) group all files of the same decoder type, and don't
732 create/delete a Decoder pr. AddFileToDB. Or make Decoders be
733 singletons, it should be a fairly simple change.
734
735 2) RemoveFileFromDB should group the remove into one big SQL.
736
737 3) UpdateFileInDB, same as 1.
738 */
739
740 for (iter = music_files.begin(); iter != music_files.end(); iter++)
741 {
742 if ((*iter).location == MusicFileScanner::kFileSystem)
743 {
744 AddFileToDB(iter.key(), (*iter).startDir);
745 }
746 else if ((*iter).location == MusicFileScanner::kDatabase)
747 {
748 RemoveFileFromDB(iter.key(), (*iter).startDir);
749 }
750 else if ((*iter).location == MusicFileScanner::kNeedUpdate)
751 {
752 UpdateFileInDB(iter.key(), (*iter).startDir);
754 }
755 }
756
757 for (iter = art_files.begin(); iter != art_files.end(); iter++)
758 {
759 if ((*iter).location == MusicFileScanner::kFileSystem)
760 {
761 AddFileToDB(iter.key(), (*iter).startDir);
762 }
763 else if ((*iter).location == MusicFileScanner::kDatabase)
764 {
765 RemoveFileFromDB(iter.key(), (*iter).startDir);
766 }
767 else if ((*iter).location == MusicFileScanner::kNeedUpdate)
768 {
769 UpdateFileInDB(iter.key(), (*iter).startDir);
771 }
772 }
773
774 // Cleanup orphaned entries from the database
775 cleanDB();
776
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)")
783
784
785 LOG(VB_GENERAL, LOG_INFO, "Music file scanner finished ");
786 LOG(VB_GENERAL, LOG_INFO, trackStatus);
787 LOG(VB_GENERAL, LOG_INFO, coverartStatus);
788
789 gCoreContext->SendMessage(QString("MUSIC_SCANNER_FINISHED %1 %2 %3 %4 %5")
790 .arg(host).arg(m_tracksTotal).arg(m_tracksAdded)
792
794 status = QString("success - %1 - %2").arg(trackStatus, coverartStatus);
795 updateLastRunStatus(status);
796}
797
806{
807 MusicLoadedMap::Iterator iter;
808
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");
815
816 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
817
818 if (!query.exec())
819 MythDB::DBError("MusicFileScanner::ScanMusic", query);
820
821 LOG(VB_GENERAL, LOG_INFO, "Checking tracks");
822
823 QString name;
824
825 if (query.isActive() && query.size() > 0)
826 {
827 while (query.next())
828 {
829 for (int x = 0; x < m_startDirs.count(); x++)
830 {
831 name = m_startDirs[x] + query.value(0).toString();
832 iter = music_files.find(name);
833 if (iter != music_files.end())
834 break;
835 }
836
837 if (iter != music_files.end())
838 {
839 if (music_files[name].location == MusicFileScanner::kDatabase)
840 continue;
841 if (m_forceupdate || HasFileChanged(name, query.value(1).toString()))
842 {
843 music_files[name].location = MusicFileScanner::kNeedUpdate;
844 }
845 else
846 {
848 music_files.erase(iter);
849 }
850 }
851 else
852 {
853 music_files[name].location = MusicFileScanner::kDatabase;
854 }
855 }
856 }
857}
858
867{
868 MusicLoadedMap::Iterator iter;
869
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");
876
877 query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
878
879 if (!query.exec())
880 MythDB::DBError("MusicFileScanner::ScanArtwork", query);
881
882 LOG(VB_GENERAL, LOG_INFO, "Checking artwork");
883
884 QString name;
885
886 if (query.isActive() && query.size() > 0)
887 {
888 while (query.next())
889 {
890 for (int x = 0; x < m_startDirs.count(); x++)
891 {
892 name = m_startDirs[x] + query.value(0).toString();
893 iter = music_files.find(name);
894 if (iter != music_files.end())
895 break;
896 }
897
898 if (iter != music_files.end())
899 {
900 if (music_files[name].location == MusicFileScanner::kDatabase)
901 continue;
903 music_files.erase(iter);
904 }
905 else
906 {
907 music_files[name].location = MusicFileScanner::kDatabase;
908 }
909 }
910 }
911}
912
913// static
915{
916 return gCoreContext->GetSetting("MusicScannerLastRunStatus", "") == "running";
917}
918
920{
921 QDateTime qdtNow = MythDate::current();
922 gCoreContext->SaveSetting("MusicScannerLastRunEnd", qdtNow.toString(Qt::ISODate));
923}
924
926{
927 QDateTime qdtNow = MythDate::current();
928 gCoreContext->SaveSetting("MusicScannerLastRunStart", qdtNow.toString(Qt::ISODate));
929}
930
932{
933 gCoreContext->SaveSetting("MusicScannerLastRunStatus", status);
934}
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.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
QVariant value(int i) const
Definition: mythdbcon.h:204
int size(void) const
Definition: mythdbcon.h:214
int numRowsAffected() const
Definition: mythdbcon.h:217
bool isActive(void) const
Definition: mythdbcon.h:215
bool seek(int where, bool relative=false)
Wrap QSqlQuery::seek(int,bool)
Definition: mythdbcon.cpp:833
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:936
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
Definition: metaio.h:18
static MusicMetadata * getMetadata(const QString &filename)
Get the metadata for filename.
Definition: metaio.cpp:90
static const QString kValidFileExtensions
Definition: metaio.h:160
static MusicMetadata * readMetadata(const QString &filename)
Read the metadata from filename directly.
Definition: metaio.cpp:62
virtual bool supportsEmbeddedImages(void)
Does the tag support embedded cover art.
Definition: metaio.h:57
static MetaIO * createTagger(const QString &filename)
Finds an appropriate tagger for the given file.
Definition: metaio.cpp:31
virtual AlbumArtList getAlbumArtList(const QString &filename)
Reads the list of embedded images in the tag.
Definition: metaio.h:68
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)
QStringList m_startDirs
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.
void setDirectoryId(int ldirectoryid)
void setArtistId(int lartistid)
void setID(IdType lid)
void setHostname(const QString &host)
QString CompilationArtist() const
void setCompilationArtistId(int lartistid)
void setAlbumId(int lalbumid)
int Playcount() const
void setEmbeddedAlbumArt(AlbumArtList &albumart)
IdType ID() const
QString Filename(bool find=true)
QString Artist() const
void setRating(int lrating)
void setPlaycount(int lplaycount)
int Rating() const
void setGenreId(int lgenreid)
int PlayCount() const
QString Genre() const
int getCompilationArtistId()
AlbumArtImages * getAlbumArtImages(void)
QString Album() const
void setFileSize(uint64_t lfilesize)
void dumpToDatabase(void)
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)
Definition: mythdb.cpp:225
static const iso6937table * d
QList< AlbumArtImage * > AlbumArtList
Definition: musicmetadata.h:58
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ ISODate
Default UTC.
Definition: mythdate.h:17
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15