Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Library scan: log summary and show popup #13427

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
9 changes: 8 additions & 1 deletion src/coreservices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#endif
#include "library/coverartcache.h"
#include "library/library.h"
#include "library/library_decl.h"
#include "library/library_prefs.h"
#include "library/trackcollection.h"
#include "library/trackcollectionmanager.h"
Expand Down Expand Up @@ -443,10 +444,16 @@ void CoreServices::initialize(QApplication* pApp) {
pConfig->set(ConfigKey("[Library]", "SupportedFileExtensions"),
supportedFileSuffixes.join(","));

// Forward the scanner signal so MixxxMainWindow can display a summary popup.
connect(m_pTrackCollectionManager.get(),
&TrackCollectionManager::libraryScanSummary,
this,
&CoreServices::libraryScanSummary,
Qt::UniqueConnection);
// Scan the library directory. Do this after the skinloader has
// loaded a skin, see issue #6625
if (rescan || musicDirAdded || m_pSettingsManager->shouldRescanLibrary()) {
m_pTrackCollectionManager->startLibraryScan();
m_pTrackCollectionManager->startLibraryAutoScan();
}

// This has to be done before m_pSoundManager->setupDevices()
Expand Down
2 changes: 2 additions & 0 deletions src/coreservices.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class TrackCollectionManager;
class Library;
class SkinControls;
class ControlPushButton;
struct LibraryScanResultSummary;

namespace mixxx {

Expand Down Expand Up @@ -105,6 +106,7 @@ class CoreServices : public QObject {

signals:
void initializationProgressUpdate(int progress, const QString& serviceName);
void libraryScanSummary(const LibraryScanResultSummary& result);

public slots:
void slotOptionsKeyboard(bool toggle);
Expand Down
9 changes: 5 additions & 4 deletions src/library/dao/playlistdao.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ QList<TrackId> PlaylistDAO::getTrackIds(const int playlistId) const {
return trackIds;
}

const int trackIdColumn = query.record().indexOf("track_id");
const int trackIdColumn = query.record().indexOf(PLAYLISTTRACKSTABLE_TRACKID);
while (query.next()) {
trackIds.append(TrackId(query.value(trackIdColumn)));
}
Expand Down Expand Up @@ -644,8 +644,9 @@ void PlaylistDAO::removeTracksFromPlaylist(int playlistId, const QList<int>& pos
void PlaylistDAO::removeTracksFromPlaylistInner(int playlistId, int position) {
QSqlQuery query(m_database);
query.prepare(QStringLiteral(
"SELECT track_id FROM PlaylistTracks "
"WHERE playlist_id=:id AND position=:position"));
"SELECT %1 FROM PlaylistTracks "
"WHERE playlist_id=:id AND position=:position")
.arg(PLAYLISTTRACKSTABLE_TRACKID));
query.bindValue(":id", playlistId);
query.bindValue(":position", position);

Expand All @@ -659,7 +660,7 @@ void PlaylistDAO::removeTracksFromPlaylistInner(int playlistId, int position) {
<< position << "in playlist:" << playlistId;
return;
}
TrackId trackId(query.value(query.record().indexOf("track_id")));
TrackId trackId(query.value(query.record().indexOf(PLAYLISTTRACKSTABLE_TRACKID)));

// Delete the track from the playlist.
query.prepare(QStringLiteral(
Expand Down
149 changes: 92 additions & 57 deletions src/library/dao/trackdao.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "library/dao/cuedao.h"
#include "library/dao/libraryhashdao.h"
#include "library/dao/playlistdao.h"
#include "library/dao/trackschema.h"
#include "library/library_prefs.h"
#include "library/queryutil.h"
#include "moc_trackdao.cpp"
Expand Down Expand Up @@ -74,6 +75,15 @@ QString locationPathPrefixFromRootDir(const QDir& rootDir) {
return mixxx::FileInfo(rootDir).location() + '/';
}

QSet<QString> collectTrackLocations(FwdSqlQuery& query) {
QSet<QString> locations;
const int locationColumn = query.record().indexOf(LIBRARYTABLE_LOCATION);
while (query.next()) {
locations.insert(query.fieldValue(locationColumn).toString());
}
return locations;
}

} // anonymous namespace

TrackDAO::TrackDAO(CueDAO& cueDao,
Expand Down Expand Up @@ -159,7 +169,7 @@ TrackId TrackDAO::getTrackIdByLocation(const QString& location) const {
qDebug() << "TrackDAO::getTrackId(): Track location not found in library:" << location;
return {};
}
const auto trackId = TrackId(query.value(query.record().indexOf("id")));
const auto trackId = TrackId(query.value(query.record().indexOf(LIBRARYTABLE_ID)));
DEBUG_ASSERT(trackId.isValid());
return trackId;
}
Expand Down Expand Up @@ -209,7 +219,7 @@ QList<TrackId> TrackDAO::resolveTrackIds(
LOG_FAILED_QUERY(query);
DEBUG_ASSERT(!"Failed query");
}
const int locationColumn = query.record().indexOf("location");
const int locationColumn = query.record().indexOf(LIBRARYTABLE_LOCATION);
while (query.next()) {
QString location = query.value(locationColumn).toString();
addTracksAddFile(location, true);
Expand All @@ -235,7 +245,7 @@ QList<TrackId> TrackDAO::resolveTrackIds(
// "ORDER BY playlist_import.ROWID");

if (query.exec()) {
const int idColumn = query.record().indexOf("id");
const int idColumn = query.record().indexOf(LIBRARYTABLE_ID);
while (query.next()) {
trackIds.append(TrackId(query.value(idColumn)));
}
Expand All @@ -262,20 +272,39 @@ QList<TrackId> TrackDAO::resolveTrackIds(
}

QSet<QString> TrackDAO::getAllTrackLocations() const {
QSet<QString> locations;
QSqlQuery query(m_database);
query.prepare("SELECT track_locations.location FROM track_locations "
"INNER JOIN library on library.location = track_locations.id");
if (!query.exec()) {
FwdSqlQuery query(m_database, QStringLiteral("SELECT track_locations.location "
"FROM track_locations "
"INNER JOIN library "
"ON library.location = track_locations.id"));
VERIFY_OR_DEBUG_ASSERT(!query.hasError() && query.execPrepared()) {
LOG_FAILED_QUERY(query);
DEBUG_ASSERT(!"Failed query");
return {};
}
return collectTrackLocations(query);
}

QSet<QString> TrackDAO::getAllExistingTrackLocations() const {
FwdSqlQuery query(m_database, QStringLiteral("SELECT track_locations.location "
"FROM library INNER JOIN track_locations "
"ON library.location = track_locations.id "
"WHERE fs_deleted=0"));
VERIFY_OR_DEBUG_ASSERT(!query.hasError() && query.execPrepared()) {
LOG_FAILED_QUERY(query);
return {};
}
return collectTrackLocations(query);
}

int locationColumn = query.record().indexOf("location");
while (query.next()) {
locations.insert(query.value(locationColumn).toString());
QSet<QString> TrackDAO::getAllMissingTrackLocations() const {
FwdSqlQuery query(m_database, QStringLiteral("SELECT track_locations.location "
"FROM library INNER JOIN track_locations "
"ON library.location = track_locations.id "
"WHERE fs_deleted=1"));
VERIFY_OR_DEBUG_ASSERT(!query.hasError() && query.execPrepared()) {
LOG_FAILED_QUERY(query);
return {};
}
return locations;
return collectTrackLocations(query);
}

// Some code (eg. drag and drop) needs to just get a track's location, and it's
Expand All @@ -294,7 +323,7 @@ QString TrackDAO::getTrackLocation(TrackId trackId) const {
DEBUG_ASSERT(!"Failed query");
return "";
}
const int locationColumn = query.record().indexOf("location");
const int locationColumn = query.record().indexOf(LIBRARYTABLE_LOCATION);
while (query.next()) {
trackLocation = query.value(locationColumn).toString();
}
Expand Down Expand Up @@ -709,7 +738,9 @@ TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) {
return TrackId();
}
if (m_trackLocationIdColumn == UndefinedRecordIndex) {
m_trackLocationIdColumn = m_pQueryTrackLocationSelect->record().indexOf("id");
m_trackLocationIdColumn =
m_pQueryTrackLocationSelect->record().indexOf(
LIBRARYTABLE_MIXXXDELETED);
}
DbId trackLocationId;
while (m_pQueryTrackLocationSelect->next()) {
Expand All @@ -729,9 +760,9 @@ TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) {
}
if (m_queryLibraryIdColumn == UndefinedRecordIndex) {
QSqlRecord queryLibraryRecord = m_pQueryLibrarySelect->record();
m_queryLibraryIdColumn = queryLibraryRecord.indexOf("id");
m_queryLibraryIdColumn = queryLibraryRecord.indexOf(LIBRARYTABLE_ID);
m_queryLibraryMixxxDeletedColumn =
queryLibraryRecord.indexOf("mixxx_deleted");
queryLibraryRecord.indexOf(LIBRARYTABLE_MIXXXDELETED);
}
while (m_pQueryLibrarySelect->next()) {
// This loop body is executed at most once
Expand Down Expand Up @@ -973,8 +1004,8 @@ QList<TrackRef> TrackDAO::getAllTrackRefs(const QDir& rootDir) const {
}

QList<TrackRef> trackRefs;
const int idColumn = query.record().indexOf("id");
const int locationColumn = query.record().indexOf("location");
const int idColumn = query.record().indexOf(LIBRARYTABLE_MIXXXDELETED);
const int locationColumn = query.record().indexOf(LIBRARYTABLE_LOCATION);
while (query.next()) {
const auto trackId = TrackId(query.value(idColumn));
const auto fileLocation = query.value(locationColumn).toString();
Expand Down Expand Up @@ -1010,8 +1041,8 @@ bool TrackDAO::onPurgingTracks(
}

QSqlRecord queryRecord = query.record();
const int locationColumn = queryRecord.indexOf("location");
const int directoryColumn = queryRecord.indexOf("directory");
const int locationColumn = queryRecord.indexOf(LIBRARYTABLE_LOCATION);
const int directoryColumn = queryRecord.indexOf(TRACKLOCATIONSTABLE_DIRECTORY);
while (query.next()) {
QString filePath = query.record().value(locationColumn).toString();
locations << filePath;
Expand Down Expand Up @@ -1757,7 +1788,7 @@ void TrackDAO::markUnverifiedTracksAsDeleted() {
DEBUG_ASSERT(!"Failed query");
}
while (query.next()) {
trackIds.insert(TrackId(query.value(query.record().indexOf("id"))));
trackIds.insert(TrackId(query.value(query.record().indexOf(LIBRARYTABLE_MIXXXDELETED))));
}
emit tracksRemoved(trackIds);
query.prepare("UPDATE track_locations "
Expand All @@ -1771,27 +1802,27 @@ void TrackDAO::markUnverifiedTracksAsDeleted() {
}

namespace {
// Computed the longest match from the right of both strings
int matchStringSuffix(const QString& str1, const QString& str2) {
int matchLength = 0;
int minLength = math_min(str1.length(), str2.length());
while (matchLength < minLength) {
if (str1[str1.length() - matchLength - 1] != str2[str2.length() - matchLength - 1]) {
// first mismatch
break;
}
++matchLength;
// Computed the longest match from the right of both strings
int matchStringSuffix(const QString& str1, const QString& str2) {
int matchLength = 0;
int minLength = math_min(str1.length(), str2.length());
while (matchLength < minLength) {
if (str1[str1.length() - matchLength - 1] != str2[str2.length() - matchLength - 1]) {
// first mismatch
break;
}
return matchLength;
++matchLength;
}
} // namespace
return matchLength;
}
} // namespace
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only fixing indentation, pre-commit


// 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
// files size exists in the track_locations table. That means the file has
// 'deleted on disk' and see if another track with the same file name and
// duration exists in the track_locations table. That means the file has been
// moved instead of being deleted outright, and so we can salvage your
// existing metadata that you have in your DB (like cue points, etc.).
// returns falls if canceled
// Returns false if canceled.
bool TrackDAO::detectMovedTracks(
QList<RelocatedTrack> *pRelocatedTracks,
const QStringList& addedTracks,
Expand All @@ -1813,34 +1844,38 @@ bool TrackDAO::detectMovedTracks(
// Since duration is stored as double-precision floating-point and since it
// is sometimes truncated to nearest integer, tolerance of 1 second is used.
QSqlQuery newTrackQuery(m_database);
newTrackQuery.prepare(QString(
"SELECT library.id as track_id, track_locations.id as location_id, "
newTrackQuery.prepare(QStringLiteral(
"SELECT library.id as %1, track_locations.id as %2, "
"track_locations.location "
"FROM library INNER JOIN track_locations ON library.location=track_locations.id "
"WHERE track_locations.location IN (%1) AND "
"WHERE track_locations.location IN (%3) AND "
"filename=:filename AND "
"ABS(duration - :duration) < 1 AND "
"fs_deleted=0").arg(
SqlStringFormatter::formatList(m_database, addedTracks)));
"fs_deleted=0")
.arg(
TRACK_ID,
LOCATION_ID,
SqlStringFormatter::formatList(m_database, addedTracks)));

// Query tracks, where we need a successor for
QSqlQuery oldTrackQuery(m_database);
oldTrackQuery.prepare(
"SELECT library.id as track_id, track_locations.id as location_id, "
oldTrackQuery.prepare(QStringLiteral(
"SELECT library.id as %1, track_locations.id as %2, "
"track_locations.location, filename, duration "
"FROM library INNER JOIN track_locations ON library.location=track_locations.id "
"WHERE fs_deleted=1");
"WHERE fs_deleted=1")
.arg(TRACK_ID, LOCATION_ID));
if (!oldTrackQuery.exec()) {
LOG_FAILED_QUERY(oldTrackQuery);
DEBUG_ASSERT(!"Failed query");
return false;
}
QSqlRecord oldTrackQueryRecord = oldTrackQuery.record();
const int oldTrackIdColumn = oldTrackQueryRecord.indexOf("track_id");
const int oldLocationIdColumn = oldTrackQueryRecord.indexOf("location_id");
const int oldLocationColumn = oldTrackQueryRecord.indexOf("location");
const int filenameColumn = oldTrackQueryRecord.indexOf("filename");
const int durationColumn = oldTrackQueryRecord.indexOf("duration");
const int oldTrackIdColumn = oldTrackQueryRecord.indexOf(TRACK_ID);
const int oldLocationIdColumn = oldTrackQueryRecord.indexOf(LOCATION_ID);
const int oldLocationColumn = oldTrackQueryRecord.indexOf(LIBRARYTABLE_LOCATION);
const int filenameColumn = oldTrackQueryRecord.indexOf(TRACKLOCATIONSTABLE_FILENAME);
const int durationColumn = oldTrackQueryRecord.indexOf(LIBRARYTABLE_DURATION);

// For each track that's been "deleted" on disk...
while (oldTrackQuery.next()) {
Expand All @@ -1864,9 +1899,9 @@ bool TrackDAO::detectMovedTracks(
DEBUG_ASSERT(!"Failed query");
continue;
}
const auto newTrackIdColumn = newTrackQuery.record().indexOf("track_id");
const auto newTrackLocationIdColumn = newTrackQuery.record().indexOf("location_id");
const auto newTrackLocationColumn = newTrackQuery.record().indexOf("location");
const auto newTrackIdColumn = newTrackQuery.record().indexOf(TRACK_ID);
const auto newTrackLocationIdColumn = newTrackQuery.record().indexOf(LOCATION_ID);
const auto newTrackLocationColumn = newTrackQuery.record().indexOf(LIBRARYTABLE_LOCATION);
int newTrackLocationSuffixMatch = 0;
TrackId newTrackId;
DbId newTrackLocationId;
Expand All @@ -1883,6 +1918,7 @@ bool TrackDAO::detectMovedTracks(
const auto nextSuffixMatch =
matchStringSuffix(nextTrackLocation, oldTrackLocation);
DEBUG_ASSERT(nextSuffixMatch >= filename.length());
// document this
if (newTrackLocationSuffixMatch < nextSuffixMatch) {
newTrackLocationSuffixMatch = nextSuffixMatch;
newTrackId = TrackId(newTrackQuery.value(newTrackIdColumn));
Expand Down Expand Up @@ -1988,7 +2024,7 @@ void TrackDAO::hideAllTracks(const QDir& rootDir) const {
}

QStringList trackIds;
const int idColumn = query.record().indexOf("id");
const int idColumn = query.record().indexOf(LIBRARYTABLE_MIXXXDELETED);
while (query.next()) {
trackIds.append(TrackId(query.value(idColumn)).toString());
}
Expand Down Expand Up @@ -2027,15 +2063,14 @@ bool TrackDAO::verifyRemainingTracks(
"SET fs_deleted=:fs_deleted, needs_verification=0 "
"WHERE location=:location");

const int locationColumn = query.record().indexOf("location");
const int locationColumn = query.record().indexOf(LIBRARYTABLE_LOCATION);
QString trackLocation;
while (query.next()) {
trackLocation = query.value(locationColumn).toString();
int fs_deleted = 0;
for (const auto& rootDir : libraryRootDirs) {
if (trackLocation.startsWith(rootDir.location())) {
// Track is under the library root,
// but was not verified.
// Track is under the library root, but was not verified.
// This happens if the track was deleted
// a symlink duplicate or on a non normalized
// path like on non case sensitive file systems.
Expand Down
7 changes: 6 additions & 1 deletion src/library/dao/trackdao.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,13 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC
TrackPointer getTrackByRef(
const TrackRef& trackRef) const;

// Returns a set of all track locations in the library.
// Returns a set of all track locations in the library,
// incl. locations of tracks currently marked as missing.
QSet<QString> getAllTrackLocations() const;
// Return only tracks that are reported to exist during last scan.
QSet<QString> getAllExistingTrackLocations() const;
// Return all tracks reported missing during last scan.
QSet<QString> getAllMissingTrackLocations() const;
QString getTrackLocation(TrackId trackId) const;

// Only used by friend class LibraryScanner, but public for testing!
Expand Down
Loading
Loading