diff options
author | Hannah von Reth <hannah.vonreth@owncloud.com> | 2021-02-19 18:00:32 +0300 |
---|---|---|
committer | Matthieu Gallien <matthieu.gallien@nextcloud.com> | 2021-08-13 00:17:13 +0300 |
commit | 8adf24112370fef10b49c274e59d89cf2ffbb7d6 (patch) | |
tree | e00d04663f07125810a80c5902a2dc21f85cf0d6 | |
parent | 899010b37c4c40be90e567c965dbc7afdf439a68 (diff) |
Ensure the socket listener still existsfeature/commandUploadListOfFiles
-rw-r--r-- | src/gui/socketapi/socketapi.cpp | 158 | ||||
-rw-r--r-- | src/gui/socketapi/socketapi.h | 7 | ||||
-rw-r--r-- | src/gui/socketapi/socketapi_p.h | 53 | ||||
-rw-r--r-- | src/gui/socketapi/socketuploadjob.cpp | 49 | ||||
-rw-r--r-- | src/gui/socketapi/socketuploadjob.h | 9 |
5 files changed, 173 insertions, 103 deletions
diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp index 4335c4e10..0669ebc64 100644 --- a/src/gui/socketapi/socketapi.cpp +++ b/src/gui/socketapi/socketapi.cpp @@ -70,8 +70,6 @@ #include <QProcess> #include <QStandardPaths> -#include <QTemporaryFile> -#include <networkjobs.h> #ifdef Q_OS_MAC #include <CoreFoundation/CoreFoundation.h> @@ -197,11 +195,6 @@ Q_LOGGING_CATEGORY(lcSocketApi, "nextcloud.gui.socketapi", QtInfoMsg) Q_LOGGING_CATEGORY(lcPublicLink, "nextcloud.gui.socketapi.publiclink", QtInfoMsg) -void SocketListener::sendMessage(const QString &function, const QJsonObject &obj, bool doWait) const -{ - sendMessage(function + QLatin1Char(':') + QJsonDocument(obj).toJson(QJsonDocument::Compact), doWait); -} - void SocketListener::sendMessage(const QString &message, bool doWait) const { if (!socket) { @@ -225,16 +218,6 @@ void SocketListener::sendMessage(const QString &message, bool doWait) const } } -struct ListenerHasSocketPred -{ - QIODevice *socket; - ListenerHasSocketPred(QIODevice *socket) - : socket(socket) - { - } - bool operator()(const SocketListener &listener) const { return listener.socket == socket; } -}; - SocketApi::SocketApi(QObject *parent) : QObject(parent) { @@ -242,6 +225,7 @@ SocketApi::SocketApi(QObject *parent) qRegisterMetaType<SocketListener *>("SocketListener*"); qRegisterMetaType<QSharedPointer<SocketApiJob>>("QSharedPointer<SocketApiJob>"); + qRegisterMetaType<QSharedPointer<SocketApiJobV2>>("QSharedPointer<SocketApiJobV2>"); if (Utility::isWindows()) { socketPath = QLatin1String(R"(\\.\pipe\)") @@ -312,7 +296,7 @@ SocketApi::~SocketApi() qCDebug(lcSocketApi) << "dtor"; _localServer.close(); // All remaining sockets will be destroyed with _localServer, their parent - ASSERT(_listeners.isEmpty() || _listeners.first().socket->parent() == &_localServer); + ASSERT(_listeners.isEmpty() || _listeners.first()->socket->parent() == &_localServer) _listeners.clear(); } @@ -331,14 +315,13 @@ void SocketApi::slotNewConnection() connect(socket, &QObject::destroyed, this, &SocketApi::slotSocketDestroyed); ASSERT(socket->readAll().isEmpty()); - _listeners.append(SocketListener(socket)); - SocketListener &listener = _listeners.last(); - - foreach (Folder *f, FolderMan::instance()->map()) { + auto listener = QSharedPointer<SocketListener>::create(socket); + _listeners.insert(socket, listener); + for (Folder *f : FolderMan::instance()->map()) { if (f->canSync()) { QString message = buildRegisterPathMessage(removeTrailingSlash(f->path())); - qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener.socket; - listener.sendMessage(message); + qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener->socket; + listener->sendMessage(message); } } } @@ -350,13 +333,13 @@ void SocketApi::onLostConnection() auto socket = qobject_cast<QIODevice *>(sender()); ASSERT(socket); - _listeners.erase(std::remove_if(_listeners.begin(), _listeners.end(), ListenerHasSocketPred(socket)), _listeners.end()); + _listeners.remove(socket); } void SocketApi::slotSocketDestroyed(QObject *obj) { auto *socket = static_cast<QIODevice *>(obj); - _listeners.erase(std::remove_if(_listeners.begin(), _listeners.end(), ListenerHasSocketPred(socket)), _listeners.end()); + _listeners.remove(socket); } void SocketApi::slotReadSocket() @@ -370,27 +353,38 @@ void SocketApi::slotReadSocket() // the readyRead() signals are received - in that case there won't be a // valid listener. We execute the handler anyway, but it will work with // a SocketListener that doesn't send any messages. - static auto noListener = SocketListener(nullptr); - SocketListener *listener = &noListener; - auto listenerIt = std::find_if(_listeners.begin(), _listeners.end(), ListenerHasSocketPred(socket)); - if (listenerIt != _listeners.end()) { - listener = &*listenerIt; - } - + static auto invalidListener = QSharedPointer<SocketListener>::create(nullptr); + const auto listener = _listeners.value(socket, invalidListener); while (socket->canReadLine()) { // Make sure to normalize the input from the socket to // make sure that the path will match, especially on OS X. const QString line = QString::fromUtf8(socket->readLine().trimmed()).normalized(QString::NormalizationForm_C); qCInfo(lcSocketApi) << "Received SocketAPI message <--" << line << "from" << socket; - const QByteArray command = line.midRef(0, line.indexOf(QLatin1Char(':'))).toUtf8().toUpper().replace("/", "_"); - const QByteArray functionWithArguments = "command_" + command + (command.startsWith("ASYNC_") ? "(QSharedPointer<SocketApiJob>)" : "(QString,SocketListener*)"); - const int indexOfMethod = staticMetaObject.indexOfMethod(functionWithArguments); + const int argPos = line.indexOf(QLatin1Char(':')); + const QByteArray command = line.midRef(0, argPos).toUtf8().toUpper(); + const int indexOfMethod = [&] { + QByteArray functionWithArguments = QByteArrayLiteral("command_"); + if (command.startsWith("ASYNC_")) { + functionWithArguments += command + QByteArrayLiteral("(QSharedPointer<SocketApiJob>)"); + } else if (command.startsWith("V2/")) { + functionWithArguments += QByteArrayLiteral("V2_") + command.mid(3) + QByteArrayLiteral("(QSharedPointer<SocketApiJobV2>)"); + } else { + functionWithArguments += command + QByteArrayLiteral("(QString,SocketListener*)"); + } + Q_ASSERT(staticQtMetaObject.normalizedSignature(functionWithArguments) == functionWithArguments); + const auto out = staticMetaObject.indexOfMethod(functionWithArguments); + if (out == -1) { + listener->sendError(QStringLiteral("Function %1 not found").arg(QString::fromUtf8(functionWithArguments))); + } + ASSERT(out != -1) + return out; + }(); - const auto argument = line.midRef(command.length() + 1); + const auto argument = argPos != -1 ? line.midRef(argPos + 1) : QStringRef(); if (command.startsWith("ASYNC_")) { auto arguments = argument.split('|'); if (arguments.size() != 2) { - listener->sendMessage(QStringLiteral("argument count is wrong")); + listener->sendError(QStringLiteral("argument count is wrong")); return; } @@ -409,15 +403,31 @@ void SocketApi::slotReadSocket() << "with argument:" << argument; socketApiJob->reject(QStringLiteral("command not found")); } + } else if (command.startsWith("V2/")) { + QJsonParseError error; + const auto json = QJsonDocument::fromJson(argument.toUtf8(), &error).object(); + if (error.error != QJsonParseError::NoError) { + qCWarning(lcSocketApi()) << "Invalid json" << argument.toString() << error.errorString(); + listener->sendError(error.errorString()); + return; + } + auto socketApiJob = QSharedPointer<SocketApiJobV2>::create(listener, command, json); + if (indexOfMethod != -1) { + staticMetaObject.method(indexOfMethod) + .invoke(this, Qt::QueuedConnection, + Q_ARG(QSharedPointer<SocketApiJobV2>, socketApiJob)); + } else { + qCWarning(lcSocketApi) << "The command is not supported by this version of the client:" << command + << "with argument:" << argument; + socketApiJob->failure(QStringLiteral("command not found")); + } } else { if (indexOfMethod != -1) { // to ensure that listener is still valid we need to call it with Qt::DirectConnection ASSERT(thread() == QThread::currentThread()) staticMetaObject.method(indexOfMethod) .invoke(this, Qt::DirectConnection, Q_ARG(QString, argument.toString()), - Q_ARG(SocketListener *, listener)); - } else { - qCWarning(lcSocketApi) << "The command is not supported by this version of the client:" << command << "with argument:" << argument; + Q_ARG(SocketListener *, listener.data())); } } } @@ -431,10 +441,10 @@ void SocketApi::slotRegisterPath(const QString &alias) Folder *f = FolderMan::instance()->folder(alias); if (f) { - QString message = buildRegisterPathMessage(removeTrailingSlash(f->path())); - foreach (auto &listener, _listeners) { - qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener.socket; - listener.sendMessage(message); + const QString message = buildRegisterPathMessage(removeTrailingSlash(f->path())); + for (const auto &listener : qAsConst(_listeners)) { + qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener->socket; + listener->sendMessage(message); } } @@ -479,8 +489,8 @@ void SocketApi::slotUpdateFolderView(Folder *f) void SocketApi::broadcastMessage(const QString &msg, bool doWait) { - foreach (auto &listener, _listeners) { - listener.sendMessage(msg, doWait); + for (const auto &listener : qAsConst(_listeners)) { + listener->sendMessage(msg, doWait); } } @@ -530,8 +540,8 @@ void SocketApi::broadcastStatusPushMessage(const QString &systemPath, SyncFileSt QString msg = buildMessage(QLatin1String("STATUS"), systemPath, fileStatus.toSocketAPIString()); Q_ASSERT(!systemPath.endsWith('/')); uint directoryHash = qHash(systemPath.left(systemPath.lastIndexOf('/'))); - foreach (auto &listener, _listeners) { - listener.sendMessageIfDirectoryMonitored(msg, directoryHash); + for (const auto &listener : qAsConst(_listeners)) { + listener->sendMessageIfDirectoryMonitored(msg, directoryHash); } } @@ -935,20 +945,20 @@ void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *) solver.setRemoteVersionFilename(target); } -void SocketApi::command_V2_LIST_ACCOUNTS(const QString &, SocketListener *listener) const +void SocketApi::command_V2_LIST_ACCOUNTS(const QSharedPointer<SocketApiJobV2> &job) const { QJsonArray out; for (auto acc : AccountManager::instance()->accounts()) { // TODO: Use uuid once https://github.com/owncloud/client/pull/8397 is merged out << QJsonObject({ { "name", acc->account()->displayName() }, { "id", acc->account()->id() } }); } - listener->sendMessage(QStringLiteral("V2/ACCOUNTS"), { { "accounts", out } }); + job->success({ { "accounts", out } }); } -void SocketApi::command_V2_UPLOAD_FILES_FROM(const QString &argument, SocketListener *listener) const +void SocketApi::command_V2_UPLOAD_FILES_FROM(const QSharedPointer<SocketApiJobV2> &job) const { - auto job = new SocketUploadJob(listener, argument); - job->start(); + auto uploadJob = new SocketUploadJob(job); + uploadJob->start(); } void SocketApi::emailPrivateLink(const QString &link) @@ -1429,6 +1439,46 @@ QString SocketApi::buildRegisterPathMessage(const QString &path) return message; } +void SocketApiJob::resolve(const QString &response) +{ + _socketListener->sendMessage(QStringLiteral("RESOLVE|") + _jobId + QLatin1Char('|') + response); +} + +void SocketApiJob::resolve(const QJsonObject &response) +{ + resolve(QJsonDocument { response }.toJson()); +} + +void SocketApiJob::reject(const QString &response) +{ + _socketListener->sendMessage(QStringLiteral("REJECT|") + _jobId + QLatin1Char('|') + response); +} + +SocketApiJobV2::SocketApiJobV2(const QSharedPointer<SocketListener> &socketListener, const QByteArray &command, const QJsonObject &arguments) + : _socketListener(socketListener) + , _command(command) + , _jobId(arguments[QStringLiteral("id")].toString()) + , _arguments(arguments[QStringLiteral("arguments")].toObject()) +{ + ASSERT(!_jobId.isEmpty()) +} + +void SocketApiJobV2::success(const QJsonObject &response) const +{ + doFinish(response); +} + +void SocketApiJobV2::failure(const QString &error) const +{ + doFinish({ { QStringLiteral("error"), error } }); +} + +void SocketApiJobV2::doFinish(const QJsonObject &obj) const +{ + _socketListener->sendMessage(_command + QStringLiteral("_RESULT:") + QJsonDocument({ { QStringLiteral("id"), _jobId }, { QStringLiteral("arguments"), obj } }).toJson(QJsonDocument::Compact)); + Q_EMIT finished(); +} + } // namespace OCC #include "socketapi.moc" diff --git a/src/gui/socketapi/socketapi.h b/src/gui/socketapi/socketapi.h index cd7d9ff01..1350607e4 100644 --- a/src/gui/socketapi/socketapi.h +++ b/src/gui/socketapi/socketapi.h @@ -40,6 +40,7 @@ class Folder; class SocketListener; class DirectEditor; class SocketApiJob; +class SocketApiJobV2; Q_DECLARE_LOGGING_CATEGORY(lcSocketApi) @@ -130,8 +131,8 @@ private: #endif // External sync - Q_INVOKABLE void command_V2_LIST_ACCOUNTS(const QString &argument, SocketListener *listener) const; - Q_INVOKABLE void command_V2_UPLOAD_FILES_FROM(const QString &argument, SocketListener *listener) const; + Q_INVOKABLE void command_V2_LIST_ACCOUNTS(const QSharedPointer<SocketApiJobV2> &job) const; + Q_INVOKABLE void command_V2_UPLOAD_FILES_FROM(const QSharedPointer<SocketApiJobV2> &job) const; // Fetch the private link and call targetFun void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun); @@ -168,7 +169,7 @@ private: QString buildRegisterPathMessage(const QString &path); QSet<QString> _registeredAliases; - QList<SocketListener> _listeners; + QMap<QIODevice *, QSharedPointer<SocketListener>> _listeners; SocketApiServer _localServer; }; } diff --git a/src/gui/socketapi/socketapi_p.h b/src/gui/socketapi/socketapi_p.h index 38738c84b..91724d6da 100644 --- a/src/gui/socketapi/socketapi_p.h +++ b/src/gui/socketapi/socketapi_p.h @@ -62,13 +62,20 @@ class SocketListener public: QPointer<QIODevice> socket; - explicit SocketListener(QIODevice *socket) - : socket(socket) + explicit SocketListener(QIODevice *_socket) + : socket(_socket) { } void sendMessage(const QString &message, bool doWait = false) const; - void sendMessage(const QString &function, const QJsonObject &obj, bool doWait = false) const; + void sendWarning(const QString &message, bool doWait = false) const + { + sendMessage(QStringLiteral("WARNING:") + message, doWait); + } + void sendError(const QString &message, bool doWait = false) const + { + sendMessage(QStringLiteral("ERROR:") + message, doWait); + } void sendMessageIfDirectoryMonitored(const QString &message, uint systemDirectoryHash) const { @@ -110,30 +117,48 @@ class SocketApiJob : public QObject { Q_OBJECT public: - SocketApiJob(const QString &jobId, SocketListener *socketListener, const QJsonObject &arguments) + explicit SocketApiJob(const QString &jobId, const QSharedPointer<SocketListener> &socketListener, const QJsonObject &arguments) : _jobId(jobId) , _socketListener(socketListener) , _arguments(arguments) { } - void resolve(const QString &response = QString()) - { - _socketListener->sendMessage(QLatin1String("RESOLVE|") + _jobId + '|' + response); - } + void resolve(const QString &response = QString()); - void resolve(const QJsonObject &response) { resolve(QJsonDocument { response }.toJson()); } + void resolve(const QJsonObject &response); const QJsonObject &arguments() { return _arguments; } - void reject(const QString &response) - { - _socketListener->sendMessage(QLatin1String("REJECT|") + _jobId + '|' + response); - } + void reject(const QString &response); + +protected: + QString _jobId; + QSharedPointer<SocketListener> _socketListener; + QJsonObject _arguments; +}; + +class SocketApiJobV2 : public QObject +{ + Q_OBJECT +public: + explicit SocketApiJobV2(const QSharedPointer<SocketListener> &socketListener, const QByteArray &command, const QJsonObject &arguments); + + void success(const QJsonObject &response) const; + void failure(const QString &error) const; + + const QJsonObject &arguments() const { return _arguments; } + QByteArray command() const { return _command; } + +Q_SIGNALS: + void finished() const; private: + void doFinish(const QJsonObject &obj) const; + + QSharedPointer<SocketListener> _socketListener; + const QByteArray _command; QString _jobId; - SocketListener *_socketListener; QJsonObject _arguments; }; } diff --git a/src/gui/socketapi/socketuploadjob.cpp b/src/gui/socketapi/socketuploadjob.cpp index 8ade844b0..467a46973 100644 --- a/src/gui/socketapi/socketuploadjob.cpp +++ b/src/gui/socketapi/socketuploadjob.cpp @@ -25,24 +25,30 @@ using namespace OCC; -SocketUploadJob::SocketUploadJob(OCC::SocketListener *listener, const QString &argument, QObject *parent) - : QObject(parent) - , _listener(listener) +SocketUploadJob::SocketUploadJob(const QSharedPointer<SocketApiJobV2> &job) + : _apiJob(job) { - const auto args = QJsonDocument::fromJson(argument.toUtf8()).object(); - _localPath = args[QLatin1String("localPath")].toString(); - _remotePath = args[QLatin1String("remotePath")].toString(); - if (!_remotePath.startsWith("/")) { + connect(job.data(), &SocketApiJobV2::finished, this, &SocketUploadJob::deleteLater); + + _localPath = _apiJob->arguments()[QLatin1String("localPath")].toString(); + _remotePath = _apiJob->arguments()[QLatin1String("remotePath")].toString(); + if (!_remotePath.startsWith(QLatin1Char('/'))) { _remotePath = QLatin1Char('/') + _remotePath; } - _pattern = args[QLatin1String("pattern")].toString(); + _pattern = job->arguments()[QLatin1String("pattern")].toString(); // TODO: use uuid - const auto accname = args[QLatin1String("account")][QLatin1String("name")].toString(); + const auto accname = job->arguments()[QLatin1String("account")][QLatin1String("name")].toString(); auto account = AccountManager::instance()->account(accname); - ENFORCE(QFileInfo(_localPath).isAbsolute()) - ENFORCE(_tmp.open()) + if (!QFileInfo(_localPath).isAbsolute()) { + job->failure(QStringLiteral("Local path must be a an absolute path")); + return; + } + if (!_tmp.open()) { + job->failure(QStringLiteral("Failed to create temporary database")); + return; + } _db = new SyncJournalDb(_tmp.fileName(), this); _engine = new SyncEngine(account->account(), _localPath.endsWith(QLatin1Char('/')) ? _localPath : _localPath + QLatin1Char('/'), _remotePath, _db); @@ -54,10 +60,12 @@ SocketUploadJob::SocketUploadJob(OCC::SocketListener *listener, const QString &a connect(_engine, &OCC::SyncEngine::finished, this, [this](bool ok) { if (ok) { - finish({}); + _apiJob->success({ { "localPath", _localPath }, { "syncedFiles", QJsonArray::fromStringList(_syncedFiles) } }); } }); - connect(_engine, &OCC::SyncEngine::syncError, this, &SocketUploadJob::finish); + connect(_engine, &OCC::SyncEngine::syncError, this, [this](const QString &error, ErrorCategory) { + _apiJob->failure(error); + }); } void SocketUploadJob::start() @@ -65,7 +73,7 @@ void SocketUploadJob::start() auto opt = _engine->syncOptions(); opt.setFilePattern(_pattern); if (!opt.fileRegex().isValid()) { - finish(opt.fileRegex().errorString()); + _apiJob->failure(opt.fileRegex().errorString()); return; } _engine->setSyncOptions(opt); @@ -75,19 +83,10 @@ void SocketUploadJob::start() connect(mkdir, &OCC::MkColJob::finishedWithoutError, _engine, &OCC::SyncEngine::startSync); connect(mkdir, &OCC::MkColJob::finishedWithError, this, [this](QNetworkReply *reply) { if (reply->error() == 202) { - finish(QStringLiteral("Destination %1 already exists").arg(_remotePath)); + _apiJob->failure(QStringLiteral("Destination %1 already exists").arg(_remotePath)); } else { - finish(reply->errorString()); + _apiJob->failure(reply->errorString()); } }); mkdir->start(); } - -void SocketUploadJob::finish(const QString &error) -{ - if (!_finished) { - _finished = true; - _listener->sendMessage(QStringLiteral("V2/UPLOAD_FILES_RESULT"), { { "localPath", _localPath }, { "error", error }, { "syncedFiles", QJsonArray::fromStringList(_syncedFiles) } }); - deleteLater(); - } -} diff --git a/src/gui/socketapi/socketuploadjob.h b/src/gui/socketapi/socketuploadjob.h index 477b8bf0c..3b04061d3 100644 --- a/src/gui/socketapi/socketuploadjob.h +++ b/src/gui/socketapi/socketuploadjob.h @@ -28,14 +28,11 @@ class SocketUploadJob : public QObject { Q_OBJECT public: - SocketUploadJob(OCC::SocketListener *listener, const QString &argument, QObject *parent = nullptr); - + SocketUploadJob(const QSharedPointer<SocketApiJobV2> &job); void start(); - void finish(const QString &error); - private: - SocketListener *_listener; + QSharedPointer<SocketApiJobV2> _apiJob; QString _localPath; QString _remotePath; QString _pattern; @@ -43,7 +40,5 @@ private: SyncJournalDb *_db; SyncEngine *_engine; QStringList _syncedFiles; - - bool _finished = false; }; } |