return -1;
}
-
/*
* When file is encrypted it's phash (path hash) will not match the local file phash,
* we could match the e2eMangledName but that might be slow wihout index, and it's
}
}
+ // The db entry might be for a placeholder, so look for that on the
+ // remote side. If we find one, change the current fs to look like a
+ // placeholder too, because that's what one would see if the remote
+ // db was filled from the database.
+ if (ctx->current == REMOTE_REPLICA && !base.isValid() && fs->type == ItemTypeFile) {
+ auto placeholderPath = fs->path;
+ placeholderPath.append(ctx->placeholder_suffix);
+ ctx->statedb->getFileRecord(placeholderPath, &base);
+ if (base.isValid() && base._type == ItemTypePlaceholder) {
+ fs->type = ItemTypePlaceholder;
+ fs->path = placeholderPath;
+ } else {
+ base = OCC::SyncJournalFileRecord();
+ }
+ }
+
if(base.isValid()) { /* there is an entry in the database */
// When the file is loaded from the file system it misses
// the e2e mangled name and e2e encryption status
if (ctx->current == REMOTE_REPLICA && fs->etag != base._etag) {
fs->instruction = CSYNC_INSTRUCTION_EVAL;
- if (base._type == ItemTypePlaceholder && fs->type == ItemTypeFile) {
+ if (fs->type == ItemTypePlaceholder) {
// If the local thing is a placeholder, we just update the metadata
fs->instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
- fs->type = ItemTypePlaceholder; // retain the PLACEHOLDER type in the db
} else if (base._type != fs->type) {
// Preserve the EVAL flag later on if the type has changed.
fs->child_modified = true;
return;
}
+ // Now we know there is a sane rename candidate.
+
+ // Rename of a placeholder
+ if (base._type == ItemTypePlaceholder && fs->type == ItemTypeFile) {
+ fs->type = ItemTypePlaceholder;
+ fs->path.append(ctx->placeholder_suffix);
+ }
+
// Record directory renames
if (fs->type == ItemTypeDirectory) {
// If the same folder was already renamed by a different entry,
}
}
- // Potentially turn new remote files into placeholders
+ // Turn new remote files into placeholders if the option is enabled.
if (ctx->new_files_are_placeholders
&& fs->instruction == CSYNC_INSTRUCTION_NEW
&& fs->type == ItemTypeFile) {
fs->type = ItemTypePlaceholder;
+ fs->path.append(ctx->placeholder_suffix);
}
goto out;
// When encountering placeholder files, read the relevant
// entry from the db instead.
if (ctx->current == LOCAL_REPLICA
- && dirent->type == ItemTypeFile
- && filename.endsWith(".owncloud")) {
+ && dirent->type == ItemTypeFile
+ && filename.endsWith(ctx->placeholder_suffix)) {
QByteArray db_uri = fullpath.mid(strlen(ctx->local.uri) + 1);
- db_uri = db_uri.left(db_uri.size() - 9);
+ QByteArray base_uri = db_uri.left(db_uri.size() - ctx->placeholder_suffix.size());
- // 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))
+ // Don't fill the local tree with placeholder data if a real
+ // file was found. The remote tree will still have the placeholder
+ // file.
+ if (ctx->local.files.findFile(base_uri))
continue;
if( ! fill_tree_from_db(ctx, db_uri.constData(), true) ) {
FakeFolder fakeFolder{FileInfo()};
SyncOptions syncOptions;
- syncOptions._usePlaceholders = true;
+ syncOptions._newFilesArePlaceholders = true;
fakeFolder.syncEngine().setSyncOptions(syncOptions);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
- QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypePlaceholder);
+ QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NEW));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
cleanup();
// Another sync doesn't actually lead to changes
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
+ QVERIFY(completeSpy.isEmpty());
+ cleanup();
+
+ // Not even when the remote is rediscovered
+ fakeFolder.syncJournal().forceRemoteDiscoveryNextSync();
+ QVERIFY(fakeFolder.syncOnce());
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
QVERIFY(completeSpy.isEmpty());
cleanup();
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_UPDATE_METADATA));
- QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypePlaceholder);
- QCOMPARE(dbRecord(fakeFolder, "A/a1")._fileSize, 65);
+ QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_UPDATE_METADATA));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._fileSize, 65);
cleanup();
// If the local placeholder file is removed, it'll just be recreated
QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
- QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
- QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypePlaceholder);
- QCOMPARE(dbRecord(fakeFolder, "A/a1")._fileSize, 65);
+ QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_NEW));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._type, ItemTypePlaceholder);
+ QCOMPARE(dbRecord(fakeFolder, "A/a1.owncloud")._fileSize, 65);
cleanup();
// Remote rename is propagated
QVERIFY(fakeFolder.currentLocalState().find("A/a1m.owncloud"));
QVERIFY(!fakeFolder.currentRemoteState().find("A/a1"));
QVERIFY(fakeFolder.currentRemoteState().find("A/a1m"));
- QVERIFY(itemInstruction(completeSpy, "A/a1m", CSYNC_INSTRUCTION_RENAME));
- QCOMPARE(dbRecord(fakeFolder, "A/a1m")._type, ItemTypePlaceholder);
+ QVERIFY(itemInstruction(completeSpy, "A/a1m.owncloud", CSYNC_INSTRUCTION_RENAME));
+ QCOMPARE(dbRecord(fakeFolder, "A/a1m.owncloud")._type, ItemTypePlaceholder);
cleanup();
// Remote remove is propagated
QVERIFY(fakeFolder.syncOnce());
QVERIFY(!fakeFolder.currentLocalState().find("A/a1m.owncloud"));
QVERIFY(!fakeFolder.currentRemoteState().find("A/a1m"));
- QVERIFY(itemInstruction(completeSpy, "A/a1m", CSYNC_INSTRUCTION_REMOVE));
- QVERIFY(!dbRecord(fakeFolder, "A/a1m").isValid());
+ QVERIFY(itemInstruction(completeSpy, "A/a1m.owncloud", CSYNC_INSTRUCTION_REMOVE));
+ QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a1m.owncloud").isValid());
cleanup();
}
{
FakeFolder fakeFolder{ FileInfo() };
SyncOptions syncOptions;
- syncOptions._usePlaceholders = true;
+ syncOptions._newFilesArePlaceholders = true;
fakeFolder.syncEngine().setSyncOptions(syncOptions);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
};
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().mkdir("B");
fakeFolder.remoteModifier().insert("B/b1", 64);
fakeFolder.remoteModifier().insert("B/b2", 64);
+ fakeFolder.remoteModifier().mkdir("C");
+ fakeFolder.remoteModifier().insert("C/c1", 64);
QVERIFY(fakeFolder.syncOnce());
QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
QVERIFY(fakeFolder.currentLocalState().find("B/b2.owncloud"));
// A: the correct file and a conflicting file are added, placeholders stay
// B: same setup, but the placeholders are deleted by the user
+ // C: user adds a *directory* locally
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");
+ fakeFolder.localModifier().mkdir("C/c1");
+ fakeFolder.localModifier().insert("C/c1/foo");
QVERIFY(fakeFolder.syncOnce());
// Everything is CONFLICT since mtimes are different even for a1/b1
QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_CONFLICT));
QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_CONFLICT));
QVERIFY(itemInstruction(completeSpy, "B/b2", CSYNC_INSTRUCTION_CONFLICT));
+ QVERIFY(itemInstruction(completeSpy, "C/c1", 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"));
+ QVERIFY(!fakeFolder.currentLocalState().find("C/c1.owncloud"));
// conflict files should exist
- QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 2);
+ QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 3);
// 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);
+ QCOMPARE(dbRecord(fakeFolder, "C/c1")._type, ItemTypeFile);
+ QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a2.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "B/b1.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "B/b2.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "C/c1.owncloud").isValid());
cleanup();
}
{
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
SyncOptions syncOptions;
- syncOptions._usePlaceholders = true;
+ syncOptions._newFilesArePlaceholders = true;
fakeFolder.syncEngine().setSyncOptions(syncOptions);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
QVERIFY(!fakeFolder.currentLocalState().find("A/new"));
QVERIFY(fakeFolder.currentLocalState().find("A/new.owncloud"));
QVERIFY(fakeFolder.currentRemoteState().find("A/new"));
- QVERIFY(itemInstruction(completeSpy, "A/new", CSYNC_INSTRUCTION_NEW));
- QCOMPARE(dbRecord(fakeFolder, "A/new")._type, ItemTypePlaceholder);
+ QVERIFY(itemInstruction(completeSpy, "A/new.owncloud", CSYNC_INSTRUCTION_NEW));
+ QCOMPARE(dbRecord(fakeFolder, "A/new.owncloud")._type, ItemTypePlaceholder);
cleanup();
}
{
FakeFolder fakeFolder{FileInfo()};
SyncOptions syncOptions;
- syncOptions._usePlaceholders = true;
+ syncOptions._newFilesArePlaceholders = true;
fakeFolder.syncEngine().setSyncOptions(syncOptions);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
auto triggerDownload = [&](const QByteArray &path) {
auto &journal = fakeFolder.syncJournal();
SyncJournalFileRecord record;
- journal.getFileRecord(path, &record);
+ journal.getFileRecord(path + ".owncloud", &record);
if (!record.isValid())
return;
record._type = ItemTypePlaceholderDownload;
fakeFolder.localModifier().remove("A/a6.owncloud");
QVERIFY(fakeFolder.syncOnce());
QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_NEW));
+ QVERIFY(itemInstruction(completeSpy, "A/a1.owncloud", CSYNC_INSTRUCTION_REMOVE));
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/a2.owncloud", CSYNC_INSTRUCTION_REMOVE));
+ QVERIFY(itemInstruction(completeSpy, "A/a3.owncloud", CSYNC_INSTRUCTION_REMOVE));
+ QVERIFY(itemInstruction(completeSpy, "A/a4.owncloud", CSYNC_INSTRUCTION_REMOVE));
QVERIFY(itemInstruction(completeSpy, "A/a4m", CSYNC_INSTRUCTION_NEW));
QVERIFY(itemInstruction(completeSpy, "A/a5", CSYNC_INSTRUCTION_CONFLICT));
+ QVERIFY(itemInstruction(completeSpy, "A/a5.owncloud", CSYNC_INSTRUCTION_REMOVE));
QVERIFY(itemInstruction(completeSpy, "A/a6", CSYNC_INSTRUCTION_CONFLICT));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a4m")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a5")._type, ItemTypeFile);
QCOMPARE(dbRecord(fakeFolder, "A/a6")._type, ItemTypeFile);
+ QVERIFY(!dbRecord(fakeFolder, "A/a1.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a2.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a3.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a4.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a5.owncloud").isValid());
+ QVERIFY(!dbRecord(fakeFolder, "A/a6.owncloud").isValid());
+ }
+
+ // Check what might happen if an older sync client encounters placeholders
+ void testOldVersion()
+ {
+ FakeFolder fakeFolder{ FileInfo() };
+ SyncOptions syncOptions;
+ syncOptions._newFilesArePlaceholders = true;
+ fakeFolder.syncEngine().setSyncOptions(syncOptions);
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+
+ // Create a placeholder
+ fakeFolder.remoteModifier().mkdir("A");
+ fakeFolder.remoteModifier().insert("A/a1");
+ QVERIFY(fakeFolder.syncOnce());
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+
+ // Simulate an old client by switching the type of all ItemTypePlaceholder
+ // entries in the db to an invalid type.
+ auto &db = fakeFolder.syncJournal();
+ SyncJournalFileRecord rec;
+ db.getFileRecord(QByteArray("A/a1.owncloud"), &rec);
+ QVERIFY(rec.isValid());
+ QCOMPARE(rec._type, ItemTypePlaceholder);
+ rec._type = static_cast<ItemType>(-1);
+ db.setFileRecord(rec);
+
+ // Also switch off new files becoming placeholders
+ syncOptions._newFilesArePlaceholders = false;
+ fakeFolder.syncEngine().setSyncOptions(syncOptions);
+
+ // A sync that doesn't do remote discovery has no effect
+ QVERIFY(fakeFolder.syncOnce());
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
+ QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
+ QVERIFY(!fakeFolder.currentRemoteState().find("A/a1.owncloud"));
+
+ // But with a remote discovery the placeholders will be removed and
+ // the remote files will be downloaded.
+ db.forceRemoteDiscoveryNextSync();
+ QVERIFY(fakeFolder.syncOnce());
+ QVERIFY(fakeFolder.currentLocalState().find("A/a1"));
+ QVERIFY(!fakeFolder.currentLocalState().find("A/a1.owncloud"));
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
};