From: allexzander Date: Tue, 5 Jan 2021 10:48:30 +0000 (+0200) Subject: Implement nested items removal when removing root encrypted folder. X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~21^2~431^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=4ed7feab2bce2c0c57912264705cbc762e9cc01c;p=nextcloud-desktop.git Implement nested items removal when removing root encrypted folder. Signed-off-by: allexzander --- diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 6679d917e..99f718270 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -34,6 +34,8 @@ set(libsync_SRCS networkjobs.cpp owncloudpropagator.cpp nextcloudtheme.cpp + abstractpropagateremotedeleteencrypted.cpp + deletejob.cpp progressdispatcher.cpp propagatorjobs.cpp propagatedownload.cpp @@ -42,6 +44,7 @@ set(libsync_SRCS propagateuploadng.cpp propagateremotedelete.cpp propagateremotedeleteencrypted.cpp + propagateremotedeleteencryptedrootfolder.cpp propagateremotemove.cpp propagateremotemkdir.cpp propagateuploadencrypted.cpp diff --git a/src/libsync/abstractpropagateremotedeleteencrypted.cpp b/src/libsync/abstractpropagateremotedeleteencrypted.cpp new file mode 100644 index 000000000..c50166f9b --- /dev/null +++ b/src/libsync/abstractpropagateremotedeleteencrypted.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#include + +#include "abstractpropagateremotedeleteencrypted.h" +#include "account.h" +#include "clientsideencryptionjobs.h" +#include "deletejob.h" +#include "owncloudpropagator.h" + +Q_LOGGING_CATEGORY(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED, "nextcloud.sync.propagator.remove.encrypted") + +namespace OCC { + +AbstractPropagateRemoteDeleteEncrypted::AbstractPropagateRemoteDeleteEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent) + : QObject(parent) + , _propagator(propagator) + , _item(item) +{} + +QNetworkReply::NetworkError AbstractPropagateRemoteDeleteEncrypted::networkError() const +{ + return _networkError; +} + +QString AbstractPropagateRemoteDeleteEncrypted::errorString() const +{ + return _errorString; +} + +void AbstractPropagateRemoteDeleteEncrypted::storeFirstError(QNetworkReply::NetworkError err) +{ + if (_networkError == QNetworkReply::NetworkError::NoError) { + _networkError = err; + } +} + +void AbstractPropagateRemoteDeleteEncrypted::storeFirstErrorString(const QString &errString) +{ + if (_errorString.isEmpty()) { + _errorString = errString; + } +} + +void AbstractPropagateRemoteDeleteEncrypted::startLsColJob(const QString &path) +{ + qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Folder is encrypted, let's get the Id from it."; + auto job = new LsColJob(_propagator->account(), path, this); + job->setProperties({"resourcetype", "http://owncloud.org/ns:fileid"}); + connect(job, &LsColJob::directoryListingSubfolders, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived); + connect(job, &LsColJob::finishedWithError, this, &AbstractPropagateRemoteDeleteEncrypted::taskFailed); + job->start(); +} + +void AbstractPropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived(const QStringList &list) +{ + qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Received id of folder, trying to lock it so we can prepare the metadata"; + auto job = qobject_cast(sender()); + const ExtraFolderInfo folderInfo = job->_folderInfos.value(list.first()); + slotTryLock(folderInfo.fileId); +} + +void AbstractPropagateRemoteDeleteEncrypted::slotTryLock(const QByteArray &folderId) +{ + auto lockJob = new LockEncryptFolderApiJob(_propagator->account(), folderId, this); + connect(lockJob, &LockEncryptFolderApiJob::success, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully); + connect(lockJob, &LockEncryptFolderApiJob::error, this, &AbstractPropagateRemoteDeleteEncrypted::taskFailed); + lockJob->start(); +} + +void AbstractPropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully(const QByteArray &folderId, const QByteArray &token) +{ + qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Folder id" << folderId << "Locked Successfully for Upload, Fetching Metadata"; + _folderLocked = true; + _folderToken = token; + _folderId = folderId; + + auto job = new GetMetadataApiJob(_propagator->account(), _folderId); + connect(job, &GetMetadataApiJob::jsonReceived, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived); + connect(job, &GetMetadataApiJob::error, this, &AbstractPropagateRemoteDeleteEncrypted::taskFailed); + job->start(); +} + +void AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully(const QByteArray &folderId) +{ + Q_UNUSED(folderId); + qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Folder id" << folderId << "successfully unlocked"; + _folderLocked = false; + _folderToken = ""; +} + +void AbstractPropagateRemoteDeleteEncrypted::slotDeleteRemoteItemFinished() +{ + auto *deleteJob = qobject_cast(QObject::sender()); + + Q_ASSERT(deleteJob); + + if (!deleteJob) { + qCCritical(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Sender is not a DeleteJob instance."; + taskFailed(); + return; + } + + const auto err = deleteJob->reply()->error(); + + _item->_httpErrorCode = deleteJob->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + _item->_responseTimeStamp = deleteJob->responseTimestamp(); + _item->_requestId = deleteJob->requestId(); + + if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) { + storeFirstErrorString(deleteJob->errorString()); + storeFirstError(err); + + taskFailed(); + return; + } + + // A 404 reply is also considered a success here: We want to make sure + // a file is gone from the server. It not being there in the first place + // is ok. This will happen for files that are in the DB but not on + // the server or the local file system. + if (_item->_httpErrorCode != 204 && _item->_httpErrorCode != 404) { + // Normally we expect "204 No Content" + // If it is not the case, it might be because of a proxy or gateway intercepting the request, so we must + // throw an error. + storeFirstErrorString(tr("Wrong HTTP code returned by server. Expected 204, but received \"%1 %2\".") + .arg(_item->_httpErrorCode) + .arg(deleteJob->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString())); + + taskFailed(); + return; + } + + _propagator->_journal->deleteFileRecord(_item->_originalFile, _item->isDirectory()); + _propagator->_journal->commit("Remote Remove"); + + unlockFolder(); +} + +void AbstractPropagateRemoteDeleteEncrypted::deleteRemoteItem(const QString &filename) +{ + qCInfo(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Deleting nested encrypted item" << filename; + + auto deleteJob = new DeleteJob(_propagator->account(), _propagator->fullRemotePath(filename), this); + deleteJob->setFolderToken(_folderToken); + + connect(deleteJob, &DeleteJob::finishedSignal, this, &AbstractPropagateRemoteDeleteEncrypted::slotDeleteRemoteItemFinished); + + deleteJob->start(); +} + +void AbstractPropagateRemoteDeleteEncrypted::unlockFolder() +{ + if (!_folderLocked) { + emit finished(true); + return; + } + + qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Unlocking folder" << _folderId; + auto unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(), _folderId, _folderToken, this); + + connect(unlockJob, &UnlockEncryptFolderApiJob::success, this, &AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully); + connect(unlockJob, &UnlockEncryptFolderApiJob::error, this, [this] (const QByteArray& fileId, int httpReturnCode) { + Q_UNUSED(fileId); + _folderLocked = false; + _folderToken = ""; + _item->_httpErrorCode = httpReturnCode; + _errorString = tr("\"%1 Failed to unlock encrypted folder %2\".") + .arg(httpReturnCode) + .arg(QString::fromUtf8(fileId)); + _item->_errorString =_errorString; + taskFailed(); + }); + unlockJob->start(); +} + +void AbstractPropagateRemoteDeleteEncrypted::taskFailed() +{ + qCDebug(ABSTRACT_PROPAGATE_REMOVE_ENCRYPTED) << "Task failed for job" << sender(); + _isTaskFailed = true; + if (_folderLocked) { + unlockFolder(); + } else { + emit finished(false); + } +} + +} // namespace OCC diff --git a/src/libsync/abstractpropagateremotedeleteencrypted.h b/src/libsync/abstractpropagateremotedeleteencrypted.h new file mode 100644 index 000000000..c36fc956d --- /dev/null +++ b/src/libsync/abstractpropagateremotedeleteencrypted.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include +#include +#include + +#include "syncfileitem.h" + +namespace OCC { + +class OwncloudPropagator; +/** + * @brief The AbstractPropagateRemoteDeleteEncrypted class is the base class for Propagate Remote Delete Encrypted jobs + * @ingroup libsync + */ +class AbstractPropagateRemoteDeleteEncrypted : public QObject +{ + Q_OBJECT +public: + AbstractPropagateRemoteDeleteEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent); + virtual ~AbstractPropagateRemoteDeleteEncrypted() = default; + + QNetworkReply::NetworkError networkError() const; + QString errorString() const; + + virtual void start() = 0; + +signals: + void finished(bool success); + +protected: + void storeFirstError(QNetworkReply::NetworkError err); + void storeFirstErrorString(const QString &errString); + + void startLsColJob(const QString &path); + void slotFolderEncryptedIdReceived(const QStringList &list); + void slotTryLock(const QByteArray &folderId); + void slotFolderLockedSuccessfully(const QByteArray &folderId, const QByteArray &token); + virtual void slotFolderUnLockedSuccessfully(const QByteArray &folderId); + virtual void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode) = 0; + void slotDeleteRemoteItemFinished(); + + void deleteRemoteItem(const QString &filename); + void unlockFolder(); + void taskFailed(); + +protected: + OwncloudPropagator *_propagator = nullptr; + SyncFileItemPtr _item; + QByteArray _folderToken; + QByteArray _folderId; + bool _folderLocked = false; + bool _isTaskFailed = false; + QNetworkReply::NetworkError _networkError = QNetworkReply::NoError; + QString _errorString; +}; + +} diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index 69dfafc62..92bd1a103 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -1468,6 +1468,11 @@ void FolderMetadata::removeEncryptedFile(const EncryptedFile &f) } } +void FolderMetadata::removeAllEncryptedFiles() +{ + _files.clear(); +} + QVector FolderMetadata::files() const { return _files; } diff --git a/src/libsync/clientsideencryption.h b/src/libsync/clientsideencryption.h index 37d220ec2..c32e54707 100644 --- a/src/libsync/clientsideencryption.h +++ b/src/libsync/clientsideencryption.h @@ -137,6 +137,7 @@ public: QByteArray encryptedMetadata(); void addEncryptedFile(const EncryptedFile& f); void removeEncryptedFile(const EncryptedFile& f); + void removeAllEncryptedFiles(); QVector files() const; diff --git a/src/libsync/clientsideencryptionjobs.cpp b/src/libsync/clientsideencryptionjobs.cpp index 490f9f077..ad8e30c66 100644 --- a/src/libsync/clientsideencryptionjobs.cpp +++ b/src/libsync/clientsideencryptionjobs.cpp @@ -257,8 +257,8 @@ bool LockEncryptFolderApiJob::finished() return true; } -SetEncryptionFlagApiJob::SetEncryptionFlagApiJob(const AccountPtr& account, const QByteArray& fileId, QObject* parent) -: AbstractNetworkJob(account, baseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId) +SetEncryptionFlagApiJob::SetEncryptionFlagApiJob(const AccountPtr& account, const QByteArray& fileId, FlagAction flagAction, QObject* parent) +: AbstractNetworkJob(account, baseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId), _flagAction(flagAction) { } @@ -268,8 +268,10 @@ void SetEncryptionFlagApiJob::start() req.setRawHeader("OCS-APIREQUEST", "true"); QUrl url = Utility::concatUrlPath(account()->url(), path()); - qCInfo(lcCseJob()) << "marking the file with id" << _fileId << "as encrypted"; - sendRequest("PUT", url, req); + qCInfo(lcCseJob()) << "marking the file with id" << _fileId << "as" << (_flagAction == Set ? "encrypted" : "non-encrypted") << "."; + + sendRequest(_flagAction == Set ? "PUT" : "DELETE", url, req); + AbstractNetworkJob::start(); } diff --git a/src/libsync/clientsideencryptionjobs.h b/src/libsync/clientsideencryptionjobs.h index a64a59b43..1ab38fa5c 100644 --- a/src/libsync/clientsideencryptionjobs.h +++ b/src/libsync/clientsideencryptionjobs.h @@ -114,7 +114,12 @@ class OWNCLOUDSYNC_EXPORT SetEncryptionFlagApiJob : public AbstractNetworkJob { Q_OBJECT public: - explicit SetEncryptionFlagApiJob(const AccountPtr &account, const QByteArray& fileId, QObject *parent = nullptr); + enum FlagAction { + Clear = 0, + Set = 1 + }; + + explicit SetEncryptionFlagApiJob(const AccountPtr &account, const QByteArray &fileId, FlagAction flagAction = Set, QObject *parent = nullptr); public slots: void start() override; @@ -123,11 +128,12 @@ protected: bool finished() override; signals: - void success(const QByteArray fileId); - void error(const QByteArray fileId, int httpReturnCode); + void success(const QByteArray &fileId); + void error(const QByteArray &fileId, int httpReturnCode); private: QByteArray _fileId; + FlagAction _flagAction = Set; }; class OWNCLOUDSYNC_EXPORT LockEncryptFolderApiJob : public AbstractNetworkJob diff --git a/src/libsync/deletejob.cpp b/src/libsync/deletejob.cpp new file mode 100644 index 000000000..168349221 --- /dev/null +++ b/src/libsync/deletejob.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) by Olivier Goffart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "deletejob.h" +#include "account.h" +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcDeleteJob, "nextcloud.sync.networkjob.delete", QtInfoMsg) + +DeleteJob::DeleteJob(AccountPtr account, const QString &path, QObject *parent) + : AbstractNetworkJob(account, path, parent) +{ +} + +DeleteJob::DeleteJob(AccountPtr account, const QUrl &url, QObject *parent) + : AbstractNetworkJob(account, QString(), parent) + , _url(url) +{ +} + +void DeleteJob::start() +{ + QNetworkRequest req; + if (!_folderToken.isEmpty()) { + req.setRawHeader("e2e-token", _folderToken); + } + + if (_url.isValid()) { + sendRequest("DELETE", _url, req); + } else { + sendRequest("DELETE", makeDavUrl(path()), req); + } + + if (reply()->error() != QNetworkReply::NoError) { + qCWarning(lcDeleteJob) << " Network error: " << reply()->errorString(); + } + AbstractNetworkJob::start(); +} + +bool DeleteJob::finished() +{ + qCInfo(lcDeleteJob) << "DELETE of" << reply()->request().url() << "FINISHED WITH STATUS" + << replyStatusString(); + + emit finishedSignal(); + return true; +} + +QByteArray DeleteJob::folderToken() const +{ + return _folderToken; +} + +void DeleteJob::setFolderToken(const QByteArray &folderToken) +{ + _folderToken = folderToken; +} + +} // namespace OCC diff --git a/src/libsync/deletejob.h b/src/libsync/deletejob.h new file mode 100644 index 000000000..075dd1478 --- /dev/null +++ b/src/libsync/deletejob.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) by Olivier Goffart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include "accountfwd.h" +#include "networkjobs.h" + +namespace OCC { + +/** + * @brief The DeleteJob class + * @ingroup libsync + */ +class DeleteJob : public AbstractNetworkJob +{ + Q_OBJECT +public: + explicit DeleteJob(AccountPtr account, const QString &path, QObject *parent = nullptr); + explicit DeleteJob(AccountPtr account, const QUrl &url, QObject *parent = nullptr); + + void start() override; + bool finished() override; + + QByteArray folderToken() const; + void setFolderToken(const QByteArray &folderToken); + +signals: + void finishedSignal(); + +private: + QUrl _url; // Only used if the constructor taking a url is taken. + QByteArray _folderToken; +}; +} diff --git a/src/libsync/encryptfolderjob.cpp b/src/libsync/encryptfolderjob.cpp index 798778ea9..1a9fc6584 100644 --- a/src/libsync/encryptfolderjob.cpp +++ b/src/libsync/encryptfolderjob.cpp @@ -34,7 +34,7 @@ EncryptFolderJob::EncryptFolderJob(const AccountPtr &account, SyncJournalDb *jou void EncryptFolderJob::start() { - auto job = new OCC::SetEncryptionFlagApiJob(_account, _fileId, this); + auto job = new OCC::SetEncryptionFlagApiJob(_account, _fileId, OCC::SetEncryptionFlagApiJob::Set, this); connect(job, &OCC::SetEncryptionFlagApiJob::success, this, &EncryptFolderJob::slotEncryptionFlagSuccess); connect(job, &OCC::SetEncryptionFlagApiJob::error, this, &EncryptFolderJob::slotEncryptionFlagError); job->start(); diff --git a/src/libsync/propagateremotedelete.cpp b/src/libsync/propagateremotedelete.cpp index 6da195379..4dc8ecf3c 100644 --- a/src/libsync/propagateremotedelete.cpp +++ b/src/libsync/propagateremotedelete.cpp @@ -14,66 +14,18 @@ #include "propagateremotedelete.h" #include "propagateremotedeleteencrypted.h" +#include "propagateremotedeleteencryptedrootfolder.h" #include "owncloudpropagator_p.h" #include "account.h" +#include "deletejob.h" #include "common/asserts.h" #include namespace OCC { -Q_LOGGING_CATEGORY(lcDeleteJob, "nextcloud.sync.networkjob.delete", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropagateRemoteDelete, "nextcloud.sync.propagator.remotedelete", QtInfoMsg) -DeleteJob::DeleteJob(AccountPtr account, const QString &path, QObject *parent) - : AbstractNetworkJob(account, path, parent) -{ -} - -DeleteJob::DeleteJob(AccountPtr account, const QUrl &url, QObject *parent) - : AbstractNetworkJob(account, QString(), parent) - , _url(url) -{ -} - -void DeleteJob::start() -{ - QNetworkRequest req; - if (!_folderToken.isEmpty()) { - req.setRawHeader("e2e-token", _folderToken); - } - - if (_url.isValid()) { - sendRequest("DELETE", _url, req); - } else { - sendRequest("DELETE", makeDavUrl(path()), req); - } - - if (reply()->error() != QNetworkReply::NoError) { - qCWarning(lcDeleteJob) << " Network error: " << reply()->errorString(); - } - AbstractNetworkJob::start(); -} - -bool DeleteJob::finished() -{ - qCInfo(lcDeleteJob) << "DELETE of" << reply()->request().url() << "FINISHED WITH STATUS" - << replyStatusString(); - - emit finishedSignal(); - return true; -} - -QByteArray DeleteJob::folderToken() const -{ - return _folderToken; -} - -void DeleteJob::setFolderToken(const QByteArray &folderToken) -{ - _folderToken = folderToken; -} - PropagatorJob::JobParallelism PropagateRemoteDelete::parallelism() { return _item->_encryptedFileName.isEmpty() ? FullParallelism : WaitForFinished; @@ -84,11 +36,22 @@ void PropagateRemoteDelete::start() if (propagator()->_abortRequested) return; - if (!_item->_encryptedFileName.isEmpty()) { - _deleteEncryptedHelper = new PropagateRemoteDeleteEncrypted(propagator(), _item, this); - connect(_deleteEncryptedHelper, &PropagateRemoteDeleteEncrypted::finished, this, [this] (bool success) { - Q_UNUSED(success) // Should we skip file deletion in case of failure? - createDeleteJob(_item->_encryptedFileName); + if (!_item->_encryptedFileName.isEmpty() || _item->_isEncrypted) { + if (!_item->_encryptedFileName.isEmpty()) { + _deleteEncryptedHelper = new PropagateRemoteDeleteEncrypted(propagator(), _item, this); + } else { + _deleteEncryptedHelper = new PropagateRemoteDeleteEncryptedRootFolder(propagator(), _item, this); + } + connect(_deleteEncryptedHelper, &AbstractPropagateRemoteDeleteEncrypted::finished, this, [this] (bool success) { + if (!success) { + SyncFileItem::Status status = SyncFileItem::NormalError; + if (_deleteEncryptedHelper->networkError() != QNetworkReply::NoError && _deleteEncryptedHelper->networkError() != QNetworkReply::ContentNotFoundError) { + status = classifyError(_deleteEncryptedHelper->networkError(), _item->_httpErrorCode, &propagator()->_anotherSyncNeeded); + } + done(status, _deleteEncryptedHelper->errorString()); + } else { + done(SyncFileItem::Success); + } }); _deleteEncryptedHelper->start(); } else { @@ -103,9 +66,7 @@ void PropagateRemoteDelete::createDeleteJob(const QString &filename) _job = new DeleteJob(propagator()->account(), propagator()->fullRemotePath(filename), this); - if (_deleteEncryptedHelper && !_deleteEncryptedHelper->folderToken().isEmpty()) { - _job->setFolderToken(_deleteEncryptedHelper->folderToken()); - } + connect(_job.data(), &DeleteJob::finishedSignal, this, &PropagateRemoteDelete::slotDeleteJobFinished); propagator()->_activeJobList.append(this); _job->start(); @@ -158,16 +119,6 @@ void PropagateRemoteDelete::slotDeleteJobFinished() propagator()->_journal->deleteFileRecord(_item->_originalFile, _item->isDirectory()); propagator()->_journal->commit("Remote Remove"); - if (_deleteEncryptedHelper && !_job->folderToken().isEmpty()) { - propagator()->_activeJobList.append(this); - connect(_deleteEncryptedHelper, &PropagateRemoteDeleteEncrypted::folderUnlocked, - this, [this] { - propagator()->_activeJobList.removeOne(this); - done(SyncFileItem::Success); - }); - _deleteEncryptedHelper->unlockFolder(); - } else { - done(SyncFileItem::Success); - } + done(SyncFileItem::Success); } } diff --git a/src/libsync/propagateremotedelete.h b/src/libsync/propagateremotedelete.h index 583020992..57d0aeafc 100644 --- a/src/libsync/propagateremotedelete.h +++ b/src/libsync/propagateremotedelete.h @@ -18,32 +18,9 @@ namespace OCC { -class PropagateRemoteDeleteEncrypted; +class DeleteJob; -/** - * @brief The DeleteJob class - * @ingroup libsync - */ -class DeleteJob : public AbstractNetworkJob -{ - Q_OBJECT - QUrl _url; // Only used if the constructor taking a url is taken. -public: - explicit DeleteJob(AccountPtr account, const QString &path, QObject *parent = nullptr); - explicit DeleteJob(AccountPtr account, const QUrl &url, QObject *parent = nullptr); - - void start() override; - bool finished() override; - - QByteArray folderToken() const; - void setFolderToken(const QByteArray &folderToken); - -signals: - void finishedSignal(); - -private: - QByteArray _folderToken; -}; +class AbstractPropagateRemoteDeleteEncrypted; /** * @brief The PropagateRemoteDelete class @@ -53,7 +30,7 @@ class PropagateRemoteDelete : public PropagateItemJob { Q_OBJECT QPointer _job; - PropagateRemoteDeleteEncrypted *_deleteEncryptedHelper = nullptr; + AbstractPropagateRemoteDeleteEncrypted *_deleteEncryptedHelper = nullptr; public: PropagateRemoteDelete(OwncloudPropagator *propagator, const SyncFileItemPtr &item) diff --git a/src/libsync/propagateremotedeleteencrypted.cpp b/src/libsync/propagateremotedeleteencrypted.cpp index 76818a2b4..c648d79e6 100644 --- a/src/libsync/propagateremotedeleteencrypted.cpp +++ b/src/libsync/propagateremotedeleteencrypted.cpp @@ -1,85 +1,61 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + #include "propagateremotedeleteencrypted.h" #include "clientsideencryptionjobs.h" -#include "clientsideencryption.h" #include "owncloudpropagator.h" - +#include "encryptfolderjob.h" #include -#include #include -#include using namespace OCC; Q_LOGGING_CATEGORY(PROPAGATE_REMOVE_ENCRYPTED, "nextcloud.sync.propagator.remove.encrypted") PropagateRemoteDeleteEncrypted::PropagateRemoteDeleteEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent) - : QObject(parent) - , _propagator(propagator) - , _item(item) + : AbstractPropagateRemoteDeleteEncrypted(propagator, item, parent) { } -QByteArray PropagateRemoteDeleteEncrypted::folderToken() -{ - return _folderToken; -} - void PropagateRemoteDeleteEncrypted::start() { Q_ASSERT(!_item->_encryptedFileName.isEmpty()); - QFileInfo info(_item->_encryptedFileName); - qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Folder is encrypted, let's get the Id from it."; - auto job = new LsColJob(_propagator->account(), info.path(), this); - job->setProperties({"resourcetype", "http://owncloud.org/ns:fileid"}); - connect(job, &LsColJob::directoryListingSubfolders, this, &PropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived); - connect(job, &LsColJob::finishedWithError, this, &PropagateRemoteDeleteEncrypted::taskFailed); - job->start(); -} -void PropagateRemoteDeleteEncrypted::slotFolderEncryptedIdReceived(const QStringList &list) -{ - qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Received id of folder, trying to lock it so we can prepare the metadata"; - auto job = qobject_cast(sender()); - const ExtraFolderInfo folderInfo = job->_folderInfos.value(list.first()); - slotTryLock(folderInfo.fileId); + const QFileInfo info(_item->_encryptedFileName); + startLsColJob(info.path()); } -void PropagateRemoteDeleteEncrypted::slotTryLock(const QByteArray &folderId) +void PropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully(const QByteArray &folderId) { - auto lockJob = new LockEncryptFolderApiJob(_propagator->account(), folderId, this); - connect(lockJob, &LockEncryptFolderApiJob::success, this, &PropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully); - connect(lockJob, &LockEncryptFolderApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed); - lockJob->start(); -} - -void PropagateRemoteDeleteEncrypted::slotFolderLockedSuccessfully(const QByteArray &folderId, const QByteArray &token) -{ - qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Folder id" << folderId << "Locked Successfully for Upload, Fetching Metadata"; - _folderLocked = true; - _folderToken = token; - _folderId = folderId; - - auto job = new GetMetadataApiJob(_propagator->account(), _folderId); - connect(job, &GetMetadataApiJob::jsonReceived, this, &PropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived); - connect(job, &GetMetadataApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed); - job->start(); + AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully(folderId); + emit finished(!_isTaskFailed); } void PropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode) { if (statusCode == 404) { - qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata not found, ignoring."; - emit finished(true); + qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata not found, but let's proceed with removing the file anyway."; + deleteRemoteItem(_item->_encryptedFileName); return; } - qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata Received, Preparing it for the new file."; - - // Encrypt File! FolderMetadata metadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode); - QFileInfo info(_propagator->fullLocalPath(_item->_file)); + qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata Received, preparing it for removal of the file"; + + const QFileInfo info(_propagator->fullLocalPath(_item->_file)); const QString fileName = info.fileName(); // Find existing metadata for this file @@ -94,50 +70,18 @@ void PropagateRemoteDeleteEncrypted::slotFolderEncryptedMetadataReceived(const Q } if (!found) { - // The removed file was not in the JSON so nothing else to do - emit finished(true); + // file is not found in the metadata, but we still need to remove it + deleteRemoteItem(_item->_encryptedFileName); return; } qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Metadata updated, sending to the server."; - auto job = new UpdateMetadataApiJob(_propagator->account(), - _folderId, - metadata.encryptedMetadata(), - _folderToken); - - connect(job, &UpdateMetadataApiJob::success, this, [this] { emit finished(true); }); + auto job = new UpdateMetadataApiJob(_propagator->account(), _folderId, metadata.encryptedMetadata(), _folderToken); + connect(job, &UpdateMetadataApiJob::success, this, [this](const QByteArray& fileId) { + Q_UNUSED(fileId); + deleteRemoteItem(_item->_encryptedFileName); + }); connect(job, &UpdateMetadataApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed); job->start(); } - -void PropagateRemoteDeleteEncrypted::unlockFolder() -{ - if (!_folderLocked) { - emit folderUnlocked(); - return; - } - - qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Unlocking folder" << _folderId; - auto unlockJob = new UnlockEncryptFolderApiJob(_propagator->account(), - _folderId, _folderToken, this); - - connect(unlockJob, &UnlockEncryptFolderApiJob::success, [this] { - qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Folder successfully unlocked" << _folderId; - _folderLocked = false; - emit folderUnlocked(); - }); - connect(unlockJob, &UnlockEncryptFolderApiJob::error, this, &PropagateRemoteDeleteEncrypted::taskFailed); - unlockJob->start(); -} - -void PropagateRemoteDeleteEncrypted::taskFailed() -{ - qCDebug(PROPAGATE_REMOVE_ENCRYPTED) << "Task failed of job" << sender(); - if (_folderLocked) { - connect(this, &PropagateRemoteDeleteEncrypted::folderUnlocked, this, [this] { emit finished(false); }); - unlockFolder(); - } else { - emit finished(false); - } -} diff --git a/src/libsync/propagateremotedeleteencrypted.h b/src/libsync/propagateremotedeleteencrypted.h index 25ef3738b..f29ae1939 100644 --- a/src/libsync/propagateremotedeleteencrypted.h +++ b/src/libsync/propagateremotedeleteencrypted.h @@ -1,43 +1,34 @@ -#ifndef PROPAGATEREMOTEDELETEENCRYPTED_H -#define PROPAGATEREMOTEDELETEENCRYPTED_H - -#include -#include - -#include "syncfileitem.h" +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include "abstractpropagateremotedeleteencrypted.h" namespace OCC { -class OwncloudPropagator; -class PropagateRemoteDeleteEncrypted : public QObject +class PropagateRemoteDeleteEncrypted : public AbstractPropagateRemoteDeleteEncrypted { Q_OBJECT public: - PropagateRemoteDeleteEncrypted(OwncloudPropagator *_propagator, SyncFileItemPtr item, QObject *parent); - - QByteArray folderToken(); - void unlockFolder(); + PropagateRemoteDeleteEncrypted(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent); - void start(); - -signals: - void finished(bool success); - void folderUnlocked(); + virtual void start() Q_DECL_OVERRIDE; private: - void slotFolderEncryptedIdReceived(const QStringList &list); - void slotTryLock(const QByteArray &folderId); - void slotFolderLockedSuccessfully(const QByteArray &fileId, const QByteArray &token); - void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode); - void taskFailed(); - - OwncloudPropagator *_propagator; - SyncFileItemPtr _item; - QByteArray _folderToken; - QByteArray _folderId; - bool _folderLocked = false; + void slotFolderUnLockedSuccessfully(const QByteArray &folderId) override; + void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode) override; }; } - -#endif // PROPAGATEREMOTEDELETEENCRYPTED_H diff --git a/src/libsync/propagateremotedeleteencryptedrootfolder.cpp b/src/libsync/propagateremotedeleteencryptedrootfolder.cpp new file mode 100644 index 000000000..6dfe3c1d3 --- /dev/null +++ b/src/libsync/propagateremotedeleteencryptedrootfolder.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/* + * Removing the root encrypted folder is consisted of multiple steps: + * - 1st step is to obtain the folderID via LsColJob so it then can be used for the next step + * - 2nd step is to lock the root folder useing the folderID from the previous step. !!! NOTE: If there are no nested items in the folder, this, and subsequent steps are skipped until step 7. + * - 3rd step is to obtain the root folder's metadata (it contains list of nested files and folders) + * - 4th step is to remove the nested files and folders from the metadata and send it to the server via UpdateMetadataApiJob + * - 5th step is to trigger DeleteJob for every nested file and folder of the root folder + * - 6th step is to unlock the root folder using the previously obtained token from locking + * - 7th step is to decrypt and delete the root folder, because it is now possible as it has become empty + */ + +#include +#include + +#include "deletejob.h" +#include "clientsideencryptionjobs.h" +#include "clientsideencryption.h" +#include "encryptfolderjob.h" +#include "owncloudpropagator.h" +#include "propagateremotedeleteencryptedrootfolder.h" + +namespace { + const char* encryptedFileNamePropertyKey = "encryptedFileName"; +} + +using namespace OCC; + +Q_LOGGING_CATEGORY(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER, "nextcloud.sync.propagator.remove.encrypted.rootfolder") + +PropagateRemoteDeleteEncryptedRootFolder::PropagateRemoteDeleteEncryptedRootFolder(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent) + : AbstractPropagateRemoteDeleteEncrypted(propagator, item, parent) +{ + +} + +void PropagateRemoteDeleteEncryptedRootFolder::start() +{ + Q_ASSERT(_item->_isEncrypted); + + const bool listFilesResult = _propagator->_journal->listFilesInPath(_item->_file.toUtf8(), [this](const OCC::SyncJournalFileRecord &record) { + _nestedItems[record._e2eMangledName] = record; + }); + + if (!listFilesResult || _nestedItems.isEmpty()) { + // if the folder is empty, just decrypt and delete it + decryptAndRemoteDelete(); + return; + } + + startLsColJob(_item->_file); +} + +void PropagateRemoteDeleteEncryptedRootFolder::slotFolderUnLockedSuccessfully(const QByteArray &folderId) +{ + AbstractPropagateRemoteDeleteEncrypted::slotFolderUnLockedSuccessfully(folderId); + decryptAndRemoteDelete(); +} + +void PropagateRemoteDeleteEncryptedRootFolder::slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode) +{ + if (statusCode == 404) { + // we've eneded up having no metadata, but, _nestedItems is not empty since we went this far, let's proceed with removing the nested items without modifying the metadata + qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "There is no metadata for this folder. Just remove it's nested items."; + for (auto it = _nestedItems.constBegin(); it != _nestedItems.constEnd(); ++it) { + deleteNestedRemoteItem(it.key()); + } + return; + } + + FolderMetadata metadata(_propagator->account(), json.toJson(QJsonDocument::Compact), statusCode); + + qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "It's a root encrypted folder. Let's remove nested items first."; + + metadata.removeAllEncryptedFiles(); + + qCDebug(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Metadata updated, sending to the server."; + + auto job = new UpdateMetadataApiJob(_propagator->account(), _folderId, metadata.encryptedMetadata(), _folderToken); + connect(job, &UpdateMetadataApiJob::success, this, [this](const QByteArray& fileId) { + Q_UNUSED(fileId); + for (auto it = _nestedItems.constBegin(); it != _nestedItems.constEnd(); ++it) { + deleteNestedRemoteItem(it.key()); + } + }); + connect(job, &UpdateMetadataApiJob::error, this, &PropagateRemoteDeleteEncryptedRootFolder::taskFailed); + job->start(); +} + +void PropagateRemoteDeleteEncryptedRootFolder::slotDeleteNestedRemoteItemFinished() +{ + auto *deleteJob = qobject_cast(QObject::sender()); + + Q_ASSERT(deleteJob); + + if (!deleteJob) { + return; + } + + const QString encryptedFileName = deleteJob->property(encryptedFileNamePropertyKey).toString(); + + if (!encryptedFileName.isEmpty()) { + const auto nestedItem = _nestedItems.take(encryptedFileName); + + if (nestedItem.isValid()) { + _propagator->_journal->deleteFileRecord(nestedItem._path, nestedItem._type == ItemTypeDirectory); + _propagator->_journal->commit("Remote Remove"); + } + } + + QNetworkReply::NetworkError err = deleteJob->reply()->error(); + + const auto httpErrorCode = deleteJob->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + _item->_responseTimeStamp = deleteJob->responseTimestamp(); + _item->_requestId = deleteJob->requestId(); + + if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) { + storeFirstError(err); + storeFirstErrorString(deleteJob->errorString()); + qCWarning(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Delete nested item finished with error" << err << "."; + } else if (httpErrorCode != 204 && httpErrorCode != 404) { + // A 404 reply is also considered a success here: We want to make sure + // a file is gone from the server. It not being there in the first place + // is ok. This will happen for files that are in the DB but not on + // the server or the local file system. + + // Normally we expect "204 No Content" + // If it is not the case, it might be because of a proxy or gateway intercepting the request, so we must + // throw an error. + storeFirstErrorString(tr("Wrong HTTP code returned by server. Expected 204, but received \"%1 %2\".") + .arg(httpErrorCode) + .arg(deleteJob->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString())); + if (_item->_httpErrorCode == 0) { + _item->_httpErrorCode = httpErrorCode; + } + + qCWarning(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Delete nested item finished with error" << httpErrorCode << "."; + } + + if (_nestedItems.size() == 0) { + // we wait for all _nestedItems' DeleteJobs to finish, and then - fail if any of those jobs has failed + if (networkError() != QNetworkReply::NetworkError::NoError || _item->_httpErrorCode != 0) { + const int errorCode = networkError() != QNetworkReply::NetworkError::NoError ? networkError() : _item->_httpErrorCode; + qCCritical(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Delete of nested items finished with error" << errorCode << ". Failing the entire sequence."; + taskFailed(); + return; + } + unlockFolder(); + } +} + +void PropagateRemoteDeleteEncryptedRootFolder::deleteNestedRemoteItem(const QString &filename) +{ + qCInfo(PROPAGATE_REMOVE_ENCRYPTED_ROOTFOLDER) << "Deleting nested encrypted remote item" << filename; + + auto deleteJob = new DeleteJob(_propagator->account(), _propagator->fullRemotePath(filename), this); + deleteJob->setFolderToken(_folderToken); + deleteJob->setProperty(encryptedFileNamePropertyKey, filename); + + connect(deleteJob, &DeleteJob::finishedSignal, this, &PropagateRemoteDeleteEncryptedRootFolder::slotDeleteNestedRemoteItemFinished); + + deleteJob->start(); +} + +void PropagateRemoteDeleteEncryptedRootFolder::decryptAndRemoteDelete() +{ + auto job = new OCC::SetEncryptionFlagApiJob(_propagator->account(), _item->_fileId, OCC::SetEncryptionFlagApiJob::Clear, this); + connect(job, &OCC::SetEncryptionFlagApiJob::success, this, [this] (const QByteArray &fileId) { + Q_UNUSED(fileId); + deleteRemoteItem(_item->_file); + }); + connect(job, &OCC::SetEncryptionFlagApiJob::error, this, [this] (const QByteArray &fileId, int httpReturnCode) { + Q_UNUSED(fileId); + _item->_httpErrorCode = httpReturnCode; + taskFailed(); + }); + job->start(); +} diff --git a/src/libsync/propagateremotedeleteencryptedrootfolder.h b/src/libsync/propagateremotedeleteencryptedrootfolder.h new file mode 100644 index 000000000..6cb083a5d --- /dev/null +++ b/src/libsync/propagateremotedeleteencryptedrootfolder.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#pragma once + +#include + +#include "abstractpropagateremotedeleteencrypted.h" +#include "syncfileitem.h" + +namespace OCC { + +class PropagateRemoteDeleteEncryptedRootFolder : public AbstractPropagateRemoteDeleteEncrypted +{ + Q_OBJECT +public: + PropagateRemoteDeleteEncryptedRootFolder(OwncloudPropagator *propagator, SyncFileItemPtr item, QObject *parent); + + virtual void start() Q_DECL_OVERRIDE; + +private: + void slotFolderUnLockedSuccessfully(const QByteArray &folderId) override; + void slotFolderEncryptedMetadataReceived(const QJsonDocument &json, int statusCode) override; + void slotDeleteNestedRemoteItemFinished(); + + void deleteNestedRemoteItem(const QString &filename); + void decryptAndRemoteDelete(); + + QMap _nestedItems; // Nested files and folders +}; + +} diff --git a/src/libsync/propagateremotemkdir.cpp b/src/libsync/propagateremotemkdir.cpp index 4d1703054..3aeb49388 100644 --- a/src/libsync/propagateremotemkdir.cpp +++ b/src/libsync/propagateremotemkdir.cpp @@ -17,7 +17,7 @@ #include "account.h" #include "common/syncjournalfilerecord.h" #include "propagateuploadencrypted.h" -#include "propagateremotedelete.h" +#include "deletejob.h" #include "common/asserts.h" #include "encryptfolderjob.h" diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp index 0cbb73ec6..a3dcb6571 100644 --- a/src/libsync/propagateupload.cpp +++ b/src/libsync/propagateupload.cpp @@ -25,7 +25,7 @@ #include "propagatorjobs.h" #include "common/checksums.h" #include "syncengine.h" -#include "propagateremotedelete.h" +#include "deletejob.h" #include "common/asserts.h" #include "networkjobs.h" #include "clientsideencryption.h" diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp index e45d09d16..ca67b7c04 100644 --- a/src/libsync/propagateuploadng.cpp +++ b/src/libsync/propagateuploadng.cpp @@ -24,7 +24,7 @@ #include "propagatorjobs.h" #include "syncengine.h" #include "propagateremotemove.h" -#include "propagateremotedelete.h" +#include "deletejob.h" #include "common/asserts.h" #include diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 89d4057fc..f0c9d627e 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -23,7 +23,7 @@ #include "common/syncfilestatus.h" #include "csync_exclude.h" #include "filesystem.h" -#include "propagateremotedelete.h" +#include "deletejob.h" #include "propagatedownload.h" #include "common/asserts.h" #include "configfile.h"