Placeholders: Deal with conflicts when a placeholder exists

So "foo.owncloud" exists but the user adds a new "foo".
This commit is contained in:
Christian Kamm 2018-01-17 10:48:25 +01:00
parent a41d385607
commit e2f2c9a153
3 changed files with 108 additions and 0 deletions

View File

@ -289,6 +289,16 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
/* file on current replica is changed or new */
case CSYNC_INSTRUCTION_EVAL:
case CSYNC_INSTRUCTION_NEW:
// If the db says this is a placeholder, but there is a local item,
// go to "possible conflict" mode by adjusting the remote instruction.
if (ctx->current == LOCAL_REPLICA
&& (other->type == ItemTypePlaceholder || other->type == ItemTypePlaceholderDownload)
&& cur->type != ItemTypePlaceholder
&& other->instruction == CSYNC_INSTRUCTION_NONE) {
other->instruction = CSYNC_INSTRUCTION_EVAL;
other->type = ItemTypePlaceholderDownload;
}
switch (other->instruction) {
/* file on other replica is changed or new */
case CSYNC_INSTRUCTION_NEW:

View File

@ -198,12 +198,24 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
*reinterpret_cast<short*>(&fs->remotePerm), *reinterpret_cast<short*>(&base._remotePerm),
fs->type, base._type,
base._serverHasIgnoredFiles );
// If the db suggests a placeholder should be downloaded,
// treat the file as new on the remote.
if (ctx->current == REMOTE_REPLICA && base._type == ItemTypePlaceholderDownload) {
fs->instruction = CSYNC_INSTRUCTION_NEW;
fs->type = ItemTypePlaceholderDownload;
goto out;
}
// If what the db thinks is a placeholder is actually a file/dir,
// treat it as new locally.
if (ctx->current == LOCAL_REPLICA
&& (base._type == ItemTypePlaceholder || base._type == ItemTypePlaceholderDownload)
&& fs->type != ItemTypePlaceholder) {
fs->instruction = CSYNC_INSTRUCTION_EVAL;
goto out;
}
if (ctx->current == REMOTE_REPLICA && fs->etag != base._etag) {
fs->instruction = CSYNC_INSTRUCTION_EVAL;
@ -707,6 +719,12 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
&& filename.endsWith(".owncloud")) {
QByteArray db_uri = fullpath.mid(strlen(ctx->local.uri) + 1);
db_uri = db_uri.left(db_uri.size() - 9);
// Don't overwrite data that was already retrieved from disk!
// This can happen if foo.owncloud exists and the user adds foo.
if (ctx->local.files.findFile(db_uri))
continue;
if( ! fill_tree_from_db(ctx, db_uri.constData(), true) ) {
errno = ENOENT;
ctx->status_code = CSYNC_STATUS_OPENDIR_ERROR;

View File

@ -131,6 +131,69 @@ private slots:
cleanup();
}
void testPlaceholderConflict()
{
FakeFolder fakeFolder{ FileInfo() };
SyncOptions syncOptions;
syncOptions._usePlaceholders = true;
fakeFolder.syncEngine().setSyncOptions(syncOptions);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
auto cleanup = [&]() {
completeSpy.clear();
};
cleanup();
Logger::instance()->setLogDebug(true);
Logger::instance()->setLogFile("-");
// Create a placeholder for a new remote file
fakeFolder.remoteModifier().mkdir("A");
fakeFolder.remoteModifier().insert("A/a1", 64);
fakeFolder.remoteModifier().insert("A/a2", 64);
fakeFolder.remoteModifier().mkdir("B");
fakeFolder.remoteModifier().insert("B/b1", 64);
fakeFolder.remoteModifier().insert("B/b2", 64);
QVERIFY(fakeFolder.syncOnce());
QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
QVERIFY(fakeFolder.currentLocalState().find("B/b2.owncloud"));
cleanup();
// A: the correct file and a conflicting file are added, placeholders stay
// B: same setup, but the placeholders are deleted by the user
fakeFolder.localModifier().insert("A/a1", 64);
fakeFolder.localModifier().insert("A/a2", 30);
fakeFolder.localModifier().insert("B/b1", 64);
fakeFolder.localModifier().insert("B/b2", 30);
fakeFolder.localModifier().remove("B/b1.owncloud");
fakeFolder.localModifier().remove("B/b2.owncloud");
QVERIFY(fakeFolder.syncOnce());
// Everything is CONFLICT since mtimes are different even for a1/b1
QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_CONFLICT));
QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_CONFLICT));
QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_CONFLICT));
QVERIFY(itemInstruction(completeSpy, "B/b2", CSYNC_INSTRUCTION_CONFLICT));
// no placeholder files should remain
QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
QVERIFY(!fakeFolder.currentLocalState().find("A/a2.owncloud"));
QVERIFY(!fakeFolder.currentLocalState().find("B/b1.owncloud"));
QVERIFY(!fakeFolder.currentLocalState().find("B/b2.owncloud"));
// conflict files should exist
QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 2);
// nothing should have the placeholder tag
QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "B/b1")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "B/b2")._type, ItemTypeFile);
cleanup();
}
void testWithNormalSync()
{
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
@ -199,11 +262,15 @@ private slots:
fakeFolder.remoteModifier().insert("A/a2");
fakeFolder.remoteModifier().insert("A/a3");
fakeFolder.remoteModifier().insert("A/a4");
fakeFolder.remoteModifier().insert("A/a5");
fakeFolder.remoteModifier().insert("A/a6");
QVERIFY(fakeFolder.syncOnce());
QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
QVERIFY(fakeFolder.currentLocalState().find("A/a2.owncloud"));
QVERIFY(fakeFolder.currentLocalState().find("A/a3.owncloud"));
QVERIFY(fakeFolder.currentLocalState().find("A/a4.owncloud"));
QVERIFY(fakeFolder.currentLocalState().find("A/a5.owncloud"));
QVERIFY(fakeFolder.currentLocalState().find("A/a6.owncloud"));
cleanup();
// Download by changing the db entry
@ -211,16 +278,29 @@ private slots:
triggerDownload("A/a2");
triggerDownload("A/a3");
triggerDownload("A/a4");
triggerDownload("A/a5");
triggerDownload("A/a6");
fakeFolder.remoteModifier().appendByte("A/a2");
fakeFolder.remoteModifier().remove("A/a3");
fakeFolder.remoteModifier().rename("A/a4", "A/a4m");
fakeFolder.localModifier().insert("A/a5");
fakeFolder.localModifier().insert("A/a6");
fakeFolder.localModifier().remove("A/a6.owncloud");
QVERIFY(fakeFolder.syncOnce());
QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_NEW));
QVERIFY(itemInstruction(completeSpy, "A/a3", CSYNC_INSTRUCTION_REMOVE));
QVERIFY(itemInstruction(completeSpy, "A/a4", CSYNC_INSTRUCTION_REMOVE));
QVERIFY(itemInstruction(completeSpy, "A/a4m", CSYNC_INSTRUCTION_NEW));
QVERIFY(itemInstruction(completeSpy, "A/a5", CSYNC_INSTRUCTION_CONFLICT));
QVERIFY(itemInstruction(completeSpy, "A/a6", CSYNC_INSTRUCTION_CONFLICT));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile);
QVERIFY(!dbRecord(fakeFolder, "A/a3").isValid());
QCOMPARE(dbRecord(fakeFolder, "A/a4m")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a5")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a6")._type, ItemTypeFile);
}
};