CFAPI: Handle cancelation of hydration requests
authorFelix Weilbach <felix.weilbach@nextcloud.com>
Tue, 16 Mar 2021 12:31:12 +0000 (13:31 +0100)
committerFelix Weilbach (Rebase PR Action) <felix.weilbach@t-online.de>
Thu, 18 Mar 2021 10:43:48 +0000 (10:43 +0000)
Signed-off-by: Felix Weilbach <felix.weilbach@nextcloud.com>
src/libsync/propagatedownload.cpp
src/libsync/propagatedownload.h
src/libsync/vfs/cfapi/cfapiwrapper.cpp
src/libsync/vfs/cfapi/hydrationjob.cpp
src/libsync/vfs/cfapi/hydrationjob.h
src/libsync/vfs/cfapi/vfs_cfapi.cpp
src/libsync/vfs/cfapi/vfs_cfapi.h

index 25d6e8e153416163f902a2b4ef06baa897a93852..8ad83206618eca82a4446ed9e8d6fb616a48c75c 100644 (file)
@@ -342,6 +342,15 @@ void GETFileJob::slotReadyRead()
     }
 }
 
+void GETFileJob::cancel()
+{
+    if (reply()->isRunning()) {
+        reply()->abort();
+    }
+
+    emit canceled();
+}
+
 void GETFileJob::onTimedOut()
 {
     qCWarning(lcGetJob) << "Timeout" << (reply() ? reply()->request().url() : path());
index 15c27ac5f1ef4ce63573c770367e7d4e3e859018..06c9cc7f1f2389e51bb374e6000b29b122d40228 100644 (file)
@@ -83,6 +83,8 @@ public:
         }
     }
 
+    void cancel();
+
     void newReplyHook(QNetworkReply *reply) override;
 
     void setBandwidthManager(BandwidthManager *bwm);
@@ -108,6 +110,7 @@ public:
     void setExpectedContentLength(qint64 size) { _expectedContentLength = size; }
 
 signals:
+    void canceled();
     void finishedSignal();
     void downloadProgress(qint64, qint64);
 private slots:
index b569c1c41a140919efc9f1e05ec863f784c25da9..1d64ec5d569207ea5b44ea7702bc17d1715aa9b4 100644 (file)
@@ -58,7 +58,6 @@ void cfApiSendTransferInfo(const CF_CONNECTION_KEY &connectionKey, const CF_TRAN
     }
 }
 
-
 void CALLBACK cfApiFetchDataCallback(const CF_CALLBACK_INFO *callbackInfo, const CF_CALLBACK_PARAMETERS *callbackParameters)
 {
     qDebug(lcCfApiWrapper) << "Fetch data callback called. File size:" << callbackInfo->FileSize.QuadPart;
@@ -181,8 +180,27 @@ void CALLBACK cfApiFetchDataCallback(const CF_CALLBACK_INFO *callbackInfo, const
     }
 }
 
+void CALLBACK cfApiCancelFetchData(const CF_CALLBACK_INFO *callbackInfo, const CF_CALLBACK_PARAMETERS * /*callbackParameters*/)
+{
+    const auto path = QString(QString::fromWCharArray(callbackInfo->VolumeDosName) + QString::fromWCharArray(callbackInfo->NormalizedPath));
+
+    qInfo(lcCfApiWrapper) << "Cancel fetch data of" << path;
+
+    auto vfs = reinterpret_cast<OCC::VfsCfApi *>(callbackInfo->CallbackContext);
+    Q_ASSERT(vfs->metaObject()->className() == QByteArrayLiteral("OCC::VfsCfApi"));
+    const auto requestId = QString::number(callbackInfo->TransferKey.QuadPart, 16);
+
+    const auto invokeResult = QMetaObject::invokeMethod(
+        vfs, [=] { vfs->cancelHydration(requestId, path); }, Qt::QueuedConnection);
+    if (!invokeResult) {
+        qCritical(lcCfApiWrapper) << "Failed to cancel hydration for" << path << requestId;
+    }
+}
+
+
 CF_CALLBACK_REGISTRATION cfApiCallbacks[] = {
     { CF_CALLBACK_TYPE_FETCH_DATA, cfApiFetchDataCallback },
+    { CF_CALLBACK_TYPE_CANCEL_FETCH_DATA, cfApiCancelFetchData },
     CF_CALLBACK_REGISTRATION_END
 };
 
index 9d98f38a9b61ba43a89bff1c09ff6c407c3ba314..d71751fda1d3e913fb5ee045270fbf0bf6c24c50 100644 (file)
@@ -116,6 +116,15 @@ void OCC::HydrationJob::start()
     connect(_server, &QLocalServer::newConnection, this, &HydrationJob::onNewConnection);
 }
 
+void OCC::HydrationJob::cancel()
+{
+    if (!_job) {
+        return;
+    }
+
+    _job->cancel();
+}
+
 void OCC::HydrationJob::emitFinished(Status status)
 {
     _status = status;
@@ -131,6 +140,16 @@ void OCC::HydrationJob::emitFinished(Status status)
     }
 }
 
