include duplication check

works now on all platforms without fdupes installed
This commit is contained in:
Christoph Cullmann 2024-05-10 21:49:56 +02:00
parent b971378872
commit 4038960c82
3 changed files with 40 additions and 107 deletions

View File

@ -12,10 +12,6 @@ if(BUILD_TESTING)
TEST_NAME "newline"
LINK_LIBRARIES Qt6::Test
)
ecm_add_test(dupetest.cpp
TEST_NAME "dupe"
LINK_LIBRARIES Qt6::Test
)
ecm_add_test(scalabletest.cpp
TEST_NAME "scalable"
LINK_LIBRARIES Qt6::Test

View File

@ -1,94 +0,0 @@
/*
Copyright 2016 Harald Sitter <sitter@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QObject>
#include <QProcess>
#include <QStandardPaths>
#include <QTest>
#include "testhelpers.h"
class DupeTest : public QObject
{
Q_OBJECT
QStringList splitOnUnescapedSpace(const QString &line)
{
QStringList ret;
const int lineLength = line.length();
int start = 0;
for (int pos = 0; pos < lineLength; ++pos) {
const QChar ch = line[pos];
if (ch == QLatin1Char('\\')) {
++pos;
continue;
} else if (ch == QLatin1Char(' ')) {
ret.append(line.mid(start, pos - start));
start = pos + 1;
}
}
if (start < lineLength) {
ret.append(line.mid(start));
}
return ret;
}
void readLines(QProcess &proc)
{
QString line;
while (proc.canReadLine() || proc.waitForReadyRead()) {
line = QString::fromUtf8(proc.readLine());
failListContent(splitOnUnescapedSpace(line.simplified()), QStringLiteral("The following files are duplicates but not links:\n"));
}
}
void dupesForDirectory(const QString &path)
{
const QString exec = QStandardPaths::findExecutable(QStringLiteral("fdupes"));
QVERIFY(!exec.isEmpty());
QProcess proc;
proc.setProgram(exec);
proc.setArguments(QStringList() << QStringLiteral("--recurse") << QStringLiteral("--sameline") << QStringLiteral("--nohidden") << path);
proc.start();
proc.waitForStarted();
readLines(proc);
}
private Q_SLOTS:
void test_duplicates()
{
if (QStandardPaths::findExecutable(QStringLiteral("fdupes")).isEmpty()) {
#ifdef Q_OS_UNIX
// Fail and skip. This is a fairly relevant test, so it not running is a warning really.
QFAIL("this test needs the fdupes binary (1.51+) to run");
#else
// On Windows let's just skip it
QSKIP("this test needs the fdupes binary (1.51+) to run");
#endif
}
for (auto dir : ICON_DIRS) {
dupesForDirectory(PROJECT_SOURCE_DIR + QStringLiteral("/") + dir);
}
}
};
QTEST_GUILESS_MAIN(DupeTest)
#include "dupetest.moc"

View File

@ -12,24 +12,39 @@
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QHash>
#include <QRegularExpression>
#include <QSet>
#include <QString>
#include <QXmlStreamReader>
/**
* Check if this file is a duplicate of an other on, dies then.
* @param fileName file to check
*/
static void checkForDuplicates(const QString &fileName)
{
// get full content for dupe checking
QFile in(fileName);
if (!in.open(QIODevice::ReadOnly)) {
qFatal() << "failed to open" << in.fileName() << "for XML validation";
}
const auto fullContent = in.readAll();
// see if we did have this content already and die
static QHash<QByteArray, QString> contentToFileName;
if (const auto it = contentToFileName.find(fullContent); it != contentToFileName.end()) {
qFatal() << "file" << fileName << "is a duplicate of file" << it.value();
}
contentToFileName.insert(fullContent, fileName);
}
/**
* Validate the XML, dies on errors.
* @param fileName file to validate
*/
static void validateXml(const QString &fileName)
{
// do checks just once, if we encounter this multiple times because of aliasing
static QSet<QString> seenFiles;
if (seenFiles.contains(fileName)) {
return;
}
seenFiles.insert(fileName);
// read once and bail out on errors
QFile in(fileName);
if (!in.open(QIODevice::ReadOnly)) {
@ -96,6 +111,9 @@ static void generateQRCAndCheckInputs(const QStringList &indirs, const QString &
out.write("<!DOCTYPE RCC><RCC version=\"1.0\">\n");
out.write("<qresource>\n");
// loop over the inputs, remember if we do look at generated stuff for checks
bool generatedIcons = false;
QSet<QString> checkedFiles;
for (const auto &indir : indirs) {
// go to input dir to have proper relative paths
if (!QDir::setCurrent(indir)) {
@ -127,14 +145,27 @@ static void generateQRCAndCheckInputs(const QStringList &indirs, const QString &
fullPath = QFileInfo(aliasLink).absoluteFilePath();
}
// do some checks for SVGs
// do checks just once, if we encounter this multiple times because of aliasing
if (fullPath.endsWith(QLatin1String(".svg")) && !checkedFiles.contains(fullPath)) {
// fill our guard
checkedFiles.insert(fullPath);
// validate it as XML if it is an SVG
if (fullPath.endsWith(QLatin1String(".svg"))) {
validateXml(fullPath);
// do duplicate check for non-generated icons
if (!generatedIcons) {
checkForDuplicates(fullPath);
}
}
// write the one alias to file entry
out.write(QStringLiteral(" <file alias=\"%1\">%2</file>\n").arg(file, fullPath).toUtf8());
}
// starting with the second directory we look at generated icons
generatedIcons = true;
}
out.write("</qresource>\n");