From 369cf2c1e9bbc4ee3601aeaf5f9fe5d613460a01 Mon Sep 17 00:00:00 2001 From: Scharel Clemens Date: Mon, 27 Jan 2020 23:05:20 +0100 Subject: [PATCH] Began implementing more detailled states for server API requests --- qml/pages/LoginPage.qml | 30 ++------ src/notesapi.cpp | 150 +++++++++++++++++++++++----------------- src/notesapi.h | 39 ++++++----- 3 files changed, 115 insertions(+), 104 deletions(-) diff --git a/qml/pages/LoginPage.qml b/qml/pages/LoginPage.qml index a48f441..95601a3 100644 --- a/qml/pages/LoginPage.qml +++ b/qml/pages/LoginPage.qml @@ -6,18 +6,6 @@ Page { id: loginPage property string accountId - /* Possible states for loginStatus: - - newAccount: No information about the account is known yet - - unknownLoggedOut: The user is logged out and the server version is not known - - unknownLoggedIn: The user is logged in but the server version is not known - - legacyLoggedOut: The user is logged out and the server does not accept Login Flow V2 - - legacyPending: The user has provided his credentials but they are not verified yet - - legacyLoggedIn: The user has provided his credentials and they have been accepted by the server - - flowV2LoggedOut: The user is logged out and the server does support Login Flow V2 - - flowV2Pending: The user has initiated the Login Flow V2 workflow - - flowV2LoggedIn: The Login Flow V2 workflow has been successfully finished - */ - property string loginStatus: "unknownLoggedOut" ConfigurationGroup { id: account @@ -40,24 +28,18 @@ Page { Connections { target: notesApi + onStatusStatusChanged: { + + } + onLoginStatusChanged: { + + } onStatusInstalledChanged: { if (notesApi.statusInstalled) { console.log("Nextcloud instance found") } } onStatusVersionChanged: { - if (notesApi.statusVersion.split('.')[0] < 16) { - if (loginStatus === "unknownLoggedOut") - loginStatus = "legacyLoggedOut" - if (loginStatus === "unknownLoggedIn") - loginStatus = "legacyLoggedIn" - } - if (notesApi.statusVersion.split('.')[0] >= 16) { - if (loginStatus === "unknownLoggedOut") - loginStatus = "flowV2LoggedOut" - if (loginStatus === "unknownLoggedIn") - loginStatus = "flowV2LoggedIn" - } } onLoginUrlChanged: { if (notesApi.loginUrl) { diff --git a/src/notesapi.cpp b/src/notesapi.cpp index 39dcaa0..e5bec28 100644 --- a/src/notesapi.cpp +++ b/src/notesapi.cpp @@ -10,6 +10,11 @@ NotesApi::NotesApi(const QString statusEndpoint, const QString loginEndpoint, co // TODO verify connections (also in destructor) m_loginPollTimer.setInterval(5000); connect(&m_loginPollTimer, SIGNAL(timeout()), this, SLOT(pollLoginUrl())); + m_statusReply = NULL; + m_loginReply = NULL; + m_pollReply = NULL; + m_statusStatus = RequestStatus::StatusNone; + m_loginStatus = RequestStatus::StatusNone; mp_model = new NotesModel(this); mp_modelProxy = new NotesProxyModel(this); mp_modelProxy->setSourceModel(mp_model); @@ -18,9 +23,6 @@ NotesApi::NotesApi(const QString statusEndpoint, const QString loginEndpoint, co mp_modelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); mp_modelProxy->setFilterRole(NotesModel::ContentRole); connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(verifyUrl(QUrl))); - connect(this, SIGNAL(statusBusyChanged(bool)), this, SIGNAL(busyChanged(bool))); - connect(this, SIGNAL(loginBusyChanged(bool)), this, SIGNAL(busyChanged(bool))); - connect(this, SIGNAL(notesBusyChanged(bool)), this, SIGNAL(busyChanged(bool))); connect(&m_manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this, SLOT(requireAuthentication(QNetworkReply*,QAuthenticator*))); connect(&m_manager, SIGNAL(networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility)), this, SLOT(onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility))); connect(&m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); @@ -154,48 +156,56 @@ void NotesApi::setDataFile(QString dataFile) { } } -void NotesApi::getStatus() { - QUrl url = apiEndpointUrl(m_statusEndpoint); - if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) { - qDebug() << "POST" << url.toDisplayString(); - m_request.setUrl(url); - m_statusReplies << m_manager.post(m_request, QByteArray()); - emit statusBusyChanged(true); +bool NotesApi::getStatus() { + if (m_statusStatus != RequestStatus::StatusBusy) { + m_statusStatus = RequestStatus::StatusBusy; + emit statusStatusChanged(m_statusStatus); + QUrl url = apiEndpointUrl(m_statusEndpoint); + if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) { + qDebug() << "POST" << url.toDisplayString(); + m_request.setUrl(url); + m_statusReply = m_manager.post(m_request, QByteArray()); + return true; + } } + return false; } -void NotesApi::initiateFlowV2Login() { - QUrl url = apiEndpointUrl(m_loginEndpoint); - if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) { - qDebug() << "POST" << url.toDisplayString(); - m_request.setUrl(url); - m_loginReplies << m_manager.post(m_request, QByteArray()); - m_loginPollTimer.start(); - emit loginBusyChanged(true); +bool NotesApi::initiateFlowV2Login() { + if (m_loginStatus == RequestStatus::StatusInitiated || m_loginStatus == RequestStatus::StatusBusy) { + abortFlowV2Login(); } + if (m_loginStatus != RequestStatus::StatusInitiated) { + m_loginStatus = RequestStatus::StatusInitiated; + emit loginStatusChanged(m_loginStatus); + QUrl url = apiEndpointUrl(m_loginEndpoint); + if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) { + qDebug() << "POST" << url.toDisplayString(); + m_request.setUrl(url); + m_loginReply = m_manager.post(m_request, QByteArray()); + return true; + } + } + return false; } void NotesApi::abortFlowV2Login() { - // TODO crashes! m_loginPollTimer.stop(); m_loginUrl.clear(); emit loginUrlChanged(m_loginUrl); m_pollUrl.clear(); m_pollToken.clear(); - for (int i = 0; i < m_loginReplies.size(); ++i) { - m_loginReplies[i]->abort(); - } - for (int i = 0; i < m_pollReplies.size(); ++i) { - m_pollReplies[i]->abort(); - } + if (m_loginReply->isRunning()) + m_loginReply->abort(); + if (m_pollReply->isRunning()) + m_pollReply->abort(); } void NotesApi::pollLoginUrl() { if (m_pollUrl.isValid() && !m_pollUrl.scheme().isEmpty() && !m_pollUrl.host().isEmpty() && !m_pollToken.isEmpty()) { qDebug() << "POST" << m_pollUrl.toDisplayString(); m_request.setUrl(m_pollUrl); - m_pollReplies << m_manager.post(m_request, QByteArray("token=").append(m_pollToken)); - emit loginBusyChanged(true); + m_pollReply = m_manager.post(m_request, QByteArray("token=").append(m_pollToken)); } } @@ -207,7 +217,7 @@ void NotesApi::getAllNotes(QStringList excludeFields) { qDebug() << "GET" << url.toDisplayString(); m_authenticatedRequest.setUrl(url); m_notesReplies << m_manager.get(m_authenticatedRequest); - emit notesBusyChanged(true); + emit busyChanged(true); } } @@ -219,7 +229,7 @@ void NotesApi::getNote(double noteId, QStringList excludeFields) { qDebug() << "GET" << url.toDisplayString(); m_authenticatedRequest.setUrl(url); m_notesReplies << m_manager.get(m_authenticatedRequest); - emit notesBusyChanged(true); + emit busyChanged(true); } } @@ -234,7 +244,7 @@ void NotesApi::createNote(QVariantMap fields) { qDebug() << "POST" << url.toDisplayString(); m_authenticatedRequest.setUrl(url); m_notesReplies << m_manager.post(m_authenticatedRequest, note.toJsonDocument().toJson()); - emit notesBusyChanged(true); + emit busyChanged(true); } } @@ -249,7 +259,7 @@ void NotesApi::updateNote(double noteId, QVariantMap fields) { qDebug() << "PUT" << url.toDisplayString(); m_authenticatedRequest.setUrl(url); m_notesReplies << m_manager.put(m_authenticatedRequest, note.toJsonDocument().toJson()); - emit notesBusyChanged(true); + emit busyChanged(true); } } @@ -263,7 +273,7 @@ void NotesApi::deleteNote(double noteId) { qDebug() << "DELETE" << url.toDisplayString(); m_authenticatedRequest.setUrl(url); m_notesReplies << m_manager.deleteResource(m_authenticatedRequest); - emit notesBusyChanged(true); + emit busyChanged(true); } mp_model->removeNote(noteId); } @@ -308,27 +318,24 @@ void NotesApi::replyFinished(QNetworkReply *reply) { qDebug() << "Notes reply"; }*/ - if (m_loginReplies.contains(reply)) { + if (reply == m_loginReply) { qDebug() << "Login reply"; if (json.isObject()) updateLoginFlow(json.object()); - //m_loginReplies.removeAll(reply); - emit loginBusyChanged(loginBusy()); + m_loginReply = NULL; } - else if (m_pollReplies.contains(reply)) { + else if (reply == m_pollReply) { qDebug() << "Poll reply, finished"; if (json.isObject()) updateLoginCredentials(json.object()); - //m_pollReplies.removeAll(reply); + m_pollReply = NULL; abortFlowV2Login(); - emit loginBusyChanged(loginBusy()); } - else if (m_statusReplies.contains(reply)) { + else if (reply == m_statusReply) { qDebug() << "Status reply"; if (json.isObject()) updateStatus(json.object()); - //m_statusReplies.removeAll(reply); - emit statusBusyChanged(statusBusy()); + m_statusReply = NULL; } else if (m_notesReplies.contains(reply)) { qDebug() << "Notes reply"; @@ -338,8 +345,8 @@ void NotesApi::replyFinished(QNetworkReply *reply) { emit lastSyncChanged(m_lastSync); } } - //m_notesReplies.removeAll(reply); - emit notesBusyChanged(notesBusy()); + m_notesReplies.removeOne(reply); + emit busyChanged(busy()); } else { qDebug() << "Unknown reply"; @@ -349,23 +356,22 @@ void NotesApi::replyFinished(QNetworkReply *reply) { else if (reply->error() == QNetworkReply::AuthenticationRequiredError) { emit error(AuthenticationError); } - else { - if (m_pollReplies.contains(reply)) { - qDebug() << "Poll reply"; - //qDebug() << "Polling not finished yet" << m_pollUrl; - } - else if (m_statusReplies.contains(reply)) { - updateStatus(QJsonObject()); - qDebug() << "Could not retreive status"; - } - else { - emit error(CommunicationError); - } + else if (reply->error() == QNetworkReply::ContentNotFoundError && reply == m_pollReply) { + qDebug() << "Polling not finished yet" << reply->url().toDisplayString(); + } + else { + if (reply == m_loginReply) + m_loginReply = NULL; + else if (reply == m_pollReply) + m_pollReply = NULL; + else if (reply == m_statusReply) + m_statusReply = NULL; + else if (m_notesReplies.contains(reply)) { + m_notesReplies.removeOne(reply); + emit busyChanged(busy()); + } + emit error(CommunicationError); } - m_loginReplies.removeAll(reply); - m_pollReplies.removeAll(reply); - m_statusReplies.removeAll(reply); - m_notesReplies.removeAll(reply); reply->deleteLater(); } @@ -428,9 +434,11 @@ void NotesApi::updateStatus(const QJsonObject &status) { m_status_extendedSupport = status.value("extendedSupport").toBool(); emit statusExtendedSupportChanged(m_status_extendedSupport); } + m_statusStatus = RequestStatus::StatusFinished; + emit statusStatusChanged(m_statusStatus); } -void NotesApi::updateLoginFlow(const QJsonObject &login) { +bool NotesApi::updateLoginFlow(const QJsonObject &login) { QUrl url; QString token; if (!login.isEmpty()) { @@ -448,15 +456,24 @@ void NotesApi::updateLoginFlow(const QJsonObject &login) { qDebug() << "Invalid Poll URL:" << url; } } + url = login.value("login").toString(); - if (m_loginUrl != url && url.isValid()) { + if (m_loginUrl != url && url.isValid() && m_pollUrl.isValid() && !m_pollToken.isEmpty()) { m_loginUrl = url; + m_loginStatus = RequestStatus::StatusBusy; emit loginUrlChanged(m_loginUrl); + emit loginStatusChanged(m_loginStatus); + m_loginPollTimer.start(); + return true; + } + else { + abortFlowV2Login(); } } + return false; } -void NotesApi::updateLoginCredentials(const QJsonObject &credentials) { +bool NotesApi::updateLoginCredentials(const QJsonObject &credentials) { QString serverAddr; QString loginName; QString appPassword; @@ -471,7 +488,14 @@ void NotesApi::updateLoginCredentials(const QJsonObject &credentials) { if (!appPassword.isEmpty() && appPassword != password()) setPassword(appPassword); } - qDebug() << "Login successfull for user" << loginName << "on" << serverAddr; + if (!serverAddr.isEmpty() && !loginName.isEmpty() && !appPassword.isEmpty()) { + qDebug() << "Login successfull for user" << loginName << "on" << serverAddr; + m_loginStatus = RequestStatus::StatusFinished; + emit loginStatusChanged(m_loginStatus); + return true; + } + qDebug() << "Login failed for user" << loginName << "on" << serverAddr; + return false; } const QString NotesApi::errorMessage(int error) const { diff --git a/src/notesapi.h b/src/notesapi.h index 9ec7d01..9b63401 100644 --- a/src/notesapi.h +++ b/src/notesapi.h @@ -73,15 +73,14 @@ public: Q_PROPERTY(QDateTime lastSync READ lastSync NOTIFY lastSyncChanged) QDateTime lastSync() const { return m_lastSync; } - Q_PROPERTY(bool statusBusy READ statusBusy NOTIFY statusBusyChanged) - bool statusBusy() const { return !m_statusReplies.empty(); } - Q_PROPERTY(bool loginBusy READ loginBusy NOTIFY loginBusyChanged) - bool loginBusy() const { return !m_loginReplies.empty() || !m_pollReplies.empty() || m_loginPollTimer.isActive(); } - Q_PROPERTY(bool notesBusy READ notesBusy NOTIFY notesBusyChanged) - bool notesBusy() const { return !m_notesReplies.empty(); } Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) - bool busy() const { return statusBusy() | loginBusy() | notesBusy(); } + bool busy() const { return !m_notesReplies.empty();; } + enum RequestStatus { StatusNone, StatusInitiated, StatusBusy, StatusFinished, StatusError }; + Q_ENUM(RequestStatus) + + Q_PROPERTY(RequestStatus statusStatus READ statusStatus NOTIFY statusStatusChanged) + RequestStatus statusStatus() const { return m_statusStatus; } Q_PROPERTY(bool statusInstalled READ statusInstalled NOTIFY statusInstalledChanged) bool statusInstalled() const { return m_status_installed; } Q_PROPERTY(bool statusMaintenance READ statusMaintenance NOTIFY statusMaintenanceChanged) @@ -98,11 +97,14 @@ public: QString statusProductName() const { return m_status_productname; } Q_PROPERTY(bool statusExtendedSupport READ statusExtendedSupport NOTIFY statusExtendedSupportChanged) bool statusExtendedSupport() const { return m_status_extendedSupport; } + + Q_PROPERTY(RequestStatus loginStatus READ loginStatus NOTIFY loginStatusChanged) + RequestStatus loginStatus() const { return m_loginStatus; } Q_PROPERTY(QUrl loginUrl READ loginUrl NOTIFY loginUrlChanged) QUrl loginUrl() const { return m_loginUrl; } - Q_INVOKABLE void getStatus(); - Q_INVOKABLE void initiateFlowV2Login(); + Q_INVOKABLE bool getStatus(); + Q_INVOKABLE bool initiateFlowV2Login(); Q_INVOKABLE void abortFlowV2Login(); Q_INVOKABLE void getAllNotes(QStringList excludeFields = QStringList()); Q_INVOKABLE void getNote(double noteId, QStringList excludeFields = QStringList()); @@ -136,10 +138,9 @@ signals: void dataFileChanged(QString dataFile); void networkAccessibleChanged(bool accessible); void lastSyncChanged(QDateTime lastSync); - void statusBusyChanged(bool busy); - void loginBusyChanged(bool busy); - void notesBusyChanged(bool busy); void busyChanged(bool busy); + + void statusStatusChanged(RequestStatus status); void statusInstalledChanged(bool installed); void statusMaintenanceChanged(bool maintenance); void statusNeedsDbUpgradeChanged(bool needsDbUpgrade); @@ -148,6 +149,8 @@ signals: void statusEditionChanged(QString edition); void statusProductNameChanged(QString productName); void statusExtendedSupportChanged(bool extendedSupport); + + void loginStatusChanged(RequestStatus status); void loginUrlChanged(QUrl url); void error(int error); @@ -174,8 +177,9 @@ private: // Nextcloud status.php const QString m_statusEndpoint; - QVector m_statusReplies; + QNetworkReply* m_statusReply; void updateStatus(const QJsonObject &status); + RequestStatus m_statusStatus; bool m_status_installed; bool m_status_maintenance; bool m_status_needsDbUpgrade; @@ -187,10 +191,11 @@ private: // Nextcloud Login Flow v2 - https://docs.nextcloud.com/server/18/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2 const QString m_loginEndpoint; - QVector m_loginReplies; - QVector m_pollReplies; - void updateLoginFlow(const QJsonObject &login); - void updateLoginCredentials(const QJsonObject &credentials); + QNetworkReply* m_loginReply; + QNetworkReply* m_pollReply; + bool updateLoginFlow(const QJsonObject &login); + bool updateLoginCredentials(const QJsonObject &credentials); + RequestStatus m_loginStatus; QTimer m_loginPollTimer; QUrl m_loginUrl; QUrl m_pollUrl;