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 <mythdate.h>
10 #include <mythdb.h>
11 #include <mythcontext.h>
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 
73 void 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 : qAsConst(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 
173 int MusicFileScanner::GetDirectoryId(const QString &directory, const 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 
250 void 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]);
272  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
273 
274  if (!query.exec() || query.numRowsAffected() <= 0)
275  {
276  MythDB::DBError("music insert artwork", query);
277  }
278 
279  ++m_coverartAdded;
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()] =
333  data->getCompilationArtistId();
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 
358  ++m_tracksAdded;
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  // get a list of directory_ids not referenced in music_songs
427  if (!query.exec("SELECT d.directory_id, d.parent_id FROM music_directories d "
428  "LEFT JOIN music_songs s ON d.directory_id=s.directory_id "
429  "WHERE s.directory_id IS NULL ORDER BY directory_id DESC;"))
430  MythDB::DBError("MusicFileScanner::cleanDB - select music_directories", query);
431 
432  deletequery.prepare("DELETE FROM music_directories WHERE directory_id=:DIRECTORYID");
433 
434  MSqlQuery parentquery(MSqlQuery::InitCon());
435  parentquery.prepare("SELECT COUNT(*) FROM music_directories "
436  "WHERE parent_id=:DIRECTORYID ");
437 
438  int deletedCount = 0;
439 
440  do
441  {
442  deletedCount = 0;
443 
444  if (!query.first())
445  break;
446 
447  // loop through the list of unused directory_ids deleting any which
448  // aren't referenced by any other directories parent_id
449  do
450  {
451  int directoryid = query.value(0).toInt();
452 
453  // have we still got references to this directory_id from other directories
454  parentquery.bindValue(":DIRECTORYID", directoryid);
455  if (!parentquery.exec())
456  MythDB::DBError("MusicFileScanner::cleanDB - get parent directory count",
457  parentquery);
458 
459  if (parentquery.next())
460  {
461  int parentCount = parentquery.value(0).toInt();
462 
463  if (parentCount == 0)
464  {
465  deletequery.bindValue(":DIRECTORYID", directoryid);
466  if (!deletequery.exec())
467  MythDB::DBError("MusicFileScanner::cleanDB - delete music_directories",
468  deletequery);
469 
470  deletedCount += deletequery.numRowsAffected();
471  }
472  }
473 
474  } while (query.next());
475 
476  } while (deletedCount > 0);
477 
478  // delete unused albumart_ids from music_albumart (embedded images)
479  if (!query.exec("SELECT a.albumart_id FROM music_albumart a LEFT JOIN "
480  "music_songs s ON a.song_id=s.song_id WHERE "
481  "embedded='1' AND s.song_id IS NULL;"))
482  MythDB::DBError("MusicFileScanner::cleanDB - select music_albumart", query);
483 
484  deletequery.prepare("DELETE FROM music_albumart WHERE albumart_id=:ALBUMARTID");
485  while (query.next())
486  {
487  int albumartid = query.value(0).toInt();
488  deletequery.bindValue(":ALBUMARTID", albumartid);
489  if (!deletequery.exec())
490  MythDB::DBError("MusicFileScanner::cleanDB - delete music_albumart",
491  deletequery);
492  }
493 }
494 
505 void MusicFileScanner::RemoveFileFromDB(const QString &filename, const QString &startDir)
506 {
507  QString sqlfilename(filename);
508  sqlfilename.remove(0, startDir.length());
509  // We know that the filename will not contain :// as the SQL limits this
510  QString directory = sqlfilename.section( '/', 0, -2 ) ;
511  sqlfilename = sqlfilename.section( '/', -1 ) ;
512 
513  QString extension = sqlfilename.section( '.', -1 ) ;
514 
515  QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter",
516  "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
517 
518  if (nameFilter.indexOf(extension.toLower()) > -1)
519  {
521  query.prepare("DELETE FROM music_albumart WHERE filename= :FILE AND "
522  "directory_id= :DIRID;");
523  query.bindValue(":FILE", sqlfilename);
524  query.bindValue(":DIRID", m_directoryid[directory]);
525 
526  if (!query.exec() || query.numRowsAffected() <= 0)
527  {
528  MythDB::DBError("music delete artwork", query);
529  }
530 
532 
533  return;
534  }
535 
537  query.prepare("DELETE FROM music_songs WHERE filename = :NAME ;");
538  query.bindValue(":NAME", sqlfilename);
539  if (!query.exec())
540  MythDB::DBError("MusicFileScanner::RemoveFileFromDB - deleting music_songs",
541  query);
542 
543  ++m_tracksRemoved;
544 }
545 
556 void MusicFileScanner::UpdateFileInDB(const QString &filename, const QString &startDir)
557 {
558  QString dbFilename = filename;
559  dbFilename.remove(0, startDir.length());
560 
561  QString directory = filename;
562  directory.remove(0, startDir.length());
563  directory = directory.section( '/', 0, -2);
564 
565  MusicMetadata *db_meta = MetaIO::getMetadata(dbFilename);
567 
568  if (db_meta && disk_meta)
569  {
570  if (db_meta->ID() <= 0)
571  {
572  LOG(VB_GENERAL, LOG_ERR, QString("Asked to update track with "
573  "invalid ID - %1")
574  .arg(db_meta->ID()));
575  delete disk_meta;
576  delete db_meta;
577  return;
578  }
579 
580  disk_meta->setID(db_meta->ID());
581  disk_meta->setRating(db_meta->Rating());
582  if (db_meta->PlayCount() > disk_meta->PlayCount())
583  disk_meta->setPlaycount(db_meta->Playcount());
584 
585  QString album_cache_string;
586 
587  // Set values from cache
588  int did = m_directoryid[directory];
589  if (did > 0)
590  disk_meta->setDirectoryId(did);
591 
592  int aid = m_artistid[disk_meta->Artist().toLower()];
593  if (aid > 0)
594  {
595  disk_meta->setArtistId(aid);
596 
597  // The album cache depends on the artist id
598  album_cache_string = QString::number(disk_meta->getArtistId()) + "#" +
599  disk_meta->Album().toLower();
600 
601  if (m_albumid[album_cache_string] > 0)
602  disk_meta->setAlbumId(m_albumid[album_cache_string]);
603  }
604 
605  int caid = m_artistid[disk_meta->CompilationArtist().toLower()];
606  if (caid > 0)
607  disk_meta->setCompilationArtistId(caid);
608 
609  int gid = m_genreid[disk_meta->Genre().toLower()];
610  if (gid > 0)
611  disk_meta->setGenreId(gid);
612 
613  disk_meta->setFileSize((quint64)QFileInfo(filename).size());
614 
615  disk_meta->setHostname(gCoreContext->GetHostName());
616 
617  // Commit track info to database
618  disk_meta->dumpToDatabase();
619 
620  // Update the cache
621  m_artistid[disk_meta->Artist().toLower()]
622  = disk_meta->getArtistId();
623  m_artistid[disk_meta->CompilationArtist().toLower()]
624  = disk_meta->getCompilationArtistId();
625  m_genreid[disk_meta->Genre().toLower()]
626  = disk_meta->getGenreId();
627  album_cache_string = QString::number(disk_meta->getArtistId()) + "#" +
628  disk_meta->Album().toLower();
629  m_albumid[album_cache_string] = disk_meta->getAlbumId();
630  }
631 
632  delete disk_meta;
633  delete db_meta;
634 }
635 
645 void MusicFileScanner::SearchDirs(const QStringList &dirList)
646 {
647  QString host = gCoreContext->GetHostName();
648 
649  if (IsRunning())
650  {
651  // check how long the scanner has been running
652  // if it's more than 60 minutes assume something went wrong
653  QString lastRun = gCoreContext->GetSetting("MusicScannerLastRunStart", "");
654  if (!lastRun.isEmpty())
655  {
656  QDateTime dtLastRun = QDateTime::fromString(lastRun, Qt::ISODate);
657  if (dtLastRun.isValid())
658  {
659  if (MythDate::current() > dtLastRun.addSecs(60*60))
660  {
661  LOG(VB_GENERAL, LOG_INFO, "Music file scanner has been running for more than 60 minutes. Lets reset and try again");
662  gCoreContext->SendMessage(QString("MUSIC_SCANNER_ERROR %1 %2").arg(host).arg("Stalled"));
663 
664  // give the user time to read the notification before restarting the scan
665  sleep(5);
666  }
667  else
668  {
669  LOG(VB_GENERAL, LOG_INFO, "Music file scanner is already running");
670  gCoreContext->SendMessage(QString("MUSIC_SCANNER_ERROR %1 %2").arg(host).arg("Already_Running"));
671  return;
672  }
673  }
674  }
675  }
676 
677  //TODO: could sanity check the directory exists and is readable here?
678 
679  LOG(VB_GENERAL, LOG_INFO, "Music file scanner started");
680  gCoreContext->SendMessage(QString("MUSIC_SCANNER_STARTED %1").arg(host));
681 
683  QString status = QString("running");
684  updateLastRunStatus(status);
685 
688 
689  MusicLoadedMap music_files;
690  MusicLoadedMap art_files;
691  MusicLoadedMap::Iterator iter;
692 
693  for (int x = 0; x < dirList.count(); x++)
694  {
695  QString startDir = dirList[x];
696  m_startDirs.append(startDir + '/');
697  LOG(VB_GENERAL, LOG_INFO, QString("Searching '%1' for music files").arg(startDir));
698 
699  BuildFileList(startDir, music_files, art_files, 0);
700  }
701 
702  m_tracksTotal = music_files.count();
703  m_coverartTotal = art_files.count();
704 
705  ScanMusic(music_files);
706  ScanArtwork(art_files);
707 
708  LOG(VB_GENERAL, LOG_INFO, "Updating database");
709 
710  /*
711  This can be optimised quite a bit by consolidating all commands
712  via a lot of refactoring.
713 
714  1) group all files of the same decoder type, and don't
715  create/delete a Decoder pr. AddFileToDB. Or make Decoders be
716  singletons, it should be a fairly simple change.
717 
718  2) RemoveFileFromDB should group the remove into one big SQL.
719 
720  3) UpdateFileInDB, same as 1.
721  */
722 
723  for (iter = music_files.begin(); iter != music_files.end(); iter++)
724  {
725  if ((*iter).location == MusicFileScanner::kFileSystem)
726  AddFileToDB(iter.key(), (*iter).startDir);
727  else if ((*iter).location == MusicFileScanner::kDatabase)
728  RemoveFileFromDB(iter.key(), (*iter).startDir);
729  else if ((*iter).location == MusicFileScanner::kNeedUpdate)
730  {
731  UpdateFileInDB(iter.key(), (*iter).startDir);
732  ++m_tracksUpdated;
733  }
734  }
735 
736  for (iter = art_files.begin(); iter != art_files.end(); iter++)
737  {
738  if ((*iter).location == MusicFileScanner::kFileSystem)
739  AddFileToDB(iter.key(), (*iter).startDir);
740  else if ((*iter).location == MusicFileScanner::kDatabase)
741  RemoveFileFromDB(iter.key(), (*iter).startDir);
742  else if ((*iter).location == MusicFileScanner::kNeedUpdate)
743  {
744  UpdateFileInDB(iter.key(), (*iter).startDir);
746  }
747  }
748 
749  // Cleanup orphaned entries from the database
750  cleanDB();
751 
752  QString trackStatus = QString("total tracks found: %1 (unchanged: %2, added: %3, removed: %4, updated %5)")
754  .arg(m_tracksRemoved).arg(m_tracksUpdated);
755  QString coverartStatus = QString("total coverart found: %1 (unchanged: %2, added: %3, removed: %4, updated %5)")
758 
759 
760  LOG(VB_GENERAL, LOG_INFO, "Music file scanner finished ");
761  LOG(VB_GENERAL, LOG_INFO, trackStatus);
762  LOG(VB_GENERAL, LOG_INFO, coverartStatus);
763 
764  gCoreContext->SendMessage(QString("MUSIC_SCANNER_FINISHED %1 %2 %3 %4 %5")
767 
769  status = QString("success - %1 - %2").arg(trackStatus).arg(coverartStatus);
770  updateLastRunStatus(status);
771 }
772 
781 {
782  MusicLoadedMap::Iterator iter;
783 
785  query.prepare("SELECT CONCAT_WS('/', path, filename), date_modified "
786  "FROM music_songs LEFT JOIN music_directories ON "
787  "music_songs.directory_id=music_directories.directory_id "
788  "WHERE filename NOT LIKE BINARY ('%://%') "
789  "AND hostname = :HOSTNAME");
790 
791  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
792 
793  if (!query.exec())
794  MythDB::DBError("MusicFileScanner::ScanMusic", query);
795 
796  LOG(VB_GENERAL, LOG_INFO, "Checking tracks");
797 
798  QString name;
799 
800  if (query.isActive() && query.size() > 0)
801  {
802  while (query.next())
803  {
804  for (int x = 0; x < m_startDirs.count(); x++)
805  {
806  name = m_startDirs[x] + query.value(0).toString();
807  if ((iter = music_files.find(name)) != music_files.end())
808  break;
809  }
810 
811  if (iter != music_files.end())
812  {
813  if (music_files[name].location == MusicFileScanner::kDatabase)
814  continue;
815  if (m_forceupdate || HasFileChanged(name, query.value(1).toString()))
816  music_files[name].location = MusicFileScanner::kNeedUpdate;
817  else
818  {
820  music_files.erase(iter);
821  }
822  }
823  else
824  music_files[name].location = MusicFileScanner::kDatabase;
825  }
826  }
827 }
828 
837 {
838  MusicLoadedMap::Iterator iter;
839 
841  query.prepare("SELECT CONCAT_WS('/', path, filename) "
842  "FROM music_albumart "
843  "LEFT JOIN music_directories ON music_albumart.directory_id=music_directories.directory_id "
844  "WHERE music_albumart.embedded = 0 "
845  "AND music_albumart.hostname = :HOSTNAME");
846 
847  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
848 
849  if (!query.exec())
850  MythDB::DBError("MusicFileScanner::ScanArtwork", query);
851 
852  LOG(VB_GENERAL, LOG_INFO, "Checking artwork");
853 
854  QString name;
855 
856  if (query.isActive() && query.size() > 0)
857  {
858  while (query.next())
859  {
860  for (int x = 0; x < m_startDirs.count(); x++)
861  {
862  name = m_startDirs[x] + query.value(0).toString();
863  if ((iter = music_files.find(name)) != music_files.end())
864  break;
865  }
866 
867  if (iter != music_files.end())
868  {
869  if (music_files[name].location == MusicFileScanner::kDatabase)
870  continue;
872  music_files.erase(iter);
873  }
874  else
875  {
876  music_files[name].location = MusicFileScanner::kDatabase;
877  }
878  }
879  }
880 }
881 
882 // static
884 {
885  return gCoreContext->GetSetting("MusicScannerLastRunStatus", "") == "running";
886 }
887 
889 {
890  QDateTime qdtNow = MythDate::current();
891  gCoreContext->SaveSetting("MusicScannerLastRunEnd", qdtNow.toString(Qt::ISODate));
892 }
893 
895 {
896  QDateTime qdtNow = MythDate::current();
897  gCoreContext->SaveSetting("MusicScannerLastRunStart", qdtNow.toString(Qt::ISODate));
898 }
899 
901 {
902  gCoreContext->SaveSetting("MusicScannerLastRunStatus", status);
903 }
MusicFileScanner::MusicLoadedMap
QMap< QString, MusicFileData > MusicLoadedMap
Definition: musicfilescanner.h:30
force
bool force
Definition: mythtv/programs/mythcommflag/main.cpp:74
MusicMetadata::getCompilationArtistId
int getCompilationArtistId()
Definition: musicmetadata.cpp:540
MusicMetadata::setDirectoryId
void setDirectoryId(int ldirectoryid)
Definition: musicmetadata.h:181
MSqlQuery::isActive
bool isActive(void) const
Definition: mythdbcon.h:204
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:126
MusicFileScanner::ScanArtwork
void ScanArtwork(MusicLoadedMap &music_files)
Check a list of files against images already in the database.
Definition: musicfilescanner.cpp:836
MusicMetadata::getGenreId
int getGenreId()
Definition: musicmetadata.cpp:623
MythCoreContext::SendMessage
void SendMessage(const QString &message)
Definition: mythcorecontext.cpp:1523
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:203
MusicMetadata::Filename
QString Filename(bool find=true)
Definition: musicmetadata.cpp:966
MusicMetadata::Genre
QString Genre() const
Definition: musicmetadata.h:175
mythdb.h
MusicMetadata::getAlbumArtImages
AlbumArtImages * getAlbumArtImages(void)
Definition: musicmetadata.cpp:1365
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:32
MusicFileScanner::updateLastRunEnd
static void updateLastRunEnd(void)
Definition: musicfilescanner.cpp:888
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:888
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:198
arg
arg(title).arg(filename).arg(doDelete))
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:250
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:73
MusicFileScanner::m_artistid
IdCache m_artistid
Definition: musicfilescanner.h:58
MetaIO
Definition: metaio.h:18
MusicFileScanner::m_coverartUpdated
uint m_coverartUpdated
Definition: musicfilescanner.h:71
MusicMetadata::ID
IdType ID() const
Definition: musicmetadata.h:217
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
MusicFileScanner::MusicFileData::location
MusicFileLocation location
Definition: musicfilescanner.h:27
MusicMetadata::setRating
void setRating(int lrating)
Definition: musicmetadata.h:241
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MusicMetadata
Definition: musicmetadata.h:81
AlbumArtImages::guessImageType
static ImageType guessImageType(const QString &filename)
Definition: musicmetadata.cpp:2182
MusicFileScanner::m_coverartTotal
uint m_coverartTotal
Definition: musicfilescanner.h:67
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:1204
MusicFileScanner::kFileSystem
@ kFileSystem
Definition: musicfilescanner.h:18
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
musicfilescanner.h
sleep
unsigned sleep(unsigned int x)
Definition: compat.h:160
MetaIO::supportsEmbeddedImages
virtual bool supportsEmbeddedImages(void)
Does the tag support embedded cover art.
Definition: metaio.h:60
MusicFileScanner::m_albumid
IdCache m_albumid
Definition: musicfilescanner.h:60
mythdate.h
MusicFileScanner::IsRunning
static bool IsRunning(void)
Definition: musicfilescanner.cpp:883
MusicMetadata::setHostname
void setHostname(const QString &host)
Definition: musicmetadata.h:230
MusicMetadata::Playcount
int Playcount() const
Definition: musicmetadata.h:214
MSqlQuery::first
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:793
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
MusicFileScanner::MusicFileScanner
MusicFileScanner(bool force=false)
Definition: musicfilescanner.cpp:16
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:178
MusicMetadata::getArtistId
int getArtistId()
Definition: musicmetadata.cpp:503
MusicMetadata::getAlbumId
int getAlbumId()
Definition: musicmetadata.cpp:582
MusicFileScanner::IsArtFile
static bool IsArtFile(const QString &filename)
Definition: musicfilescanner.cpp:144
MusicMetadata::setFileSize
void setFileSize(uint64_t lfilesize)
Definition: musicmetadata.h:233
MusicFileScanner::m_coverartUnchanged
uint m_coverartUnchanged
Definition: musicfilescanner.h:68
MusicFileScanner::m_tracksUnchanged
uint m_tracksUnchanged
Definition: musicfilescanner.h:63
filename
QString filename
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:637
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:505
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:56
MetaIO::getAlbumArtList
virtual AlbumArtList getAlbumArtList(const QString &filename)
Reads the list of embedded images in the tag.
Definition: metaio.h:71
MusicFileScanner::ScanMusic
void ScanMusic(MusicLoadedMap &music_files)
Check a list of files against musics files already in the database.
Definition: musicfilescanner.cpp:780
MythDate::fromString
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
MusicMetadata::Album
QString Album() const
Definition: musicmetadata.h:150
MusicFileScanner::updateLastRunStatus
static void updateLastRunStatus(QString &status)
Definition: musicfilescanner.cpp:900
MusicMetadata::dumpToDatabase
void dumpToDatabase(void)
Definition: musicmetadata.cpp:672
AlbumArtList
QList< AlbumArtImage * > AlbumArtList
Definition: musicmetadata.h:56
dir
QDir dir
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1174
metaio.h
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
MusicFileScanner::m_genreid
IdCache m_genreid
Definition: musicfilescanner.h:59
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:14
MusicMetadata::setAlbumId
void setAlbumId(int lalbumid)
Definition: musicmetadata.h:190
mythcontext.h
MusicFileScanner::m_coverartRemoved
uint m_coverartRemoved
Definition: musicfilescanner.h:70
dirList
QStringList dirList
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1172
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:645
MusicFileScanner::cleanDB
static void cleanDB()
Clear orphaned entries from the genre, artist, album and albumart tables.
Definition: musicfilescanner.cpp:368
MusicMetadata::PlayCount
int PlayCount() const
Definition: musicmetadata.h:247
MusicMetadata::setPlaycount
void setPlaycount(int lplaycount)
Definition: musicmetadata.h:215
MSqlQuery::numRowsAffected
int numRowsAffected() const
Definition: mythdbcon.h:206
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:855
MusicMetadata::Rating
int Rating() const
Definition: musicmetadata.h:238
MusicFileScanner::updateLastRunStart
static void updateLastRunStart(void)
Definition: musicfilescanner.cpp:894
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:556
MusicMetadata::setID
void setID(IdType lid)
Definition: musicmetadata.h:218
MusicFileScanner::GetDirectoryId
static int GetDirectoryId(const QString &directory, const int &parentid)
Get an ID for the given directory from the database. If it doesn't already exist in the database,...
Definition: musicfilescanner.cpp:173
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:91
d
static const iso6937table * d
Definition: iso6937tables.cpp:1025
MythCoreContext::SaveSetting
void SaveSetting(const QString &key, int newValue)
Definition: mythcorecontext.cpp:898
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:220
MusicFileScanner::MusicFileData
Definition: musicfilescanner.h:25
MusicFileScanner::m_startDirs
QStringList m_startDirs
Definition: musicfilescanner.h:56
MetaIO::kValidFileExtensions
static const QString kValidFileExtensions
Definition: metaio.h:173
AlbumArtImages::dumpToDatabase
void dumpToDatabase(void)
saves or updates the image details in the DB
Definition: musicmetadata.cpp:2258
query
MSqlQuery query(MSqlQuery::InitCon())
MetaIO::readMetadata
static MusicMetadata * readMetadata(const QString &filename)
Read the metadata from filename directly.
Definition: metaio.cpp:63
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:915
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
MusicFileScanner::IsMusicFile
static bool IsMusicFile(const QString &filename)
Definition: musicfilescanner.cpp:154