+void OCC::HydrationJob::emitCanceled()
+{
+    connect(_socket, &QLocalSocket::disconnected, this, [=] {
+        _socket->close();
+    });
+    _socket->disconnectFromServer();
+
+    emit canceled(this);
+}
+
 void OCC::HydrationJob::onNewConnection()
 {
     Q_ASSERT(!_socket);
@@ -140,9 +159,16 @@ void OCC::HydrationJob::onNewConnection()
     _socket = _server->nextPendingConnection();
     _job = new GETFileJob(_account, _remotePath + _folderPath, _socket, {}, {}, 0, this);
     connect(_job, &GETFileJob::finishedSignal, this, &HydrationJob::onGetFinished);
+    connect(_job, &GETFileJob::canceled, this, &HydrationJob::onGetCanceled);
     _job->start();
 }
 
+void OCC::HydrationJob::onGetCanceled()
+{
+    qCInfo(lcHydration) << "GETFileJob canceled" << _requestId << _folderPath << _job->reply()->error();
+    emitCanceled();
+}
+
 void OCC::HydrationJob::onGetFinished()
 {
     qCInfo(lcHydration) << "GETFileJob finished" << _requestId << _folderPath << _job->reply()->error();
index 52709abe35cb64e1f1df84d3c2d0c1275c8be93b..47a12475152a1937457afa374b7f467bfc04b3ea 100644 (file)
@@ -57,15 +57,19 @@ public:
     Status status() const;
 
     void start();
+    void cancel();
 
 signals:
     void finished(HydrationJob *job);
+    void canceled(HydrationJob *job);
 
 private:
     void emitFinished(Status status);
+    void emitCanceled();
 
     void onNewConnection();
     void onGetFinished();
+    void onGetCanceled();
 
     AccountPtr _account;
     QString _remotePath;
index 6c6d9e49de3b2a6f4b0ca8c35831b159b7e1f092..925dacf35b3abb3aaef94a9f7c7e1d1df299cfb3 100644 (file)
@@ -264,6 +264,19 @@ Vfs::AvailabilityResult VfsCfApi::availability(const QString &folderPath)
     return AvailabilityError::NoSuchItem;
 }
 
+void VfsCfApi::cancelHydration(const QString &requestId, const QString & /*path*/)
+{
+    // Find matching hydration job for request id
+    const auto hydrationJobsIter = std::find_if(d->hydrationJobs.cbegin(), d->hydrationJobs.cend(), [&](const HydrationJob *job) {
+        return job->requestId() == requestId;
+    });
+
+    // If found, cancel it
+    if (hydrationJobsIter != d->hydrationJobs.cend()) {
+        (*hydrationJobsIter)->cancel();
+    }
+}
+
 void VfsCfApi::requestHydration(const QString &requestId, const QString &path)
 {
     qCInfo(lcCfApi) << "Received request to hydrate" << path << requestId;
@@ -331,6 +344,7 @@ void VfsCfApi::scheduleHydrationJob(const QString &requestId, const QString &fol
     job->setRequestId(requestId);
     job->setFolderPath(folderPath);
     connect(job, &HydrationJob::finished, this, &VfsCfApi::onHydrationJobFinished);
+    connect(job, &HydrationJob::canceled, this, &VfsCfApi::onHydrationJobCanceled);
     d->hydrationJobs << job;
     job->start();
     emit hydrationRequestReady(requestId);
@@ -347,6 +361,22 @@ void VfsCfApi::onHydrationJobFinished(HydrationJob *job)
     }
 }
 
+void VfsCfApi::onHydrationJobCanceled(HydrationJob *job)
+{
+    const auto folderPath = job->localPath();
+    const auto folderRelativePath = job->folderPath();
+
+    // Remove placeholder file because there might be already pumped
+    // some data into it
+    QFile::remove(folderPath + folderRelativePath);
+
+    // Create a new placeholder file
+    SyncJournalFileRecord record;
+    params().journal->getFileRecord(folderRelativePath, &record);
+    const auto item = SyncFileItem::fromSyncJournalFileRecord(record);
+    createPlaceholder(*item);
+}
+
 VfsCfApi::HydratationAndPinStates VfsCfApi::computeRecursiveHydrationAndPinStates(const QString &folderPath, const Optional<PinState> &basePinState)
 {
     Q_ASSERT(!folderPath.endsWith('/'));
index 96dda52d4fc4547216c6b6f49cc75fbfc4413181..90511a48a8511c6263eab2787ab06a031f390bbf 100644 (file)
@@ -53,6 +53,8 @@ public:
     Optional<PinState> pinState(const QString &folderPath) override;
     AvailabilityResult availability(const QString &folderPath) override;
 
+    void cancelHydration(const QString &requestId, const QString &path);
+
 public slots:
     void requestHydration(const QString &requestId, const QString &path);
     void fileStatusChanged(const QString &systemFileName, SyncFileStatus fileStatus) override;
@@ -68,6 +70,7 @@ protected:
 private:
     void scheduleHydrationJob(const QString &requestId, const QString &folderPath);
     void onHydrationJobFinished(HydrationJob *job);
+    void onHydrationJobCanceled(HydrationJob *job);
 
     struct HasHydratedDehydrated {
         bool hasHydrated = false;