feat(vfs): skeleton of a new linux VFS plugin

should allow building a new linux VFS plugin to integrate within desktop
environment via a DBus API for reuse in KIO or GIO subsystems

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
This commit is contained in:
Matthieu Gallien 2025-05-14 10:32:31 +02:00
parent 3e39a89d47
commit e22fc51d3e
No known key found for this signature in database
GPG Key ID: 7D0F74F05C22F553
10 changed files with 869 additions and 1 deletions

View File

@ -37,6 +37,8 @@ QString Vfs::modeToString(Mode mode)
return QStringLiteral("wincfapi");
case XAttr:
return QStringLiteral("xattr");
case DBusApi:
return QStringLiteral("dbusapi");
}
return QStringLiteral("off");
}
@ -50,6 +52,8 @@ Optional<Vfs::Mode> Vfs::modeFromString(const QString &str)
return WithSuffix;
} else if (str == QLatin1String("wincfapi")) {
return WindowsCfApi;
} else if (str == QLatin1String("dbusapi")) {
return DBusApi;
}
return {};
}
@ -195,6 +199,10 @@ Vfs::Mode OCC::bestAvailableVfsMode()
return Vfs::WindowsCfApi;
}
if (isVfsPluginAvailable(Vfs::DBusApi)) {
return Vfs::DBusApi;
}
if (isVfsPluginAvailable(Vfs::WithSuffix)) {
return Vfs::WithSuffix;
}

View File

