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