diff --git a/src/common/vfs.h b/src/common/vfs.h index d0f4e28fc0..8f875332b2 100644 --- a/src/common/vfs.h +++ b/src/common/vfs.h @@ -14,6 +14,7 @@ #include #include +#include #include using csync_file_stat_t = struct csync_file_stat_s; @@ -25,6 +26,7 @@ using AccountPtr = QSharedPointer; class SyncJournalDb; class VfsPrivate; class SyncFileItem; +using SyncFileItemPtr = QSharedPointer; /** 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 createPlaceholder(const SyncFileItem &item) = 0; + [[nodiscard]] virtual Result createPlaceholder(const SyncFileItem &item) = 0; + + /// Create a new dehydrated list of placeholders + [[nodiscard]] virtual Result createPlaceholders(const QList &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 dehydratePlaceholder(const SyncFileItem &item) = 0; + [[nodiscard]] virtual Result 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 convertToPlaceholder(const QString &filename, + [[nodiscard]] virtual Result 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(const QString &folderPath) = 0; + [[nodiscard]] virtual Optional 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 updateMetadata(const SyncFileItem &, const QString &, const QString &) override { return {OCC::Vfs::ConvertToPlaceholderResult::Ok}; } Result 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 createPlaceholder(const SyncFileItem &) override { return {}; } + Result createPlaceholders(const QList &) override { return {}; } + Result dehydratePlaceholder(const SyncFileItem &) override { return {}; } Result convertToPlaceholder(const QString &, const SyncFileItem &, const QString &, const UpdateMetadataTypes) override { return ConvertToPlaceholderResult::Ok; } diff --git a/src/gui/sharemanager.h b/src/gui/sharemanager.h index 6a34d921ab..0653f2d5ff 100644 --- a/src/gui/sharemanager.h +++ b/src/gui/sharemanager.h @@ -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) diff --git a/src/gui/systray.h b/src/gui/systray.h index 3d2337b226..807b7498b4 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -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; diff --git a/src/gui/userstatusselectormodel.h b/src/gui/userstatusselectormodel.h index c9e56f07f5..424925f555 100644 --- a/src/gui/userstatusselectormodel.h +++ b/src/gui/userstatusselectormodel.h @@ -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 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) const; - Q_REQUIRED_RESULT QString clearAtStageToString(ClearStageType stage) const; - Q_REQUIRED_RESULT QString timeDifferenceToString(int differenceSecs) const; - Q_REQUIRED_RESULT Optional clearStageTypeToDateTime(ClearStageType type) const; + [[nodiscard]] QString clearAtReadable(const Optional &clearAt) const; + [[nodiscard]] QString clearAtStageToString(ClearStageType stage) const; + [[nodiscard]] QString timeDifferenceToString(int differenceSecs) const; + [[nodiscard]] Optional clearStageTypeToDateTime(ClearStageType type) const; void setError(const QString &reason); void clearError(); diff --git a/src/libsync/bulkpropagatordownloadjob.cpp b/src/libsync/bulkpropagatordownloadjob.cpp index 991280dbe3..dd9ca34299 100644 --- a/src/libsync/bulkpropagatordownloadjob.cpp +++ b/src/libsync/bulkpropagatordownloadjob.cpp @@ -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 @@ -23,11 +21,10 @@ namespace OCC { Q_LOGGING_CATEGORY(lcBulkPropagatorDownloadJob, "nextcloud.sync.propagator.bulkdownload", QtInfoMsg) BulkPropagatorDownloadJob::BulkPropagatorDownloadJob(OwncloudPropagator *propagator, - PropagateDirectory *parentDirJob, - const std::vector &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; } diff --git a/src/libsync/bulkpropagatordownloadjob.h b/src/libsync/bulkpropagatordownloadjob.h index ec0bfedf1a..4611e094da 100644 --- a/src/libsync/bulkpropagatordownloadjob.h +++ b/src/libsync/bulkpropagatordownloadjob.h @@ -8,21 +8,18 @@ #include "owncloudpropagator.h" #include "abstractnetworkjob.h" -#include -#include +#include 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 &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 _filesToDownload; - - std::vector _filesDownloading; + QList _filesToDownload; PropagateDownloadEncrypted *_downloadEncryptedHelper = nullptr; diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index d8b16f877d..cafdf82205 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -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); diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h index 1bb177dbfa..e131b858bc 100644 --- a/src/libsync/owncloudpropagator.h +++ b/src/libsync/owncloudpropagator.h @@ -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& delayedTasks() const + [[nodiscard]] const std::deque& delayedTasks() const { return _delayedTasks; } diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 94ba15a09c..78019533f4 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -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); diff --git a/src/libsync/userstatusconnector.h b/src/libsync/userstatusconnector.h index ebd4ff37a8..4fe0b5732e 100644 --- a/src/libsync/userstatusconnector.h +++ b/src/libsync/userstatusconnector.h @@ -62,11 +62,11 @@ public: UserStatus(const QString &id, const QString &message, const QString &icon, OnlineStatus state, bool messagePredefined, const Optional &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() const; + [[nodiscard]] QString id() const; + [[nodiscard]] QString message() const; + [[nodiscard]] QString icon() const; + [[nodiscard]] OnlineStatus state() const; + [[nodiscard]] Optional clearAt() const; [[nodiscard]] QString clearAtDisplayString() const; @@ -77,9 +77,9 @@ public: void setMessagePredefined(bool value); void setClearAt(const Optional &dateTime); - Q_REQUIRED_RESULT bool messagePredefined() const; + [[nodiscard]] bool messagePredefined() const; - Q_REQUIRED_RESULT QUrl stateIcon() const; + [[nodiscard]] QUrl stateIcon() const; private: QString _id; diff --git a/src/libsync/vfs/cfapi/cfapiwrapper.cpp b/src/libsync/vfs/cfapi/cfapiwrapper.cpp index e61525c29e..59dbbfb874 100644 --- a/src/libsync/vfs/cfapi/cfapiwrapper.cpp +++ b/src/libsync/vfs/cfapi/cfapiwrapper.cpp @@ -906,6 +906,69 @@ OCC::Result OCC::CfApiWrapper::createPlaceholderInfo(const QStrin return {}; } +OCC::Result OCC::CfApiWrapper::createPlaceholdersInfo(const QString &localBasePath, const QList &itemsInfo) +{ + auto filteredItemsInfo = QList{}; + 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(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(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::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); diff --git a/src/libsync/vfs/cfapi/cfapiwrapper.h b/src/libsync/vfs/cfapi/cfapiwrapper.h index 329a8ca6fa..b2627356e6 100644 --- a/src/libsync/vfs/cfapi/cfapiwrapper.h +++ b/src/libsync/vfs/cfapi/cfapiwrapper.h @@ -15,6 +15,8 @@ #include "common/result.h" #include "common/vfs.h" +#include + #include struct CF_PLACEHOLDER_BASIC_INFO; @@ -90,6 +92,17 @@ enum SetPinRecurseMode { NEXTCLOUD_CFAPI_EXPORT Result setPinState(const QString &path, PinState state, SetPinRecurseMode mode); NEXTCLOUD_CFAPI_EXPORT Result 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 createPlaceholdersInfo(const QString &localBasePath, const QList &itemsInfo); NEXTCLOUD_CFAPI_EXPORT Result updatePlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath = QString()); NEXTCLOUD_CFAPI_EXPORT Result convertToPlaceholder(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath); NEXTCLOUD_CFAPI_EXPORT Result dehydratePlaceholder(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId); diff --git a/src/libsync/vfs/cfapi/vfs_cfapi.cpp b/src/libsync/vfs/cfapi/vfs_cfapi.cpp index 8b3757ef58..3103881a4c 100644 --- a/src/libsync/vfs/cfapi/vfs_cfapi.cpp +++ b/src/libsync/vfs/cfapi/vfs_cfapi.cpp @@ -211,6 +211,22 @@ Result VfsCfApi::createPlaceholder(const SyncFileItem &item) return result; } +Result VfsCfApi::createPlaceholders(const QList &items) +{ + const auto fileInfo = QFileInfo(_setupParams.filesystemPath + items[0]->_file); + const auto localPath = QDir::toNativeSeparators(fileInfo.absolutePath()); + + auto allPlaceholdersInfo = QList{}; + 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 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; } diff --git a/src/libsync/vfs/cfapi/vfs_cfapi.h b/src/libsync/vfs/cfapi/vfs_cfapi.h index 91cdabe87e..0eef29251e 100644 --- a/src/libsync/vfs/cfapi/vfs_cfapi.h +++ b/src/libsync/vfs/cfapi/vfs_cfapi.h @@ -39,6 +39,8 @@ public: [[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override; Result createPlaceholder(const SyncFileItem &item) override; + Result createPlaceholders(const QList &items) override; + Result dehydratePlaceholder(const SyncFileItem &item) override; Result convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &replacesFile, UpdateMetadataTypes updateType) override; diff --git a/src/libsync/vfs/suffix/vfs_suffix.cpp b/src/libsync/vfs/suffix/vfs_suffix.cpp index 7f18412bfb..7cb5a43185 100644 --- a/src/libsync/vfs/suffix/vfs_suffix.cpp +++ b/src/libsync/vfs/suffix/vfs_suffix.cpp @@ -108,6 +108,21 @@ Result VfsSuffix::createPlaceholder(const SyncFileItem &item) return {}; } +Result VfsSuffix::createPlaceholders(const QList &items) +{ + auto result = Result{}; + + for (const auto &oneItem : items) { + const auto itemResult = createPlaceholder(*oneItem); + if (!itemResult) { + result = itemResult; + break; + } + } + + return result; +} + Result VfsSuffix::dehydratePlaceholder(const SyncFileItem &item) { SyncFileItem virtualItem(item); diff --git a/src/libsync/vfs/suffix/vfs_suffix.h b/src/libsync/vfs/suffix/vfs_suffix.h index 0c0e5de7e4..803338ec65 100644 --- a/src/libsync/vfs/suffix/vfs_suffix.h +++ b/src/libsync/vfs/suffix/vfs_suffix.h @@ -35,6 +35,8 @@ public: [[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override { Q_UNUSED(filePath) return true; } Result createPlaceholder(const SyncFileItem &item) override; + Result createPlaceholders(const QList &items) override; + Result dehydratePlaceholder(const SyncFileItem &item) override; Result convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &, UpdateMetadataTypes updateType) override; diff --git a/src/libsync/vfs/xattr/vfs_xattr.cpp b/src/libsync/vfs/xattr/vfs_xattr.cpp index c6315b82ac..e5bd5e213c 100644 --- a/src/libsync/vfs/xattr/vfs_xattr.cpp +++ b/src/libsync/vfs/xattr/vfs_xattr.cpp @@ -97,6 +97,21 @@ Result VfsXAttr::createPlaceholder(const SyncFileItem &item) return xattr::addNextcloudPlaceholderAttributes(path); } +Result VfsXAttr::createPlaceholders(const QList &items) +{ + auto result = Result{}; + + for (const auto &oneItem : items) { + const auto itemResult = createPlaceholder(*oneItem); + if (!itemResult) { + result = itemResult; + break; + } + } + + return result; +} + Result VfsXAttr::dehydratePlaceholder(const SyncFileItem &item) { const auto path = QString(_setupParams.filesystemPath + item._file); diff --git a/src/libsync/vfs/xattr/vfs_xattr.h b/src/libsync/vfs/xattr/vfs_xattr.h index 382f4d1d81..1922c44370 100644 --- a/src/libsync/vfs/xattr/vfs_xattr.h +++ b/src/libsync/vfs/xattr/vfs_xattr.h @@ -34,6 +34,8 @@ public: [[nodiscard]] bool isPlaceHolderInSync(const QString &filePath) const override { Q_UNUSED(filePath) return true; } Result createPlaceholder(const SyncFileItem &item) override; + Result createPlaceholders(const QList &items) override; + Result dehydratePlaceholder(const SyncFileItem &item) override; Result convertToPlaceholder(const QString &filename, const SyncFileItem &item, diff --git a/test/testsynccfapi.cpp b/test/testsynccfapi.cpp index 60678ad829..d16832470b 100644 --- a/test/testsynccfapi.cpp +++ b/test/testsynccfapi.cpp @@ -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)