@ -96,6 +96,7 @@ public:
WithSuffix,
WindowsCfApi,
XAttr,
DBusApi,
};
Q_ENUM(Mode)
enum class ConvertToPlaceholderResult {

View File

@ -107,6 +107,8 @@ OwncloudAdvancedSetupPage::OwncloudAdvancedSetupPage(OwncloudWizard *wizard)
bestAvailableVfsMode() == Vfs::WindowsCfApi
#elif defined(BUILD_FILE_PROVIDER_MODULE)
Mac::FileProvider::fileProviderAvailable()
#elif defined Q_OS_LINUX
bestAvailableVfsMode() == Vfs::DBusApi
#else
false
#endif

View File

@ -476,6 +476,9 @@ void OwncloudWizard::askExperimentalVirtualFilesFeature(QWidget *receiver, const
case Vfs::WindowsCfApi:
callback(true);
return;
case Vfs::DBusApi:
callback(true);
return;
case Vfs::WithSuffix:
msgBox = new QMessageBox(
QMessageBox::Warning,

View File

@ -5,7 +5,7 @@
# that create directories for the build.
#file(GLOB VIRTUAL_FILE_SYSTEM_PLUGINS RELATIVE ${CMAKE_CURRENT_LIST_DIR} "*")
list(APPEND VIRTUAL_FILE_SYSTEM_PLUGINS "suffix" "cfapi" "xattr")
list(APPEND VIRTUAL_FILE_SYSTEM_PLUGINS "suffix" "cfapi" "xattr" "dbusapi")
message("list of plugins ${VIRTUAL_FILE_SYSTEM_PLUGINS}")

View File

@ -0,0 +1,46 @@
# SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: GPL-2.0-or-later
if (LINUX)
add_library(nextcloudsync_vfs_dbus SHARED
hydrationjob.h
hydrationjob.cpp
vfs_dbusapi.h
vfs_dbusapi.cpp
)
target_link_libraries(nextcloudsync_vfs_dbus PRIVATE
Nextcloud::sync
)
set_target_properties(nextcloudsync_vfs_dbus
PROPERTIES
LIBRARY_OUTPUT_DIRECTORY
${BIN_OUTPUT_DIRECTORY}
RUNTIME_OUTPUT_DIRECTORY
${BIN_OUTPUT_DIRECTORY}
PREFIX
""
AUTOMOC
TRUE
LIBRARY_OUTPUT_NAME
${APPLICATION_EXECUTABLE}sync_vfs_dbus
RUNTIME_OUTPUT_NAME
${APPLICATION_EXECUTABLE}sync_vfs_dbus
)
target_include_directories(nextcloudsync_vfs_dbus BEFORE PUBLIC ${CMAKE_CURRENT_BINARY_DIR} INTERFACE ${CMAKE_BINARY_DIR})
set(vfs_installdir "${PLUGINDIR}")
generate_export_header(nextcloudsync_vfs_dbus
BASE_NAME nextcloudsync_vfs_dbus
EXPORT_MACRO_NAME NEXTCLOUD_CFAPI_EXPORT
EXPORT_FILE_NAME cfapiexport.h
)
install(TARGETS nextcloudsync_vfs_dbus
LIBRARY DESTINATION "${vfs_installdir}"
RUNTIME DESTINATION "${vfs_installdir}"
)
endif()

View File

@ -0,0 +1,368 @@
/*
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "hydrationjob.h"
#include "common/syncjournaldb.h"
#include "propagatedownload.h"
#include "vfs/cfapi/vfs_cfapi.h"
#include <clientsideencryptionjobs.h>
#include "encryptedfoldermetadatahandler.h"
#include "foldermetadata.h"
#include "filesystem.h"
#include <QLocalServer>
#include <QLocalSocket>
Q_LOGGING_CATEGORY(lcHydration, "nextcloud.sync.vfs.hydrationjob", QtInfoMsg)
OCC::HydrationJob::HydrationJob(QObject *parent)
: QObject(parent)
{
}
OCC::HydrationJob::~HydrationJob() = default;
OCC::AccountPtr OCC::HydrationJob::account() const
{
return _account;
}
void OCC::HydrationJob::setAccount(const AccountPtr &account)
{
_account = account;
}
QString OCC::HydrationJob::remoteSyncRootPath() const
{
return _remoteSyncRootPath;
}
void OCC::HydrationJob::setRemoteSyncRootPath(const QString &path)
{
_remoteSyncRootPath = Utility::noLeadingSlashPath(path);
}
QString OCC::HydrationJob::localPath() const
{
return _localPath;
}
void OCC::HydrationJob::setLocalPath(const QString &localPath)
{
_localPath = localPath;
}
OCC::SyncJournalDb *OCC::HydrationJob::journal() const
{
return _journal;
}
void OCC::HydrationJob::setJournal(SyncJournalDb *journal)
{
_journal = journal;
}
QString OCC::HydrationJob::requestId() const
{
return _requestId;
}
void OCC::HydrationJob::setRequestId(const QString &requestId)
{
_requestId = requestId;
}
QString OCC::HydrationJob::folderPath() const
{
return _folderPath;
}
void OCC::HydrationJob::setFolderPath(const QString &folderPath)
{
_folderPath = folderPath;
}
bool OCC::HydrationJob::isEncryptedFile() const
{
return _isEncryptedFile;
}
void OCC::HydrationJob::setIsEncryptedFile(bool isEncrypted)
{
_isEncryptedFile = isEncrypted;
}
QString OCC::HydrationJob::e2eMangledName() const
{
return _e2eMangledName;
}
void OCC::HydrationJob::setE2eMangledName(const QString &e2eMangledName)
{
_e2eMangledName = e2eMangledName;
}
OCC::HydrationJob::Status OCC::HydrationJob::status() const
{
return _status;
}
int OCC::HydrationJob::errorCode() const
{
return _errorCode;
}
int OCC::HydrationJob::statusCode() const
{
return _statusCode;
}
QString OCC::HydrationJob::errorString() const
{
return _errorString;
}
void OCC::HydrationJob::start()
{
Q_ASSERT(_account);
Q_ASSERT(_journal);
Q_ASSERT(!_remoteSyncRootPath.isEmpty() && !_localPath.isEmpty());
Q_ASSERT(!_requestId.isEmpty() && !_folderPath.isEmpty());
Q_ASSERT(_remoteSyncRootPath.endsWith('/'));
Q_ASSERT(_localPath.endsWith('/'));
Q_ASSERT(!_folderPath.startsWith('/'));
const auto startServer = [this](const QString &serverName) -> QLocalServer * {
const auto server = new QLocalServer(this);
const auto listenResult = server->listen(serverName);
if (!listenResult) {
qCCritical(lcHydration) << "Couldn't get server to listen" << serverName
<< _localPath << _folderPath;
if (!_isCancelled) {
emitFinished(Error);
}
return nullptr;
}
qCInfo(lcHydration) << "Server ready, waiting for connections" << serverName
<< _localPath << _folderPath;
return server;
};
// Start cancellation server
_signalServer = startServer(_requestId + ":cancellation");
Q_ASSERT(_signalServer);
if (!_signalServer) {
return;
}
connect(_signalServer, &QLocalServer::newConnection, this, &HydrationJob::onCancellationServerNewConnection);
// Start transfer data server
_transferDataServer = startServer(_requestId);
Q_ASSERT(_transferDataServer);
if (!_transferDataServer) {
return;
}
connect(_transferDataServer, &QLocalServer::newConnection, this, &HydrationJob::onNewConnection);
}
void OCC::HydrationJob::cancel()
{
_isCancelled = true;
if (_job) {
_job->cancel();
}
if (_signalSocket) {
_signalSocket->write("cancelled");
_signalSocket->close();
}
if (_transferDataSocket) {
_transferDataSocket->close();
}
emitFinished(Cancelled);
}
void OCC::HydrationJob::emitFinished(Status status)
{
_status = status;
if (_signalSocket) {
_signalSocket->close();
}
if (status == Success) {
connect(_transferDataSocket, &QLocalSocket::disconnected, this, [=, this] {
_transferDataSocket->close();
emit finished(this);
});
_transferDataSocket->disconnectFromServer();
return;
}
if (_transferDataSocket) {
_transferDataSocket->close();
}
emit finished(this);
}
void OCC::HydrationJob::onCancellationServerNewConnection()
{
Q_ASSERT(!_signalSocket);
qCInfo(lcHydration) << "Got new connection on cancellation server" << _requestId << _folderPath;
_signalSocket = _signalServer->nextPendingConnection();
}
void OCC::HydrationJob::onNewConnection()
{
Q_ASSERT(!_transferDataSocket);
Q_ASSERT(!_job);
if (isEncryptedFile()) {
handleNewConnectionForEncryptedFile();
} else {
handleNewConnection();
}
}
void OCC::HydrationJob::finalize(OCC::VfsCfApi *vfs)
{
// Mark the file as hydrated in the sync journal
SyncJournalFileRecord record;
if (!_journal->getFileRecord(_folderPath, &record)) {
qCWarning(lcHydration) << "could not get file from local DB" << _folderPath;
return;
}
Q_ASSERT(record.isValid());
if (!record.isValid()) {
qCWarning(lcHydration) << "Couldn't find record to update after hydration" << _requestId << _folderPath;
// emitFinished(Error);
return;
}
if (_isCancelled) {
// Remove placeholder file because there might be already pumped
// some data into it
QFile::remove(_localPath + _folderPath);
// Create a new placeholder file
const auto item = SyncFileItem::fromSyncJournalFileRecord(record);
vfs->createPlaceholder(*item);
return;
}
switch(_status) {
case Success:
record._type = ItemTypeFile;
break;
case Error:
case Cancelled:
record._type = CSyncEnums::ItemTypeVirtualFile;
break;
};
// store the actual size of a file that has been decrypted as we will need its actual size when dehydrating it if requested
record._fileSize = FileSystem::getSize(localPath() + folderPath());
const auto result = _journal->setFileRecord(record);
if (!result) {
qCWarning(lcHydration) << "Error when setting the file record to the database" << record._path << result.error();
}
}
void OCC::HydrationJob::slotFetchMetadataJobFinished(int statusCode, const QString &message)
{
if (statusCode != 200) {
qCCritical(lcHydration) << "Failed to find encrypted metadata information of remote file" << e2eMangledName() << message;
emitFinished(Error);
return;
}
// TODO: the following code is borrowed from PropagateDownloadEncrypted (see HydrationJob::onNewConnection() for explanation of next steps)
qCDebug(lcHydration) << "Metadata Received reading" << e2eMangledName();
const auto metadata = _encryptedFolderMetadataHandler->folderMetadata();
if (!metadata->isValid()) {
qCCritical(lcHydration) << "Failed to find encrypted metadata information of a remote file" << e2eMangledName();
emitFinished(Error);
return;
}
const auto files = metadata->files();
const QString encryptedFileExactName = e2eMangledName().section(QLatin1Char('/'), -1);
for (const FolderMetadata::EncryptedFile &file : files) {
if (encryptedFileExactName == file.encryptedFilename) {
qCDebug(lcHydration) << "Found matching encrypted metadata for file, starting download" << _requestId << _folderPath;
_transferDataSocket = _transferDataServer->nextPendingConnection();
_job = new GETEncryptedFileJob(_account, Utility::trailingSlashPath(_remoteSyncRootPath) + e2eMangledName(), _transferDataSocket, {}, {}, 0, file, this);
connect(qobject_cast<GETEncryptedFileJob *>(_job), &GETEncryptedFileJob::finishedSignal, this, &HydrationJob::onGetFinished);
_job->start();
return;
}
}
}
void OCC::HydrationJob::onGetFinished()
{
_errorCode = _job->reply()->error();
_statusCode = _job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (_errorCode != 0 || (_statusCode != 200 && _statusCode != 204)) {
_errorString = _job->reply()->errorString();
}
if (!_errorString.isEmpty()) {
qCInfo(lcHydration) << "GETFileJob finished" << _requestId << _folderPath << _errorCode << _statusCode << _errorString;
} else {
qCInfo(lcHydration) << "GETFileJob finished" << _requestId << _folderPath;
}
// GETFileJob deletes itself after this signal was handled
_job = nullptr;
if (_isCancelled) {
_errorCode = 0;
_statusCode = 0;
_errorString.clear();
return;
}
if (_errorCode) {
emitFinished(Error);
return;
}
emitFinished(Success);
}
void OCC::HydrationJob::handleNewConnection()
{
qCInfo(lcHydration) << "Got new connection starting GETFileJob" << _requestId << _folderPath;
_transferDataSocket = _transferDataServer->nextPendingConnection();
_job = new GETFileJob(_account, Utility::trailingSlashPath(_remoteSyncRootPath) + _folderPath, _transferDataSocket, {}, {}, 0, this);
connect(_job, &GETFileJob::finishedSignal, this, &HydrationJob::onGetFinished);
_job->start();
}
void OCC::HydrationJob::handleNewConnectionForEncryptedFile()
{
// TODO: the following code is borrowed from PropagateDownloadEncrypted (should we factor it out and reuse? YES! Should we do it now? Probably not, as, this would imply modifying PropagateDownloadEncrypted, so we need a separate PR)
qCInfo(lcHydration) << "Got new connection for encrypted file. Getting required info for decryption...";
const auto remoteFilename = e2eMangledName();
const QString fullRemotePath = Utility::trailingSlashPath(_remoteSyncRootPath) + remoteFilename;
const auto containingFolderFullRemotePath = fullRemotePath.left(fullRemotePath.lastIndexOf('/'));
SyncJournalFileRecord rec;
if (!_journal->getRootE2eFolderRecord(Utility::fullRemotePathToRemoteSyncRootRelative(containingFolderFullRemotePath, _remoteSyncRootPath), &rec) || !rec.isValid()) {
emitFinished(Error);
return;
}
_encryptedFolderMetadataHandler.reset(
new EncryptedFolderMetadataHandler(_account, containingFolderFullRemotePath, _remoteSyncRootPath, _journal, rec.path()));
connect(_encryptedFolderMetadataHandler.data(),
&EncryptedFolderMetadataHandler::fetchFinished,
this,
&HydrationJob::slotFetchMetadataJobFinished);
_encryptedFolderMetadataHandler->fetchMetadata();
}

View File

@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QObject>
#include "account.h"
class QLocalServer;
class QLocalSocket;
namespace OCC {
class EncryptedFolderMetadataHandler;
class GETFileJob;
class SyncJournalDb;
class VfsCfApi;
namespace EncryptionHelper {
class StreamingDecryptor;
};
class HydrationJob : public QObject
{
Q_OBJECT
public:
enum Status {
Success = 0,
Error,
Cancelled,
};
Q_ENUM(Status)
explicit HydrationJob(QObject *parent = nullptr);
~HydrationJob() override;
AccountPtr account() const;
void setAccount(const AccountPtr &account);
[[nodiscard]] QString remoteSyncRootPath() const;
void setRemoteSyncRootPath(const QString &path);
QString localPath() const;
void setLocalPath(const QString &localPath);
SyncJournalDb *journal() const;
void setJournal(SyncJournalDb *journal);
QString requestId() const;
void setRequestId(const QString &requestId);
QString folderPath() const;
void setFolderPath(const QString &folderPath);
bool isEncryptedFile() const;
void setIsEncryptedFile(bool isEncrypted);
QString e2eMangledName() const;
void setE2eMangledName(const QString &e2eMangledName);
qint64 fileTotalSize() const;
void setFileTotalSize(qint64 totalSize);
Status status() const;
[[nodiscard]] int errorCode() const;
[[nodiscard]] int statusCode() const;
[[nodiscard]] QString errorString() const;
void start();
void cancel();
void finalize(OCC::VfsCfApi *vfs);
signals:
void finished(HydrationJob *job);
private slots:
void slotFetchMetadataJobFinished(int statusCode, const QString &message);
private:
void emitFinished(Status status);
void onNewConnection();
void onCancellationServerNewConnection();
void onGetFinished();
void handleNewConnection();
void handleNewConnectionForEncryptedFile();
void startServerAndWaitForConnections();
AccountPtr _account;
QString _remoteSyncRootPath;
QString _localPath;
SyncJournalDb *_journal = nullptr;
bool _isCancelled = false;
QString _requestId;
QString _folderPath;
bool _isEncryptedFile = false;
QString _e2eMangledName;
QLocalServer *_transferDataServer = nullptr;
QLocalServer *_signalServer = nullptr;
QLocalSocket *_transferDataSocket = nullptr;
QLocalSocket *_signalSocket = nullptr;
GETFileJob *_job = nullptr;
Status _status = Success;
int _errorCode = 0;
int _statusCode = 0;
QString _errorString;
QString _remoteParentPath;
QScopedPointer<EncryptedFolderMetadataHandler> _encryptedFolderMetadataHandler;
};
} // namespace OCC

View File

@ -0,0 +1,230 @@
/*
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "vfs_dbusapi.h"
#include <QDir>
#include <QFile>
#include "syncfileitem.h"
#include "filesystem.h"
#include "common/filesystembase.h"
#include "common/syncjournaldb.h"
#include "config.h"
#include <QCoreApplication>
Q_LOGGING_CATEGORY(lcDBusApi, "nextcloud.sync.vfs.dbusapi", QtInfoMsg)
namespace OCC {
class VfsDBusApiPrivate
{
public:
QList<HydrationJob *> hydrationJobs;
};
VfsDBusApi::VfsDBusApi(QObject *parent)
: Vfs(parent)
, d(new VfsDBusApiPrivate)
{
}
VfsDBusApi::~VfsDBusApi() = default;
Vfs::Mode VfsDBusApi::mode() const
{
return DBusApi;
}
QString VfsDBusApi::fileSuffix() const
{
return {};
}
void VfsDBusApi::startImpl(const VfsSetupParams &params)
{
}
void VfsDBusApi::stop()
{
}
void VfsDBusApi::unregisterFolder()
{
}
bool VfsDBusApi::socketApiPinStateActionsShown() const
{
return true;
}
bool VfsDBusApi::isHydrating() const
{
return !d->hydrationJobs.isEmpty();
}
Result<void, QString> VfsDBusApi::updateMetadata(const QString &filePath, time_t modtime, qint64 size, const QByteArray &fileId)
{
}
Result<Vfs::ConvertToPlaceholderResult, QString> VfsDBusApi::updatePlaceholderMarkInSync(const QString &filePath, const QByteArray &fileId)
{
return ConvertToPlaceholderResult::Error;
}
bool VfsDBusApi::isPlaceHolderInSync(const QString &filePath) const
{
return false;
}
Result<void, QString> VfsDBusApi::createPlaceholder(const SyncFileItem &item)
{
return {};
}
Result<void, QString> VfsDBusApi::dehydratePlaceholder(const SyncFileItem &item)
{
return {};
}
Result<Vfs::ConvertToPlaceholderResult, QString> VfsDBusApi::convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &replacesFile, UpdateMetadataTypes updateType)
{
return ConvertToPlaceholderResult::Error;
}
bool VfsDBusApi::needsMetadataUpdate(const SyncFileItem &item)
{
return false;
}
bool VfsDBusApi::isDehydratedPlaceholder(const QString &filePath)
{
return false;
}
bool VfsDBusApi::statTypeVirtualFile(csync_file_stat_t *stat, void *statData)
{
return false;
}
bool VfsDBusApi::setPinState(const QString &folderPath, PinState state)
{
qCDebug(lcDBusApi()) << "setPinState" << folderPath << state;
return setPinStateInDb(folderPath, state);
}
Optional<PinState> VfsDBusApi::pinState(const QString &folderPath)
{
return pinStateInDb(folderPath);
}
Vfs::AvailabilityResult VfsDBusApi::availability(const QString &folderPath, const AvailabilityRecursivity recursiveCheck)
{
return AvailabilityError::NoSuchItem;
}
void VfsDBusApi::cancelHydration(const QString &requestId, const QString & /*path*/)
{
}
void VfsDBusApi::requestHydration(const QString &requestId, const QString &path)
{
qCInfo(lcDBusApi) << "Received request to hydrate" << path << requestId;
const auto root = QDir::toNativeSeparators(params().filesystemPath);
Q_ASSERT(path.startsWith(root));
const auto relativePath = QDir::fromNativeSeparators(path.mid(root.length()));
const auto journal = params().journal;
// Set in the database that we should download the file
SyncJournalFileRecord record;
if (!journal->getFileRecord(relativePath, &record) || !record.isValid()) {
qCInfo(lcDBusApi) << "Couldn't hydrate, did not find file in db";
emit hydrationRequestFailed(requestId);
return;
}
bool isNotVirtualFileFailure = false;
if (!record.isVirtualFile()) {
if (isDehydratedPlaceholder(path)) {
qCWarning(lcDBusApi) << "Hydration requested for a placeholder file not marked as virtual in local DB. Attempting to fix it...";
record._type = ItemTypeVirtualFileDownload;
isNotVirtualFileFailure = !journal->setFileRecord(record);
} else {
isNotVirtualFileFailure = true;
}
}
if (isNotVirtualFileFailure) {
qCWarning(lcDBusApi) << "Couldn't hydrate, the file is not virtual";
emit hydrationRequestFailed(requestId);
return;
}
}
void VfsDBusApi::fileStatusChanged(const QString &systemFileName, SyncFileStatus fileStatus)
{
Q_UNUSED(systemFileName);
Q_UNUSED(fileStatus);
}
int VfsDBusApi::finalizeHydrationJob(const QString &requestId)
{
return -1;
}
VfsDBusApi::HydratationAndPinStates VfsDBusApi::computeRecursiveHydrationAndPinStates(const QString &folderPath, const Optional<PinState> &basePinState)
{
Q_ASSERT(!folderPath.endsWith('/'));
const auto fullPath = QString{params().filesystemPath + folderPath};
QFileInfo info(params().filesystemPath + folderPath);
if (!FileSystem::fileExists(fullPath)) {
return {};
}
const auto effectivePin = pinState(folderPath);
const auto pinResult = (!effectivePin && !basePinState) ? Optional<PinState>()
: (!effectivePin || !basePinState) ? PinState::Inherited
: (*effectivePin == *basePinState) ? *effectivePin
: PinState::Inherited;
if (FileSystem::isDir(fullPath)) {
const auto dirState = HydratationAndPinStates {
pinResult,
{}
};
const auto dir = QDir(info.absoluteFilePath());
Q_ASSERT(dir.exists());
const auto children = dir.entryList();
return std::accumulate(std::cbegin(children), std::cend(children), dirState, [=, this](const HydratationAndPinStates &currentState, const QString &name) {
if (name == QStringLiteral("..") || name == QStringLiteral(".")) {
return currentState;
}
// if the folderPath.isEmpty() we don't want to end up having path "/example.file" because this will lead to double slash later, when appending to "SyncFolder/"
const auto path = folderPath.isEmpty() ? name : folderPath + '/' + name;
const auto states = computeRecursiveHydrationAndPinStates(path, currentState.pinState);
return HydratationAndPinStates {
states.pinState,
{
states.hydrationStatus.hasHydrated || currentState.hydrationStatus.hasHydrated,
states.hydrationStatus.hasDehydrated || currentState.hydrationStatus.hasDehydrated,
}
};
});
} else { // file case
const auto isDehydrated = isDehydratedPlaceholder(info.absoluteFilePath());
return {
pinResult,
{
!isDehydrated,
isDehydrated
}
};
}
}
} // namespace OCC

View File

@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QObject>
#include <QScopedPointer>
#include "common/vfs.h"
#include "common/plugin.h"
namespace OCC {
class HydrationJob;
class VfsDBusApiPrivate;
class SyncJournalFileRecord;
class VfsDBusApi : public Vfs
{
Q_OBJECT
public:
explicit VfsDBusApi(QObject *parent = nullptr);
~VfsDBusApi();
Mode mode() const override;
QString fileSuffix() const override;
void stop() override;
void unregisterFolder() override;
bool socketApiPinStateActionsShown() const override;
bool isHydrating() const override;
Result<void, QString> updateMetadata(const QString &filePath, time_t modtime, qint64 size, const QByteArray &fileId) override;
Result<Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderMarkInSync(const QString &filePath, const QByteArray &fileId) override;
[[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override;
Result<void, QString> createPlaceholder(const SyncFileItem &item) override;
Result<void, QString> dehydratePlaceholder(const SyncFileItem &item) override;
Result<Vfs::ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &replacesFile, UpdateMetadataTypes updateType) override;
bool needsMetadataUpdate(const SyncFileItem &) override;
bool isDehydratedPlaceholder(const QString &filePath) override;
bool statTypeVirtualFile(csync_file_stat_t *stat, void *statData) override;
bool setPinState(const QString &folderPath, PinState state) override;
Optional<PinState> pinState(const QString &folderPath) override;
AvailabilityResult availability(const QString &folderPath, const AvailabilityRecursivity recursiveCheck) override;
void cancelHydration(const QString &requestId, const QString &path);
int finalizeHydrationJob(const QString &requestId);
public slots:
void requestHydration(const QString &requestId, const QString &path);
void fileStatusChanged(const QString &systemFileName, OCC::SyncFileStatus fileStatus) override;
signals:
void hydrationRequestReady(const QString &requestId);
void hydrationRequestFailed(const QString &requestId);
void hydrationRequestFinished(const QString &requestId);
protected:
void startImpl(const VfsSetupParams &params) override;
private:
struct HasHydratedDehydrated {
bool hasHydrated = false;
bool hasDehydrated = false;
};
struct HydratationAndPinStates {
Optional<PinState> pinState;
HasHydratedDehydrated hydrationStatus;
};
HydratationAndPinStates computeRecursiveHydrationAndPinStates(const QString &path, const Optional<PinState> &basePinState);
QScopedPointer<VfsDBusApiPrivate> d;
};
class DBusApiVfsPluginFactory : public QObject, public DefaultPluginFactory<VfsDBusApi>
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.owncloud.PluginFactory" FILE "vfspluginmetadata.json")
Q_INTERFACES(OCC::PluginFactory)
};
} // namespace OCC