=== modified file 'mixxx/src/library/browse/browsetablemodel.cpp' --- mixxx/src/library/browse/browsetablemodel.cpp 2011-05-01 03:59:49 +0000 +++ mixxx/src/library/browse/browsetablemodel.cpp 2011-08-03 19:17:29 +0000 @@ -191,8 +191,8 @@ any_deleted = true; // If the track was contained in the Mixxx library, delete it - if (track_dao.trackExistsInDatabase(track_location)) { - int id = track_dao.getTrackId(track_location); + int id = track_dao.getTrackId(track_location); + if (id != -1) { qDebug() << "BrowseFeature: Deletion affected database"; track_dao.removeTrack(id); } === modified file 'mixxx/src/library/dao/trackdao.cpp' --- mixxx/src/library/dao/trackdao.cpp 2011-03-29 12:58:50 +0000 +++ mixxx/src/library/dao/trackdao.cpp 2011-08-07 20:21:04 +0000 @@ -10,6 +10,9 @@ #include "audiotagger.h" #include "soundsourceproxy.h" +QHash TrackDAO::m_sTracks; +QMutex TrackDAO::m_sTracksMutex; + // The number of tracks to cache in memory at once. Once the n+1'th track is // created, the TrackDAO's QCache deletes its TrackPointer to the track, which // allows the track reference count to drop to zero. The track cache basically @@ -20,14 +23,38 @@ TrackDAO::TrackDAO(QSqlDatabase& database, CueDAO& cueDao, ConfigObject * pConfig) : m_database(database), m_cueDao(cueDao), + m_trackCache(TRACK_CACHE_SIZE), m_pConfig(pConfig), - m_trackCache(TRACK_CACHE_SIZE) { - + m_pQueryTrackLocationInsert(NULL), + m_pQueryLibraryInsert(NULL){ + +} + +TrackDAO::~TrackDAO() +{ + qDebug() << "~TrackDAO()"; } void TrackDAO::finish() { - //clear out played information on exit + // Save all tracks that haven't been saved yet. + m_sTracksMutex.lock(); + QHashIterator it(m_sTracks); + while (it.hasNext()) { + it.next(); + // Auto-cast from TrackWeakPointer to TrackPointer + TrackPointer pTrack = it.value(); + if (pTrack && pTrack->isDirty()) { + saveTrack(pTrack); + // now pTrack->isDirty will return false + } + } + m_sTracksMutex.unlock(); + + // Clear cache, so all cached tracks without other references where deleted + clearCache(); + + //clear out played information on exit //crash prevention: if mixxx crashes, played information will be maintained qDebug() << "Clearing played information for this session"; QSqlQuery query(m_database); @@ -37,11 +64,6 @@ } } -TrackDAO::~TrackDAO() -{ - qDebug() << "~TrackDAO()"; -} - void TrackDAO::initialize() { qDebug() << "TrackDAO::initialize" << QThread::currentThread() << m_database.connectionName(); @@ -113,15 +135,16 @@ void TrackDAO::saveTrack(TrackInfoObject* pTrack) { if (!pTrack) { qWarning() << "TrackDAO::saveTrack() was given NULL track."; + return; } //qDebug() << "TrackDAO::saveTrack" << pTrack->getId() << pTrack->getInfo(); // If track's id is not -1, then update, otherwise add. int trackId = pTrack->getId(); if (trackId != -1) { if (pTrack->isDirty()) { - if (!m_dirtyTracks.contains(trackId)) { - qDebug() << "WARNING: Inconsistent state in TrackDAO. Track is dirty while TrackDAO thinks it is clean."; - } + //if (!m_dirtyTracks.contains(trackId)) { + // qDebug() << "WARNING: Inconsistent state in TrackDAO. Track is dirty while TrackDAO thinks it is clean."; + //} //qDebug() << this << "Dirty tracks before claen save:" << m_dirtyTracks.size(); //qDebug() << "TrackDAO::saveTrack. Dirty. Calling update"; @@ -136,10 +159,10 @@ //qDebug() << this << "Dirty tracks remaining:" << m_dirtyTracks.size(); // Q_ASSERT(!m_dirtyTracks.contains(trackId)); - if (m_dirtyTracks.contains(trackId)) { - qDebug() << "WARNING: Inconsistent state in TrackDAO. Track is clean while TrackDAO thinks it is dirty. Correcting."; - m_dirtyTracks.remove(trackId); - } + //if (m_dirtyTracks.contains(trackId)) { + // qDebug() << "WARNING: Inconsistent state in TrackDAO. Track is clean while TrackDAO thinks it is dirty. Correcting."; + // m_dirtyTracks.remove(trackId); + //} //qDebug() << "Skipping track update for track" << pTrack->getId(); } @@ -148,9 +171,11 @@ } } +/* bool TrackDAO::isDirty(int trackId) { return m_dirtyTracks.contains(trackId); } +*/ void TrackDAO::slotTrackDirty(TrackInfoObject* pTrack) { //qDebug() << "TrackDAO::slotTrackDirty" << pTrack->getInfo(); @@ -158,11 +183,13 @@ // TrackDAO. It is a way for the track to ask that it be saved. The only // time this could be unsafe is when the TIO's reference count drops to // 0. When that happens, the TIO is deleted with QObject:deleteLater, so Qt - // will wait for this slot to comlete. + // will wait for this slot to complete. if (pTrack) { int id = pTrack->getId(); if (id != -1) { - m_dirtyTracks.insert(id); + //m_dirtyTracks.insert(id); + // TODO(XXX) Do we realy need to emit that? + // No one is connected to that. emit(trackDirty(id)); } } @@ -174,12 +201,12 @@ // TrackDAO. It is a way for the track to ask that it be saved. The only // time this could be unsafe is when the TIO's reference count drops to // 0. When that happens, the TIO is deleted with QObject:deleteLater, so Qt - // will wait for this slot to comlete. + // will wait for this slot to complete. if (pTrack) { int id = pTrack->getId(); if (id != -1) { - m_dirtyTracks.remove(id); + //m_dirtyTracks.remove(id); emit(trackClean(id)); } } @@ -191,7 +218,7 @@ // TrackDAO. It is a way for the track to ask that it be saved. The only // time this could be unsafe is when the TIO's reference count drops to // 0. When that happens, the TIO is deleted with QObject:deleteLater, so Qt - // will wait for this slot to comlete. + // will wait for this slot to complete. if (pTrack) { int id = pTrack->getId(); if (id != -1) { @@ -206,81 +233,48 @@ // TrackDAO. It is a way for the track to ask that it be saved. The last // time it is used is when the track is being deleted (i.e. its reference // count has dropped to 0). The TIO is deleted with QObject:deleteLater, so - // Qt will wait for this slot to comlete. + // Qt will wait for this slot to complete. if (pTrack) { saveTrack(pTrack); } } -void TrackDAO::saveDirtyTracks() { - qDebug() << "TrackDAO::saveDirtyTracks()"; - QHashIterator it(m_tracks); - while (it.hasNext()) { - it.next(); - // Auto-cast from TrackWeakPointer to TrackPointer - TrackPointer pTrack = it.value(); - if (pTrack && pTrack->isDirty()) { - saveTrack(pTrack); - } - } - clearCache(); -} - -void TrackDAO::prepareTrackLocationsInsert(QSqlQuery& query) { - query.prepare("INSERT INTO track_locations (location, directory, filename, filesize, fs_deleted, needs_verification) " - "VALUES (:location, :directory, :filename, :filesize, :fs_deleted, :needs_verification)"); -} - -void TrackDAO::bindTrackToTrackLocationsInsert(QSqlQuery& query, TrackInfoObject* pTrack) { - query.bindValue(":location", pTrack->getLocation()); - query.bindValue(":directory", pTrack->getDirectory()); - query.bindValue(":filename", pTrack->getFilename()); - query.bindValue(":filesize", pTrack->getLength()); +void TrackDAO::bindTrackToTrackLocationsInsert(TrackInfoObject* pTrack) { + m_pQueryTrackLocationInsert->bindValue(":location", pTrack->getLocation()); + m_pQueryTrackLocationInsert->bindValue(":directory", pTrack->getDirectory()); + m_pQueryTrackLocationInsert->bindValue(":filename", pTrack->getFilename()); + m_pQueryTrackLocationInsert->bindValue(":filesize", pTrack->getLength()); // Should this check pTrack->exists()? - query.bindValue(":fs_deleted", 0); - query.bindValue(":needs_verification", 0); -} - -void TrackDAO::prepareLibraryInsert(QSqlQuery& query) { - query.prepare("INSERT INTO library (artist, title, album, year, genre, tracknumber, " - "filetype, location, comment, url, duration, rating, key, " - "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, " - "timesplayed, " - "channels, mixxx_deleted, header_parsed, beats_version, beats) " - "VALUES (:artist, " - ":title, :album, :year, :genre, :tracknumber, " - ":filetype, :location, :comment, :url, :duration, :rating, :key, " - ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, " - ":timesplayed, " - ":channels, :mixxx_deleted, :header_parsed, :beats_version, :beats)"); -} - -void TrackDAO::bindTrackToLibraryInsert(QSqlQuery& query, TrackInfoObject* pTrack, int trackLocationId) { - query.bindValue(":artist", pTrack->getArtist()); - query.bindValue(":title", pTrack->getTitle()); - query.bindValue(":album", pTrack->getAlbum()); - query.bindValue(":year", pTrack->getYear()); - query.bindValue(":genre", pTrack->getGenre()); - query.bindValue(":tracknumber", pTrack->getTrackNumber()); - query.bindValue(":filetype", pTrack->getType()); - query.bindValue(":location", trackLocationId); - query.bindValue(":comment", pTrack->getComment()); - query.bindValue(":url", pTrack->getURL()); - query.bindValue(":duration", pTrack->getDuration()); - query.bindValue(":rating", pTrack->getRating()); - query.bindValue(":bitrate", pTrack->getBitrate()); - query.bindValue(":samplerate", pTrack->getSampleRate()); - query.bindValue(":cuepoint", pTrack->getCuePoint()); - - query.bindValue(":replaygain", pTrack->getReplayGain()); - query.bindValue(":key", pTrack->getKey()); + m_pQueryTrackLocationInsert->bindValue(":fs_deleted", 0); + m_pQueryTrackLocationInsert->bindValue(":needs_verification", 0); +} + +void TrackDAO::bindTrackToLibraryInsert(TrackInfoObject* pTrack, int trackLocationId) { + m_pQueryLibraryInsert->bindValue(":artist", pTrack->getArtist()); + m_pQueryLibraryInsert->bindValue(":title", pTrack->getTitle()); + m_pQueryLibraryInsert->bindValue(":album", pTrack->getAlbum()); + m_pQueryLibraryInsert->bindValue(":year", pTrack->getYear()); + m_pQueryLibraryInsert->bindValue(":genre", pTrack->getGenre()); + m_pQueryLibraryInsert->bindValue(":tracknumber", pTrack->getTrackNumber()); + m_pQueryLibraryInsert->bindValue(":filetype", pTrack->getType()); + m_pQueryLibraryInsert->bindValue(":location", trackLocationId); + m_pQueryLibraryInsert->bindValue(":comment", pTrack->getComment()); + m_pQueryLibraryInsert->bindValue(":url", pTrack->getURL()); + m_pQueryLibraryInsert->bindValue(":duration", pTrack->getDuration()); + m_pQueryLibraryInsert->bindValue(":rating", pTrack->getRating()); + m_pQueryLibraryInsert->bindValue(":bitrate", pTrack->getBitrate()); + m_pQueryLibraryInsert->bindValue(":samplerate", pTrack->getSampleRate()); + m_pQueryLibraryInsert->bindValue(":cuepoint", pTrack->getCuePoint()); + + m_pQueryLibraryInsert->bindValue(":replaygain", pTrack->getReplayGain()); + m_pQueryLibraryInsert->bindValue(":key", pTrack->getKey()); const QByteArray* pWaveSummary = pTrack->getWaveSummary(); - query.bindValue(":wavesummaryhex", pWaveSummary ? *pWaveSummary : QVariant(QVariant::ByteArray)); - query.bindValue(":timesplayed", pTrack->getTimesPlayed()); + m_pQueryLibraryInsert->bindValue(":wavesummaryhex", pWaveSummary ? *pWaveSummary : QVariant(QVariant::ByteArray)); + m_pQueryLibraryInsert->bindValue(":timesplayed", pTrack->getTimesPlayed()); //query.bindValue(":datetime_added", pTrack->getDateAdded()); - query.bindValue(":channels", pTrack->getChannels()); - query.bindValue(":mixxx_deleted", 0); - query.bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0); + m_pQueryLibraryInsert->bindValue(":channels", pTrack->getChannels()); + m_pQueryLibraryInsert->bindValue(":mixxx_deleted", 0); + m_pQueryLibraryInsert->bindValue(":header_parsed", pTrack->getHeaderParsed() ? 1 : 0); const QByteArray* pBeatsBlob = NULL; QString blobVersion = ""; @@ -294,90 +288,116 @@ dBpm = pBeats->getBpm(); } - query.bindValue(":bpm", dBpm); - query.bindValue(":beats_version", blobVersion); - query.bindValue(":beats", pBeatsBlob ? *pBeatsBlob : QVariant(QVariant::ByteArray)); + m_pQueryLibraryInsert->bindValue(":bpm", dBpm); + m_pQueryLibraryInsert->bindValue(":beats_version", blobVersion); + m_pQueryLibraryInsert->bindValue(":beats", pBeatsBlob ? *pBeatsBlob : QVariant(QVariant::ByteArray)); delete pBeatsBlob; } -void TrackDAO::addTracks(QList tracksToAdd) { - QTime time; - time.start(); - +void TrackDAO::addTracksPrepare() { //Start the transaction m_database.transaction(); - QSqlQuery query(m_database); - QSqlQuery query_finder(m_database); - query_finder.prepare("SELECT id FROM track_locations WHERE location=:location"); - - // Major time saver for having this outside the loop - prepareTrackLocationsInsert(query); - - foreach (TrackInfoObject* pTrack, tracksToAdd) { - if (!isTrackFormatSupported(pTrack)) { - continue; - } - - bindTrackToTrackLocationsInsert(query, pTrack); - - int trackLocationId = -1; - if (!query.exec()) { - qDebug() << "Location " << pTrack->getLocation() << " is already in the DB"; - query.bindValue(":location", pTrack->getLocation()); - - if (!query.exec()) { - // We can't even select this, something is wrong. - qDebug() << query.lastError(); - m_database.rollback(); - return; - } - while (query.next()) { - trackLocationId = query.value(query.record().indexOf("id")).toInt(); - } - } else { - // Inserting succeeded, so just get the last rowid. - QVariant lastInsert = query.lastInsertId(); - trackLocationId = lastInsert.toInt(); - } - - // To save time on future queries, setId the trackLocationId on the - // track. This takes advantage of the fact that I know the - // LibraryScanner doesn't use these tracks for anything. rryan 9/2010 - pTrack->setId(trackLocationId); - } - - // Major time saver for having this outside the loop - prepareLibraryInsert(query); - - foreach (TrackInfoObject* pTrack, tracksToAdd) { - bindTrackToLibraryInsert(query, pTrack, pTrack->getId()); - - if (!query.exec()) { - qDebug() << "Failed to INSERT new track into library:" - << pTrack->getFilename() - << __FILE__ << __LINE__ << query.lastError(); - m_database.rollback(); - return; - } - - int trackId = query.lastInsertId().toInt(); - - if (trackId >= 0) { - m_cueDao.saveTrackCues(trackId, pTrack); - } else { - qDebug() << "Could not get track ID to save the track cue points:" - << query.lastError(); - } - - // Undo the hack we did above by setting this track to its new id in the - // database. - pTrack->setId(trackId); - } + m_pQueryTrackLocationInsert = new QSqlQuery(m_database); + m_pQueryLibraryInsert = new QSqlQuery(m_database); + + m_pQueryTrackLocationInsert->prepare("INSERT INTO track_locations " + "(location, directory, filename, filesize, fs_deleted, needs_verification) " + "VALUES (:location, :directory, :filename, :filesize, :fs_deleted, :needs_verification)" + ); + + m_pQueryLibraryInsert->prepare("INSERT INTO library " + "(artist, title, album, year, genre, tracknumber, " + "filetype, location, comment, url, duration, rating, key, " + "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, " + "timesplayed, " + "channels, mixxx_deleted, header_parsed, beats_version, beats) " + "VALUES (" + ":artist, :title, :album, :year, :genre, :tracknumber, " + ":filetype, :location, :comment, :url, :duration, :rating, :key, " + ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, " + ":timesplayed, " + ":channels, :mixxx_deleted, :header_parsed, :beats_version, :beats)" + ); +} + +void TrackDAO::addTracksFinish() { + delete m_pQueryTrackLocationInsert; + delete m_pQueryLibraryInsert; + m_pQueryTrackLocationInsert = NULL; + m_pQueryLibraryInsert = NULL; m_database.commit(); - - qDebug() << "Writing tracks to database by TrackDAO::addTracks() took " << time.elapsed() << " ms"; +} + +bool TrackDAO::addTracksTrack(TrackInfoObject* pTrack) { + + int trackLocationId = -1; + + // Insert the track location into the corresponding table. This will fail + // silently if the location is already in the table because it has a UNIQUE + // constraint. + bindTrackToTrackLocationsInsert(pTrack); + + if (!m_pQueryTrackLocationInsert->exec()) { + qDebug() << "Location " << pTrack->getLocation() << " is already in the DB"; + // Inserting into track_locations failed, so the file already + // exists. Query for its id + + m_pQueryTrackLocationInsert->bindValue(":location", pTrack->getLocation()); + + if (!m_pQueryTrackLocationInsert->exec()) { + // We can't even select this, something is wrong. + qDebug() << m_pQueryTrackLocationInsert->lastError(); + m_database.rollback(); + return false; + } + while (m_pQueryTrackLocationInsert->next()) { + trackLocationId = m_pQueryTrackLocationInsert->value(m_pQueryTrackLocationInsert->record().indexOf("id")).toInt(); + } + } else { + // Inserting succeeded, so just get the last rowid. + QVariant lastInsert = m_pQueryTrackLocationInsert->lastInsertId(); + trackLocationId = lastInsert.toInt(); + } + + //Failure of this assert indicates that we were unable to insert the track + //location into the table AND we could not retrieve the id of that track + //location from the same table. "It shouldn't happen"... unless I screwed up + //- Albert :) + Q_ASSERT(trackLocationId >= 0); + + // To save time on future queries, setId the trackLocationId on the + // track. This takes advantage of the fact that I know the + // LibraryScanner doesn't use these tracks for anything. rryan 9/2010 + // (daschuer:) This comment seems to be out dated + //pTrack->setId(trackLocationId); + + + bindTrackToLibraryInsert(pTrack, trackLocationId); + + if (!m_pQueryLibraryInsert->exec()) { + qDebug() << "Failed to INSERT new track into library:" + << pTrack->getFilename() + << __FILE__ << __LINE__ << m_pQueryLibraryInsert->lastError(); + m_database.rollback(); + return false; + } + + int trackId = m_pQueryLibraryInsert->lastInsertId().toInt(); + + if (pTrack->getCuePoints().count()) { + if (trackId >= 0) { + m_cueDao.saveTrackCues(trackId, pTrack); + } else { + qDebug() << "Could not get track ID to save the track cue points:" + << m_pQueryLibraryInsert->lastError(); + } + } + + pTrack->setId(trackId); + pTrack->setDirty(false); + return true; } int TrackDAO::addTrack(QFileInfo& fileInfo) { @@ -412,80 +432,10 @@ return; } - //Start the transaction - m_database.transaction(); - - QSqlQuery query(m_database); - int trackLocationId = -1; - - // Insert the track location into the corresponding table. This will fail - // silently if the location is already in the table because it has a UNIQUE - // constraint. - prepareTrackLocationsInsert(query); - bindTrackToTrackLocationsInsert(query, pTrack); - - if (!query.exec()) { - // Inserting into track_locations failed, so the file already - // exists. Query for its id. - query.prepare("SELECT id FROM track_locations WHERE location=:location"); - query.bindValue(":location", pTrack->getLocation()); - - if (!query.exec()) { - // We can't even select this, something is wrong. - qDebug() << query.lastError(); - m_database.rollback(); - return; - } - while (query.next()) { - trackLocationId = query.value(query.record().indexOf("id")).toInt(); - } - } else { - // Inserting succeeded, so just get the last rowid. - QVariant lastInsert = query.lastInsertId(); - trackLocationId = lastInsert.toInt(); - } - - //Failure of this assert indicates that we were unable to insert the track - //location into the table AND we could not retrieve the id of that track - //location from the same table. "It shouldn't happen"... unless I screwed up - //- Albert :) - Q_ASSERT(trackLocationId >= 0); - - prepareLibraryInsert(query); - bindTrackToLibraryInsert(query, pTrack, trackLocationId); - - int trackId = -1; - - if (!query.exec()) - { - qDebug() << "Failed to INSERT new track into library" - << __FILE__ << __LINE__ << query.lastError(); - m_database.rollback(); - return; - } else { - // Inserting succeeded, so just get the last rowid. - trackId = query.lastInsertId().toInt(); - } - //query.finish(); - - Q_ASSERT(trackId >= 0); - - if (trackId >= 0) { - m_cueDao.saveTrackCues(trackId, pTrack); - } else { - qDebug() << "Could not get track ID to save the track cue points:" - << query.lastError(); - } - - pTrack->setId(trackId); - pTrack->setDirty(false); - - //query.finish(); - - //Commit the transaction - m_database.commit(); - //qDebug() << "addTrack took" << time.elapsed() << "ms"; -} + addTracksPrepare(); + addTracksTrack(pTrack); + addTracksFinish(); + } /** Removes a track from the library track collection. */ void TrackDAO::removeTrack(int id) @@ -552,135 +502,159 @@ qDebug() << "Got deletion call for track" << pTrack << "ID" << pTrack->getId() << pTrack->getInfo(); // Save the track if it is dirty. - pTrack->doSave(); + if (pTrack->isDirty()) { + pTrack->doSave(); + } + + // Delete Track from weak reference cache + m_sTracksMutex.lock(); + m_sTracks.remove(pTrack->getId()); + m_sTracksMutex.unlock(); // Now Qt will delete it in the event loop. pTrack->deleteLater(); } -TrackPointer TrackDAO::getTrackFromDB(QSqlQuery &query) const +TrackPointer TrackDAO::getTrackFromDB(int id) const { - if (!query.isValid()) { - //query.exec(); - } - - //Print out any SQL error, if there was one. - if (query.lastError().isValid()) { - qDebug() << query.lastError(); - } - - int locationId = -1; - while (query.next()) { - // Good god! Assign query.record() to a freaking variable! - int trackId = query.value(query.record().indexOf("id")).toInt(); - QString artist = query.value(query.record().indexOf("artist")).toString(); - QString title = query.value(query.record().indexOf("title")).toString(); - QString album = query.value(query.record().indexOf("album")).toString(); - QString year = query.value(query.record().indexOf("year")).toString(); - QString genre = query.value(query.record().indexOf("genre")).toString(); - QString tracknumber = query.value(query.record().indexOf("tracknumber")).toString(); - QString comment = query.value(query.record().indexOf("comment")).toString(); - QString url = query.value(query.record().indexOf("url")).toString(); - QString key = query.value(query.record().indexOf("key")).toString(); - int duration = query.value(query.record().indexOf("duration")).toInt(); - int bitrate = query.value(query.record().indexOf("bitrate")).toInt(); - int rating = query.value(query.record().indexOf("rating")).toInt(); - int samplerate = query.value(query.record().indexOf("samplerate")).toInt(); - int cuepoint = query.value(query.record().indexOf("cuepoint")).toInt(); - QString bpm = query.value(query.record().indexOf("bpm")).toString(); - QString replaygain = query.value(query.record().indexOf("replaygain")).toString(); - QByteArray* wavesummaryhex = new QByteArray( - query.value(query.record().indexOf("wavesummaryhex")).toByteArray()); - int timesplayed = query.value(query.record().indexOf("timesplayed")).toInt(); - int played = query.value(query.record().indexOf("played")).toInt(); - int channels = query.value(query.record().indexOf("channels")).toInt(); - int filesize = query.value(query.record().indexOf("filesize")).toInt(); - QString filetype = query.value(query.record().indexOf("filetype")).toString(); - QString location = query.value(query.record().indexOf("location")).toString(); - bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool(); - - TrackInfoObject* track = new TrackInfoObject(location, false); - TrackPointer pTrack = TrackPointer(track, &TrackDAO::deleteTrack); - - // TIO already stats the file to see if it exists, what its length is, - // etc. So don't bother setting it. - //track->setLength(filesize); - - track->setId(trackId); - track->setArtist(artist); - track->setTitle(title); - track->setAlbum(album); - track->setYear(year); - track->setGenre(genre); - track->setTrackNumber(tracknumber); - track->setRating(rating); - track->setKey(key); - - track->setComment(comment); - track->setURL(url); - track->setDuration(duration); - track->setBitrate(bitrate); - track->setSampleRate(samplerate); - track->setCuePoint((float)cuepoint); - track->setBpm(bpm.toFloat()); - track->setReplayGain(replaygain.toFloat()); - track->setWaveSummary(wavesummaryhex, false); - delete wavesummaryhex; - - QString beatsVersion = query.value(query.record().indexOf("beats_version")).toString(); - QByteArray beatsBlob = query.value(query.record().indexOf("beats")).toByteArray(); - BeatsPointer pBeats = BeatFactory::loadBeatsFromByteArray(pTrack, beatsVersion, &beatsBlob); - if (pBeats) { - pTrack->setBeats(pBeats); - } - - track->setTimesPlayed(timesplayed); - track->setPlayed(played); - track->setChannels(channels); - track->setType(filetype); - track->setLocation(location); - track->setHeaderParsed(header_parsed); - - track->setCuePoints(m_cueDao.getCuesForTrack(trackId)); - track->setDirty(false); - - // Listen to dirty and changed signals - connect(track, SIGNAL(dirty(TrackInfoObject*)), - this, SLOT(slotTrackDirty(TrackInfoObject*)), - Qt::DirectConnection); - connect(track, SIGNAL(clean(TrackInfoObject*)), - this, SLOT(slotTrackClean(TrackInfoObject*)), - Qt::DirectConnection); - connect(track, SIGNAL(changed(TrackInfoObject*)), - this, SLOT(slotTrackChanged(TrackInfoObject*)), - Qt::DirectConnection); - connect(track, SIGNAL(save(TrackInfoObject*)), - this, SLOT(slotTrackSave(TrackInfoObject*)), - Qt::DirectConnection); - - // Automatic conversion to a weak pointer - m_tracks[trackId] = pTrack; - m_trackCache.insert(trackId, new TrackPointer(pTrack)); - - // If the header hasn't been parsed, parse it but only after we set the - // track clean and hooked it up to the track cache, because this will - // dirty it. - if (!header_parsed) { - pTrack->parse(); - } - - if (!pBeats && pTrack->getBpm() != 0.0f) { - // The track has no stored beats but has a previously detected BPM - // or a BPM loaded from metadata. Automatically create a beatgrid - // for the track. This dirties the track so we have to do it after - // all the signal connections, etc. are in place. - BeatsPointer pBeats = BeatFactory::makeBeatGrid(pTrack, pTrack->getBpm(), 0.0f); - pTrack->setBeats(pBeats); - } - - return pTrack; - } - //query.finish(); + QTime time; + time.start(); + + QSqlQuery query(m_database); + + query.prepare( + "SELECT library.id, artist, title, album, year, genre, tracknumber, " + "filetype, rating, key, track_locations.location as location, " + "track_locations.filesize as filesize, comment, url, duration, bitrate, " + "samplerate, cuepoint, bpm, replaygain, wavesummaryhex, channels, " + "header_parsed, timesplayed, played, beats_version, beats " + "FROM Library " + "INNER JOIN track_locations " + "ON library.location = track_locations.id " + "WHERE library.id=" + QString("%1").arg(id) + ); + + if (query.exec()) { + + //int locationId = -1; + while (query.next()) { + // Good god! Assign query.record() to a freaking variable! + // int trackId = query.value(query.record().indexOf("id")).toInt(); + QString artist = query.value(query.record().indexOf("artist")).toString(); + QString title = query.value(query.record().indexOf("title")).toString(); + QString album = query.value(query.record().indexOf("album")).toString(); + QString year = query.value(query.record().indexOf("year")).toString(); + QString genre = query.value(query.record().indexOf("genre")).toString(); + QString tracknumber = query.value(query.record().indexOf("tracknumber")).toString(); + QString comment = query.value(query.record().indexOf("comment")).toString(); + QString url = query.value(query.record().indexOf("url")).toString(); + QString key = query.value(query.record().indexOf("key")).toString(); + int duration = query.value(query.record().indexOf("duration")).toInt(); + int bitrate = query.value(query.record().indexOf("bitrate")).toInt(); + int rating = query.value(query.record().indexOf("rating")).toInt(); + int samplerate = query.value(query.record().indexOf("samplerate")).toInt(); + int cuepoint = query.value(query.record().indexOf("cuepoint")).toInt(); + QString bpm = query.value(query.record().indexOf("bpm")).toString(); + QString replaygain = query.value(query.record().indexOf("replaygain")).toString(); + QByteArray* wavesummaryhex = new QByteArray( + query.value(query.record().indexOf("wavesummaryhex")).toByteArray()); + int timesplayed = query.value(query.record().indexOf("timesplayed")).toInt(); + int played = query.value(query.record().indexOf("played")).toInt(); + int channels = query.value(query.record().indexOf("channels")).toInt(); + //int filesize = query.value(query.record().indexOf("filesize")).toInt(); + QString filetype = query.value(query.record().indexOf("filetype")).toString(); + QString location = query.value(query.record().indexOf("location")).toString(); + bool header_parsed = query.value(query.record().indexOf("header_parsed")).toBool(); + + TrackPointer pTrack = TrackPointer(new TrackInfoObject(location, false), &TrackDAO::deleteTrack); + + // TIO already stats the file to see if it exists, what its length is, + // etc. So don't bother setting it. + //track->setLength(filesize); + + pTrack->setId(id); + pTrack->setArtist(artist); + pTrack->setTitle(title); + pTrack->setAlbum(album); + pTrack->setYear(year); + pTrack->setGenre(genre); + pTrack->setTrackNumber(tracknumber); + pTrack->setRating(rating); + pTrack->setKey(key); + + pTrack->setComment(comment); + pTrack->setURL(url); + pTrack->setDuration(duration); + pTrack->setBitrate(bitrate); + pTrack->setSampleRate(samplerate); + pTrack->setCuePoint((float)cuepoint); + pTrack->setBpm(bpm.toFloat()); + pTrack->setReplayGain(replaygain.toFloat()); + pTrack->setWaveSummary(wavesummaryhex, false); + delete wavesummaryhex; + + QString beatsVersion = query.value(query.record().indexOf("beats_version")).toString(); + QByteArray beatsBlob = query.value(query.record().indexOf("beats")).toByteArray(); + BeatsPointer pBeats = BeatFactory::loadBeatsFromByteArray(pTrack, beatsVersion, &beatsBlob); + if (pBeats) { + pTrack->setBeats(pBeats); + } + + pTrack->setTimesPlayed(timesplayed); + pTrack->setPlayed(played); + pTrack->setChannels(channels); + pTrack->setType(filetype); + pTrack->setLocation(location); + pTrack->setHeaderParsed(header_parsed); + + pTrack->setCuePoints(m_cueDao.getCuesForTrack(id)); + pTrack->setDirty(false); + + // Listen to dirty and changed signals + connect(pTrack.data(), SIGNAL(dirty(TrackInfoObject*)), + this, SLOT(slotTrackDirty(TrackInfoObject*)), + Qt::DirectConnection); + connect(pTrack.data(), SIGNAL(clean(TrackInfoObject*)), + this, SLOT(slotTrackClean(TrackInfoObject*)), + Qt::DirectConnection); + connect(pTrack.data(), SIGNAL(changed(TrackInfoObject*)), + this, SLOT(slotTrackChanged(TrackInfoObject*)), + Qt::DirectConnection); + connect(pTrack.data(), SIGNAL(save(TrackInfoObject*)), + this, SLOT(slotTrackSave(TrackInfoObject*)), + Qt::DirectConnection); + + + m_sTracksMutex.lock(); + // Automatic conversion to a weak pointer + m_sTracks[id] = pTrack; + m_sTracksMutex.unlock(); + qDebug() << "m_sTracks.count() =" << m_sTracks.count(); + m_trackCache.insert(id, new TrackPointer(pTrack)); + + // If the header hasn't been parsed, parse it but only after we set the + // track clean and hooked it up to the track cache, because this will + // dirty it. + if (!header_parsed) { + pTrack->parse(); + } + + if (!pBeats && pTrack->getBpm() != 0.0f) { + // The track has no stored beats but has a previously detected BPM + // or a BPM loaded from metadata. Automatically create a beatgrid + // for the track. This dirties the track so we have to do it after + // all the signal connections, etc. are in place. + BeatsPointer pBeats = BeatFactory::makeBeatGrid(pTrack, pTrack->getBpm(), 0.0f); + pTrack->setBeats(pBeats); + } + + return pTrack; + } // while (query.next()) + + } else { + qDebug() << QString("getTrack(%1)").arg(id) << query.lastError(); + } + //qDebug() << "getTrack hit the database, took " << time.elapsed() << "ms"; return TrackPointer(); } @@ -695,8 +669,7 @@ if (m_trackCache.contains(id)) { TrackPointer pTrack = *m_trackCache[id]; - // If the strong reference is still valid (it should be), then return - // it. Otherwise query the DB for the track. + // If the strong reference is still valid (it should be), then return it. if (pTrack) return pTrack; } @@ -705,15 +678,24 @@ // into memory. It's possible that something is currently using this track, // so its reference count is non-zero despite it not being present in the // track cache. m_tracks is a map of weak pointers to the tracks. - if (m_tracks.contains(id)) { + m_sTracksMutex.lock(); + if (m_sTracks.contains(id)) { //qDebug() << "Returning cached TIO for track" << id; - TrackPointer pTrack = m_tracks[id]; + TrackPointer pTrack = m_sTracks[id]; // If the pointer to the cached copy is still valid, return - // it. Otherwise, re-query the DB for the track. - if (pTrack) - return pTrack; + // it. Otherwise, re-query the DB for the track. + if (pTrack) { + m_sTracksMutex.unlock(); + // Add pinter to Cache again + m_trackCache.insert(id, new TrackPointer(pTrack)); + return pTrack; + } } + m_sTracksMutex.unlock(); + + + // The person only wanted the track if it was cached. if (cacheOnly) { @@ -721,32 +703,7 @@ return TrackPointer(); } - QTime time; - time.start(); - QSqlQuery query(m_database); - - query.prepare( - "SELECT library.id, artist, title, album, year, genre, tracknumber, " - "filetype, rating, key, track_locations.location as location, " - "track_locations.filesize as filesize, comment, url, duration, bitrate, " - "samplerate, cuepoint, bpm, replaygain, wavesummaryhex, channels, " - "header_parsed, timesplayed, played, beats_version, beats " - "FROM Library " - "INNER JOIN track_locations " - "ON library.location = track_locations.id " - "WHERE library.id=" + QString("%1").arg(id) - ); - - TrackPointer pTrack; - - if (query.exec()) { - pTrack = getTrackFromDB(query); - } else { - qDebug() << QString("getTrack(%1)").arg(id) << query.lastError(); - } - //qDebug() << "getTrack hit the database, took " << time.elapsed() << "ms"; - - return pTrack; + return getTrackFromDB(id); } /** Saves a track's info back to the database */ @@ -816,6 +773,7 @@ query.bindValue(":beats", pBeatsBlob ? *pBeatsBlob : QVariant(QVariant::ByteArray)); query.bindValue(":beats_version", beatsVersion); query.bindValue(":bpm", dBpm); + delete pBeatsBlob; if (!query.exec()) { qDebug() << query.lastError(); @@ -994,7 +952,7 @@ void TrackDAO::clearCache() { m_trackCache.clear(); - m_dirtyTracks.clear(); + //m_dirtyTracks.clear(); } void TrackDAO::writeAudioMetaData(TrackInfoObject* pTrack){ === modified file 'mixxx/src/library/dao/trackdao.h' --- mixxx/src/library/dao/trackdao.h 2011-07-19 18:12:33 +0000 +++ mixxx/src/library/dao/trackdao.h 2011-08-07 20:09:30 +0000 @@ -72,7 +72,11 @@ QString getTrackLocation(int id); int addTrack(QString absoluteFilePath); int addTrack(QFileInfo& fileInfo); - void addTracks(QList tracksToAdd); + + void addTracksPrepare(); + void addTracksFinish(); + bool addTracksTrack(TrackInfoObject* pTrack); + void removeTrack(int id); void removeTracks(QList ids); void unremoveTrack(int trackId); @@ -99,11 +103,6 @@ // call. void saveTrack(TrackPointer pTrack); - // TrackDAO provides a cache of TrackInfoObject's that have been requested - // via getTrack(). saveDirtyTracks() saves all cached tracks marked dirty - // to the database. - void saveDirtyTracks(); - // Clears the cached TrackInfoObjects, which can be useful when the // underlying database tables change (eg. during a library rescan, // we might detect that a track has been moved and modify the update @@ -121,14 +120,11 @@ void saveTrack(TrackInfoObject* pTrack); void updateTrack(TrackInfoObject* pTrack); void addTrack(TrackInfoObject* pTrack); - TrackPointer getTrackFromDB(QSqlQuery &query) const; + TrackPointer getTrackFromDB(int id) const; QString absoluteFilePath(QString location); - void prepareTrackLocationsInsert(QSqlQuery& query); - void bindTrackToTrackLocationsInsert(QSqlQuery& query, TrackInfoObject* pTrack); - void prepareLibraryInsert(QSqlQuery& query); - void bindTrackToLibraryInsert(QSqlQuery& query, - TrackInfoObject* pTrack, int trackLocationId); + void bindTrackToTrackLocationsInsert(TrackInfoObject* pTrack); + void bindTrackToLibraryInsert(TrackInfoObject* pTrack, int trackLocationId); void writeAudioMetaData(TrackInfoObject* pTrack); // Called when the TIO reference count drops to 0 @@ -151,10 +147,14 @@ QSqlDatabase &m_database; CueDAO &m_cueDao; - mutable QHash m_tracks; - mutable QSet m_dirtyTracks; + static QHash m_sTracks; + static QMutex m_sTracksMutex; + //mutable QSet m_dirtyTracks; mutable QCache m_trackCache; ConfigObject * m_pConfig; + + QSqlQuery* m_pQueryTrackLocationInsert; + QSqlQuery* m_pQueryLibraryInsert; }; #endif //TRACKDAO_H === modified file 'mixxx/src/library/libraryscanner.cpp' --- mixxx/src/library/libraryscanner.cpp 2010-12-08 02:59:01 +0000 +++ mixxx/src/library/libraryscanner.cpp 2011-08-03 19:17:29 +0000 @@ -210,37 +210,18 @@ //THIS SHOULD NOT BE IN A TRANSACTION! Each addTrack() call from inside //recursiveScan() handles it's own transactions. - QList tracksToAdd; + m_trackDao.addTracksPrepare(); - bool bScanFinishedCleanly = recursiveScan(m_qLibraryPath, tracksToAdd); + bool bScanFinishedCleanly = recursiveScan(m_qLibraryPath); if (!bScanFinishedCleanly) { qDebug() << "Recursive scan interrupted."; } else { qDebug() << "Recursive scan finished cleanly."; } - /* - * We store the scanned files in the database: Note that the recursiveScan() - * method used TrackCollection::importDirectory() to scan the folders. The - * method TrackCollection::importDirectory() added all the files to the - * 'tracksToAdd' list. - * - * The following statement writes all the scanned tracks in the list to the - * database at once. We don't care if the scan has been cancelled or not. - * - * This new approach accelerates the scanning process massively by a factor - * of 3-4 !!! - */ // Runs inside a transaction - m_trackDao.addTracks(tracksToAdd); - - QMutableListIterator it(tracksToAdd); - while (it.hasNext()) { - TrackInfoObject* pTrack = it.next(); - it.remove(); - delete pTrack; - } + m_trackDao.addTracksFinish(); //Start a transaction for all the library hashing (moved file detection) //stuff. @@ -342,7 +323,7 @@ have already been scanned and have not changed. Changes are tracked by performing a hash of the directory's file list, and those hashes are stored in the database. */ -bool LibraryScanner::recursiveScan(QString dirPath, QList& tracksToAdd) +bool LibraryScanner::recursiveScan(QString dirPath) { QDirIterator fileIt(dirPath, nameFilters, QDir::Files | QDir::NoDotAndDotDot); QString currentFile; @@ -385,7 +366,7 @@ } //Rescan that mofo! - bScanFinishedCleanly = m_pCollection->importDirectory(dirPath, m_trackDao, tracksToAdd); + bScanFinishedCleanly = m_pCollection->importDirectory(dirPath, m_trackDao, nameFilters); } else //prevHash == newHash { @@ -428,7 +409,7 @@ if (m_directoriesBlacklist.contains(nextPath)) continue; - if (!recursiveScan(nextPath, tracksToAdd)) + if (!recursiveScan(nextPath)) bScanFinishedCleanly = false; } === modified file 'mixxx/src/library/libraryscanner.h' --- mixxx/src/library/libraryscanner.h 2010-10-19 01:35:26 +0000 +++ mixxx/src/library/libraryscanner.h 2011-08-03 19:17:29 +0000 @@ -44,7 +44,7 @@ void run(); void scan(QString libraryPath); void scan(); - bool recursiveScan(QString dirPath, QList& tracksToAdd); + bool recursiveScan(QString dirPath); public slots: void cancel(); void resetCancel(); === modified file 'mixxx/src/library/libraryscannerdlg.cpp' --- mixxx/src/library/libraryscannerdlg.cpp 2010-08-16 18:04:27 +0000 +++ mixxx/src/library/libraryscannerdlg.cpp 2011-08-03 19:55:34 +0000 @@ -26,6 +26,8 @@ m_bCancelled = false; m_progress = new QWidget(); + m_progress->setWindowIcon(QIcon(":/images/ic_mixxx_window.png")); + m_layout = new QVBoxLayout(); m_label = new QLabel(tr("It's taking Mixxx a minute to scan your music library, please wait...")); m_layout->addWidget(m_label); === modified file 'mixxx/src/library/trackcollection.cpp' --- mixxx/src/library/trackcollection.cpp 2011-03-24 06:34:54 +0000 +++ mixxx/src/library/trackcollection.cpp 2011-08-07 20:15:47 +0000 @@ -12,7 +12,7 @@ TrackCollection::TrackCollection(ConfigObject* pConfig) : m_pConfig(pConfig), - m_db(QSqlDatabase::addDatabase("QSQLITE")), + m_db(QSqlDatabase::addDatabase("QSQLITE")), // defaultConnection m_playlistDao(m_db), m_cueDao(m_db), m_trackDao(m_db, m_cueDao, pConfig), @@ -42,9 +42,7 @@ TrackCollection::~TrackCollection() { // Save all tracks that haven't been saved yet. - m_trackDao.saveDirtyTracks(); - // TODO(XXX) Maybe fold saveDirtyTracks into TrackDAO::finish now that it - // exists? -- rryan 10/2010 + // and reset played flags m_trackDao.finish(); Q_ASSERT(!m_db.rollback()); //Rollback any uncommitted transaction @@ -91,7 +89,7 @@ QSqlDatabase& TrackCollection::getDatabase() { return m_db; - } +} /** Do a non-recursive import of all the songs in a directory. Does NOT decend into subdirectories. @@ -99,27 +97,16 @@ @return true if the scan completed without being cancelled. False if the scan was cancelled part-way through. */ bool TrackCollection::importDirectory(QString directory, TrackDAO &trackDao, - QList& tracksToAdd) + const QStringList & nameFilters) { //qDebug() << "TrackCollection::importDirectory(" << directory<< ")"; emit(startedLoading()); QFileInfoList files; - //Check to make sure the path exists. - QDir dir(directory); - if (dir.exists()) { - files = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); - } else { - qDebug() << "Error: Import path does not exist." << directory; - return true; - } - - //The directory exists, so get a list of the contents of the directory and go through it. - QList::iterator it = files.begin(); - while (it != files.end()) { - QFileInfo file = *it; //TODO: THIS IS SLOW! - it++; + //get a list of the contents of the directory and go through it. + QDirIterator it(directory, nameFilters, QDir::Files | QDir::NoDotAndDotDot); + while (it.hasNext()) { //If a flag was raised telling us to cancel the library scan then stop. m_libraryScanMutex.lock(); @@ -130,36 +117,29 @@ return false; } - QString absoluteFilePath = file.absoluteFilePath(); - QString fileName = file.fileName(); - - if (fileName.count(m_supportedFileExtensionsRegex)) { - //If the track is in the database, mark it as existing. This code gets exectuted - //when other files in the same directory have changed (the directory hash has changed). - trackDao.markTrackLocationAsVerified(absoluteFilePath); - - // If the file already exists in the database, continue and go on to - // the next file. - - // If the file doesn't already exist in the database, then add - // it. If it does exist in the database, then it is either in the - // user's library OR the user has "removed" the track via - // "Right-Click -> Remove". These tracks stay in the library, but - // their mixxx_deleted column is 1. - if (!trackDao.trackExistsInDatabase(absoluteFilePath)) - { - //qDebug() << "Loading" << file.fileName(); - emit(progressLoading(fileName)); - - // addTrack uses this QFileInfo instead of making a new one now. - //trackDao.addTrack(file); - TrackInfoObject* pTrack = new TrackInfoObject(file.absoluteFilePath()); - tracksToAdd.append(pTrack); - } - } else { - //qDebug() << "Skipping" << file.fileName() << - // "because it did not match thesupported audio files filter:" << - } + QString absoluteFilePath = it.next(); + + //If the track is in the database, mark it as existing. This code gets exectuted + //when other files in the same directory have changed (the directory hash has changed). + trackDao.markTrackLocationAsVerified(absoluteFilePath); + + // If the file already exists in the database, continue and go on to + // the next file. + + // If the file doesn't already exist in the database, then add + // it. If it does exist in the database, then it is either in the + // user's library OR the user has "removed" the track via + // "Right-Click -> Remove". These tracks stay in the library, but + // their mixxx_deleted column is 1. + if (!trackDao.trackExistsInDatabase(absoluteFilePath)) + { + //qDebug() << "Loading" << it.fileName(); + emit(progressLoading(it.fileName())); + + TrackInfoObject* pTrack = new TrackInfoObject(absoluteFilePath); + trackDao.addTracksTrack(pTrack); + delete pTrack; + } } emit(finishedLoading()); return true; === modified file 'mixxx/src/library/trackcollection.h' --- mixxx/src/library/trackcollection.h 2010-09-13 00:42:56 +0000 +++ mixxx/src/library/trackcollection.h 2011-08-03 19:17:29 +0000 @@ -51,7 +51,7 @@ /** Import the files in a given diretory, without recursing into subdirectories */ bool importDirectory(QString directory, TrackDAO &trackDao, - QList& tracksToAdd); + const QStringList & nameFilters); void resetLibaryCancellation(); QSqlDatabase& getDatabase(); === modified file 'mixxx/src/trackinfoobject.cpp' --- mixxx/src/trackinfoobject.cpp 2011-05-06 02:34:38 +0000 +++ mixxx/src/trackinfoobject.cpp 2011-08-07 20:09:30 +0000 @@ -545,11 +545,11 @@ QMutexLocker lock(&m_qMutex); if (bPlayed) { ++m_iTimesPlayed; - setDirty(true); + setDirty(true); } else if (m_bPlayed && !bPlayed) { --m_iTimesPlayed; - setDirty(true); + setDirty(true); } m_bPlayed = bPlayed; } @@ -662,10 +662,12 @@ void TrackInfoObject::setId(int iId) { QMutexLocker lock(&m_qMutex); - bool dirty = m_iId != iId; + // changing the Id does not make the track drity because the Id is always + // generated by the Database itself + //bool dirty = m_iId != iId; m_iId = iId; - if (dirty) - setDirty(true); + //if (dirty) + // setDirty(true); } QVector * TrackInfoObject::getVisualWaveform() { @@ -815,8 +817,10 @@ emit(clean(this)); } // Emit a changed signal regardless if this attempted to set us dirty. - if (bDirty) - emit(changed(this)); + if (bDirty) { + // TODO(XXX) is this still required? + emit(changed(this)); + } //qDebug() << QString("TrackInfoObject %1 %2 set to %3").arg(m_iId).arg(m_sLocation).arg(m_bDirty ? "dirty" : "clean"); }