#include <QDir>
#include <QNetworkReply>
+#include <QMap>
#include <QtTest>
static const QUrl sRootUrl("owncloud://somehost/owncloud/remote.php/webdav/");
-static QString generateEtag() {
+inline QString generateEtag() {
return QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch(), 16);
}
+inline QByteArray generateFileId() {
+ return QByteArray::number(qrand(), 16);
+}
class PathComponents : public QStringList {
public:
virtual void setContents(const QString &relativePath, char contentChar) = 0;
virtual void appendByte(const QString &relativePath) = 0;
virtual void mkdir(const QString &relativePath) = 0;
+ virtual void rename(const QString &relativePath, const QString &relativeDestinationDirectory) = 0;
};
class DiskFileModifier : public FileModifier
void mkdir(const QString &relativePath) override {
_rootDir.mkpath(relativePath);
}
+ void rename(const QString &, const QString &) override {
+ Q_ASSERT(!"not implemented");
+ }
};
class FileInfo : public FileModifier
for (const auto &source : children) {
auto &dest = this->children[source.name] = source;
dest.parentPath = p;
+ dest.fixupParentPathRecursively();
}
}
createDir(relativePath);
}
+ void rename(const QString &oldPath, const QString &newPath) override {
+ const PathComponents newPathComponents{newPath};
+ FileInfo *dir = findInvalidatingEtags(newPathComponents.parentDirComponents());
+ Q_ASSERT(dir);
+ Q_ASSERT(dir->isDir);
+ const PathComponents pathComponents{oldPath};
+ FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents());
+ Q_ASSERT(parent);
+ FileInfo fi = parent->children.take(pathComponents.fileName());
+ fi.parentPath = dir->path();
+ fi.name = newPathComponents.fileName();
+ fi.fixupParentPathRecursively();
+ dir->children.insert(newPathComponents.fileName(), std::move(fi));
+ }
+
FileInfo *find(const PathComponents &pathComponents, const bool invalidateEtags = false) {
if (pathComponents.isEmpty()) {
if (invalidateEtags)
bool isShared = false;
QDateTime lastModified = QDateTime::currentDateTime().addDays(-7);
QString etag = generateEtag();
+ QByteArray fileId = generateFileId();
qint64 size = 0;
char contentChar = 'W';
FileInfo *findInvalidatingEtags(const PathComponents &pathComponents) {
return find(pathComponents, true);
}
+
+ void fixupParentPathRecursively() {
+ auto p = path();
+ for (auto it = children.begin(); it != children.end(); ++it) {
+ Q_ASSERT(it.key() == it->name);
+ it->parentPath = p;
+ it->fixupParentPathRecursively();
+ }
+ }
+
+ friend inline QDebug operator<<(QDebug dbg, const FileInfo& fi) {
+ return dbg << "{ " << fi.path() << ": " << fi.children;
+ }
};
class FakePropfindReply : public QNetworkReply
xml.writeTextElement(davUri, QStringLiteral("getcontentlength"), QString::number(fileInfo.size));
xml.writeTextElement(davUri, QStringLiteral("getetag"), fileInfo.etag);
xml.writeTextElement(ocUri, QStringLiteral("permissions"), fileInfo.isShared ? QStringLiteral("SRDNVCKW") : QStringLiteral("RDNVCKW"));
+ xml.writeTextElement(ocUri, QStringLiteral("id"), fileInfo.fileId);
xml.writeEndElement(); // prop
xml.writeTextElement(davUri, QStringLiteral("status"), "HTTP/1.1 200 OK");
xml.writeEndElement(); // propstat
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
const FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
+ Q_ASSERT(fileInfo);
writeFileResponse(*fileInfo);
foreach(const FileInfo &childFileInfo, fileInfo->children)
}
Q_INVOKABLE void respond() {
- // FIXME: setRawHeader("OC-FileId", fileInfo->???);
+ setRawHeader("OC-FileId", fileInfo->fileId);
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
emit metaDataChanged();
emit finished();
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
setRawHeader("OC-ETag", fileInfo->etag.toLatin1());
setRawHeader("ETag", fileInfo->etag.toLatin1());
+ setRawHeader("OC-FileId", fileInfo->fileId);
emit metaDataChanged();
if (bytesAvailable())
emit readyRead();
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a3.eml"));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
+
+ void testRemoteChangeInMovedFolder() {
+ // issue #5192
+ FakeFolder fakeFolder{FileInfo{ QString(), {
+ FileInfo { QStringLiteral("folder"), {
+ FileInfo{ QStringLiteral("folderA"), { { QStringLiteral("file.txt"), 400 } } },
+ QStringLiteral("folderB")
+ }
+ }}}};
+
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+
+ // Edit a file in a moved directory.
+ fakeFolder.remoteModifier().setContents("folder/folderA/file.txt", 'a');
+ fakeFolder.remoteModifier().rename("folder/folderA", "folder/folderB/folderA");
+ fakeFolder.syncOnce();
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+ auto oldState = fakeFolder.currentLocalState();
+ QVERIFY(oldState.find(PathComponents("folder/folderB/folderA/file.txt")));
+ QVERIFY(!oldState.find(PathComponents("folder/folderA/file.txt")));
+
+ // This sync should not remove the file
+ fakeFolder.syncOnce();
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+ QCOMPARE(fakeFolder.currentLocalState(), oldState);
+
+ }
};
QTEST_GUILESS_MAIN(TestSyncEngine)