Browse Source

Implement nested items removal when removing root encrypted folder.

Signed-off-by: allexzander <blackslayer4@gmail.com>
allexzander 5 years ago
parent
commit
4ed7feab2b

+ 3 - 0
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

+ 201 - 0
src/libsync/abstractpropagateremotedeleteencrypted.cpp

@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
+ *
+ * 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 <QFileInfo>
+#include <QLoggingCategory>
+
+#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<LsColJob *>(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<DeleteJob *>(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

+ 72 - 0
src/libsync/abstractpropagateremotedeleteencrypted.h

@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
+ *
+ * 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 <QObject>
+#include <QString>
+#include <QNetworkReply>
+
+#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;
+};
+
+}

+ 5 - 0
src/libsync/clientsideencryption.cpp

@@ -1468,6 +1468,11 @@ void FolderMetadata::removeEncryptedFile(const EncryptedFile &f)
     }
 }
 
+void FolderMetadata::removeAllEncryptedFiles()
+{
+    _files.clear();
+}
+
 QVector<EncryptedFile> FolderMetadata::files() const {
     return _files;
 }

+ 1 - 0
src/libsync/clientsideencryption.h

@@ -137,6 +137,7 @@ public:
     QByteArray encryptedMetadata();
     void addEncryptedFile(const EncryptedFile& f);
     void removeEncryptedFile(const EncryptedFile& f);
+    void removeAllEncryptedFiles();
     QVector<EncryptedFile> files() const;
 
 

+ 6 - 4
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();
 }
 

+ 9 - 3
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

+ 72 - 0
src/libsync/deletejob.cpp

@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
+ *
+ * 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 <QLoggingCategory>
+
+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

+ 46 - 0
src/libsync/deletejob.h

@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
+ *
+ * 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;
+};
+}

+ 1 - 1
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();

+ 20 - 69
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 <QLoggingCategory>
 
 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);
 }
 }

+ 3 - 26
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<DeleteJob> _job;
-    PropagateRemoteDeleteEncrypted *_deleteEncryptedHelper = nullptr;
+    AbstractPropagateRemoteDeleteEncrypted *_deleteEncryptedHelper = nullptr;
 
 public:
     PropagateRemoteDelete(OwncloudPropagator *propagator, const SyncFileItemPtr &item)

+ 33 - 89
src/libsync/propagateremotedeleteencrypted.cpp

@@ -1,85 +1,61 @@
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
+ *
+ * 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 <QLoggingCategory>
-#include <QMimeDatabase>
 #include <QFileInfo>
-#include <QDir>
 
 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<LsColJob *>(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);
-    }
-}

+ 22 - 31
src/libsync/propagateremotedeleteencrypted.h

@@ -1,43 +1,34 @@
-#ifndef PROPAGATEREMOTEDELETEENCRYPTED_H
-#define PROPAGATEREMOTEDELETEENCRYPTED_H
-
-#include <QObject>
-#include <QElapsedTimer>
-
-#include "syncfileitem.h"
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
+ *
+ * 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

+ 191 - 0
src/libsync/propagateremotedeleteencryptedrootfolder.cpp

@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
+ *
+ * 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 <QFileInfo>
+#include <QLoggingCategory>
+
+#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<DeleteJob *>(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();
+}

+ 43 - 0
src/libsync/propagateremotedeleteencryptedrootfolder.h

@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
+ *
+ * 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 <QMap>
+
+#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<QString, OCC::SyncJournalFileRecord> _nestedItems; // Nested files and folders
+};
+
+}

+ 1 - 1
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"
 

+ 1 - 1
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"

+ 1 - 1
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 <QNetworkAccessManager>

+ 1 - 1
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"