Skip to content

Commit

Permalink
Merge pull request #3741 from uklotzde/fileinfo
Browse files Browse the repository at this point in the history
Add mixxx::FileInfo for file system references
  • Loading branch information
daschuer authored Mar 23, 2021
2 parents 1cd3aed + 13f5884 commit e1f8fea
Show file tree
Hide file tree
Showing 6 changed files with 430 additions and 0 deletions.
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

0 comments on commit e1f8fea

Please sign in to comment.