From 5a3dccdc4aab86a160eabc77adf931bcdfcbcebf Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Thu, 23 May 2024 23:56:45 +0800 Subject: [PATCH 01/30] Add starter EditLocallyVerificationJob Signed-off-by: Claudio Cambra --- src/gui/CMakeLists.txt | 2 ++ src/gui/editlocallyverificationjob.cpp | 32 +++++++++++++++++++ src/gui/editlocallyverificationjob.h | 43 ++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 src/gui/editlocallyverificationjob.cpp create mode 100644 src/gui/editlocallyverificationjob.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 290e16e9443d8..fde6ac4a0d10d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -76,6 +76,8 @@ set(client_SRCS editlocallyjob.cpp editlocallymanager.h editlocallymanager.cpp + editlocallyverificationjob.h + editlocallyverificationjob.cpp filetagmodel.h filetagmodel.cpp folder.h diff --git a/src/gui/editlocallyverificationjob.cpp b/src/gui/editlocallyverificationjob.cpp new file mode 100644 index 0000000000000..b67c16fe7a61a --- /dev/null +++ b/src/gui/editlocallyverificationjob.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) by Claudio Cambra + * + * 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 "editlocallyverificationjob.h" + +#include + +namespace OCC +{ + +Q_LOGGING_CATEGORY(lcEditLocallyVerificationJob, "nextcloud.gui.editlocallyverificationjob", QtInfoMsg) + +EditLocallyVerificationJob::EditLocallyVerificationJob(const AccountStatePtr &accountState, const QString &relPath, const QString &token, QObject *const parent) + : QObject(parent) + , _accountState(accountState) + , _relPath(relPath) + , _token(token) +{ +} + +} diff --git a/src/gui/editlocallyverificationjob.h b/src/gui/editlocallyverificationjob.h new file mode 100644 index 0000000000000..bc53fd14e16e0 --- /dev/null +++ b/src/gui/editlocallyverificationjob.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) by Claudio Cambra + * + * 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 "accountstate.h" + +namespace OCC { + +class EditLocallyVerificationJob : public QObject +{ + Q_OBJECT + +public: + explicit EditLocallyVerificationJob(const AccountStatePtr &accountState, + const QString &relPath, + const QString &token, + QObject *const parent = nullptr); + +signals: + void error(const QString &message, const QString &informativeText); + void finished(); + +private: + AccountStatePtr _accountState; + QString _relPath; // full remote path for a file (as on the server) + QString _token; +}; + +} From d8f5996c4b1cea5d1f46eb07f60db9297931ebd6 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 24 May 2024 00:00:39 +0800 Subject: [PATCH 02/30] Send and respond to SimpleApiJob to verify edit locally request with server in validation job Signed-off-by: Claudio Cambra --- src/gui/editlocallyverificationjob.cpp | 46 +++++++++++++++++++++++++- src/gui/editlocallyverificationjob.h | 6 ++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/gui/editlocallyverificationjob.cpp b/src/gui/editlocallyverificationjob.cpp index b67c16fe7a61a..f90847a395220 100644 --- a/src/gui/editlocallyverificationjob.cpp +++ b/src/gui/editlocallyverificationjob.cpp @@ -15,13 +15,19 @@ #include "editlocallyverificationjob.h" #include +#include + +#include "libsync/networkjobs.h" namespace OCC { Q_LOGGING_CATEGORY(lcEditLocallyVerificationJob, "nextcloud.gui.editlocallyverificationjob", QtInfoMsg) -EditLocallyVerificationJob::EditLocallyVerificationJob(const AccountStatePtr &accountState, const QString &relPath, const QString &token, QObject *const parent) +EditLocallyVerificationJob::EditLocallyVerificationJob(const AccountStatePtr &accountState, + const QString &relPath, + const QString &token, + QObject *const parent) : QObject(parent) , _accountState(accountState) , _relPath(relPath) @@ -29,4 +35,42 @@ EditLocallyVerificationJob::EditLocallyVerificationJob(const AccountStatePtr &ac { } +void EditLocallyVerificationJob::start() +{ + if (!_accountState || _relPath.isEmpty() || _token.isEmpty()) { + qCWarning(lcEditLocallyVerificationJob) << "Could not start token check." + << "accountState:" << _accountState + << "relPath:" << _relPath + << "token:" << _token; + + emit error(tr("Could not start editing locally."), + tr("An error occurred trying to verify the request to edit locally.")); + return; + } + + const auto encodedToken = QString::fromUtf8(QUrl::toPercentEncoding(_token)); // Sanitise the token + const auto encodedRelPath = QUrl::toPercentEncoding(_relPath); // Sanitise the relPath + const auto checkTokenJob = new SimpleApiJob(_accountState->account(), + QStringLiteral("/ocs/v2.php/apps/files/api/v1/openlocaleditor/%1").arg(encodedToken)); + const auto slashedPath = encodedRelPath.startsWith('/') ? encodedRelPath : '/' + encodedRelPath; + + QUrlQuery params; + params.addQueryItem(QStringLiteral("path"), slashedPath); + checkTokenJob->addQueryParams(params); + checkTokenJob->setVerb(SimpleApiJob::Verb::Post); + connect(checkTokenJob, &SimpleApiJob::resultReceived, this, &EditLocallyVerificationJob::responseReceived); + + checkTokenJob->start(); +} + +void EditLocallyVerificationJob::responseReceived(const int statusCode) +{ + if (statusCode == 200) { + emit finished(); + } else { + emit error(tr("Could not start editing locally."), + tr("An error occurred trying to verify the request to edit locally.")); + } +} + } diff --git a/src/gui/editlocallyverificationjob.h b/src/gui/editlocallyverificationjob.h index bc53fd14e16e0..a7ca224294b12 100644 --- a/src/gui/editlocallyverificationjob.h +++ b/src/gui/editlocallyverificationjob.h @@ -34,6 +34,12 @@ class EditLocallyVerificationJob : public QObject void error(const QString &message, const QString &informativeText); void finished(); +public slots: + void start(); + +private slots: + void responseReceived(const int statusCode); + private: AccountStatePtr _accountState; QString _relPath; // full remote path for a file (as on the server) From 1422df733a6174d7a2fd87a8e90426fb09000940 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 24 May 2024 00:16:41 +0800 Subject: [PATCH 03/30] Ensure verification job gets deleted after finish Signed-off-by: Claudio Cambra --- src/gui/editlocallyverificationjob.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/editlocallyverificationjob.cpp b/src/gui/editlocallyverificationjob.cpp index f90847a395220..861c4c09d322d 100644 --- a/src/gui/editlocallyverificationjob.cpp +++ b/src/gui/editlocallyverificationjob.cpp @@ -71,6 +71,8 @@ void EditLocallyVerificationJob::responseReceived(const int statusCode) emit error(tr("Could not start editing locally."), tr("An error occurred trying to verify the request to edit locally.")); } + + deleteLater(); } } From a5713fc233f18ff9dfaa09cca456687c6af1c0fb Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 24 May 2024 00:17:09 +0800 Subject: [PATCH 04/30] Use verification job within edit locally job Signed-off-by: Claudio Cambra --- src/gui/editlocallyjob.cpp | 44 ++++---------------------------------- src/gui/editlocallyjob.h | 1 - 2 files changed, 4 insertions(+), 41 deletions(-) diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index 9af8e83de3b5d..e2d7f5639a150 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -18,7 +18,7 @@ #include #include -#include "editlocallymanager.h" +#include "editlocallyverificationjob.h" #include "folder.h" #include "folderman.h" #include "syncengine.h" @@ -84,45 +84,9 @@ void EditLocallyJob::startSetup() void EditLocallyJob::startTokenRemoteCheck() { - if (!_accountState || _relPath.isEmpty() || _token.isEmpty()) { - qCWarning(lcEditLocallyJob) << "Could not start token check." - << "accountState:" << _accountState - << "relPath:" << _relPath - << "token:" << _token; - - showError(tr("Could not start editing locally."), - tr("An error occurred trying to verify the request to edit locally.")); - return; - } - - const auto encodedToken = QString::fromUtf8(QUrl::toPercentEncoding(_token)); // Sanitise the token - const auto encodedRelPath = QUrl::toPercentEncoding(_relPath); // Sanitise the relPath - - const auto checkTokenJob = new SimpleApiJob(_accountState->account(), - QStringLiteral("/ocs/v2.php/apps/files/api/v1/openlocaleditor/%1").arg(encodedToken)); - - QUrlQuery params; - params.addQueryItem(QStringLiteral("path"), prefixSlashToPath(encodedRelPath)); - checkTokenJob->addQueryParams(params); - checkTokenJob->setVerb(SimpleApiJob::Verb::Post); - connect(checkTokenJob, &SimpleApiJob::resultReceived, this, &EditLocallyJob::remoteTokenCheckResultReceived); - - checkTokenJob->start(); -} - -void EditLocallyJob::remoteTokenCheckResultReceived(const int statusCode) -{ - qCInfo(lcEditLocallyJob) << "token check result" << statusCode; - - constexpr auto HTTP_OK_CODE = 200; - _tokenVerified = statusCode == HTTP_OK_CODE; - - if (!_tokenVerified) { - showError(tr("Could not validate the request to open a file from server."), tr("Please try again.")); - return; - } - - findAfolderAndConstructPaths(); + const auto verificationJob = new EditLocallyVerificationJob(_accountState, _relPath, _token); + connect(verificationJob, &EditLocallyVerificationJob::error, this, &EditLocallyJob::showError); + connect(verificationJob, &EditLocallyVerificationJob::finished, this, &EditLocallyJob::findAfolderAndConstructPaths); } void EditLocallyJob::proceedWithSetup() diff --git a/src/gui/editlocallyjob.h b/src/gui/editlocallyjob.h index e778cf4a8cc21..84bb11123e88d 100644 --- a/src/gui/editlocallyjob.h +++ b/src/gui/editlocallyjob.h @@ -63,7 +63,6 @@ private slots: void showErrorNotification(const QString &message, const QString &informativeText) const; void showErrorMessageBox(const QString &message, const QString &informativeText) const; - void remoteTokenCheckResultReceived(const int statusCode); void slotItemDiscovered(const OCC::SyncFileItemPtr &item); void slotItemCompleted(const OCC::SyncFileItemPtr &item); From e38368d4285bd9ab2823ce020a3ea6fbde749197 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Fri, 24 May 2024 00:46:50 +0800 Subject: [PATCH 05/30] Move token and relpath validity checks to edit locally verification job Signed-off-by: Claudio Cambra --- src/gui/editlocallyjob.cpp | 68 ++--------------------- src/gui/editlocallyjob.h | 4 +- src/gui/editlocallyverificationjob.cpp | 74 +++++++++++++++++++++++--- src/gui/editlocallyverificationjob.h | 3 ++ 4 files changed, 75 insertions(+), 74 deletions(-) diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index e2d7f5639a150..8f0a906740f1b 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -44,9 +44,9 @@ void EditLocallyJob::startSetup() { if (_token.isEmpty() || _relPath.isEmpty() || _userId.isEmpty()) { qCWarning(lcEditLocallyJob) << "Could not start setup." - << "token:" << _token - << "relPath:" << _relPath - << "userId" << _userId; + << "token:" << _token + << "relPath:" << _relPath + << "userId" << _userId; return; } @@ -54,35 +54,14 @@ void EditLocallyJob::startSetup() // verified the token Systray::instance()->createEditFileLocallyLoadingDialog({}); - // We check the input data locally first, without modifying any state or - // showing any potentially misleading data to the user - if (!isTokenValid(_token)) { - qCWarning(lcEditLocallyJob) << "Edit locally request is missing a valid token, will not open file. " - << "Token received was:" << _token; - showError(tr("Invalid token received."), tr("Please try again.")); - return; - } - - if (!isRelPathValid(_relPath)) { - qCWarning(lcEditLocallyJob) << "Provided relPath was:" << _relPath << "which is not canonical."; - showError(tr("Invalid file path was provided."), tr("Please try again.")); - return; - } - _accountState = AccountManager::instance()->accountFromUserId(_userId); - if (!_accountState) { - qCWarning(lcEditLocallyJob) << "Could not find an account " << _userId << " to edit file " << _relPath << " locally."; - showError(tr("Could not find an account for local editing."), tr("Please try again.")); - return; - } - // We now ask the server to verify the token, before we again modify any // state or look at local files - startTokenRemoteCheck(); + startCheck(); } -void EditLocallyJob::startTokenRemoteCheck() +void EditLocallyJob::startCheck() { const auto verificationJob = new EditLocallyVerificationJob(_accountState, _relPath, _token); connect(verificationJob, &EditLocallyVerificationJob::error, this, &EditLocallyJob::showError); @@ -300,43 +279,6 @@ const QString EditLocallyJob::getRelativePathParent() const return QStringLiteral("/"); } -bool EditLocallyJob::isTokenValid(const QString &token) -{ - if (token.isEmpty()) { - return false; - } - - // Token is an alphanumeric string 128 chars long. - // Ensure that is what we received and what we are sending to the server. - static const QRegularExpression tokenRegex("^[a-zA-Z0-9]{128}$"); - const auto regexMatch = tokenRegex.match(token); - - return regexMatch.hasMatch(); -} - -bool EditLocallyJob::isRelPathValid(const QString &relPath) -{ - if (relPath.isEmpty()) { - return false; - } - - // We want to check that the path is canonical and not relative - // (i.e. that it doesn't contain ../../) but we always receive - // a relative path, so let's make it absolute by prepending a - // slash - const auto slashPrefixedPath = prefixSlashToPath(relPath); - - // Let's check that the filepath is canonical, and that the request - // contains no funny behaviour regarding paths - const auto cleanedPath = QDir::cleanPath(slashPrefixedPath); - - if (cleanedPath != slashPrefixedPath) { - return false; - } - - return true; -} - OCC::Folder *EditLocallyJob::findFolderForFile(const QString &relPath, const QString &userId) { if (relPath.isEmpty()) { diff --git a/src/gui/editlocallyjob.h b/src/gui/editlocallyjob.h index 84bb11123e88d..32f096ab8f250 100644 --- a/src/gui/editlocallyjob.h +++ b/src/gui/editlocallyjob.h @@ -37,8 +37,6 @@ class EditLocallyJob : public QObject const QString &token, QObject *parent = nullptr); - [[nodiscard]] static bool isTokenValid(const QString &token); - [[nodiscard]] static bool isRelPathValid(const QString &relPath); [[nodiscard]] static OCC::Folder *findFolderForFile(const QString &relPath, const QString &userId); [[nodiscard]] static QString prefixSlashToPath(const QString &path); @@ -55,7 +53,7 @@ private slots: void fetchRemoteFileParentInfo(); void startSyncBeforeOpening(); - void startTokenRemoteCheck(); + void startCheck(); void proceedWithSetup(); void findAfolderAndConstructPaths(); diff --git a/src/gui/editlocallyverificationjob.cpp b/src/gui/editlocallyverificationjob.cpp index 861c4c09d322d..8331a8dda8982 100644 --- a/src/gui/editlocallyverificationjob.cpp +++ b/src/gui/editlocallyverificationjob.cpp @@ -14,11 +14,21 @@ #include "editlocallyverificationjob.h" +#include #include #include #include "libsync/networkjobs.h" +namespace { + +QString prefixSlashToPath(const QString &path) +{ + return path.startsWith('/') ? path : '/' + path; +} + +} + namespace OCC { @@ -35,16 +45,64 @@ EditLocallyVerificationJob::EditLocallyVerificationJob(const AccountStatePtr &ac { } +bool EditLocallyVerificationJob::isTokenValid(const QString &token) +{ + if (token.isEmpty()) { + return false; + } + + // Token is an alphanumeric string 128 chars long. + // Ensure that is what we received and what we are sending to the server. + static const QRegularExpression tokenRegex("^[a-zA-Z0-9]{128}$"); + const auto regexMatch = tokenRegex.match(token); + + return regexMatch.hasMatch(); +} + +bool EditLocallyVerificationJob::isRelPathValid(const QString &relPath) +{ + if (relPath.isEmpty()) { + return false; + } + + // We want to check that the path is canonical and not relative + // (i.e. that it doesn't contain ../../) but we always receive + // a relative path, so let's make it absolute by prepending a + // slash + const auto slashPrefixedPath = prefixSlashToPath(relPath); + + // Let's check that the filepath is canonical, and that the request + // contains no funny behaviour regarding paths + const auto cleanedPath = QDir::cleanPath(slashPrefixedPath); + + if (cleanedPath != slashPrefixedPath) { + return false; + } + + return true; +} + void EditLocallyVerificationJob::start() { - if (!_accountState || _relPath.isEmpty() || _token.isEmpty()) { - qCWarning(lcEditLocallyVerificationJob) << "Could not start token check." - << "accountState:" << _accountState - << "relPath:" << _relPath - << "token:" << _token; + // We check the input data locally first, without modifying any state or + // showing any potentially misleading data to the user + if (!isTokenValid(_token)) { + qCWarning(lcEditLocallyVerificationJob) << "Edit locally request is missing a valid token, will not open file. " + << "Token received was:" << _token; + emit error(tr("Invalid token received."), tr("Please try again.")); + return; + } - emit error(tr("Could not start editing locally."), - tr("An error occurred trying to verify the request to edit locally.")); + if (!isRelPathValid(_relPath)) { + qCWarning(lcEditLocallyVerificationJob) << "Provided relPath was:" << _relPath + << "which is not canonical."; + emit error(tr("Invalid file path was provided."), tr("Please try again.")); + return; + } + + if (!_accountState) { + qCWarning(lcEditLocallyVerificationJob) << "No account found to edit file " << _relPath << " locally."; + emit error(tr("Could not find an account for local editing."), tr("Please try again.")); return; } @@ -52,7 +110,7 @@ void EditLocallyVerificationJob::start() const auto encodedRelPath = QUrl::toPercentEncoding(_relPath); // Sanitise the relPath const auto checkTokenJob = new SimpleApiJob(_accountState->account(), QStringLiteral("/ocs/v2.php/apps/files/api/v1/openlocaleditor/%1").arg(encodedToken)); - const auto slashedPath = encodedRelPath.startsWith('/') ? encodedRelPath : '/' + encodedRelPath; + const auto slashedPath = prefixSlashToPath(encodedRelPath); QUrlQuery params; params.addQueryItem(QStringLiteral("path"), slashedPath); diff --git a/src/gui/editlocallyverificationjob.h b/src/gui/editlocallyverificationjob.h index a7ca224294b12..1bd5b13955a18 100644 --- a/src/gui/editlocallyverificationjob.h +++ b/src/gui/editlocallyverificationjob.h @@ -30,6 +30,9 @@ class EditLocallyVerificationJob : public QObject const QString &token, QObject *const parent = nullptr); + [[nodiscard]] static bool isTokenValid(const QString &token); + [[nodiscard]] static bool isRelPathValid(const QString &relPath); + signals: void error(const QString &message, const QString &informativeText); void finished(); From a5c7bfe584a899a5dc9bb1ec6ea791d85bea74c0 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 27 May 2024 18:26:25 +0800 Subject: [PATCH 06/30] Ensure we emit error during edit locally job setup issues Signed-off-by: Claudio Cambra --- src/gui/editlocallyjob.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index 8f0a906740f1b..8b2e45085a685 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -47,6 +47,7 @@ void EditLocallyJob::startSetup() << "token:" << _token << "relPath:" << _relPath << "userId" << _userId; + showError(tr("Could not start editing locally."), tr("An error occurred during setup.")); return; } From 776118247481e3a797186967c415683aa1a7b0b1 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 27 May 2024 18:31:11 +0800 Subject: [PATCH 07/30] Directly use accountstate in editlocallyjob Signed-off-by: Claudio Cambra --- src/gui/editlocallyjob.cpp | 31 ++++++++++++++++--------------- src/gui/editlocallyjob.h | 2 +- src/gui/editlocallymanager.cpp | 9 ++++++--- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index 8b2e45085a685..3e50e2f1afc8b 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -28,12 +28,12 @@ namespace OCC { Q_LOGGING_CATEGORY(lcEditLocallyJob, "nextcloud.gui.editlocallyjob", QtInfoMsg) -EditLocallyJob::EditLocallyJob(const QString &userId, - const QString &relPath, - const QString &token, - QObject *parent) +EditLocallyJob::EditLocallyJob(const AccountStatePtr &accountState, + const QString &relPath, + const QString &token, + QObject *parent) : QObject{parent} - , _userId(userId) + , _accountState(accountState) , _relPath(relPath) , _token(token) { @@ -42,11 +42,11 @@ EditLocallyJob::EditLocallyJob(const QString &userId, void EditLocallyJob::startSetup() { - if (_token.isEmpty() || _relPath.isEmpty() || _userId.isEmpty()) { + if (_token.isEmpty() || _relPath.isEmpty() || !_accountState) { qCWarning(lcEditLocallyJob) << "Could not start setup." << "token:" << _token << "relPath:" << _relPath - << "userId" << _userId; + << "accountState:" << _accountState; showError(tr("Could not start editing locally."), tr("An error occurred during setup.")); return; } @@ -55,8 +55,6 @@ void EditLocallyJob::startSetup() // verified the token Systray::instance()->createEditFileLocallyLoadingDialog({}); - _accountState = AccountManager::instance()->accountFromUserId(_userId); - // We now ask the server to verify the token, before we again modify any // state or look at local files startCheck(); @@ -84,7 +82,7 @@ void EditLocallyJob::proceedWithSetup() } _fileName = relPathSplit.last(); - _folderForFile = findFolderForFile(_relPath, _userId); + _folderForFile = findFolderForFile(_relPath, _accountState->account()->userIdAtHostWithPort()); if (!_folderForFile) { showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath); @@ -104,7 +102,7 @@ void EditLocallyJob::proceedWithSetup() void EditLocallyJob::findAfolderAndConstructPaths() { - _folderForFile = findFolderForFile(_relPath, _userId); + _folderForFile = findFolderForFile(_relPath, _accountState->account()->userIdAtHostWithPort()); if (!_folderForFile) { showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath); @@ -287,19 +285,22 @@ OCC::Folder *EditLocallyJob::findFolderForFile(const QString &relPath, const QSt } const auto folderMap = FolderMan::instance()->map(); - const auto relPathSplit = relPath.split(QLatin1Char('/')); // a file is on the first level of remote root, so, we just need a proper folder that points to a remote root if (relPathSplit.size() == 1) { - const auto foundIt = std::find_if(std::begin(folderMap), std::end(folderMap), [&userId](const OCC::Folder *folder) { - return folder->remotePath() == QStringLiteral("/") && folder->accountState()->account()->userIdAtHostWithPort() == userId; + const auto foundIt = std::find_if(std::begin(folderMap), + std::end(folderMap), + [&userId](const OCC::Folder *folder) { + const auto folderUserId = folder->accountState()->account()->userIdAtHostWithPort(); + return folder->remotePath() == QStringLiteral("/") && folderUserId == userId; }); return foundIt != std::end(folderMap) ? foundIt.value() : nullptr; } - const auto relPathWithSlash = relPath.startsWith(QStringLiteral("/")) ? relPath : QStringLiteral("/") + relPath; + const auto relPathWithSlash = + relPath.startsWith(QStringLiteral("/")) ? relPath : QStringLiteral("/") + relPath; for (const auto &folder : folderMap) { // make sure we properly handle folders with non-root(nested) remote paths diff --git a/src/gui/editlocallyjob.h b/src/gui/editlocallyjob.h index 32f096ab8f250..3ee4a37bad2bd 100644 --- a/src/gui/editlocallyjob.h +++ b/src/gui/editlocallyjob.h @@ -32,7 +32,7 @@ class EditLocallyJob : public QObject Q_OBJECT public: - explicit EditLocallyJob(const QString &userId, + explicit EditLocallyJob(const AccountStatePtr &accountState, const QString &relPath, const QString &token, QObject *parent = nullptr); diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index 8784c159b963b..56bbca5a127af 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -18,6 +18,8 @@ #include #include +#include "accountmanager.h" + namespace OCC { Q_LOGGING_CATEGORY(lcEditLocallyManager, "nextcloud.gui.editlocallymanager", QtInfoMsg) @@ -40,7 +42,8 @@ EditLocallyManager *EditLocallyManager::instance() void EditLocallyManager::editLocally(const QUrl &url) { const auto inputs = parseEditLocallyUrl(url); - createJob(inputs.userId, inputs.relPath, inputs.token); + const auto accountState = AccountManager::instance()->accountFromUserId(inputs.userId); + createJob(accountState, inputs.relPath, inputs.token); } EditLocallyManager::EditLocallyInputData EditLocallyManager::parseEditLocallyUrl(const QUrl &url) @@ -68,14 +71,14 @@ EditLocallyManager::EditLocallyInputData EditLocallyManager::parseEditLocallyUrl return {userId, fileRemotePath, token}; } -void EditLocallyManager::createJob(const QString &userId, const QString &relPath, const QString &token) +void EditLocallyManager::createJob(const AccountStatePtr &accountState, { if (_jobs.contains(token)) { return; } - const EditLocallyJobPtr job(new EditLocallyJob(userId, relPath, token)); + const EditLocallyJobPtr job(new EditLocallyJob(accountState, relPath, token)); // We need to make sure the job sticks around until it is finished _jobs.insert(token, job); From 1659b2158e4c9d0f199266e36b7060c93683d15c Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 27 May 2024 18:54:14 +0800 Subject: [PATCH 08/30] Add a EditLocallyVerificationJobPtr type Signed-off-by: Claudio Cambra --- src/gui/editlocallyverificationjob.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/editlocallyverificationjob.h b/src/gui/editlocallyverificationjob.h index 1bd5b13955a18..3a71f2e1e47a9 100644 --- a/src/gui/editlocallyverificationjob.h +++ b/src/gui/editlocallyverificationjob.h @@ -20,6 +20,9 @@ namespace OCC { +class EditLocallyVerificationJob; +using EditLocallyVerificationJobPtr = QSharedPointer; + class EditLocallyVerificationJob : public QObject { Q_OBJECT From 83beaf8c8041f8828ad861f90101e93ee4b9c4d9 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 27 May 2024 19:01:44 +0800 Subject: [PATCH 09/30] Move verification job handling and scheduling out of editlocallyjob, into editlocallymanager Signed-off-by: Claudio Cambra --- src/gui/application.cpp | 4 +- src/gui/cocoainitializer_mac.mm | 2 +- src/gui/editlocallyjob.cpp | 78 ++++++++++++++------------------- src/gui/editlocallyjob.h | 2 - src/gui/editlocallymanager.cpp | 35 +++++++++++---- src/gui/editlocallymanager.h | 15 ++++--- 6 files changed, 74 insertions(+), 62 deletions(-) diff --git a/src/gui/application.cpp b/src/gui/application.cpp index c2d0b55c3729a..083f58a978b09 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -963,7 +963,7 @@ void Application::handleEditLocallyFromOptions() return; } - EditLocallyManager::instance()->editLocally(_editFileLocallyUrl); + EditLocallyManager::instance()->handleRequest(_editFileLocallyUrl); _editFileLocallyUrl.clear(); } @@ -1125,7 +1125,7 @@ bool Application::event(QEvent *event) // On macOS, Qt does not handle receiving a custom URI as it does on other systems (as an application argument). // Instead, it sends out a QFileOpenEvent. We therefore need custom handling for our URI handling on macOS. qCInfo(lcApplication) << "macOS: Opening local file for editing: " << openEvent->url(); - EditLocallyManager::instance()->editLocally(openEvent->url()); + EditLocallyManager::instance()->handleRequest(openEvent->url()); } else { const auto errorParsingLocalFileEditingUrl = QStringLiteral("The supplied url for local file editing '%1' is invalid!").arg(openEvent->url().toString()); qCInfo(lcApplication) << errorParsingLocalFileEditingUrl; diff --git a/src/gui/cocoainitializer_mac.mm b/src/gui/cocoainitializer_mac.mm index e2bc287facc48..571ff6b1c2637 100644 --- a/src/gui/cocoainitializer_mac.mm +++ b/src/gui/cocoainitializer_mac.mm @@ -59,7 +59,7 @@ - (void)handleURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEv { NSURL* url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]]; const auto qtUrl = QUrl::fromNSURL(url); - OCC::EditLocallyManager::instance()->editLocally(qtUrl); + OCC::EditLocallyManager::instance()->handleRequest(qtUrl); } @end diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index 3e50e2f1afc8b..ca0264ca56736 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -54,50 +54,7 @@ void EditLocallyJob::startSetup() // Show the loading dialog but don't show the filename until we have // verified the token Systray::instance()->createEditFileLocallyLoadingDialog({}); - - // We now ask the server to verify the token, before we again modify any - // state or look at local files - startCheck(); -} - -void EditLocallyJob::startCheck() -{ - const auto verificationJob = new EditLocallyVerificationJob(_accountState, _relPath, _token); - connect(verificationJob, &EditLocallyVerificationJob::error, this, &EditLocallyJob::showError); - connect(verificationJob, &EditLocallyVerificationJob::finished, this, &EditLocallyJob::findAfolderAndConstructPaths); -} - -void EditLocallyJob::proceedWithSetup() -{ - if (!_tokenVerified) { - qCWarning(lcEditLocallyJob) << "Could not proceed with setup as token is not verified."; - showError(tr("Could not validate the request to open a file from server."), tr("Please try again.")); - return; - } - - const auto relPathSplit = _relPath.split(QLatin1Char('/')); - if (relPathSplit.isEmpty()) { - showError(tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), _relPath); - return; - } - - _fileName = relPathSplit.last(); - _folderForFile = findFolderForFile(_relPath, _accountState->account()->userIdAtHostWithPort()); - - if (!_folderForFile) { - showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath); - return; - } - - if (!isFileParentItemValid()) { - showError(tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), _relPath); - return; - } - - _localFilePath = _folderForFile->path() + _relativePathToRemoteRoot; - - Systray::instance()->destroyEditFileLocallyLoadingDialog(); - startEditLocally(); + findAfolderAndConstructPaths(); } void EditLocallyJob::findAfolderAndConstructPaths() @@ -165,6 +122,39 @@ void EditLocallyJob::fetchRemoteFileParentInfo() job->start(); } +void EditLocallyJob::proceedWithSetup() +{ + if (!_tokenVerified) { + qCWarning(lcEditLocallyJob) << "Could not proceed with setup as token is not verified."; + showError(tr("Could not validate the request to open a file from server."), tr("Please try again.")); + return; + } + + const auto relPathSplit = _relPath.split(QLatin1Char('/')); + if (relPathSplit.isEmpty()) { + showError(tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), _relPath); + return; + } + + _fileName = relPathSplit.last(); + _folderForFile = findFolderForFile(_relPath, _accountState->account()->userIdAtHostWithPort()); + + if (!_folderForFile) { + showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath); + return; + } + + if (!isFileParentItemValid()) { + showError(tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), _relPath); + return; + } + + _localFilePath = _folderForFile->path() + _relativePathToRemoteRoot; + + Systray::instance()->destroyEditFileLocallyLoadingDialog(); + startEditLocally(); +} + bool EditLocallyJob::checkIfFileParentSyncIsNeeded() { if (_relPathParent == QLatin1String("/")) { diff --git a/src/gui/editlocallyjob.h b/src/gui/editlocallyjob.h index 3ee4a37bad2bd..e50b47c7edc2d 100644 --- a/src/gui/editlocallyjob.h +++ b/src/gui/editlocallyjob.h @@ -53,7 +53,6 @@ private slots: void fetchRemoteFileParentInfo(); void startSyncBeforeOpening(); - void startCheck(); void proceedWithSetup(); void findAfolderAndConstructPaths(); @@ -94,7 +93,6 @@ private slots: bool _shouldScheduleFolderSyncAfterFileIsOpened = false; AccountStatePtr _accountState; - QString _userId; QString _relPath; // full remote path for a file (as on the server) QString _relativePathToRemoteRoot; // (relative path - Folder::remotePath()) for folders pointing to a non-root remote path e.g. '/subfolder' instead of '/' QString _relPathParent; // a folder where the file resides ('/' if it is in the first level of a remote root, or e.g. a '/subfolder/a/b/c if it resides in a nested folder) diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index 56bbca5a127af..7cfb5c72c9aa1 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -19,6 +19,7 @@ #include #include "accountmanager.h" +#include "editlocallyverificationjob.h" namespace OCC { @@ -39,11 +40,11 @@ EditLocallyManager *EditLocallyManager::instance() return _instance; } -void EditLocallyManager::editLocally(const QUrl &url) +void EditLocallyManager::handleRequest(const QUrl &url) { const auto inputs = parseEditLocallyUrl(url); const auto accountState = AccountManager::instance()->accountFromUserId(inputs.userId); - createJob(accountState, inputs.relPath, inputs.token); + verify(accountState, inputs.relPath, inputs.token); } EditLocallyManager::EditLocallyInputData EditLocallyManager::parseEditLocallyUrl(const QUrl &url) @@ -71,18 +72,36 @@ EditLocallyManager::EditLocallyInputData EditLocallyManager::parseEditLocallyUrl return {userId, fileRemotePath, token}; } - const QString &relPath, - const QString &token) -void EditLocallyManager::createJob(const AccountStatePtr &accountState, +void EditLocallyManager::verify(const AccountStatePtr &accountState, + const QString &relPath, + const QString &token) { - if (_jobs.contains(token)) { + const auto removeJob = [this, token] { _verificationJobs.remove(token); }; + const auto startEditLocally = [this, accountState, relPath, token, removeJob] { + removeJob(); + editLocally(accountState, relPath, token); + }; + const auto verificationJob = EditLocallyVerificationJobPtr( + new EditLocallyVerificationJob(accountState, relPath, token) + ); + _verificationJobs.insert(token, verificationJob); + connect(verificationJob.data(), &EditLocallyVerificationJob::error, this, removeJob); + connect(verificationJob.data(), &EditLocallyVerificationJob::finished, this, startEditLocally); + verificationJob->start(); +} + +void EditLocallyManager::editLocally(const AccountStatePtr &accountState, + const QString &relPath, + const QString &token) +{ + if (_editLocallyJobs.contains(token)) { return; } const EditLocallyJobPtr job(new EditLocallyJob(accountState, relPath, token)); // We need to make sure the job sticks around until it is finished - _jobs.insert(token, job); + _editLocallyJobs.insert(token, job); - const auto removeJob = [this, token] { _jobs.remove(token); }; + const auto removeJob = [this, token] { _editLocallyJobs.remove(token); }; connect(job.data(), &EditLocallyJob::error, this, removeJob); connect(job.data(), &EditLocallyJob::finished, this, removeJob); diff --git a/src/gui/editlocallymanager.h b/src/gui/editlocallymanager.h index 079ba2c83194b..baa52d5c393d3 100644 --- a/src/gui/editlocallymanager.h +++ b/src/gui/editlocallymanager.h @@ -18,6 +18,7 @@ #include #include "editlocallyjob.h" +#include "editlocallyverificationjob.h" namespace OCC { @@ -29,12 +30,15 @@ class EditLocallyManager : public QObject [[nodiscard]] static EditLocallyManager *instance(); public slots: - void editLocally(const QUrl &url); + void handleRequest(const QUrl &url); private slots: - void createJob(const QString &userId, - const QString &relPath, - const QString &token); + void verify(const AccountStatePtr &accountState, + const QString &relPath, + const QString &token); + void editLocally(const AccountStatePtr &accountState, + const QString &relPath, + const QString &token); private: explicit EditLocallyManager(QObject *parent = nullptr); @@ -48,7 +52,8 @@ private slots: [[nodiscard]] static EditLocallyInputData parseEditLocallyUrl(const QUrl &url); - QHash _jobs; + QHash _verificationJobs; + QHash _editLocallyJobs; }; } From 35f988dd8bf420450da82698d0ba878501d110c0 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 27 May 2024 22:30:27 +0800 Subject: [PATCH 10/30] Move show error display features from editlocallyjob to editlocallymanager Signed-off-by: Claudio Cambra --- src/gui/editlocallyjob.cpp | 36 +++------------------------------- src/gui/editlocallyjob.h | 2 -- src/gui/editlocallymanager.cpp | 32 +++++++++++++++++++++++++++++- src/gui/editlocallymanager.h | 3 +++ 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index ca0264ca56736..1654d76ca5205 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -18,6 +18,7 @@ #include #include +#include "editlocallymanager.h" #include "editlocallyverificationjob.h" #include "folder.h" #include "folderman.h" @@ -318,42 +319,11 @@ OCC::Folder *EditLocallyJob::findFolderForFile(const QString &relPath, const QSt void EditLocallyJob::showError(const QString &message, const QString &informativeText) { - Systray::instance()->destroyEditFileLocallyLoadingDialog(); - showErrorNotification(message, informativeText); - // to make sure the error is not missed, show a message box in addition - showErrorMessageBox(message, informativeText); + Systray::instance()->destroyEditFileLocallyLoadingDialog(); + EditLocallyManager::showError(message, informativeText); Q_EMIT error(message, informativeText); } -void EditLocallyJob::showErrorNotification(const QString &message, const QString &informativeText) const -{ - if (!_accountState || !_accountState->account()) { - return; - } - - const auto folderMap = FolderMan::instance()->map(); - const auto foundFolder = std::find_if(folderMap.cbegin(), folderMap.cend(), [this](const auto &folder) { - return _accountState->account()->davUrl() == folder->remoteUrl(); - }); - - if (foundFolder != folderMap.cend()) { - emit (*foundFolder)->syncEngine().addErrorToGui(SyncFileItem::SoftError, message, informativeText, OCC::ErrorCategory::GenericError); - } -} - -void EditLocallyJob::showErrorMessageBox(const QString &message, const QString &informativeText) const -{ - const auto messageBox = new QMessageBox; - messageBox->setAttribute(Qt::WA_DeleteOnClose); - messageBox->setText(message); - messageBox->setInformativeText(informativeText); - messageBox->setIcon(QMessageBox::Warning); - messageBox->addButton(QMessageBox::StandardButton::Ok); - messageBox->show(); - messageBox->activateWindow(); - messageBox->raise(); -} - void EditLocallyJob::startEditLocally() { if (_fileName.isEmpty() || _localFilePath.isEmpty() || !_folderForFile) { diff --git a/src/gui/editlocallyjob.h b/src/gui/editlocallyjob.h index e50b47c7edc2d..6a0aef0ee7a83 100644 --- a/src/gui/editlocallyjob.h +++ b/src/gui/editlocallyjob.h @@ -57,8 +57,6 @@ private slots: void findAfolderAndConstructPaths(); void showError(const QString &message, const QString &informativeText); - void showErrorNotification(const QString &message, const QString &informativeText) const; - void showErrorMessageBox(const QString &message, const QString &informativeText) const; void slotItemDiscovered(const OCC::SyncFileItemPtr &item); void slotItemCompleted(const OCC::SyncFileItemPtr &item); diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index 7cfb5c72c9aa1..69fc494e02340 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -14,12 +14,14 @@ #include "editlocallymanager.h" +#include +#include #include #include -#include #include "accountmanager.h" #include "editlocallyverificationjob.h" +#include "systray.h" namespace OCC { @@ -40,6 +42,34 @@ EditLocallyManager *EditLocallyManager::instance() return _instance; } +void EditLocallyManager::showError(const QString &message, const QString &informativeText) +{ + showErrorNotification(message, informativeText); + // to make sure the error is not missed, show a message box in addition + showErrorMessageBox(message, informativeText); + qCWarning(lcEditLocallyManager) << message << informativeText; +} + +void EditLocallyManager::showErrorNotification(const QString &message, + const QString &informativeText) +{ + Systray::instance()->showMessage(message, informativeText, Systray::MessageIcon::Critical); +} + +void EditLocallyManager::showErrorMessageBox(const QString &message, + const QString &informativeText) +{ + const auto messageBox = new QMessageBox; + messageBox->setAttribute(Qt::WA_DeleteOnClose); + messageBox->setText(message); + messageBox->setInformativeText(informativeText); + messageBox->setIcon(QMessageBox::Warning); + messageBox->addButton(QMessageBox::StandardButton::Ok); + messageBox->show(); + messageBox->activateWindow(); + messageBox->raise(); +} + void EditLocallyManager::handleRequest(const QUrl &url) { const auto inputs = parseEditLocallyUrl(url); diff --git a/src/gui/editlocallymanager.h b/src/gui/editlocallymanager.h index baa52d5c393d3..d8727a2db0858 100644 --- a/src/gui/editlocallymanager.h +++ b/src/gui/editlocallymanager.h @@ -28,6 +28,7 @@ class EditLocallyManager : public QObject public: [[nodiscard]] static EditLocallyManager *instance(); + static void showError(const QString &message, const QString &informativeText); public slots: void handleRequest(const QUrl &url); @@ -50,6 +51,8 @@ private slots: QString token; }; + static void showErrorNotification(const QString &message, const QString &informativeText); + static void showErrorMessageBox(const QString &message, const QString &informativeText); [[nodiscard]] static EditLocallyInputData parseEditLocallyUrl(const QUrl &url); QHash _verificationJobs; From 00cd258edd23c0e2f35593f87915b85eaff2d727 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 27 May 2024 22:40:11 +0800 Subject: [PATCH 11/30] Remove token related tasks from editlocallyjob Signed-off-by: Claudio Cambra --- src/gui/editlocallyjob.cpp | 13 +------------ src/gui/editlocallyjob.h | 4 ---- src/gui/editlocallymanager.cpp | 3 ++- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index 1654d76ca5205..94be3a27afa8a 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -31,29 +31,24 @@ Q_LOGGING_CATEGORY(lcEditLocallyJob, "nextcloud.gui.editlocallyjob", QtInfoMsg) EditLocallyJob::EditLocallyJob(const AccountStatePtr &accountState, const QString &relPath, - const QString &token, QObject *parent) : QObject{parent} , _accountState(accountState) , _relPath(relPath) - , _token(token) { connect(this, &EditLocallyJob::callShowError, this, &EditLocallyJob::showError, Qt::QueuedConnection); } void EditLocallyJob::startSetup() { - if (_token.isEmpty() || _relPath.isEmpty() || !_accountState) { + if (_relPath.isEmpty() || !_accountState) { qCWarning(lcEditLocallyJob) << "Could not start setup." - << "token:" << _token << "relPath:" << _relPath << "accountState:" << _accountState; showError(tr("Could not start editing locally."), tr("An error occurred during setup.")); return; } - // Show the loading dialog but don't show the filename until we have - // verified the token Systray::instance()->createEditFileLocallyLoadingDialog({}); findAfolderAndConstructPaths(); } @@ -125,12 +120,6 @@ void EditLocallyJob::fetchRemoteFileParentInfo() void EditLocallyJob::proceedWithSetup() { - if (!_tokenVerified) { - qCWarning(lcEditLocallyJob) << "Could not proceed with setup as token is not verified."; - showError(tr("Could not validate the request to open a file from server."), tr("Please try again.")); - return; - } - const auto relPathSplit = _relPath.split(QLatin1Char('/')); if (relPathSplit.isEmpty()) { showError(tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), _relPath); diff --git a/src/gui/editlocallyjob.h b/src/gui/editlocallyjob.h index 6a0aef0ee7a83..d1029376c23e5 100644 --- a/src/gui/editlocallyjob.h +++ b/src/gui/editlocallyjob.h @@ -34,7 +34,6 @@ class EditLocallyJob : public QObject public: explicit EditLocallyJob(const AccountStatePtr &accountState, const QString &relPath, - const QString &token, QObject *parent = nullptr); [[nodiscard]] static OCC::Folder *findFolderForFile(const QString &relPath, const QString &userId); @@ -86,15 +85,12 @@ private slots: [[nodiscard]] bool isFileParentItemValid() const; - bool _tokenVerified = false; - bool _shouldScheduleFolderSyncAfterFileIsOpened = false; AccountStatePtr _accountState; QString _relPath; // full remote path for a file (as on the server) QString _relativePathToRemoteRoot; // (relative path - Folder::remotePath()) for folders pointing to a non-root remote path e.g. '/subfolder' instead of '/' QString _relPathParent; // a folder where the file resides ('/' if it is in the first level of a remote root, or e.g. a '/subfolder/a/b/c if it resides in a nested folder) - QString _token; SyncFileItemPtr _fileParentItem; QString _fileName; diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index 69fc494e02340..29de4fba2daa0 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -127,7 +127,8 @@ void EditLocallyManager::editLocally(const AccountStatePtr &accountState, if (_editLocallyJobs.contains(token)) { return; } - const EditLocallyJobPtr job(new EditLocallyJob(accountState, relPath, token)); + + const EditLocallyJobPtr job(new EditLocallyJob(accountState, relPath)); // We need to make sure the job sticks around until it is finished _editLocallyJobs.insert(token, job); From 263563d821663b574707434f91b24369a0bc340c Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 27 May 2024 22:40:45 +0800 Subject: [PATCH 12/30] Display token verification failure through GUI in edit locally manager Signed-off-by: Claudio Cambra --- src/gui/editlocallymanager.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index 29de4fba2daa0..b23ca9437cc45 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -106,17 +106,32 @@ void EditLocallyManager::verify(const AccountStatePtr &accountState, const QString &relPath, const QString &token) { - const auto removeJob = [this, token] { _verificationJobs.remove(token); }; - const auto startEditLocally = [this, accountState, relPath, token, removeJob] { - removeJob(); + // Show the loading dialog but don't show the filename until we have + // verified the token + Systray::instance()->createEditFileLocallyLoadingDialog({}); + + const auto finishedHandler = [this, token] { + Systray::instance()->destroyEditFileLocallyLoadingDialog(); + _verificationJobs.remove(token); + }; + const auto errorEditLocally = [finishedHandler] { + finishedHandler(); + showError(tr("Could not validate the request to open a file from server."), + tr("Please try again.")); + }; + const auto startEditLocally = [this, accountState, relPath, token, finishedHandler] { + finishedHandler(); editLocally(accountState, relPath, token); }; const auto verificationJob = EditLocallyVerificationJobPtr( new EditLocallyVerificationJob(accountState, relPath, token) ); _verificationJobs.insert(token, verificationJob); - connect(verificationJob.data(), &EditLocallyVerificationJob::error, this, removeJob); + connect(verificationJob.data(), &EditLocallyVerificationJob::error, this, errorEditLocally); connect(verificationJob.data(), &EditLocallyVerificationJob::finished, this, startEditLocally); + + // We now ask the server to verify the token, before we again modify any + // state or look at local files verificationJob->start(); } From 1080389d955a355436abfbdc8225017660e120b9 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 27 May 2024 22:45:20 +0800 Subject: [PATCH 13/30] Show edit locally loading dialog with filename immediately in editlocallyjob constructor By the time we launch this job we ahve already finished verifying the token after recent changes Signed-off-by: Claudio Cambra --- src/gui/editlocallyjob.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index 94be3a27afa8a..7ec6b32532dc8 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -19,7 +19,6 @@ #include #include "editlocallymanager.h" -#include "editlocallyverificationjob.h" #include "folder.h" #include "folderman.h" #include "syncengine.h" @@ -49,7 +48,15 @@ void EditLocallyJob::startSetup() return; } - Systray::instance()->createEditFileLocallyLoadingDialog({}); + const auto relPathSplit = _relPath.split(QLatin1Char('/')); + if (relPathSplit.isEmpty()) { + showError(tr("Could not find a file for local editing." + "Make sure its path is valid and it is synced locally."), _relPath); + return; + } + + _fileName = relPathSplit.last(); + Systray::instance()->createEditFileLocallyLoadingDialog(_fileName); findAfolderAndConstructPaths(); } @@ -120,15 +127,8 @@ void EditLocallyJob::fetchRemoteFileParentInfo() void EditLocallyJob::proceedWithSetup() { - const auto relPathSplit = _relPath.split(QLatin1Char('/')); - if (relPathSplit.isEmpty()) { - showError(tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), _relPath); - return; - } - - _fileName = relPathSplit.last(); _folderForFile = findFolderForFile(_relPath, _accountState->account()->userIdAtHostWithPort()); - + if (!_folderForFile) { showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath); return; @@ -141,7 +141,6 @@ void EditLocallyJob::proceedWithSetup() _localFilePath = _folderForFile->path() + _relativePathToRemoteRoot; - Systray::instance()->destroyEditFileLocallyLoadingDialog(); startEditLocally(); } @@ -325,8 +324,6 @@ void EditLocallyJob::startEditLocally() return; } - Systray::instance()->createEditFileLocallyLoadingDialog(_fileName); - if (_folderForFile->isSyncRunning()) { // in case sync is already running - terminate it and start a new one _syncTerminatedConnection = connect(_folderForFile, &Folder::syncFinished, this, [this]() { From 5df96b80e05f899bf7d91d512ebb0df319571d10 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 27 May 2024 22:49:41 +0800 Subject: [PATCH 14/30] Do not automatically delete edit locally token verification job upon completion, rely on manager's smart pointers Signed-off-by: Claudio Cambra --- src/gui/editlocallyverificationjob.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gui/editlocallyverificationjob.cpp b/src/gui/editlocallyverificationjob.cpp index 8331a8dda8982..dd6417e3defc3 100644 --- a/src/gui/editlocallyverificationjob.cpp +++ b/src/gui/editlocallyverificationjob.cpp @@ -129,8 +129,6 @@ void EditLocallyVerificationJob::responseReceived(const int statusCode) emit error(tr("Could not start editing locally."), tr("An error occurred trying to verify the request to edit locally.")); } - - deleteLater(); } } From 834e0965ee831d545127b06293dbf2a0c684e35a Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 28 May 2024 16:03:19 +0800 Subject: [PATCH 15/30] Add starter FileProviderEditLocally class Signed-off-by: Claudio Cambra --- src/gui/CMakeLists.txt | 2 ++ src/gui/macOS/fileprovidereditlocallyjob.cpp | 32 +++++++++++++++++ src/gui/macOS/fileprovidereditlocallyjob.h | 38 ++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/gui/macOS/fileprovidereditlocallyjob.cpp create mode 100644 src/gui/macOS/fileprovidereditlocallyjob.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index fde6ac4a0d10d..8cf5eb98166e8 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -290,6 +290,8 @@ IF( APPLE ) macOS/fileproviderdomainmanager_mac.mm macOS/fileproviderdomainsyncstatus.h macOS/fileproviderdomainsyncstatus_mac.mm + macOS/fileprovidereditlocallyjob.h + macOS/fileprovidereditlocallyjob.cpp macOS/fileprovideritemmetadata.h macOS/fileprovideritemmetadata.cpp macOS/fileprovideritemmetadata_mac.mm diff --git a/src/gui/macOS/fileprovidereditlocallyjob.cpp b/src/gui/macOS/fileprovidereditlocallyjob.cpp new file mode 100644 index 0000000000000..97a089745a134 --- /dev/null +++ b/src/gui/macOS/fileprovidereditlocallyjob.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) by Claudio Cambra + * + * 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 "fileprovidereditlocallyjob.h" + +#include + +namespace OCC::Mac { + +Q_LOGGING_CATEGORY(lcFileProviderEditLocallyJob, "nextcloud.gui.fileprovidereditlocally", QtInfoMsg) + +FileProviderEditLocallyJob::FileProviderEditLocallyJob(const AccountStatePtr &accountState, + const QString &relPath, + QObject *const parent) + : QObject(parent) + , _accountState(accountState) + , _relPath(relPath) +{ +} + +} // namespace OCC::Mac diff --git a/src/gui/macOS/fileprovidereditlocallyjob.h b/src/gui/macOS/fileprovidereditlocallyjob.h new file mode 100644 index 0000000000000..5c0c9d85db579 --- /dev/null +++ b/src/gui/macOS/fileprovidereditlocallyjob.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) by Claudio Cambra + * + * 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 "accountstate.h" + +namespace OCC::Mac { + +class FileProviderEditLocallyJob : public QObject +{ + Q_OBJECT + +public: + explicit FileProviderEditLocallyJob(const AccountStatePtr &accountState, + const QString &relPath, + QObject * const parent = nullptr); + +private: + AccountStatePtr _accountState; + QString _relPath; +}; + +} From 7ee296acdb68c514f1e84a22f20da3a24d2a90eb Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 28 May 2024 18:31:18 +0800 Subject: [PATCH 16/30] Add FileProviderEditLocallyPtr type Signed-off-by: Claudio Cambra --- src/gui/macOS/fileprovidereditlocallyjob.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/macOS/fileprovidereditlocallyjob.h b/src/gui/macOS/fileprovidereditlocallyjob.h index 5c0c9d85db579..0300e14faf94f 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.h +++ b/src/gui/macOS/fileprovidereditlocallyjob.h @@ -21,6 +21,9 @@ namespace OCC::Mac { +class FileProviderEditLocallyJob; +using FileProviderEditLocallyJobPtr = QSharedPointer; + class FileProviderEditLocallyJob : public QObject { Q_OBJECT From 9642a03455f13cef6c3fd6be2b8881f096013c24 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 28 May 2024 18:33:37 +0800 Subject: [PATCH 17/30] Add completion signals to fileprovidereditlocallyjob Signed-off-by: Claudio Cambra --- src/gui/macOS/fileprovidereditlocallyjob.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gui/macOS/fileprovidereditlocallyjob.h b/src/gui/macOS/fileprovidereditlocallyjob.h index 0300e14faf94f..2e6dce576198b 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.h +++ b/src/gui/macOS/fileprovidereditlocallyjob.h @@ -33,6 +33,11 @@ class FileProviderEditLocallyJob : public QObject const QString &relPath, QObject * const parent = nullptr); +signals: + void error(const QString &message, const QString &informativeText); + void notAvailable(); + void finished(); + private: AccountStatePtr _accountState; QString _relPath; From 5ce3cdc6ea99ed67d037e1930f732ba463e25cb6 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 28 May 2024 18:37:54 +0800 Subject: [PATCH 18/30] Try to use FileProviderEditLocallyJob before attempting standard EditLocallyJob in EditLocallyManager Signed-off-by: Claudio Cambra --- src/gui/editlocallymanager.cpp | 41 ++++++++++++++++++++++++++++++---- src/gui/editlocallymanager.h | 16 +++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index b23ca9437cc45..aa5d781897585 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -23,6 +23,10 @@ #include "editlocallyverificationjob.h" #include "systray.h" +#ifdef BUILD_FILE_PROVIDER_MODULE +#include "macOS/fileprovidereditlocallyjob.h" +#endif + namespace OCC { Q_LOGGING_CATEGORY(lcEditLocallyManager, "nextcloud.gui.editlocallymanager", QtInfoMsg) @@ -121,7 +125,11 @@ void EditLocallyManager::verify(const AccountStatePtr &accountState, }; const auto startEditLocally = [this, accountState, relPath, token, finishedHandler] { finishedHandler(); - editLocally(accountState, relPath, token); +#ifdef BUILD_FILE_PROVIDER_MODULE + editLocallyFileProvider(accountState, relPath, token); +#else + editLocally(accountState, relPath, token); +#endif }; const auto verificationJob = EditLocallyVerificationJobPtr( new EditLocallyVerificationJob(accountState, relPath, token) @@ -135,6 +143,32 @@ void EditLocallyManager::verify(const AccountStatePtr &accountState, verificationJob->start(); } +#ifdef BUILD_FILE_PROVIDER_MODULE +void EditLocallyManager::editLocallyFileProvider(const AccountStatePtr &accountState, + const QString &relPath, + const QString &token) +{ + if (_editLocallyFpJobs.contains(token)) { + return; + } + + const auto removeJob = [this, token] { _editLocallyFpJobs.remove(token); }; + const auto tryStandardJob = [this, accountState, relPath, token, removeJob] { + removeJob(); + editLocally(accountState, relPath, token); + }; + const Mac::FileProviderEditLocallyJobPtr job( + new Mac::FileProviderEditLocallyJob(accountState, relPath) + ); + // We need to make sure the job sticks around until it is finished + _editLocallyFpJobs.insert(token, job); + + connect(job.data(), &Mac::FileProviderEditLocallyJob::error, this, removeJob); + connect(job.data(), &Mac::FileProviderEditLocallyJob::notAvailable, this, tryStandardJob); + connect(job.data(), &Mac::FileProviderEditLocallyJob::finished, this, removeJob); +} +#endif + void EditLocallyManager::editLocally(const AccountStatePtr &accountState, const QString &relPath, const QString &token) @@ -143,12 +177,11 @@ void EditLocallyManager::editLocally(const AccountStatePtr &accountState, return; } + const auto removeJob = [this, token] { _editLocallyJobs.remove(token); }; const EditLocallyJobPtr job(new EditLocallyJob(accountState, relPath)); // We need to make sure the job sticks around until it is finished _editLocallyJobs.insert(token, job); - - const auto removeJob = [this, token] { _editLocallyJobs.remove(token); }; - + connect(job.data(), &EditLocallyJob::error, this, removeJob); connect(job.data(), &EditLocallyJob::finished, this, removeJob); diff --git a/src/gui/editlocallymanager.h b/src/gui/editlocallymanager.h index d8727a2db0858..a0263168b0419 100644 --- a/src/gui/editlocallymanager.h +++ b/src/gui/editlocallymanager.h @@ -17,9 +17,14 @@ #include #include +#include "config.h" #include "editlocallyjob.h" #include "editlocallyverificationjob.h" +#ifdef BUILD_FILE_PROVIDER_MODULE +#include "macOS/fileprovidereditlocallyjob.h" +#endif + namespace OCC { class EditLocallyManager : public QObject @@ -40,6 +45,13 @@ private slots: void editLocally(const AccountStatePtr &accountState, const QString &relPath, const QString &token); + +#ifdef BUILD_FILE_PROVIDER_MODULE + void editLocallyFileProvider(const AccountStatePtr &accountState, + const QString &relPath, + const QString &token); +#endif + private: explicit EditLocallyManager(QObject *parent = nullptr); @@ -57,6 +69,10 @@ private slots: QHash _verificationJobs; QHash _editLocallyJobs; + +#ifdef BUILD_FILE_PROVIDER_MODULE + QHash _editLocallyFpJobs; +#endif }; } From eb5fa218c35cd6e9d92eb1d22608c4993bac70e1 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 28 May 2024 19:05:39 +0800 Subject: [PATCH 19/30] Add showError to FileProviderEditLocally Signed-off-by: Claudio Cambra --- src/gui/macOS/fileprovidereditlocallyjob.cpp | 9 +++++++++ src/gui/macOS/fileprovidereditlocallyjob.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/src/gui/macOS/fileprovidereditlocallyjob.cpp b/src/gui/macOS/fileprovidereditlocallyjob.cpp index 97a089745a134..547da6ed94158 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.cpp +++ b/src/gui/macOS/fileprovidereditlocallyjob.cpp @@ -16,6 +16,7 @@ #include +#include "editlocallymanager.h" namespace OCC::Mac { Q_LOGGING_CATEGORY(lcFileProviderEditLocallyJob, "nextcloud.gui.fileprovidereditlocally", QtInfoMsg) @@ -29,4 +30,12 @@ FileProviderEditLocallyJob::FileProviderEditLocallyJob(const AccountStatePtr &ac { } +void FileProviderEditLocallyJob::showError(const QString &message, + const QString &informativeText) +{ + Systray::instance()->destroyEditFileLocallyLoadingDialog(); + EditLocallyManager::showError(message, informativeText); + Q_EMIT error(message, informativeText); +} + } // namespace OCC::Mac diff --git a/src/gui/macOS/fileprovidereditlocallyjob.h b/src/gui/macOS/fileprovidereditlocallyjob.h index 2e6dce576198b..9fd89bb120cdd 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.h +++ b/src/gui/macOS/fileprovidereditlocallyjob.h @@ -38,6 +38,9 @@ class FileProviderEditLocallyJob : public QObject void notAvailable(); void finished(); +private slots: + void showError(const QString &message, const QString &informativeText); + private: AccountStatePtr _accountState; QString _relPath; From 98a0d8117088b2c04e87d282060bf7e7596e597b Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 28 May 2024 19:06:34 +0800 Subject: [PATCH 20/30] Begin setting up dialog for file provider edit locally job Signed-off-by: Claudio Cambra --- src/gui/macOS/fileprovidereditlocallyjob.cpp | 23 ++++++++++++++++++++ src/gui/macOS/fileprovidereditlocallyjob.h | 3 +++ 2 files changed, 26 insertions(+) diff --git a/src/gui/macOS/fileprovidereditlocallyjob.cpp b/src/gui/macOS/fileprovidereditlocallyjob.cpp index 547da6ed94158..7105debd280db 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.cpp +++ b/src/gui/macOS/fileprovidereditlocallyjob.cpp @@ -17,6 +17,8 @@ #include #include "editlocallymanager.h" +#include "systray.h" + namespace OCC::Mac { Q_LOGGING_CATEGORY(lcFileProviderEditLocallyJob, "nextcloud.gui.fileprovidereditlocally", QtInfoMsg) @@ -30,6 +32,27 @@ FileProviderEditLocallyJob::FileProviderEditLocallyJob(const AccountStatePtr &ac { } +void FileProviderEditLocallyJob::start() +{ + if (_relPath.isEmpty() || !_accountState) { + qCWarning(lcFileProviderEditLocallyJob) << "Could not start setup." + << "relPath:" << _relPath + << "accountState:" << _accountState; + showError(tr("Could not start editing locally."), tr("An error occurred during setup.")); + return; + } + + const auto relPathSplit = _relPath.split(QLatin1Char('/')); + if (relPathSplit.isEmpty()) { + showError(tr("Could not find a file for local editing." + "Make sure its path is valid and it is synced locally."), _relPath); + return; + } + + const auto filename = relPathSplit.last(); + Systray::instance()->createEditFileLocallyLoadingDialog(filename); +} + void FileProviderEditLocallyJob::showError(const QString &message, const QString &informativeText) { diff --git a/src/gui/macOS/fileprovidereditlocallyjob.h b/src/gui/macOS/fileprovidereditlocallyjob.h index 9fd89bb120cdd..45ed2aac027bc 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.h +++ b/src/gui/macOS/fileprovidereditlocallyjob.h @@ -33,6 +33,9 @@ class FileProviderEditLocallyJob : public QObject const QString &relPath, QObject * const parent = nullptr); +public slots: + void start(); + signals: void error(const QString &message, const QString &informativeText); void notAvailable(); From 231d30e94205416cb2e0590c33f5f5736cc57115 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 28 May 2024 19:17:55 +0800 Subject: [PATCH 21/30] Fetch ocId for a given relative path Signed-off-by: Claudio Cambra --- src/gui/macOS/fileprovidereditlocallyjob.cpp | 24 ++++++++++++++++++++ src/gui/macOS/fileprovidereditlocallyjob.h | 3 +++ 2 files changed, 27 insertions(+) diff --git a/src/gui/macOS/fileprovidereditlocallyjob.cpp b/src/gui/macOS/fileprovidereditlocallyjob.cpp index 7105debd280db..10a2e402db3a9 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.cpp +++ b/src/gui/macOS/fileprovidereditlocallyjob.cpp @@ -17,6 +17,7 @@ #include #include "editlocallymanager.h" +#include "networkjobs.h" #include "systray.h" namespace OCC::Mac { @@ -51,6 +52,11 @@ void FileProviderEditLocallyJob::start() const auto filename = relPathSplit.last(); Systray::instance()->createEditFileLocallyLoadingDialog(filename); + + const auto idJob = new PropfindJob(_accountState->account(), _relPath, this); + idJob->setProperties({ QByteArrayLiteral("http://owncloud.org/ns:id") }); + connect(idJob, &PropfindJob::finishedWithError, this, &FileProviderEditLocallyJob::idGetError); + connect(idJob, &PropfindJob::result, this, &FileProviderEditLocallyJob::idGetFinished); } void FileProviderEditLocallyJob::showError(const QString &message, @@ -61,4 +67,22 @@ void FileProviderEditLocallyJob::showError(const QString &message, Q_EMIT error(message, informativeText); } +void FileProviderEditLocallyJob::idGetError(const QNetworkReply &reply) +{ + const auto errorString = reply.errorString(); + qCWarning(lcFileProviderEditLocallyJob) << "Could not get file ocId." << errorString; + showError(tr("Could not get file id."), errorString); +} + +void FileProviderEditLocallyJob::idGetFinished(const QVariantMap &data) +{ + const auto ocId = data.value("id").toString(); + if (ocId.isEmpty()) { + qCWarning(lcFileProviderEditLocallyJob) << "Could not get file ocId."; + showError(tr("Could not get file identifier."), tr("The file identifier is empty.")); + return; + } + +} + } // namespace OCC::Mac diff --git a/src/gui/macOS/fileprovidereditlocallyjob.h b/src/gui/macOS/fileprovidereditlocallyjob.h index 45ed2aac027bc..3c4c70f8f7fed 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.h +++ b/src/gui/macOS/fileprovidereditlocallyjob.h @@ -14,6 +14,7 @@ #pragma once +#include #include #include @@ -43,6 +44,8 @@ public slots: private slots: void showError(const QString &message, const QString &informativeText); + void idGetError(const QNetworkReply &reply); + void idGetFinished(const QVariantMap &data); private: AccountStatePtr _accountState; From 26c08e43280c4fb777e5eec6ebb77eaff2d1fede Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 29 May 2024 00:00:17 +0800 Subject: [PATCH 22/30] Expose domain manager in Mac::FileProvider Signed-off-by: Claudio Cambra --- src/gui/macOS/fileprovider.h | 1 + src/gui/macOS/fileprovider_mac.mm | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/gui/macOS/fileprovider.h b/src/gui/macOS/fileprovider.h index 58d6fcc2bdfe8..d4ddcf596d30a 100644 --- a/src/gui/macOS/fileprovider.h +++ b/src/gui/macOS/fileprovider.h @@ -40,6 +40,7 @@ class FileProvider : public QObject [[nodiscard]] static bool fileProviderAvailable(); [[nodiscard]] FileProviderXPC *xpc() const; + [[nodiscard]] FileProviderDomainManager *domainManager() const; private slots: void configureXPC(); diff --git a/src/gui/macOS/fileprovider_mac.mm b/src/gui/macOS/fileprovider_mac.mm index 807372b1acf65..73c2e0d259f95 100644 --- a/src/gui/macOS/fileprovider_mac.mm +++ b/src/gui/macOS/fileprovider_mac.mm @@ -102,5 +102,10 @@ return _xpc.get(); } +FileProviderDomainManager *FileProvider::domainManager() const +{ + return _domainManager.get(); +} + } // namespace Mac } // namespace OCC From a5a9a0258e4240ab200105d5b02708d8b1b82efa Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 29 May 2024 00:00:33 +0800 Subject: [PATCH 23/30] Allow retrieval of domain associated with account in FileProviderDomainManager Signed-off-by: Claudio Cambra --- src/gui/macOS/fileproviderdomainmanager.h | 1 + .../macOS/fileproviderdomainmanager_mac.mm | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/gui/macOS/fileproviderdomainmanager.h b/src/gui/macOS/fileproviderdomainmanager.h index dcb1f9f6071c3..ecbed149c9f39 100644 --- a/src/gui/macOS/fileproviderdomainmanager.h +++ b/src/gui/macOS/fileproviderdomainmanager.h @@ -36,6 +36,7 @@ class FileProviderDomainManager : public QObject static QString fileProviderDomainIdentifierFromAccountState(const AccountStatePtr &accountState); void start(); + void* domainForAccount(const OCC::AccountState * const accountState); signals: void domainSetupComplete(); diff --git a/src/gui/macOS/fileproviderdomainmanager_mac.mm b/src/gui/macOS/fileproviderdomainmanager_mac.mm index 64e49163dc94d..e433717047844 100644 --- a/src/gui/macOS/fileproviderdomainmanager_mac.mm +++ b/src/gui/macOS/fileproviderdomainmanager_mac.mm @@ -154,6 +154,22 @@ void findExistingFileProviderDomains() } } + NSFileProviderDomain *domainForAccount(const AccountState * const accountState) + { + if (@available(macOS 11.0, *)) { + Q_ASSERT(accountState); + const auto account = accountState->account(); + Q_ASSERT(account); + + const auto domainId = domainIdentifierForAccount(account); + if (_registeredDomains.contains(domainId)) { + return _registeredDomains[domainId]; + } + } + + return nil; + } + void addFileProviderDomain(const AccountState * const accountState) { if (@available(macOS 11.0, *)) { @@ -600,6 +616,11 @@ QStringList configuredDomainIds() const return domainIdentifierForAccount(accountState->account()); } +void* FileProviderDomainManager::domainForAccount(const AccountState * const accountState) +{ + return d->domainForAccount(accountState); +} + } // namespace Mac } // namespace OCC From 47c09f9ffbe1d6ab3c0987d1100a9ab25278079b Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 29 May 2024 00:01:11 +0800 Subject: [PATCH 24/30] Anticipate null reply in idGetError of FileProviderEditLocallyJob Signed-off-by: Claudio Cambra --- src/gui/macOS/fileprovidereditlocallyjob.cpp | 4 ++-- src/gui/macOS/fileprovidereditlocallyjob.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/macOS/fileprovidereditlocallyjob.cpp b/src/gui/macOS/fileprovidereditlocallyjob.cpp index 10a2e402db3a9..441c44d22a9f0 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.cpp +++ b/src/gui/macOS/fileprovidereditlocallyjob.cpp @@ -67,9 +67,9 @@ void FileProviderEditLocallyJob::showError(const QString &message, Q_EMIT error(message, informativeText); } -void FileProviderEditLocallyJob::idGetError(const QNetworkReply &reply) +void FileProviderEditLocallyJob::idGetError(const QNetworkReply * const reply) { - const auto errorString = reply.errorString(); + const auto errorString = reply == nullptr ? "Unknown error" : reply->errorString(); qCWarning(lcFileProviderEditLocallyJob) << "Could not get file ocId." << errorString; showError(tr("Could not get file id."), errorString); } diff --git a/src/gui/macOS/fileprovidereditlocallyjob.h b/src/gui/macOS/fileprovidereditlocallyjob.h index 3c4c70f8f7fed..763debe108053 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.h +++ b/src/gui/macOS/fileprovidereditlocallyjob.h @@ -44,7 +44,7 @@ public slots: private slots: void showError(const QString &message, const QString &informativeText); - void idGetError(const QNetworkReply &reply); + void idGetError(const QNetworkReply *const reply); void idGetFinished(const QVariantMap &data); private: From 7adce87aea86e4659b09d1c3d6aafe2d48af9b3d Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 29 May 2024 00:02:11 +0800 Subject: [PATCH 25/30] Remove redundant includes in editlocallymanager Signed-off-by: Claudio Cambra --- src/gui/editlocallymanager.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index aa5d781897585..ecb7a3decbb35 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -20,13 +20,8 @@ #include #include "accountmanager.h" -#include "editlocallyverificationjob.h" #include "systray.h" -#ifdef BUILD_FILE_PROVIDER_MODULE -#include "macOS/fileprovidereditlocallyjob.h" -#endif - namespace OCC { Q_LOGGING_CATEGORY(lcEditLocallyManager, "nextcloud.gui.editlocallymanager", QtInfoMsg) From 15382807fadafdc6e51b5ec301c05e72800a301c Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 29 May 2024 00:02:44 +0800 Subject: [PATCH 26/30] Implement opening of a file with a given ocId in File Provider files Signed-off-by: Claudio Cambra --- src/gui/CMakeLists.txt | 1 + src/gui/macOS/fileprovidereditlocallyjob.h | 1 + .../macOS/fileprovidereditlocallyjob_mac.mm | 92 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/gui/macOS/fileprovidereditlocallyjob_mac.mm diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 8cf5eb98166e8..f26bfaa8e4fd0 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -292,6 +292,7 @@ IF( APPLE ) macOS/fileproviderdomainsyncstatus_mac.mm macOS/fileprovidereditlocallyjob.h macOS/fileprovidereditlocallyjob.cpp + macOS/fileprovidereditlocallyjob_mac.mm macOS/fileprovideritemmetadata.h macOS/fileprovideritemmetadata.cpp macOS/fileprovideritemmetadata_mac.mm diff --git a/src/gui/macOS/fileprovidereditlocallyjob.h b/src/gui/macOS/fileprovidereditlocallyjob.h index 763debe108053..1f3829999a10f 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.h +++ b/src/gui/macOS/fileprovidereditlocallyjob.h @@ -39,6 +39,7 @@ public slots: signals: void error(const QString &message, const QString &informativeText); + void ocIdAcquired(const QString &ocId); void notAvailable(); void finished(); diff --git a/src/gui/macOS/fileprovidereditlocallyjob_mac.mm b/src/gui/macOS/fileprovidereditlocallyjob_mac.mm new file mode 100644 index 0000000000000..f7ebd1d281e9e --- /dev/null +++ b/src/gui/macOS/fileprovidereditlocallyjob_mac.mm @@ -0,0 +1,92 @@ +/* + * Copyright (C) by Claudio Cambra + * + * 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 "fileprovidereditlocallyjob.h" + +#include + +#include "account.h" +#include "accountstate.h" +#include "editlocallymanager.h" +#include "systray.h" + +#include "macOS/fileprovider.h" +#include "macOS/fileproviderdomainmanager.h" + +#import +#import + +namespace OCC::Mac { + +Q_LOGGING_CATEGORY(lcFileProviderEditLocallyMacJob, + "nextcloud.gui.fileprovidereditlocallymac", + QtInfoMsg) + +void FileProviderEditLocallyJob::openFileProviderFile(const QString &ocId) +{ + qCDebug(lcFileProviderEditLocallyMacJob) << "Opening file provider file with OC ID" << ocId; + + const auto nsOcId = ocId.toNSString(); + const auto userId = _accountState->account()->userIdAtHostWithPort(); + const auto ncDomainManager = FileProvider::instance()->domainManager(); + const auto voidDomain = ncDomainManager->domainForAccount(_accountState.data()); + + NSFileProviderDomain *const domain = (NSFileProviderDomain *)voidDomain; + if (domain == nil) { + qCWarning(lcFileProviderEditLocallyMacJob) << "Could not get domain for account:" + << userId; + emit notAvailable(); + } + + NSFileProviderManager *const manager = [NSFileProviderManager managerForDomain:domain]; + if (manager == nil) { + qCWarning(lcFileProviderEditLocallyMacJob) << "Could not get file provider manager" + "for domain of account:" << userId;; + emit notAvailable(); + } + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block NSError *receivedError; + __block NSURL *itemLocalUrl; + [manager getUserVisibleURLForItemIdentifier:nsOcId + completionHandler:^(NSURL *const url, NSError *const error) { + [url retain]; + [error retain]; + itemLocalUrl = url; + receivedError = error; + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + Systray::instance()->destroyEditFileLocallyLoadingDialog(); + + if (receivedError != nil) { + const auto errorMessage = QString::fromNSString(receivedError.localizedDescription); + qCWarning(lcFileProviderEditLocallyMacJob) << "Error getting user visible URL for item" + << ocId << ":" << errorMessage; + emit notAvailable(); + } else if (itemLocalUrl != nil) { + const auto itemLocalPath = QString::fromNSString(itemLocalUrl.path); + qCDebug(lcFileProviderEditLocallyMacJob) << "Got user visible URL for item" + << ocId << ":" << itemLocalPath; + [NSWorkspace.sharedWorkspace openURL:itemLocalUrl]; + emit finished(); + } else { + qCWarning(lcFileProviderEditLocallyMacJob) << "Got nil user visible URL for item" + << ocId; + emit notAvailable(); + } +} + +} // namespace OCC::Mac From e345cd43d88767cf8d30da7dacbb93d7e575aa09 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 29 May 2024 00:03:07 +0800 Subject: [PATCH 27/30] Connect receipt of a file's ocId with file opening procedure in FileProviderEditLocallyJob Signed-off-by: Claudio Cambra --- src/gui/macOS/fileprovidereditlocallyjob.cpp | 9 +++++++++ src/gui/macOS/fileprovidereditlocallyjob.h | 1 + 2 files changed, 10 insertions(+) diff --git a/src/gui/macOS/fileprovidereditlocallyjob.cpp b/src/gui/macOS/fileprovidereditlocallyjob.cpp index 441c44d22a9f0..1045c1424090a 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.cpp +++ b/src/gui/macOS/fileprovidereditlocallyjob.cpp @@ -53,10 +53,17 @@ void FileProviderEditLocallyJob::start() const auto filename = relPathSplit.last(); Systray::instance()->createEditFileLocallyLoadingDialog(filename); + qCDebug(lcFileProviderEditLocallyJob) << "Getting file ocId for" << _relPath; + const auto idJob = new PropfindJob(_accountState->account(), _relPath, this); idJob->setProperties({ QByteArrayLiteral("http://owncloud.org/ns:id") }); connect(idJob, &PropfindJob::finishedWithError, this, &FileProviderEditLocallyJob::idGetError); connect(idJob, &PropfindJob::result, this, &FileProviderEditLocallyJob::idGetFinished); + + connect(this, &FileProviderEditLocallyJob::ocIdAcquired, + this, &FileProviderEditLocallyJob::openFileProviderFile); + + idJob->start(); } void FileProviderEditLocallyJob::showError(const QString &message, @@ -83,6 +90,8 @@ void FileProviderEditLocallyJob::idGetFinished(const QVariantMap &data) return; } + qCDebug(lcFileProviderEditLocallyJob) << "Got file ocId for" << _relPath << ocId; + emit ocIdAcquired(ocId); } } // namespace OCC::Mac diff --git a/src/gui/macOS/fileprovidereditlocallyjob.h b/src/gui/macOS/fileprovidereditlocallyjob.h index 1f3829999a10f..ead0f0e44bc73 100644 --- a/src/gui/macOS/fileprovidereditlocallyjob.h +++ b/src/gui/macOS/fileprovidereditlocallyjob.h @@ -47,6 +47,7 @@ private slots: void showError(const QString &message, const QString &informativeText); void idGetError(const QNetworkReply *const reply); void idGetFinished(const QVariantMap &data); + void openFileProviderFile(const QString &ocId); private: AccountStatePtr _accountState; From 52e29fe73e907083834508bff90f8e753703bd2d Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 29 May 2024 00:03:23 +0800 Subject: [PATCH 28/30] Improve logging in editlocallymanager Signed-off-by: Claudio Cambra --- src/gui/editlocallymanager.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index ecb7a3decbb35..785db143209c2 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -120,6 +120,8 @@ void EditLocallyManager::verify(const AccountStatePtr &accountState, }; const auto startEditLocally = [this, accountState, relPath, token, finishedHandler] { finishedHandler(); + qCDebug(lcEditLocallyManager) << "Starting to edit file locally" << relPath + << "with token" << token; #ifdef BUILD_FILE_PROVIDER_MODULE editLocallyFileProvider(accountState, relPath, token); #else @@ -147,6 +149,9 @@ void EditLocallyManager::editLocallyFileProvider(const AccountStatePtr &accountS return; } + qCDebug(lcEditLocallyManager) << "Starting to edit file locally with file provider" << relPath + << "with token" << token; + const auto removeJob = [this, token] { _editLocallyFpJobs.remove(token); }; const auto tryStandardJob = [this, accountState, relPath, token, removeJob] { removeJob(); @@ -172,6 +177,9 @@ void EditLocallyManager::editLocally(const AccountStatePtr &accountState, return; } + qCDebug(lcEditLocallyManager) << "Starting to edit file locally" << relPath + << "with token" << token; + const auto removeJob = [this, token] { _editLocallyJobs.remove(token); }; const EditLocallyJobPtr job(new EditLocallyJob(accountState, relPath)); // We need to make sure the job sticks around until it is finished From 8147e831c8361c892a5b80525654e68e5febb6ef Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 29 May 2024 00:03:38 +0800 Subject: [PATCH 29/30] Start the FileProviderEditLocallyJob in editlocallymanager Signed-off-by: Claudio Cambra --- src/gui/editlocallymanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index 785db143209c2..a67daf9ae55ea 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -166,6 +166,8 @@ void EditLocallyManager::editLocallyFileProvider(const AccountStatePtr &accountS connect(job.data(), &Mac::FileProviderEditLocallyJob::error, this, removeJob); connect(job.data(), &Mac::FileProviderEditLocallyJob::notAvailable, this, tryStandardJob); connect(job.data(), &Mac::FileProviderEditLocallyJob::finished, this, removeJob); + + job->start(); } #endif From 27bc838c9c0c5e78d43ed4aee7a55e994674cd63 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 29 May 2024 00:37:23 +0800 Subject: [PATCH 30/30] Fix crash upon removing edit locally file provider jobs in edit locally manager Signed-off-by: Claudio Cambra --- src/gui/editlocallymanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/editlocallymanager.cpp b/src/gui/editlocallymanager.cpp index a67daf9ae55ea..59c25e830b03b 100644 --- a/src/gui/editlocallymanager.cpp +++ b/src/gui/editlocallymanager.cpp @@ -152,7 +152,7 @@ void EditLocallyManager::editLocallyFileProvider(const AccountStatePtr &accountS qCDebug(lcEditLocallyManager) << "Starting to edit file locally with file provider" << relPath << "with token" << token; - const auto removeJob = [this, token] { _editLocallyFpJobs.remove(token); }; + const auto removeJob = [&] { _editLocallyFpJobs.remove(token); }; const auto tryStandardJob = [this, accountState, relPath, token, removeJob] { removeJob(); editLocally(accountState, relPath, token);