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 
182  MSqlQuery query(MSqlQuery::InitCon());
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 
268  MSqlQuery query(MSqlQuery::InitCon());
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]);
275  query.bindValue(":TYPE", AlbumArtImages::guessImageType(name));
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 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_genreid[data->Genre().toLower()] =
333  data->getGenreId();
334 
335  album_cache_string = QString::number(data->getArtistId()) + "#"
336  + data->Album().toLower();
337  m_albumid[album_cache_string] = data->getAlbumId();
338 
339  // read any embedded images from the tag
341 
342  if (tagger)
343  {
344  if (tagger->supportsEmbeddedImages())
345  {
346  AlbumArtList artList = tagger->getAlbumArtList(data->Filename());
347  data->setEmbeddedAlbumArt(artList);
349  }
350  delete tagger;
351  }
352 
353  delete data;
354 
355  ++m_tracksAdded;
356  }
357 }
358 
366 {
367  LOG(VB_GENERAL, LOG_INFO, "Cleaning old entries from music database");
368 
369  MSqlQuery query(MSqlQuery::InitCon());
370  MSqlQuery deletequery(MSqlQuery::InitCon());
371 
372  // delete unused genre_ids from music_genres
373  if (!query.exec("SELECT g.genre_id FROM music_genres g "
374  "LEFT JOIN music_songs s ON g.genre_id=s.genre_id "
375  "WHERE s.genre_id IS NULL;"))
376  MythDB::DBError("MusicFileScanner::cleanDB - select music_genres", query);
377 
378  deletequery.prepare("DELETE FROM music_genres WHERE genre_id=:GENREID");
379  while (query.next())
380  {
381  int genreid = query.value(0).toInt();
382  deletequery.bindValue(":GENREID", genreid);
383  if (!deletequery.exec())
384  MythDB::DBError("MusicFileScanner::cleanDB - delete music_genres",
385  deletequery);
386  }
387 
388  // delete unused album_ids from music_albums
389  if (!query.exec("SELECT a.album_id FROM music_albums a "
390  "LEFT JOIN music_songs s ON a.album_id=s.album_id "
391  "WHERE s.album_id IS NULL;"))
392  MythDB::DBError("MusicFileScanner::cleanDB - select music_albums", query);
393 
394  deletequery.prepare("DELETE FROM music_albums WHERE album_id=:ALBUMID");
395  while (query.next())
396  {
397  int albumid = query.value(0).toInt();
398  deletequery.bindValue(":ALBUMID", albumid);
399  if (!deletequery.exec())
400  MythDB::DBError("MusicFileScanner::cleanDB - delete music_albums",
401  deletequery);
402  }
403 
404  // delete unused artist_ids from music_artists
405  if (!query.exec("SELECT a.artist_id FROM music_artists a "
406  "LEFT JOIN music_songs s ON a.artist_id=s.artist_id "
407  "LEFT JOIN music_albums l ON a.artist_id=l.artist_id "
408  "WHERE s.artist_id IS NULL AND l.artist_id IS NULL"))
409  MythDB::DBError("MusicFileScanner::cleanDB - select music_artists", query);
410 
411 
412  deletequery.prepare("DELETE FROM music_artists WHERE artist_id=:ARTISTID");
413  while (query.next())
414  {
415  int artistid = query.value(0).toInt();
416  deletequery.bindValue(":ARTISTID", artistid);
417  if (!deletequery.exec())
418  MythDB::DBError("MusicFileScanner::cleanDB - delete music_artists",
419  deletequery);
420  }
421 
422  // delete unused directory_ids from music_directories
423  // get a list of directory_ids not referenced in music_songs
424  if (!query.exec("SELECT d.directory_id, d.parent_id FROM music_directories d "
425  "LEFT JOIN music_songs s ON d.directory_id=s.directory_id "
426  "WHERE s.directory_id IS NULL ORDER BY directory_id DESC;"))
427  MythDB::DBError("MusicFileScanner::cleanDB - select music_directories", query);
428 
429  deletequery.prepare("DELETE FROM music_directories WHERE directory_id=:DIRECTORYID");
430 
431  MSqlQuery parentquery(MSqlQuery::InitCon());
432  parentquery.prepare("SELECT COUNT(*) FROM music_directories "
433  "WHERE parent_id=:DIRECTORYID ");
434 
435  int deletedCount = 0;
436 
437  do
438  {
439  deletedCount = 0;
440 
441  if (!query.first())
442  break;
443 
444  // loop through the list of unused directory_ids deleting any which
445  // aren't referenced by any other directories parent_id
446  do
447  {
448  int directoryid = query.value(0).toInt();
449 
450  // have we still got references to this directory_id from other directories
451  parentquery.bindValue(":DIRECTORYID", directoryid);
452  if (!parentquery.exec())
453  MythDB::DBError("MusicFileScanner::cleanDB - get parent directory count",
454  parentquery);
455 
456  if (parentquery.next())
457  {
458  int parentCount = parentquery.value(0).toInt();
459 
460  if (parentCount == 0)
461  {
462  deletequery.bindValue(":DIRECTORYID", directoryid);
463  if (!deletequery.exec())
464  MythDB::DBError("MusicFileScanner::cleanDB - delete music_directories",
465  deletequery);
466 
467  deletedCount += deletequery.numRowsAffected();
468  }
469  }
470 
471  } while (query.next());
472 
473  } while (deletedCount > 0);
474 
475  // delete unused albumart_ids from music_albumart (embedded images)
476  if (!query.exec("SELECT a.albumart_id FROM music_albumart a LEFT JOIN "
477  "music_songs s ON a.song_id=s.song_id WHERE "
478  "embedded='1' AND s.song_id IS NULL;"))
479  MythDB::DBError("MusicFileScanner::cleanDB - select music_albumart", query);
480 
481  deletequery.prepare("DELETE FROM music_albumart WHERE albumart_id=:ALBUMARTID");
482  while (query.next())
483  {
484  int albumartid = query.value(0).toInt();
485  deletequery.bindValue(":ALBUMARTID", albumartid);
486  if (!deletequery.exec())
487  MythDB::DBError("MusicFileScanner::cleanDB - delete music_albumart",
488  deletequery);
489  }
490 }
491 
502 void MusicFileScanner::RemoveFileFromDB(const QString &filename, const QString &startDir)
503 {
504  QString sqlfilename(filename);
505  sqlfilename.remove(0, startDir.length());
506  // We know that the filename will not contain :// as the SQL limits this
507  QString directory = sqlfilename.section( '/', 0, -2 ) ;
508  sqlfilename = sqlfilename.section( '/', -1 ) ;
509 
510  QString extension = sqlfilename.section( '.', -1 ) ;
511 
512  QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter",
513  "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
514 
515  if (nameFilter.indexOf(extension.toLower()) > -1)
516  {
517  MSqlQuery query(MSqlQuery::InitCon());
518  query.prepare("DELETE FROM music_albumart WHERE filename= :FILE AND "
519  "directory_id= :DIRID;");
520  query.bindValue(":FILE", sqlfilename);
521  query.bindValue(":DIRID", m_directoryid[directory]);
522 
523  if (!query.exec() || query.numRowsAffected() <= 0)
524  {
525  MythDB::DBError("music delete artwork", query);
526  }
527 
529 
530  return;
531  }
532 
533  MSqlQuery query(MSqlQuery::InitCon());
534  query.prepare("DELETE FROM music_songs WHERE filename = :NAME ;");
535  query.bindValue(":NAME", sqlfilename);
536  if (!query.exec())
537  MythDB::DBError("MusicFileScanner::RemoveFileFromDB - deleting music_songs",
538  query);
539 
540  ++m_tracksRemoved;
541 }
542 
553 void MusicFileScanner::UpdateFileInDB(const QString &filename, const QString &startDir)
554 {
555  QString dbFilename = filename;
556  dbFilename.remove(0, startDir.length());
557 
558  QString directory = filename;
559  directory.remove(0, startDir.length());
560  directory = directory.section( '/', 0, -2);
561 
562  MusicMetadata *db_meta = MetaIO::getMetadata(dbFilename);
564 
565  if (db_meta && disk_meta)
566  {
567  if (db_meta->ID() <= 0)
568  {
569  LOG(VB_GENERAL, LOG_ERR, QString("Asked to update track with "
570  "invalid ID - %1")
571  .arg(db_meta->ID()));
572  delete disk_meta;
573  delete db_meta;
574  return;
575  }
576 
577  disk_meta->setID(db_meta->ID());
578  disk_meta->setRating(db_meta->Rating());
579  if (db_meta->PlayCount() > disk_meta->PlayCount())
580  disk_meta->setPlaycount(db_meta->Playcount());
581 
582  QString album_cache_string;
583 
584  // Set values from cache
585  int did = m_directoryid[directory];
586  if (did > 0)
587  disk_meta->setDirectoryId(did);
588 
589  int aid = m_artistid[disk_meta->Artist().toLower()];
590  if (aid > 0)
591  {
592  disk_meta->setArtistId(aid);
593 
594  // The album cache depends on the artist id
595  album_cache_string = QString::number(disk_meta->getArtistId()) + "#" +
596  disk_meta->Album().toLower();
597 
598  if (m_albumid[album_cache_string] > 0)
599  disk_meta->setAlbumId(m_albumid[album_cache_string]);
600  }
601 
602  int gid = m_genreid[disk_meta->Genre().toLower()];
603  if (gid > 0)
604  disk_meta->setGenreId(gid);
605 
606  disk_meta->setFileSize((quint64)QFileInfo(filename).size());
607 
608  disk_meta->setHostname(gCoreContext->GetHostName());
609 
610  // Commit track info to database
611  disk_meta->dumpToDatabase();
612 
613  // Update the cache
614  m_artistid[disk_meta->Artist().toLower()]
615  = disk_meta->getArtistId();
616  m_genreid[disk_meta->Genre().toLower()]
617  = disk_meta->getGenreId();
618  album_cache_string = QString::number(disk_meta->getArtistId()) + "#" +
619  disk_meta->Album().toLower();
620  m_albumid[album_cache_string] = disk_meta->getAlbumId();
621  }
622 
623  delete disk_meta;
624  delete db_meta;
625 }
626 
636 void MusicFileScanner::SearchDirs(const QStringList &dirList)
637 {
638  QString host = gCoreContext->GetHostName();
639 
640  if (IsRunning())
641  {
642  // check how long the scanner has been running
643  // if it's more than 60 minutes assume something went wrong
644  QString lastRun = gCoreContext->GetSetting("MusicScannerLastRunStart", "");
645  if (!lastRun.isEmpty())
646  {
647  QDateTime dtLastRun = QDateTime::fromString(lastRun, Qt::ISODate);
648  if (dtLastRun.isValid())
649  {
650  if (MythDate::current() > dtLastRun.addSecs(60*60))
651  {
652  LOG(VB_GENERAL, LOG_INFO, "Music file scanner has been running for more than 60 minutes. Lets reset and try again");
653  gCoreContext->SendMessage(QString("MUSIC_SCANNER_ERROR %1 %2").arg(host).arg("Stalled"));
654 
655  // give the user time to read the notification before restarting the scan
656  sleep(5);
657  }
658  else
659  {
660  LOG(VB_GENERAL, LOG_INFO, "Music file scanner is already running");
661  gCoreContext->SendMessage(QString("MUSIC_SCANNER_ERROR %1 %2").arg(host).arg("Already_Running"));
662  return;
663  }
664  }
665  }
666  }
667 
668  //TODO: could sanity check the directory exists and is readable here?
669 
670  LOG(VB_GENERAL, LOG_INFO, "Music file scanner started");
671  gCoreContext->SendMessage(QString("MUSIC_SCANNER_STARTED %1").arg(host));
672 
674  QString status = QString("running");
675  updateLastRunStatus(status);
676 
679 
680  MusicLoadedMap music_files;
681  MusicLoadedMap art_files;
682  MusicLoadedMap::Iterator iter;
683 
684  for (int x = 0; x < dirList.count(); x++)
685  {
686  QString startDir = dirList[x];
687  m_startDirs.append(startDir + '/');
688  LOG(VB_GENERAL, LOG_INFO, QString("Searching '%1' for music files").arg(startDir));
689 
690  BuildFileList(startDir, music_files, art_files, 0);
691  }
692 
693  m_tracksTotal = music_files.count();
694  m_coverartTotal = art_files.count();
695 
696  ScanMusic(music_files);
697  ScanArtwork(art_files);
698 
699  LOG(VB_GENERAL, LOG_INFO, "Updating database");
700 
701  /*
702  This can be optimised quite a bit by consolidating all commands
703  via a lot of refactoring.
704 
705  1) group all files of the same decoder type, and don't
706  create/delete a Decoder pr. AddFileToDB. Or make Decoders be
707  singletons, it should be a fairly simple change.
708 
709  2) RemoveFileFromDB should group the remove into one big SQL.
710 
711  3) UpdateFileInDB, same as 1.
712  */
713 
714  for (iter = music_files.begin(); iter != music_files.end(); iter++)
715  {
716  if ((*iter).location == MusicFileScanner::kFileSystem)
717  AddFileToDB(iter.key(), (*iter).startDir);
718  else if ((*iter).location == MusicFileScanner::kDatabase)
719  RemoveFileFromDB(iter.key(), (*iter).startDir);
720  else if ((*iter).location == MusicFileScanner::kNeedUpdate)
721  {
722  UpdateFileInDB(iter.key(), (*iter).startDir);
723  ++m_tracksUpdated;
724  }
725  }
726 
727  for (iter = art_files.begin(); iter != art_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);
737  }
738  }
739 
740  // Cleanup orphaned entries from the database
741  cleanDB();
742 
743  QString trackStatus = QString("total tracks found: %1 (unchanged: %2, added: %3, removed: %4, updated %5)")
745  .arg(m_tracksRemoved).arg(m_tracksUpdated);
746  QString coverartStatus = QString("total coverart found: %1 (unchanged: %2, added: %3, removed: %4, updated %5)")
749 
750 
751  LOG(VB_GENERAL, LOG_INFO, "Music file scanner finished ");
752  LOG(VB_GENERAL, LOG_INFO, trackStatus);
753  LOG(VB_GENERAL, LOG_INFO, coverartStatus);
754 
755  gCoreContext->SendMessage(QString("MUSIC_SCANNER_FINISHED %1 %2 %3 %4 %5")
756  .arg(host).arg(m_tracksTotal).arg(m_tracksAdded)
757  .arg(m_coverartTotal).arg(m_coverartAdded));
758 
760  status = QString("success - %1 - %2").arg(trackStatus).arg(coverartStatus);
761  updateLastRunStatus(status);
762 }
763 
772 {
773  MusicLoadedMap::Iterator iter;
774 
775  MSqlQuery query(MSqlQuery::InitCon());
776  query.prepare("SELECT CONCAT_WS('/', path, filename), date_modified "
777  "FROM music_songs LEFT JOIN music_directories ON "
778  "music_songs.directory_id=music_directories.directory_id "
779  "WHERE filename NOT LIKE BINARY ('%://%') "
780  "AND hostname = :HOSTNAME");
781 
782  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
783 
784  if (!query.exec())
785  MythDB::DBError("MusicFileScanner::ScanMusic", query);
786 
787  LOG(VB_GENERAL, LOG_INFO, "Checking tracks");
788 
789  QString name;
790 
791  if (query.isActive() && query.size() > 0)
792  {
793  while (query.next())
794  {
795  for (int x = 0; x < m_startDirs.count(); x++)
796  {
797  name = m_startDirs[x] + query.value(0).toString();
798  if ((iter = music_files.find(name)) != music_files.end())
799  break;
800  }
801 
802  if (iter != music_files.end())
803  {
804  if (music_files[name].location == MusicFileScanner::kDatabase)
805  continue;
806  if (HasFileChanged(name, query.value(1).toString()))
807  music_files[name].location = MusicFileScanner::kNeedUpdate;
808  else
809  {
811  music_files.erase(iter);
812  }
813  }
814  else
815  music_files[name].location = MusicFileScanner::kDatabase;
816  }
817  }
818 }
819 
828 {
829  MusicLoadedMap::Iterator iter;
830 
831  MSqlQuery query(MSqlQuery::InitCon());
832  query.prepare("SELECT CONCAT_WS('/', path, filename) "
833  "FROM music_albumart "
834  "LEFT JOIN music_directories ON music_albumart.directory_id=music_directories.directory_id "
835  "WHERE music_albumart.embedded = 0 "
836  "AND music_albumart.hostname = :HOSTNAME");
837 
838  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
839 
840  if (!query.exec())
841  MythDB::DBError("MusicFileScanner::ScanArtwork", query);
842 
843  LOG(VB_GENERAL, LOG_INFO, "Checking artwork");
844 
845  QString name;
846 
847  if (query.isActive() && query.size() > 0)
848  {
849  while (query.next())
850  {
851  for (int x = 0; x < m_startDirs.count(); x++)
852  {
853  name = m_startDirs[x] + query.value(0).toString();
854  if ((iter = music_files.find(name)) != music_files.end())
855  break;
856  }
857 
858  if (iter != music_files.end())
859  {
860  if (music_files[name].location == MusicFileScanner::kDatabase)
861  continue;
863  music_files.erase(iter);
864  }
865  else
866  {
867  music_files[name].location = MusicFileScanner::kDatabase;
868  }
869  }
870  }
871 }
872 
873 // static
875 {
876  return gCoreContext->GetSetting("MusicScannerLastRunStatus", "") == "running";
877 }
878 
880 {
881  QDateTime qdtNow = MythDate::current();
882  gCoreContext->SaveSetting("MusicScannerLastRunEnd", qdtNow.toString(Qt::ISODate));
883 }
884 
886 {
887  QDateTime qdtNow = MythDate::current();
888  gCoreContext->SaveSetting("MusicScannerLastRunStart", qdtNow.toString(Qt::ISODate));
889 }
890 
892 {
893  gCoreContext->SaveSetting("MusicScannerLastRunStatus", status);
894 }
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)
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)
QMap< QString, MusicFileData > MusicLoadedMap
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: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.
#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
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)
void setEmbeddedAlbumArt(AlbumArtList &albumart)