=== modified file 'mixxx/src/library/basetrackcache.cpp' --- mixxx/src/library/basetrackcache.cpp 2012-04-09 04:27:00 +0000 +++ mixxx/src/library/basetrackcache.cpp 2012-06-28 09:02:39 +0000 @@ -76,6 +76,13 @@ updateTracksInIndex(trackIds); } +void BaseTrackCache::slotDbTrackAdded(TrackPointer pTrack) { + if (sDebug) { + qDebug() << this << "slotDbTrackAdded"; + } + updateIndexWithTrackpointer(pTrack); +} + void BaseTrackCache::slotTracksRemoved(QSet trackIds) { if (sDebug) { qDebug() << this << "slotTracksRemoved" << trackIds.size(); @@ -129,6 +136,32 @@ return TrackPointer(); } +bool BaseTrackCache::updateIndexWithTrackpointer(TrackPointer pTrack) { + if (sDebug) { + qDebug() << "updateIndexWithTrackpointer:" << pTrack->getFilename(); + } + + if (pTrack.isNull()) { + return false; + } + + int numColumns = columnCount(); + + int id = pTrack->getId(); + + if (id > 0) { + //m_trackInfo[id] will insert a QVector into the + //m_trackInfo HashTable with the key "id" + QVector& record = m_trackInfo[id]; + record.resize(numColumns); + + for (int i = 0; i < numColumns; ++i) { + getTrackValueForColumn(pTrack, i,record[i]); + } + } + return true; +} + bool BaseTrackCache::updateIndexWithQuery(QString queryString) { QTime timer; timer.start(); @@ -156,6 +189,8 @@ while (query.next()) { int id = query.value(idColumn).toInt(); + //m_trackInfo[id] will insert a QVector into the + //m_trackInfo HashTable with the key "id" QVector& record = m_trackInfo[id]; record.resize(numColumns); @@ -222,6 +257,56 @@ emit(tracksChanged(trackIds)); } +void BaseTrackCache::getTrackValueForColumn(TrackPointer pTrack, + int column, + QVariant& track) const { + if (!pTrack || column < 0) { + return; + } + + // TODO(XXX) Qt properties could really help here. + // TODO(rryan) this is all TrackDAO specific. What about iTunes/RB/etc.? + if (fieldIndex(LIBRARYTABLE_ARTIST) == column) { + track = QVariant(pTrack->getArtist()); + } else if (fieldIndex(LIBRARYTABLE_TITLE) == column) { + track = QVariant(pTrack->getTitle()); + } else if (fieldIndex(LIBRARYTABLE_ALBUM) == column) { + track = QVariant(pTrack->getAlbum()); + } else if (fieldIndex(LIBRARYTABLE_YEAR) == column) { + track = QVariant(pTrack->getYear()); + } else if (fieldIndex(LIBRARYTABLE_DATETIMEADDED) == column) { + track = QVariant(pTrack->getDateAdded()); + } else if (fieldIndex(LIBRARYTABLE_GENRE) == column) { + track = QVariant(pTrack->getGenre()); + } else if (fieldIndex(LIBRARYTABLE_COMPOSER) == column) { + track = QVariant(pTrack->getComposer()); + } else if (fieldIndex(LIBRARYTABLE_FILETYPE) == column) { + track = QVariant(pTrack->getType()); + } else if (fieldIndex(LIBRARYTABLE_TRACKNUMBER) == column) { + track = QVariant(pTrack->getTrackNumber()); + } else if (fieldIndex(LIBRARYTABLE_LOCATION) == column) { + track = QVariant(pTrack->getLocation()); + } else if (fieldIndex(LIBRARYTABLE_COMMENT) == column) { + track = QVariant(pTrack->getComment()); + } else if (fieldIndex(LIBRARYTABLE_DURATION) == column) { + track = QVariant(pTrack->getDuration()); + } else if (fieldIndex(LIBRARYTABLE_BITRATE) == column) { + track = QVariant(pTrack->getBitrate()); + } else if (fieldIndex(LIBRARYTABLE_BPM) == column) { + track = QVariant(pTrack->getBpm()); + } else if (fieldIndex(LIBRARYTABLE_PLAYED) == column) { + track = QVariant(pTrack->getPlayed()); + } else if (fieldIndex(LIBRARYTABLE_TIMESPLAYED) == column) { + track = QVariant(pTrack->getTimesPlayed()); + } else if (fieldIndex(LIBRARYTABLE_RATING) == column) { + track = QVariant(pTrack->getRating()); + } else if (fieldIndex(LIBRARYTABLE_KEY) == column) { + track = QVariant(pTrack->getKey()); + } else if (fieldIndex(LIBRARYTABLE_BPM_LOCK) == column) { + track = QVariant(pTrack->hasBpmLock()); + } +} + QVariant BaseTrackCache::getTrackValueForColumn(TrackPointer pTrack, int column) const { if (!pTrack || column < 0) { return QVariant(); === modified file 'mixxx/src/library/basetrackcache.h' --- mixxx/src/library/basetrackcache.h 2012-03-28 03:17:53 +0000 +++ mixxx/src/library/basetrackcache.h 2012-06-28 08:42:47 +0000 @@ -65,13 +65,17 @@ void slotTrackDirty(int trackId); void slotTrackClean(int trackId); void slotTrackChanged(int trackId); + void slotDbTrackAdded(TrackPointer pTrack); private: TrackPointer lookupCachedTrack(int trackId) const; bool updateIndexWithQuery(QString query); + bool updateIndexWithTrackpointer(TrackPointer pTrack); void updateTrackInIndex(int trackId); void updateTracksInIndex(QSet trackIds); QVariant getTrackValueForColumn(TrackPointer pTrack, int column) const; + void getTrackValueForColumn(TrackPointer pTrack, int column, + QVariant& track) const; QString filterClause(QString query, QString extraFilter, QStringList idStrings) const; === modified file 'mixxx/src/library/dao/trackdao.cpp' --- mixxx/src/library/dao/trackdao.cpp 2012-06-21 16:56:05 +0000 +++ mixxx/src/library/dao/trackdao.cpp 2012-06-28 08:56:33 +0000 @@ -11,6 +11,9 @@ #include "track/beats.h" #include "trackinfoobject.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 @@ -30,12 +33,35 @@ m_crateDao(crateDao), m_analysisDao(analysisDao), m_pConfig(pConfig), - m_trackCache(TRACK_CACHE_SIZE) { + m_trackCache(TRACK_CACHE_SIZE), + m_pQueryTrackLocationInsert(NULL), + m_pQueryTrackLocationSelect(NULL), + m_pQueryLibraryInsert(NULL), + m_pQueryLibraryUpdate(NULL), + m_pQueryLibrarySelect(NULL) { +} + +TrackDAO::~TrackDAO() { + qDebug() << "~TrackDAO()"; } void TrackDAO::finish() { // Save all tracks that haven't been saved yet. - saveDirtyTracks(); + QMutexLocker locker(&m_sTracksMutex); + 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 + } + } + + // 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"; @@ -46,10 +72,6 @@ } } -TrackDAO::~TrackDAO() { - qDebug() << "~TrackDAO()"; -} - void TrackDAO::initialize() { qDebug() << "TrackDAO::initialize" << QThread::currentThread() << m_database.connectionName(); } @@ -115,34 +137,25 @@ 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."; - } - - //qDebug() << this << "Dirty tracks before claen save:" << m_dirtyTracks.size(); + //qDebug() << this << "Dirty tracks before clean save:" << m_dirtyTracks.size(); //qDebug() << "TrackDAO::saveTrack. Dirty. Calling update"; updateTrack(pTrack); // Write audio meta data, if enabled in the preferences + // TODO(DSC) Only wite tag if file Metatdate is dirty writeAudioMetaData(pTrack); //qDebug() << this << "Dirty tracks remaining after clean save:" << m_dirtyTracks.size(); } else { //qDebug() << "TrackDAO::saveTrack. Not Dirty"; //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); - } - //qDebug() << "Skipping track update for track" << pTrack->getId(); } } else { @@ -150,21 +163,16 @@ } } -bool TrackDAO::isDirty(int trackId) { - return m_dirtyTracks.contains(trackId); -} - void TrackDAO::slotTrackDirty(TrackInfoObject* pTrack) { //qDebug() << "TrackDAO::slotTrackDirty" << pTrack->getInfo(); // This is a private slot that is connected to TIO's created by this // 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); emit(trackDirty(id)); } } @@ -176,24 +184,32 @@ // 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); emit(trackClean(id)); } } } +void TrackDAO::databaseTrackAdded(TrackPointer pTrack) { + emit(dbTrackAdded(pTrack)); +} + +void TrackDAO::databaseTracksMoved(QSet tracksMovedSetOld, QSet tracksMovedSetNew) { + emit(tracksRemoved(tracksMovedSetNew)); + emit(tracksAdded(tracksMovedSetOld)); // results in a call of BaseTrackCache::updateTracksInIndex(trackIds); +} + void TrackDAO::slotTrackChanged(TrackInfoObject* pTrack) { //qDebug() << "TrackDAO::slotTrackChanged" << pTrack->getInfo(); // This is a private slot that is connected to TIO's created by this // 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) { @@ -208,86 +224,53 @@ // 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) { + // gets called only in addTracksAdd + 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, composer, " - "tracknumber, filetype, location, comment, url, duration, rating, key, " - "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, " - "timesplayed, " - "channels, mixxx_deleted, header_parsed, beats_version, beats_sub_version, beats, bpm_lock) " - "VALUES (:artist, " - ":title, :album, :year, :genre, :composer, :tracknumber, " - ":filetype, :location, :comment, :url, :duration, :rating, :key, " - ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, " - ":timesplayed, " - ":channels, :mixxx_deleted, :header_parsed, :beats_version, :beats_sub_version, :beats, :bpm_lock)"); -} - -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(":composer", pTrack->getComposer()); - 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(":bpm_lock", pTrack->hasBpmLock()? 1 : 0); - - 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) { + // gets called only in addTracksAdd + 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(":composer", pTrack->getComposer()); + 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(":bpm_lock", pTrack->hasBpmLock()? 1 : 0); + + m_pQueryLibraryInsert->bindValue(":replaygain", pTrack->getReplayGain()); + m_pQueryLibraryInsert->bindValue(":key", pTrack->getKey()); // We no longer store the wavesummary in the library table. - query.bindValue(":wavesummaryhex", QVariant(QVariant::ByteArray)); + m_pQueryLibraryInsert->bindValue(":wavesummaryhex", QVariant(QVariant::ByteArray)); - query.bindValue(":timesplayed", pTrack->getTimesPlayed()); + 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 = ""; @@ -301,119 +284,129 @@ 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, bool unremove) { - QSet tracksAddedSet; - QTime time; - time.start(); +void TrackDAO::addTracksPrepare() { + if (m_pQueryLibraryInsert || m_pQueryTrackLocationInsert || + m_pQueryLibrarySelect || m_pQueryTrackLocationSelect) { + qDebug() << "TrackDAO::addTracksPrepare: old Querys have been + "left open, closing them now"; + addTracksFinish(); + } // Start the transaction ScopedTransaction transaction(m_database); - 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); - - QStringList trackLocationIds; - foreach (TrackInfoObject* pTrack, tracksToAdd) { - if (pTrack == NULL || !isTrackFormatSupported(pTrack)) { - // TODO(XXX) provide some kind of error code on a per-track basis. - continue; - } - bindTrackToTrackLocationsInsert(query, pTrack); - - int trackLocationId = -1; - if (!query.exec()) { - qDebug() << "Location " << pTrack->getLocation() << " is already in the DB"; - query_finder.bindValue(":location", pTrack->getLocation()); - - if (!query_finder.exec()) { - // We can't even select this, something is wrong. Skip this - // track -- maybe we'll have luck with others. - LOG_FAILED_QUERY(query_finder) + m_pQueryTrackLocationInsert = new QSqlQuery(m_database); + m_pQueryTrackLocationSelect = new QSqlQuery(m_database); + m_pQueryLibraryInsert = new QSqlQuery(m_database); + m_pQueryLibraryUpdate = new QSqlQuery(m_database); + m_pQueryLibrarySelect = 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_pQueryTrackLocationSelect->prepare("SELECT id FROM track_locations WHERE location=:location"); + + 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_sub_version, beats, bpm_lock) " + "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_sub_version, :beats, :bpm_lock)"); + + m_pQueryLibraryUpdate->prepare("UPDATE library SET mixxx_deleted = 0 " + "WHERE id = :id"); + + m_pQueryLibrarySelect->prepare("SELECT location, id, mixxx_deleted from library " + "WHERE location = :location"); +} + +void TrackDAO::addTracksFinish() { + delete m_pQueryTrackLocationInsert; + delete m_pQueryTrackLocationSelect; + delete m_pQueryLibraryInsert; + delete m_pQueryLibrarySelect; + m_pQueryTrackLocationInsert = NULL; + m_pQueryTrackLocationSelect = NULL; + m_pQueryLibraryInsert = NULL; + m_pQueryLibrarySelect = NULL; + + m_database.commit(); + + emit(tracksAdded(m_tracksAddedSet)); + m_tracksAddedSet.clear(); +} + +bool TrackDAO::addTracksAdd(TrackInfoObject* pTrack, bool unremove) { + + if (!m_pQueryLibraryInsert || !m_pQueryTrackLocationInsert || + !m_pQueryLibrarySelect || !m_pQueryTrackLocationSelect) { + qDebug() << "TrackDAO::addTracksAdd: needed SqlQuerys have not " + "been prepared. Adding no tracks"; + return false; + } + + qDebug() << "TrackDAO::addTracksTrack" << pTrack->getFilename(); + + int trackLocationId = -1; + int trackId = -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 trackLocationId. + + m_pQueryTrackLocationSelect->bindValue(":location", pTrack->getLocation()); + + if (!m_pQueryTrackLocationSelect->exec()) { + // We can't even select this, something is wrong. + LOG_FAILED_QUERY(*m_pQueryTrackLocationSelect) << "Can't find track location ID after failing to insert. Something is wrong."; - continue; - } - while (query_finder.next()) { - trackLocationId = query_finder.value(query_finder.record().indexOf("id")).toInt(); - } + return false; + } + while (m_pQueryTrackLocationSelect->next()) { + trackLocationId = m_pQueryTrackLocationSelect->value(m_pQueryTrackLocationSelect->record().indexOf("id")).toInt(); + } + + m_pQueryLibrarySelect->bindValue(":location", trackLocationId); + if (!m_pQueryLibrarySelect->exec()) { + LOG_FAILED_QUERY(*m_pQueryLibrarySelect) + << "Failed to query existing track: " + << pTrack->getFilename(); } 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); - trackLocationIds.append(QString::number(trackLocationId)); - } - - // Look up pre-existing library records for the track location ids. - QSqlQuery track_lookup(m_database); - // Mapping of track library record id to mixxx_deleted field. - QHash > tracksPresent; - track_lookup.prepare( - QString("SELECT location, id, mixxx_deleted from library WHERE location IN (%1)") - .arg(trackLocationIds.join(","))); - - if (!track_lookup.exec()) { - LOG_FAILED_QUERY(track_lookup) - << "Failed to lookup existing tracks:"; - } else { - QSqlRecord track_lookup_record = track_lookup.record(); - int locationIdColumn = track_lookup_record.indexOf("location"); - int idColumn = track_lookup_record.indexOf("id"); - int mixxxDeletedColumn = track_lookup_record.indexOf("mixxx_deleted"); - while (track_lookup.next()) { - int locationId = track_lookup.value(locationIdColumn).toInt(); - int trackId = track_lookup.value(idColumn).toInt(); - bool removed = track_lookup.value(mixxxDeletedColumn).toBool(); - tracksPresent[locationId] = QPair(trackId, removed); - } - } - - // Major time saver for having this outside the loop - prepareLibraryInsert(query); - - foreach (TrackInfoObject* pTrack, tracksToAdd) { - // Skip tracks that did not make it past the previous part. - if (pTrack == NULL || pTrack->getId() < 0) { - continue; - } - - // Immediately undo the hack we did above so we do not accidentally - // leave the ID incorrectly set. - int locationId = pTrack->getId(); - pTrack->setId(-1); - - // Skip tracks that are already in the database. Optionally unremove - // them. - QHash >::const_iterator it = tracksPresent.find(locationId); - if (it != tracksPresent.end()) { - int trackId = it.value().first; - bool removed = it.value().second; - if (removed && unremove) { - QSqlQuery unremove_query(m_database); - unremove_query.prepare("UPDATE library SET mixxx_deleted = 0 WHERE id = :id"); - unremove_query.bindValue(":id", trackId); - if (!unremove_query.exec()) { - LOG_FAILED_QUERY(unremove_query) - << "Could not unremove track" << trackId; - } else { - tracksAddedSet.insert(trackId); + bool mixxx_deleted = 0; + + while (m_pQueryLibrarySelect->next()) { + trackId = m_pQueryLibrarySelect->value(m_pQueryLibrarySelect->record().indexOf("id")).toInt(); + mixxx_deleted = m_pQueryLibrarySelect->value(m_pQueryLibrarySelect->record().indexOf("mixxx_deleted")).toBool(); + } + if (unremove && mixxx_deleted) { + // Set mixxx_deleted back to 0 + m_pQueryLibraryUpdate->bindValue(":id", trackId); + if (!m_pQueryLibraryUpdate->exec()) { + LOG_FAILED_QUERY(*m_pQueryLibraryUpdate) + << "Failed to unremove existing track: " + << pTrack->getFilename(); } } - // Regardless of whether we unremoved this track or not -- it's // already in the library and so we need to skip it. Set the track's // trackId so the caller can know it. TODO(XXX) this is a little @@ -421,37 +414,40 @@ // and that metadata may differ from what is already in the // database. I'm ignoring this corner case. rryan 10/2011 pTrack->setId(trackId); - continue; } - - bindTrackToLibraryInsert(query, pTrack, locationId); - - if (!query.exec()) { + return false; + } 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); + + bindTrackToLibraryInsert(pTrack, trackLocationId); + + if (!m_pQueryLibraryInsert->exec()) { // We failed to insert the track. Maybe it is already in the library // but marked deleted? Skip this track. - LOG_FAILED_QUERY(query) + LOG_FAILED_QUERY(*m_pQueryLibraryInsert) << "Failed to INSERT new track into library:" << pTrack->getFilename(); - continue; + return false; } - int trackId = query.lastInsertId().toInt(); + trackId = m_pQueryLibraryInsert->lastInsertId().toInt(); pTrack->setId(trackId); m_analysisDao.saveTrackAnalyses(pTrack); m_cueDao.saveTrackCues(trackId, pTrack); pTrack->setDirty(false); - tracksAddedSet.insert(trackId); - } - - transaction.commit(); - - qDebug() << this << "addTracks took" << time.elapsed() << "ms to add" - << tracksAddedSet.size() << "tracks"; - if (tracksAddedSet.size() > 0) { - emit(tracksAdded(tracksAddedSet)); - } + } + m_tracksAddedSet.insert(trackId); + return true; } -int TrackDAO::addTrack(QFileInfo& fileInfo, bool unremove) { +int TrackDAO::addTrack(const QFileInfo& fileInfo, bool unremove) { int trackId = -1; TrackInfoObject * pTrack = new TrackInfoObject(fileInfo); if (pTrack) { @@ -463,41 +459,50 @@ return trackId; } +int TrackDAO::addTrack(const QString& file, bool unremove) { + QFileInfo fileInfo(file); + return addTrack(fileInfo, unremove); +} + +void TrackDAO::addTrack(TrackInfoObject* pTrack, bool unremove) { + //qDebug() << "TrackDAO::addTrack" << QThread::currentThread() << m_database.connectionName(); + //qDebug() << "TrackCollection::addTrack(), inserting into DB"; + Q_ASSERT(pTrack); //Why you be giving me NULL pTracks + + // Check that track is a supported extension. + if (!isTrackFormatSupported(pTrack)) { + // TODO(XXX) provide some kind of error code on a per-track basis. + return; + } + + addTracksPrepare(); + addTracksAdd(pTrack, unremove); + addTracksFinish(); +} + QList TrackDAO::addTracks(QList fileInfoList, bool unremove) { QList trackIDs; + TrackInfoObject* pTrack; + + addTracksPrepare(); //create the list of TrackInfoObjects from the fileInfoList - QList pTrackList; QMutableListIterator it(fileInfoList); while (it.hasNext()) { QFileInfo& info = it.next(); - pTrackList.append(new TrackInfoObject(info)); - } - - addTracks(pTrackList, unremove); - - foreach (TrackInfoObject* pTrack, pTrackList) { + pTrack = new TrackInfoObject(info); + addTracksAdd(pTrack, unremove); int trackID = pTrack->getId(); if (trackID >= 0) { trackIDs.append(trackID); } delete pTrack; } + + addTracksFinish(); return trackIDs; } -int TrackDAO::addTrack(QString absoluteFilePath, bool unremove) -{ - QFileInfo fileInfo(absoluteFilePath); - return addTrack(fileInfo, unremove); -} - -void TrackDAO::addTrack(TrackInfoObject* pTrack, bool unremove) { - QList tracksToAdd; - tracksToAdd.push_back(pTrack); - addTracks(tracksToAdd, unremove); -} - void TrackDAO::hideTracks(QList ids) { QStringList idList; foreach (int id, ids) { @@ -629,122 +634,149 @@ //qDebug() << "Garbage Collecting" << 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 { - //Print out any SQL error, if there was one. - if (query.lastError().isValid()) { - LOG_FAILED_QUERY(query); - } - - 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 composer = query.value(query.record().indexOf("composer")).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(); - 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(); - QDateTime date_created = query.value(query.record().indexOf("datetime_added")).toDateTime(); - bool has_bpm_lock = query.value(query.record().indexOf("bpm_lock")).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(trackId); - pTrack->setArtist(artist); - pTrack->setTitle(title); - pTrack->setAlbum(album); - pTrack->setYear(year); - pTrack->setGenre(genre); - pTrack->setComposer(composer); - 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->setReplayGain(replaygain.toFloat()); - - QString beatsVersion = query.value(query.record().indexOf("beats_version")).toString(); - QString beatsSubVersion = query.value(query.record().indexOf("beats_sub_version")).toString(); - QByteArray beatsBlob = query.value(query.record().indexOf("beats")).toByteArray(); - BeatsPointer pBeats = BeatFactory::loadBeatsFromByteArray(pTrack, beatsVersion, beatsSubVersion, &beatsBlob); - if (pBeats) { - pTrack->setBeats(pBeats); - } else { - pTrack->setBpm(bpm.toFloat()); - } - pTrack->setBpmLock(has_bpm_lock); - - pTrack->setTimesPlayed(timesplayed); - pTrack->setPlayed(played); - pTrack->setChannels(channels); - pTrack->setType(filetype); - pTrack->setLocation(location); - pTrack->setHeaderParsed(header_parsed); - pTrack->setDateAdded(date_created); - pTrack->setCuePoints(m_cueDao.getCuesForTrack(trackId)); - - 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); - - // 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(); - } - - return pTrack; - } +TrackPointer TrackDAO::getTrackFromDB(int id) const { + QTime time; + time.start(); + QSqlQuery query(m_database); + + query.prepare( + "SELECT library.id, artist, title, album, year, genre, composer, tracknumber, " + "filetype, rating, key, track_locations.location as location, " + "track_locations.filesize as filesize, comment, url, duration, bitrate, " + "samplerate, cuepoint, bpm, replaygain, channels, " + "header_parsed, timesplayed, played, beats_version, beats_sub_version, beats, datetime_added, bpm_lock " + "FROM Library " + "INNER JOIN track_locations " + "ON library.location = track_locations.id " + "WHERE library.id=" + QString("%1").arg(id) + ); + + if (query.exec()) { + 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 composer = query.value(query.record().indexOf("composer")).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(); + 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(); + bool has_bpm_lock = query.value(query.record().indexOf("bpm_lock")).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->setComposer(composer); + 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->setReplayGain(replaygain.toFloat()); + + QString beatsVersion = query.value(query.record().indexOf("beats_version")).toString(); + QString beatsSubVersion = query.value(query.record().indexOf("beats_sub_version")).toString(); + QByteArray beatsBlob = query.value(query.record().indexOf("beats")).toByteArray(); + BeatsPointer pBeats = BeatFactory::loadBeatsFromByteArray(pTrack, beatsVersion, beatsSubVersion, &beatsBlob); + if (pBeats) { + pTrack->setBeats(pBeats); + } else { + pTrack->setBpm(bpm.toFloat()); + } + pTrack->setBpmLock(has_bpm_lock); + + 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(); + } + + return pTrack; + } // while (query.next()) + + } else { + LOG_FAILED_QUERY(query) + << QString("getTrack(%1)").arg(id); + } + //qDebug() << "getTrack hit the database, took " << time.elapsed() << "ms"; return TrackPointer(); } @@ -758,8 +790,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; } @@ -768,14 +799,18 @@ // 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)) { + QMutexLocker locker(&m_sTracksMutex); + 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) + if (pTrack) { + // Add pinter to Cache again + m_trackCache.insert(id, new TrackPointer(pTrack)); return pTrack; + } } // The person only wanted the track if it was cached. @@ -784,36 +819,10 @@ return TrackPointer(); } - QTime time; - time.start(); - QSqlQuery query(m_database); - - query.prepare( - "SELECT library.id, artist, title, album, year, genre, composer, tracknumber, " - "filetype, rating, key, track_locations.location as location, " - "track_locations.filesize as filesize, comment, url, duration, bitrate, " - "samplerate, cuepoint, bpm, replaygain, channels, " - "header_parsed, timesplayed, played, beats_version, beats_sub_version, beats, datetime_added, bpm_lock " - "FROM Library " - "INNER JOIN track_locations " - "ON library.location = track_locations.id " - "WHERE library.id=:track_id"); - query.bindValue(":track_id", id); - - TrackPointer pTrack; - - if (query.exec()) { - pTrack = getTrackFromDB(query); - } else { - LOG_FAILED_QUERY(query) - << QString("getTrack(%1)").arg(id); - } - //qDebug() << "getTrack hit the database, took " << time.elapsed() << "ms"; - - return pTrack; + return getTrackFromDB(id); } -/** Saves a track's info back to the database */ +// Saves a track's info back to the database void TrackDAO::updateTrack(TrackInfoObject* pTrack) { ScopedTransaction transaction(m_database); QTime time; @@ -908,9 +917,9 @@ //qDebug() << "Dirtying track took: " << time.elapsed() << "ms"; } -/** Mark all the tracks whose paths begin with libraryPath as invalid. - That means we'll need to later check that those tracks actually - (still) exist as part of the library scanning procedure. */ +// Mark all the tracks whose paths begin with libraryPath as invalid. +// That means we'll need to later check that those tracks actually +// (still) exist as part of the library scanning procedure. void TrackDAO::invalidateTrackLocationsInLibrary(QString libraryPath) { //qDebug() << "TrackDAO::invalidateTrackLocations" << QThread::currentThread() << m_database.connectionName(); //qDebug() << "invalidateTrackLocations(" << libraryPath << ")"; @@ -993,17 +1002,18 @@ } } -/** Look for moved files. Look for files that have been marked as "deleted on disk" - and see if another "file" with the same name and filesize exists in the track_locations - table. That means the file has moved instead of being deleted outright, and so - we can salvage your existing metadata that you have in your DB (like cue points, etc.). */ -void TrackDAO::detectMovedFiles() { +// Look for moved files. Look for files that have been marked as "deleted on disk" +// and see if another "file" with the same name and filesize exists in the track_locations +// table. That means the file has moved instead of being deleted outright, and so +// we can salvage your existing metadata that you have in your DB (like cue points, etc.). +void TrackDAO::detectMovedFiles(QSet* pTracksMovedSetOld, QSet* pTracksMovedSetNew) { //This function should not start a transaction on it's own! //When it's called from libraryscanner.cpp, there already is a transaction //started! QSqlQuery query(m_database); QSqlQuery query2(m_database); + QSqlQuery query3(m_database); int oldTrackLocationId = -1; int newTrackLocationId = -1; QString filename; @@ -1015,6 +1025,11 @@ LOG_FAILED_QUERY(query); } + query2.prepare("SELECT id FROM track_locations WHERE " + "fs_deleted=0 AND " + "filename=:filename AND " + "filesize=:filesize"); + //For each track that's been "deleted" on disk... while (query.next()) { newTrackLocationId = -1; //Reset this var @@ -1022,10 +1037,6 @@ filename = query.value(query.record().indexOf("filename")).toString(); fileSize = query.value(query.record().indexOf("filesize")).toInt(); - query2.prepare("SELECT id FROM track_locations WHERE " - "fs_deleted=0 AND " - "filename=:filename AND " - "filesize=:filesize"); query2.bindValue(":filename", filename); query2.bindValue(":filesize", fileSize); Q_ASSERT(query2.exec()); @@ -1042,34 +1053,60 @@ qDebug() << "Found moved track!" << filename; //Remove old row from track_locations table - query2.prepare("DELETE FROM track_locations WHERE " + query3.prepare("DELETE FROM track_locations WHERE " "id=:id"); - query2.bindValue(":id", oldTrackLocationId); - Q_ASSERT(query2.exec()); + query3.bindValue(":id", oldTrackLocationId); + Q_ASSERT(query3.exec()); //The library scanner will have added a new row to the Library //table which corresponds to the track in the new location. We need //to remove that so we don't end up with two rows in the library table //for the same track. - query2.prepare("DELETE FROM library WHERE " + query3.prepare("SELECT id FROM library WHERE " "location=:location"); - query2.bindValue(":location", newTrackLocationId); - Q_ASSERT(query2.exec()); + query3.bindValue(":location", newTrackLocationId); + Q_ASSERT(query3.exec()); + + while (query3.next()) + { + int newTrackId = query3.value(query3.record().indexOf("id")).toInt(); + query3.prepare("DELETE FROM library WHERE " + "id=:newid"); + query3.bindValue(":newid", newTrackLocationId); + Q_ASSERT(query3.exec()); + + // We collect all the new tracks the where added to BaseTrackCache as well + pTracksMovedSetNew->insert(newTrackId); + } //Update the location foreign key for the existing row in the library table //to point to the correct row in the track_locations table. - query2.prepare("UPDATE library " - "SET location=:newloc WHERE location=:oldloc"); - query2.bindValue(":newloc", newTrackLocationId); - query2.bindValue(":oldloc", oldTrackLocationId); - Q_ASSERT(query2.exec()); + query3.prepare("SELECT id FROM library WHERE " + "location=:location"); + query3.bindValue(":location", oldTrackLocationId); + Q_ASSERT(query3.exec()); + + + while (query3.next()) + { + int oldTrackId = query3.value(query3.record().indexOf("id")).toInt(); + + query3.prepare("UPDATE library " + "SET location=:newloc WHERE id=:oldid"); + query3.bindValue(":newloc", newTrackLocationId); + query3.bindValue(":oldid", oldTrackId); + Q_ASSERT(query3.exec()); + + // We collect all the old tracks that has to be updated in BaseTrackCache as well + pTracksMovedSetOld->insert(oldTrackId); + } } } } void TrackDAO::clearCache() { m_trackCache.clear(); - m_dirtyTracks.clear(); + //m_dirtyTracks.clear(); } void TrackDAO::writeAudioMetaData(TrackInfoObject* pTrack){ @@ -1090,5 +1127,8 @@ } bool TrackDAO::isTrackFormatSupported(TrackInfoObject* pTrack) const { - return SoundSourceProxy::isFilenameSupported(pTrack->getFilename()); + if (pTrack) { + return SoundSourceProxy::isFilenameSupported(pTrack->getFilename()); + } + return false; } === modified file 'mixxx/src/library/dao/trackdao.h' --- mixxx/src/library/dao/trackdao.h 2012-06-20 15:36:38 +0000 +++ mixxx/src/library/dao/trackdao.h 2012-06-28 08:54:19 +0000 @@ -60,8 +60,8 @@ class TrackDAO : public QObject, public virtual DAO { Q_OBJECT public: - /** The 'config object' is necessary because users decide ID3 tags get - * synchronized on track metadata change **/ + // The 'config object' is necessary because users decide ID3 tags get + // synchronized on track metadata change TrackDAO(QSqlDatabase& database, CueDAO& cueDao, PlaylistDAO& playlistDao, CrateDAO& crateDao, AnalysisDao& analysisDao, @@ -75,9 +75,11 @@ int getTrackId(QString absoluteFilePath); bool trackExistsInDatabase(QString absoluteFilePath); QString getTrackLocation(int id); - int addTrack(QString absoluteFilePath, bool unremove); - int addTrack(QFileInfo& fileInfo, bool unremove); - void addTracks(QList tracksToAdd, bool unremove); + int addTrack(const QString& file, bool unremove); + int addTrack(const QFileInfo& fileInfo, bool unremove); + void addTracksPrepare(); + bool addTracksAdd(TrackInfoObject* pTrack, bool unremove); + void addTracksFinish(); QList addTracks(QList fileInfoList, bool unremove); void hideTracks(QList ids); void purgeTracks(QList ids); @@ -91,7 +93,9 @@ void invalidateTrackLocationsInLibrary(QString libraryPath); void markUnverifiedTracksAsDeleted(); void markTrackLocationsAsDeleted(QString directory); - void detectMovedFiles(); + void detectMovedFiles(QSet* pTracksMovedSetNew, QSet* pTracksMovedSetOld); + void databaseTrackAdded(TrackPointer pTrack); + void databaseTracksMoved(QSet tracksMovedSetOld, QSet tracksMovedSetNew); signals: void trackDirty(int trackId); @@ -99,6 +103,7 @@ void trackChanged(int trackId); void tracksAdded(QSet trackIds); void tracksRemoved(QSet trackIds); + void dbTrackAdded(TrackPointer pTrack); public slots: // The public interface to the TrackDAO requires a TrackPointer so that we @@ -107,11 +112,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 @@ -129,14 +129,11 @@ void saveTrack(TrackInfoObject* pTrack); void updateTrack(TrackInfoObject* pTrack); void addTrack(TrackInfoObject* pTrack, bool unremove); - 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 @@ -148,10 +145,18 @@ CrateDAO &m_crateDao; AnalysisDao& m_analysisDao; ConfigObject * m_pConfig; - mutable QHash m_tracks; - mutable QSet m_dirtyTracks; + static QHash m_sTracks; + static QMutex m_sTracksMutex; mutable QCache m_trackCache; + QSqlQuery* m_pQueryTrackLocationInsert; + QSqlQuery* m_pQueryTrackLocationSelect; + QSqlQuery* m_pQueryLibraryInsert; + QSqlQuery* m_pQueryLibraryUpdate; + QSqlQuery* m_pQueryLibrarySelect; + + QSet m_tracksAddedSet; + DISALLOW_COPY_AND_ASSIGN(TrackDAO); }; === modified file 'mixxx/src/library/libraryscanner.cpp' --- mixxx/src/library/libraryscanner.cpp 2012-06-20 15:36:38 +0000 +++ mixxx/src/library/libraryscanner.cpp 2012-06-28 07:56:19 +0000 @@ -147,17 +147,13 @@ //Lower our priority to help not grind crappy computers. setPriority(QThread::LowPriority); + qRegisterMetaType >("QSet"); if (!m_database.isValid()) { - m_database = QSqlDatabase::addDatabase("QSQLITE", "LIBRARY_SCANNER"); + m_database = QSqlDatabase::cloneDatabase(m_pCollection->getDatabase(), "LIBRARY_SCANNER"); } if (!m_database.isOpen()) { - m_database.setHostName("localhost"); - m_database.setDatabaseName(MIXXX_DB_PATH); - m_database.setUserName("mixxx"); - m_database.setPassword("mixxx"); - //Open the database connection in this thread. if (!m_database.open()) { qDebug() << "Failed to open database from library scanner thread." << m_database.lastError(); @@ -185,6 +181,8 @@ //Try to upgrade the library from 1.7 (XML) to 1.8+ (DB) if needed. If the //upgrade_filename already exists, then do not try to upgrade since we have //already done it. + // TODO(XXX) SETTINGS_PATH may change in new Mixxx Versions. Here we need + // the SETTINGS_PATH from Mixxx V <= 1.7 QString upgrade_filename = QDir::homePath().append("/").append(SETTINGS_PATH).append("DBUPGRADED"); qDebug() << "upgrade filename is " << upgrade_filename; QFile upgradefile(upgrade_filename); @@ -220,50 +218,38 @@ qDebug() << "Recursively scanning library."; //Start scanning the library. - //THIS SHOULD NOT BE IN A TRANSACTION! Each addTrack() call from inside - //recursiveScan() handles it's own transactions. + // THIS SHOULD NOT BE IN A TRANSACTION! Each addTrack() call from inside + // recursiveScan() handles it's own transactions. - QList tracksToAdd; + // this will prepare some querys in TrackDAO, this needs be done because + // BaseTrackCache and TrackCollection will call TrackDAO::addTracksAdd + // and this function needs the querys + m_trackDao.addTracksPrepare(); QStringList verifiedDirectories; - bool bScanFinishedCleanly = recursiveScan(m_qLibraryPath, tracksToAdd, verifiedDirectories); + bool bScanFinishedCleanly = recursiveScan(m_qLibraryPath, verifiedDirectories); 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. Do not unremove files. - m_trackDao.addTracks(tracksToAdd, false); - - QMutableListIterator it(tracksToAdd); - while (it.hasNext()) { - TrackInfoObject* pTrack = it.next(); - it.remove(); - delete pTrack; - } + + // Runs inside a transaction + m_trackDao.addTracksFinish(); // Start a transaction for all the library hashing (moved file detection) // stuff. ScopedTransaction transaction(m_database); - //At the end of a scan, mark all tracks and directories that - //weren't "verified" as "deleted" (as long as the scan wasn't cancelled - //half way through. This condition is important because our rescanning - //algorithm starts by marking all tracks and dirs as unverified, so a - //cancelled scan might leave half of your library as unverified. Don't - //want to mark those tracks/dirs as deleted in that case) :) + // At the end of a scan, mark all tracks and directories that + // weren't "verified" as "deleted" (as long as the scan wasn't canceled + // half way through. This condition is important because our rescanning + // algorithm starts by marking all tracks and dirs as unverified, so a + // canceled scan might leave half of your library as unverified. Don't + // want to mark those tracks/dirs as deleted in that case) :) + QSet tracksMovedSetOld; + QSet tracksMovedSetNew; if (bScanFinishedCleanly) { qDebug() << "Marking unchanged directories and tracks as verified"; m_libraryHashDao.updateDirectoryStatuses(verifiedDirectories, false, true); @@ -277,7 +263,7 @@ //Check to see if the "deleted" tracks showed up in another location, //and if so, do some magic to update all our tables. qDebug() << "Detecting moved files."; - m_trackDao.detectMovedFiles(); + m_trackDao.detectMovedFiles(&tracksMovedSetOld, &tracksMovedSetNew); //Remove the hashes for any directories that have been //marked as deleted to clean up. We need to do this otherwise @@ -287,8 +273,7 @@ transaction.commit(); qDebug() << "Scan finished cleanly"; - } - else { + } else { transaction.rollback(); qDebug() << "Scan cancelled"; } @@ -298,6 +283,9 @@ //m_pProgress->slotStopTiming(); m_database.close(); + // Update BaseTrackCache via the main TrackDao + m_pCollection->getTrackDAO().databaseTracksMoved(tracksMovedSetOld, tracksMovedSetNew); + resetCancel(); emit(scanFinished()); } @@ -349,12 +337,10 @@ start(); //Starts the thread by calling run() } -/** Recursively scan a music library. Doesn't import tracks for any directories that - 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, QStringList& verifiedDirectories) -{ +// Recursively scan a music library. Doesn't import tracks for any directories that +// 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, QStringList& verifiedDirectories) { QDirIterator fileIt(dirPath, m_nameFilters, QDir::Files | QDir::NoDotAndDotDot); QString currentFile; bool bScanFinishedCleanly = true; @@ -365,7 +351,7 @@ bool prevHashExists = false; int newHash = -1; int prevHash = -1; - //Note: A hash of "0" is a real hash if the directory contains no files! + // Note: A hash of "0" is a real hash if the directory contains no files! while (fileIt.hasNext()) { @@ -374,14 +360,14 @@ newHashStr += currentFile; } - //Calculate a hash of the directory's file list. + // Calculate a hash of the directory's file list. newHash = qHash(newHashStr); - //Try to retrieve a hash from the last time that directory was scanned. + // Try to retrieve a hash from the last time that directory was scanned. prevHash = m_libraryHashDao.getDirectoryHash(dirPath); prevHashExists = (prevHash == -1) ? false : true; - //Compare the hashes, and if they don't match, rescan the files in that directory! + // Compare the hashes, and if they don't match, rescan the files in that directory! if (prevHash != newHash) { //If we didn't know about this directory before... if (!prevHashExists) { @@ -393,8 +379,8 @@ m_libraryHashDao.updateDirectoryHash(dirPath, newHash, 0); } - //Rescan that mofo! - bScanFinishedCleanly = m_pCollection->importDirectory(dirPath, m_trackDao, tracksToAdd); + // Rescan that mofo! + bScanFinishedCleanly = m_pCollection->importDirectory(dirPath, m_trackDao, m_nameFilters); } else { //prevHash == newHash // Add the directory to the verifiedDirectories list, so that later they // (and the tracks inside them) will be marked as verified @@ -411,19 +397,20 @@ return false; } - // Look at all the subdirectories and scan them recursively... QDirIterator dirIt(dirPath, QDir::Dirs | QDir::NoDotAndDotDot); while (dirIt.hasNext() && bScanFinishedCleanly) { QString nextPath = dirIt.next(); + //qDebug() << "nextPath: " << nextPath; // Skip the iTunes Album Art Folder since it is probably a waste of // time. if (m_directoriesBlacklist.contains(nextPath)) continue; - if (!recursiveScan(nextPath, tracksToAdd, verifiedDirectories)) + if (!recursiveScan(nextPath, verifiedDirectories)) { bScanFinishedCleanly = false; + } } return bScanFinishedCleanly; === modified file 'mixxx/src/library/libraryscanner.h' --- mixxx/src/library/libraryscanner.h 2012-06-20 15:36:38 +0000 +++ mixxx/src/library/libraryscanner.h 2012-06-28 07:56:19 +0000 @@ -46,7 +46,7 @@ void run(); void scan(QString libraryPath); void scan(); - bool recursiveScan(QString dirPath, QList& tracksToAdd, QStringList& verifiedDirectories); + bool recursiveScan(QString dirPath, QStringList& verifiedDirectories); public slots: void cancel(); void resetCancel(); @@ -54,11 +54,11 @@ void scanFinished(); void progressHashing(QString); private: - TrackCollection* m_pCollection; //The library trackcollection - QSqlDatabase m_database; /**Hang on to a different DB connection - since we run in a different thread */ - QString m_qLibraryPath; //The path to the library on disk - LibraryScannerDlg* m_pProgress; //The library scanning window + TrackCollection* m_pCollection; // The library trackcollection + QSqlDatabase m_database; // Hang on to a different DB connection + // since we run in a different thread */ + QString m_qLibraryPath; // The path to the library on disk + LibraryScannerDlg* m_pProgress; // The library scanning window LibraryHashDAO m_libraryHashDao; CueDAO m_cueDao; === modified file 'mixxx/src/library/libraryscannerdlg.cpp' --- mixxx/src/library/libraryscannerdlg.cpp 2011-11-13 21:11:47 +0000 +++ mixxx/src/library/libraryscannerdlg.cpp 2012-06-28 07:56:19 +0000 @@ -26,6 +26,8 @@ { m_bCancelled = false; + setWindowIcon(QIcon(":/images/ic_mixxx_window.png")); + QVBoxLayout* pLayout = new QVBoxLayout(this); setWindowTitle(tr("Library Scanner")); === modified file 'mixxx/src/library/mixxxlibraryfeature.cpp' --- mixxx/src/library/mixxxlibraryfeature.cpp 2012-06-20 17:19:07 +0000 +++ mixxx/src/library/mixxxlibraryfeature.cpp 2012-06-28 07:56:19 +0000 @@ -76,6 +76,8 @@ pBaseTrackCache, SLOT(slotTracksAdded(QSet))); connect(&pTrackCollection->getTrackDAO(), SIGNAL(tracksRemoved(QSet)), pBaseTrackCache, SLOT(slotTracksRemoved(QSet))); + connect(&pTrackCollection->getTrackDAO(), SIGNAL(dbTrackAdded(TrackPointer)), + pBaseTrackCache, SLOT(slotDbTrackAdded(TrackPointer))); m_pBaseTrackCache = QSharedPointer(pBaseTrackCache); pTrackCollection->addTrackSource(QString("default"), m_pBaseTrackCache); @@ -116,9 +118,6 @@ void MixxxLibraryFeature::refreshLibraryModels() { - if (m_pBaseTrackCache) { - m_pBaseTrackCache->buildIndex(); - } if (m_pLibraryTableModel) { m_pLibraryTableModel->select(); } @@ -184,5 +183,5 @@ void MixxxLibraryFeature::onLazyChildExpandation(const QModelIndex &index){ Q_UNUSED(index); -//Nothing to do because the childmodel is not of lazy nature. + // Nothing to do because the childmodel is not of lazy nature. } === modified file 'mixxx/src/library/queryutil.h' --- mixxx/src/library/queryutil.h 2012-05-09 02:34:21 +0000 +++ mixxx/src/library/queryutil.h 2012-06-28 07:56:19 +0000 @@ -5,7 +5,7 @@ #include #define LOG_FAILED_QUERY(query) qDebug() << __FILE__ << __LINE__ << "FAILED QUERY [" \ - << query.executedQuery() << "]" << query.lastError() + << (query).executedQuery() << "]" << (query).lastError() class ScopedTransaction { public: === modified file 'mixxx/src/library/trackcollection.cpp' --- mixxx/src/library/trackcollection.cpp 2012-06-21 17:06:07 +0000 +++ mixxx/src/library/trackcollection.cpp 2012-06-28 07:56:19 +0000 @@ -14,15 +14,15 @@ TrackCollection::TrackCollection(ConfigObject* pConfig) : m_pConfig(pConfig), - m_db(QSqlDatabase::addDatabase("QSQLITE")), // defaultConnection - m_playlistDao(m_db), - m_crateDao(m_db), - m_cueDao(m_db), - m_analysisDao(m_db), - m_trackDao(m_db, m_cueDao, m_playlistDao, m_crateDao, m_analysisDao, pConfig), - m_supportedFileExtensionsRegex( - SoundSourceProxy::supportedFileExtensionsRegex(), - Qt::CaseInsensitive) { + m_db(QSqlDatabase::addDatabase("QSQLITE")), // defaultConnection + m_playlistDao(m_db), + m_crateDao(m_db), + m_cueDao(m_db), + m_analysisDao(m_db), + m_trackDao(m_db, m_cueDao, m_playlistDao, m_crateDao, m_analysisDao, pConfig), + m_supportedFileExtensionsRegex( + SoundSourceProxy::supportedFileExtensionsRegex(), + Qt::CaseInsensitive) { bCancelLibraryScan = false; qDebug() << "Available QtSQL drivers:" << QSqlDatabase::drivers(); @@ -31,7 +31,7 @@ m_db.setUserName("mixxx"); m_db.setPassword("mixxx"); bool ok = m_db.open(); - qDebug() << __FILE__ << "DB status:" << ok; + qDebug() << "DB status:" << m_db.databaseName() << "=" << ok; if (m_db.lastError().isValid()) { qDebug() << "Error loading database:" << m_db.lastError(); } @@ -52,23 +52,23 @@ // transaction somewhere that should be. if (m_db.rollback()) { qDebug() << "ERROR: There was a transaction in progress on the main database connection while shutting down." - << "There is a logic error somewhere."; + << "There is a logic error somewhere."; } m_db.close(); } else { qDebug() << "ERROR: The main database connection was closed before TrackCollection closed it." - << "There is a logic error somewhere."; + << "There is a logic error somewhere."; } } bool TrackCollection::checkForTables() { if (!m_db.open()) { QMessageBox::critical(0, tr("Cannot open database"), - tr("Unable to establish a database connection.\n" - "Mixxx requires QT with SQLite support. Please read " - "the Qt SQL driver documentation for information on how " - "to build it.\n\n" - "Click OK to exit."), QMessageBox::Ok); + tr("Unable to establish a database connection.\n" + "Mixxx requires QT with SQLite support. Please read " + "the Qt SQL driver documentation for information on how " + "to build it.\n\n" + "Click OK to exit."), QMessageBox::Ok); return false; } @@ -83,23 +83,23 @@ if (result < 0) { if (result == -1) { QMessageBox::warning(0, upgradeFailed, - upgradeToVersionFailed + "\n" + - tr("Your %1 file may be outdated.").arg(schemaFilename) + - "\n\n" + okToExit, - QMessageBox::Ok); + upgradeToVersionFailed + "\n" + + tr("Your %1 file may be outdated.").arg(schemaFilename) + + "\n\n" + okToExit, + QMessageBox::Ok); } else if (result == -2) { QMessageBox::warning(0, upgradeFailed, - upgradeToVersionFailed + "\n" + - tr("Your mixxxdb.sqlite file may be corrupt.") + "\n" + - tr("Try renaming it and restarting Mixxx.") + - "\n\n" + okToExit, - QMessageBox::Ok); + upgradeToVersionFailed + "\n" + + tr("Your mixxxdb.sqlite file may be corrupt.") + "\n" + + tr("Try renaming it and restarting Mixxx.") + + "\n\n" + okToExit, + QMessageBox::Ok); } else { // -3 QMessageBox::warning(0, upgradeFailed, - upgradeToVersionFailed + "\n" + - tr("Your %1 file may be missing or invalid.").arg(schemaFilename) + - "\n\n" + okToExit, - QMessageBox::Ok); + upgradeToVersionFailed + "\n" + + tr("Your %1 file may be missing or invalid.").arg(schemaFilename) + + "\n\n" + okToExit, + QMessageBox::Ok); } return false; } @@ -121,26 +121,15 @@ @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(); @@ -150,35 +139,36 @@ 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); + 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())); + + TrackPointer pTrack =TrackPointer(new TrackInfoObject( + absoluteFilePath), &QObject::deleteLater); + + if (trackDao.addTracksAdd(pTrack.data(), false)) { + // Successful added + // signal the main instance of TrackDao, that there is a + // new Track in the database + m_trackDao.databaseTrackAdded(pTrack); + } else { + qDebug() << "Track ("+absoluteFilePath+") could not be added"; } - } else { - //qDebug() << "Skipping" << file.fileName() << - // "because it did not match thesupported audio files filter:" << } } emit(finishedLoading()); === modified file 'mixxx/src/library/trackcollection.h' --- mixxx/src/library/trackcollection.h 2012-05-20 20:13:07 +0000 +++ mixxx/src/library/trackcollection.h 2012-06-28 07:56:19 +0000 @@ -53,7 +53,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 2012-05-22 20:48:21 +0000 +++ mixxx/src/trackinfoobject.cpp 2012-06-28 07:56:19 +0000 @@ -42,7 +42,7 @@ m_waveformSummary = new Waveform; } -TrackInfoObject::TrackInfoObject(QFileInfo& fileInfo, bool parseHeader) +TrackInfoObject::TrackInfoObject(const QFileInfo& fileInfo, bool parseHeader) : m_qMutex(QMutex::Recursive) { populateLocation(fileInfo); initialize(parseHeader); @@ -102,7 +102,7 @@ m_waveformSummary = new Waveform; } -void TrackInfoObject::populateLocation(QFileInfo& fileInfo) { +void TrackInfoObject::populateLocation(const QFileInfo& fileInfo) { m_sFilename = fileInfo.fileName(); m_sLocation = fileInfo.absoluteFilePath(); m_sDirectory = fileInfo.absolutePath(); @@ -187,8 +187,6 @@ } -static void doNothing(TrackInfoObject* pTrack) {} - int TrackInfoObject::parse() { // Add basic information derived from the filename: @@ -701,10 +699,9 @@ 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 m_iId = iId; - if (dirty) - setDirty(true); } === modified file 'mixxx/src/trackinfoobject.h' --- mixxx/src/trackinfoobject.h 2012-05-22 20:48:21 +0000 +++ mixxx/src/trackinfoobject.h 2012-06-28 07:56:19 +0000 @@ -54,8 +54,8 @@ /** Initialize a new track with the filename. */ TrackInfoObject(const QString sLocation="", bool parseHeader=true); // Initialize track with a QFileInfo class - TrackInfoObject(QFileInfo& fileInfo, bool parseHeader=true); - /** Creates a new track given information from the xml file. */ + TrackInfoObject(const QFileInfo& fileInfo, bool parseHeader=true); + // Creates a new track given information from the xml file. TrackInfoObject(const QDomNode &); virtual ~TrackInfoObject(); @@ -272,7 +272,7 @@ void initialize(bool parseHeader); // Initialize all the location variables. - void populateLocation(QFileInfo& fileInfo); + void populateLocation(const QFileInfo& fileInfo); // Method for parsing information from knowing only the file name. It // assumes that the filename is written like: "artist - trackname.xxx"