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

Add mixxx::FileInfo for file system references #3741

Merged
merged 9 commits into from
Mar 23, 2021
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/util/duration.cpp
src/util/experiment.cpp
src/util/file.cpp
src/util/fileinfo.cpp
src/util/imageutils.cpp
src/util/indexrange.cpp
src/util/logger.cpp
Expand Down Expand Up @@ -1493,6 +1494,7 @@ add_executable(mixxx-test
src/test/enginemastertest.cpp
src/test/enginemicrophonetest.cpp
src/test/enginesynctest.cpp
src/test/fileinfo_test.cpp
src/test/globaltrackcache_test.cpp
src/test/hotcuecontrol_test.cpp
src/test/imageutils_test.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/mixxxapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "track/trackref.h"
#include "util/cache.h"
#include "util/color/rgbcolor.h"
#include "util/fileinfo.h"
#include "util/math.h"

// When linking Qt statically on Windows we have to Q_IMPORT_PLUGIN all the
Expand Down Expand Up @@ -96,6 +97,7 @@ void MixxxApplication::registerMetaTypes() {
qRegisterMetaType<mixxx::Bpm>("mixxx::Bpm");
qRegisterMetaType<mixxx::Duration>("mixxx::Duration");
qRegisterMetaType<std::optional<mixxx::RgbColor>>("std::optional<mixxx::RgbColor>");
qRegisterMetaType<mixxx::FileInfo>("mixxx::FileInfo");
}

bool MixxxApplication::notify(QObject* target, QEvent* event) {
Expand Down
101 changes: 101 additions & 0 deletions src/test/fileinfo_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include "util/fileinfo.h"

#include <gtest/gtest.h>

#include <QTemporaryDir>
#include <QtDebug>

namespace mixxx {

class FileInfoTest : public testing::Test {
protected:
const QTemporaryDir m_tempDir;

const QString m_relativePath;
const QString m_absolutePath;

const QString m_relativePathMissing;
const QString m_absolutePathMissing;

FileInfoTest()
: m_relativePath(QStringLiteral("relative")),
m_absolutePath(m_tempDir.filePath(m_relativePath)),
m_relativePathMissing(QStringLiteral("missing")),
m_absolutePathMissing(m_tempDir.filePath(m_relativePathMissing)) {
}

void SetUp() override {
ASSERT_TRUE(m_tempDir.isValid());
ASSERT_TRUE(QDir(m_tempDir.path()).mkpath(m_relativePath));
ASSERT_TRUE(FileInfo(m_absolutePath).exists());
ASSERT_FALSE(FileInfo(m_absolutePathMissing).exists());
}
};

TEST_F(FileInfoTest, emptyPathIsRelative) {
EXPECT_FALSE(FileInfo().isAbsolute());
EXPECT_TRUE(FileInfo().isRelative());
}

TEST_F(FileInfoTest, nonEmptyPathIsEitherAbsoluteOrRelative) {
EXPECT_TRUE(FileInfo(m_absolutePath).isAbsolute());
EXPECT_FALSE(FileInfo(m_absolutePath).isRelative());
EXPECT_TRUE(FileInfo(m_absolutePathMissing).isAbsolute());
EXPECT_FALSE(FileInfo(m_absolutePathMissing).isRelative());
EXPECT_FALSE(FileInfo(m_relativePath).isAbsolute());
EXPECT_TRUE(FileInfo(m_relativePath).isRelative());
EXPECT_FALSE(FileInfo(m_relativePathMissing).isAbsolute());
EXPECT_TRUE(FileInfo(m_relativePathMissing).isRelative());
}

TEST_F(FileInfoTest, hasLocation) {
EXPECT_FALSE(FileInfo().hasLocation());
EXPECT_TRUE(FileInfo(m_absolutePath).hasLocation());
EXPECT_TRUE(FileInfo(m_absolutePath).hasLocation());
EXPECT_FALSE(FileInfo(m_relativePath).hasLocation());
EXPECT_TRUE(FileInfo(m_absolutePathMissing).hasLocation());
EXPECT_FALSE(FileInfo(m_relativePathMissing).hasLocation());
}

TEST_F(FileInfoTest, freshCanonicalFileInfo) {
FileInfo fileInfo(m_absolutePathMissing);
// This test assumes that caching is enabled resulting
// in expected inconsistencies until refreshed.
ASSERT_TRUE(fileInfo.m_fileInfo.caching());

ASSERT_TRUE(fileInfo.canonicalLocation().isEmpty());
ASSERT_TRUE(fileInfo.resolveCanonicalLocation().isEmpty());

// Restore the missing file
QFile file(m_absolutePathMissing);
ASSERT_FALSE(fileInfo.checkFileExists());
ASSERT_TRUE(file.open(QIODevice::ReadWrite | QIODevice::NewOnly));
ASSERT_TRUE(fileInfo.checkFileExists());

// The cached canonical location should still be invalid
EXPECT_TRUE(fileInfo.canonicalLocation().isEmpty());
// The refreshed canonical location should be valid
EXPECT_FALSE(fileInfo.resolveCanonicalLocation().isEmpty());
// The cached canonical location should have been updated
EXPECT_FALSE(fileInfo.canonicalLocation().isEmpty());

// Remove the file
ASSERT_TRUE(file.remove());
ASSERT_FALSE(fileInfo.checkFileExists());
ASSERT_TRUE(FileInfo(m_absolutePathMissing).canonicalLocation().isEmpty());

// Note: Qt (5.14.x) doesn't seem to invalidate the canonical location
// after it has been set once, even when refreshing the QFileInfo. This
// is what the remaining part of the test validates, although Mixxx does
// NOT rely on this behavior! Just to get notified when this behavior
// changes for some future Qt version and for ensuring that the behavior
// is identical on all platforms.

// The cached canonical location should still be valid
EXPECT_FALSE(fileInfo.canonicalLocation().isEmpty());
// The canonical location should not be refreshed again, i.e. it remains
// valid after the file has disappeared
EXPECT_FALSE(fileInfo.resolveCanonicalLocation().isEmpty());
}

} // namespace mixxx
2 changes: 2 additions & 0 deletions src/track/trackfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
//
// Copying an instance of this class is thread-safe, because the
// underlying QFileInfo is implicitly shared.
//
// TODO: Replace with mixxx::FileInfo
class TrackFile {
public:
static TrackFile fromUrl(const QUrl& url) {
Expand Down
40 changes: 40 additions & 0 deletions src/util/fileinfo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "util/fileinfo.h"

namespace mixxx {

// static
bool FileInfo::isRootSubCanonicalLocation(
const QString& rootCanonicalLocation,
const QString& subCanonicalLocation) {
VERIFY_OR_DEBUG_ASSERT(!rootCanonicalLocation.isEmpty()) {
return false;
}
VERIFY_OR_DEBUG_ASSERT(!subCanonicalLocation.isEmpty()) {
return false;
}
DEBUG_ASSERT(QDir::isAbsolutePath(rootCanonicalLocation));
DEBUG_ASSERT(QDir::isAbsolutePath(subCanonicalLocation));
if (subCanonicalLocation.size() < rootCanonicalLocation.size()) {
return false;
}
if (subCanonicalLocation.size() > rootCanonicalLocation.size() &&
subCanonicalLocation[rootCanonicalLocation.size()] != QChar('/')) {
return false;
}
return subCanonicalLocation.startsWith(rootCanonicalLocation);
}

QString FileInfo::resolveCanonicalLocation() {
// Note: We return here the cached value, that was calculated just after
// init this FileInfo object. This will avoid repeated use of the time
// consuming file I/O.
QString currentCanonicalLocation = canonicalLocation();
if (!currentCanonicalLocation.isEmpty()) {
return currentCanonicalLocation;
}
m_fileInfo.refresh();
// Return whatever is available after the refresh
return canonicalLocation();
}

} // namespace mixxx
Loading