![]() |
Mixxx
|
00001 #include <QtDebug> 00002 #include <QtCore> 00003 #include <QtSql> 00004 00005 #include "library/dao/trackdao.h" 00006 00007 #include "audiotagger.h" 00008 #include "library/queryutil.h" 00009 #include "soundsourceproxy.h" 00010 #include "track/beatfactory.h" 00011 #include "track/beats.h" 00012 #include "trackinfoobject.h" 00013 00014 // The number of tracks to cache in memory at once. Once the n+1'th track is 00015 // created, the TrackDAO's QCache deletes its TrackPointer to the track, which 00016 // allows the track reference count to drop to zero. The track cache basically 00017 // functions to hold a reference to the track so its reference count stays above 00018 // 0. 00019 #define TRACK_CACHE_SIZE 5 00020 00021 TrackDAO::TrackDAO(QSqlDatabase& database, 00022 CueDAO& cueDao, 00023 PlaylistDAO& playlistDao, 00024 CrateDAO& crateDao, 00025 ConfigObject<ConfigValue> * pConfig) 00026 : m_database(database), 00027 m_cueDao(cueDao), 00028 m_playlistDao(playlistDao), 00029 m_crateDao(crateDao), 00030 m_trackCache(TRACK_CACHE_SIZE), 00031 m_pConfig(pConfig) { 00032 } 00033 00034 void TrackDAO::finish() { 00035 //clear out played information on exit 00036 //crash prevention: if mixxx crashes, played information will be maintained 00037 qDebug() << "Clearing played information for this session"; 00038 QSqlQuery query(m_database); 00039 if (!query.exec("UPDATE library SET played=0")) { 00040 LOG_FAILED_QUERY(query) 00041 << "Error clearing played value"; 00042 } 00043 } 00044 00045 TrackDAO::~TrackDAO() { 00046 qDebug() << "~TrackDAO()"; 00047 } 00048 00049 void TrackDAO::initialize() { 00050 qDebug() << "TrackDAO::initialize" << QThread::currentThread() << m_database.connectionName(); 00051 } 00052 00057 int TrackDAO::getTrackId(QString absoluteFilePath) { 00058 //qDebug() << "TrackDAO::getTrackId" << QThread::currentThread() << m_database.connectionName(); 00059 00060 QSqlQuery query(m_database); 00061 query.prepare("SELECT library.id FROM library INNER JOIN track_locations ON library.location = track_locations.id WHERE track_locations.location=:location"); 00062 query.bindValue(":location", absoluteFilePath); 00063 00064 if (!query.exec()) { 00065 LOG_FAILED_QUERY(query); 00066 return -1; 00067 } 00068 00069 int libraryTrackId = -1; 00070 if (query.next()) { 00071 libraryTrackId = query.value(query.record().indexOf("id")).toInt(); 00072 } 00073 00074 return libraryTrackId; 00075 } 00076 00079 QString TrackDAO::getTrackLocation(int trackId) { 00080 qDebug() << "TrackDAO::getTrackLocation" 00081 << QThread::currentThread() << m_database.connectionName(); 00082 QSqlQuery query(m_database); 00083 QString trackLocation = ""; 00084 query.prepare("SELECT track_locations.location FROM track_locations INNER JOIN library ON library.location = track_locations.id WHERE library.id=:id"); 00085 query.bindValue(":id", trackId); 00086 if (!query.exec()) { 00087 LOG_FAILED_QUERY(query); 00088 return ""; 00089 } 00090 while (query.next()) { 00091 trackLocation = query.value(query.record().indexOf("location")).toString(); 00092 } 00093 00094 return trackLocation; 00095 } 00096 00101 bool TrackDAO::trackExistsInDatabase(QString absoluteFilePath) { 00102 return (getTrackId(absoluteFilePath) != -1); 00103 } 00104 00105 void TrackDAO::saveTrack(TrackPointer track) { 00106 if (track) { 00107 saveTrack(track.data()); 00108 } 00109 } 00110 00111 void TrackDAO::saveTrack(TrackInfoObject* pTrack) { 00112 if (!pTrack) { 00113 qWarning() << "TrackDAO::saveTrack() was given NULL track."; 00114 } 00115 //qDebug() << "TrackDAO::saveTrack" << pTrack->getId() << pTrack->getInfo(); 00116 // If track's id is not -1, then update, otherwise add. 00117 int trackId = pTrack->getId(); 00118 if (trackId != -1) { 00119 if (pTrack->isDirty()) { 00120 if (!m_dirtyTracks.contains(trackId)) { 00121 qDebug() << "WARNING: Inconsistent state in TrackDAO. Track is dirty while TrackDAO thinks it is clean."; 00122 } 00123 00124 //qDebug() << this << "Dirty tracks before claen save:" << m_dirtyTracks.size(); 00125 //qDebug() << "TrackDAO::saveTrack. Dirty. Calling update"; 00126 updateTrack(pTrack); 00127 00128 // Write audio meta data, if enabled in the preferences 00129 writeAudioMetaData(pTrack); 00130 00131 //qDebug() << this << "Dirty tracks remaining after clean save:" << m_dirtyTracks.size(); 00132 } else { 00133 //qDebug() << "TrackDAO::saveTrack. Not Dirty"; 00134 //qDebug() << this << "Dirty tracks remaining:" << m_dirtyTracks.size(); 00135 00136 // Q_ASSERT(!m_dirtyTracks.contains(trackId)); 00137 if (m_dirtyTracks.contains(trackId)) { 00138 qDebug() << "WARNING: Inconsistent state in TrackDAO. Track is clean while TrackDAO thinks it is dirty. Correcting."; 00139 m_dirtyTracks.remove(trackId); 00140 } 00141 00142 //qDebug() << "Skipping track update for track" << pTrack->getId(); 00143 } 00144 } else { 00145 addTrack(pTrack, false); 00146 } 00147 } 00148 00149 bool TrackDAO::isDirty(int trackId) { 00150 return m_dirtyTracks.contains(trackId); 00151 } 00152 00153 void TrackDAO::slotTrackDirty(TrackInfoObject* pTrack) { 00154 //qDebug() << "TrackDAO::slotTrackDirty" << pTrack->getInfo(); 00155 // This is a private slot that is connected to TIO's created by this 00156 // TrackDAO. It is a way for the track to ask that it be saved. The only 00157 // time this could be unsafe is when the TIO's reference count drops to 00158 // 0. When that happens, the TIO is deleted with QObject:deleteLater, so Qt 00159 // will wait for this slot to comlete. 00160 if (pTrack) { 00161 int id = pTrack->getId(); 00162 if (id != -1) { 00163 m_dirtyTracks.insert(id); 00164 emit(trackDirty(id)); 00165 } 00166 } 00167 } 00168 00169 void TrackDAO::slotTrackClean(TrackInfoObject* pTrack) { 00170 //qDebug() << "TrackDAO::slotTrackClean" << pTrack->getInfo(); 00171 // This is a private slot that is connected to TIO's created by this 00172 // TrackDAO. It is a way for the track to ask that it be saved. The only 00173 // time this could be unsafe is when the TIO's reference count drops to 00174 // 0. When that happens, the TIO is deleted with QObject:deleteLater, so Qt 00175 // will wait for this slot to comlete. 00176 00177 if (pTrack) { 00178 int id = pTrack->getId(); 00179 if (id != -1) { 00180 m_dirtyTracks.remove(id); 00181 emit(trackClean(id)); 00182 } 00183 } 00184 } 00185 00186 void TrackDAO::slotTrackChanged(TrackInfoObject* pTrack) { 00187 //qDebug() << "TrackDAO::slotTrackChanged" << pTrack->getInfo(); 00188 // This is a private slot that is connected to TIO's created by this 00189 // TrackDAO. It is a way for the track to ask that it be saved. The only 00190 // time this could be unsafe is when the TIO's reference count drops to 00191 // 0. When that happens, the TIO is deleted with QObject:deleteLater, so Qt 00192 // will wait for this slot to comlete. 00193 if (pTrack) { 00194 int id = pTrack->getId(); 00195 if (id != -1) { 00196 emit(trackChanged(id)); 00197 } 00198 } 00199 } 00200 00201 void TrackDAO::slotTrackSave(TrackInfoObject* pTrack) { 00202 //qDebug() << "TrackDAO::slotTrackSave" << pTrack->getId() << pTrack->getInfo(); 00203 // This is a private slot that is connected to TIO's created by this 00204 // TrackDAO. It is a way for the track to ask that it be saved. The last 00205 // time it is used is when the track is being deleted (i.e. its reference 00206 // count has dropped to 0). The TIO is deleted with QObject:deleteLater, so 00207 // Qt will wait for this slot to comlete. 00208 if (pTrack) { 00209 saveTrack(pTrack); 00210 } 00211 } 00212 00213 void TrackDAO::saveDirtyTracks() { 00214 qDebug() << "TrackDAO::saveDirtyTracks()"; 00215 QHashIterator<int, TrackWeakPointer> it(m_tracks); 00216 while (it.hasNext()) { 00217 it.next(); 00218 // Auto-cast from TrackWeakPointer to TrackPointer 00219 TrackPointer pTrack = it.value(); 00220 if (pTrack && pTrack->isDirty()) { 00221 saveTrack(pTrack); 00222 } 00223 } 00224 clearCache(); 00225 } 00226 00227 void TrackDAO::prepareTrackLocationsInsert(QSqlQuery& query) { 00228 query.prepare("INSERT INTO track_locations (location, directory, filename, filesize, fs_deleted, needs_verification) " 00229 "VALUES (:location, :directory, :filename, :filesize, :fs_deleted, :needs_verification)"); 00230 } 00231 00232 void TrackDAO::bindTrackToTrackLocationsInsert(QSqlQuery& query, TrackInfoObject* pTrack) { 00233 query.bindValue(":location", pTrack->getLocation()); 00234 query.bindValue(":directory", pTrack->getDirectory()); 00235 query.bindValue(":filename", pTrack->getFilename()); 00236 query.bindValue(":filesize", pTrack->getLength()); 00237 // Should this check pTrack->exists()? 00238 query.bindValue(":fs_deleted", 0); 00239 query.bindValue(":needs_verification", 0); 00240 } 00241 00242 void TrackDAO::prepareLibraryInsert(QSqlQuery& query) { 00243 query.prepare("INSERT INTO library (artist, title, album, year, genre, tracknumber, " 00244 "filetype, location, comment, url, duration, rating, key, " 00245 "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, " 00246 "timesplayed, " 00247 "channels, mixxx_deleted, header_parsed, beats_version, beats) " 00248 "VALUES (:artist, " 00249 ":title, :album, :year, :genre, :tracknumber, " 00250 ":filetype, :location, :comment, :url, :duration, :rating, :key, " 00251 ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, " 00252 ":timesplayed, " 00253 ":channels, :mixxx_deleted, :header_parsed, :beats_version, :beats)"); 00254 } 00255 00256 void TrackDAO::bindTrackToLibraryInsert( 00257 QSqlQuery& query, TrackInfoObject* pTrack, int trackLocationId) { 00258 query.bindValue(":artist", pTrack->getArtist()); 00259 query.bindValue(":title", pTrack->getTitle()); 00260 query.bindValue(":album", pTrack->getAlbum()); 00261 query.bindValue(":year", pTrack->getYear()); 00262 query.bindValue(":genre", pTrack->getGenre()); 00263 query.bindValue(":tracknumber", pTrack->getTrackNumber()); 00264 query.bindValue(":filetype", pTrack->getType()); 00265 query.bindValue(":location", trackLocationId); 00266 query.bindValue(":comment", pTrack->getComment()); 00267 query.bindValue(":url", pTrack->getURL()); 00268 query.bindValue(":duration", pTrack->getDuration()); 00269 query.bindValue(":rating", pTrack->getRating()); 00270 query.bindValue(":bitrate", pTrack->getBitrate()); 00271 query.bindValue(":samplerate", pTrack->getSampleRate()); 00272 query.bindValue(":cuepoint", pTrack->getCuePoint()); 00273 00274 query.bindValue(":replaygain", pTrack->getReplayGain()); 00275 query.bindValue(":key", pTrack->getKey()); 00276 const QByteArray* pWaveSummary = pTrack->getWaveSummary(); 00277 query.bindValue(":wavesummaryhex", pWaveSummary ? *pWaveSummary : QVariant(QVariant::ByteArray)); 00278 query.bindValue(":timesplayed", pTrack->getTimesPlayed()); 00279 //query.bindValue(":datetime_added", pTrack->getDateAdded()); 00280 query.bindValue(":channels", pTrack->getChannels()); 00281 query.bindValue(":mixxx_deleted", 0); 00282 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0); 00283 00284 const QByteArray* pBeatsBlob = NULL; 00285 QString blobVersion = ""; 00286 BeatsPointer pBeats = pTrack->getBeats(); 00287 // Fall back on cached BPM 00288 double dBpm = pTrack->getBpm(); 00289 00290 if (pBeats) { 00291 pBeatsBlob = pBeats->toByteArray(); 00292 blobVersion = pBeats->getVersion(); 00293 dBpm = pBeats->getBpm(); 00294 } 00295 00296 query.bindValue(":bpm", dBpm); 00297 query.bindValue(":beats_version", blobVersion); 00298 query.bindValue(":beats", pBeatsBlob ? *pBeatsBlob : QVariant(QVariant::ByteArray)); 00299 delete pBeatsBlob; 00300 } 00301 00302 void TrackDAO::addTracks(QList<TrackInfoObject*> tracksToAdd, bool unremove) { 00303 QSet<int> tracksAddedSet; 00304 QTime time; 00305 time.start(); 00306 00307 // Start the transaction 00308 m_database.transaction(); 00309 00310 QSqlQuery query(m_database); 00311 QSqlQuery query_finder(m_database); 00312 query_finder.prepare("SELECT id FROM track_locations WHERE location=:location"); 00313 00314 // Major time saver for having this outside the loop 00315 prepareTrackLocationsInsert(query); 00316 00317 QStringList trackLocationIds; 00318 foreach (TrackInfoObject* pTrack, tracksToAdd) { 00319 if (pTrack == NULL || !isTrackFormatSupported(pTrack)) { 00320 // TODO(XXX) provide some kind of error code on a per-track basis. 00321 continue; 00322 } 00323 bindTrackToTrackLocationsInsert(query, pTrack); 00324 00325 int trackLocationId = -1; 00326 if (!query.exec()) { 00327 qDebug() << "Location " << pTrack->getLocation() << " is already in the DB"; 00328 query_finder.bindValue(":location", pTrack->getLocation()); 00329 00330 if (!query_finder.exec()) { 00331 // We can't even select this, something is wrong. Skip this 00332 // track -- maybe we'll have luck with others. 00333 LOG_FAILED_QUERY(query_finder) 00334 << "Can't find track location ID after failing to insert. Something is wrong."; 00335 continue; 00336 } 00337 while (query_finder.next()) { 00338 trackLocationId = query_finder.value(query_finder.record().indexOf("id")).toInt(); 00339 } 00340 } else { 00341 // Inserting succeeded, so just get the last rowid. 00342 QVariant lastInsert = query.lastInsertId(); 00343 trackLocationId = lastInsert.toInt(); 00344 } 00345 00346 // To save time on future queries, setId the trackLocationId on the 00347 // track. This takes advantage of the fact that I know the 00348 // LibraryScanner doesn't use these tracks for anything. rryan 9/2010 00349 pTrack->setId(trackLocationId); 00350 trackLocationIds.append(QString::number(trackLocationId)); 00351 } 00352 00353 // Look up pre-existing library records for the track location ids. 00354 QSqlQuery track_lookup(m_database); 00355 // Mapping of track library record id to mixxx_deleted field. 00356 QHash<int, QPair<int, bool> > tracksPresent; 00357 track_lookup.prepare("SELECT location, id, mixxx_deleted from library WHERE location IN (" + 00358 trackLocationIds.join(",")+ ")"); 00359 if (!track_lookup.exec()) { 00360 LOG_FAILED_QUERY(track_lookup) 00361 << "Failed to lookup existing tracks:"; 00362 } else { 00363 QSqlRecord track_lookup_record = track_lookup.record(); 00364 int locationIdColumn = track_lookup_record.indexOf("location"); 00365 int idColumn = track_lookup_record.indexOf("id"); 00366 int mixxxDeletedColumn = track_lookup_record.indexOf("mixxx_deleted"); 00367 while (track_lookup.next()) { 00368 int locationId = track_lookup.value(locationIdColumn).toInt(); 00369 int trackId = track_lookup.value(idColumn).toInt(); 00370 bool removed = track_lookup.value(mixxxDeletedColumn).toBool(); 00371 tracksPresent[locationId] = QPair<int, bool>(trackId, removed); 00372 } 00373 } 00374 00375 // Major time saver for having this outside the loop 00376 prepareLibraryInsert(query); 00377 00378 foreach (TrackInfoObject* pTrack, tracksToAdd) { 00379 // Skip tracks that did not make it past the previous part. 00380 if (pTrack == NULL || pTrack->getId() < 0) { 00381 continue; 00382 } 00383 00384 // Immediately undo the hack we did above so we do not accidentally 00385 // leave the ID incorrectly set. 00386 int locationId = pTrack->getId(); 00387 pTrack->setId(-1); 00388 00389 // Skip tracks that are already in the database. Optionally unremove 00390 // them. 00391 QHash<int, QPair<int, bool> >::const_iterator it = tracksPresent.find(locationId); 00392 if (it != tracksPresent.end()) { 00393 int trackId = it.value().first; 00394 bool removed = it.value().second; 00395 if (removed && unremove) { 00396 QSqlQuery unremove_query(m_database); 00397 unremove_query.prepare("UPDATE library SET mixxx_deleted = 0 WHERE id = :id"); 00398 unremove_query.bindValue(":id", trackId); 00399 if (!unremove_query.exec()) { 00400 LOG_FAILED_QUERY(unremove_query) 00401 << "Could not unremove track" << trackId; 00402 } else { 00403 tracksAddedSet.insert(trackId); 00404 } 00405 } 00406 00407 // Regardless of whether we unremoved this track or not -- it's 00408 // already in the library and so we need to skip it. Set the track's 00409 // trackId so the caller can know it. TODO(XXX) this is a little 00410 // weird because the track has whatever metadata the caller supplied 00411 // and that metadata may differ from what is already in the 00412 // database. I'm ignoring this corner case. rryan 10/2011 00413 pTrack->setId(trackId); 00414 continue; 00415 } 00416 00417 bindTrackToLibraryInsert(query, pTrack, locationId); 00418 00419 if (!query.exec()) { 00420 // We failed to insert the track. Maybe it is already in the library 00421 // but marked deleted? Skip this track. 00422 LOG_FAILED_QUERY(query) 00423 << "Failed to INSERT new track into library:" 00424 << pTrack->getFilename(); 00425 continue; 00426 } 00427 int trackId = query.lastInsertId().toInt(); 00428 pTrack->setId(trackId); 00429 m_cueDao.saveTrackCues(trackId, pTrack); 00430 pTrack->setDirty(false); 00431 tracksAddedSet.insert(trackId); 00432 } 00433 00434 m_database.commit(); 00435 00436 qDebug() << this << "addTracks took" << time.elapsed() << "ms to add" 00437 << tracksAddedSet.size() << "tracks"; 00438 if (tracksAddedSet.size() > 0) { 00439 emit(tracksAdded(tracksAddedSet)); 00440 } 00441 } 00442 00443 int TrackDAO::addTrack(QFileInfo& fileInfo, bool unremove) { 00444 int trackId = -1; 00445 TrackInfoObject * pTrack = new TrackInfoObject(fileInfo); 00446 if (pTrack) { 00447 // Add the song to the database. 00448 addTrack(pTrack, unremove); 00449 trackId = pTrack->getId(); 00450 delete pTrack; 00451 } 00452 return trackId; 00453 } 00454 00455 int TrackDAO::addTrack(QString absoluteFilePath, bool unremove) 00456 { 00457 QFileInfo fileInfo(absoluteFilePath); 00458 return addTrack(fileInfo, unremove); 00459 } 00460 00461 void TrackDAO::addTrack(TrackInfoObject* pTrack, bool unremove) { 00462 QList<TrackInfoObject*> tracksToAdd; 00463 tracksToAdd.push_back(pTrack); 00464 addTracks(tracksToAdd, unremove); 00465 } 00466 00468 void TrackDAO::removeTrack(int id) { 00469 //qDebug() << "TrackDAO::removeTrack" << QThread::currentThread() << m_database.connectionName(); 00470 Q_ASSERT(id >= 0); 00471 QSqlQuery query(m_database); 00472 00473 // Remove track from crates and playlists. 00474 m_playlistDao.removeTrackFromPlaylists(id); 00475 m_crateDao.removeTrackFromCrates(id); 00476 00477 //Mark the track as deleted! 00478 query.prepare("UPDATE library " 00479 "SET mixxx_deleted=1 " 00480 "WHERE id = " + QString::number(id)); 00481 if (!query.exec()) { 00482 LOG_FAILED_QUERY(query); 00483 } 00484 00485 QSet<int> tracksRemovedSet; 00486 tracksRemovedSet.insert(id); 00487 emit(tracksRemoved(tracksRemovedSet)); 00488 } 00489 00490 void TrackDAO::removeTracks(QList<int> ids) { 00491 QString idList = ""; 00492 00493 foreach (int id, ids) { 00494 idList.append(QString("%1,").arg(id)); 00495 } 00496 00497 // Strip the last , 00498 if (idList.count() > 0) { 00499 idList.truncate(idList.count() - 1); 00500 } 00501 00502 QSqlQuery query(m_database); 00503 query.prepare(QString("UPDATE library SET mixxx_deleted=1 WHERE id in (%1)").arg(idList)); 00504 if (!query.exec()) { 00505 LOG_FAILED_QUERY(query); 00506 } 00507 00508 QSet<int> tracksRemovedSet = QSet<int>::fromList(ids); 00509 emit(tracksRemoved(tracksRemovedSet)); 00510 } 00511 00512 /*** If a track has been manually "removed" from Mixxx's library by the user via 00513 Mixxx's interface, this lets you add it back. When a track is removed, 00514 mixxx_deleted in the DB gets set to 1. This clears that, and makes it show 00515 up in the library views again. 00516 This function should get called if you drag-and-drop a file that's been 00517 "removed" from Mixxx back into the library view. 00518 */ 00519 void TrackDAO::unremoveTrack(int trackId) { 00520 Q_ASSERT(trackId >= 0); 00521 QSqlQuery query(m_database); 00522 query.prepare("UPDATE library " 00523 "SET mixxx_deleted=0 " 00524 "WHERE id = " + QString("%1").arg(trackId)); 00525 if (!query.exec()) { 00526 LOG_FAILED_QUERY(query) 00527 << "Failed to set track" << trackId << "as undeleted"; 00528 } 00529 QSet<int> tracksAddedSet; 00530 tracksAddedSet.insert(trackId); 00531 emit(tracksAdded(tracksAddedSet)); 00532 } 00533 00534 // static 00535 void TrackDAO::deleteTrack(TrackInfoObject* pTrack) { 00536 Q_ASSERT(pTrack); 00537 //qDebug() << "Garbage Collecting" << pTrack << "ID" << pTrack->getId() << pTrack->getInfo(); 00538 00539 // Save the track if it is dirty. 00540 pTrack->doSave(); 00541 00542 // Now Qt will delete it in the event loop. 00543 pTrack->deleteLater(); 00544 } 00545 00546 TrackPointer TrackDAO::getTrackFromDB(QSqlQuery &query) const { 00547 //Print out any SQL error, if there was one. 00548 if (query.lastError().isValid()) { 00549 LOG_FAILED_QUERY(query); 00550 } 00551 00552 while (query.next()) { 00553 // Good god! Assign query.record() to a freaking variable! 00554 int trackId = query.value(query.record().indexOf("id")).toInt(); 00555 QString artist = query.value(query.record().indexOf("artist")).toString(); 00556 QString title = query.value(query.record().indexOf("title")).toString(); 00557 QString album = query.value(query.record().indexOf("album")).toString(); 00558 QString year = query.value(query.record().indexOf("year")).toString(); 00559 QString genre = query.value(query.record().indexOf("genre")).toString(); 00560 QString tracknumber = query.value(query.record().indexOf("tracknumber")).toString(); 00561 QString comment = query.value(query.record().indexOf("comment")).toString(); 00562 QString url = query.value(query.record().indexOf("url")).toString(); 00563 QString key = query.value(query.record().indexOf("key")).toString(); 00564 int duration = query.value(query.record().indexOf("duration")).toInt(); 00565 int bitrate = query.value(query.record().indexOf("bitrate")).toInt(); 00566 int rating = query.value(query.record().indexOf("rating")).toInt(); 00567 int samplerate = query.value(query.record().indexOf("samplerate")).toInt(); 00568 int cuepoint = query.value(query.record().indexOf("cuepoint")).toInt(); 00569 QString bpm = query.value(query.record().indexOf("bpm")).toString(); 00570 QString replaygain = query.value(query.record().indexOf("replaygain")).toString(); 00571 QByteArray* wavesummaryhex = new QByteArray( 00572 query.value(query.record().indexOf("wavesummaryhex")).toByteArray()); 00573 int timesplayed = query.value(query.record().indexOf("timesplayed")).toInt(); 00574 int played = query.value(query.record().indexOf("played")).toInt(); 00575 int channels = query.value(query.record().indexOf("channels")).toInt(); 00576 //int filesize = query.value(query.record().indexOf("filesize")).toInt(); 00577 QString filetype = query.value(query.record().indexOf("filetype")).toString(); 00578 QString location = query.value(query.record().indexOf("location")).toString(); 00579 bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool(); 00580 QDateTime date_created = query.value(query.record().indexOf("datetime_added")).toDateTime(); 00581 00582 TrackPointer pTrack = TrackPointer(new TrackInfoObject(location, false), &TrackDAO::deleteTrack); 00583 00584 // TIO already stats the file to see if it exists, what its length is, 00585 // etc. So don't bother setting it. 00586 //track->setLength(filesize); 00587 00588 pTrack->setId(trackId); 00589 pTrack->setArtist(artist); 00590 pTrack->setTitle(title); 00591 pTrack->setAlbum(album); 00592 pTrack->setYear(year); 00593 pTrack->setGenre(genre); 00594 pTrack->setTrackNumber(tracknumber); 00595 pTrack->setRating(rating); 00596 pTrack->setKey(key); 00597 00598 pTrack->setComment(comment); 00599 pTrack->setURL(url); 00600 pTrack->setDuration(duration); 00601 pTrack->setBitrate(bitrate); 00602 pTrack->setSampleRate(samplerate); 00603 pTrack->setCuePoint((float)cuepoint); 00604 pTrack->setBpm(bpm.toFloat()); 00605 pTrack->setReplayGain(replaygain.toFloat()); 00606 pTrack->setWaveSummary(wavesummaryhex, false); 00607 delete wavesummaryhex; 00608 00609 QString beatsVersion = query.value(query.record().indexOf("beats_version")).toString(); 00610 QByteArray beatsBlob = query.value(query.record().indexOf("beats")).toByteArray(); 00611 BeatsPointer pBeats = BeatFactory::loadBeatsFromByteArray(pTrack, beatsVersion, &beatsBlob); 00612 if (pBeats) { 00613 pTrack->setBeats(pBeats); 00614 } 00615 00616 pTrack->setTimesPlayed(timesplayed); 00617 pTrack->setPlayed(played); 00618 pTrack->setChannels(channels); 00619 pTrack->setType(filetype); 00620 pTrack->setLocation(location); 00621 pTrack->setHeaderParsed(header_parsed); 00622 pTrack->setDateAdded(date_created); 00623 00624 pTrack->setCuePoints(m_cueDao.getCuesForTrack(trackId)); 00625 pTrack->setDirty(false); 00626 00627 // Listen to dirty and changed signals 00628 connect(pTrack.data(), SIGNAL(dirty(TrackInfoObject*)), 00629 this, SLOT(slotTrackDirty(TrackInfoObject*)), 00630 Qt::DirectConnection); 00631 connect(pTrack.data(), SIGNAL(clean(TrackInfoObject*)), 00632 this, SLOT(slotTrackClean(TrackInfoObject*)), 00633 Qt::DirectConnection); 00634 connect(pTrack.data(), SIGNAL(changed(TrackInfoObject*)), 00635 this, SLOT(slotTrackChanged(TrackInfoObject*)), 00636 Qt::DirectConnection); 00637 connect(pTrack.data(), SIGNAL(save(TrackInfoObject*)), 00638 this, SLOT(slotTrackSave(TrackInfoObject*)), 00639 Qt::DirectConnection); 00640 00641 // Automatic conversion to a weak pointer 00642 m_tracks[trackId] = pTrack; 00643 m_trackCache.insert(trackId, new TrackPointer(pTrack)); 00644 00645 // If the header hasn't been parsed, parse it but only after we set the 00646 // track clean and hooked it up to the track cache, because this will 00647 // dirty it. 00648 if (!header_parsed) { 00649 pTrack->parse(); 00650 } 00651 00652 if (!pBeats && pTrack->getBpm() != 0.0f) { 00653 // The track has no stored beats but has a previously detected BPM 00654 // or a BPM loaded from metadata. Automatically create a beatgrid 00655 // for the track. This dirties the track so we have to do it after 00656 // all the signal connections, etc. are in place. 00657 BeatsPointer pBeats = BeatFactory::makeBeatGrid(pTrack, pTrack->getBpm(), 0.0f); 00658 pTrack->setBeats(pBeats); 00659 } 00660 00661 return pTrack; 00662 } 00663 00664 return TrackPointer(); 00665 } 00666 00667 TrackPointer TrackDAO::getTrack(int id, bool cacheOnly) const { 00668 //qDebug() << "TrackDAO::getTrack" << QThread::currentThread() << m_database.connectionName(); 00669 00670 // If the track cache contains the track, use it to get a strong reference 00671 // to the track. We do this first so that the QCache keeps track of the 00672 // least-recently-used track so that it expires them intelligently. 00673 if (m_trackCache.contains(id)) { 00674 TrackPointer pTrack = *m_trackCache[id]; 00675 00676 // If the strong reference is still valid (it should be), then return 00677 // it. Otherwise query the DB for the track. 00678 if (pTrack) 00679 return pTrack; 00680 } 00681 00682 // Next, check the weak-reference cache to see if the track was ever loaded 00683 // into memory. It's possible that something is currently using this track, 00684 // so its reference count is non-zero despite it not being present in the 00685 // track cache. m_tracks is a map of weak pointers to the tracks. 00686 if (m_tracks.contains(id)) { 00687 //qDebug() << "Returning cached TIO for track" << id; 00688 TrackPointer pTrack = m_tracks[id]; 00689 00690 // If the pointer to the cached copy is still valid, return 00691 // it. Otherwise, re-query the DB for the track. 00692 if (pTrack) 00693 return pTrack; 00694 } 00695 00696 // The person only wanted the track if it was cached. 00697 if (cacheOnly) { 00698 //qDebug() << "TrackDAO::getTrack()" << id << "Caller wanted track but only if it was cached. Returning null."; 00699 return TrackPointer(); 00700 } 00701 00702 QTime time; 00703 time.start(); 00704 QSqlQuery query(m_database); 00705 00706 query.prepare( 00707 "SELECT library.id, artist, title, album, year, genre, tracknumber, " 00708 "filetype, rating, key, track_locations.location as location, " 00709 "track_locations.filesize as filesize, comment, url, duration, bitrate, " 00710 "samplerate, cuepoint, bpm, replaygain, wavesummaryhex, channels, " 00711 "header_parsed, timesplayed, played, beats_version, beats, datetime_added " 00712 "FROM Library " 00713 "INNER JOIN track_locations " 00714 "ON library.location = track_locations.id " 00715 "WHERE library.id=" + QString("%1").arg(id) 00716 ); 00717 00718 TrackPointer pTrack; 00719 00720 if (query.exec()) { 00721 pTrack = getTrackFromDB(query); 00722 } else { 00723 LOG_FAILED_QUERY(query) 00724 << QString("getTrack(%1)").arg(id); 00725 } 00726 //qDebug() << "getTrack hit the database, took " << time.elapsed() << "ms"; 00727 00728 return pTrack; 00729 } 00730 00732 void TrackDAO::updateTrack(TrackInfoObject* pTrack) { 00733 m_database.transaction(); 00734 QTime time; 00735 time.start(); 00736 Q_ASSERT(pTrack); 00737 //qDebug() << "TrackDAO::updateTrackInDatabase" << QThread::currentThread() << m_database.connectionName(); 00738 00739 //qDebug() << "Updating track" << pTrack->getInfo() << "in database..."; 00740 00741 int trackId = pTrack->getId(); 00742 Q_ASSERT(trackId >= 0); 00743 00744 QSqlQuery query(m_database); 00745 00746 //Update everything but "location", since that's what we identify the track by. 00747 query.prepare("UPDATE library " 00748 "SET artist=:artist, " 00749 "title=:title, album=:album, year=:year, genre=:genre, " 00750 "filetype=:filetype, tracknumber=:tracknumber, " 00751 "comment=:comment, url=:url, duration=:duration, rating=:rating, key=:key, " 00752 "bitrate=:bitrate, samplerate=:samplerate, cuepoint=:cuepoint, " 00753 "bpm=:bpm, replaygain=:replaygain, wavesummaryhex=:wavesummaryhex, " 00754 "timesplayed=:timesplayed, played=:played, " 00755 "channels=:channels, header_parsed=:header_parsed, " 00756 "beats_version=:beats_version, beats=:beats " 00757 "WHERE id="+QString("%1").arg(trackId)); 00758 query.bindValue(":artist", pTrack->getArtist()); 00759 query.bindValue(":title", pTrack->getTitle()); 00760 query.bindValue(":album", pTrack->getAlbum()); 00761 query.bindValue(":year", pTrack->getYear()); 00762 query.bindValue(":genre", pTrack->getGenre()); 00763 query.bindValue(":filetype", pTrack->getType()); 00764 query.bindValue(":tracknumber", pTrack->getTrackNumber()); 00765 query.bindValue(":comment", pTrack->getComment()); 00766 query.bindValue(":url", pTrack->getURL()); 00767 query.bindValue(":duration", pTrack->getDuration()); 00768 query.bindValue(":bitrate", pTrack->getBitrate()); 00769 query.bindValue(":samplerate", pTrack->getSampleRate()); 00770 query.bindValue(":cuepoint", pTrack->getCuePoint()); 00771 00772 query.bindValue(":replaygain", pTrack->getReplayGain()); 00773 query.bindValue(":key", pTrack->getKey()); 00774 query.bindValue(":rating", pTrack->getRating()); 00775 const QByteArray* pWaveSummary = pTrack->getWaveSummary(); 00776 query.bindValue(":wavesummaryhex", pWaveSummary ? *pWaveSummary : QVariant(QVariant::ByteArray)); 00777 query.bindValue(":timesplayed", pTrack->getTimesPlayed()); 00778 query.bindValue(":played", pTrack->getPlayed()); 00779 query.bindValue(":channels", pTrack->getChannels()); 00780 query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0); 00781 //query.bindValue(":location", pTrack->getLocation()); 00782 00783 BeatsPointer pBeats = pTrack->getBeats(); 00784 QByteArray* pBeatsBlob = NULL; 00785 QString beatsVersion = ""; 00786 double dBpm = pTrack->getBpm(); 00787 00788 if (pBeats) { 00789 pBeatsBlob = pBeats->toByteArray(); 00790 beatsVersion = pBeats->getVersion(); 00791 dBpm = pBeats->getBpm(); 00792 } 00793 00794 query.bindValue(":beats", pBeatsBlob ? *pBeatsBlob : QVariant(QVariant::ByteArray)); 00795 query.bindValue(":beats_version", beatsVersion); 00796 query.bindValue(":bpm", dBpm); 00797 delete pBeatsBlob; 00798 00799 if (!query.exec()) { 00800 LOG_FAILED_QUERY(query); 00801 m_database.rollback(); 00802 return; 00803 } 00804 00805 if (query.numRowsAffected() == 0) { 00806 qWarning() << "updateTrack had no effect: trackId" << trackId << "invalid"; 00807 m_database.rollback(); 00808 return; 00809 } 00810 00811 //qDebug() << "Update track took : " << time.elapsed() << "ms. Now updating cues"; 00812 time.start(); 00813 m_cueDao.saveTrackCues(trackId, pTrack); 00814 m_database.commit(); 00815 00816 //qDebug() << "Update track in database took: " << time.elapsed() << "ms"; 00817 time.start(); 00818 pTrack->setDirty(false); 00819 //qDebug() << "Dirtying track took: " << time.elapsed() << "ms"; 00820 } 00821 00825 void TrackDAO::invalidateTrackLocationsInLibrary(QString libraryPath) { 00826 //qDebug() << "TrackDAO::invalidateTrackLocations" << QThread::currentThread() << m_database.connectionName(); 00827 //qDebug() << "invalidateTrackLocations(" << libraryPath << ")"; 00828 libraryPath += "%"; //Add wildcard to SQL query to match subdirectories! 00829 00830 QSqlQuery query(m_database); 00831 query.prepare("UPDATE track_locations " 00832 "SET needs_verification=1 " 00833 "WHERE directory LIKE :directory"); 00834 query.bindValue(":directory", libraryPath); 00835 if (!query.exec()) { 00836 LOG_FAILED_QUERY(query) 00837 << "Couldn't mark tracks in directory" << libraryPath 00838 << "as needing verification."; 00839 } 00840 } 00841 00842 void TrackDAO::markTrackLocationAsVerified(QString location) 00843 { 00844 //qDebug() << "TrackDAO::markTrackLocationAsVerified" << QThread::currentThread() << m_database.connectionName(); 00845 //qDebug() << "markTrackLocationAsVerified()" << location; 00846 00847 QSqlQuery query(m_database); 00848 query.prepare("UPDATE track_locations " 00849 "SET needs_verification=0, fs_deleted=0 " 00850 "WHERE location=:location"); 00851 query.bindValue(":location", location); 00852 if (!query.exec()) { 00853 LOG_FAILED_QUERY(query) 00854 << "Couldn't mark track" << location << " as verified."; 00855 } 00856 } 00857 00858 void TrackDAO::markTracksInDirectoryAsVerified(QString directory) { 00859 //qDebug() << "TrackDAO::markTracksInDirectoryAsVerified" << QThread::currentThread() << m_database.connectionName(); 00860 //qDebug() << "markTracksInDirectoryAsVerified()" << directory; 00861 00862 QSqlQuery query(m_database); 00863 query.prepare("UPDATE track_locations " 00864 "SET needs_verification=0 " 00865 "WHERE directory=:directory"); 00866 query.bindValue(":directory", directory); 00867 if (!query.exec()) { 00868 LOG_FAILED_QUERY(query) 00869 << "Couldn't mark tracks in" << directory << " as verified."; 00870 } 00871 } 00872 00873 void TrackDAO::markUnverifiedTracksAsDeleted() { 00874 //qDebug() << "TrackDAO::markUnverifiedTracksAsDeleted" << QThread::currentThread() << m_database.connectionName(); 00875 //qDebug() << "markUnverifiedTracksAsDeleted()"; 00876 00877 QSqlQuery query(m_database); 00878 query.prepare("UPDATE track_locations " 00879 "SET fs_deleted=1, needs_verification=0 " 00880 "WHERE needs_verification=1"); 00881 if (!query.exec()) { 00882 LOG_FAILED_QUERY(query) 00883 << "Couldn't mark unverified tracks as deleted."; 00884 } 00885 00886 } 00887 00888 void TrackDAO::markTrackLocationsAsDeleted(QString directory) { 00889 //qDebug() << "TrackDAO::markTrackLocationsAsDeleted" << QThread::currentThread() << m_database.connectionName(); 00890 QSqlQuery query(m_database); 00891 query.prepare("UPDATE track_locations " 00892 "SET fs_deleted=1 " 00893 "WHERE directory=:directory"); 00894 query.bindValue(":directory", directory); 00895 if (!query.exec()) { 00896 LOG_FAILED_QUERY(query) 00897 << "Couldn't mark tracks in" << directory << "as deleted."; 00898 } 00899 } 00900 00905 void TrackDAO::detectMovedFiles() { 00906 //This function should not start a transaction on it's own! 00907 //When it's called from libraryscanner.cpp, there already is a transaction 00908 //started! 00909 00910 QSqlQuery query(m_database); 00911 QSqlQuery query2(m_database); 00912 int oldTrackLocationId = -1; 00913 int newTrackLocationId = -1; 00914 QString filename; 00915 int fileSize; 00916 00917 query.prepare("SELECT id, filename, filesize FROM track_locations WHERE fs_deleted=1"); 00918 00919 if (!query.exec()) { 00920 LOG_FAILED_QUERY(query); 00921 } 00922 00923 //For each track that's been "deleted" on disk... 00924 while (query.next()) { 00925 newTrackLocationId = -1; //Reset this var 00926 oldTrackLocationId = query.value(query.record().indexOf("id")).toInt(); 00927 filename = query.value(query.record().indexOf("filename")).toString(); 00928 fileSize = query.value(query.record().indexOf("filesize")).toInt(); 00929 00930 query2.prepare("SELECT id FROM track_locations WHERE " 00931 "fs_deleted=0 AND " 00932 "filename=:filename AND " 00933 "filesize=:filesize"); 00934 query2.bindValue(":filename", filename); 00935 query2.bindValue(":filesize", fileSize); 00936 Q_ASSERT(query2.exec()); 00937 00938 Q_ASSERT(query2.size() <= 1); //WTF duplicate tracks? 00939 while (query2.next()) 00940 { 00941 newTrackLocationId = query2.value(query2.record().indexOf("id")).toInt(); 00942 } 00943 00944 //If we found a moved track... 00945 if (newTrackLocationId >= 0) 00946 { 00947 qDebug() << "Found moved track!" << filename; 00948 00949 //Remove old row from track_locations table 00950 query2.prepare("DELETE FROM track_locations WHERE " 00951 "id=:id"); 00952 query2.bindValue(":id", oldTrackLocationId); 00953 Q_ASSERT(query2.exec()); 00954 00955 //The library scanner will have added a new row to the Library 00956 //table which corresponds to the track in the new location. We need 00957 //to remove that so we don't end up with two rows in the library table 00958 //for the same track. 00959 query2.prepare("DELETE FROM library WHERE " 00960 "location=:location"); 00961 query2.bindValue(":location", newTrackLocationId); 00962 Q_ASSERT(query2.exec()); 00963 00964 //Update the location foreign key for the existing row in the library table 00965 //to point to the correct row in the track_locations table. 00966 query2.prepare("UPDATE library " 00967 "SET location=:newloc WHERE location=:oldloc"); 00968 query2.bindValue(":newloc", newTrackLocationId); 00969 query2.bindValue(":oldloc", oldTrackLocationId); 00970 Q_ASSERT(query2.exec()); 00971 } 00972 } 00973 } 00974 00975 void TrackDAO::clearCache() { 00976 m_trackCache.clear(); 00977 m_dirtyTracks.clear(); 00978 } 00979 00980 void TrackDAO::writeAudioMetaData(TrackInfoObject* pTrack){ 00981 if (m_pConfig && m_pConfig->getValueString(ConfigKey("[Library]","WriteAudioTags")).toInt() == 1) { 00982 AudioTagger tagger(pTrack->getLocation()); 00983 00984 tagger.setArtist(pTrack->getArtist()); 00985 tagger.setTitle(pTrack->getTitle()); 00986 tagger.setGenre(pTrack->getGenre()); 00987 tagger.setAlbum(pTrack->getAlbum()); 00988 tagger.setComment(pTrack->getComment()); 00989 tagger.setTracknumber(pTrack->getTrackNumber()); 00990 tagger.setBpm(pTrack->getBpmStr()); 00991 00992 tagger.save(); 00993 } 00994 } 00995 00996 bool TrackDAO::isTrackFormatSupported(TrackInfoObject* pTrack) const { 00997 return SoundSourceProxy::isFilenameSupported(pTrack->getFilename()); 00998 }