extern "C" {
#include "csync_private.h"
+#include "csync_rename.h"
}
#include <map>
return c_strdup(path);
}
+bool csync_rename_count(CSYNC *ctx) {
+ csync_rename_s* d = csync_rename_s::get(ctx);
+ return d->folder_renamed_from.size();
+}
}
char OCSYNC_EXPORT *csync_rename_adjust_path_source(CSYNC *ctx, const char *path);
void OCSYNC_EXPORT csync_rename_destroy(CSYNC *ctx);
void OCSYNC_EXPORT csync_rename_record(CSYNC *ctx, const char *from, const char *to);
+/* Return the amount of renamed item recorded */
+bool OCSYNC_EXPORT csync_rename_count(CSYNC *ctx);
#ifdef __cplusplus
}
#include "discoveryphase.h"
#include <csync_private.h>
+#include <csync_rename.h>
#include <qdebug.h>
#include <QUrl>
return pathSlash.startsWith(*it);
}
-bool DiscoveryJob::isInSelectiveSyncBlackList(const QString& path) const
+bool DiscoveryJob::isInSelectiveSyncBlackList(const char *path) const
{
if (_selectiveSyncBlackList.isEmpty()) {
// If there is no black list, everything is allowed
}
// Block if it is in the black list
- return findPathInList(_selectiveSyncBlackList, path);
+ if (findPathInList(_selectiveSyncBlackList, QString::fromUtf8(path))) {
+ return true;
+ }
+
+ // Also try to adjust the path if there was renames
+ if (csync_rename_count(_csync_ctx)) {
+ QScopedPointer<char, QScopedPointerPodDeleter> adjusted(
+ csync_rename_adjust_path_source(_csync_ctx, path));
+ if (strcmp(adjusted.data(), path) != 0) {
+ return findPathInList(_selectiveSyncBlackList, QString::fromUtf8(adjusted.data()));
+ }
+ }
+ return false;
}
int DiscoveryJob::isInSelectiveSyncBlackListCallback(void *data, const char *path)
{
- return static_cast<DiscoveryJob*>(data)->isInSelectiveSyncBlackList(QString::fromUtf8(path));
+ return static_cast<DiscoveryJob*>(data)->isInSelectiveSyncBlackList(path);
}
bool DiscoveryJob::checkSelectiveSyncNewFolder(const QString& path)
* return true if the given path should be ignored,
* false if the path should be synced
*/
- bool isInSelectiveSyncBlackList(const QString &path) const;
+ bool isInSelectiveSyncBlackList(const char* path) const;
static int isInSelectiveSyncBlackListCallback(void *, const char *);
bool checkSelectiveSyncNewFolder(const QString &path);
static int checkSelectiveSyncNewFolderCallback(void*, const char*);
*/
#include "propagateremotemove.h"
+#include "propagatorjobs.h"
#include "owncloudpropagator_p.h"
#include "account.h"
#include "syncjournalfilerecord.h"
done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
return;
}
+
+ if (_item->_isDirectory) {
+ if (!adjustSelectiveSync(_propagator->_journal, _item->_file, _item->_renameTarget)) {
+ done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
+ return;
+ }
+ }
+
_propagator->_journal->commit("Remote Rename");
done(SyncFileItem::Success);
}
+bool PropagateRemoteMove::adjustSelectiveSync(SyncJournalDb *journal, const QString &from_, const QString &to_)
+{
+ bool ok;
+ // We only care about preserving the blacklist. The white list should anyway be empty.
+ // And the undecided list will be repopulated on the next sync, if there is anything too big.
+ QStringList list = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
+ if (!ok)
+ return false;
+
+ bool changed = false;
+ Q_ASSERT(!from_.endsWith(QLatin1String("/")));
+ Q_ASSERT(!to_.endsWith(QLatin1String("/")));
+ QString from = from_ + QLatin1String("/");
+ QString to = to_ + QLatin1String("/");
+
+ for (auto it = list.begin(); it != list.end(); ++it) {
+ if (it->startsWith(from)) {
+ *it = it->replace(0, from.size(), to);
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, list);
+ }
+ return true;
+}
}
void start() Q_DECL_OVERRIDE;
void abort() Q_DECL_OVERRIDE;
JobParallelism parallelism() Q_DECL_OVERRIDE { return OCC::PropagatorJob::WaitForFinishedInParentDirectory; }
+
+ /**
+ * Rename the directory in the selective sync list
+ */
+ static bool adjustSelectiveSync(SyncJournalDb *journal, const QString &from, const QString &to);
+
private slots:
void slotMoveJobFinished();
void finalize();
#include "propagatorjobs.h"
#include "owncloudpropagator_p.h"
-
+#include "propagateremotemove.h"
#include "utility.h"
#include "syncjournaldb.h"
#include "syncjournalfilerecord.h"
_propagator->_journal->deleteFileRecord(_item->_originalFile);
// store the rename file name in the item.
+ const auto oldFile = _item->_file;
_item->_file = _item->_renameTarget;
SyncJournalFileRecord record(*_item, targetFile);
done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
return;
}
+ } else {
+ if (!PropagateRemoteMove::adjustSelectiveSync(_propagator->_journal, oldFile, _item->_renameTarget)) {
+ done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
+ return;
+ }
}
- _propagator->_journal->commit("localRename");
+ _propagator->_journal->commit("localRename");
done(SyncFileItem::Success);
}
class PathComponents : public QStringList {
public:
+ PathComponents(const char *path) : PathComponents{QString::fromUtf8(path)} {}
PathComponents(const QString &path) : QStringList{path.split('/', QString::SkipEmptyParts)} { }
PathComponents(const QStringList &pathComponents) : QStringList{pathComponents} { }
void mkdir(const QString &relativePath) override {
_rootDir.mkpath(relativePath);
}
- void rename(const QString &, const QString &) override {
- Q_ASSERT(!"not implemented");
+ void rename(const QString &from, const QString &to) override {
+ QVERIFY(_rootDir.exists(from));
+ QVERIFY(_rootDir.rename(from, to));
}
};
qint64 readData(char *, qint64) override { return 0; }
};
+class FakeMoveReply : public QNetworkReply
+{
+ Q_OBJECT
+public:
+ FakeMoveReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
+ : QNetworkReply{parent} {
+ setRequest(request);
+ setUrl(request.url());
+ setOperation(op);
+ open(QIODevice::ReadOnly);
+
+ Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
+ QString fileName = request.url().path().mid(sRootUrl.path().length());
+ QString destPath = request.rawHeader("Destination");
+ Q_ASSERT(destPath.startsWith(sRootUrl.path()));
+ QString dest = destPath.mid(sRootUrl.path().length());
+ remoteRootFileInfo.rename(fileName, dest);
+ QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
+ }
+
+ Q_INVOKABLE void respond() {
+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
+ emit metaDataChanged();
+ emit finished();
+ }
+
+ void abort() override { }
+ qint64 readData(char *, qint64) override { return 0; }
+};
+
class FakeGetReply : public QNetworkReply
{
Q_OBJECT
return new FakeMkcolReply{_remoteRootFileInfo, op, request, this};
else if (verb == QLatin1String("DELETE"))
return new FakeDeleteReply{_remoteRootFileInfo, op, request, this};
+ else if (verb == QLatin1String("MOVE"))
+ return new FakeMoveReply{_remoteRootFileInfo, op, request, this};
else {
qDebug() << verb << outgoingData;
Q_UNREACHABLE();
#include <QtTest>
#include "syncenginetestutils.h"
+#include <syncengine.h>
using namespace OCC;
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")));
+ QVERIFY(oldState.find("folder/folderB/folderA/file.txt"));
+ QVERIFY(!oldState.find("folder/folderA/file.txt"));
// This sync should not remove the file
fakeFolder.syncOnce();
QCOMPARE(fakeFolder.currentLocalState(), oldState);
}
+
+ void testSelectiveSyncModevFolder() {
+ // issue #5224
+ FakeFolder fakeFolder{FileInfo{ QString(), {
+ FileInfo { QStringLiteral("parentFolder"), {
+ FileInfo{ QStringLiteral("subFolderA"), { { QStringLiteral("fileA.txt"), 400 } } },
+ FileInfo{ QStringLiteral("subFolderB"), { { QStringLiteral("fileB.txt"), 400 } } }
+ }
+ }}}};
+
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+ auto expectedServerState = fakeFolder.currentRemoteState();
+
+ // Remove subFolderA with selectiveSync:
+ fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
+ {"parentFolder/subFolderA/"});
+ fakeFolder.syncEngine().journal()->avoidReadFromDbOnNextSync("parentFolder/subFolderA/");
+
+ fakeFolder.syncOnce();
+
+ {
+ // Nothing changed on the server
+ QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
+ // The local state should not have subFolderA
+ auto remoteState = fakeFolder.currentRemoteState();
+ remoteState.remove("parentFolder/subFolderA");
+ QCOMPARE(fakeFolder.currentLocalState(), remoteState);
+ }
+
+ // Rename parentFolder on the server
+ fakeFolder.remoteModifier().rename("parentFolder", "parentFolderRenamed");
+ expectedServerState = fakeFolder.currentRemoteState();
+ fakeFolder.syncOnce();
+
+ {
+ QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
+ auto remoteState = fakeFolder.currentRemoteState();
+ // The subFolderA should still be there on the server.
+ QVERIFY(remoteState.find("parentFolderRenamed/subFolderA/fileA.txt"));
+ // But not on the client because of the selective sync
+ remoteState.remove("parentFolderRenamed/subFolderA");
+ QCOMPARE(fakeFolder.currentLocalState(), remoteState);
+ }
+
+ // Rename it again, locally this time.
+ fakeFolder.localModifier().rename("parentFolderRenamed", "parentThirdName");
+ fakeFolder.syncOnce();
+
+ {
+ auto remoteState = fakeFolder.currentRemoteState();
+ // The subFolderA should still be there on the server.
+ QVERIFY(remoteState.find("parentThirdName/subFolderA/fileA.txt"));
+ // But not on the client because of the selective sync
+ remoteState.remove("parentThirdName/subFolderA");
+ QCOMPARE(fakeFolder.currentLocalState(), remoteState);
+
+ expectedServerState = fakeFolder.currentRemoteState();
+ QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)));
+ fakeFolder.syncOnce(); // This sync should do nothing
+ QCOMPARE(completeSpy.count(), 0);
+
+ QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState);
+ QCOMPARE(fakeFolder.currentLocalState(), remoteState);
+ }
+ }
+
};
QTEST_GUILESS_MAIN(TestSyncEngine)