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