Began implementing more detailled states for server API requests

This commit is contained in:
Scharel Clemens 2020-01-27 23:05:20 +01:00
parent 7be33026cf
commit 369cf2c1e9
3 changed files with 115 additions and 104 deletions

View file

@ -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) {

View file

@ -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 {

View file

@ -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<QNetworkReply*> 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<QNetworkReply*> m_loginReplies;
QVector<QNetworkReply*> 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;