mirror of
https://github.com/nextcloud/desktop.git
synced 2025-10-26 11:17:43 +00:00
Merge pull request #8568 from nextcloud/feature/bulkCreateWindowsPlaceholdersPerFolders
Feature/bulk create windows placeholders per folders (V1)
This commit is contained in:
commit
a1d263c7a7
@ -14,6 +14,7 @@
|
||||
#include <QScopedPointer>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
using csync_file_stat_t = struct csync_file_stat_s;
|
||||
@ -25,6 +26,7 @@ using AccountPtr = QSharedPointer<Account>;
|
||||
class SyncJournalDb;
|
||||
class VfsPrivate;
|
||||
class SyncFileItem;
|
||||
using SyncFileItemPtr = QSharedPointer<SyncFileItem>;
|
||||
|
||||
/** Collection of parameters for initializing a Vfs instance. */
|
||||
struct OCSYNC_EXPORT VfsSetupParams
|
||||
@ -186,21 +188,24 @@ public:
|
||||
[[nodiscard]] virtual bool isPlaceHolderInSync(const QString &filePath) const = 0;
|
||||
|
||||
/// Create a new dehydrated placeholder. Called from PropagateDownload.
|
||||
Q_REQUIRED_RESULT virtual Result<void, QString> createPlaceholder(const SyncFileItem &item) = 0;
|
||||
[[nodiscard]] virtual Result<void, QString> createPlaceholder(const SyncFileItem &item) = 0;
|
||||
|
||||
/// Create a new dehydrated list of placeholders
|
||||
[[nodiscard]] virtual Result<void, QString> createPlaceholders(const QList<SyncFileItemPtr> &items) = 0;
|
||||
|
||||
/** Convert a hydrated placeholder to a dehydrated one. Called from PropagateDownlaod.
|
||||
*
|
||||
* This is different from delete+create because preserving some file metadata
|
||||
* (like pin states) may be essential for some vfs plugins.
|
||||
*/
|
||||
Q_REQUIRED_RESULT virtual Result<void, QString> dehydratePlaceholder(const SyncFileItem &item) = 0;
|
||||
[[nodiscard]] virtual Result<void, QString> dehydratePlaceholder(const SyncFileItem &item) = 0;
|
||||
|
||||
/** Discovery hook: even unchanged files may need UPDATE_METADATA.
|
||||
*
|
||||
* For instance cfapi vfs wants local hydrated non-placeholder files to
|
||||
* become hydrated placeholder files.
|
||||
*/
|
||||
Q_REQUIRED_RESULT virtual bool needsMetadataUpdate(const SyncFileItem &item) = 0;
|
||||
[[nodiscard]] virtual bool needsMetadataUpdate(const SyncFileItem &item) = 0;
|
||||
|
||||
/** Convert a new file to a hydrated placeholder.
|
||||
*
|
||||
@ -215,13 +220,13 @@ public:
|
||||
* new placeholder shall supersede, for rename-replace actions with new downloads,
|
||||
* for example.
|
||||
*/
|
||||
Q_REQUIRED_RESULT virtual Result<Vfs::ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &filename,
|
||||
[[nodiscard]] virtual Result<Vfs::ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &filename,
|
||||
const SyncFileItem &item,
|
||||
const QString &replacesFile = {},
|
||||
UpdateMetadataTypes updateType = AllMetadata) = 0;
|
||||
|
||||
/// Determine whether the file at the given absolute path is a dehydrated placeholder.
|
||||
Q_REQUIRED_RESULT virtual bool isDehydratedPlaceholder(const QString &filePath) = 0;
|
||||
[[nodiscard]] virtual bool isDehydratedPlaceholder(const QString &filePath) = 0;
|
||||
|
||||
/** Similar to isDehydratedPlaceholder() but used from sync discovery.
|
||||
*
|
||||
@ -230,7 +235,7 @@ public:
|
||||
*
|
||||
* Returning true means that type was fully determined.
|
||||
*/
|
||||
Q_REQUIRED_RESULT virtual bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) = 0;
|
||||
[[nodiscard]] virtual bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) = 0;
|
||||
|
||||
/** Sets the pin state for the item at a path.
|
||||
*
|
||||
@ -241,7 +246,7 @@ public:
|
||||
*
|
||||
* folderPath is relative to the sync folder. Can be "" for root folder.
|
||||
*/
|
||||
Q_REQUIRED_RESULT virtual bool setPinState(const QString &folderPath, PinState state) = 0;
|
||||
[[nodiscard]] virtual bool setPinState(const QString &folderPath, PinState state) = 0;
|
||||
|
||||
/** Returns the pin state of an item at a path.
|
||||
*
|
||||
@ -252,7 +257,7 @@ public:
|
||||
*
|
||||
* Returns none on retrieval error.
|
||||
*/
|
||||
Q_REQUIRED_RESULT virtual Optional<PinState> pinState(const QString &folderPath) = 0;
|
||||
[[nodiscard]] virtual Optional<PinState> pinState(const QString &folderPath) = 0;
|
||||
|
||||
/** Returns availability status of an item at a path.
|
||||
*
|
||||
@ -261,7 +266,7 @@ public:
|
||||
*
|
||||
* folderPath is relative to the sync folder. Can be "" for root folder.
|
||||
*/
|
||||
Q_REQUIRED_RESULT virtual AvailabilityResult availability(const QString &folderPath, const AvailabilityRecursivity recursiveCheck) = 0;
|
||||
[[nodiscard]] virtual AvailabilityResult availability(const QString &folderPath, const AvailabilityRecursivity recursiveCheck) = 0;
|
||||
|
||||
public slots:
|
||||
/** Update in-sync state based on SyncFileStatusTracker signal.
|
||||
@ -323,7 +328,10 @@ public:
|
||||
OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> updateMetadata(const SyncFileItem &, const QString &, const QString &) override { return {OCC::Vfs::ConvertToPlaceholderResult::Ok}; }
|
||||
Result<Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderMarkInSync(const QString &filePath, const QByteArray &fileId) override {Q_UNUSED(filePath) Q_UNUSED(fileId) return {QString{}};}
|
||||
[[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override { Q_UNUSED(filePath) return true; }
|
||||
|
||||
Result<void, QString> createPlaceholder(const SyncFileItem &) override { return {}; }
|
||||
Result<void, QString> createPlaceholders(const QList<SyncFileItemPtr> &) override { return {}; }
|
||||
|
||||
Result<void, QString> dehydratePlaceholder(const SyncFileItem &) override { return {}; }
|
||||
Result<ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &, const SyncFileItem &, const QString &, const UpdateMetadataTypes) override { return ConvertToPlaceholderResult::Ok; }
|
||||
|
||||
|
||||
@ -120,7 +120,7 @@ public:
|
||||
/*
|
||||
* Get whether the share has a password set
|
||||
*/
|
||||
[[nodiscard]] Q_REQUIRED_RESULT bool isPasswordSet() const;
|
||||
[[nodiscard]] bool isPasswordSet() const;
|
||||
|
||||
/*
|
||||
* Is it a share with a user or group (local or remote)
|
||||
|
||||
@ -81,12 +81,12 @@ public:
|
||||
enum class FileDetailsPage { Activity, Sharing };
|
||||
Q_ENUM(FileDetailsPage);
|
||||
|
||||
Q_REQUIRED_RESULT QString windowTitle() const;
|
||||
Q_REQUIRED_RESULT bool useNormalWindow() const;
|
||||
[[nodiscard]] QString windowTitle() const;
|
||||
[[nodiscard]] bool useNormalWindow() const;
|
||||
|
||||
Q_REQUIRED_RESULT bool syncIsPaused() const;
|
||||
Q_REQUIRED_RESULT bool anySyncFolders() const;
|
||||
Q_REQUIRED_RESULT bool isOpen() const;
|
||||
[[nodiscard]] bool syncIsPaused() const;
|
||||
[[nodiscard]] bool anySyncFolders() const;
|
||||
[[nodiscard]] bool isOpen() const;
|
||||
|
||||
[[nodiscard]] bool enableAddAccount() const;
|
||||
|
||||
|
||||
@ -67,30 +67,30 @@ public:
|
||||
explicit UserStatusSelectorModel(const UserStatus &userStatus,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
Q_REQUIRED_RESULT int userIndex() const;
|
||||
[[nodiscard]] int userIndex() const;
|
||||
|
||||
Q_REQUIRED_RESULT UserStatus::OnlineStatus onlineStatus() const;
|
||||
[[nodiscard]] UserStatus::OnlineStatus onlineStatus() const;
|
||||
void setOnlineStatus(UserStatus::OnlineStatus status);
|
||||
|
||||
Q_REQUIRED_RESULT QUrl onlineIcon() const;
|
||||
Q_REQUIRED_RESULT QUrl awayIcon() const;
|
||||
Q_REQUIRED_RESULT QUrl dndIcon() const;
|
||||
Q_REQUIRED_RESULT QUrl busyIcon() const;
|
||||
Q_REQUIRED_RESULT QUrl invisibleIcon() const;
|
||||
[[nodiscard]] QUrl onlineIcon() const;
|
||||
[[nodiscard]] QUrl awayIcon() const;
|
||||
[[nodiscard]] QUrl dndIcon() const;
|
||||
[[nodiscard]] QUrl busyIcon() const;
|
||||
[[nodiscard]] QUrl invisibleIcon() const;
|
||||
|
||||
Q_REQUIRED_RESULT QString userStatusMessage() const;
|
||||
[[nodiscard]] QString userStatusMessage() const;
|
||||
void setUserStatusMessage(const QString &message);
|
||||
Q_REQUIRED_RESULT QString userStatusEmoji() const;
|
||||
[[nodiscard]] QString userStatusEmoji() const;
|
||||
void setUserStatusEmoji(const QString &emoji);
|
||||
|
||||
[[nodiscard]] QVector<UserStatus> predefinedStatuses() const;
|
||||
|
||||
Q_REQUIRED_RESULT QVariantList clearStageTypes() const;
|
||||
Q_REQUIRED_RESULT QString clearAtDisplayString() const;
|
||||
[[nodiscard]] QVariantList clearStageTypes() const;
|
||||
[[nodiscard]] QString clearAtDisplayString() const;
|
||||
[[nodiscard]] Q_INVOKABLE QString clearAtReadable(const OCC::UserStatus &status) const;
|
||||
|
||||
Q_REQUIRED_RESULT QString errorMessage() const;
|
||||
Q_REQUIRED_RESULT bool busyStatusSupported() const;
|
||||
[[nodiscard]] QString errorMessage() const;
|
||||
[[nodiscard]] bool busyStatusSupported() const;
|
||||
|
||||
public slots:
|
||||
void setUserIndex(const int userIndex);
|
||||
@ -117,10 +117,10 @@ private:
|
||||
void onMessageCleared();
|
||||
void onError(UserStatusConnector::Error error);
|
||||
|
||||
Q_REQUIRED_RESULT QString clearAtReadable(const Optional<ClearAt> &clearAt) const;
|
||||
Q_REQUIRED_RESULT QString clearAtStageToString(ClearStageType stage) const;
|
||||
Q_REQUIRED_RESULT QString timeDifferenceToString(int differenceSecs) const;
|
||||
Q_REQUIRED_RESULT Optional<ClearAt> clearStageTypeToDateTime(ClearStageType type) const;
|
||||
[[nodiscard]] QString clearAtReadable(const Optional<ClearAt> &clearAt) const;
|
||||
[[nodiscard]] QString clearAtStageToString(ClearStageType stage) const;
|
||||
[[nodiscard]] QString timeDifferenceToString(int differenceSecs) const;
|
||||
[[nodiscard]] Optional<ClearAt> clearStageTypeToDateTime(ClearStageType type) const;
|
||||
void setError(const QString &reason);
|
||||
void clearError();
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
|
||||
#include "bulkpropagatordownloadjob.h"
|
||||
|
||||
#include "owncloudpropagator_p.h"
|
||||
#include "syncfileitem.h"
|
||||
#include "syncengine.h"
|
||||
#include "common/syncjournaldb.h"
|
||||
@ -13,7 +12,6 @@
|
||||
#include "propagatorjobs.h"
|
||||
#include "filesystem.h"
|
||||
#include "account.h"
|
||||
#include "networkjobs.h"
|
||||
#include "propagatedownloadencrypted.h"
|
||||
|
||||
#include <QDir>
|
||||
@ -23,11 +21,10 @@ namespace OCC {
|
||||
Q_LOGGING_CATEGORY(lcBulkPropagatorDownloadJob, "nextcloud.sync.propagator.bulkdownload", QtInfoMsg)
|
||||
|
||||
BulkPropagatorDownloadJob::BulkPropagatorDownloadJob(OwncloudPropagator *propagator,
|
||||
PropagateDirectory *parentDirJob,
|
||||
const std::vector<SyncFileItemPtr> &items)
|
||||
: PropagatorJob(propagator)
|
||||
, _filesToDownload(items)
|
||||
, _parentDirJob(parentDirJob)
|
||||
PropagateDirectory *parentDirJob)
|
||||
: PropagatorJob{propagator}
|
||||
, _filesToDownload{}
|
||||
, _parentDirJob{parentDirJob}
|
||||
{
|
||||
}
|
||||
|
||||
@ -35,15 +32,15 @@ namespace
|
||||
{
|
||||
static QString makeRecallFileName(const QString &fn)
|
||||
{
|
||||
QString recallFileName(fn);
|
||||
auto recallFileName(fn);
|
||||
// Add _recall-XXXX before the extension.
|
||||
int dotLocation = recallFileName.lastIndexOf('.');
|
||||
auto dotLocation = recallFileName.lastIndexOf('.');
|
||||
// If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file)
|
||||
if (dotLocation <= recallFileName.lastIndexOf('/') + 1) {
|
||||
dotLocation = recallFileName.size();
|
||||
}
|
||||
|
||||
QString timeString = QDateTime::currentDateTimeUtc().toString("yyyyMMdd-hhmmss");
|
||||
const auto &timeString = QDateTime::currentDateTimeUtc().toString("yyyyMMdd-hhmmss");
|
||||
recallFileName.insert(dotLocation, "_.sys.admin#recall#-" + timeString);
|
||||
|
||||
return recallFileName;
|
||||
@ -55,28 +52,28 @@ void handleRecallFile(const QString &filePath, const QString &folderPath, SyncJo
|
||||
|
||||
FileSystem::setFileHidden(filePath, true);
|
||||
|
||||
QFile file(filePath);
|
||||
auto file = QFile{filePath};
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCWarning(lcBulkPropagatorDownloadJob) << "Could not open recall file" << file.errorString();
|
||||
return;
|
||||
}
|
||||
QFileInfo existingFile(filePath);
|
||||
QDir baseDir = existingFile.dir();
|
||||
const auto existingFile = QFileInfo{filePath};
|
||||
const auto &baseDir = existingFile.dir();
|
||||
|
||||
while (!file.atEnd()) {
|
||||
QByteArray line = file.readLine();
|
||||
auto line = file.readLine();
|
||||
line.chop(1); // remove trailing \n
|
||||
|
||||
QString recalledFile = QDir::cleanPath(baseDir.filePath(line));
|
||||
const auto &recalledFile = QDir::cleanPath(baseDir.filePath(line));
|
||||
if (!recalledFile.startsWith(folderPath) || !recalledFile.startsWith(baseDir.path())) {
|
||||
qCWarning(lcBulkPropagatorDownloadJob) << "Ignoring recall of " << recalledFile;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Path of the recalled file in the local folder
|
||||
QString localRecalledFile = recalledFile.mid(folderPath.size());
|
||||
const auto &localRecalledFile = recalledFile.mid(folderPath.size());
|
||||
|
||||
SyncJournalFileRecord record;
|
||||
auto record = SyncJournalFileRecord{};
|
||||
if (!journal.getFileRecord(localRecalledFile, &record) || !record.isValid()) {
|
||||
qCWarning(lcBulkPropagatorDownloadJob) << "No db entry for recall of" << localRecalledFile;
|
||||
continue;
|
||||
@ -84,7 +81,7 @@ void handleRecallFile(const QString &filePath, const QString &folderPath, SyncJo
|
||||
|
||||
qCInfo(lcBulkPropagatorDownloadJob) << "Recalling" << localRecalledFile << "Checksum:" << record._checksumHeader;
|
||||
|
||||
QString targetPath = makeRecallFileName(recalledFile);
|
||||
const auto &targetPath = makeRecallFileName(recalledFile);
|
||||
|
||||
qCDebug(lcBulkPropagatorDownloadJob) << "Copy recall file: " << recalledFile << " -> " << targetPath;
|
||||
// Remove the target first, QFile::copy will not overwrite it.
|
||||
@ -112,18 +109,11 @@ bool BulkPropagatorDownloadJob::scheduleSelfOrChild()
|
||||
|
||||
_state = Running;
|
||||
|
||||
for (const auto &fileToDownload : _filesToDownload) {
|
||||
qCDebug(lcBulkPropagatorDownloadJob) << "Scheduling bulk propagator job:" << this << "and starting download of item"
|
||||
<< "with file:" << fileToDownload->_file << "with size:" << fileToDownload->_size;
|
||||
_filesDownloading.push_back(fileToDownload);
|
||||
start(fileToDownload);
|
||||
}
|
||||
start();
|
||||
|
||||
_filesToDownload.clear();
|
||||
_state = Finished;
|
||||
|
||||
checkPropagationIsDone();
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
PropagatorJob::JobParallelism BulkPropagatorDownloadJob::parallelism() const
|
||||
@ -131,125 +121,68 @@ PropagatorJob::JobParallelism BulkPropagatorDownloadJob::parallelism() const
|
||||
return PropagatorJob::JobParallelism::FullParallelism;
|
||||
}
|
||||
|
||||
void BulkPropagatorDownloadJob::startAfterIsEncryptedIsChecked(const SyncFileItemPtr &item)
|
||||
{
|
||||
const auto &vfs = propagator()->syncOptions()._vfs;
|
||||
Q_ASSERT(vfs && vfs->mode() == Vfs::WindowsCfApi);
|
||||
Q_ASSERT(item->_type == ItemTypeVirtualFileDehydration || item->_type == ItemTypeVirtualFile);
|
||||
|
||||
if (propagator()->localFileNameClash(item->_file)) {
|
||||
_parentDirJob->appendTask(item);
|
||||
finalizeOneFile(item);
|
||||
return;
|
||||
}
|
||||
|
||||
// For virtual files just dehydrate or create the placeholder and be done
|
||||
if (item->_type == ItemTypeVirtualFileDehydration) {
|
||||
const auto fsPath = propagator()->fullLocalPath(item->_file);
|
||||
if (!FileSystem::verifyFileUnchanged(fsPath, item->_previousSize, item->_previousModtime)) {
|
||||
propagator()->_anotherSyncNeeded = true;
|
||||
item->_errorString = tr("File has changed since discovery");
|
||||
abortWithError(item, SyncFileItem::SoftError, tr("File has changed since discovery"));
|
||||
return;
|
||||
}
|
||||
qCDebug(lcBulkPropagatorDownloadJob) << "dehydrating file" << item->_file;
|
||||
const auto r = vfs->dehydratePlaceholder(*item);
|
||||
if (!r) {
|
||||
qCCritical(lcBulkPropagatorDownloadJob) << "Could not dehydrate a file" << QDir::toNativeSeparators(item->_file) << ":" << r.error();
|
||||
abortWithError(item, SyncFileItem::NormalError, r.error());
|
||||
return;
|
||||
}
|
||||
if (!propagator()->_journal->deleteFileRecord(item->_originalFile)) {
|
||||
qCWarning(lcBulkPropagatorDownloadJob) << "could not delete file from local DB" << item->_originalFile;
|
||||
abortWithError(item, SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(item->_originalFile));
|
||||
return;
|
||||
}
|
||||
} else if (item->_type == ItemTypeVirtualFile) {
|
||||
qCDebug(lcBulkPropagatorDownloadJob) << "creating virtual file" << item->_file;
|
||||
const auto r = vfs->createPlaceholder(*item);
|
||||
if (!r) {
|
||||
qCCritical(lcBulkPropagatorDownloadJob) << "Could not create a placholder for a file" << QDir::toNativeSeparators(item->_file) << ":" << r.error();
|
||||
abortWithError(item, SyncFileItem::NormalError, r.error());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// we should never get here, as BulkPropagatorDownloadJob must only ever be instantiated and only contain virtual files
|
||||
qCCritical(lcBulkPropagatorDownloadJob) << "File" << QDir::toNativeSeparators(item->_file) << "can not be downloaded because it is non virtual!";
|
||||
abortWithError(item, SyncFileItem::NormalError, tr("File %1 cannot be downloaded because it is non virtual!").arg(QDir::toNativeSeparators(item->_file)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!updateMetadata(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item->_remotePerm.isNull() && !item->_remotePerm.hasPermission(RemotePermissions::CanWrite)) {
|
||||
// make sure ReadOnly flag is preserved for placeholder, similarly to regular files
|
||||
FileSystem::setFileReadOnly(propagator()->fullLocalPath(item->_file), true);
|
||||
}
|
||||
finalizeOneFile(item);
|
||||
}
|
||||
|
||||
void BulkPropagatorDownloadJob::finalizeOneFile(const SyncFileItemPtr &file)
|
||||
{
|
||||
const auto foundIt = std::find_if(std::cbegin(_filesDownloading), std::cend(_filesDownloading), [&file](const auto &fileDownloading) {
|
||||
const auto foundIt = std::find_if(std::cbegin(_filesToDownload), std::cend(_filesToDownload), [&file](const auto &fileDownloading) {
|
||||
return fileDownloading == file;
|
||||
});
|
||||
if (foundIt != std::cend(_filesDownloading)) {
|
||||
if (foundIt != std::cend(_filesToDownload)) {
|
||||
emit propagator()->itemCompleted(file, ErrorCategory::GenericError);
|
||||
_filesDownloading.erase(foundIt);
|
||||
}
|
||||
checkPropagationIsDone();
|
||||
}
|
||||
|
||||
void BulkPropagatorDownloadJob::checkPropagationIsDone()
|
||||
{
|
||||
if (_filesToDownload.empty() && _filesDownloading.empty()) {
|
||||
qCInfo(lcBulkPropagatorDownloadJob) << "finished with status" << SyncFileItem::Status::Success;
|
||||
emit finished(SyncFileItem::Status::Success);
|
||||
propagator()->scheduleNextJob();
|
||||
_filesToDownload.erase(foundIt);
|
||||
}
|
||||
}
|
||||
|
||||
void BulkPropagatorDownloadJob::start(const SyncFileItemPtr &item)
|
||||
void BulkPropagatorDownloadJob::start()
|
||||
{
|
||||
if (propagator()->_abortRequested) {
|
||||
abortWithError({}, SyncFileItem::NormalError, {});
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(lcBulkPropagatorDownloadJob) << item->_file << propagator()->_activeJobList.count();
|
||||
for (const auto &fileToDownload : std::as_const(_filesToDownload)) {
|
||||
Q_ASSERT(fileToDownload->_type == ItemTypeVirtualFile);
|
||||
|
||||
const auto path = item->_file;
|
||||
const auto slashPosition = path.lastIndexOf('/');
|
||||
const auto parentPath = slashPosition >= 0 ? path.left(slashPosition) : QString();
|
||||
if (propagator()->localFileNameClash(fileToDownload->_file)) {
|
||||
finalizeOneFile(fileToDownload);
|
||||
qCWarning(lcBulkPropagatorDownloadJob) << "File" << QDir::toNativeSeparators(fileToDownload->_file) << "can not be downloaded because of a local file name clash!";
|
||||
abortWithError(fileToDownload, SyncFileItem::FileNameClash, tr("File %1 can not be downloaded because of a local file name clash!").arg(QDir::toNativeSeparators(fileToDownload->_file)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SyncJournalFileRecord parentRec;
|
||||
if (!propagator()->_journal->getFileRecord(parentPath, &parentRec)) {
|
||||
qCWarning(lcBulkPropagatorDownloadJob) << "could not get file from local DB" << parentPath;
|
||||
abortWithError(item, SyncFileItem::NormalError, tr("Could not get file %1 from local DB").arg(parentPath));
|
||||
const auto &vfs = propagator()->syncOptions()._vfs;
|
||||
Q_ASSERT(vfs && vfs->mode() == Vfs::WindowsCfApi);
|
||||
|
||||
const auto r = vfs->createPlaceholders(_filesToDownload);
|
||||
|
||||
if (!r) {
|
||||
qCCritical(lcBulkPropagatorDownloadJob) << "Could not create placholders:" << r.error();
|
||||
for (const auto &fileToDownload : std::as_const(_filesToDownload)) {
|
||||
finalizeOneFile(fileToDownload);
|
||||
}
|
||||
abortWithError({}, SyncFileItem::NormalError, r.error());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!propagator()->account()->capabilities().clientSideEncryptionAvailable() || !parentRec.isValid() || !parentRec.isE2eEncrypted()) {
|
||||
startAfterIsEncryptedIsChecked(item);
|
||||
} else {
|
||||
_downloadEncryptedHelper = new PropagateDownloadEncrypted(propagator(), parentPath, item, this);
|
||||
connect(_downloadEncryptedHelper, &PropagateDownloadEncrypted::fileMetadataFound, [this, &item] {
|
||||
startAfterIsEncryptedIsChecked(item);
|
||||
});
|
||||
connect(_downloadEncryptedHelper, &PropagateDownloadEncrypted::failed, [this, &item] {
|
||||
abortWithError(
|
||||
item,
|
||||
SyncFileItem::NormalError,
|
||||
tr("File %1 cannot be downloaded because encryption information is missing.").arg(QDir::toNativeSeparators(item->_file)));
|
||||
});
|
||||
_downloadEncryptedHelper->start();
|
||||
|
||||
for (const auto &fileToDownload : std::as_const(_filesToDownload)) {
|
||||
if (!updateMetadata(fileToDownload)) {
|
||||
abortWithError(fileToDownload, SyncFileItem::NormalError, tr("Unable to update metadata of new file %1.", "error with update metadata of new Win VFS file").arg(fileToDownload->_file));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileToDownload->_remotePerm.isNull() && !fileToDownload->_remotePerm.hasPermission(RemotePermissions::CanWrite)) {
|
||||
// make sure ReadOnly flag is preserved for placeholder, similarly to regular files
|
||||
FileSystem::setFileReadOnly(propagator()->fullLocalPath(fileToDownload->_file), true);
|
||||
}
|
||||
finalizeOneFile(fileToDownload);
|
||||
}
|
||||
|
||||
done(SyncFileItem::Success);
|
||||
}
|
||||
|
||||
bool BulkPropagatorDownloadJob::updateMetadata(const SyncFileItemPtr &item)
|
||||
{
|
||||
const auto fn = propagator()->fullLocalPath(item->_file);
|
||||
const auto fullFileName = propagator()->fullLocalPath(item->_file);
|
||||
const auto result = propagator()->updateMetadata(*item);
|
||||
if (!result) {
|
||||
abortWithError(item, SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()));
|
||||
@ -264,7 +197,7 @@ bool BulkPropagatorDownloadJob::updateMetadata(const SyncFileItemPtr &item)
|
||||
// handle the special recall file
|
||||
if (!item->_remotePerm.hasPermission(RemotePermissions::IsShared)
|
||||
&& (item->_file == QLatin1String(".sys.admin#recall#") || item->_file.endsWith(QLatin1String("/.sys.admin#recall#")))) {
|
||||
handleRecallFile(fn, propagator()->localPath(), *propagator()->_journal);
|
||||
handleRecallFile(fullFileName, propagator()->localPath(), *propagator()->_journal);
|
||||
}
|
||||
|
||||
const auto isLockOwnedByCurrentUser = item->_lockOwnerId == propagator()->account()->davUser();
|
||||
@ -273,13 +206,13 @@ bool BulkPropagatorDownloadJob::updateMetadata(const SyncFileItemPtr &item)
|
||||
const auto isTokenLockOwnedByCurrentUser = (item->_lockOwnerType == SyncFileItem::LockOwnerType::TokenLock && isLockOwnedByCurrentUser);
|
||||
|
||||
if (item->_locked == SyncFileItem::LockStatus::LockedItem && !isUserLockOwnedByCurrentUser && !isTokenLockOwnedByCurrentUser) {
|
||||
qCDebug(lcBulkPropagatorDownloadJob()) << fn << "file is locked: making it read only";
|
||||
FileSystem::setFileReadOnly(fn, true);
|
||||
qCDebug(lcBulkPropagatorDownloadJob()) << fullFileName << "file is locked: making it read only";
|
||||
FileSystem::setFileReadOnly(fullFileName, true);
|
||||
} else {
|
||||
qCDebug(lcBulkPropagatorDownloadJob()) << fn << "file is not locked: making it" << ((!item->_remotePerm.isNull() && !item->_remotePerm.hasPermission(RemotePermissions::CanWrite))
|
||||
qCDebug(lcBulkPropagatorDownloadJob()) << fullFileName << "file is not locked: making it" << ((!item->_remotePerm.isNull() && !item->_remotePerm.hasPermission(RemotePermissions::CanWrite))
|
||||
? "read only"
|
||||
: "read write");
|
||||
FileSystem::setFileReadOnlyWeak(fn, (!item->_remotePerm.isNull() && !item->_remotePerm.hasPermission(RemotePermissions::CanWrite)));
|
||||
FileSystem::setFileReadOnlyWeak(fullFileName, (!item->_remotePerm.isNull() && !item->_remotePerm.hasPermission(RemotePermissions::CanWrite)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -8,21 +8,18 @@
|
||||
#include "owncloudpropagator.h"
|
||||
#include "abstractnetworkjob.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class PropagateDownloadEncrypted;
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcBulkPropagatorDownloadJob)
|
||||
|
||||
class BulkPropagatorDownloadJob : public PropagatorJob
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BulkPropagatorDownloadJob(OwncloudPropagator *propagator, PropagateDirectory *parentDirJob, const std::vector<SyncFileItemPtr> &items = {});
|
||||
explicit BulkPropagatorDownloadJob(OwncloudPropagator *propagator, PropagateDirectory *parentDirJob);
|
||||
|
||||
bool scheduleSelfOrChild() override;
|
||||
|
||||
@ -31,11 +28,9 @@ public:
|
||||
public slots:
|
||||
void addDownloadItem(const OCC::SyncFileItemPtr &item);
|
||||
|
||||
void start(const OCC::SyncFileItemPtr &item);
|
||||
void start();
|
||||
|
||||
private slots:
|
||||
void startAfterIsEncryptedIsChecked(const OCC::SyncFileItemPtr &item);
|
||||
|
||||
void finalizeOneFile(const OCC::SyncFileItemPtr &file);
|
||||
|
||||
void done(const OCC::SyncFileItem::Status status);
|
||||
@ -45,11 +40,7 @@ private slots:
|
||||
private:
|
||||
bool updateMetadata(const SyncFileItemPtr &item);
|
||||
|
||||
void checkPropagationIsDone();
|
||||
|
||||
std::vector<SyncFileItemPtr> _filesToDownload;
|
||||
|
||||
std::vector<SyncFileItemPtr> _filesDownloading;
|
||||
QList<SyncFileItemPtr> _filesToDownload;
|
||||
|
||||
PropagateDownloadEncrypted *_downloadEncryptedHelper = nullptr;
|
||||
|
||||
|
||||
@ -690,7 +690,8 @@ void OwncloudPropagator::startFilePropagation(const SyncFileItemPtr &item,
|
||||
const auto isDownload = item->_direction == SyncFileItem::Down &&
|
||||
(item->_instruction == CSYNC_INSTRUCTION_NEW || item->_instruction == CSYNC_INSTRUCTION_SYNC);
|
||||
const auto isVirtualFile = item->_type == ItemTypeVirtualFile;
|
||||
const auto shouldAddBulkPropagateDownloadItem = isDownload && isVirtualFile && isVfsCfApi;
|
||||
const auto isEncrypted = item->_e2eEncryptionStatus != SyncFileItem::EncryptionStatus::NotEncrypted;
|
||||
const auto shouldAddBulkPropagateDownloadItem = isDownload && isVirtualFile && isVfsCfApi && !isEncrypted;
|
||||
|
||||
if (shouldAddBulkPropagateDownloadItem) {
|
||||
addBulkPropagateDownloadItem(item, directories);
|
||||
|
||||
@ -523,14 +523,14 @@ public:
|
||||
*/
|
||||
bool hasCaseClashAccessibilityProblem(const QString &relfile);
|
||||
|
||||
Q_REQUIRED_RESULT QString fullLocalPath(const QString &tmp_file_name) const;
|
||||
[[nodiscard]] QString fullLocalPath(const QString &tmp_file_name) const;
|
||||
[[nodiscard]] QString localPath() const;
|
||||
|
||||
/**
|
||||
* Returns the full remote path including the folder root of a
|
||||
* folder sync path.
|
||||
*/
|
||||
Q_REQUIRED_RESULT QString fullRemotePath(const QString &tmp_file_name) const;
|
||||
[[nodiscard]] QString fullRemotePath(const QString &tmp_file_name) const;
|
||||
[[nodiscard]] QString remotePath() const;
|
||||
|
||||
[[nodiscard]] QString fulllRemotePathToPathInSyncJournalDb(const QString &fullRemotePath) const;
|
||||
@ -623,9 +623,9 @@ public:
|
||||
SyncJournalDb * const journal,
|
||||
Vfs::UpdateMetadataTypes updateType);
|
||||
|
||||
Q_REQUIRED_RESULT bool isDelayedUploadItem(const SyncFileItemPtr &item) const;
|
||||
[[nodiscard]] bool isDelayedUploadItem(const SyncFileItemPtr &item) const;
|
||||
|
||||
Q_REQUIRED_RESULT const std::deque<SyncFileItemPtr>& delayedTasks() const
|
||||
[[nodiscard]] const std::deque<SyncFileItemPtr>& delayedTasks() const
|
||||
{
|
||||
return _delayedTasks;
|
||||
}
|
||||
|
||||
@ -533,11 +533,6 @@ void PropagateDownloadFile::startAfterIsEncryptedIsChecked()
|
||||
const auto fsPath = propagator()->fullLocalPath(_item->_file);
|
||||
makeParentFolderModifiable(fsPath);
|
||||
|
||||
// do a klaas' case clash check.
|
||||
if (propagator()->localFileNameClash(_item->_file)) {
|
||||
done(SyncFileItem::FileNameClash, tr("File %1 can not be downloaded because of a local file name clash!").arg(QDir::toNativeSeparators(_item->_file)), ErrorCategory::GenericError);
|
||||
return;
|
||||
}
|
||||
auto r = vfs->createPlaceholder(*_item);
|
||||
if (!r) {
|
||||
done(SyncFileItem::NormalError, r.error(), ErrorCategory::GenericError);
|
||||
|
||||
@ -62,11 +62,11 @@ public:
|
||||
UserStatus(const QString &id, const QString &message, const QString &icon,
|
||||
OnlineStatus state, bool messagePredefined, const Optional<ClearAt> &clearAt = {});
|
||||
|
||||
Q_REQUIRED_RESULT QString id() const;
|
||||
Q_REQUIRED_RESULT QString message() const;
|
||||
Q_REQUIRED_RESULT QString icon() const;
|
||||
Q_REQUIRED_RESULT OnlineStatus state() const;
|
||||
Q_REQUIRED_RESULT Optional<ClearAt> clearAt() const;
|
||||
[[nodiscard]] QString id() const;
|
||||
[[nodiscard]] QString message() const;
|
||||
[[nodiscard]] QString icon() const;
|
||||
[[nodiscard]] OnlineStatus state() const;
|
||||
[[nodiscard]] Optional<ClearAt> clearAt() const;
|
||||
|
||||
[[nodiscard]] QString clearAtDisplayString() const;
|
||||
|
||||
@ -77,9 +77,9 @@ public:
|
||||
void setMessagePredefined(bool value);
|
||||
void setClearAt(const Optional<ClearAt> &dateTime);
|
||||
|
||||
Q_REQUIRED_RESULT bool messagePredefined() const;
|
||||
[[nodiscard]] bool messagePredefined() const;
|
||||
|
||||
Q_REQUIRED_RESULT QUrl stateIcon() const;
|
||||
[[nodiscard]] QUrl stateIcon() const;
|
||||
|
||||
private:
|
||||
QString _id;
|
||||
|
||||
@ -906,6 +906,69 @@ OCC::Result<void, QString> OCC::CfApiWrapper::createPlaceholderInfo(const QStrin
|
||||
return {};
|
||||
}
|
||||
|
||||
OCC::Result<void, QString> OCC::CfApiWrapper::createPlaceholdersInfo(const QString &localBasePath, const QList<PlaceholdersInfo> &itemsInfo)
|
||||
{
|
||||
auto filteredItemsInfo = QList<PlaceholdersInfo>{};
|
||||
filteredItemsInfo.reserve(itemsInfo.size());
|
||||
|
||||
std::copy_if(itemsInfo.begin(), itemsInfo.end(), std::back_inserter(filteredItemsInfo), [] (const auto &onePlaceholderInfo) -> bool {
|
||||
if (onePlaceholderInfo.modtime <= 0) {
|
||||
qCWarning(lcCfApiWrapper()) << "Skip invalid modtime file: " << onePlaceholderInfo.relativePath << "modtime:" << onePlaceholderInfo.modtime;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
const auto stdWStringBasePath = localBasePath.toStdWString();
|
||||
auto cloudEntry = std::make_unique<CF_PLACEHOLDER_CREATE_INFO[]>(filteredItemsInfo.size());
|
||||
|
||||
for(auto itemIndice = 0; itemIndice < filteredItemsInfo.size(); ++itemIndice) {
|
||||
const auto &placeholderInfo = filteredItemsInfo[itemIndice];
|
||||
|
||||
cloudEntry[itemIndice].FileIdentity = placeholderInfo.fileId.data();
|
||||
cloudEntry[itemIndice].FileIdentityLength = static_cast<DWORD>(placeholderInfo.fileId.length());
|
||||
|
||||
cloudEntry[itemIndice].RelativeFileName = placeholderInfo.platformNativeRelativePath.data();
|
||||
cloudEntry[itemIndice].Flags = CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC;
|
||||
cloudEntry[itemIndice].FsMetadata.FileSize.QuadPart = placeholderInfo.size;
|
||||
cloudEntry[itemIndice].FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
||||
OCC::Utility::UnixTimeToLargeIntegerFiletime(placeholderInfo.modtime, &cloudEntry[itemIndice].FsMetadata.BasicInfo.CreationTime);
|
||||
OCC::Utility::UnixTimeToLargeIntegerFiletime(placeholderInfo.modtime, &cloudEntry[itemIndice].FsMetadata.BasicInfo.LastWriteTime);
|
||||
OCC::Utility::UnixTimeToLargeIntegerFiletime(placeholderInfo.modtime, &cloudEntry[itemIndice].FsMetadata.BasicInfo.LastAccessTime);
|
||||
OCC::Utility::UnixTimeToLargeIntegerFiletime(placeholderInfo.modtime, &cloudEntry[itemIndice].FsMetadata.BasicInfo.ChangeTime);
|
||||
|
||||
if (placeholderInfo.fileInfo.isDir()) {
|
||||
cloudEntry[itemIndice].Flags |= CF_PLACEHOLDER_CREATE_FLAG_DISABLE_ON_DEMAND_POPULATION;
|
||||
cloudEntry[itemIndice].FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
||||
cloudEntry[itemIndice].FsMetadata.FileSize.QuadPart = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto numberOfCreatedPlaceholders = 0ul;
|
||||
const qint64 result = CfCreatePlaceholders(stdWStringBasePath.data(), cloudEntry.get(), filteredItemsInfo.size(), CF_CREATE_FLAG_NONE, &numberOfCreatedPlaceholders);
|
||||
if (result != S_OK) {
|
||||
qCWarning(lcCfApiWrapper) << "Couldn't create placeholders info" << ":" << QString::fromWCharArray(_com_error(result).ErrorMessage()) << "number of placeholders created:" << numberOfCreatedPlaceholders;
|
||||
|
||||
for(auto itemIndice = 0; itemIndice < filteredItemsInfo.size(); ++itemIndice) {
|
||||
qCDebug(lcCfApiWrapper) << QString::fromStdWString(cloudEntry[itemIndice].RelativeFileName) << QString::fromWCharArray(_com_error(cloudEntry[itemIndice].Result).ErrorMessage());
|
||||
}
|
||||
|
||||
return { "Couldn't create placeholder info" };
|
||||
}
|
||||
|
||||
for(auto itemIndice = 0; itemIndice < filteredItemsInfo.size(); ++itemIndice) {
|
||||
const auto &placeholderInfo = filteredItemsInfo[itemIndice];
|
||||
const auto parentInfo = findPlaceholderInfo(QDir::toNativeSeparators(QFileInfo(localBasePath + QDir::separator() + placeholderInfo.relativePath).absolutePath()));
|
||||
const auto state = parentInfo && parentInfo->PinState == CF_PIN_STATE_UNPINNED ? CF_PIN_STATE_UNPINNED : CF_PIN_STATE_INHERIT;
|
||||
|
||||
if (!setPinState(QDir::toNativeSeparators(QFileInfo(localBasePath + QDir::separator() + placeholderInfo.relativePath).absoluteFilePath()), cfPinStateToPinState(state), NoRecurse)) {
|
||||
return { "Couldn't set the default inherit pin state" };
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::updatePlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath)
|
||||
{
|
||||
return updatePlaceholderState(path, modtime, size, fileId, replacesPath, CfApiUpdateMetadataType::AllMetadata);
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
#include "common/result.h"
|
||||
#include "common/vfs.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct CF_PLACEHOLDER_BASIC_INFO;
|
||||
@ -90,6 +92,17 @@ enum SetPinRecurseMode {
|
||||
|
||||
NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> setPinState(const QString &path, PinState state, SetPinRecurseMode mode);
|
||||
NEXTCLOUD_CFAPI_EXPORT Result<void, QString> createPlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId);
|
||||
|
||||
struct PlaceholdersInfo {
|
||||
QFileInfo fileInfo;
|
||||
QString relativePath;
|
||||
std::wstring platformNativeRelativePath;
|
||||
QByteArray fileId;
|
||||
time_t modtime;
|
||||
qint64 size;
|
||||
};
|
||||
|
||||
NEXTCLOUD_CFAPI_EXPORT Result<void, QString> createPlaceholdersInfo(const QString &localBasePath, const QList<PlaceholdersInfo> &itemsInfo);
|
||||
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);
|
||||
|
||||
@ -211,6 +211,22 @@ Result<void, QString> VfsCfApi::createPlaceholder(const SyncFileItem &item)
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<void, QString> VfsCfApi::createPlaceholders(const QList<SyncFileItemPtr> &items)
|
||||
{
|
||||
const auto fileInfo = QFileInfo(_setupParams.filesystemPath + items[0]->_file);
|
||||
const auto localPath = QDir::toNativeSeparators(fileInfo.absolutePath());
|
||||
|
||||
auto allPlaceholdersInfo = QList<cfapi::PlaceholdersInfo>{};
|
||||
for (const auto &oneItem : items) {
|
||||
auto fileInfo = QFileInfo(_setupParams.filesystemPath + oneItem->_file);
|
||||
allPlaceholdersInfo.emplace_back(std::move(fileInfo), fileInfo.fileName(), QDir::toNativeSeparators(fileInfo.fileName()).toStdWString(), oneItem->_fileId, oneItem->_modtime, oneItem->_size);
|
||||
}
|
||||
|
||||
const auto result = cfapi::createPlaceholdersInfo(localPath, {allPlaceholdersInfo});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<void, QString> VfsCfApi::dehydratePlaceholder(const SyncFileItem &item)
|
||||
{
|
||||
const auto localPath = QDir::toNativeSeparators(_setupParams.filesystemPath + item._file);
|
||||
@ -534,7 +550,7 @@ VfsCfApi::HydratationAndPinStates VfsCfApi::computeRecursiveHydrationAndPinState
|
||||
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, [=](const HydratationAndPinStates ¤tState, const QString &name) {
|
||||
return std::accumulate(std::cbegin(children), std::cend(children), dirState, [=, this](const HydratationAndPinStates ¤tState, const QString &name) {
|
||||
if (name == QStringLiteral("..") || name == QStringLiteral(".")) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@ -39,6 +39,8 @@ public:
|
||||
[[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override;
|
||||
|
||||
Result<void, QString> createPlaceholder(const SyncFileItem &item) override;
|
||||
Result<void, QString> createPlaceholders(const QList<SyncFileItemPtr> &items) 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;
|
||||
|
||||
|
||||
@ -108,6 +108,21 @@ Result<void, QString> VfsSuffix::createPlaceholder(const SyncFileItem &item)
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<void, QString> VfsSuffix::createPlaceholders(const QList<SyncFileItemPtr> &items)
|
||||
{
|
||||
auto result = Result<void, QString>{};
|
||||
|
||||
for (const auto &oneItem : items) {
|
||||
const auto itemResult = createPlaceholder(*oneItem);
|
||||
if (!itemResult) {
|
||||
result = itemResult;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<void, QString> VfsSuffix::dehydratePlaceholder(const SyncFileItem &item)
|
||||
{
|
||||
SyncFileItem virtualItem(item);
|
||||
|
||||
@ -35,6 +35,8 @@ public:
|
||||
[[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override { Q_UNUSED(filePath) return true; }
|
||||
|
||||
Result<void, QString> createPlaceholder(const SyncFileItem &item) override;
|
||||
Result<void, QString> createPlaceholders(const QList<SyncFileItemPtr> &items) override;
|
||||
|
||||
Result<void, QString> dehydratePlaceholder(const SyncFileItem &item) override;
|
||||
Result<Vfs::ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &, UpdateMetadataTypes updateType) override;
|
||||
|
||||
|
||||
@ -97,6 +97,21 @@ Result<void, QString> VfsXAttr::createPlaceholder(const SyncFileItem &item)
|
||||
return xattr::addNextcloudPlaceholderAttributes(path);
|
||||
}
|
||||
|
||||
Result<void, QString> VfsXAttr::createPlaceholders(const QList<SyncFileItemPtr> &items)
|
||||
{
|
||||
auto result = Result<void, QString>{};
|
||||
|
||||
for (const auto &oneItem : items) {
|
||||
const auto itemResult = createPlaceholder(*oneItem);
|
||||
if (!itemResult) {
|
||||
result = itemResult;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<void, QString> VfsXAttr::dehydratePlaceholder(const SyncFileItem &item)
|
||||
{
|
||||
const auto path = QString(_setupParams.filesystemPath + item._file);
|
||||
|
||||
@ -34,6 +34,8 @@ public:
|
||||
[[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override { Q_UNUSED(filePath) return true; }
|
||||
|
||||
Result<void, QString> createPlaceholder(const SyncFileItem &item) override;
|
||||
Result<void, QString> createPlaceholders(const QList<SyncFileItemPtr> &items) override;
|
||||
|
||||
Result<void, QString> dehydratePlaceholder(const SyncFileItem &item) override;
|
||||
Result<ConvertToPlaceholderResult, QString> convertToPlaceholder(const QString &filename,
|
||||
const SyncFileItem &item,
|
||||
|
||||
@ -1517,6 +1517,27 @@ private slots:
|
||||
QVERIFY(fakeFolder.currentRemoteState().find("S/A/a2m"));
|
||||
QVERIFY(fakeFolder.currentRemoteState().find("S/B/b2m"));
|
||||
}
|
||||
|
||||
void createFolderAndFiles()
|
||||
{
|
||||
FakeFolder fakeFolder {FileInfo{}};
|
||||
auto vfs = setupVfs(fakeFolder);
|
||||
|
||||
fakeFolder.remoteModifier().mkdir("first folder");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
|
||||
fakeFolder.remoteModifier().insert("first folder/file1");
|
||||
fakeFolder.remoteModifier().insert("first folder/file2");
|
||||
fakeFolder.remoteModifier().insert("first folder/file3");
|
||||
fakeFolder.remoteModifier().mkdir("first folder/second folder");
|
||||
fakeFolder.remoteModifier().insert("first folder/second folder/second file1");
|
||||
fakeFolder.remoteModifier().insert("first folder/second folder/second file2");
|
||||
fakeFolder.remoteModifier().insert("first folder/second folder/second file3");
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentRemoteState(), fakeFolder.currentRemoteState());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestSyncCfApi)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user