00001
00002 #include <sys/stat.h>
00003
00004
00005 #include <qapplication.h>
00006 #include <qdir.h>
00007
00008
00009 #include <mythtv/mythcontext.h>
00010 #include <mythtv/mythdbcon.h>
00011 #include <mythtv/mythdialogs.h>
00012
00013
00014 #include "decoder.h"
00015 #include "maddecoder.h"
00016 #include "vorbisdecoder.h"
00017 #include "filescanner.h"
00018 #include "metadata.h"
00019
00020 FileScanner::FileScanner ()
00021 {
00022 MSqlQuery query(MSqlQuery::InitCon());
00023
00024
00025 query.prepare("SELECT directory_id, LOWER(path) FROM music_directories");
00026 if (query.exec() || query.isActive())
00027 {
00028 while(query.next())
00029 {
00030 m_directoryid[query.value(1).toString()] = query.value(0).toInt();
00031 }
00032 }
00033
00034
00035 query.prepare("SELECT genre_id, LOWER(genre) FROM music_genres");
00036 if (query.exec() || query.isActive())
00037 {
00038 while(query.next())
00039 {
00040 m_genreid[query.value(1).toString()] = query.value(0).toInt();
00041 }
00042 }
00043
00044
00045 query.prepare("SELECT artist_id, LOWER(artist_name) FROM music_artists");
00046 if (query.exec() || query.isActive())
00047 {
00048 while(query.next())
00049 {
00050 m_artistid[query.value(1).toString()] = query.value(0).toInt();
00051 }
00052 }
00053
00054
00055 query.prepare("SELECT album_id, artist_id, LOWER(album_name) FROM music_albums");
00056 if (query.exec() || query.isActive())
00057 {
00058 while(query.next())
00059 {
00060 m_albumid[query.value(1).toString() + "#" + query.value(2).toString()] = query.value(0).toInt();
00061 }
00062 }
00063 }
00064
00065 FileScanner::~FileScanner ()
00066 {
00067
00068 }
00069
00081 void FileScanner::BuildFileList(QString &directory, MusicLoadedMap &music_files, int parentid)
00082 {
00083 QDir d(directory);
00084
00085 if (!d.exists())
00086 return;
00087
00088 const QFileInfoList *list = d.entryInfoList();
00089 if (!list)
00090 return;
00091
00092 QFileInfoListIterator it(*list);
00093 QFileInfo *fi;
00094
00095
00096
00097 int update_interval = 0;
00098 int newparentid = 0;
00099 while ((fi = it.current()) != 0)
00100 {
00101 ++it;
00102 if (fi->fileName() == "." || fi->fileName() == "..")
00103 continue;
00104 QString filename = fi->absFilePath();
00105 if (fi->isDir())
00106 {
00107
00108 QString dir(filename);
00109 dir.remove(0, m_startdir.length());
00110
00111 newparentid = m_directoryid[dir.utf8().lower()];
00112
00113 if (newparentid == 0) {
00114 if ((m_directoryid[dir.utf8().lower()] = GetDirectoryId(dir, parentid)) > 0)
00115 {
00116 newparentid = m_directoryid[dir.utf8().lower()];
00117 }
00118 else
00119 {
00120 VERBOSE(VB_IMPORTANT, QString("Failed to get directory id for path %1").arg(dir).arg(m_directoryid[dir.utf8().lower()]));
00121 }
00122 }
00123
00124 BuildFileList(filename, music_files, newparentid);
00125
00126 qApp->processEvents ();
00127 }
00128 else
00129 {
00130 if (++update_interval > 100)
00131 {
00132 qApp->processEvents();
00133 update_interval = 0;
00134 }
00135
00136 music_files[filename] = kFileSystem;
00137 }
00138 }
00139 }
00140
00151 int FileScanner::GetDirectoryId(const QString &directory, const int &parentid)
00152 {
00153 if (directory.isEmpty())
00154 return 0;
00155
00156 MSqlQuery query(MSqlQuery::InitCon());
00157
00158
00159 query.prepare("SELECT directory_id FROM music_directories "
00160 "WHERE path = :DIRECTORY ;");
00161 query.bindValue(":DIRECTORY", directory.utf8());
00162
00163 if (query.exec() || query.isActive())
00164 {
00165 if (query.next())
00166 {
00167 return query.value(0).toInt();
00168 }
00169 else
00170 {
00171 query.prepare("INSERT INTO music_directories (path, parent_id) "
00172 "VALUES (:DIRECTORY, :PARENTID);");
00173 query.bindValue(":DIRECTORY", directory.utf8());
00174 query.bindValue(":PARENTID", parentid);
00175
00176 if (!query.exec() || !query.isActive()
00177 || query.numRowsAffected() <= 0)
00178 {
00179 MythContext::DBError("music insert directory", query);
00180 return -1;
00181 }
00182 return query.lastInsertId().toInt();
00183 }
00184 }
00185 else
00186 {
00187 MythContext::DBError("music select directory id", query);
00188 return -1;
00189 }
00190
00191 return -1;
00192 }
00193
00202 bool FileScanner::HasFileChanged(const QString &filename, const QString &date_modified)
00203 {
00204 struct stat stbuf;
00205
00206 if (stat(filename.local8Bit(), &stbuf) == 0)
00207 {
00208 if (date_modified.isEmpty() ||
00209 stbuf.st_mtime >
00210 (time_t)QDateTime::fromString(date_modified,
00211 Qt::ISODate).toTime_t())
00212 {
00213 return true;
00214 }
00215 }
00216 else {
00217 VERBOSE(VB_IMPORTANT, QString("Failed to stat file: %1")
00218 .arg(filename.local8Bit()));
00219 }
00220 return false;
00221 }
00222
00235 void FileScanner::AddFileToDB(const QString &filename)
00236 {
00237 QString extension = filename.section( '.', -1 ) ;
00238 QString directory = filename;
00239 directory.remove(0, m_startdir.length());
00240 directory = directory.section( '/', 0, -2);
00241
00242 QString nameFilter = gContext->GetSetting("AlbumArtFilter",
00243 "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
00244
00245
00246 if (nameFilter.find(extension.lower()) > -1)
00247 {
00248 QString name = filename.section( '/', -1);
00249
00250 MSqlQuery query(MSqlQuery::InitCon());
00251 query.prepare("INSERT INTO music_albumart SET filename = :FILE, "
00252 "directory_id = :DIRID, imagetype = :TYPE;");
00253 query.bindValue(":FILE", name.utf8());
00254 query.bindValue(":DIRID", m_directoryid[directory.utf8().lower()]);
00255 query.bindValue(":TYPE", AlbumArtImages::guessImageType(name));
00256
00257 if (!query.exec() || query.numRowsAffected() <= 0)
00258 {
00259 MythContext::DBError("music insert artwork", query);
00260 }
00261 return;
00262 }
00263
00264 Decoder *decoder = Decoder::create(filename, NULL, NULL, true);
00265
00266 if (decoder)
00267 {
00268 VERBOSE(VB_FILE, QString("Reading metadata from %1").arg(filename));
00269 Metadata *data = decoder->readMetadata();
00270 if (data) {
00271
00272 QString album_cache_string;
00273
00274
00275 if (m_directoryid[directory.utf8().lower()] > 0)
00276 data->setDirectoryId(m_directoryid[directory.utf8().lower()]);
00277 if (m_artistid[data->Artist().utf8().lower()] > 0)
00278 {
00279 data->setArtistId(m_artistid[data->Artist().utf8().lower()]);
00280
00281
00282 album_cache_string = data->getArtistId() + "#"
00283 + data->Album().utf8().lower();
00284
00285 if (m_albumid[album_cache_string] > 0)
00286 data->setAlbumId(m_albumid[album_cache_string]);
00287 }
00288 if (m_genreid[data->Genre().utf8().lower()] > 0)
00289 data->setGenreId(m_genreid[data->Genre().utf8().lower()]);
00290
00291
00292 data->dumpToDatabase();
00293
00294
00295 m_artistid[data->Artist().utf8().lower()] = data->getArtistId();
00296 m_genreid[data->Genre().utf8().lower()] = data->getGenreId();
00297 album_cache_string = data->getArtistId() + "#"
00298 + data->Album().utf8().lower();
00299 m_albumid[album_cache_string] = data->getAlbumId();
00300 delete data;
00301 }
00302
00303 delete decoder;
00304 }
00305 }
00306
00313 void FileScanner::cleanDB()
00314 {
00315 MythProgressDialog *clean_progress;
00316 clean_progress = new MythProgressDialog(
00317 QObject::tr("Cleaning music database"), 4);
00318
00319 int counter = 0;
00320
00321 MSqlQuery query(MSqlQuery::InitCon());
00322 MSqlQuery deletequery(MSqlQuery::InitCon());
00323
00324 query.exec("SELECT g.genre_id FROM music_genres g LEFT JOIN music_songs s "
00325 "ON g.genre_id=s.genre_id WHERE s.genre_id IS NULL;");
00326 while (query.next())
00327 {
00328 int genreid = query.value(0).toInt();
00329 deletequery.prepare("DELETE FROM music_genres WHERE genre_id=:GENREID");
00330 deletequery.bindValue(":GENREID", genreid);
00331 deletequery.exec();
00332 }
00333 clean_progress->setProgress(++counter);
00334
00335 query.exec("SELECT a.album_id FROM music_albums a LEFT JOIN music_songs s "
00336 "ON a.album_id=s.album_id WHERE s.album_id IS NULL;");
00337 while (query.next())
00338 {
00339 int albumid = query.value(0).toInt();
00340 deletequery.prepare("DELETE FROM music_albums WHERE album_id=:ALBUMID");
00341 deletequery.bindValue(":ALBUMID", albumid);
00342 deletequery.exec();
00343 }
00344 clean_progress->setProgress(++counter);
00345
00346 query.exec("SELECT a.artist_id FROM music_artists a "
00347 "LEFT JOIN music_songs s ON a.artist_id=s.artist_id "
00348 "LEFT JOIN music_albums l ON a.artist_id=l.artist_id "
00349 "WHERE s.artist_id IS NULL AND l.artist_id IS NULL");
00350 while (query.next())
00351 {
00352 int artistid = query.value(0).toInt();
00353 deletequery.prepare("DELETE FROM music_artists WHERE artist_id=:ARTISTID");
00354 deletequery.bindValue(":ARTISTID", artistid);
00355 deletequery.exec();
00356 }
00357 clean_progress->setProgress(++counter);
00358
00359 query.exec("SELECT a.albumart_id FROM music_albumart a LEFT JOIN "
00360 "music_songs s ON a.song_id=s.song_id WHERE "
00361 "embedded='1' AND s.song_id IS NULL;");
00362 while (query.next())
00363 {
00364 int albumartid = query.value(0).toInt();
00365 deletequery.prepare("DELETE FROM music_albumart WHERE albumart_id=:ALBUMARTID");
00366 deletequery.bindValue(":ALBUMARTID", albumartid);
00367 deletequery.exec();
00368 }
00369 clean_progress->setProgress(++counter);
00370
00371 clean_progress->Close();
00372 clean_progress->deleteLater();
00373 }
00374
00382 void FileScanner::RemoveFileFromDB (const QString &filename)
00383 {
00384 QString sqlfilename(filename);
00385 sqlfilename.remove(0, m_startdir.length());
00386
00387 QString directory = sqlfilename.section( '/', 0, -2 ) ;
00388 sqlfilename = sqlfilename.section( '/', -1 ) ;
00389
00390 QString extension = sqlfilename.section( '.', -1 ) ;
00391
00392 QString nameFilter = gContext->GetSetting("AlbumArtFilter",
00393 "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
00394
00395 if (nameFilter.find(extension) > -1)
00396 {
00397 MSqlQuery query(MSqlQuery::InitCon());
00398 query.prepare("DELETE FROM music_albumart WHERE filename= :FILE AND "
00399 "directory_id= :DIRID;");
00400 query.bindValue(":FILE", sqlfilename);
00401 query.bindValue(":DIRID", m_directoryid[directory.utf8().lower()]);
00402 if (!query.exec() || query.numRowsAffected() <= 0)
00403 {
00404 MythContext::DBError("music delete artwork", query);
00405 }
00406 return;
00407 }
00408
00409 MSqlQuery query(MSqlQuery::InitCon());
00410 query.prepare("DELETE FROM music_songs WHERE "
00411 "filename = :NAME ;");
00412 query.bindValue(":NAME", sqlfilename.utf8());
00413 query.exec();
00414 }
00415
00423 void FileScanner::UpdateFileInDB(const QString &filename)
00424 {
00425 QString directory = filename;
00426 directory.remove(0, m_startdir.length());
00427 directory = directory.section( '/', 0, -2);
00428
00429 Decoder *decoder = Decoder::create(filename, NULL, NULL, true);
00430
00431 if (decoder)
00432 {
00433 Metadata *db_meta = decoder->getMetadata();
00434 Metadata *disk_meta = decoder->readMetadata();
00435
00436 if (db_meta && disk_meta)
00437 {
00438 disk_meta->setID(db_meta->ID());
00439 disk_meta->setRating(db_meta->Rating());
00440
00441 QString album_cache_string;
00442
00443
00444 if (m_directoryid[directory.utf8().lower()] > 0)
00445 disk_meta->setDirectoryId(m_directoryid[directory
00446 .utf8().lower()]);
00447 if (m_artistid[disk_meta->Artist().utf8().lower()] > 0)
00448 {
00449 disk_meta->setArtistId(m_artistid[disk_meta->Artist()
00450 .utf8().lower()]);
00451
00452
00453 album_cache_string = disk_meta->getArtistId() + "#"
00454 + disk_meta->Album().utf8().lower();
00455
00456 if (m_albumid[album_cache_string] > 0)
00457 disk_meta->setAlbumId(m_albumid[album_cache_string]);
00458 }
00459 if (m_genreid[disk_meta->Genre().utf8().lower()] > 0)
00460 disk_meta->setGenreId(m_genreid[disk_meta->Genre()
00461 .utf8().lower()]);
00462
00463
00464 disk_meta->dumpToDatabase();
00465
00466
00467 m_artistid[disk_meta->Artist().utf8().lower()]
00468 = disk_meta->getArtistId();
00469 m_genreid[disk_meta->Genre().utf8().lower()]
00470 = disk_meta->getGenreId();
00471 album_cache_string = disk_meta->getArtistId() + "#"
00472 + disk_meta->Album().utf8().lower();
00473 m_albumid[album_cache_string] = disk_meta->getAlbumId();
00474 }
00475
00476 if (disk_meta)
00477 delete disk_meta;
00478
00479 if (db_meta)
00480 delete db_meta;
00481
00482 delete decoder;
00483 }
00484 }
00485
00495 void FileScanner::SearchDir(QString &directory)
00496 {
00497
00498 m_startdir = directory;
00499
00500 MusicLoadedMap music_files;
00501 MusicLoadedMap::Iterator iter;
00502
00503 MythBusyDialog *busy = new MythBusyDialog(
00504 QObject::tr("Searching for music files"));
00505
00506 busy->start();
00507 BuildFileList(m_startdir, music_files, 0);
00508 busy->Close();
00509 busy->deleteLater();
00510
00511 ScanMusic(music_files);
00512 ScanArtwork(music_files);
00513
00514 MythProgressDialog *file_checking = new MythProgressDialog(
00515 QObject::tr("Updating music database"), music_files.size());
00516
00517
00518
00519
00520
00521
00522
00523
00524
00525
00526
00527
00528
00529
00530 int counter = 0;
00531 for (iter = music_files.begin(); iter != music_files.end(); iter++)
00532 {
00533 if (*iter == kFileSystem)
00534 AddFileToDB(iter.key());
00535 else if (*iter == kDatabase)
00536 RemoveFileFromDB(iter.key ());
00537 else if (*iter == kNeedUpdate)
00538 UpdateFileInDB(iter.key());
00539
00540 file_checking->setProgress(++counter);
00541 }
00542 file_checking->Close();
00543 file_checking->deleteLater();
00544
00545
00546 cleanDB();
00547 }
00548
00556 void FileScanner::ScanMusic(MusicLoadedMap &music_files)
00557 {
00558 MusicLoadedMap::Iterator iter;
00559
00560 MSqlQuery query(MSqlQuery::InitCon());
00561 query.exec("SELECT CONCAT_WS('/', path, filename), date_modified "
00562 "FROM music_songs LEFT JOIN music_directories "
00563 "ON music_songs.directory_id=music_directories.directory_id "
00564 "WHERE filename NOT LIKE ('%://%')");
00565
00566 int counter = 0;
00567
00568 MythProgressDialog *file_checking;
00569 file_checking = new MythProgressDialog(
00570 QObject::tr("Scanning music files"), query.numRowsAffected());
00571
00572 QString name;
00573
00574 if (query.isActive() && query.size() > 0)
00575 {
00576 while (query.next())
00577 {
00578 name = m_startdir +
00579 QString::fromUtf8(query.value(0).toString());
00580
00581 if (name != QString::null)
00582 {
00583 if ((iter = music_files.find(name)) != music_files.end())
00584 {
00585 if (music_files[name] == kDatabase)
00586 {
00587 file_checking->setProgress(++counter);
00588 continue;
00589 }
00590 else if (HasFileChanged(name, query.value(1).toString()))
00591 music_files[name] = kNeedUpdate;
00592 else
00593 music_files.remove(iter);
00594 }
00595 else
00596 {
00597 music_files[name] = kDatabase;
00598 }
00599 }
00600 file_checking->setProgress(++counter);
00601 }
00602 }
00603
00604 file_checking->Close();
00605 file_checking->deleteLater();
00606 }
00607
00615 void FileScanner::ScanArtwork(MusicLoadedMap &music_files)
00616 {
00617 MusicLoadedMap::Iterator iter;
00618
00619 MSqlQuery query(MSqlQuery::InitCon());
00620 query.exec("SELECT CONCAT_WS('/', path, filename) "
00621 "FROM music_albumart LEFT JOIN music_directories "
00622 "ON music_albumart.directory_id=music_directories.directory_id "
00623 "WHERE music_albumart.embedded=0");
00624
00625 int counter = 0;
00626
00627 MythProgressDialog *file_checking;
00628 file_checking = new MythProgressDialog(
00629 QObject::tr("Scanning Album Artwork"), query.numRowsAffected());
00630
00631 if (query.isActive() && query.size() > 0)
00632 {
00633 while (query.next())
00634 {
00635 QString name;
00636
00637 name = m_startdir +
00638 QString::fromUtf8(query.value(0).toString());
00639
00640 if (name != QString::null)
00641 {
00642 if ((iter = music_files.find(name)) != music_files.end())
00643 {
00644 if (music_files[name] == kDatabase)
00645 {
00646 file_checking->setProgress(++counter);
00647 continue;
00648 }
00649 else
00650 music_files.remove(iter);
00651 }
00652 else
00653 {
00654 music_files[name] = kDatabase;
00655 }
00656 }
00657 file_checking->setProgress(++counter);
00658 }
00659 }
00660
00661 file_checking->Close();
00662 file_checking->deleteLater();
00663 }