Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/library/dao/trackdao.cpp

Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines