diff --git a/build/depends.py b/build/depends.py index f99d675b682..7c6be7f9951 100644 --- a/build/depends.py +++ b/build/depends.py @@ -957,6 +957,7 @@ def sources(self, build): "track/tracknumbers.cpp", "track/trackmetadata.cpp", "track/trackmetadatataglib.cpp", + "track/trackref.cpp", "mixer/auxiliary.cpp", "mixer/baseplayer.cpp", diff --git a/src/library/scanner/importfilestask.cpp b/src/library/scanner/importfilestask.cpp index 1370c2be4da..df770c250c2 100644 --- a/src/library/scanner/importfilestask.cpp +++ b/src/library/scanner/importfilestask.cpp @@ -1,6 +1,7 @@ #include "library/scanner/importfilestask.h" #include "library/scanner/libraryscanner.h" +#include "track/trackref.h" #include "util/timer.h" ImportFilesTask::ImportFilesTask(LibraryScanner* pScanner, @@ -29,27 +30,27 @@ void ImportFilesTask::run() { return; } - const QString filePath(fileInfo.filePath()); - //qDebug() << "ImportFilesTask::run" << filePath; + const QString trackLocation(TrackRef::location(fileInfo)); + //qDebug() << "ImportFilesTask::run" << trackLocation; // If the file does not exist in the database then add it. If it // does 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 (m_scannerGlobal->trackExistsInDatabase(filePath)) { + if (m_scannerGlobal->trackExistsInDatabase(trackLocation)) { // If the track is in the database, mark it as existing. This code gets // executed when other files in the same directory have changed (the // directory hash has changed). - emit(trackExists(filePath)); + emit(trackExists(trackLocation)); } else { if (!fileInfo.exists()) { qWarning() << "ImportFilesTask: Skipping inaccessible file" - << filePath; + << trackLocation; continue; } - qDebug() << "Importing track" << filePath; + qDebug() << "Importing track" << trackLocation; - emit(addNewTrack(filePath)); + emit(addNewTrack(trackLocation)); } } // Insert or update the hash in the database. diff --git a/src/test/trackreftest.cpp b/src/test/trackreftest.cpp new file mode 100644 index 00000000000..e3d248b4eea --- /dev/null +++ b/src/test/trackreftest.cpp @@ -0,0 +1,82 @@ +#include + +#include +#include +#include + +#include "track/trackref.h" + +namespace { + +class TrackRefTest : public testing::Test { + protected: + + TrackRefTest() + : m_tempFile("TrackRefTest.tmp"), + m_tempFileDir(QDir::tempPath()), + m_tempFileName(m_tempFile.fileName()), + m_tempFileInfo(m_tempFileDir, m_tempFileName), + m_validTrackId(123) { + } + + virtual void SetUp() { + ASSERT_TRUE(m_validTrackId.isValid()); + ASSERT_FALSE(m_invalidTrackId.isValid()); + } + + virtual void TearDown() { + } + + static void verifyFileInfo(const TrackRef& actual, const QFileInfo& fileInfo) { + EXPECT_TRUE(actual.hasLocation()); + EXPECT_EQ(TrackRef::location(fileInfo), actual.getLocation()); + EXPECT_TRUE(actual.hasCanonicalLocation()); + EXPECT_EQ(TrackRef::canonicalLocation(fileInfo), actual.getCanonicalLocation()); + } + + const QTemporaryFile m_tempFile; + const QDir m_tempFileDir; + const QString m_tempFileName; + const QFileInfo m_tempFileInfo; + const TrackId m_validTrackId; + const TrackId m_invalidTrackId; +}; + +TEST_F(TrackRefTest, DefaultConstructor) { + TrackRef actual; + + EXPECT_FALSE(actual.hasLocation()); + EXPECT_FALSE(actual.hasCanonicalLocation()); + EXPECT_FALSE(actual.hasId()); +} + +TEST_F(TrackRefTest, FromFileInfoWithId) { + const TrackRef actual( + TrackRef::fromFileInfo(m_tempFileInfo, m_validTrackId)); + + verifyFileInfo(actual, m_tempFileInfo); + EXPECT_TRUE(actual.hasId()); + EXPECT_EQ(m_validTrackId, actual.getId()); +} + +TEST_F(TrackRefTest, FromFileInfoWithoutId) { + const TrackRef actual( + TrackRef::fromFileInfo(m_tempFileInfo)); + + verifyFileInfo(actual, m_tempFileInfo); + EXPECT_FALSE(actual.hasId()); + EXPECT_EQ(m_invalidTrackId, actual.getId()); +} + +TEST_F(TrackRefTest, CopyAndSetId) { + const TrackRef withoutId( + TrackRef::fromFileInfo(m_tempFileInfo)); + + const TrackRef actual(withoutId, m_validTrackId); + + verifyFileInfo(actual, m_tempFileInfo); + EXPECT_TRUE(actual.hasId()); + EXPECT_EQ(m_validTrackId, actual.getId()); +} + +} // namespace diff --git a/src/track/trackref.cpp b/src/track/trackref.cpp new file mode 100644 index 00000000000..4b304e43ec9 --- /dev/null +++ b/src/track/trackref.cpp @@ -0,0 +1,28 @@ +#include "track/trackref.h" + + +bool TrackRef::verifyConsistency() const { + DEBUG_ASSERT_AND_HANDLE(hasLocation() || !hasCanonicalLocation()) { + return false; + } + DEBUG_ASSERT_AND_HANDLE(hasLocation() || !hasId()) { + return false; + } + return true; +} + +std::ostream& operator<<(std::ostream& os, const TrackRef& trackRef) { + return os << '[' << trackRef.getLocation().toStdString() + << " | " << trackRef.getCanonicalLocation().toStdString() + << " | " << trackRef.getId() + << ']'; + +} + +QDebug operator<<(QDebug debug, const TrackRef& trackRef) { + debug.nospace() << '[' << trackRef.getLocation() + << " | " << trackRef.getCanonicalLocation() + << " | " << trackRef.getId() + << ']'; + return debug.space(); +} diff --git a/src/track/trackref.h b/src/track/trackref.h new file mode 100644 index 00000000000..13ee290fd49 --- /dev/null +++ b/src/track/trackref.h @@ -0,0 +1,132 @@ +#ifndef TRACKREF_H +#define TRACKREF_H + + +#include + +#include "track/trackid.h" + + +// A track in the library is identified by a location and an id. +// The location is mandatory to identify the file, whereas the id +// only exists after the track has been inserted into the database. +// +// This class is intended to be used as a simple, almost immutable +// value object. Only the id can be set once. +class TrackRef { +public: + // All file-related track properties are snapshots from the provided + // QFileInfo. Obtaining them might involve accessing the file system + // and should be used consciously! The QFileInfo class does some + // caching behind the scenes. + // Please note that the canonical location of QFileInfo may change at + // any time, when the underlying file system structure is modified. + // It becomes empty if the file is deleted. + static QString location(const QFileInfo& fileInfo) { + return fileInfo.absoluteFilePath(); + } + static QString canonicalLocation(const QFileInfo& fileInfo) { + return fileInfo.canonicalFilePath(); + } + + // Converts a QFileInfo and an optional TrackId into a TrackRef. This + // involves obtaining the file-related track properties from QFileInfo + // (see above) and should used consciously! + static TrackRef fromFileInfo( + const QFileInfo& fileInfo, + TrackId id = TrackId()) { + return TrackRef( + location(fileInfo), + canonicalLocation(fileInfo), + id); + } + + // Default constructor + TrackRef() { + DEBUG_ASSERT(verifyConsistency()); + } + // Regular copy constructor + TrackRef(const TrackRef& other) = default; + // Custom copy constructor: Creates a copy of an existing TrackRef, + // but overwrite the TrackId with a custom value. + TrackRef( + const TrackRef& other, + TrackId id) + : m_location(other.m_location), + m_canonicalLocation(other.m_canonicalLocation), + m_id(id) { + DEBUG_ASSERT(verifyConsistency()); + } + + // The human-readable identifier of a track in Mixxx. The location is + // immutable and the starting point for accessing a track's file. + const QString& getLocation() const { + return m_location; + } + bool hasLocation() const { + return !getLocation().isEmpty(); + } + + // The unique identifier of a track's file at runtime and used + // for caching. The canonical location is empty for inexistent + // files. + const QString& getCanonicalLocation() const { + return m_canonicalLocation; + } + bool hasCanonicalLocation() const { + return !getCanonicalLocation().isEmpty(); + } + + // The primary key of a track in the Mixxx library. The id must only + // be set once after inserting into or after loading from the database. + // Tracks that have not been stored in the database don't have an id. + const TrackId& getId() const { + return m_id; + } + bool hasId() const { + return getId().isValid(); + } + + bool isValid() const { + return hasId() || hasCanonicalLocation(); + } +protected: + // Initializing constructor + TrackRef( + const QString& location, + const QString& canonicalLocation, + TrackId id = TrackId()) + : m_location(location), + m_canonicalLocation(canonicalLocation), + m_id(id) { + DEBUG_ASSERT(verifyConsistency()); + } + +private: + bool verifyConsistency() const; + + QString m_location; + QString m_canonicalLocation; + TrackId m_id; +}; + +inline +bool operator==(const TrackRef& lhs, const TrackRef& rhs) { + return (lhs.getId() == rhs.getId()) && + (lhs.getLocation() == rhs.getLocation()) && + (lhs.getCanonicalLocation() == rhs.getCanonicalLocation()); +} + +inline +bool operator!=(const TrackRef& lhs, const TrackRef& rhs) { + return !(lhs == rhs); +} + +Q_DECLARE_METATYPE(TrackRef) + +std::ostream& operator<<(std::ostream& os, const TrackRef& trackRef); + +QDebug operator<<(QDebug debug, const TrackRef& trackRef); + + +#endif // TRACKREF_H diff --git a/src/trackinfoobject.cpp b/src/trackinfoobject.cpp index d3d3387ff64..536fa1de56c 100644 --- a/src/trackinfoobject.cpp +++ b/src/trackinfoobject.cpp @@ -5,16 +5,14 @@ #include "trackinfoobject.h" -#include "library/coverartutils.h" -#include "soundsourceproxy.h" #include "track/beatfactory.h" #include "track/keyfactory.h" #include "track/keyutils.h" #include "track/trackmetadatataglib.h" +#include "track/trackref.h" #include "util/assert.h" #include "util/compatibility.h" #include "util/time.h" -#include "util/xml.h" namespace { @@ -155,7 +153,7 @@ QString TrackInfoObject::getLocation() const { // (copy-on write). But operating on a single instance of QFileInfo // might not be thread-safe due to internal caching! QMutexLocker lock(&m_qMutex); - return m_fileInfo.absoluteFilePath(); + return TrackRef::location(m_fileInfo); } QString TrackInfoObject::getCanonicalLocation() const { @@ -163,7 +161,7 @@ QString TrackInfoObject::getCanonicalLocation() const { // (copy-on write). But operating on a single instance of QFileInfo // might not be thread-safe due to internal caching! QMutexLocker lock(&m_qMutex); - return m_fileInfo.canonicalFilePath(); + return TrackRef::canonicalLocation(m_fileInfo); } QString TrackInfoObject::getDirectory() const { diff --git a/src/trackinfoobject.h b/src/trackinfoobject.h index 5bac592c83e..a268ae844da 100644 --- a/src/trackinfoobject.h +++ b/src/trackinfoobject.h @@ -62,8 +62,6 @@ class TrackInfoObject : public QObject { Q_PROPERTY(int duration READ getDuration WRITE setDuration) Q_PROPERTY(QString durationFormatted READ getDurationText STORED false) - TrackId getId() const; - QFileInfo getFileInfo() const { // Copying a QFileInfo is thread-safe (implicit sharing), no locking needed. return m_fileInfo; @@ -73,6 +71,8 @@ class TrackInfoObject : public QObject { return m_pSecurityToken; } + TrackId getId() const; + // Accessors for various stats of the file on disk. // Returns absolute path to the file, including the filename. QString getLocation() const; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 904bead6b3a..f0a7a7bf1c2 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -5,24 +5,26 @@ #include #include -#include "widget/wwidget.h" +#include "widget/wtracktableview.h" + +#include "widget/wcoverartmenu.h" #include "widget/wskincolor.h" #include "widget/wtracktableviewheader.h" +#include "widget/wwidget.h" #include "library/coverartcache.h" #include "library/librarytablemodel.h" #include "library/trackcollection.h" +#include "library/dlgtrackinfo.h" +#include "track/trackref.h" #include "trackinfoobject.h" #include "controlobject.h" #include "controlobjectslave.h" -#include "widget/wtracktableview.h" -#include "library/dlgtrackinfo.h" #include "soundsourceproxy.h" #include "mixer/playermanager.h" -#include "util/dnd.h" -#include "util/time.h" #include "preferences/dialog/dlgpreflibrary.h" #include "waveform/guitick.h" -#include "widget/wcoverartmenu.h" +#include "util/dnd.h" +#include "util/time.h" #include "util/assert.h" WTrackTableView::WTrackTableView(QWidget * parent, @@ -1167,8 +1169,7 @@ void WTrackTableView::dropEvent(QDropEvent * event) { QList fileLocationList; foreach (const QFileInfo& fileInfo, fileList) { - // TODO(uklotzde): Replace with TrackRef::location() - fileLocationList.append(fileInfo.absoluteFilePath()); + fileLocationList.append(TrackRef::location(fileInfo)); } // Drag-and-drop from an external application