Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/owncloud/client.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorErik Verbruggen <erik@verbruggen.consulting>2021-12-09 16:24:22 +0300
committerErik Verbruggen <Erik.Verbruggen@Me.com>2021-12-15 15:23:55 +0300
commit8484e759060f221cbc6f519165572e3559cbc560 (patch)
tree9c097283ddf675031ba88f4f46748f17acedfe32 /test
parent6300c004300655c2de18da42766a547137217f8a (diff)
Tests: always check the hydration state and the mtime
operator== will now always also check the hydration state and mtime. For the few cases where this will fail (on purpose), the equals method can be called with `IgnoreLastModified` passed to it.
Diffstat (limited to 'test')
-rw-r--r--test/testdatabaseerror.cpp12
-rw-r--r--test/testsyncengine.cpp12
-rw-r--r--test/testsyncmove.cpp50
-rw-r--r--test/testutils/syncenginetestutils.cpp86
-rw-r--r--test/testutils/syncenginetestutils.h66
5 files changed, 145 insertions, 81 deletions
diff --git a/test/testdatabaseerror.cpp b/test/testdatabaseerror.cpp
index 871514a50..ff25c2139 100644
--- a/test/testdatabaseerror.cpp
+++ b/test/testdatabaseerror.cpp
@@ -24,6 +24,13 @@ private slots:
*/
FileInfo finalState;
+
+ auto checkAgainstFinalState = [&finalState](const FileInfo &currentRemoteState) {
+ // The final state should be the same for every iteration, EXCEPT for the lastModified times:
+ // these will differ (well, increase), because files are re-created with every iteration.
+ return currentRemoteState.equals(finalState, FileInfo::IgnoreLastModified);
+ };
+
for (int count = 0; true; ++count) {
qInfo() << "Starting Iteration" << count;
@@ -55,7 +62,7 @@ private slots:
// No error was thrown, we are finished
QVERIFY(result);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
- QCOMPARE(fakeFolder.currentRemoteState(), finalState);
+ QVERIFY(checkAgainstFinalState(fakeFolder.currentRemoteState()));
return;
}
@@ -71,8 +78,7 @@ private slots:
if (count == 0) {
finalState = fakeFolder.currentRemoteState();
} else {
- // the final state should be the same for every iteration
- QCOMPARE(fakeFolder.currentRemoteState(), finalState);
+ QVERIFY(checkAgainstFinalState(fakeFolder.currentRemoteState()));
}
}
}
diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp
index 5e7058d31..80ab8dcca 100644
--- a/test/testsyncengine.cpp
+++ b/test/testsyncengine.cpp
@@ -123,6 +123,9 @@ private slots:
QCOMPARE(getDbChecksum("a3.eml"), referenceChecksum);
QCOMPARE(getDbChecksum("b3.txt"), referenceChecksum);
+ // Make sure that the lastModified time caused by the setContent calls below is actually different:
+ QThread::sleep(1);
+
ItemCompletedSpy completeSpy(fakeFolder);
// Touch the file without changing the content, shouldn't upload
fakeFolder.localModifier().setContents("a1.eml", 'A');
@@ -140,7 +143,12 @@ private slots:
QVERIFY(!itemDidComplete(completeSpy, "a1.eml"));
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a2.eml"));
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a3.eml"));
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+
+ // The local and remote state now differ: the local mtime for `a1.eml` is bigger (newer) than on the server, because
+ // the upload was skipped (same checksum). So first verify that this is the case:
+ QVERIFY(fakeFolder.currentLocalState().find("a1.eml")->lastModified() > fakeFolder.currentRemoteState().find("a1.eml")->lastModified());
+ // And then check if everything else actually is the same:
+ QVERIFY(fakeFolder.currentLocalState().equals(fakeFolder.currentRemoteState(), FileInfo::IgnoreLastModified));
}
void testSelectiveSyncBug() {
@@ -753,7 +761,7 @@ private slots:
fakeFolder.remoteModifier().insert("foo/bar");
auto datetime = QDateTime::currentDateTime();
datetime.setSecsSinceEpoch(datetime.toSecsSinceEpoch()); // wipe ms
- fakeFolder.remoteModifier().find("foo")->lastModified = datetime;
+ fakeFolder.remoteModifier().find("foo")->setLastModified(datetime);
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
diff --git a/test/testsyncmove.cpp b/test/testsyncmove.cpp
index c82bba3d2..6b118c55b 100644
--- a/test/testsyncmove.cpp
+++ b/test/testsyncmove.cpp
@@ -209,7 +209,7 @@ private slots:
QCOMPARE(nDELETE, 2);
// Move-and-change, size+content only
- auto mtime = fakeFolder.remoteModifier().find("B/b2")->lastModified;
+ auto mtime = fakeFolder.remoteModifier().find("B/b2")->lastModified();
fakeFolder.localModifier().rename("B/b2", "B/b2m");
fakeFolder.localModifier().appendByte("B/b2m");
fakeFolder.localModifier().setModTime("B/b2m", mtime);
@@ -221,7 +221,7 @@ private slots:
// Move-and-change, content only -- c1 has no checksum, so we fail to detect this!
// NOTE: This is an expected failure.
- mtime = fakeFolder.remoteModifier().find("C/c1")->lastModified;
+ mtime = fakeFolder.remoteModifier().find("C/c1")->lastModified();
fakeFolder.localModifier().rename("C/c1", "C/c1m");
fakeFolder.localModifier().setContents("C/c1m", 'C');
fakeFolder.localModifier().setModTime("C/c1m", mtime);
@@ -240,7 +240,7 @@ private slots:
QCOMPARE(nDELETE, 4);
// Move-and-change, content only, this time while having a checksum
- mtime = fakeFolder.remoteModifier().find("C/c3")->lastModified;
+ mtime = fakeFolder.remoteModifier().find("C/c3")->lastModified();
fakeFolder.localModifier().rename("C/c3", "C/c3m");
fakeFolder.localModifier().setContents("C/c3m", 'C');
fakeFolder.localModifier().setModTime("C/c3m", mtime);
@@ -642,8 +642,8 @@ private slots:
fakeFolder.setServerOverride(counter.functor());
// Changing the mtime on the server (without invalidating the etag)
- fakeFolder.remoteModifier().find("A/a1")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-50000);
- fakeFolder.remoteModifier().find("A/a2")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-40000);
+ fakeFolder.remoteModifier().find("A/a1")->setLastModified(QDateTime::currentDateTime().addSecs(-50000));
+ fakeFolder.remoteModifier().find("A/a2")->setLastModified(QDateTime::currentDateTime().addSecs(-40000));
// Move a few files
fakeFolder.remoteModifier().rename("A/a1", "A/a1_server_renamed");
@@ -662,7 +662,8 @@ private slots:
QCOMPARE(counter.nMOVE, 1);
QCOMPARE(counter.nDELETE, 0);
- QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+ // Check that everything other than the mtime is still equal:
+ QVERIFY(fakeFolder.currentLocalState().equals(fakeFolder.currentRemoteState(), FileInfo::IgnoreLastModified));
}
// Test for https://github.com/owncloud/client/issues/6694
@@ -851,7 +852,7 @@ private slots:
if (isVfsPluginAvailable(Vfs::WindowsCfApi)) {
QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true;
- // TODO: then hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now.
+ // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now.
// QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false;
} else if (Utility::isWindows()) {
QWARN("Skipping Vfs::WindowsCfApi");
@@ -866,7 +867,6 @@ private slots:
FakeFolder fakeFolder({ FileInfo {} }, vfsMode);
- FileInfo::ComparissonOption cmpOpt = FileInfo::ContentIsKing;
if (vfsMode != Vfs::Off) {
auto vfs = QSharedPointer<Vfs>(createVfsFromPlugin(vfsMode).release());
QVERIFY(vfs);
@@ -875,8 +875,6 @@ private slots:
// make files virtual
fakeFolder.syncOnce();
-
- cmpOpt = FileInfo::IgnoreContentOfDehydratedFiles;
}
fakeFolder.remoteModifier().mkdir("A");
@@ -885,12 +883,16 @@ private slots:
{
auto localState = fakeFolder.currentLocalState();
- FileInfo *file = localState.find({ "A/file" });
- QVERIFY(file != nullptr); // check if the file exists
- if (vfsMode != Vfs::Off) {
- QCOMPARE(file->isDehydratedPlaceholder, filesAreDehydrated);
- }
- QVERIFY(localState.equals(fakeFolder.currentRemoteState(), cmpOpt));
+ FileInfo *localFile = localState.find({ "A/file" });
+ QVERIFY(localFile != nullptr); // check if the file exists
+ QCOMPARE(vfsMode == Vfs::Off || localFile->isDehydratedPlaceholder, vfsMode == Vfs::Off || filesAreDehydrated);
+
+ auto remoteState = fakeFolder.currentRemoteState();
+ FileInfo *remoteFile = remoteState.find({ "A/file" });
+ QVERIFY(remoteFile != nullptr);
+ QCOMPARE(localFile->lastModified(), remoteFile->lastModified());
+
+ QCOMPARE(localState, remoteState);
}
fakeFolder.localModifier().mkdir("B");
@@ -902,12 +904,16 @@ private slots:
auto localState = fakeFolder.currentLocalState();
QVERIFY(localState.find("A/file") == nullptr); // check if the file is gone
QVERIFY(localState.find("A") == nullptr); // check if the directory is gone
- FileInfo *file = localState.find({ "B/file" });
- QVERIFY(file != nullptr); // check if the file exists
- if (vfsMode != Vfs::Off) {
- QCOMPARE(file->isDehydratedPlaceholder, filesAreDehydrated); // check that no-one messed with the placeholder state
- }
- QVERIFY(localState.equals(fakeFolder.currentRemoteState(), cmpOpt));
+ FileInfo *localFile = localState.find({ "B/file" });
+ QVERIFY(localFile != nullptr); // check if the file exists
+ QCOMPARE(vfsMode == Vfs::Off || localFile->isDehydratedPlaceholder, vfsMode == Vfs::Off || filesAreDehydrated);
+
+ auto remoteState = fakeFolder.currentRemoteState();
+ FileInfo *remoteFile = remoteState.find({ "B/file" });
+ QVERIFY(remoteFile != nullptr);
+ QCOMPARE(localFile->lastModified(), remoteFile->lastModified());
+
+ QCOMPARE(localState, remoteState);
}
}
diff --git a/test/testutils/syncenginetestutils.cpp b/test/testutils/syncenginetestutils.cpp
index 7edcb6e19..fe72c642f 100644
--- a/test/testutils/syncenginetestutils.cpp
+++ b/test/testutils/syncenginetestutils.cpp
@@ -166,7 +166,10 @@ void FileInfo::appendByte(const QString &relativePath, char contentChar)
Q_UNUSED(contentChar);
FileInfo *file = findInvalidatingEtags(relativePath);
Q_ASSERT(file);
- file->contentSize += 1;
+ if (!file->isDehydratedPlaceholder) {
+ file->contentSize += 1;
+ }
+ file->fileSize += 1;
}
void FileInfo::modifyByte(const QString &relativePath, quint64 offset, char contentChar)
@@ -203,7 +206,7 @@ void FileInfo::setModTime(const QString &relativePath, const QDateTime &modTime)
{
FileInfo *file = findInvalidatingEtags(relativePath);
Q_ASSERT(file);
- file->lastModified = modTime;
+ file->setLastModified(modTime);
}
FileInfo *FileInfo::find(PathComponents pathComponents, const bool invalidateEtags)
@@ -250,44 +253,45 @@ FileInfo *FileInfo::create(const QString &relativePath, qint64 size, char conten
return &child;
}
-bool FileInfo::operator==(const FileInfo &other) const
+bool FileInfo::equals(const FileInfo &other, CompareWhat compareWhat) const
{
- // Consider files to be equal between local<->remote as a user would.
- return name == other.name
- && isDir == other.isDir
- && contentSize == other.contentSize
- && contentChar == other.contentChar
- && children == other.children;
-}
-
-bool FileInfo::equals(const FileInfo &other, ComparissonOption opt) const
-{
- switch (opt) {
- case ContentIsKing:
- return *this == other;
- case IgnoreContentOfDehydratedFiles:
- if (!isDehydratedPlaceholder && !other.isDehydratedPlaceholder) {
- if (contentSize != other.contentSize || contentChar != other.contentChar) {
- return false;
- }
+ // Only check the content and contentSize if both files are hydrated:
+ if (!isDehydratedPlaceholder && !other.isDehydratedPlaceholder) {
+ if (contentSize != other.contentSize || contentChar != other.contentChar) {
+ return false;
}
+ }
- if (name == other.name && isDir == other.isDir && fileSize == other.fileSize && lastModified == other.lastModified) {
- if (children.size() == other.children.size()) {
- for (auto it = children.constBegin(), eit = children.constEnd(),
- oit = other.children.constBegin(), oeit = other.children.constEnd();
- it != eit && oit != oeit; ++it, ++oit) {
- if (!it->equals(*oit, opt)) {
- return false;
- }
- }
- }
+ // We need to check this before we use isDir in the next if-statement:
+ if (isDir != other.isDir) {
+ return false;
+ }
+
+ if (compareWhat == CompareLastModified) {
+ // Don't check directory mtime: it might change when (unsynced) files get created.
+ if (!isDir && _lastModifiedInSecondsUTC != other._lastModifiedInSecondsUTC) {
+ return false;
}
+ }
+
+ if (name != other.name || fileSize != other.fileSize) {
+ return false;
+ }
- return true;
+ if (children.size() != other.children.size()) {
+ return false;
}
- Q_UNREACHABLE();
+ for (auto it = children.constBegin(), eit = children.constEnd(); it != eit; ++it) {
+ auto oit = other.children.constFind(it.key());
+ if (oit == other.children.constEnd()) {
+ return false;
+ } else if (!it.value().equals(oit.value(), compareWhat)) {
+ return false;
+ }
+ }
+
+ return true;
}
QString FileInfo::path() const
@@ -360,7 +364,7 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces
} else
xml.writeEmptyElement(davUri, QStringLiteral("resourcetype"));
- auto gmtDate = fileInfo.lastModified.toUTC();
+ auto gmtDate = fileInfo.lastModifiedInUtc();
auto stringDate = QLocale::c().toString(gmtDate, QStringLiteral("ddd, dd MMM yyyy HH:mm:ss 'GMT'"));
xml.writeTextElement(davUri, QStringLiteral("getlastmodified"), stringDate);
xml.writeTextElement(davUri, QStringLiteral("getcontentlength"), QString::number(fileInfo.contentSize));
@@ -442,12 +446,12 @@ FileInfo *FakePutReply::perform(FileInfo &remoteRootFileInfo, const QNetworkRequ
if (fileInfo) {
fileInfo->contentSize = putPayload.size();
fileInfo->contentChar = putPayload.at(0);
- fileInfo->fileSize = fileInfo->contentSize; // it's hydrated on the server, so these are the same
} else {
// Assume that the file is filled with the same character
fileInfo = remoteRootFileInfo.create(fileName, putPayload.size(), putPayload.at(0));
}
- fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong());
+ fileInfo->fileSize = fileInfo->contentSize; // it's hydrated on the server, so these are the same
+ fileInfo->setLastModifiedFromSecondsUTC(request.rawHeader("X-OC-Mtime").toLongLong());
remoteRootFileInfo.find(fileName, /*invalidate_etags=*/true);
return fileInfo;
}
@@ -574,6 +578,7 @@ void FakeGetReply::respond()
setRawHeader("OC-ETag", fileInfo->etag);
setRawHeader("ETag", fileInfo->etag);
setRawHeader("OC-FileId", fileInfo->fileId);
+ setRawHeader("X-OC-Mtime", QByteArray::number(fileInfo->lastModifiedInSecondsUTC()));
emit metaDataChanged();
if (bytesAvailable())
emit readyRead();
@@ -641,6 +646,7 @@ void FakeGetWithDataReply::respond()
setRawHeader("OC-ETag", fileInfo->etag);
setRawHeader("ETag", fileInfo->etag);
setRawHeader("OC-FileId", fileInfo->fileId);
+ setRawHeader("X-OC-Mtime", QByteArray::number(fileInfo->lastModifiedInSecondsUTC()));
emit metaDataChanged();
if (bytesAvailable())
emit readyRead();
@@ -739,7 +745,7 @@ FileInfo *FakeChunkMoveReply::perform(FileInfo &uploadsFileInfo, FileInfo &remot
// Assume that the file is filled with the same character
fileInfo = remoteRootFileInfo.create(fileName, size, payload);
}
- fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong());
+ fileInfo->setLastModifiedFromSecondsUTC(request.rawHeader("X-OC-Mtime").toLongLong());
remoteRootFileInfo.find(fileName, /*invalidate_etags=*/true);
return fileInfo;
@@ -1052,7 +1058,7 @@ void FakeFolder::toDisk(QDir &dir, const FileInfo &templateFi)
file.open(QFile::WriteOnly);
file.write(QByteArray {}.fill(child.contentChar, child.contentSize));
file.close();
- OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(child.lastModified));
+ OCC::FileSystem::setModTime(file.fileName(), child.lastModifiedInSecondsUTC());
}
}
}
@@ -1065,12 +1071,14 @@ void FakeFolder::fromDisk(QDir &dir, FileInfo &templateFi)
QDir subDir = dir;
subDir.cd(diskChild.fileName());
FileInfo &subFi = templateFi.children[diskChild.fileName()] = FileInfo { diskChild.fileName() };
+ subFi.setLastModified(diskChild.lastModified());
fromDisk(subDir, subFi);
} else {
FileInfo fi(diskChild.fileName());
fi.isDir = false;
fi.fileSize = diskChild.size();
fi.isDehydratedPlaceholder = isDehydratedPlaceholder(diskChild.absoluteFilePath());
+ fi.setLastModified(diskChild.lastModified());
if (fi.isDehydratedPlaceholder) {
fi.contentChar = '\0';
} else {
@@ -1118,7 +1126,7 @@ FileInfo FakeFolder::dbState() const
item.isDir = record._type == ItemTypeDirectory;
item.permissions = record._remotePerm;
item.etag = record._etag;
- item.lastModified = OCC::Utility::qDateTimeFromTime_t(record._modtime);
+ item.setLastModifiedFromSecondsUTC(record._modtime);
item.fileId = record._fileId;
item.checksums = record._checksumHeader;
// item.contentChar can't be set from the db
diff --git a/test/testutils/syncenginetestutils.h b/test/testutils/syncenginetestutils.h
index 22b0fa296..61b258b4a 100644
--- a/test/testutils/syncenginetestutils.h
+++ b/test/testutils/syncenginetestutils.h
@@ -109,6 +109,13 @@ public:
void setModTime(const QString &relativePath, const QDateTime &modTime) override;
};
+static inline qint64 defaultLastModified()
+{
+ auto precise = QDateTime::currentDateTimeUtc().addDays(-7);
+ time_t timeInSeconds = OCC::Utility::qDateTimeToTime_t(precise);
+ return timeInSeconds;
+}
+
/// FIXME: we should make it explicit in the construtor if we're talking about a hydrated or a dehydrated file!
class FileInfo : public FileModifier
{
@@ -167,19 +174,44 @@ public:
return name < other.name;
}
- bool operator==(const FileInfo &other) const;
+ enum CompareWhat {
+ CompareLastModified,
+ IgnoreLastModified,
+ };
+
+ bool operator==(const FileInfo &other) const { return equals(other, CompareLastModified); }
+
+ bool equals(const FileInfo &other, CompareWhat compareWhat) const;
bool operator!=(const FileInfo &other) const
{
return !operator==(other);
}
- enum ComparissonOption {
- IgnoreContentOfDehydratedFiles,
- ContentIsKing,
- };
+ QDateTime lastModified() const
+ {
+ return QDateTime::fromSecsSinceEpoch(_lastModifiedInSecondsUTC, Qt::LocalTime);
+ }
+
+ QDateTime lastModifiedInUtc() const
+ {
+ return QDateTime::fromSecsSinceEpoch(_lastModifiedInSecondsUTC, Qt::UTC);
+ }
+
+ void setLastModified(const QDateTime &t)
+ {
+ _lastModifiedInSecondsUTC = t.toSecsSinceEpoch();
+ }
- bool equals(const FileInfo &other, ComparissonOption opt) const;
+ qint64 lastModifiedInSecondsUTC() const
+ {
+ return _lastModifiedInSecondsUTC;
+ }
+
+ void setLastModifiedFromSecondsUTC(qint64 utcSecs)
+ {
+ _lastModifiedInSecondsUTC = utcSecs;
+ }
QString path() const;
QString absolutePath() const;
@@ -190,7 +222,7 @@ public:
bool isDir = true;
bool isShared = false;
OCC::RemotePermissions permissions; // When uset, defaults to everything
- QDateTime lastModified = QDateTime::currentDateTimeUtc().addDays(-7);
+ qint64 _lastModifiedInSecondsUTC = defaultLastModified();
QByteArray etag = generateEtag();
QByteArray fileId = generateFileId();
QByteArray checksums;
@@ -208,12 +240,16 @@ public:
friend inline QDebug operator<<(QDebug dbg, const FileInfo &fi)
{
- return dbg << "{ " << fi.path() << ": "
- << ", fileSize:" << fi.fileSize
- << ", contentSize:" << fi.contentSize
- << ", contentChar:" << fi.contentChar
- << ", isDehydratedPlaceholder:" << fi.isDehydratedPlaceholder
- << ", children:" << fi.children;
+ return dbg.nospace().noquote()
+ << "{ '" << fi.path() << "': "
+ << ", isDir:" << fi.isDir
+ << QStringLiteral(", lastModified: %1 (%2)").arg(QString::number(fi._lastModifiedInSecondsUTC), fi.lastModifiedInUtc().toString())
+ << ", fileSize:" << fi.fileSize
+ << ", contentSize:" << fi.contentSize
+ << QStringLiteral(", contentChar: 0x%1").arg(QString::number(int(fi.contentChar), 16))
+ << ", isDehydratedPlaceholder:" << fi.isDehydratedPlaceholder
+ << ", children:" << fi.children
+ << " }";
}
};
@@ -614,11 +650,11 @@ inline void addFilesDbData(QStringList &dest, const FileInfo &fi)
{
// could include etag, permissions etc, but would need extra work
if (fi.isDir) {
- dest += QStringLiteral("%1 - %2 %3 %4").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.lastModified.toSecsSinceEpoch()), QString::fromUtf8(fi.fileId));
+ dest += QStringLiteral("%1 - %2 %3 %4").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.lastModifiedInSecondsUTC()), QString::fromUtf8(fi.fileId));
for (const auto &fi : fi.children)
addFilesDbData(dest, fi);
} else {
- dest += QStringLiteral("%1 - %2 %3 %4 %5").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.contentSize), QString::number(fi.lastModified.toSecsSinceEpoch()), QString::fromUtf8(fi.fileId));
+ dest += QStringLiteral("%1 - %2 %3 %4 %5").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.contentSize), QString::number(fi.lastModifiedInSecondsUTC()), QString::fromUtf8(fi.fileId));
}
}