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  {
140  LOG(VB_GENERAL, LOG_INFO,
141  QString("Found file with unsupported extension %1")
142  .arg(filename));
143  }
144  }
145  }
146 }
147 
149 {
150  QFileInfo fi(filename);
151  QString extension = fi.suffix().toLower();
152  QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter", "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
153 
154 
155  return !extension.isEmpty() && nameFilter.indexOf(extension.toLower()) > -1;
156 }
157 
159 {
160  QFileInfo fi(filename);
161  QString extension = fi.suffix().toLower();
162  QString nameFilter = MetaIO::kValidFileExtensions;
163 
164  return !extension.isEmpty() && nameFilter.indexOf(extension.toLower()) > -1;
165 }
166 
177 int MusicFileScanner::GetDirectoryId(const QString &directory, const int &parentid)
178 {
179  if (directory.isEmpty())
180  return 0;
181 
183 
184  // Load the directory id or insert it and get the id
185  query.prepare("SELECT directory_id FROM music_directories "
186  "WHERE path = BINARY :DIRECTORY ;");
187  query.bindValue(":DIRECTORY", directory);
188 
189  if (!query.exec())
190  {
191  MythDB::DBError("music select directory id", query);
192  return -1;
193  }
194 
195  if (query.next())
196  {
197  // we have found the directory already in the DB
198  return query.value(0).toInt();
199  }
200 
201  // directory is not in the DB so insert it
202  query.prepare("INSERT INTO music_directories (path, parent_id) "
203  "VALUES (:DIRECTORY, :PARENTID);");
204  query.bindValue(":DIRECTORY", directory);
205  query.bindValue(":PARENTID", parentid);
206 
207  if (!query.exec() || !query.isActive() || query.numRowsAffected() <= 0)
208  {
209  MythDB::DBError("music insert directory", query);
210  return -1;
211  }
212 
213  return query.lastInsertId().toInt();
214 }
215 
225  const QString &filename, const QString &date_modified)
226 {
227  QFileInfo fi(filename);
228  QDateTime dt = fi.lastModified();
229  if (dt.isValid())
230  {
231  QDateTime old_dt = MythDate::fromString(date_modified);
232  return !old_dt.isValid() || (dt > old_dt);
233  }
234  LOG(VB_GENERAL, LOG_ERR, QString("Failed to stat file: %1")
235  .arg(filename));
236  return false;
237 }
238 
254 void MusicFileScanner::AddFileToDB(const QString &filename, const QString &startDir)
255 {
256  QString extension = filename.section( '.', -1 ) ;
257  QString directory = filename;
258  directory.remove(0, startDir.length());
259  directory = directory.section( '/', 0, -2);
260 
261  QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter", "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
262 
263  // If this file is an image, insert the details into the music_albumart table
264  if (nameFilter.indexOf(extension.toLower()) > -1)
265  {
266  QString name = filename.section( '/', -1);
267 
269  query.prepare("INSERT INTO music_albumart "
270  "SET filename = :FILE, directory_id = :DIRID, "
271  "imagetype = :TYPE, hostname = :HOSTNAME;");
272 
273  query.bindValue(":FILE", name);
274  query.bindValue(":DIRID", m_directoryid[directory]);
276  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
277 
278  if (!query.exec() || query.numRowsAffected() <= 0)
279  {
280  MythDB::DBError("music insert artwork", query);
281  }
282 
283  ++m_coverartAdded;
284 
285  return;
286  }
287 
288  if (extension.isEmpty() || !MetaIO::kValidFileExtensions.contains(extension.toLower()))
289  {
290  LOG(VB_GENERAL, LOG_WARNING, QString("Ignoring filename with unsupported filename: '%1'").arg(filename));
291  return;
292  }
293 
294  LOG(VB_FILE, LOG_INFO, QString("Reading metadata from %1").arg(filename));
296  if (data)
297  {
298  data->setFileSize((quint64)QFileInfo(filename).size());
300 
301  QString album_cache_string;
302 
303  // Set values from cache
304  int did = m_directoryid[directory];
305  if (did >= 0)
306  data->setDirectoryId(did);
307 
308  int aid = m_artistid[data->Artist().toLower()];
309  if (aid > 0)
310  {
311  data->setArtistId(aid);
312 
313  // The album cache depends on the artist id
314  album_cache_string = QString::number(data->getArtistId()) + "#"
315  + data->Album().toLower();
316 
317  if (m_albumid[album_cache_string] > 0)
318  data->setAlbumId(m_albumid[album_cache_string]);
319  }
320 
321  int caid = m_artistid[data->CompilationArtist().toLower()];
322  if (caid > 0)
323  data->setCompilationArtistId(caid);
324 
325  int gid = m_genreid[data->Genre().toLower()];
326  if (gid > 0)
327  data->setGenreId(gid);
328 
329  // Commit track info to database
330  data->dumpToDatabase();
331 
332  // Update the cache
333  m_artistid[data->Artist().toLower()] =
334  data->getArtistId();
335 
336  m_artistid[data->CompilationArtist().toLower()] =
337  data->getCompilationArtistId();
338 
339  m_genreid[data->Genre().toLower()] =
340  data->getGenreId();
341 
342  album_cache_string = QString::number(data->getArtistId()) + "#"
343  + data->Album().toLower();
344  m_albumid[album_cache_string] = data->getAlbumId();
345 
346  // read any embedded images from the tag
348 
349  if (tagger)
350  {
351  if (tagger->supportsEmbeddedImages())
352  {
353  AlbumArtList artList = tagger->getAlbumArtList(data->Filename());
354  data->setEmbeddedAlbumArt(artList);
356  }
357  delete tagger;
358  }
359 
360  delete data;
361 
362  ++m_tracksAdded;
363  }
364 }
365 
373 {
374  LOG(VB_GENERAL, LOG_INFO, "Cleaning old entries from music database");
375 
377  MSqlQuery deletequery(MSqlQuery::InitCon());
378 
379  // delete unused genre_ids from music_genres
380  if (!query.exec("SELECT g.genre_id FROM music_genres g "
381  "LEFT JOIN music_songs s ON g.genre_id=s.genre_id "
382  "WHERE s.genre_id IS NULL;"))
383  MythDB::DBError("MusicFileScanner::cleanDB - select music_genres", query);
384 
385  deletequery.prepare("DELETE FROM music_genres WHERE genre_id=:GENREID");
386  while (query.next())
387  {
388  int genreid = query.value(0).toInt();
389  deletequery.bindValue(":GENREID", genreid);
390  if (!deletequery.exec())
391  MythDB::DBError("MusicFileScanner::cleanDB - delete music_genres",
392  deletequery);
393  }
394 
395  // delete unused album_ids from music_albums
396  if (!query.exec("SELECT a.album_id FROM music_albums a "
397  "LEFT JOIN music_songs s ON a.album_id=s.album_id "
398  "WHERE s.album_id IS NULL;"))
399  MythDB::DBError("MusicFileScanner::cleanDB - select music_albums", query);
400 
401  deletequery.prepare("DELETE FROM music_albums WHERE album_id=:ALBUMID");
402  while (query.next())
403  {
404  int albumid = query.value(0).toInt();
405  deletequery.bindValue(":ALBUMID", albumid);
406  if (!deletequery.exec())
407  MythDB::DBError("MusicFileScanner::cleanDB - delete music_albums",
408  deletequery);
409  }
410 
411  // delete unused artist_ids from music_artists
412  if (!query.exec("SELECT a.artist_id FROM music_artists a "
413  "LEFT JOIN music_songs s ON a.artist_id=s.artist_id "
414  "LEFT JOIN music_albums l ON a.artist_id=l.artist_id "
415  "WHERE s.artist_id IS NULL AND l.artist_id IS NULL"))
416  MythDB::DBError("MusicFileScanner::cleanDB - select music_artists", query);
417 
418 
419  deletequery.prepare("DELETE FROM music_artists WHERE artist_id=:ARTISTID");
420  while (query.next())
421  {
422  int artistid = query.value(0).toInt();
423  deletequery.bindValue(":ARTISTID", artistid);
424  if (!deletequery.exec())
425  MythDB::DBError("MusicFileScanner::cleanDB - delete music_artists",
426  deletequery);
427  }
428 
429  // delete unused directory_ids from music_directories
430  // get a list of directory_ids not referenced in music_songs
431  if (!query.exec("SELECT d.directory_id, d.parent_id FROM music_directories d "
432  "LEFT JOIN music_songs s ON d.directory_id=s.directory_id "
433  "WHERE s.directory_id IS NULL ORDER BY directory_id DESC;"))
434  MythDB::DBError("MusicFileScanner::cleanDB - select music_directories", query);
435 
436  deletequery.prepare("DELETE FROM music_directories WHERE directory_id=:DIRECTORYID");
437 
438  MSqlQuery parentquery(MSqlQuery::InitCon());
439  parentquery.prepare("SELECT COUNT(*) FROM music_directories "
440  "WHERE parent_id=:DIRECTORYID ");
441 
442  int deletedCount = 0;
443 
444  do
445  {
446  deletedCount = 0;
447 
448  if (!query.first())
449  break;
450 
451  // loop through the list of unused directory_ids deleting any which
452  // aren't referenced by any other directories parent_id
453  do
454  {
455  int directoryid = query.value(0).toInt();
456 
457  // have we still got references to this directory_id from other directories
458  parentquery.bindValue(":DIRECTORYID", directoryid);
459  if (!parentquery.exec())
460  MythDB::DBError("MusicFileScanner::cleanDB - get parent directory count",
461  parentquery);
462 
463  if (parentquery.next())
464  {
465  int parentCount = parentquery.value(0).toInt();
466 
467  if (parentCount == 0)
468  {
469  deletequery.bindValue(":DIRECTORYID", directoryid);
470  if (!deletequery.exec())
471  MythDB::DBError("MusicFileScanner::cleanDB - delete music_directories",
472  deletequery);
473 
474  deletedCount += deletequery.numRowsAffected();
475  }
476  }
477 
478  } while (query.next());
479 
480  } while (deletedCount > 0);
481 
482  // delete unused albumart_ids from music_albumart (embedded images)
483  if (!query.exec("SELECT a.albumart_id FROM music_albumart a LEFT JOIN "
484  "music_songs s ON a.song_id=s.song_id WHERE "
485  "embedded='1' AND s.song_id IS NULL;"))
486  MythDB::DBError("MusicFileScanner::cleanDB - select music_albumart", query);
487 
488  deletequery.prepare("DELETE FROM music_albumart WHERE albumart_id=:ALBUMARTID");
489  while (query.next())
490  {
491  int albumartid = query.value(0).toInt();
492  deletequery.bindValue(":ALBUMARTID", albumartid);
493  if (!deletequery.exec())
494  MythDB::DBError("MusicFileScanner::cleanDB - delete music_albumart",
495  deletequery);
496  }
497 }
498 
509 void MusicFileScanner::RemoveFileFromDB(const QString &filename, const QString &startDir)
510 {
511  QString sqlfilename(filename);
512  sqlfilename.remove(0, startDir.length());
513  // We know that the filename will not contain :// as the SQL limits this
514  QString directory = sqlfilename.section( '/', 0, -2 ) ;
515  sqlfilename = sqlfilename.section( '/', -1 ) ;
516 
517  QString extension = sqlfilename.section( '.', -1 ) ;
518 
519  QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter",
520  "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
521 
522  if (nameFilter.indexOf(extension.toLower()) > -1)
523  {
525  query.prepare("DELETE FROM music_albumart WHERE filename= :FILE AND "
526  "directory_id= :DIRID;");
527  query.bindValue(":FILE", sqlfilename);
528  query.bindValue(":DIRID", m_directoryid[directory]);
529 
530  if (!query.exec() || query.numRowsAffected() <= 0)
531  {
532  MythDB::DBError("music delete artwork", query);
533  }
534 
536 
537  return;
538  }
539 
541  query.prepare("DELETE FROM music_songs WHERE filename = :NAME ;");
542  query.bindValue(":NAME", sqlfilename);
543  if (!query.exec())
544  MythDB::DBError("MusicFileScanner::RemoveFileFromDB - deleting music_songs",
545  query);
546 
547  ++m_tracksRemoved;
548 }
549 
560 void MusicFileScanner::UpdateFileInDB(const QString &filename, const QString &startDir)
561 {
562  QString dbFilename = filename;
563  dbFilename.remove(0, startDir.length());
564 
565  QString directory = filename;
566  directory.remove(0, startDir.length());
567  directory = directory.section( '/', 0, -2);
568 
569  MusicMetadata *db_meta = MetaIO::getMetadata(dbFilename);
571 
572  if (db_meta && disk_meta)
573  {
574  if (db_meta->ID() <= 0)
575  {
576  LOG(VB_GENERAL, LOG_ERR, QString("Asked to update track with "
577  "invalid ID - %1")
578  .arg(db_meta->ID()));
579  delete disk_meta;
580  delete db_meta;
581  return;
582  }
583 
584  disk_meta->setID(db_meta->ID());
585  disk_meta->setRating(db_meta->Rating());
586  if (db_meta->PlayCount() > disk_meta->PlayCount())
587  disk_meta->setPlaycount(db_meta->Playcount());
588 
589  QString album_cache_string;
590 
591  // Set values from cache
592  int did = m_directoryid[directory];
593  if (did > 0)
594  disk_meta->setDirectoryId(did);
595 
596  int aid = m_artistid[disk_meta->Artist().toLower()];
597  if (aid > 0)
598  {
599  disk_meta->setArtistId(aid);
600 
601  // The album cache depends on the artist id
602  album_cache_string = QString::number(disk_meta->getArtistId()) + "#" +
603  disk_meta->Album().toLower();
604 
605  if (m_albumid[album_cache_string] > 0)
606  disk_meta->setAlbumId(m_albumid[album_cache_string]);
607  }
608 
609  int caid = m_artistid[disk_meta->CompilationArtist().toLower()];
610  if (caid > 0)
611  disk_meta->setCompilationArtistId(caid);
612 
613  int gid = m_genreid[disk_meta->Genre().toLower()];
614  if (gid > 0)
615  disk_meta->setGenreId(gid);
616 
617  disk_meta->setFileSize((quint64)QFileInfo(filename).size());
618 
619  disk_meta->setHostname(gCoreContext->GetHostName());
620 
621  // Commit track info to database
622  disk_meta->dumpToDatabase();
623 
624  // Update the cache
625  m_artistid[disk_meta->Artist().toLower()]
626  = disk_meta->getArtistId();
627  m_artistid[disk_meta->CompilationArtist().toLower()]
628  = disk_meta->getCompilationArtistId();
629  m_genreid[disk_meta->Genre().toLower()]
630  = disk_meta->getGenreId();
631  album_cache_string = QString::number(disk_meta->getArtistId()) + "#" +
632  disk_meta->Album().toLower();
633  m_albumid[album_cache_string] = disk_meta->getAlbumId();
634  }
635 
636  delete disk_meta;
637  delete db_meta;
638 }
639 
649 void MusicFileScanner::SearchDirs(const QStringList &dirList)
650 {
651  QString host = gCoreContext->GetHostName();
652 
653  if (IsRunning())
654  {
655  // check how long the scanner has been running
656  // if it's more than 60 minutes assume something went wrong
657  QString lastRun = gCoreContext->GetSetting("MusicScannerLastRunStart", "");
658  if (!lastRun.isEmpty())
659  {
660  QDateTime dtLastRun = QDateTime::fromString(lastRun, Qt::ISODate);
661  if (dtLastRun.isValid())
662  {
663  if (MythDate::current() > dtLastRun.addSecs(60*60))
664  {
665  LOG(VB_GENERAL, LOG_INFO, "Music file scanner has been running for more than 60 minutes. Lets reset and try again");
666  gCoreContext->SendMessage(QString("MUSIC_SCANNER_ERROR %1 %2").arg(host).arg("Stalled"));
667 
668  // give the user time to read the notification before restarting the scan
669  sleep(5);
670  }
671  else
672  {
673  LOG(VB_GENERAL, LOG_INFO, "Music file scanner is already running");
674  gCoreContext->SendMessage(QString("MUSIC_SCANNER_ERROR %1 %2").arg(host).arg("Already_Running"));
675  return;
676  }
677  }
678  }
679  }
680 
681  //TODO: could sanity check the directory exists and is readable here?
682 
683  LOG(VB_GENERAL, LOG_INFO, "Music file scanner started");
684  gCoreContext->SendMessage(QString("MUSIC_SCANNER_STARTED %1").arg(host));
685 
687  QString status = QString("running");
688  updateLastRunStatus(status);
689 
692 
693  MusicLoadedMap music_files;
694  MusicLoadedMap art_files;
695  MusicLoadedMap::Iterator iter;
696 
697  for (int x = 0; x < dirList.count(); x++)
698  {
699  QString startDir = dirList[x];
700  m_startDirs.append(startDir + '/');
701  LOG(VB_GENERAL, LOG_INFO, QString("Searching '%1' for music files").arg(startDir));
702 
703  BuildFileList(startDir, music_files, art_files, 0);
704  }
705 
706  m_tracksTotal = music_files.count();
707  m_coverartTotal = art_files.count();
708 
709  ScanMusic(music_files);
710  ScanArtwork(art_files);
711 
712  LOG(VB_GENERAL, LOG_INFO, "Updating database");
713 
714  /*
715  This can be optimised quite a bit by consolidating all commands
716  via a lot of refactoring.
717 
718  1) group all files of the same decoder type, and don't
719  create/delete a Decoder pr. AddFileToDB. Or make Decoders be
720  singletons, it should be a fairly simple change.
721 
722  2) RemoveFileFromDB should group the remove into one big SQL.
723 
724  3) UpdateFileInDB, same as 1.
725  */
726 
727  for (iter = music_files.begin(); iter != music_files.end(); iter++)
728  {
729  if ((*iter).location == MusicFileScanner::kFileSystem)
730  AddFileToDB(iter.key(), (*iter).startDir);
731  else if ((*iter).location == MusicFileScanner::kDatabase)
732  RemoveFileFromDB(iter.key(), (*iter).startDir);
733  else if ((*iter).location == MusicFileScanner::kNeedUpdate)
734  {
735  UpdateFileInDB(iter.key(), (*iter).startDir);
736  ++m_tracksUpdated;
737  }
738  }
739 
740  for (iter = art_files.begin(); iter != art_files.end(); iter++)
741  {
742  if ((*iter).location == MusicFileScanner::kFileSystem)
743  AddFileToDB(iter.key(), (*iter).startDir);
744  else if ((*iter).location == MusicFileScanner::kDatabase)
745  RemoveFileFromDB(iter.key(), (*iter).startDir);
746  else if ((*iter).location == MusicFileScanner::kNeedUpdate)
747  {
748  UpdateFileInDB(iter.key(), (*iter).startDir);
750  }
751  }
752 
753  // Cleanup orphaned entries from the database
754  cleanDB();
755 
756  QString trackStatus = QString("total tracks found: %1 (unchanged: %2, added: %3, removed: %4, updated %5)")
758  .arg(m_tracksRemoved).arg(m_tracksUpdated);
759  QString coverartStatus = QString("total coverart found: %1 (unchanged: %2, added: %3, removed: %4, updated %5)")
762 
763 
764  LOG(VB_GENERAL, LOG_INFO, "Music file scanner finished ");
765  LOG(VB_GENERAL, LOG_INFO, trackStatus);
766  LOG(VB_GENERAL, LOG_INFO, coverartStatus);
767 
768  gCoreContext->SendMessage(QString("MUSIC_SCANNER_FINISHED %1 %2 %3 %4 %5")
769  .arg(host).arg(m_tracksTotal).arg(m_tracksAdded)
770  .arg(m_coverartTotal).arg(m_coverartAdded));
771 
773  status = QString("success - %1 - %2").arg(trackStatus).arg(coverartStatus);
774  updateLastRunStatus(status);
775 }
776 
785 {
786  MusicLoadedMap::Iterator iter;
787 
789  query.prepare("SELECT CONCAT_WS('/', path, filename), date_modified "
790  "FROM music_songs LEFT JOIN music_directories ON "
791  "music_songs.directory_id=music_directories.directory_id "
792  "WHERE filename NOT LIKE BINARY ('%://%') "
793  "AND hostname = :HOSTNAME");
794 
795  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
796 
797  if (!query.exec())
798  MythDB::DBError("MusicFileScanner::ScanMusic", query);
799 
800  LOG(VB_GENERAL, LOG_INFO, "Checking tracks");
801 
802  QString name;
803 
804  if (query.isActive() && query.size() > 0)
805  {
806  while (query.next())
807  {
808  for (int x = 0; x < m_startDirs.count(); x++)
809  {
810  name = m_startDirs[x] + query.value(0).toString();
811  if ((iter = music_files.find(name)) != music_files.end())
812  break;
813  }
814 
815  if (iter != music_files.end())
816  {
817  if (music_files[name].location == MusicFileScanner::kDatabase)
818  continue;
819  if (m_forceupdate || HasFileChanged(name, query.value(1).toString()))
820  music_files[name].location = MusicFileScanner::kNeedUpdate;
821  else
822  {
824  music_files.erase(iter);
825  }
826  }
827  else
828  music_files[name].location = MusicFileScanner::kDatabase;
829  }
830  }
831 }
832 
841 {
842  MusicLoadedMap::Iterator iter;
843 
845  query.prepare("SELECT CONCAT_WS('/', path, filename) "
846  "FROM music_albumart "
847  "LEFT JOIN music_directories ON music_albumart.directory_id=music_directories.directory_id "
848  "WHERE music_albumart.embedded = 0 "
849  "AND music_albumart.hostname = :HOSTNAME");
850 
851  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
852 
853  if (!query.exec())
854  MythDB::DBError("MusicFileScanner::ScanArtwork", query);
855 
856  LOG(VB_GENERAL, LOG_INFO, "Checking artwork");
857 
858  QString name;
859 
860  if (query.isActive() && query.size() > 0)
861  {
862  while (query.next())
863  {
864  for (int x = 0; x < m_startDirs.count(); x++)
865  {
866  name = m_startDirs[x] + query.value(0).toString();
867  if ((iter = music_files.find(name)) != music_files.end())
868  break;
869  }
870 
871  if (iter != music_files.end())
872  {
873  if (music_files[name].location == MusicFileScanner::kDatabase)
874  continue;
876  music_files.erase(iter);
877  }
878  else
879  {
880  music_files[name].location = MusicFileScanner::kDatabase;
881  }
882  }
883  }
884 }
885 
886 // static
888 {
889  return gCoreContext->GetSetting("MusicScannerLastRunStatus", "") == "running";
890 }
891 
893 {
894  QDateTime qdtNow = MythDate::current();
895  gCoreContext->SaveSetting("MusicScannerLastRunEnd", qdtNow.toString(Qt::ISODate));
896 }
897 
899 {
900  QDateTime qdtNow = MythDate::current();
901  gCoreContext->SaveSetting("MusicScannerLastRunStart", qdtNow.toString(Qt::ISODate));
902 }
903 
905 {
906  gCoreContext->SaveSetting("MusicScannerLastRunStatus", status);
907 }
QList< AlbumArtImage * > AlbumArtList
Definition: musicmetadata.h:56
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
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:864
void setGenreId(int lgenreid)
int getCompilationArtistId()
void setDirectoryId(int ldirectoryid)
void SaveSetting(const QString &key, int newValue)
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
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
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 kValidFileExtensions
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)
void setCompilationArtistId(int lartistid)
QMap< QString, MusicFileData > MusicLoadedMap
virtual bool supportsEmbeddedImages(void)
Does the tag support embedded cover art.
Definition: metaio.h:60
MSqlQuery query(MSqlQuery::InitCon())
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:888
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
void AddFileToDB(const QString &filename, const QString &startDir)
Insert file details into database. If it is an audio file, read the metadata and insert that informat...
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
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.
static ImageType guessImageType(const QString &filename)
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:793
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:808
void ScanArtwork(MusicLoadedMap &music_files)
Check a list of files against images already in the database.
void setPlaycount(int lplaycount)
void setFileSize(uint64_t lfilesize)
QString Filename(bool find=true)
int numRowsAffected() const
Definition: mythdbcon.h:206
MusicFileScanner(bool force=false)
QString CompilationArtist() const
Default UTC.
Definition: mythdate.h:14
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)
static bool IsArtFile(const QString &filename)
static bool IsRunning(void)
void setID(IdType lid)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
void setEmbeddedAlbumArt(AlbumArtList &albumart)