Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable-3.9] Convert VFS file to placeholder again if needed #5927

Merged
merged 3 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/csync/csync.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ struct OCSYNC_EXPORT csync_file_stat_s {
bool has_ignored_files BITFIELD(1); // Specify that a directory, or child directory contains ignored files.
bool is_hidden BITFIELD(1); // Not saved in the DB, only used during discovery for local files.
bool isE2eEncrypted BITFIELD(1);
bool is_metadata_missing BITFIELD(1); // Indicates the file has missing metadata, f.ex. the file is not a placeholder in case of vfs.

QByteArray path;
QByteArray rename_path;
Expand All @@ -233,6 +234,7 @@ struct OCSYNC_EXPORT csync_file_stat_s {
, has_ignored_files(false)
, is_hidden(false)
, isE2eEncrypted(false)
, is_metadata_missing(false)
{ }
};

Expand Down
2 changes: 2 additions & 0 deletions src/gui/folder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ void Folder::slotWatchedPathChanged(const QString &path, ChangeReason reason)
spurious = false;
if (*pinState == PinState::OnlineOnly && record.isFile())
spurious = false;
} else {
spurious = false;
}
}
if (spurious) {
Expand Down
4 changes: 3 additions & 1 deletion src/libsync/discovery.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*

Check notice on line 1 in src/libsync/discovery.cpp

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/libsync/discovery.cpp

File src/libsync/discovery.cpp (lines 476, 477, 1077): Code does not conform to Custom style guidelines.
* Copyright (C) by Olivier Goffart <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
Expand Down Expand Up @@ -473,7 +473,8 @@
<< " | type: " << dbEntry._type << "/" << localEntry.type << "/" << (serverEntry.isDirectory ? ItemTypeDirectory : ItemTypeFile)
<< " | e2ee: " << dbEntry.isE2eEncrypted() << "/" << serverEntry.isE2eEncrypted()
<< " | e2eeMangledName: " << dbEntry.e2eMangledName() << "/" << serverEntry.e2eMangledName
<< " | file lock: " << localFileIsLocked << "//" << serverFileIsLocked;
<< " | file lock: " << localFileIsLocked << "//" << serverFileIsLocked
<< " | metadata missing: /" << localEntry.isMetadataMissing << '/';

if (localEntry.isValid()
&& !serverEntry.isValid()
Expand Down Expand Up @@ -1073,6 +1074,7 @@
item->_type = ItemTypeVirtualFileDehydration;
} else if (!serverModified
&& (dbEntry._inode != localEntry.inode
|| localEntry.isMetadataMissing
|| _discoveryData->_syncOptions._vfs->needsMetadataUpdate(*item))) {
item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
item->_direction = SyncFileItem::Down;
Expand Down
1 change: 1 addition & 0 deletions src/libsync/discoveryphase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ void DiscoverySingleLocalDirectoryJob::run() {
i.isHidden = dirent->is_hidden;
i.isSymLink = dirent->type == ItemTypeSoftLink;
i.isVirtualFile = dirent->type == ItemTypeVirtualFile || dirent->type == ItemTypeVirtualFileDownload;
i.isMetadataMissing = dirent->is_metadata_missing;
i.type = dirent->type;
results.push_back(i);
}
Expand Down
1 change: 1 addition & 0 deletions src/libsync/discoveryphase.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ struct LocalInfo
bool isHidden = false;
bool isVirtualFile = false;
bool isSymLink = false;
bool isMetadataMissing = false;
[[nodiscard]] bool isValid() const { return !name.isNull(); }
};

Expand Down
11 changes: 11 additions & 0 deletions src/libsync/vfs/cfapi/cfapiwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -862,3 +862,14 @@ OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::co
return stateResult;
}
}

OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::revertPlaceholder(const QString &path)
{
const auto result = CfRevertPlaceholder(handleForPath(path).get(), CF_REVERT_FLAG_NONE, nullptr);
if (result != S_OK) {
qCWarning(lcCfApiWrapper) << "Couldn't revert placeholder for" << path << ":" << QString::fromWCharArray(_com_error(result).ErrorMessage());
return {"Couldn't revert placeholder"};
}

return OCC::Vfs::ConvertToPlaceholderResult::Ok;
}
1 change: 1 addition & 0 deletions src/libsync/vfs/cfapi/cfapiwrapper.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*

