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