Check notice on line 1 in src/libsync/vfs/cfapi/cfapiwrapper.h

View workflow job for this annotation

GitHub Actions / build

Run clang-format on src/libsync/vfs/cfapi/cfapiwrapper.h

File src/libsync/vfs/cfapi/cfapiwrapper.h (lines 100): Code does not conform to Custom style guidelines.
* Copyright (C) by Kevin Ottens <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
Expand Down Expand Up @@ -97,6 +97,7 @@
NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath = QString());
NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath);
NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> dehydratePlaceholder(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId);
NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> revertPlaceholder(const QString &path);

}

Expand Down
6 changes: 4 additions & 2 deletions src/libsync/vfs/cfapi/vfs_cfapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,17 +264,19 @@ bool VfsCfApi::statTypeVirtualFile(csync_file_stat_t *stat, void *statData)
const auto isPinned = (ffd->dwFileAttributes & FILE_ATTRIBUTE_PINNED) != 0;
const auto isUnpinned = (ffd->dwFileAttributes & FILE_ATTRIBUTE_UNPINNED) != 0;
const auto hasReparsePoint = (ffd->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
const auto hasCloudTag = (ffd->dwReserved0 & IO_REPARSE_TAG_CLOUD) != 0;
const auto hasCloudTag = hasReparsePoint && (ffd->dwReserved0 & ~IO_REPARSE_TAG_CLOUD_MASK) == (IO_REPARSE_TAG_CLOUD & ~IO_REPARSE_TAG_CLOUD_MASK);

const auto isWindowsShortcut = !isDirectory && FileSystem::isLnkFile(stat->path);

const auto isExcludeFile = !isDirectory && FileSystem::isExcludeFile(stat->path);

stat->is_metadata_missing = !hasCloudTag;

// It's a dir with a reparse point due to the placeholder info (hence the cloud tag)
// if we don't remove the reparse point flag the discovery will end up thinking
// it is a file... let's prevent it
if (isDirectory) {
if (hasReparsePoint && hasCloudTag) {
if (hasCloudTag) {
ffd->dwFileAttributes &= ~FILE_ATTRIBUTE_REPARSE_POINT;
}
return false;
Expand Down
39 changes: 39 additions & 0 deletions test/testsynccfapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,45 @@ private slots:
QTest::newRow("skip local discovery") << false;
}

void testReplaceFileByIdenticalFile()
{
FakeFolder fakeFolder{FileInfo{}};
auto vfs = setupVfs(fakeFolder);
ItemCompletedSpy completeSpy(fakeFolder);

// Create a new local (non-placeholder) file
fakeFolder.localModifier().insert("file0");
fakeFolder.localModifier().insert("file1");
CopyFile(QString(fakeFolder.localPath() + "file1").toStdWString().data(), QString(fakeFolder.localPath() + "file2").toStdWString().data(), false);
QVERIFY(!vfs->pinState("file0").isValid());
QVERIFY(!vfs->pinState("file1").isValid());
QVERIFY(!vfs->pinState("file2").isValid());

// Sync the files: files should be converted to placeholder files
QVERIFY(fakeFolder.syncOnce());
QVERIFY(vfs->pinState("file0").isValid());
QVERIFY(vfs->pinState("file1").isValid());
QVERIFY(vfs->pinState("file2").isValid());

// Sync again to ensure items are fully synced, otherwise test may succeed due to those pending changes.
QVERIFY(fakeFolder.syncOnce());
completeSpy.clear();
QVERIFY(fakeFolder.syncOnce());
QVERIFY(completeSpy.isEmpty());

// Replace file1 by identical file2: Windows will convert file1 to a regular (non-placeholder) file again.
CopyFile(QString(fakeFolder.localPath() + "file2").toStdWString().data(), QString(fakeFolder.localPath() + "file1").toStdWString().data(), false);
QVERIFY(vfs->pinState("file0").isValid());
QVERIFY(!vfs->pinState("file1").isValid());
QVERIFY(vfs->pinState("file2").isValid());

// Sync again: file should be correctly converted to placeholders
QVERIFY(fakeFolder.syncOnce());
QVERIFY(vfs->pinState("file0").isValid());
QVERIFY(vfs->pinState("file1").isValid());
QVERIFY(vfs->pinState("file2").isValid());
}

void testReplaceOnlineOnlyFile()
{
FakeFolder fakeFolder{FileInfo{}};
Expand Down
Loading